Skip to content

Commit f48f526

Browse files
authored
Allow selection of constraint conflict resolution algorithm in SQLite.load! (#302)
* allow selection of onstraint conflict resolution algorithm in load! * Improve documentation for on_conflict keyword arg * Add tests for on_conflict keyword argument * Update argument name in load! docstring
1 parent ace4925 commit f48f526

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

src/tables.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,15 @@ function tableinfo(db::DB, name::AbstractString)
214214
end
215215

216216
"""
217-
source |> SQLite.load!(db::SQLite.DB, tablename::String; temp::Bool=false, ifnotexists::Bool=false, replace::Bool=false, analyze::Bool=false)
218-
SQLite.load!(source, db, tablename; temp=false, ifnotexists=false, replace::Bool=false, analyze::Bool=false)
217+
source |> SQLite.load!(db::SQLite.DB, tablename::String; temp::Bool=false, ifnotexists::Bool=false, replace::Bool=false, on_conflict::Union{String, Nothing} = nothing, analyze::Bool=false)
218+
SQLite.load!(source, db, tablename; temp=false, ifnotexists=false, replace::Bool=false, on_conflict::Union{String, Nothing} = nothing, analyze::Bool=false)
219219
220220
Load a Tables.jl input `source` into an SQLite table that will be named `tablename` (will be auto-generated if not specified).
221221
222222
* `temp=true` will create a temporary SQLite table that will be destroyed automatically when the database is closed
223223
* `ifnotexists=false` will throw an error if `tablename` already exists in `db`
224-
* `replace=false` controls whether an `INSERT INTO ...` statement is generated or a `REPLACE INTO ...`
224+
* `on_conflict=nothing` allows to specify an alternative [constraint conflict resolution algorithm](https://sqlite.org/lang_conflict.html): "ABORT", "FAIL", "IGNORE", "REPLACE", or "ROLLBACK".
225+
* `replace=false` controls whether an `INSERT INTO ...` statement is generated or a `REPLACE INTO ...`. This keyword argument exists for backward compatibility, and is overridden if an algorithm is selected using the `on_conflict` keyword.
225226
* `analyze=true` will execute `ANALYZE` at the end of the insert
226227
"""
227228
function load! end
@@ -292,6 +293,7 @@ function load!(
292293
st = nothing;
293294
temp::Bool = false,
294295
ifnotexists::Bool = false,
296+
on_conflict::Union{String, Nothing} = nothing,
295297
replace::Bool = false,
296298
analyze::Bool = false,
297299
)
@@ -306,7 +308,7 @@ function load!(
306308
# build insert statement
307309
columns = join(esc_id.(string.(sch.names)), ",")
308310
params = chop(repeat("?,", length(sch.names)))
309-
kind = replace ? "REPLACE" : "INSERT"
311+
kind = isnothing(on_conflict) ? (replace ? "REPLACE" : "INSERT") : "INSERT OR $on_conflict"
310312
stmt = Stmt(
311313
db,
312314
"$kind INTO $(esc_id(string(name))) ($columns) VALUES ($params)";

test/runtests.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,49 @@ end
936936
tbl = DBInterface.execute(db, "select * from tmp") |> columntable
937937
@test tbl == (a = [1], b = [5], c = [6])
938938

939+
# https://github.com/JuliaDatabases/SQLite.jl/pull/302
940+
db = SQLite.DB()
941+
DBInterface.execute(
942+
db,
943+
"create table tmp ( a INTEGER NOT NULL PRIMARY KEY, b INTEGER, c INTEGER )",
944+
)
945+
@test_throws SQLite.SQLiteException SQLite.load!(
946+
UnknownSchemaTable(),
947+
db,
948+
"tmp",
949+
on_conflict = "ROLLBACK"
950+
)
951+
tbl = DBInterface.execute(db, "select * from tmp") |> columntable
952+
@test tbl == (a = [], b = [], c = [])
953+
@test_throws SQLite.SQLiteException SQLite.load!(
954+
UnknownSchemaTable(),
955+
db,
956+
"tmp",
957+
on_conflict = "ABORT"
958+
)
959+
@test_throws SQLite.SQLiteException SQLite.load!(
960+
UnknownSchemaTable(),
961+
db,
962+
"tmp",
963+
on_conflict = "FAIL"
964+
)
965+
SQLite.load!(
966+
UnknownSchemaTable(),
967+
db,
968+
"tmp",
969+
on_conflict = "IGNORE"
970+
)
971+
tbl = DBInterface.execute(db, "select * from tmp") |> columntable
972+
@test tbl == (a = [1], b = [3], c = [4])
973+
SQLite.load!(
974+
UnknownSchemaTable(),
975+
db,
976+
"tmp",
977+
on_conflict = "REPLACE"
978+
)
979+
tbl = DBInterface.execute(db, "select * from tmp") |> columntable
980+
@test tbl == (a = [1], b = [5], c = [6])
981+
939982
db = SQLite.DB()
940983
DBInterface.execute(db, "create table tmp ( x TEXT )")
941984
DBInterface.execute(db, "insert into tmp values (?)", (nothing,))

0 commit comments

Comments
 (0)