Skip to content

Commit c56e458

Browse files
authored
expose underlying schema type of statement (#333)
1 parent c175a8a commit c56e458

File tree

2 files changed

+65
-8
lines changed

2 files changed

+65
-8
lines changed

crates/duckdb/src/column.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::str;
22

3+
use arrow::datatypes::DataType;
4+
35
use crate::{Error, Result, Statement};
46

57
/// Information about a column of a DuckDB query.
@@ -29,6 +31,9 @@ impl Statement<'_> {
2931
/// If associated DB schema can be altered concurrently, you should make
3032
/// sure that current statement has already been stepped once before
3133
/// calling this method.
34+
///
35+
/// # Caveats
36+
/// Panics if the query has not been [`execute`](Statement::execute)d yet.
3237
pub fn column_names(&self) -> Vec<String> {
3338
self.stmt
3439
.schema()
@@ -87,7 +92,9 @@ impl Statement<'_> {
8792
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
8893
/// column range for this row.
8994
///
90-
/// Panics when column name is not valid UTF-8.
95+
/// # Caveats
96+
/// Panics if the query has not been [`execute`](Statement::execute)d yet
97+
/// or when column name is not valid UTF-8.
9198
#[inline]
9299
pub fn column_name(&self, col: usize) -> Result<&String> {
93100
self.stmt.column_name(col).ok_or(Error::InvalidColumnIndex(col))
@@ -106,6 +113,9 @@ impl Statement<'_> {
106113
///
107114
/// Will return an `Error::InvalidColumnName` when there is no column with
108115
/// the specified `name`.
116+
///
117+
/// # Caveats
118+
/// Panics if the query has not been [`execute`](Statement::execute)d yet.
109119
#[inline]
110120
pub fn column_index(&self, name: &str) -> Result<usize> {
111121
let n = self.column_count();
@@ -119,6 +129,15 @@ impl Statement<'_> {
119129
Err(Error::InvalidColumnName(String::from(name)))
120130
}
121131

132+
/// Returns the declared data type of the column.
133+
///
134+
/// # Caveats
135+
/// Panics if the query has not been [`execute`](Statement::execute)d yet.
136+
#[inline]
137+
pub fn column_type(&self, idx: usize) -> DataType {
138+
self.stmt.column_type(idx)
139+
}
140+
122141
/// Returns a slice describing the columns of the result of the query.
123142
///
124143
/// If associated DB schema can be altered concurrently, you should make

crates/duckdb/src/statement.rs

+45-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{convert, ffi::c_void, fmt, mem, os::raw::c_char, ptr, str};
22

3-
use arrow::{array::StructArray, datatypes::DataType};
3+
use arrow::{array::StructArray, datatypes::SchemaRef};
44

55
use super::{ffi, AndThenRows, Connection, Error, MappedRows, Params, RawStatement, Result, Row, Rows, ValueRef};
66
#[cfg(feature = "polars")]
@@ -452,6 +452,15 @@ impl Statement<'_> {
452452
Rows::new(self)
453453
}
454454

455+
/// Returns the underlying schema of the prepared statement.
456+
///
457+
/// # Caveats
458+
/// Panics if the query has not been [`execute`](Statement::execute)d yet.
459+
#[inline]
460+
pub fn schema(&self) -> SchemaRef {
461+
self.stmt.schema()
462+
}
463+
455464
// generic because many of these branches can constant fold away.
456465
fn bind_parameter<P: ?Sized + ToSql>(&self, param: &P, col: usize) -> Result<()> {
457466
let value = param.to_sql()?;
@@ -542,12 +551,6 @@ impl Statement<'_> {
542551
pub(super) fn new(conn: &Connection, stmt: RawStatement) -> Statement<'_> {
543552
Statement { conn, stmt }
544553
}
545-
546-
/// column_type
547-
#[inline]
548-
pub fn column_type(&self, idx: usize) -> DataType {
549-
self.stmt.column_type(idx)
550-
}
551554
}
552555

553556
#[cfg(test)]
@@ -806,6 +809,41 @@ mod test {
806809
Ok(())
807810
}
808811

812+
#[test]
813+
fn test_get_schema_of_executed_result() -> Result<()> {
814+
use arrow::datatypes::{DataType, Field, Schema};
815+
let db = Connection::open_in_memory()?;
816+
let sql = "BEGIN;
817+
CREATE TABLE foo(x STRING, y INTEGER);
818+
INSERT INTO foo VALUES('hello', 3);
819+
END;";
820+
db.execute_batch(sql)?;
821+
let mut stmt = db.prepare("SELECT x, y FROM foo")?;
822+
let _ = stmt.execute([]);
823+
let schema = stmt.schema();
824+
assert_eq!(
825+
*schema,
826+
Schema::new(vec![
827+
Field::new("x", DataType::Utf8, true),
828+
Field::new("y", DataType::Int32, true)
829+
])
830+
);
831+
Ok(())
832+
}
833+
834+
#[test]
835+
#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
836+
fn test_unexecuted_schema_panics() {
837+
let db = Connection::open_in_memory().unwrap();
838+
let sql = "BEGIN;
839+
CREATE TABLE foo(x STRING, y INTEGER);
840+
INSERT INTO foo VALUES('hello', 3);
841+
END;";
842+
db.execute_batch(sql).unwrap();
843+
let stmt = db.prepare("SELECT x, y FROM foo").unwrap();
844+
let _ = stmt.schema();
845+
}
846+
809847
#[test]
810848
fn test_query_by_column_name_ignore_case() -> Result<()> {
811849
let db = Connection::open_in_memory()?;

0 commit comments

Comments
 (0)