From 0d2865280c99c7517c3ef0fab86ada3253856900 Mon Sep 17 00:00:00 2001 From: Maarten Pronk Date: Mon, 8 Dec 2025 18:45:08 +0100 Subject: [PATCH] Fix and test strict keyword. --- src/SQLite.jl | 8 ++++---- src/tables.jl | 30 +++++++++++++++++++----------- test/runtests.jl | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/SQLite.jl b/src/SQLite.jl index 6525c84..63929ec 100644 --- a/src/SQLite.jl +++ b/src/SQLite.jl @@ -154,7 +154,7 @@ mutable struct Stmt <: DBInterface.Statement end end -_get_stmt_handle(stmt::Stmt) = stmt.stmt_wrapper[] +_get_stmt_handle(stmt::Stmt)::StmtHandle = stmt.stmt_wrapper[] function _set_stmt_handle(stmt::Stmt, handle) stmt.stmt_wrapper[] = handle end @@ -484,15 +484,15 @@ function sqlitevalue( ::Type{T}, handle, col, -) where {T<:Union{Base.BitSigned,Base.BitUnsigned}} +)::T where {T<:Union{Base.BitSigned,Base.BitUnsigned}} convert(T, C.sqlite3_column_int64(handle, col - 1)) end const FLOAT_TYPES = Union{Float16,Float32,Float64} # exclude BigFloat -function sqlitevalue(::Type{T}, handle, col) where {T<:FLOAT_TYPES} +function sqlitevalue(::Type{T}, handle, col)::T where {T<:FLOAT_TYPES} convert(T, C.sqlite3_column_double(handle, col - 1)) end #TODO: test returning a WeakRefString instead of calling `unsafe_string` -function sqlitevalue(::Type{T}, handle, col) where {T<:AbstractString} +function sqlitevalue(::Type{T}, handle, col)::T where {T<:AbstractString} convert(T, unsafe_string(C.sqlite3_column_text(handle, col - 1))) end function sqlitevalue(::Type{T}, handle, col) where {T} diff --git a/src/tables.jl b/src/tables.jl index 206f9b7..7ab994b 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -78,26 +78,34 @@ end end function getvalue( - q::Query{strict}, + q::Query{true}, col::Int, rownumber::Int, ::Type{T}, -) where {strict,T} +)::Union{Missing,nonmissingtype(T)} where {T} + rownumber == q.current_rownumber[] || wrongrow(rownumber) + handle = _get_stmt_handle(q.stmt) + t = C.sqlite3_column_type(handle, col - 1) + if t == C.SQLITE_NULL + return missing + end + sqlitevalue(nonmissingtype(T), handle, col) +end + +function getvalue( + q::Query{false}, + col::Int, + rownumber::Int, + ::Type{T}, +) where {T} rownumber == q.current_rownumber[] || wrongrow(rownumber) handle = _get_stmt_handle(q.stmt) t = C.sqlite3_column_type(handle, col - 1) if t == C.SQLITE_NULL return missing - elseif strict - return sqlitevalue(T, handle, col) - else - TT = juliatype(t) # native SQLite Int, Float, and Text types - return sqlitevalue( - ifelse(TT === Any && !isbitstype(T), T, TT), - handle, - col, - ) end + TT = juliatype(t) # native SQLite Int, Float, and Text types + return sqlitevalue(ifelse(TT === Any && !isbitstype(T), T, TT), handle, col) end function Tables.getcolumn(r::Row, ::Type{T}, i::Int, nm::Symbol) where {T} diff --git a/test/runtests.jl b/test/runtests.jl index 9c78383..86ba449 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -999,6 +999,48 @@ 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 + tbl = + DBInterface.execute( + DBInterface.prepare(db, "select x from tmp"), + (); + strict = true, + ) |> columntable + @test isequal(tbl.x, [missing, "7JL\x1e\x04"]) + + # Symbol in BLOB type does work strict + db = SQLite.DB() + DBInterface.execute(db, "create table tmp ( x BLOB )") + DBInterface.execute(db, "insert into tmp values (?)", (nothing,)) + DBInterface.execute(db, "insert into tmp values (?)", (:a,)) + tbl = DBInterface.execute(db, "select x from tmp") |> columntable + @test isequal(tbl.x, [missing, :a]) + + tbl = + DBInterface.execute( + DBInterface.prepare(db, "select x from tmp"), + (); + strict = true, + ) |> columntable + @test isequal(tbl.x, [missing, :a]) + + # Symbol in TEXT always works when first row is not NULL + db = SQLite.DB() + DBInterface.execute(db, "create table tmp ( x TEXT )") + DBInterface.execute(db, "insert into tmp values (?)", (:a,)) + DBInterface.execute(db, "insert into tmp values (?)", (nothing,)) + tbl = DBInterface.execute(db, "select x from tmp") |> columntable + @test isequal(tbl.x, [:a, missing]) + + tbl = + DBInterface.execute( + DBInterface.prepare(db, "select x from tmp"), + (); + strict = true, + ) |> columntable + @test isequal(tbl.x, [:a, missing]) + db = SQLite.DB() DBInterface.execute( db,