Skip to content

Commit 82e1add

Browse files
authored
Ensure schema-less tables have all rows respected in load! (#260)
Fixes #259. This one feels bad. For the schema-less input table case, we iterated the first row to get the names so we could do some schema validation, but then threw the row away, assuming that as we iterate later, we would somehow get all the rows anyway. That probably was fine in cases like a DataFrame, where iteration would correctly restart, but for most schema-less tables, it's not uncommon to be a stateful iterator that is forward-pass only (like `SQLite.Query`!). We fix this here by ensuring that row stays intact and is inserted before iterating further rows.
1 parent 42c9c3d commit 82e1add

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

src/tables.jl

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ function createtable!(db::DB, name::AbstractString, ::Tables.Schema{names, types
148148
columns = [string(esc_id(String(names[i])), ' ',
149149
sqlitetype(types !== nothing ? fieldtype(types, i) : Any))
150150
for i in eachindex(names)]
151-
return execute(db, "CREATE $temp TABLE $ifnotexists $(esc_id(string(name))) ($(join(columns, ',')))")
151+
sql = "CREATE $temp TABLE $ifnotexists $(esc_id(string(name))) ($(join(columns, ',')))"
152+
return execute(db, sql)
152153
end
153154

154155
# table info for load!():
@@ -213,7 +214,7 @@ function checknames(::Tables.Schema{names}, db_names::AbstractVector{String}) wh
213214
return true
214215
end
215216

216-
function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tableinfo::Union{NamedTuple, Nothing};
217+
function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tableinfo::Union{NamedTuple, Nothing}, row=nothing, st=nothing;
217218
temp::Bool=false, ifnotexists::Bool=false, analyze::Bool=false)
218219
# check for case-insensitive duplicate column names (sqlite doesn't allow)
219220
checkdupnames(sch.names)
@@ -229,12 +230,20 @@ function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tablei
229230
stmt = _Stmt(db, "INSERT INTO $(esc_id(string(name))) ($columns) VALUES ($params)")
230231
# start a transaction for inserting rows
231232
transaction(db) do
232-
for row in rows
233+
if row === nothing
234+
state = iterate(rows)
235+
state === nothing && return
236+
row, st = state
237+
end
238+
while true
233239
Tables.eachcolumn(sch, row) do val, col, _
234240
bind!(stmt, col, val)
235241
end
236242
sqlite3_step(stmt.handle)
237243
sqlite3_reset(stmt.handle)
244+
state = iterate(rows, st)
245+
state === nothing && break
246+
row, st = state
238247
end
239248
end
240249
_close!(stmt)
@@ -250,5 +259,5 @@ function load!(::Nothing, rows, db::DB, name::AbstractString,
250259
row, st = state
251260
names = propertynames(row)
252261
sch = Tables.Schema(names, nothing)
253-
return load!(sch, rows, db, name, db_tableinfo; kwargs...)
262+
return load!(sch, rows, db, name, db_tableinfo, row, st; kwargs...)
254263
end

test/runtests.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,25 @@ end
519519
end
520520

521521
end # @testset
522+
523+
struct UnknownSchemaTable
524+
end
525+
526+
Tables.isrowtable(::Type{UnknownSchemaTable}) = true
527+
Tables.rows(x::UnknownSchemaTable) = x
528+
Base.length(x::UnknownSchemaTable) = 3
529+
Base.iterate(::UnknownSchemaTable, st=1) = st == 4 ? nothing : ((a=1, b=2, c=3), st + 1)
530+
531+
@testset "misc" begin
532+
533+
# https://github.com/JuliaDatabases/SQLite.jl/issues/259
534+
db = SQLite.DB()
535+
SQLite.load!(UnknownSchemaTable(), db, "tbl")
536+
tbl = DBInterface.execute(db, "select * from tbl") |> columntable
537+
@test tbl == (
538+
a = [1, 1, 1],
539+
b = [2, 2, 2],
540+
c = [3, 3, 3]
541+
)
542+
543+
end

0 commit comments

Comments
 (0)