Skip to content

Commit 69ee0df

Browse files
authored
(Fix) Handle nullability of SQLite rowid alias columns (#4088)
* Correctly handle rowid alias columns * Added test for rowid aliasing * (SQLite) Added test for nullability of rowid Tests the nullability of explicitly referring to the rowid column (not through an alias).
1 parent 4dc32ec commit 69ee0df

File tree

2 files changed

+90
-8
lines changed

2 files changed

+90
-8
lines changed

sqlx-sqlite/src/connection/explain.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const OP_IDX_GE: &str = "IdxGE";
4343
const OP_IDX_GT: &str = "IdxGT";
4444
const OP_IDX_LE: &str = "IdxLE";
4545
const OP_IDX_LT: &str = "IdxLT";
46+
const OP_IDX_ROWID: &str = "IdxRowid";
4647
const OP_IF: &str = "If";
4748
const OP_IF_NO_HOPE: &str = "IfNoHope";
4849
const OP_IF_NOT: &str = "IfNot";
@@ -361,7 +362,9 @@ fn opcode_to_type(op: &str) -> DataType {
361362
OP_REAL => DataType::Float,
362363
OP_BLOB => DataType::Blob,
363364
OP_AND | OP_OR => DataType::Bool,
364-
OP_NEWROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => DataType::Integer,
365+
OP_NEWROWID | OP_IDX_ROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => {
366+
DataType::Integer
367+
}
365368
OP_STRING8 => DataType::Text,
366369
OP_COLUMN | _ => DataType::Null,
367370
}
@@ -676,7 +679,7 @@ pub(super) fn explain(
676679

677680
//nobranch if maybe not null
678681
let might_not_branch = match state.mem.r.get(&p1) {
679-
Some(r_p1) => !matches!(r_p1.map_to_datatype(), DataType::Null),
682+
Some(r_p1) => r_p1.map_to_nullable() != Some(false),
680683
_ => false,
681684
};
682685

@@ -1379,7 +1382,8 @@ pub(super) fn explain(
13791382
state.mem.r.insert(p2, RegDataType::Int(p1));
13801383
}
13811384

1382-
OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_NEWROWID => {
1385+
OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_IDX_ROWID
1386+
| OP_NEWROWID => {
13831387
// r[p2] = <value of constant>
13841388
state.mem.r.insert(
13851389
p2,
@@ -1778,3 +1782,63 @@ fn test_root_block_columns_has_types() {
17781782
);
17791783
}
17801784
}
1785+
1786+
#[test]
1787+
fn test_explain() {
1788+
use crate::SqliteConnectOptions;
1789+
use std::str::FromStr;
1790+
let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap();
1791+
let mut conn = super::EstablishParams::from_options(&conn_options)
1792+
.unwrap()
1793+
.establish()
1794+
.unwrap();
1795+
1796+
assert!(execute::iter(
1797+
&mut conn,
1798+
r"CREATE TABLE an_alias(a INTEGER PRIMARY KEY);",
1799+
None,
1800+
false
1801+
)
1802+
.unwrap()
1803+
.next()
1804+
.is_some());
1805+
1806+
assert!(execute::iter(
1807+
&mut conn,
1808+
r"CREATE TABLE not_an_alias(a INT PRIMARY KEY);",
1809+
None,
1810+
false
1811+
)
1812+
.unwrap()
1813+
.next()
1814+
.is_some());
1815+
1816+
assert!(
1817+
if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM an_alias") {
1818+
ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)]
1819+
} else {
1820+
false
1821+
}
1822+
);
1823+
assert!(
1824+
if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM not_an_alias") {
1825+
ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(true)]
1826+
} else {
1827+
false
1828+
}
1829+
);
1830+
assert!(
1831+
if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM an_alias") {
1832+
ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)]
1833+
} else {
1834+
false
1835+
}
1836+
);
1837+
assert!(
1838+
if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM not_an_alias") {
1839+
ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)]
1840+
} else {
1841+
false
1842+
}
1843+
);
1844+
}

sqlx-sqlite/src/statement/handle.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,15 @@ impl StatementHandle {
198198
}
199199
}
200200

201+
/// Use sqlite3_column_metadata to determine if a specific column is nullable.
202+
///
203+
/// Returns None in the case of INTEGER PRIMARY KEYs
204+
/// This is because this column is an alias to rowid if the table does not use a compound
205+
/// primary key. In this case the row is not nullable, and the output of
206+
/// sqlite3_column_metadata may be incorrect.
201207
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
202208
unsafe {
203209
let index = check_col_idx!(index);
204-
205210
// https://sqlite.org/c3ref/column_database_name.html
206211
//
207212
// ### Note
@@ -212,24 +217,25 @@ impl StatementHandle {
212217
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index);
213218
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index);
214219
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index);
215-
216220
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
217221
return Ok(None);
218222
}
219223

220224
let mut not_null: c_int = 0;
225+
let mut datatype: *const c_char = ptr::null();
226+
let mut primary_key: c_int = 0;
221227

222228
// https://sqlite.org/c3ref/table_column_metadata.html
223229
let status = sqlite3_table_column_metadata(
224230
self.db_handle(),
225231
db_name,
226232
table_name,
227233
origin_name,
234+
&mut datatype,
228235
// function docs state to provide NULL for return values you don't care about
229236
ptr::null_mut(),
230-
ptr::null_mut(),
231237
&mut not_null,
232-
ptr::null_mut(),
238+
&mut primary_key,
233239
ptr::null_mut(),
234240
);
235241

@@ -245,7 +251,19 @@ impl StatementHandle {
245251
return Err(SqliteError::new(self.db_handle()).into());
246252
}
247253

248-
Ok(Some(not_null == 0))
254+
let datatype = CStr::from_ptr(datatype);
255+
256+
Ok(
257+
if primary_key != 0
258+
&& datatype
259+
.to_bytes()
260+
.eq_ignore_ascii_case("integer".as_bytes())
261+
{
262+
None
263+
} else {
264+
Some(not_null == 0)
265+
},
266+
)
249267
}
250268
}
251269

0 commit comments

Comments
 (0)