Skip to content

Commit 3f312e5

Browse files
authored
Correct scheme for empty Query (#231)
* juliatype(str): recognize more SQLite decltypes * add isempty(Query) * Query: provide proper schema if result is empty
1 parent 6b1fc67 commit 3f312e5

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

src/SQLite.jl

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,33 @@ function juliatype(handle, col)
360360
end
361361
end
362362

363-
juliatype(x::Integer) = x == SQLITE_INTEGER ? Int : x == SQLITE_FLOAT ? Float64 : x == SQLITE_TEXT ? String : Any
364-
juliatype(x::String) = x == "INTEGER" ? Int : x in ("NUMERIC","REAL") ? Float64 : x == "TEXT" ? String : Any
363+
juliatype(x::Integer) =
364+
x == SQLITE_INTEGER ? Int :
365+
x == SQLITE_FLOAT ? Float64 :
366+
x == SQLITE_TEXT ? String :
367+
Any
368+
369+
function juliatype(typestr::AbstractString)
370+
typeuc = uppercase(typestr)
371+
if typeuc in ("INTEGER", "INT")
372+
return Int
373+
elseif typeuc in ("NUMERIC", "REAL")
374+
return Float64
375+
elseif typeuc == "TEXT"
376+
return String
377+
elseif typeuc == "BLOB"
378+
return Any
379+
elseif typeuc == "DATETIME"
380+
return Any # FIXME
381+
elseif occursin(r"^NVARCHAR\(\d+\)$", typeuc)
382+
return String
383+
elseif occursin(r"^NUMERIC\(\d+,\d+\)$", typeuc)
384+
return Float64
385+
else
386+
@warn "Unsupported SQLite type $typestr"
387+
return Any
388+
end
389+
end
365390

366391
sqlitevalue(::Type{T}, handle, col) where {T <: Union{Base.BitSigned, Base.BitUnsigned}} = convert(T, sqlite3_column_int64(handle, col))
367392
const FLOAT_TYPES = Union{Float16, Float32, Float64} # exclude BigFloat

src/tables.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,27 @@ struct Query
1010
lookup::Dict{Symbol, Int}
1111
end
1212

13+
# check if the query has no (more) rows
14+
Base.isempty(q::Query) = q.status[] == SQLITE_DONE
15+
1316
struct Row <: Tables.AbstractRow
1417
q::Query
1518
end
1619

1720
getquery(r::Row) = getfield(r, :q)
1821

1922
Tables.isrowtable(::Type{Query}) = true
23+
Tables.columnnames(q::Query) = q.names
24+
25+
function Tables.schema(q::Query)
26+
if isempty(q)
27+
# when the query is empty, return the types provided by SQLite
28+
# by default SQLite.jl assumes all columns can have missing values
29+
Tables.Schema(Tables.columnnames(q), q.types)
30+
else
31+
return nothing # fallback to the actual column types of the result
32+
end
33+
end
2034

2135
Base.IteratorSize(::Type{Query}) = Base.SizeUnknown()
2236
Base.eltype(q::Query) = Row
@@ -57,7 +71,7 @@ Tables.getcolumn(r::Row, ::Type{T}, i::Int, nm::Symbol) where {T} = getvalue(get
5771

5872
Tables.getcolumn(r::Row, i::Int) = Tables.getcolumn(r, getquery(r).types[i], i, getquery(r).names[i])
5973
Tables.getcolumn(r::Row, nm::Symbol) = Tables.getcolumn(r, getquery(r).lookup[nm])
60-
Tables.columnnames(r::Row) = getquery(r).names
74+
Tables.columnnames(r::Row) = Tables.columnnames(getquery(r))
6175

6276
function Base.iterate(q::Query)
6377
done(q) && return nothing

test/runtests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ results1 = SQLite.tables(db)
8484
DBInterface.close!(employees_stmt)
8585
end
8686

87+
@testset "isempty(::Query)" begin
88+
@test !DBInterface.execute(isempty, db, "SELECT * FROM Employee")
89+
@test DBInterface.execute(isempty, db, "SELECT * FROM Employee WHERE FirstName='Joanne'")
90+
end
91+
92+
@testset "empty query has correct schema and return type" begin
93+
empty_scheme = DBInterface.execute(Tables.schema, db, "SELECT * FROM Employee WHERE FirstName='Joanne'")
94+
all_scheme = DBInterface.execute(Tables.schema, db, "SELECT * FROM Employee WHERE FirstName='Joanne'")
95+
@test empty_scheme.names == all_scheme.names
96+
@test all(ea -> ea[1] <: ea[2], zip(empty_scheme.types, all_scheme.types))
97+
98+
empty_tbl = DBInterface.execute(columntable, db, "SELECT * FROM Employee WHERE FirstName='Joanne'")
99+
all_tbl = DBInterface.execute(columntable, db, "SELECT * FROM Employee")
100+
@test propertynames(empty_tbl) == propertynames(all_tbl)
101+
@test all(col -> eltype(empty_tbl[col]) >: eltype(all_tbl[col]), propertynames(all_tbl))
102+
end
103+
87104
DBInterface.execute(db, "create table temp as select * from album")
88105
DBInterface.execute(db, "alter table temp add column colyear int")
89106
DBInterface.execute(db, "update temp set colyear = 2014")

0 commit comments

Comments
 (0)