Skip to content

Commit 44ad035

Browse files
authored
Scan multiple rows for determining type in strict mode (#349)
* Scan multiple rows for determining type in strict mode. * Add small remark to docstring.
1 parent 1dd7982 commit 44ad035

3 files changed

Lines changed: 26 additions & 3 deletions

File tree

src/SQLite.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,14 +480,36 @@ end
480480
# get julia type for given column of the given statement
481481
function juliatype(handle, col)
482482
stored_typeid = C.sqlite3_column_type(handle, col - 1)
483+
did_row_scan = false
484+
while stored_typeid == C.SQLITE_NULL
485+
# Scan forward through the rows until we find a non-NULL value for this column
486+
st = C.sqlite3_step(handle)
487+
did_row_scan = true
488+
if st == C.SQLITE_DONE
489+
break
490+
end
491+
stored_typeid = C.sqlite3_column_type(handle, col - 1)
492+
if stored_typeid != C.SQLITE_NULL
493+
break
494+
end
495+
end
483496
if stored_typeid == C.SQLITE_BLOB
484497
# blobs are serialized julia types, so just try to deserialize it
498+
# when forward scanning we need to use the current step to deserialize
485499
deser_val = sqlitevalue(Any, handle, col)
486500
# FIXME deserialized type have priority over declared type, is it fine?
501+
if did_row_scan
502+
C.sqlite3_reset(handle)
503+
C.sqlite3_step(handle)
504+
end
487505
return typeof(deser_val)
488506
else
489507
stored_type = juliatype(stored_typeid)
490508
end
509+
if did_row_scan
510+
C.sqlite3_reset(handle)
511+
C.sqlite3_step(handle)
512+
end
491513
decl_typestr = C.sqlite3_column_decltype(handle, col - 1)
492514
if decl_typestr != C_NULL
493515
return juliatype(unsafe_string(decl_typestr), stored_type)

src/tables.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ The resultset iterator supports the [Tables.jl](https://github.com/JuliaData/Tab
150150
like `DataFrame(results)`, `CSV.write("results.csv", results)`, etc.
151151
152152
Passing `strict=true` to `DBInterface.execute` will cause the resultset iterator to return values of the exact type specified by SQLite.
153+
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.
153154
"""
154155
function DBInterface.execute(
155156
stmt::Stmt,

test/runtests.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,16 +1091,16 @@ end
10911091
tbl = DBInterface.execute(db, "select x from tmp") |> columntable
10921092
@test isequal(tbl.x, [missing, :a])
10931093

1094-
# Symbol in TEXT type doesn't work
1095-
# when strict and first row is NULL (the serialized bytes are interpreted as a string)
1094+
# Symbol in TEXT type now works even when strict and first row is NULL,
1095+
# because juliatype scans multiple rows to find the actual stored type (BLOB)
10961096
tbl =
10971097
DBInterface.execute(
10981098
DBInterface.prepare(db, "select x from tmp"),
10991099
();
11001100
strict = true,
11011101
) |> columntable
11021102
@test tbl.x[1] === missing
1103-
@test tbl.x[2] isa String # Symbol gets incorrectly deserialized as garbage string
1103+
@test tbl.x[2] === :a
11041104

11051105
# Symbol in BLOB type does work strict
11061106
db = SQLite.DB()

0 commit comments

Comments
 (0)