Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions sqlx-sqlite/src/connection/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::type_info::DataType;
use crate::{Sqlite, SqliteColumn};
use sqlx_core::sql_str::SqlStr;
use sqlx_core::Either;
use std::convert::identity;

pub(crate) fn describe(
conn: &mut ConnectionState,
Expand Down Expand Up @@ -78,9 +77,8 @@ pub(crate) fn describe(
ty
};

// check explain
let col_nullable = stmt.handle.column_nullable(col)?;
let exp_nullable = fallback_nullable.get(col).copied().and_then(identity);
let exp_nullable = fallback_nullable.get(col).copied().flatten();

nullable.push(exp_nullable.or(col_nullable));

Expand Down
16 changes: 8 additions & 8 deletions sqlx-sqlite/src/connection/explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,17 +373,17 @@ fn opcode_to_type(op: &str) -> DataType {
fn root_block_columns(
conn: &mut ConnectionState,
) -> Result<HashMap<(i64, i64), IntMap<ColumnType>>, Error> {
let table_block_columns: Vec<(i64, i64, i64, String, bool)> = execute::iter(
let table_block_columns: Vec<(i64, i64, i64, String, bool, i64)> = execute::iter(
conn,
"SELECT s.dbnum, s.rootpage, col.cid as colnum, col.type, col.\"notnull\"
"SELECT s.dbnum, s.rootpage, col.cid as colnum, col.type, col.\"notnull\", col.pk
FROM (
select 1 dbnum, tss.* from temp.sqlite_schema tss
UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss
) s
JOIN pragma_table_info(s.name) AS col
WHERE s.type = 'table'
UNION ALL
SELECT s.dbnum, s.rootpage, idx.seqno as colnum, col.type, col.\"notnull\"
SELECT s.dbnum, s.rootpage, idx.seqno as colnum, col.type, col.\"notnull\", col.pk
FROM (
select 1 dbnum, tss.* from temp.sqlite_schema tss
UNION ALL select 0 dbnum, mss.* from main.sqlite_schema mss
Expand All @@ -400,13 +400,13 @@ fn root_block_columns(
.collect::<Result<Vec<_>, Error>>()?;

let mut row_info: HashMap<(i64, i64), IntMap<ColumnType>> = HashMap::new();
for (dbnum, block, colnum, datatype, notnull) in table_block_columns {
for (dbnum, block, colnum, datatype, notnull, pk) in table_block_columns {
let row_info = row_info.entry((dbnum, block)).or_default();
row_info.insert(
colnum,
ColumnType::Single {
datatype: datatype.parse().unwrap_or(DataType::Null),
nullable: Some(!notnull),
nullable: Some(!(notnull || (pk > 0 && datatype.to_lowercase() == "integer"))),
Comment thread
at264939-ctrl marked this conversation as resolved.
},
);
}
Expand Down Expand Up @@ -1640,7 +1640,7 @@ fn test_root_block_columns_has_types() {
assert_eq!(
Some(&ColumnType::Single {
datatype: DataType::Integer,
nullable: Some(true) //sqlite primary key columns are nullable unless declared not null
nullable: Some(false)
}),
root_block_cols[&table_db_block].get(&0)
);
Expand All @@ -1665,7 +1665,7 @@ fn test_root_block_columns_has_types() {
assert_eq!(
Some(&ColumnType::Single {
datatype: DataType::Integer,
nullable: Some(true) //sqlite primary key columns are nullable unless declared not null
nullable: Some(false)
}),
root_block_cols[&table_db_block].get(&0)
);
Expand All @@ -1683,7 +1683,7 @@ fn test_root_block_columns_has_types() {
assert_eq!(
Some(&ColumnType::Single {
datatype: DataType::Integer,
nullable: Some(true) //sqlite primary key columns are nullable unless declared not null
nullable: Some(false)
}),
root_block_cols[&table_db_block].get(&0)
);
Expand Down
6 changes: 3 additions & 3 deletions sqlx-sqlite/src/statement/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,9 @@ impl StatementHandle {

/// Use sqlite3_column_metadata to determine if a specific column is nullable.
///
/// Returns None in the case of INTEGER PRIMARY KEYs
/// Returns Some(false) in the case of INTEGER PRIMARY KEYs
/// This is because this column is an alias to rowid if the table does not use a compound
/// primary key. In this case the row is not nullable, and the output of
/// primary key. In this case the column is not nullable, and the output of
/// sqlite3_column_metadata may be incorrect.
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
unsafe {
Expand Down Expand Up @@ -271,7 +271,7 @@ impl StatementHandle {
.to_bytes()
.eq_ignore_ascii_case("integer".as_bytes())
{
None
Some(false)
Comment thread
at264939-ctrl marked this conversation as resolved.
} else {
Some(not_null == 0)
},
Expand Down
17 changes: 9 additions & 8 deletions sqlx-sqlite/src/types/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ fn decode_datetime_from_text(value: &str) -> Option<PrimitiveDateTime> {
None
}

#[allow(deprecated)]
Comment thread
at264939-ctrl marked this conversation as resolved.
mod formats {
use time::format_description::BorrowedFormatItem::{Component, Literal, Optional};
use time::format_description::{modifier, BorrowedFormatItem, Component::*};
Expand Down Expand Up @@ -270,12 +271,12 @@ mod formats {
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
SECOND,
Optional(&Literal(b".")),
Optional(&SUBSECOND),
Optional(&OFFSET_HOUR),
SUBSECOND,
OFFSET_HOUR,
Optional(&Literal(b":")),
Optional(&OFFSET_MINUTE),
OFFSET_MINUTE,
]
};

Expand All @@ -291,9 +292,9 @@ mod formats {
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
SECOND,
Optional(&Literal(b".")),
Optional(&SUBSECOND),
SUBSECOND,
Optional(&Literal(b"Z")),
]
};
Expand All @@ -310,9 +311,9 @@ mod formats {
Literal(b":"),
MINUTE,
Optional(&Literal(b":")),
Optional(&SECOND),
SECOND,
Optional(&Literal(b".")),
Optional(&SUBSECOND),
SUBSECOND,
Optional(&Literal(b"Z")),
]
};
Expand Down
30 changes: 29 additions & 1 deletion tests/sqlite/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,32 @@ async fn test_column_override_exact_nullable() -> anyhow::Result<()> {
Ok(())
}

// we don't emit bind parameter typechecks for SQLite so testing the overrides is redundant
Comment thread
at264939-ctrl marked this conversation as resolved.
Comment thread
at264939-ctrl marked this conversation as resolved.
// Regression test: INTEGER PRIMARY KEY (a rowid alias) must not be nullable.
// Before the fix, `query!` would infer `id` as `Option<i64>` and the
// assignment `let _: i64 = row.id` would fail to compile.
#[sqlx_macros::test]
async fn test_returning_primary_key_is_not_nullable() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;

let row =
sqlx::query!(r#"INSERT INTO accounts_no_not_null ( name ) VALUES ( 'test' ) RETURNING id"#)
.fetch_one(&mut conn)
.await?;

let _: i64 = row.id;

Ok(())
}

#[sqlx_macros::test]
async fn test_returning_with_foreign_key_is_not_nullable() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;

let _id: i64 = sqlx::query_scalar!(
r#"INSERT INTO packages ( project_id ) VALUES ( 1 ) RETURNING package_id"#
)
.fetch_one(&mut conn)
.await?;

Ok(())
}
9 changes: 9 additions & 0 deletions tests/sqlite/setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ CREATE TABLE accounts (
name TEXT NOT NULL,
is_active BOOLEAN
);
CREATE TABLE accounts_no_not_null (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
INSERT INTO accounts(id, name, is_active)
VALUES (1, 'Herp Derpinson', 1);
CREATE VIEW accounts_view as
Expand All @@ -35,3 +39,8 @@ CREATE TABLE products (
price NUMERIC,
CONSTRAINT price_greater_than_zero CHECK (price > 0)
);

CREATE TABLE packages (
package_id INTEGER PRIMARY KEY,
project_id INTEGER
);
Binary file modified tests/sqlite/sqlite.db
Binary file not shown.
Loading