Skip to content

Commit 5ee73ff

Browse files
authored
Add strict mode for more type-stable results if desired (#304)
1 parent 63dc515 commit 5ee73ff

File tree

1 file changed

+15
-10
lines changed

1 file changed

+15
-10
lines changed

src/tables.jl

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ using Tables
22

33
sym(ptr) = ccall(:jl_symbol, Ref{Symbol}, (Ptr{UInt8},), ptr)
44

5-
struct Query
5+
struct Query{strict}
66
stmt::Stmt
77
status::Base.RefValue{Cint}
88
names::Vector{Symbol}
@@ -14,14 +14,14 @@ end
1414
# check if the query has no (more) rows
1515
Base.isempty(q::Query) = q.status[] == C.SQLITE_DONE
1616

17-
struct Row <: Tables.AbstractRow
18-
q::Query
17+
struct Row{strict} <: Tables.AbstractRow
18+
q::Query{strict}
1919
rownumber::Int
2020
end
2121

2222
getquery(r::Row) = getfield(r, :q)
2323

24-
Tables.isrowtable(::Type{Query}) = true
24+
Tables.isrowtable(::Type{<:Query}) = true
2525
Tables.columnnames(q::Query) = q.names
2626

2727
struct DBTable
@@ -37,17 +37,17 @@ Tables.istable(::Type{<:DBTables}) = true
3737
Tables.rowaccess(::Type{<:DBTables}) = true
3838
Tables.rows(dbtbl::DBTables) = dbtbl
3939

40-
function Tables.schema(q::Query)
41-
if isempty(q)
42-
# when the query is empty, return the types provided by SQLite
40+
function Tables.schema(q::Query{strict}) where {strict}
41+
if isempty(q) || strict
42+
# when the query is empty or types are strict, return the types provided by SQLite
4343
# by default SQLite.jl assumes all columns can have missing values
4444
Tables.Schema(Tables.columnnames(q), q.types)
4545
else
4646
return nothing # fallback to the actual column types of the result
4747
end
4848
end
4949

50-
Base.IteratorSize(::Type{Query}) = Base.SizeUnknown()
50+
Base.IteratorSize(::Type{<:Query}) = Base.SizeUnknown()
5151
Base.eltype(q::Query) = Row
5252

5353
function reset!(q::Query)
@@ -77,12 +77,14 @@ end
7777
)
7878
end
7979

80-
function getvalue(q::Query, col::Int, rownumber::Int, ::Type{T}) where {T}
80+
function getvalue(q::Query{strict}, col::Int, rownumber::Int, ::Type{T}) where {strict, T}
8181
rownumber == q.current_rownumber[] || wrongrow(rownumber)
8282
handle = _get_stmt_handle(q.stmt)
8383
t = C.sqlite3_column_type(handle, col - 1)
8484
if t == C.SQLITE_NULL
8585
return missing
86+
elseif strict
87+
return sqlitevalue(T, handle, col)
8688
else
8789
TT = juliatype(t) # native SQLite Int, Float, and Text types
8890
return sqlitevalue(
@@ -133,11 +135,14 @@ Calling `SQLite.reset!(result)` will re-execute the query and reset the iterator
133135
134136
The resultset iterator supports the [Tables.jl](https://github.com/JuliaData/Tables.jl) interface, so results can be collected in any Tables.jl-compatible sink,
135137
like `DataFrame(results)`, `CSV.write("results.csv", results)`, etc.
138+
139+
Passing `strict=true` to `DBInterface.execute` will cause the resultset iterator to return values of the exact type specified by SQLite.
136140
"""
137141
function DBInterface.execute(
138142
stmt::Stmt,
139143
params::DBInterface.StatementParams;
140144
allowduplicates::Bool = false,
145+
strict::Bool = false,
141146
)
142147
status = execute(stmt, params)
143148
handle = _get_stmt_handle(stmt)
@@ -158,7 +163,7 @@ function DBInterface.execute(
158163
header[i] = nm
159164
types[i] = Union{juliatype(handle, i),Missing}
160165
end
161-
return Query(
166+
return Query{strict}(
162167
stmt,
163168
Ref(status),
164169
header,

0 commit comments

Comments
 (0)