From f9624b9e663246d3fe748cb532912a21314cecb5 Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Mon, 4 May 2026 18:42:47 +0200 Subject: [PATCH 1/2] Scan multiple rows for determining type in strict mode. --- src/SQLite.jl | 22 ++++++++++++++++++++++ test/runtests.jl | 6 +++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/SQLite.jl b/src/SQLite.jl index a7f9bf6..270cdaa 100644 --- a/src/SQLite.jl +++ b/src/SQLite.jl @@ -438,14 +438,36 @@ end # get julia type for given column of the given statement function juliatype(handle, col) stored_typeid = C.sqlite3_column_type(handle, col - 1) + did_row_scan = false + while stored_typeid == C.SQLITE_NULL + # Scan forward through the rows until we find a non-NULL value for this column + st = C.sqlite3_step(handle) + did_row_scan = true + if st == C.SQLITE_DONE + break + end + stored_typeid = C.sqlite3_column_type(handle, col - 1) + if stored_typeid != C.SQLITE_NULL + break + end + end if stored_typeid == C.SQLITE_BLOB # blobs are serialized julia types, so just try to deserialize it + # when forward scanning we need to use the current step to deserialize deser_val = sqlitevalue(Any, handle, col) # FIXME deserialized type have priority over declared type, is it fine? + if did_row_scan + C.sqlite3_reset(handle) + C.sqlite3_step(handle) + end return typeof(deser_val) else stored_type = juliatype(stored_typeid) end + if did_row_scan + C.sqlite3_reset(handle) + C.sqlite3_step(handle) + end decl_typestr = C.sqlite3_column_decltype(handle, col - 1) if decl_typestr != C_NULL return juliatype(unsafe_string(decl_typestr), stored_type) diff --git a/test/runtests.jl b/test/runtests.jl index 3a86863..64daa2c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1067,8 +1067,8 @@ end tbl = DBInterface.execute(db, "select x from tmp") |> columntable @test isequal(tbl.x, [missing, :a]) - # Symbol in TEXT type doesn't work - # when strict and first row is NULL (the serialized bytes are interpreted as a string) + # Symbol in TEXT type now works even when strict and first row is NULL, + # because juliatype scans multiple rows to find the actual stored type (BLOB) tbl = DBInterface.execute( DBInterface.prepare(db, "select x from tmp"), @@ -1076,7 +1076,7 @@ end strict = true, ) |> columntable @test tbl.x[1] === missing - @test tbl.x[2] isa String # Symbol gets incorrectly deserialized as garbage string + @test tbl.x[2] === :a # Symbol in BLOB type does work strict db = SQLite.DB() From afc6130d76ccc5f55ace66d619aa32a89f29b3db Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Sat, 23 May 2026 17:43:12 +0200 Subject: [PATCH 2/2] Add small remark to docstring. --- src/tables.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tables.jl b/src/tables.jl index 65772df..2769b11 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -150,6 +150,7 @@ The resultset iterator supports the [Tables.jl](https://github.com/JuliaData/Tab like `DataFrame(results)`, `CSV.write("results.csv", results)`, etc. Passing `strict=true` to `DBInterface.execute` will cause the resultset iterator to return values of the exact type specified by SQLite. +While more type-stable, determining the exact types of the results can be more expensive as it will iterate through each column to find the first non-NULL value to determine the type. """ function DBInterface.execute( stmt::Stmt,