mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-04-08 19:25:50 +00:00
feat: expose column information on Row
- add database-specific Column types: MySqlColumn, PgColumn, etc. - add Row::columns() -> &[DB::Column] - add Row::column(I) and Row::try_column(I)
This commit is contained in:
@@ -70,11 +70,13 @@ impl SqliteArguments<'_> {
|
||||
};
|
||||
|
||||
if n > self.values.len() {
|
||||
return Err(err_protocol!(
|
||||
"wrong number of parameters, parameter ?{} requested but have only {}",
|
||||
n,
|
||||
self.values.len()
|
||||
));
|
||||
// SQLite treats unbound variables as NULL
|
||||
// we reproduce this here
|
||||
// If you are reading this and think this should be an error, open an issue and we can
|
||||
// discuss configuring this somehow
|
||||
// Note that the query macros have a different way of enforcing
|
||||
// argument arity
|
||||
break;
|
||||
}
|
||||
|
||||
self.values[n - 1].bind(handle, param_i)?;
|
||||
|
||||
29
sqlx-core/src/sqlite/column.rs
Normal file
29
sqlx-core/src/sqlite/column.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::column::Column;
|
||||
use crate::ext::ustr::UStr;
|
||||
use crate::sqlite::{Sqlite, SqliteTypeInfo};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct SqliteColumn {
|
||||
pub(crate) name: UStr,
|
||||
pub(crate) ordinal: usize,
|
||||
pub(crate) type_info: SqliteTypeInfo,
|
||||
}
|
||||
|
||||
impl crate::column::private_column::Sealed for SqliteColumn {}
|
||||
|
||||
impl Column for SqliteColumn {
|
||||
type Database = Sqlite;
|
||||
|
||||
fn ordinal(&self) -> usize {
|
||||
self.ordinal
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&*self.name
|
||||
}
|
||||
|
||||
fn type_info(&self) -> &SqliteTypeInfo {
|
||||
&self.type_info
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,31 @@
|
||||
use crate::describe::{Column, Describe};
|
||||
use crate::error::Error;
|
||||
use crate::sqlite::connection::explain::explain;
|
||||
use crate::sqlite::statement::SqliteStatement;
|
||||
use crate::sqlite::type_info::DataType;
|
||||
use crate::sqlite::{Sqlite, SqliteConnection, SqliteTypeInfo};
|
||||
use crate::sqlite::{Sqlite, SqliteColumn, SqliteConnection};
|
||||
use crate::statement::StatementInfo;
|
||||
use either::Either;
|
||||
use futures_core::future::BoxFuture;
|
||||
use std::convert::identity;
|
||||
|
||||
pub(super) async fn describe(
|
||||
conn: &mut SqliteConnection,
|
||||
query: &str,
|
||||
) -> Result<Describe<Sqlite>, Error> {
|
||||
describe_with(conn, query, vec![]).await
|
||||
}
|
||||
|
||||
pub(super) fn describe_with<'c: 'e, 'q: 'e, 'e>(
|
||||
pub(super) fn describe<'c: 'e, 'q: 'e, 'e>(
|
||||
conn: &'c mut SqliteConnection,
|
||||
query: &'q str,
|
||||
fallback: Vec<SqliteTypeInfo>,
|
||||
) -> BoxFuture<'e, Result<Describe<Sqlite>, Error>> {
|
||||
) -> BoxFuture<'e, Result<StatementInfo<Sqlite>, Error>> {
|
||||
Box::pin(async move {
|
||||
// describing a statement from SQLite can be involved
|
||||
// each SQLx statement is comprised of multiple SQL statements
|
||||
|
||||
let SqliteConnection {
|
||||
ref mut handle,
|
||||
ref worker,
|
||||
..
|
||||
} = conn;
|
||||
|
||||
let statement = SqliteStatement::prepare(handle, query, false);
|
||||
let statement = SqliteStatement::prepare(&mut conn.handle, query, false);
|
||||
|
||||
let mut columns = Vec::new();
|
||||
let mut nullable = Vec::new();
|
||||
let mut num_params = 0;
|
||||
|
||||
let mut statement = statement?;
|
||||
|
||||
// we start by finding the first statement that *can* return results
|
||||
while let Some((statement, _)) = statement.execute()? {
|
||||
while let Some((statement, ..)) = statement.execute()? {
|
||||
num_params += statement.bind_parameter_count();
|
||||
|
||||
let mut stepped = false;
|
||||
@@ -50,6 +39,20 @@ pub(super) fn describe_with<'c: 'e, 'q: 'e, 'e>(
|
||||
// next we try to use [column_decltype] to inspect the type of each column
|
||||
columns.reserve(num);
|
||||
|
||||
// as a last resort, we explain the original query and attempt to
|
||||
// infer what would the expression types be as a fallback
|
||||
// to [column_decltype]
|
||||
|
||||
// if explain.. fails, ignore the failure and we'll have no fallback
|
||||
let (fallback, fallback_nullable) = match explain(conn, statement.sql()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
log::debug!("describe: explain introspection failed: {}", err);
|
||||
|
||||
(vec![], vec![])
|
||||
}
|
||||
};
|
||||
|
||||
for col in 0..num {
|
||||
let name = statement.column_name(col).to_owned();
|
||||
|
||||
@@ -59,32 +62,18 @@ pub(super) fn describe_with<'c: 'e, 'q: 'e, 'e>(
|
||||
// if that fails, we back up and attempt to step the statement
|
||||
// once *if* its read-only and then use [column_type] as a
|
||||
// fallback to [column_decltype]
|
||||
if !stepped && statement.read_only() && fallback.is_empty() {
|
||||
if !stepped && statement.read_only() {
|
||||
stepped = true;
|
||||
|
||||
worker.execute(statement);
|
||||
worker.wake();
|
||||
conn.worker.execute(statement);
|
||||
conn.worker.wake();
|
||||
|
||||
let _ = worker.step(statement).await?;
|
||||
let _ = conn.worker.step(statement).await;
|
||||
}
|
||||
|
||||
let mut ty = statement.column_type_info(col);
|
||||
|
||||
if ty.0 == DataType::Null {
|
||||
if fallback.is_empty() {
|
||||
// this will _still_ fail if there are no actual rows to return
|
||||
// this happens more often than not for the macros as we tell
|
||||
// users to execute against an empty database
|
||||
|
||||
// as a last resort, we explain the original query and attempt to
|
||||
// infer what would the expression types be as a fallback
|
||||
// to [column_decltype]
|
||||
|
||||
let fallback = explain(conn, statement.sql()).await?;
|
||||
|
||||
return describe_with(conn, query, fallback).await;
|
||||
}
|
||||
|
||||
if let Some(fallback) = fallback.get(col).cloned() {
|
||||
ty = fallback;
|
||||
}
|
||||
@@ -93,21 +82,23 @@ pub(super) fn describe_with<'c: 'e, 'q: 'e, 'e>(
|
||||
ty
|
||||
};
|
||||
|
||||
let not_null = statement.column_not_null(col)?;
|
||||
nullable.push(statement.column_nullable(col)?.or_else(|| {
|
||||
// if we do not *know* if this is nullable, check the EXPLAIN fallback
|
||||
fallback_nullable.get(col).copied().and_then(identity)
|
||||
}));
|
||||
|
||||
columns.push(Column {
|
||||
name,
|
||||
type_info: Some(type_info),
|
||||
not_null,
|
||||
columns.push(SqliteColumn {
|
||||
name: name.into(),
|
||||
type_info,
|
||||
ordinal: col,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// println!("describe ->> {:#?}", columns);
|
||||
|
||||
Ok(Describe {
|
||||
Ok(StatementInfo {
|
||||
columns,
|
||||
params: vec![None; num_params],
|
||||
parameters: Some(Either::Right(num_params)),
|
||||
nullable,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ use futures_util::{FutureExt, TryStreamExt};
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::common::StatementCache;
|
||||
use crate::describe::Describe;
|
||||
use crate::error::Error;
|
||||
use crate::executor::{Execute, Executor};
|
||||
use crate::ext::ustr::UStr;
|
||||
use crate::sqlite::connection::describe::describe;
|
||||
use crate::sqlite::connection::ConnectionHandle;
|
||||
use crate::sqlite::statement::{SqliteStatement, StatementHandle};
|
||||
use crate::sqlite::{Sqlite, SqliteArguments, SqliteConnection, SqliteRow};
|
||||
use crate::sqlite::{Sqlite, SqliteArguments, SqliteColumn, SqliteConnection, SqliteRow};
|
||||
use crate::statement::StatementInfo;
|
||||
|
||||
fn prepare<'a>(
|
||||
conn: &mut ConnectionHandle,
|
||||
@@ -59,16 +59,26 @@ fn bind(
|
||||
|
||||
fn emplace_row_metadata(
|
||||
statement: &StatementHandle,
|
||||
columns: &mut Vec<SqliteColumn>,
|
||||
column_names: &mut HashMap<UStr, usize>,
|
||||
) -> Result<(), Error> {
|
||||
columns.clear();
|
||||
column_names.clear();
|
||||
|
||||
let num = statement.column_count();
|
||||
|
||||
column_names.reserve(num);
|
||||
columns.reserve(num);
|
||||
|
||||
for i in 0..num {
|
||||
let name: UStr = statement.column_name(i).to_owned().into();
|
||||
let type_info = statement.column_type_info(i);
|
||||
|
||||
columns.push(SqliteColumn {
|
||||
ordinal: i,
|
||||
name: name.clone(),
|
||||
type_info,
|
||||
});
|
||||
|
||||
column_names.insert(name, i);
|
||||
}
|
||||
@@ -106,7 +116,9 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
|
||||
// bind arguments, if any, to the statement
|
||||
bind(&mut stmt, arguments)?;
|
||||
|
||||
while let Some((handle, last_row_values)) = stmt.execute()? {
|
||||
while let Some((handle, columns, last_row_values)) = stmt.execute()? {
|
||||
let mut have_metadata = false;
|
||||
|
||||
// tell the worker about the new statement
|
||||
worker.execute(handle);
|
||||
|
||||
@@ -114,17 +126,24 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
|
||||
// the worker parks its thread on async-std when not in use
|
||||
worker.wake();
|
||||
|
||||
emplace_row_metadata(
|
||||
handle,
|
||||
Arc::make_mut(scratch_row_column_names),
|
||||
)?;
|
||||
|
||||
loop {
|
||||
// save the rows from the _current_ position on the statement
|
||||
// and send them to the still-live row object
|
||||
SqliteRow::inflate_if_needed(handle, last_row_values.take());
|
||||
SqliteRow::inflate_if_needed(handle, &*columns, last_row_values.take());
|
||||
|
||||
match worker.step(handle).await? {
|
||||
let s = worker.step(handle).await?;
|
||||
|
||||
if !have_metadata {
|
||||
have_metadata = true;
|
||||
|
||||
emplace_row_metadata(
|
||||
handle,
|
||||
Arc::make_mut(columns),
|
||||
Arc::make_mut(scratch_row_column_names),
|
||||
)?;
|
||||
}
|
||||
|
||||
match s {
|
||||
Either::Left(changes) => {
|
||||
r#yield!(Either::Left(changes));
|
||||
|
||||
@@ -134,6 +153,7 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
|
||||
Either::Right(()) => {
|
||||
let (row, weak_values_ref) = SqliteRow::current(
|
||||
*handle,
|
||||
columns,
|
||||
scratch_row_column_names
|
||||
);
|
||||
|
||||
@@ -172,7 +192,10 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn describe<'e, 'q: 'e, E: 'q>(self, query: E) -> BoxFuture<'e, Result<Describe<Sqlite>, Error>>
|
||||
fn describe<'e, 'q: 'e, E: 'q>(
|
||||
self,
|
||||
query: E,
|
||||
) -> BoxFuture<'e, Result<StatementInfo<Sqlite>, Error>>
|
||||
where
|
||||
'c: 'e,
|
||||
E: Execute<'q, Self::Database>,
|
||||
|
||||
@@ -3,21 +3,34 @@ use crate::query_as::query_as;
|
||||
use crate::sqlite::type_info::DataType;
|
||||
use crate::sqlite::{SqliteConnection, SqliteTypeInfo};
|
||||
use hashbrown::HashMap;
|
||||
use std::str::from_utf8;
|
||||
|
||||
// affinity
|
||||
const SQLITE_AFF_NONE: u8 = 0x40; /* '@' */
|
||||
const SQLITE_AFF_BLOB: u8 = 0x41; /* 'A' */
|
||||
const SQLITE_AFF_TEXT: u8 = 0x42; /* 'B' */
|
||||
const SQLITE_AFF_NUMERIC: u8 = 0x43; /* 'C' */
|
||||
const SQLITE_AFF_INTEGER: u8 = 0x44; /* 'D' */
|
||||
const SQLITE_AFF_REAL: u8 = 0x45; /* 'E' */
|
||||
|
||||
// opcodes
|
||||
const OP_INIT: &str = "Init";
|
||||
const OP_GOTO: &str = "Goto";
|
||||
const OP_COLUMN: &str = "Column";
|
||||
const OP_AGG_STEP: &str = "AggStep";
|
||||
const OP_FUNCTION: &str = "Function";
|
||||
const OP_MOVE: &str = "Move";
|
||||
const OP_COPY: &str = "Copy";
|
||||
const OP_SCOPY: &str = "SCopy";
|
||||
const OP_INT_COPY: &str = "IntCopy";
|
||||
const OP_CAST: &str = "Cast";
|
||||
const OP_STRING8: &str = "String8";
|
||||
const OP_INT64: &str = "Int64";
|
||||
const OP_INTEGER: &str = "Integer";
|
||||
const OP_REAL: &str = "Real";
|
||||
const OP_NOT: &str = "Not";
|
||||
const OP_BLOB: &str = "Blob";
|
||||
const OP_VARIABLE: &str = "Variable";
|
||||
const OP_COUNT: &str = "Count";
|
||||
const OP_ROWID: &str = "Rowid";
|
||||
const OP_OR: &str = "Or";
|
||||
@@ -34,7 +47,19 @@ const OP_REMAINDER: &str = "Remainder";
|
||||
const OP_CONCAT: &str = "Concat";
|
||||
const OP_RESULT_ROW: &str = "ResultRow";
|
||||
|
||||
fn to_type(op: &str) -> DataType {
|
||||
fn affinity_to_type(affinity: u8) -> DataType {
|
||||
match affinity {
|
||||
SQLITE_AFF_BLOB => DataType::Blob,
|
||||
SQLITE_AFF_INTEGER => DataType::Int64,
|
||||
SQLITE_AFF_NUMERIC => DataType::Numeric,
|
||||
SQLITE_AFF_REAL => DataType::Float,
|
||||
SQLITE_AFF_TEXT => DataType::Text,
|
||||
|
||||
SQLITE_AFF_NONE | _ => DataType::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn opcode_to_type(op: &str) -> DataType {
|
||||
match op {
|
||||
OP_REAL => DataType::Float,
|
||||
OP_BLOB => DataType::Blob,
|
||||
@@ -48,11 +73,12 @@ fn to_type(op: &str) -> DataType {
|
||||
pub(super) async fn explain(
|
||||
conn: &mut SqliteConnection,
|
||||
query: &str,
|
||||
) -> Result<Vec<SqliteTypeInfo>, Error> {
|
||||
) -> Result<(Vec<SqliteTypeInfo>, Vec<Option<bool>>), Error> {
|
||||
let mut r = HashMap::<i64, DataType>::with_capacity(6);
|
||||
let mut n = HashMap::<i64, bool>::with_capacity(6);
|
||||
|
||||
let program =
|
||||
query_as::<_, (i64, String, i64, i64, i64, String)>(&*format!("EXPLAIN {}", query))
|
||||
query_as::<_, (i64, String, i64, i64, i64, Vec<u8>)>(&*format!("EXPLAIN {}", query))
|
||||
.fetch_all(&mut *conn)
|
||||
.await?;
|
||||
|
||||
@@ -78,15 +104,46 @@ pub(super) async fn explain(
|
||||
OP_COLUMN => {
|
||||
// r[p3] = <value of column>
|
||||
r.insert(p3, DataType::Null);
|
||||
n.insert(p3, true);
|
||||
}
|
||||
|
||||
OP_VARIABLE => {
|
||||
// r[p2] = <value of variable>
|
||||
r.insert(p2, DataType::Null);
|
||||
n.insert(p3, true);
|
||||
}
|
||||
|
||||
OP_FUNCTION => {
|
||||
// r[p1] = func( _ )
|
||||
match from_utf8(p4).map_err(Error::protocol)? {
|
||||
"last_insert_rowid(0)" => {
|
||||
// last_insert_rowid() -> INTEGER
|
||||
r.insert(p3, DataType::Int64);
|
||||
n.insert(p3, false);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
OP_AGG_STEP => {
|
||||
let p4 = from_utf8(p4).map_err(Error::protocol)?;
|
||||
|
||||
if p4.starts_with("count(") {
|
||||
// count(_) -> INTEGER
|
||||
r.insert(p3, DataType::Int64);
|
||||
n.insert(p3, false);
|
||||
} else if let Some(v) = r.get(&p2).copied() {
|
||||
// r[p3] = AGG ( r[p2] )
|
||||
r.insert(p3, v);
|
||||
n.insert(p3, n.get(&p2).copied().unwrap_or(true));
|
||||
}
|
||||
}
|
||||
|
||||
OP_CAST => {
|
||||
// affinity(r[p1])
|
||||
if let Some(v) = r.get_mut(&p1) {
|
||||
*v = affinity_to_type(p2 as u8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,18 +151,21 @@ pub(super) async fn explain(
|
||||
// r[p2] = r[p1]
|
||||
if let Some(v) = r.get(&p1).copied() {
|
||||
r.insert(p2, v);
|
||||
n.insert(p2, n.get(&p1).copied().unwrap_or(true));
|
||||
}
|
||||
}
|
||||
|
||||
OP_OR | OP_AND | OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_INTEGER | OP_ROWID => {
|
||||
// r[p2] = <value of constant>
|
||||
r.insert(p2, to_type(&opcode));
|
||||
r.insert(p2, opcode_to_type(&opcode));
|
||||
n.insert(p2, false);
|
||||
}
|
||||
|
||||
OP_NOT => {
|
||||
// r[p2] = NOT r[p1]
|
||||
if let Some(a) = r.get(&p1).copied() {
|
||||
r.insert(p2, a);
|
||||
n.insert(p2, n.get(&p1).copied().unwrap_or(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,16 +187,40 @@ pub(super) async fn explain(
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match (n.get(&p1).copied(), n.get(&p2).copied()) {
|
||||
(Some(a), Some(b)) => {
|
||||
n.insert(p3, a || b);
|
||||
}
|
||||
|
||||
(None, Some(b)) => {
|
||||
n.insert(p3, b);
|
||||
}
|
||||
|
||||
(Some(a), None) => {
|
||||
n.insert(p3, a);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
OP_RESULT_ROW => {
|
||||
// output = r[p1 .. p1 + p2]
|
||||
let mut output = Vec::with_capacity(p2 as usize);
|
||||
let mut nullable = Vec::with_capacity(p2 as usize);
|
||||
|
||||
for i in p1..p1 + p2 {
|
||||
output.push(SqliteTypeInfo(r.remove(&i).unwrap_or(DataType::Null)));
|
||||
|
||||
nullable.push(if n.remove(&i).unwrap_or(true) {
|
||||
None
|
||||
} else {
|
||||
Some(false)
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(output);
|
||||
return Ok((output, nullable));
|
||||
}
|
||||
|
||||
_ => {
|
||||
@@ -149,5 +233,5 @@ pub(super) async fn explain(
|
||||
}
|
||||
|
||||
// no rows
|
||||
Ok(vec![])
|
||||
Ok((vec![], vec![]))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::database::{Database, HasArguments, HasStatementCache, HasValueRef};
|
||||
use crate::sqlite::{
|
||||
SqliteArgumentValue, SqliteArguments, SqliteConnection, SqliteRow, SqliteTransactionManager,
|
||||
SqliteTypeInfo, SqliteValue, SqliteValueRef,
|
||||
SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnection, SqliteRow,
|
||||
SqliteTransactionManager, SqliteTypeInfo, SqliteValue, SqliteValueRef,
|
||||
};
|
||||
|
||||
/// Sqlite database driver.
|
||||
@@ -15,6 +15,8 @@ impl Database for Sqlite {
|
||||
|
||||
type Row = SqliteRow;
|
||||
|
||||
type Column = SqliteColumn;
|
||||
|
||||
type TypeInfo = SqliteTypeInfo;
|
||||
|
||||
type Value = SqliteValue;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
mod arguments;
|
||||
mod column;
|
||||
mod connection;
|
||||
mod database;
|
||||
mod error;
|
||||
@@ -18,6 +19,7 @@ pub mod types;
|
||||
mod value;
|
||||
|
||||
pub use arguments::{SqliteArgumentValue, SqliteArguments};
|
||||
pub use column::SqliteColumn;
|
||||
pub use connection::SqliteConnection;
|
||||
pub use database::Sqlite;
|
||||
pub use error::SqliteError;
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::error::Error;
|
||||
use crate::ext::ustr::UStr;
|
||||
use crate::row::{ColumnIndex, Row};
|
||||
use crate::sqlite::statement::StatementHandle;
|
||||
use crate::sqlite::{Sqlite, SqliteValue, SqliteValueRef};
|
||||
use crate::sqlite::{Sqlite, SqliteColumn, SqliteValue, SqliteValueRef};
|
||||
|
||||
/// Implementation of [`Row`] for SQLite.
|
||||
pub struct SqliteRow {
|
||||
@@ -25,6 +25,7 @@ pub struct SqliteRow {
|
||||
pub(crate) values: Arc<AtomicPtr<SqliteValue>>,
|
||||
pub(crate) num_values: usize,
|
||||
|
||||
pub(crate) columns: Arc<Vec<SqliteColumn>>,
|
||||
pub(crate) column_names: Arc<HashMap<UStr, usize>>,
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ impl SqliteRow {
|
||||
// to increment the statement with [step]
|
||||
pub(crate) fn current(
|
||||
statement: StatementHandle,
|
||||
columns: &Arc<Vec<SqliteColumn>>,
|
||||
column_names: &Arc<HashMap<UStr, usize>>,
|
||||
) -> (Self, Weak<AtomicPtr<SqliteValue>>) {
|
||||
let values = Arc::new(AtomicPtr::new(null_mut()));
|
||||
@@ -55,6 +57,7 @@ impl SqliteRow {
|
||||
statement,
|
||||
values,
|
||||
num_values: size,
|
||||
columns: Arc::clone(columns),
|
||||
column_names: Arc::clone(column_names),
|
||||
};
|
||||
|
||||
@@ -63,12 +66,20 @@ impl SqliteRow {
|
||||
|
||||
// inflates this Row into memory as a list of owned, protected SQLite value objects
|
||||
// this is called by the
|
||||
pub(crate) fn inflate(statement: &StatementHandle, values_ref: &AtomicPtr<SqliteValue>) {
|
||||
pub(crate) fn inflate(
|
||||
statement: &StatementHandle,
|
||||
columns: &[SqliteColumn],
|
||||
values_ref: &AtomicPtr<SqliteValue>,
|
||||
) {
|
||||
let size = statement.column_count();
|
||||
let mut values = Vec::with_capacity(size);
|
||||
|
||||
for i in 0..size {
|
||||
values.push(statement.column_value(i));
|
||||
values.push(unsafe {
|
||||
let raw = statement.column_value(i);
|
||||
|
||||
SqliteValue::new(raw, columns[i].type_info.clone())
|
||||
});
|
||||
}
|
||||
|
||||
// decay the array signifier and become just a normal, leaked array
|
||||
@@ -80,10 +91,11 @@ impl SqliteRow {
|
||||
|
||||
pub(crate) fn inflate_if_needed(
|
||||
statement: &StatementHandle,
|
||||
columns: &[SqliteColumn],
|
||||
weak_values_ref: Option<Weak<AtomicPtr<SqliteValue>>>,
|
||||
) {
|
||||
if let Some(v) = weak_values_ref.and_then(|v| v.upgrade()) {
|
||||
SqliteRow::inflate(statement, &v);
|
||||
SqliteRow::inflate(statement, &columns, &v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,8 +103,8 @@ impl SqliteRow {
|
||||
impl Row for SqliteRow {
|
||||
type Database = Sqlite;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.num_values
|
||||
fn columns(&self) -> &[SqliteColumn] {
|
||||
&self.columns
|
||||
}
|
||||
|
||||
fn try_get_raw<I>(&self, index: I) -> Result<SqliteValueRef<'_>, Error>
|
||||
@@ -109,7 +121,11 @@ impl Row for SqliteRow {
|
||||
|
||||
Ok(SqliteValueRef::value(&values[index]))
|
||||
} else {
|
||||
Ok(SqliteValueRef::statement(&self.statement, index))
|
||||
Ok(SqliteValueRef::statement(
|
||||
&self.statement,
|
||||
self.columns[index].type_info.clone(),
|
||||
index,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice::from_raw_parts;
|
||||
use std::str::{from_utf8, from_utf8_unchecked};
|
||||
|
||||
use libsqlite3_sys::{
|
||||
@@ -12,14 +14,12 @@ use libsqlite3_sys::{
|
||||
sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64, sqlite3_column_name,
|
||||
sqlite3_column_origin_name, sqlite3_column_table_name, sqlite3_column_type,
|
||||
sqlite3_column_value, sqlite3_db_handle, sqlite3_sql, sqlite3_stmt, sqlite3_stmt_readonly,
|
||||
sqlite3_table_column_metadata, SQLITE_OK, SQLITE_TRANSIENT, SQLITE_UTF8,
|
||||
sqlite3_table_column_metadata, sqlite3_value, SQLITE_OK, SQLITE_TRANSIENT, SQLITE_UTF8,
|
||||
};
|
||||
|
||||
use crate::error::{BoxDynError, Error};
|
||||
use crate::sqlite::type_info::DataType;
|
||||
use crate::sqlite::{SqliteError, SqliteTypeInfo, SqliteValue};
|
||||
use std::ptr;
|
||||
use std::slice::from_raw_parts;
|
||||
use crate::sqlite::{SqliteError, SqliteTypeInfo};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct StatementHandle(pub(super) NonNull<sqlite3_stmt>);
|
||||
@@ -104,7 +104,7 @@ impl StatementHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn column_not_null(&self, index: usize) -> Result<Option<bool>, Error> {
|
||||
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
|
||||
unsafe {
|
||||
// https://sqlite.org/c3ref/column_database_name.html
|
||||
//
|
||||
@@ -149,7 +149,7 @@ impl StatementHandle {
|
||||
return Err(SqliteError::new(self.db_handle()).into());
|
||||
}
|
||||
|
||||
Ok(Some(not_null != 0))
|
||||
Ok(Some(not_null == 0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ impl StatementHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn column_value(&self, index: usize) -> SqliteValue {
|
||||
unsafe { SqliteValue::new(sqlite3_column_value(self.0.as_ptr(), index as c_int)) }
|
||||
pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value {
|
||||
unsafe { sqlite3_column_value(self.0.as_ptr(), index as c_int) }
|
||||
}
|
||||
|
||||
pub(crate) fn column_blob(&self, index: usize) -> &[u8] {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::i32;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::{null, null_mut, NonNull};
|
||||
use std::sync::{atomic::AtomicPtr, Weak};
|
||||
use std::sync::{atomic::AtomicPtr, Arc, Weak};
|
||||
|
||||
use bytes::{Buf, Bytes};
|
||||
use libsqlite3_sys::{
|
||||
@@ -12,7 +12,7 @@ use smallvec::SmallVec;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::sqlite::connection::ConnectionHandle;
|
||||
use crate::sqlite::{SqliteError, SqliteRow, SqliteValue};
|
||||
use crate::sqlite::{SqliteColumn, SqliteError, SqliteRow, SqliteValue};
|
||||
|
||||
mod handle;
|
||||
mod worker;
|
||||
@@ -35,6 +35,9 @@ pub(crate) struct SqliteStatement {
|
||||
// we use a [`SmallVec`] to optimize for the most likely case of a single statement
|
||||
pub(crate) handles: SmallVec<[StatementHandle; 1]>,
|
||||
|
||||
// weak references to each set of columns
|
||||
pub(crate) columns: SmallVec<[Arc<Vec<SqliteColumn>>; 1]>,
|
||||
|
||||
// weak reference to the previous row from this connection
|
||||
// we use the notice of a successful upgrade of this reference as an indicator that the
|
||||
// row is still around, in which we then inflate the row such that we can let SQLite
|
||||
@@ -122,6 +125,7 @@ impl SqliteStatement {
|
||||
tail: query,
|
||||
handles,
|
||||
index: 0,
|
||||
columns: SmallVec::from([Default::default(); 1]),
|
||||
last_row_values: SmallVec::from([None; 1]),
|
||||
})
|
||||
}
|
||||
@@ -133,7 +137,14 @@ impl SqliteStatement {
|
||||
|
||||
pub(crate) fn execute(
|
||||
&mut self,
|
||||
) -> Result<Option<(&StatementHandle, &mut Option<Weak<AtomicPtr<SqliteValue>>>)>, Error> {
|
||||
) -> Result<
|
||||
Option<(
|
||||
&StatementHandle,
|
||||
&mut Arc<Vec<SqliteColumn>>,
|
||||
&mut Option<Weak<AtomicPtr<SqliteValue>>>,
|
||||
)>,
|
||||
Error,
|
||||
> {
|
||||
while self.handles.len() == self.index {
|
||||
if self.tail.is_empty() {
|
||||
return Ok(None);
|
||||
@@ -143,6 +154,7 @@ impl SqliteStatement {
|
||||
unsafe { prepare(self.connection(), &mut self.tail, self.persistent)? }
|
||||
{
|
||||
self.handles.push(handle);
|
||||
self.columns.push(Default::default());
|
||||
self.last_row_values.push(None);
|
||||
}
|
||||
}
|
||||
@@ -152,6 +164,7 @@ impl SqliteStatement {
|
||||
|
||||
Ok(Some((
|
||||
&self.handles[index],
|
||||
&mut self.columns[index],
|
||||
&mut self.last_row_values[index],
|
||||
)))
|
||||
}
|
||||
@@ -160,7 +173,7 @@ impl SqliteStatement {
|
||||
self.index = 0;
|
||||
|
||||
for (i, handle) in self.handles.iter().enumerate() {
|
||||
SqliteRow::inflate_if_needed(&handle, self.last_row_values[i].take());
|
||||
SqliteRow::inflate_if_needed(&handle, &self.columns[i], self.last_row_values[i].take());
|
||||
|
||||
unsafe {
|
||||
// Reset A Prepared Statement Object
|
||||
@@ -176,7 +189,7 @@ impl SqliteStatement {
|
||||
impl Drop for SqliteStatement {
|
||||
fn drop(&mut self) {
|
||||
for (i, handle) in self.handles.drain(..).enumerate() {
|
||||
SqliteRow::inflate_if_needed(&handle, self.last_row_values[i].take());
|
||||
SqliteRow::inflate_if_needed(&handle, &self.columns[i], self.last_row_values[i].take());
|
||||
|
||||
unsafe {
|
||||
// https://sqlite.org/c3ref/finalize.html
|
||||
|
||||
@@ -11,6 +11,10 @@ impl Type<Sqlite> for [u8] {
|
||||
fn type_info() -> SqliteTypeInfo {
|
||||
SqliteTypeInfo(DataType::Blob)
|
||||
}
|
||||
|
||||
fn compatible(ty: &SqliteTypeInfo) -> bool {
|
||||
matches!(ty.0, DataType::Blob | DataType::Text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q> Encode<'q, Sqlite> for &'q [u8] {
|
||||
@@ -31,6 +35,10 @@ impl Type<Sqlite> for Vec<u8> {
|
||||
fn type_info() -> SqliteTypeInfo {
|
||||
<&[u8] as Type<Sqlite>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &SqliteTypeInfo) -> bool {
|
||||
<&[u8] as Type<Sqlite>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q> Encode<'q, Sqlite> for Vec<u8> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice::from_raw_parts;
|
||||
use std::str::from_utf8;
|
||||
@@ -6,18 +5,19 @@ use std::sync::Arc;
|
||||
|
||||
use libsqlite3_sys::{
|
||||
sqlite3_value, sqlite3_value_blob, sqlite3_value_bytes, sqlite3_value_double,
|
||||
sqlite3_value_dup, sqlite3_value_int, sqlite3_value_int64, sqlite3_value_type, SQLITE_NULL,
|
||||
sqlite3_value_dup, sqlite3_value_free, sqlite3_value_int, sqlite3_value_int64,
|
||||
sqlite3_value_type, SQLITE_NULL,
|
||||
};
|
||||
|
||||
use crate::error::BoxDynError;
|
||||
use crate::sqlite::statement::StatementHandle;
|
||||
use crate::sqlite::type_info::DataType;
|
||||
use crate::sqlite::{Sqlite, SqliteTypeInfo};
|
||||
use crate::value::{Value, ValueRef};
|
||||
|
||||
enum SqliteValueData<'r> {
|
||||
Statement {
|
||||
statement: &'r StatementHandle,
|
||||
type_info: SqliteTypeInfo,
|
||||
index: usize,
|
||||
},
|
||||
|
||||
@@ -31,41 +31,64 @@ impl<'r> SqliteValueRef<'r> {
|
||||
Self(SqliteValueData::Value(value))
|
||||
}
|
||||
|
||||
pub(crate) fn statement(statement: &'r StatementHandle, index: usize) -> Self {
|
||||
Self(SqliteValueData::Statement { statement, index })
|
||||
pub(crate) fn statement(
|
||||
statement: &'r StatementHandle,
|
||||
type_info: SqliteTypeInfo,
|
||||
index: usize,
|
||||
) -> Self {
|
||||
Self(SqliteValueData::Statement {
|
||||
statement,
|
||||
type_info,
|
||||
index,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn int(&self) -> i32 {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_int(index),
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_int(index),
|
||||
|
||||
SqliteValueData::Value(v) => v.int(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn int64(&self) -> i64 {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_int64(index),
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_int64(index),
|
||||
|
||||
SqliteValueData::Value(v) => v.int64(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn double(&self) -> f64 {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_double(index),
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_double(index),
|
||||
|
||||
SqliteValueData::Value(v) => v.double(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn blob(&self) -> &'r [u8] {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_blob(index),
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_blob(index),
|
||||
|
||||
SqliteValueData::Value(v) => v.blob(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn text(&self) -> Result<&'r str, BoxDynError> {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_text(index),
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_text(index),
|
||||
|
||||
SqliteValueData::Value(v) => v.text(),
|
||||
}
|
||||
}
|
||||
@@ -76,32 +99,28 @@ impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
|
||||
|
||||
fn to_owned(&self) -> SqliteValue {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement.column_value(index),
|
||||
SqliteValueData::Statement {
|
||||
statement,
|
||||
index,
|
||||
ref type_info,
|
||||
} => unsafe { SqliteValue::new(statement.column_value(index), type_info.clone()) },
|
||||
|
||||
SqliteValueData::Value(v) => v.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_info(&self) -> Option<Cow<'_, SqliteTypeInfo>> {
|
||||
fn type_info(&self) -> &SqliteTypeInfo {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => statement
|
||||
.column_decltype(index)
|
||||
.or_else(|| {
|
||||
// fall back to the storage class for expressions
|
||||
Some(SqliteTypeInfo(DataType::from_code(
|
||||
statement.column_type(index),
|
||||
)))
|
||||
})
|
||||
.map(Cow::Owned),
|
||||
|
||||
SqliteValueData::Value(v) => v.type_info(),
|
||||
SqliteValueData::Statement { ref type_info, .. } => &type_info,
|
||||
SqliteValueData::Value(v) => &v.type_info,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_null(&self) -> bool {
|
||||
match self.0 {
|
||||
SqliteValueData::Statement { statement, index } => {
|
||||
statement.column_type(index) == SQLITE_NULL
|
||||
}
|
||||
SqliteValueData::Statement {
|
||||
statement, index, ..
|
||||
} => statement.column_type(index) == SQLITE_NULL,
|
||||
|
||||
SqliteValueData::Value(v) => v.is_null(),
|
||||
}
|
||||
@@ -109,43 +128,50 @@ impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SqliteValue(pub(crate) Arc<NonNull<sqlite3_value>>);
|
||||
pub struct SqliteValue {
|
||||
pub(crate) handle: Arc<ValueHandle>,
|
||||
pub(crate) type_info: SqliteTypeInfo,
|
||||
}
|
||||
|
||||
pub(crate) struct ValueHandle(NonNull<sqlite3_value>);
|
||||
|
||||
// SAFE: only protected value objects are stored in SqliteValue
|
||||
unsafe impl Send for SqliteValue {}
|
||||
unsafe impl Sync for SqliteValue {}
|
||||
unsafe impl Send for ValueHandle {}
|
||||
unsafe impl Sync for ValueHandle {}
|
||||
|
||||
impl SqliteValue {
|
||||
pub(crate) unsafe fn new(value: *mut sqlite3_value) -> Self {
|
||||
pub(crate) unsafe fn new(value: *mut sqlite3_value, type_info: SqliteTypeInfo) -> Self {
|
||||
debug_assert!(!value.is_null());
|
||||
Self(Arc::new(NonNull::new_unchecked(sqlite3_value_dup(value))))
|
||||
}
|
||||
|
||||
fn r#type(&self) -> DataType {
|
||||
DataType::from_code(unsafe { sqlite3_value_type(self.0.as_ptr()) })
|
||||
Self {
|
||||
type_info,
|
||||
handle: Arc::new(ValueHandle(NonNull::new_unchecked(sqlite3_value_dup(
|
||||
value,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
|
||||
fn int(&self) -> i32 {
|
||||
unsafe { sqlite3_value_int(self.0.as_ptr()) }
|
||||
unsafe { sqlite3_value_int(self.handle.0.as_ptr()) }
|
||||
}
|
||||
|
||||
fn int64(&self) -> i64 {
|
||||
unsafe { sqlite3_value_int64(self.0.as_ptr()) }
|
||||
unsafe { sqlite3_value_int64(self.handle.0.as_ptr()) }
|
||||
}
|
||||
|
||||
fn double(&self) -> f64 {
|
||||
unsafe { sqlite3_value_double(self.0.as_ptr()) }
|
||||
unsafe { sqlite3_value_double(self.handle.0.as_ptr()) }
|
||||
}
|
||||
|
||||
fn blob(&self) -> &[u8] {
|
||||
let len = unsafe { sqlite3_value_bytes(self.0.as_ptr()) } as usize;
|
||||
let len = unsafe { sqlite3_value_bytes(self.handle.0.as_ptr()) } as usize;
|
||||
|
||||
if len == 0 {
|
||||
// empty blobs are NULL so just return an empty slice
|
||||
return &[];
|
||||
}
|
||||
|
||||
let ptr = unsafe { sqlite3_value_blob(self.0.as_ptr()) } as *const u8;
|
||||
let ptr = unsafe { sqlite3_value_blob(self.handle.0.as_ptr()) } as *const u8;
|
||||
debug_assert!(!ptr.is_null());
|
||||
|
||||
unsafe { from_raw_parts(ptr, len) }
|
||||
@@ -163,11 +189,19 @@ impl Value for SqliteValue {
|
||||
SqliteValueRef::value(self)
|
||||
}
|
||||
|
||||
fn type_info(&self) -> Option<Cow<'_, SqliteTypeInfo>> {
|
||||
Some(Cow::Owned(SqliteTypeInfo(self.r#type())))
|
||||
fn type_info(&self) -> &SqliteTypeInfo {
|
||||
&self.type_info
|
||||
}
|
||||
|
||||
fn is_null(&self) -> bool {
|
||||
unsafe { sqlite3_value_type(self.0.as_ptr()) == SQLITE_NULL }
|
||||
unsafe { sqlite3_value_type(self.handle.0.as_ptr()) == SQLITE_NULL }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ValueHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sqlite3_value_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user