diff --git a/src/SQLite.jl b/src/SQLite.jl index d7a1271..6a67362 100644 --- a/src/SQLite.jl +++ b/src/SQLite.jl @@ -705,7 +705,9 @@ function sqlitevalue(::Type{T}, handle, col)::T where {T<:FLOAT_TYPES} end #TODO: test returning a WeakRefString instead of calling `unsafe_string` function sqlitevalue(::Type{T}, handle, col)::T where {T<:AbstractString} - convert(T, unsafe_string(C.sqlite3_column_text(handle, col - 1))) + text = C.sqlite3_column_text(handle, col - 1) + nbytes = C.sqlite3_column_bytes(handle, col - 1) + convert(T, unsafe_string(text, nbytes)) end function sqlitevalue(::Type{T}, handle, col) where {T} blob = convert(Ptr{UInt8}, C.sqlite3_column_blob(handle, col - 1)) diff --git a/src/UDF.jl b/src/UDF.jl index f2ac862..f2921ec 100644 --- a/src/UDF.jl +++ b/src/UDF.jl @@ -11,7 +11,9 @@ function sqlvalue(values, i) elseif valuetype == C.SQLITE_FLOAT return C.sqlite3_value_double(temp_val_ptr) elseif valuetype == C.SQLITE_TEXT - return unsafe_string(C.sqlite3_value_text(temp_val_ptr)) + text = C.sqlite3_value_text(temp_val_ptr) + nbytes = C.sqlite3_value_bytes(temp_val_ptr) + return unsafe_string(text, nbytes) elseif valuetype == C.SQLITE_BLOB nbytes = C.sqlite3_value_bytes(temp_val_ptr) blob = C.sqlite3_value_blob(temp_val_ptr) diff --git a/test/runtests.jl b/test/runtests.jl index bad6ae0..70d15c2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,6 +114,42 @@ end DBInterface.close!(db) end + @testset "TEXT byte lengths" begin + values = [missing, "", "plain", "é🚀", "\0", "\0lead", "tail\0", "a\0b\0c", "é\0🚀"] + expected_bytes = [ismissing(v) ? missing : ncodeunits(v) for v in values] + expected_hex = [ismissing(v) ? "" : uppercase(bytes2hex(codeunits(v))) for v in values] + db = SQLite.DB() + saved_values = [] + try + SQLite.execute(db, "CREATE TABLE text_values (value TEXT)") + insert = SQLite.Stmt(db, "INSERT INTO text_values VALUES (?)") + for value in values + SQLite.execute(insert, (value,)) + end + SQLite.register(db, v -> ismissing(v) ? missing : ncodeunits(v); + name = "text_bytes", nargs = 1) + SQLite.register(db, 0, (n, v) -> n + (ismissing(v) ? 0 : ncodeunits(v)); + name = "total_text_bytes", nargs = 1) + select = SQLite.Stmt(db, + "SELECT value, hex(value) AS bytes, text_bytes(value) AS byte_count FROM text_values ORDER BY rowid") + for strict in (false, true) + result = Tables.columntable(DBInterface.execute(select, (); strict = strict)) + @test isequal(result.value, values) + @test result.bytes == expected_hex + @test isequal(result.byte_count, expected_bytes) + push!(saved_values, result.value) + end + result = DBInterface.execute(columntable, db, + "SELECT total_text_bytes(value) AS n FROM text_values") + @test only(result.n) == sum(skipmissing(expected_bytes)) + finally + close(db) + end + for result in saved_values + @test isequal(result, values) + end + end + @testset "Julia to SQLite3 type conversion" begin @test SQLite.sqlitetype(Int) == "INT NOT NULL" @test SQLite.sqlitetype(Union{Float64,Missing}) == "REAL"