Skip to content

Commit dae091e

Browse files
authored
Merge pull request #1 from queryverse/drop-df-dep
I might do some renaming just because I have an obsessive need to avoid abbreviations
2 parents c5c95da + f51c79d commit dae091e

3 files changed

Lines changed: 107 additions & 28 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ QueryableBackend = "0898d9ac-042b-5d31-8dcc-959fb3365f19"
99
QueryOperators = "2aef5ad7-51ca-5a8f-8e88-e75cf067b44b"
1010
SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9"
1111
TableTraits = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
12+
DataValues = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5"
13+
TableShowUtils = "5e66a065-1f0a-5976-b372-e0b8c017ca10"
1214

1315
[extras]
1416
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/QuerySQLite.jl

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import Base: !, &, |, ==, !=, coalesce, getproperty, in, isequal, isless, ismiss
44
using Base: Generator, NamedTuple, tail
55
import Base.Iterators: drop, take
66
using Base.Meta: quot
7-
import DataFrames: DataFrame
87
import MacroTools
98
using MacroTools: @capture
109
import QueryOperators
1110
import QueryOperators: orderby, query
1211
import SQLite
1312
using SQLite: columns, DB, tables
13+
import IteratorInterfaceExtensions, TableTraits
14+
using DataValues
15+
import TableShowUtils
1416

1517
map_unrolled(call, variables::Tuple{}) = ()
1618
map_unrolled(call, variables) =
@@ -56,14 +58,6 @@ get_column_names(outside::DB, table_name) =
5658
as_symbols(columns(outside, String(table_name)).name)
5759
export get_column_names
5860

59-
"""
60-
submit_to(outside, text)
61-
62-
Send `text` to `outside`
63-
"""
64-
submit_to(outside::DB, text) = DataFrame(Query(outside, text))
65-
export submit_to
66-
6761
"""
6862
abstract type OutsideTables{Outside} end
6963
@@ -398,14 +392,11 @@ translate_call(::typeof(take), iterator, number) =
398392

399393
# SQLite interface
400394

401-
using DataFrames: DataFrame
402-
403395
to_symbols(them) = map_unrolled(Symbol, (them...,))
404396

405397
get_table_names(database::DB) = to_symbols(tables(database).name)
406398
get_column_names(database::DB, table_name) =
407399
to_symbols(SQLite.columns(database, String(table_name)).name)
408-
submit_to(database::DB, text) = DataFrame(SQLite.Query(database, text))
409400

410401
# dispatch
411402

@@ -440,10 +431,92 @@ translate(code::Expr) =
440431
# collect
441432
query(outside_code::OutsideCode) = outside_code
442433

443-
DataFrame(outside_code::OutsideCode) =
444-
submit_to(
445-
outside_code.outside,
446-
translate(outside_code.code)
447-
)
434+
struct SQLiteCursor{T}
435+
stmt::SQLite.Stmt
436+
status::Base.RefValue{Cint}
437+
cur_row::Base.RefValue{Int}
438+
end
439+
440+
Base.eltype(q::SQLiteCursor{T}) where {T} = T
441+
Base.IteratorSize(::Type{<:SQLiteCursor}) = Base.SizeUnknown()
442+
443+
function isdone(q::SQLiteCursor)
444+
st = q.status[]
445+
st == SQLite.SQLITE_DONE && return true
446+
st == SQLite.SQLITE_ROW || SQLite.sqliteerror(q.stmt.db)
447+
return false
448+
end
449+
450+
function SQLite.getvalue(q::SQLiteCursor, col::Int, ::Type{T}) where {T}
451+
handle = q.stmt.handle
452+
t = SQLite.sqlite3_column_type(handle, col)
453+
if t == SQLite.SQLITE_NULL
454+
return T()
455+
else
456+
TT = SQLite.juliatype(t) # native SQLite Int, Float, and Text types
457+
return SQLite.sqlitevalue(ifelse(TT === Any && !isbitstype(T), T, TT), handle, col)
458+
end
459+
end
460+
461+
462+
463+
function Base.iterate(q::SQLiteCursor{NT}) where {NT}
464+
isdone(q) && return nothing
465+
nt = SQLite.generate_namedtuple(NT, q)
466+
q.cur_row[] = 1
467+
return nt, 1
468+
end
469+
470+
function Base.iterate(q::SQLiteCursor{NT}, state) where {NT}
471+
state != q.cur_row[] && error("FOO")
472+
q.status[] = SQLite.sqlite3_step(q.stmt.handle)
473+
isdone(q) && return nothing
474+
nt = SQLite.generate_namedtuple(NT, q)
475+
q.cur_row[] = state + 1
476+
return nt, state + 1
477+
end
478+
479+
IteratorInterfaceExtensions.isiterable(::OutsideCode) = true
480+
TableTraits.isiterabletable(::OutsideCode) = true
481+
482+
Base.collect(source::OutsideCode) = collect(IteratorInterfaceExtensions.getiterator(source))
483+
484+
function IteratorInterfaceExtensions.getiterator(outside_code::OutsideCode)
485+
# TODO REVIEW
486+
stricttypes = true
487+
nullable = true
488+
489+
stmt = SQLite.Stmt(outside_code.outside, translate(outside_code.code))
490+
# bind!(stmt, values)
491+
status = SQLite.execute!(stmt)
492+
cols = SQLite.sqlite3_column_count(stmt.handle)
493+
header = Vector{Symbol}(undef, cols)
494+
types = Vector{Type}(undef, cols)
495+
for i = 1:cols
496+
header[i] = Symbol(unsafe_string(SQLite.sqlite3_column_name(stmt.handle, i)))
497+
if nullable
498+
types[i] = stricttypes ? DataValue{SQLite.juliatype(stmt.handle, i)} : Any
499+
else
500+
types[i] = stricttypes ? SQLite.juliatype(stmt.handle, i) : Any
501+
end
502+
end
503+
return SQLiteCursor{NamedTuple{Tuple(header), Tuple{types...}}}(stmt, Ref(status), Ref(0))
504+
end
505+
506+
function Base.show(io::IO, source::OutsideCode)
507+
TableShowUtils.printtable(io, IteratorInterfaceExtensions.getiterator(source), "SQLite query result")
508+
end
509+
510+
function Base.show(io::IO, ::MIME"text/html", source::OutsideCode)
511+
TableShowUtils.printHTMLtable(io, IteratorInterfaceExtensions.getiterator(source))
512+
end
513+
514+
Base.Multimedia.showable(::MIME"text/html", source::OutsideCode) = true
515+
516+
function Base.show(io::IO, ::MIME"application/vnd.dataresource+json", source::OutsideCode)
517+
TableShowUtils.printdataresource(io, IteratorInterfaceExtensions.getiterator(source))
518+
end
519+
520+
Base.Multimedia.showable(::MIME"application/vnd.dataresource+json", source::OutsideCode) = true
448521

449522
end # module

test/runtests.jl

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,47 @@
11
using Query
2-
using Test: @test, @testset
2+
using Test
33
using QuerySQLite
44
using SQLite: DB
5-
using DataFrames: DataFrame
5+
using QueryTables
66

77
filename = joinpath(@__DIR__, "Chinook_Sqlite.sqlite")
88
database = NamedTuple(OutsideTables(DB(filename)))
99

1010
@testset "QuerySQLite" begin
1111

12-
@test names(database.Track |>
12+
@test database.Track |>
1313
@map({_.TrackId, _.Name, _.Composer, _.UnitPrice}) |>
14-
DataFrame) == [:TrackId, :Name, :Composer, :UnitPrice]
14+
collect |>
15+
first |>
16+
propertynames == (:TrackId, :Name, :Composer, :UnitPrice)
1517

1618
@test (database.Customer |>
1719
@map({_.City, _.Country}) |>
1820
@orderby(_.Country) |>
19-
DataFrame).Country[1] == "Argentina"
21+
DataTable).Country[1] == "Argentina"
2022

21-
@test length((database.Customer |>
23+
@test database.Customer |>
2224
@map({_.City}) |>
2325
@unique() |>
24-
DataFrame).City) == 53
26+
collect |>
27+
length == 53
2528

26-
@test length((database.Track |>
29+
@test database.Track |>
2730
@map({_.TrackId, _.Name}) |>
2831
@take(10) |>
29-
DataFrame).Name) == 10
32+
collect |>
33+
length == 10
3034

3135
@test first((database.Track |>
3236
@map({_.TrackId, _.Name}) |>
3337
@drop(10) |>
3438
@take(10) |>
35-
DataFrame).Name) == "C.O.D."
39+
DataTable).Name) == "C.O.D."
3640

3741
@test first((database.Track |>
3842
@map({_.TrackId, _.Name, _.Bytes}) |>
3943
@orderby_descending(_.Bytes) |>
4044
@thenby(_.Name) |>
41-
DataFrame).Bytes) == 1059546140
45+
DataTable).Bytes) == 1059546140
4246

4347
end

0 commit comments

Comments
 (0)