Skip to content

Commit 339cb22

Browse files
authored
Add replace keyword arg to SQLite.load! (#263)
Fixes #243. Allows changing the "load" generated statement from INSERT to REPLACE. We also add in error checking on each statement execution, otherwise we never see an error in the INSERT case for primary key duplication (it just silently fails and doesn't insert, which isn't great).
1 parent 4b5fc58 commit 339cb22

File tree

2 files changed

+33
-13
lines changed

2 files changed

+33
-13
lines changed

src/tables.jl

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,15 @@ tableinfo(db::DB, name::AbstractString) =
175175
end
176176

177177
"""
178-
source |> SQLite.load!(db::SQLite.DB, tablename::String; temp::Bool=false, ifnotexists::Bool=false, analyze::Bool=false)
179-
SQLite.load!(source, db, tablename; temp=false, ifnotexists=false, analyze::Bool=false)
178+
source |> SQLite.load!(db::SQLite.DB, tablename::String; temp::Bool=false, ifnotexists::Bool=false, replace::Bool=false, analyze::Bool=false)
179+
SQLite.load!(source, db, tablename; temp=false, ifnotexists=false, replace::Bool=false, analyze::Bool=false)
180180
181181
Load a Tables.jl input `source` into an SQLite table that will be named `tablename` (will be auto-generated if not specified).
182182
183-
`temp=true` will create a temporary SQLite table that will be destroyed automatically when the database is closed
184-
`ifnotexists=false` will throw an error if `tablename` already exists in `db`
185-
`analyze=true` will execute `ANALYZE` at the end of the insert
183+
* `temp=true` will create a temporary SQLite table that will be destroyed automatically when the database is closed
184+
* `ifnotexists=false` will throw an error if `tablename` already exists in `db`
185+
* `replace=false` controls whether an `INSERT INTO ...` statement is generated or a `REPLACE INTO ...`
186+
* `analyze=true` will execute `ANALYZE` at the end of the insert
186187
"""
187188
function load! end
188189

@@ -222,7 +223,7 @@ function checknames(::Tables.Schema{names}, db_names::AbstractVector{String}) wh
222223
end
223224

224225
function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tableinfo::Union{NamedTuple, Nothing}, row=nothing, st=nothing;
225-
temp::Bool=false, ifnotexists::Bool=false, analyze::Bool=false)
226+
temp::Bool=false, ifnotexists::Bool=false, replace::Bool=false, analyze::Bool=false)
226227
# check for case-insensitive duplicate column names (sqlite doesn't allow)
227228
checkdupnames(sch.names)
228229
# check if `rows` column names match the existing table, or create the new one
@@ -234,7 +235,8 @@ function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tablei
234235
# build insert statement
235236
columns = join(esc_id.(string.(sch.names)), ",")
236237
params = chop(repeat("?,", length(sch.names)))
237-
stmt = _Stmt(db, "INSERT INTO $(esc_id(string(name))) ($columns) VALUES ($params)")
238+
kind = replace ? "REPLACE" : "INSERT"
239+
stmt = _Stmt(db, "$kind INTO $(esc_id(string(name))) ($columns) VALUES ($params)")
238240
# start a transaction for inserting rows
239241
transaction(db) do
240242
if row === nothing
@@ -246,8 +248,14 @@ function load!(sch::Tables.Schema, rows, db::DB, name::AbstractString, db_tablei
246248
Tables.eachcolumn(sch, row) do val, col, _
247249
bind!(stmt, col, val)
248250
end
249-
sqlite3_step(stmt.handle)
250-
sqlite3_reset(stmt.handle)
251+
r = sqlite3_step(stmt.handle)
252+
if r == SQLITE_DONE
253+
sqlite3_reset(stmt.handle)
254+
elseif r != SQLITE_ROW
255+
e = sqliteexception(db)
256+
sqlite3_reset(stmt.handle)
257+
throw(e)
258+
end
251259
state = iterate(rows, st)
252260
state === nothing && break
253261
row, st = state

test/runtests.jl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ end
526526
Tables.isrowtable(::Type{UnknownSchemaTable}) = true
527527
Tables.rows(x::UnknownSchemaTable) = x
528528
Base.length(x::UnknownSchemaTable) = 3
529-
Base.iterate(::UnknownSchemaTable, st=1) = st == 4 ? nothing : ((a=1, b=2, c=3), st + 1)
529+
Base.iterate(::UnknownSchemaTable, st=1) = st == 4 ? nothing : ((a=1, b=2 + st, c=3 + st), st + 1)
530530

531531
@testset "misc" begin
532532

@@ -536,15 +536,27 @@ SQLite.load!(UnknownSchemaTable(), db, "tbl")
536536
tbl = DBInterface.execute(db, "select * from tbl") |> columntable
537537
@test tbl == (
538538
a = [1, 1, 1],
539-
b = [2, 2, 2],
540-
c = [3, 3, 3]
539+
b = [3, 4, 5],
540+
c = [4, 5, 6]
541541
)
542542

543543
# https://github.com/JuliaDatabases/SQLite.jl/issues/251
544544
q = DBInterface.execute(db, "select * from tbl")
545545
row, st = iterate(q)
546-
@test row.a == 1 && row.b == 2 && row.c == 3
546+
@test row.a == 1 && row.b == 3 && row.c == 4
547547
row2, st = iterate(q, st)
548548
@test_throws ArgumentError row.a
549549

550+
# https://github.com/JuliaDatabases/SQLite.jl/issues/243
551+
db = SQLite.DB()
552+
DBInterface.execute(db, "create table tmp ( a INTEGER NOT NULL PRIMARY KEY, b INTEGER, c INTEGER )")
553+
@test_throws SQLite.SQLiteException SQLite.load!(UnknownSchemaTable(), db, "tmp")
554+
SQLite.load!(UnknownSchemaTable(), db, "tmp"; replace=true)
555+
tbl = DBInterface.execute(db, "select * from tmp") |> columntable
556+
@test tbl == (
557+
a = [1],
558+
b = [5],
559+
c = [6]
560+
)
561+
550562
end

0 commit comments

Comments
 (0)