Skip to content

Commit 2c99915

Browse files
authored
Improve juliatype() (#232)
* add comments for juliatype() methods * juliatype(): support more SQLite type strings addresses issue mentioned in #219 * juliatype(): fall back to stored type when typestr is not recognized * more SQLite -> Julia type conversion tests
1 parent 4b15b85 commit 2c99915

File tree

2 files changed

+56
-25
lines changed

2 files changed

+56
-25
lines changed

src/SQLite.jl

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -345,46 +345,56 @@ end
345345
#int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
346346
#int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
347347

348+
# get julia type for given column of the given statement
348349
function juliatype(handle, col)
349-
t = SQLite.sqlite3_column_decltype(handle, col)
350-
if t != C_NULL
351-
T = juliatype(unsafe_string(t))
352-
T !== Any && return T
350+
stored_typeid = SQLite.sqlite3_column_type(handle, col)
351+
if stored_typeid == SQLite.SQLITE_BLOB
352+
# blobs are serialized julia types, so just try to deserialize it
353+
deser_val = SQLite.sqlitevalue(Any, handle, col)
354+
# FIXME deserialized type have priority over declared type, is it fine?
355+
return typeof(deser_val)
356+
else
357+
stored_type = juliatype(stored_typeid)
353358
end
354-
x = SQLite.sqlite3_column_type(handle, col)
355-
if x == SQLite.SQLITE_BLOB
356-
val = SQLite.sqlitevalue(Any, handle, col)
357-
return typeof(val)
359+
decl_typestr = SQLite.sqlite3_column_decltype(handle, col)
360+
if decl_typestr != C_NULL
361+
return juliatype(unsafe_string(decl_typestr), stored_type)
358362
else
359-
return juliatype(x)
363+
return stored_type
360364
end
361365
end
362366

367+
# convert SQLite stored type into Julia equivalent
363368
juliatype(x::Integer) =
364369
x == SQLITE_INTEGER ? Int :
365370
x == SQLITE_FLOAT ? Float64 :
366371
x == SQLITE_TEXT ? String :
367372
Any
368373

369-
function juliatype(typestr::AbstractString)
370-
typeuc = uppercase(typestr)
374+
# convert SQLite declared type into Julia equivalent,
375+
# fall back to default (stored type), if no good match
376+
function juliatype(decl_typestr::AbstractString,
377+
default::Type = Any)
378+
typeuc = uppercase(decl_typestr)
371379
if typeuc in ("INTEGER", "INT")
372380
return Int
373-
elseif typeuc in ("NUMERIC", "REAL")
381+
elseif typeuc in ("NUMERIC", "REAL", "FLOAT")
374382
return Float64
375383
elseif typeuc == "TEXT"
376384
return String
377385
elseif typeuc == "BLOB"
378386
return Any
379387
elseif typeuc == "DATETIME"
380-
return Any # FIXME
381-
elseif occursin(r"^NVARCHAR\(\d+\)$", typeuc)
388+
return default # FIXME
389+
elseif typeuc == "TIMESTAMP"
390+
return default # FIXME
391+
elseif occursin(r"^(?:N?VAR)?CHAR\(\d+\)$", typeuc)
382392
return String
383393
elseif occursin(r"^NUMERIC\(\d+,\d+\)$", typeuc)
384394
return Float64
385395
else
386-
@warn "Unsupported SQLite type $typestr"
387-
return Any
396+
@warn "Unsupported SQLite declared type $decl_typestr, falling back to $default type"
397+
return default
388398
end
389399
end
390400

test/runtests.jl

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,15 +272,36 @@ stmt = DBInterface.prepare(db, "INSERT INTO tbl (a) VALUES (@a);")
272272
SQLite.bind!(stmt, "@a", 1)
273273
SQLite.clear!(stmt)
274274

275-
binddb = SQLite.DB()
276-
DBInterface.execute(binddb, "CREATE TABLE temp (n NULL, i6 INT, f REAL, s TEXT, a BLOB)")
277-
DBInterface.execute(binddb, "INSERT INTO temp VALUES (?1, ?2, ?3, ?4, ?5)", [missing, Int64(6), 6.4, "some text", b"bytearray"])
278-
r = DBInterface.execute(binddb, "SELECT * FROM temp") |> columntable
279-
@test isa(r[1][1], Missing)
280-
@test isa(r[2][1], Int)
281-
@test isa(r[3][1], Float64)
282-
@test isa(r[4][1], AbstractString)
283-
@test isa(r[5][1], Base.CodeUnits)
275+
@testset "SQLite to Julia type conversion" begin
276+
binddb = SQLite.DB()
277+
DBInterface.execute(binddb,
278+
"CREATE TABLE temp (n NULL, i1 INT, i2 integer,
279+
f1 REAL, f2 FLOAT, f3 NUMERIC,
280+
s1 TEXT, s2 CHAR(10), s3 VARCHAR(15), s4 NVARCHAR(5),
281+
d1 DATETIME, ts1 TIMESTAMP,
282+
b BLOB,
283+
x1 UNKNOWN1, x2 UNKNOWN2)")
284+
DBInterface.execute(binddb, "INSERT INTO temp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
285+
[missing, Int64(6), Int64(4),
286+
6.4, 6.3, Int64(7),
287+
"some long text", "short text", "another text", "short",
288+
"2021-02-21", "2021-02-12::1532",
289+
b"bytearray",
290+
"actually known", 435])
291+
rr = (;) # just to have the var declared
292+
@test_logs(
293+
(:warn, "Unsupported SQLite declared type UNKNOWN1, falling back to String type"),
294+
(:warn, "Unsupported SQLite declared type UNKNOWN2, falling back to $(Int) type"),
295+
rr = DBInterface.execute(rowtable, binddb, "SELECT * FROM temp"))
296+
@test length(rr) == 1
297+
r = first(rr)
298+
@test typeof.(Tuple(r)) == (Missing, Int, Int,
299+
Float64, Float64, Int,
300+
String, String, String, String,
301+
String, String,
302+
Base.CodeUnits{UInt8, String},
303+
String, Int)
304+
end
284305

285306
############################################
286307

0 commit comments

Comments
 (0)