Skip to content

Commit ac955a3

Browse files
quinnjJuliaTagBot
andauthored
Implement DBInterface (#196)
* Start work on structtypes integration * Implement DBInterface * Cleanup * Updates * Cleanups and get tests passing * try to fix tests * try to fix tests * Add tests for coverage * Install TagBot as a GitHub Action (#199) * Updates Co-authored-by: Julia TagBot <[email protected]>
1 parent 5fb68f2 commit ac955a3

File tree

5 files changed

+267
-289
lines changed

5 files changed

+267
-289
lines changed

Project.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
name = "SQLite"
22
uuid = "0aa819cd-b072-5ff4-a722-6bc24af294d9"
33
authors = ["Jacob Quinn <[email protected]>"]
4-
version = "0.9"
4+
version = "1.0"
55

66
[deps]
77
BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
8-
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
98
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
9+
DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965"
1010
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
1111
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1212
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
@@ -16,7 +16,7 @@ WeakRefStrings = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
1616

1717
[compat]
1818
BinaryProvider = "0.5"
19-
DataFrames = "0.18,0.19,0.20"
20-
Tables = "0.1,0.2"
19+
DBInterface = "2"
20+
Tables = "1"
2121
WeakRefStrings = "0.4,0.5,0.6"
2222
julia = "1"

docs/src/index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## High-level interface
77
```@docs
8-
SQLite.Query
8+
DBInterface.execute
99
SQLite.load!
1010
```
1111

@@ -15,7 +15,6 @@ SQLite.load!
1515
SQLite.DB
1616
SQLite.Stmt
1717
SQLite.bind!
18-
SQLite.execute!
1918
SQLite.createtable!
2019
SQLite.drop!
2120
SQLite.dropindex!

src/SQLite.jl

Lines changed: 63 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module SQLite
22

33
using Random, Serialization
4-
using WeakRefStrings, DataFrames
4+
using WeakRefStrings, DBInterface
55

66
struct SQLiteException <: Exception
77
msg::AbstractString
@@ -39,34 +39,36 @@ To create an in-memory temporary database, call `SQLite.DB()`.
3939
The `SQLite.DB` will automatically closed/shutdown when it goes out of scope
4040
(i.e. the end of the Julia session, end of a function call wherein it was created, etc.)
4141
"""
42-
mutable struct DB
42+
mutable struct DB <: DBInterface.Connection
4343
file::String
4444
handle::Ptr{Cvoid}
45-
changes::Int
4645

4746
function DB(f::AbstractString)
4847
handle = Ref{Ptr{Cvoid}}()
4948
f = isempty(f) ? f : expanduser(f)
5049
if @OK sqliteopen(f, handle)
51-
db = new(f, handle[], 0)
50+
db = new(f, handle[])
5251
finalizer(_close, db)
5352
return db
5453
else # error
55-
db = new(f, handle[], 0)
54+
db = new(f, handle[])
5655
finalizer(_close, db)
5756
sqliteerror(db)
5857
end
5958
end
6059
end
6160
DB() = DB(":memory:")
61+
DBInterface.connect(::Type{DB}) = DB()
62+
DBInterface.connect(::Type{DB}, f::AbstractString) = DB(f)
63+
DBInterface.close!(db::DB) = _close(db)
6264

6365
function _close(db::DB)
6466
db.handle == C_NULL || sqlite3_close_v2(db.handle)
6567
db.handle = C_NULL
6668
return
6769
end
6870

69-
Base.show(io::IO, db::SQLite.DB) = print(io, string("SQLite.DB(", db.file == ":memory:" ? "in-memory" : "\"$(db.file)\"", ")"))
71+
Base.show(io::IO, db::SQLite.DB) = print(io, string("SQLite.DB(", "\"$(db.file)\"", ")"))
7072

7173
"""
7274
Constructs and prepares (compiled by the SQLite library)
@@ -75,25 +77,28 @@ Note the SQL statement is not actually executed,
7577
but only compiled
7678
(mainly for usage where the same statement
7779
is repeated with different parameters bound as values.
78-
See [`SQLite.bind!`](@ref) below).
79-
80-
The `SQLite.Stmt` will automatically closed/shutdown when it goes out of scope (i.e. the end of the Julia session, end of a function call wherein it was created, etc.)
8180
81+
The `SQLite.Stmt` will be automatically closed/shutdown when it goes out of scope
82+
(i.e. the end of the Julia session, end of a function call wherein it was created, etc.),
83+
but you can close `DBInterface.close!(stmt)` to explicitly and immediately close the statement.
8284
"""
83-
mutable struct Stmt
85+
mutable struct Stmt <: DBInterface.Statement
8486
db::DB
8587
handle::Ptr{Cvoid}
8688
params::Dict{Int, Any}
89+
status::Int
8790

8891
function Stmt(db::DB,sql::AbstractString)
8992
handle = Ref{Ptr{Cvoid}}()
9093
sqliteprepare(db, sql, handle, Ref{Ptr{Cvoid}}())
91-
stmt = new(db, handle[], Dict{Int, Any}())
94+
stmt = new(db, handle[], Dict{Int, Any}(), 0)
9295
finalizer(_close, stmt)
9396
return stmt
9497
end
9598
end
9699

100+
DBInterface.close!(stmt::Stmt) = _close(stmt)
101+
97102
function _close(stmt::Stmt)
98103
stmt.handle == C_NULL || sqlite3_finalize(stmt.handle)
99104
stmt.handle = C_NULL
@@ -103,7 +108,7 @@ end
103108
sqliteprepare(db, sql, stmt, null) = @CHECK db sqlite3_prepare_v2(db.handle, sql, stmt, null)
104109

105110
include("UDF.jl")
106-
export @sr_str, @register, register
111+
export @sr_str
107112

108113
"""
109114
`SQLite.clear!(stmt::SQLite.Stmt)`
@@ -164,41 +169,36 @@ From the [SQLite documentation](https://www3.sqlite.org/cintro.html):
164169
"""
165170
function bind! end
166171

167-
bind!(stmt::Stmt, ::Nothing) = nothing
168-
169-
function bind!(stmt::Stmt, values::Tuple)
172+
function bind!(stmt::Stmt, params::Union{NamedTuple, Dict})
170173
nparams = sqlite3_bind_parameter_count(stmt.handle)
171-
@assert nparams == length(values) "you must provide values for all query placeholders"
174+
@assert nparams == length(params) "you must provide values for all query placeholders"
172175
for i in 1:nparams
173-
@inbounds bind!(stmt, i, values[i])
176+
name = unsafe_string(sqlite3_bind_parameter_name(stmt.handle, i))
177+
@assert !isempty(name) "nameless parameters should be passed as a Vector"
178+
# name is returned with the ':', '@' or '$' at the start
179+
sym = Symbol(name[2:end])
180+
haskey(params, sym) || throw(SQLiteException("`$name` not found in values keyword arguments to bind to sql statement"))
181+
bind!(stmt, i, params[sym])
174182
end
175183
end
176-
function bind!(stmt::Stmt, values::Vector)
184+
185+
function bind!(stmt::Stmt, values::Union{Vector, Tuple})
177186
nparams = sqlite3_bind_parameter_count(stmt.handle)
178187
@assert nparams == length(values) "you must provide values for all query placeholders"
179188
for i in 1:nparams
180189
@inbounds bind!(stmt, i, values[i])
181190
end
182191
end
183-
function bind!(stmt::Stmt, values::Dict{Symbol, V}) where {V}
184-
nparams = sqlite3_bind_parameter_count(stmt.handle)
185-
for i in 1:nparams
186-
name = unsafe_string(sqlite3_bind_parameter_name(stmt.handle, i))
187-
@assert !isempty(name) "nameless parameters should be passed as a Vector"
188-
# name is returned with the ':', '@' or '$' at the start
189-
sym = Symbol(name[2:end])
190-
haskey(values, sym) || throw(SQLiteException("`$name` not found in values Dict to bind to sql statement"))
191-
bind!(stmt, i, values[sym])
192-
end
193-
end
192+
194193
# Binding parameters to SQL statements
195-
function bind!(stmt::Stmt,name::AbstractString, val)
194+
function bind!(stmt::Stmt, name::AbstractString, val)
196195
i::Int = sqlite3_bind_parameter_index(stmt.handle, name)
197196
if i == 0
198197
throw(SQLiteException("SQL parameter $name not found in $stmt"))
199198
end
200199
return bind!(stmt, i, val)
201200
end
201+
202202
bind!(stmt::Stmt, i::Int, val::AbstractFloat) = (stmt.params[i] = val; @CHECK stmt.db sqlite3_bind_double(stmt.handle, i ,Float64(val)); return nothing)
203203
bind!(stmt::Stmt, i::Int, val::Int32) = (stmt.params[i] = val; @CHECK stmt.db sqlite3_bind_int(stmt.handle, i ,val); return nothing)
204204
bind!(stmt::Stmt, i::Int, val::Int64) = (stmt.params[i] = val; @CHECK stmt.db sqlite3_bind_int64(stmt.handle, i ,val); return nothing)
@@ -295,24 +295,21 @@ sqlitetype(::Type{Missing}) = "NULL"
295295
sqlitetype(x) = "BLOB"
296296

297297
"""
298-
Used to execute a prepared `SQLite.Stmt`.
299-
The 2nd method is a convenience method to pass in an SQL statement as a string
300-
which gets prepared and executed in one call.
301-
This method does not check for or return any results,
302-
hence it is only useful for database manipulation methods
303-
(i.e. ALTER, CREATE, UPDATE, DROP).
304-
To return results, see [`SQLite.Query`](@ref) above.
298+
SQLite.execute(db::SQLite.DB, sql, [params])
299+
SQLite.execute(stmt::SQLite.Stmt, [params])
305300
306-
With a prepared `stmt`,
307-
you can also pass a `values` iterable or `Dict`
308-
that will bind to parameters in the prepared query.
301+
An internal method that takes a `db` and `sql` arguments, or an already prepared `stmt` (2nd method),
302+
any positional parameters (`params` given as `Vector` or `Tuple`) or named parameters (`params` given as `Dict` or `NamedTuple`),
303+
binds any parameters, and executes the query.
309304
305+
The sqlite return status code is returned. To return results from a query, please see [`DBInterface.execute`](@ref).
310306
"""
311-
function execute! end
307+
function execute end
312308

313-
function execute!(stmt::Stmt; values=nothing)
314-
bind!(stmt, values)
309+
function execute(stmt::Stmt, params=())
310+
bind!(stmt, params)
315311
r = sqlite3_step(stmt.handle)
312+
stmt.status = r
316313
if r == SQLITE_DONE
317314
sqlite3_reset(stmt.handle)
318315
elseif r != SQLITE_ROW
@@ -323,13 +320,7 @@ function execute!(stmt::Stmt; values=nothing)
323320
return r
324321
end
325322

326-
function execute!(db::DB, sql::AbstractString; values=nothing)
327-
stmt = Stmt(db, sql)
328-
bind!(stmt, values)
329-
r = execute!(stmt)
330-
finalize(stmt)
331-
return r
332-
end
323+
execute(db::DB, sql::AbstractString, params=()) = execute(Stmt(db, sql), params)
333324

334325
"""
335326
SQLite.esc_id(x::Union{AbstractString,Vector{AbstractString}})
@@ -397,18 +388,18 @@ In the second method, `func` is executed within a transaction (the transaction b
397388
function transaction end
398389

399390
function transaction(db, mode="DEFERRED")
400-
execute!(db, "PRAGMA temp_store=MEMORY;")
391+
execute(db, "PRAGMA temp_store=MEMORY;")
401392
if uppercase(mode) in ["", "DEFERRED", "IMMEDIATE", "EXCLUSIVE"]
402-
execute!(db, "BEGIN $(mode) TRANSACTION;")
393+
execute(db, "BEGIN $(mode) TRANSACTION;")
403394
else
404-
execute!(db, "SAVEPOINT $(mode);")
395+
execute(db, "SAVEPOINT $(mode);")
405396
end
406397
end
407398

408399
@inline function transaction(f::Function, db)
409400
# generate a random name for the savepoint
410401
name = string("SQLITE", Random.randstring(10))
411-
execute!(db, "PRAGMA synchronous = OFF;")
402+
execute(db, "PRAGMA synchronous = OFF;")
412403
transaction(db, name)
413404
try
414405
f()
@@ -418,7 +409,7 @@ end
418409
finally
419410
# savepoints are not released on rollback
420411
commit(db, name)
421-
execute!(db, "PRAGMA synchronous = ON;")
412+
execute(db, "PRAGMA synchronous = ON;")
422413
end
423414
end
424415

@@ -432,8 +423,8 @@ commit a transaction or named savepoint
432423
"""
433424
function commit end
434425

435-
commit(db) = execute!(db, "COMMIT TRANSACTION;")
436-
commit(db, name) = execute!(db, "RELEASE SAVEPOINT $(name);")
426+
commit(db) = execute(db, "COMMIT TRANSACTION;")
427+
commit(db, name) = execute(db, "RELEASE SAVEPOINT $(name);")
437428

438429
"""
439430
`SQLite.rollback(db)`
@@ -445,8 +436,8 @@ rollback transaction or named savepoint
445436
"""
446437
function rollback end
447438

448-
rollback(db) = execute!(db, "ROLLBACK TRANSACTION;")
449-
rollback(db, name) = execute!(db, "ROLLBACK TRANSACTION TO SAVEPOINT $(name);")
439+
rollback(db) = execute(db, "ROLLBACK TRANSACTION;")
440+
rollback(db, name) = execute(db, "ROLLBACK TRANSACTION TO SAVEPOINT $(name);")
450441

451442
"""
452443
`SQLite.drop!(db, table; ifexists::Bool=true)`
@@ -456,9 +447,9 @@ drop the SQLite table `table` from the database `db`; `ifexists=true` will preve
456447
function drop!(db::DB, table::AbstractString; ifexists::Bool=false)
457448
exists = ifexists ? "IF EXISTS" : ""
458449
transaction(db) do
459-
execute!(db, "DROP TABLE $exists $(esc_id(table))")
450+
execute(db, "DROP TABLE $exists $(esc_id(table))")
460451
end
461-
execute!(db, "VACUUM")
452+
execute(db, "VACUUM")
462453
return
463454
end
464455

@@ -470,7 +461,7 @@ drop the SQLite index `index` from the database `db`; `ifexists=true` will not r
470461
function dropindex!(db::DB, index::AbstractString; ifexists::Bool=false)
471462
exists = ifexists ? "IF EXISTS" : ""
472463
transaction(db) do
473-
execute!(db, "DROP INDEX $exists $(esc_id(index))")
464+
execute(db, "DROP INDEX $exists $(esc_id(index))")
474465
end
475466
return
476467
end
@@ -486,9 +477,9 @@ function createindex!(db::DB, table::AbstractString, index::AbstractString, cols
486477
u = unique ? "UNIQUE" : ""
487478
exists = ifnotexists ? "IF NOT EXISTS" : ""
488479
transaction(db) do
489-
execute!(db, "CREATE $u INDEX $exists $(esc_id(index)) ON $(esc_id(table)) ($(esc_id(cols)))")
480+
execute(db, "CREATE $u INDEX $exists $(esc_id(index)) ON $(esc_id(table)) ($(esc_id(cols)))")
490481
end
491-
execute!(db, "ANALYZE $index")
482+
execute(db, "ANALYZE $index")
492483
return
493484
end
494485

@@ -506,34 +497,34 @@ function removeduplicates!(db, table::AbstractString, cols::AbstractArray{T}) wh
506497
end
507498
colsstr = chop(colsstr)
508499
transaction(db) do
509-
execute!(db, "DELETE FROM $(esc_id(table)) WHERE _ROWID_ NOT IN (SELECT max(_ROWID_) from $(esc_id(table)) GROUP BY $(colsstr));")
500+
execute(db, "DELETE FROM $(esc_id(table)) WHERE _ROWID_ NOT IN (SELECT max(_ROWID_) from $(esc_id(table)) GROUP BY $(colsstr));")
510501
end
511-
execute!(db, "ANALYZE $table")
502+
execute(db, "ANALYZE $table")
512503
return
513504
end
514505

515506
include("tables.jl")
516507

517508
"""
518-
`SQLite.tables(db, sink=DataFrame)`
509+
`SQLite.tables(db, sink=columntable)`
519510
520511
returns a list of tables in `db`
521512
"""
522-
tables(db::DB, sink=DataFrame) = Query(db, "SELECT name FROM sqlite_master WHERE type='table';") |> sink
513+
tables(db::DB, sink=columntable) = DBInterface.execute(db, "SELECT name FROM sqlite_master WHERE type='table';") |> sink
523514

524515
"""
525-
`SQLite.indices(db, sink=DataFrame)`
516+
`SQLite.indices(db, sink=columntable)`
526517
527518
returns a list of indices in `db`
528519
"""
529-
indices(db::DB, sink=DataFrame) = Query(db, "SELECT name FROM sqlite_master WHERE type='index';") |> sink
520+
indices(db::DB, sink=columntable) = DBInterface.execute(db, "SELECT name FROM sqlite_master WHERE type='index';") |> sink
530521

531522
"""
532-
`SQLite.columns(db, table, sink=DataFrame)`
523+
`SQLite.columns(db, table, sink=columntable)`
533524
534525
returns a list of columns in `table`
535526
"""
536-
columns(db::DB, table::AbstractString, sink=DataFrame) = Query(db, "PRAGMA table_info($(esc_id(table)))") |> sink
527+
columns(db::DB, table::AbstractString, sink=columntable) = DBInterface.execute(db, "PRAGMA table_info($(esc_id(table)))") |> sink
537528

538529
"""
539530
`SQLite.last_insert_rowid(db)`

0 commit comments

Comments
 (0)