Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions docs/src/test/other.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ with the database catalog.
using FunSQL: SQLConnection, SQLCatalog, SQLTable
using Pkg.Artifacts, LazyArtifacts
using SQLite
using Tables

const DATABASE = joinpath(artifact"synpuf-10p", "synpuf-10p.sqlite")

Expand Down Expand Up @@ -42,11 +43,54 @@ a FunSQL-specific `SQLStatement` object.
DBInterface.getconnection(stmt)
#-> SQLConnection( … )

DBInterface.execute(stmt)
#-> SQLite.Query{false}( … )
The output of the statement is wrapped in a FunSQL-specific `SQLCursor`
object.

cr = DBInterface.execute(stmt)
#-> SQLCursor(SQLite.Query{false}( … ))

`SQLCursor` implements standard interfaces by delegating supported methods
to the wrapped cursor object.

eltype(cr)
#-> SQLite.Row

for row in cr
println(row)
end
#=>
SQLite.Row{false}:
:person_id 1780
:year_of_birth 1940
=#

DBInterface.lastrowid(cr)
#-> 0

Tables.schema(cr)
#=>
Tables.Schema:
:person_id Union{Missing, Int64}
:year_of_birth Union{Missing, Int64}
=#

cr = DBInterface.execute(stmt)
display(Tables.rowtable(cr))
#=>
10-element Vector{@NamedTuple{ … }}:
(person_id = 1780, year_of_birth = 1940)
=#

cr = DBInterface.execute(stmt)
display(Tables.columntable(cr))
#-> (person_id = …[1780, … ], year_of_birth = …[1940, … ])

DBInterface.close!(stmt)

DBInterface.close!(cr)

For a query with parameters, this allows us to specify the parameter values
by name.

Expand All @@ -59,7 +103,7 @@ by name.
#-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ), vars = [:YEAR])

DBInterface.execute(stmt, YEAR = 1950)
#-> SQLite.Query{false}( … )
#-> SQLCursor(SQLite.Query{false}( … ))

DBInterface.close!(stmt)

Expand Down
59 changes: 58 additions & 1 deletion src/connections.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,57 @@ Shorthand for [`SQLConnection`](@ref).
"""
const DB = SQLConnection

"""
SQLCursor(raw)

Wraps the query result.
"""
struct SQLCursor{RawCrType} <: DBInterface.Cursor
raw::RawCrType

SQLCursor{RawCrType}(raw::RawCrType) where {RawCrType} =
new(raw)
end

SQLCursor(raw::RawCrType) where {RawCrType} =
SQLCursor{RawCrType}(raw)

function Base.show(io::IO, cr::SQLCursor)
print(io, "SQLCursor(")
show(io, cr.raw)
print(io, ")")
end

Base.eltype(cr::SQLCursor) =
eltype(cr.raw)

Base.IteratorSize(::Type{SQLCursor{RawCrType}}) where {RawCrType} =
Base.IteratorSize(RawCrType)

Base.length(cr::SQLCursor) =
length(cr.raw)

Base.iterate(cr::SQLCursor, state...) =
iterate(cr.raw, state...)

Tables.istable(::Type{SQLCursor{RawCrType}}) where {RawCrType} =
Tables.istable(RawCrType)

Tables.rowaccess(::Type{SQLCursor{RawCrType}}) where {RawCrType} =
Tables.rowaccess(RawCrType)

Tables.rows(cr::SQLCursor) =
Tables.rows(cr.raw)

Tables.columnaccess(::Type{SQLCursor{RawCrType}}) where {RawCrType} =
Tables.columnaccess(RawCrType)

Tables.columns(cr::SQLCursor) =
Tables.columns(cr.raw)

Tables.schema(cr::SQLCursor) =
Tables.schema(cr.raw)

"""
DBInterface.connect(DB{RawConnType},
args...;
Expand Down Expand Up @@ -132,10 +183,16 @@ DBInterface.close!(conn::SQLConnection) =
Execute the prepared SQL statement.
"""
DBInterface.execute(stmt::SQLStatement, params) =
DBInterface.execute(stmt.raw, pack(stmt.vars, params))
SQLCursor(DBInterface.execute(stmt.raw, pack(stmt.vars, params)))

DBInterface.getconnection(stmt::SQLStatement) =
stmt.conn

DBInterface.close!(stmt::SQLStatement) =
DBInterface.close!(stmt.raw)

DBInterface.lastrowid(cr::SQLCursor) =
DBInterface.lastrowid(cr.raw)

DBInterface.close!(cr::SQLCursor) =
DBInterface.close!(cr.raw)
140 changes: 99 additions & 41 deletions src/nodes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,51 @@ struct TransliterateContext
end

"""
Convenient notation for assembling FunSQL queries.
@funsql ex

Assemble a FunSQL query using convenient macro notation.
"""
macro funsql(ex)
ctx = TransliterateContext(__module__, __source__)
transliterate_toplevel(ex, ctx)
if transliterate_is_definition(ex)
transliterate_definition(ex, ctx)
else
transliterate_toplevel(ex, ctx)
end
end

"""
@funsql db ex args...

Assemble and execute a FunSQL query using convenient macro notation.
"""
macro funsql(db, ex, args...)
ctx = TransliterateContext(__module__, __source__)
q = transliterate_toplevel(ex, ctx)
args = Any[transliterate_parameter(arg, ctx) for arg in args]
Expr(:call, DBInterface.execute, esc(db), q, args...)
end

function transliterate_is_definition(@nospecialize(ex))
ex isa Expr || return false
if @dissect(ex, Expr(:(=), Expr(:call, _...), _))
return true
end
if @dissect(ex, Expr(:macrocall, GlobalRef($Core, $(Symbol("@doc"))), _, _, (local arg)))
if @dissect(arg, ::Symbol || Expr(:macrocall, GlobalRef($Core, $(Symbol("@cmd"))), _, _))
return true
end
if @dissect(arg, Expr(:(=), Expr(:call, _...), _))
return true
end
end
if @dissect(ex, Expr(:block, (local args)...))
for arg in args
!(arg isa LineNumberNode) || continue
return transliterate_is_definition(arg)
end
end
return false
end

function transliterate_toplevel(@nospecialize(ex), ctx)
Expand Down Expand Up @@ -672,23 +712,6 @@ function transliterate(@nospecialize(ex), ctx::TransliterateContext)
if @dissect(ex, Expr(:($), (local arg)))
# $(...)
return esc(arg)
elseif @dissect(ex, Expr(:macrocall, (local ref = GlobalRef($Core, $(Symbol("@doc")))), (local ln)::LineNumberNode, (local doc), (local arg)))
# "..." ...
if @dissect(arg, (local name)::Symbol || Expr(:macrocall, GlobalRef($Core, $(Symbol("@cmd"))), ::LineNumberNode, (local name)::String))
arg = Symbol("funsql_$name")
else
ctx = TransliterateContext(ctx, line = ln)
arg = transliterate(arg, ctx)
end
return Expr(:macrocall, ref, ln, doc, arg)
elseif @dissect(ex, Expr(:(=), Expr(:call, (local name)::Symbol || Expr(:macrocall, GlobalRef($Core, $(Symbol("@cmd"))), ::LineNumberNode, (local name)::String), (local args)...), (local body)))
# name(args...) = body
ctx = TransliterateContext(ctx, decl = true)
trs = Any[transliterate(arg, ctx) for arg in args]
ctx = TransliterateContext(ctx, def = Symbol(name), decl = false)
return Expr(:(=),
:($(esc(Symbol("funsql_$name")))($(trs...))),
transliterate_toplevel(body, ctx))
elseif @dissect(ex, Expr(:(=), (local name)::Symbol, (local arg)))
# name = arg
return Expr(:(=), esc(name), transliterate(arg, ctx))
Expand Down Expand Up @@ -777,6 +800,11 @@ function transliterate(@nospecialize(ex), ctx::TransliterateContext)
# ||(args...)
trs = Any[transliterate(arg, ctx) for arg in args]
return :($Fun(:or, args = [$(trs...)]))
elseif @dissect(ex, Expr(:(:=), (local arg1), (local arg2)))
# arg1 := arg2
tr1 = transliterate(arg1, ctx)
tr2 = transliterate(arg2, ctx)
return :($(esc(Symbol("funsql_:=")))($tr1, $tr2))
elseif @dissect(ex, Expr(:call, (local op = :+ || :-), (local arg = :Inf)))
# ±Inf
tr = transliterate(arg, ctx)
Expand All @@ -791,30 +819,16 @@ function transliterate(@nospecialize(ex), ctx::TransliterateContext)
return :($(esc(Symbol("funsql_$name")))($(trs...)))
elseif @dissect(ex, Expr(:block, (local args)...))
# begin; args...; end
if all(@dissect(arg, ::LineNumberNode || Expr(:(=), _...) || Expr(:macrocall, GlobalRef($Core, $(Symbol("@doc"))), _...))
for arg in args)
trs = Any[]
for arg in args
if arg isa LineNumberNode
ctx = TransliterateContext(ctx, base = arg, line = arg)
push!(trs, arg)
else
push!(trs, transliterate(arg, ctx))
end
end
return Expr(:block, trs...)
else
tr = nothing
for arg in args
if arg isa LineNumberNode
ctx = TransliterateContext(ctx, line = arg)
else
tr′ = Expr(:block, ctx.line, transliterate_toplevel(arg, ctx))
tr = tr !== nothing ? :($Chain($tr, $tr′)) : tr′
end
tr = nothing
for arg in args
if arg isa LineNumberNode
ctx = TransliterateContext(ctx, line = arg)
else
tr′ = Expr(:block, ctx.line, transliterate_toplevel(arg, ctx))
tr = tr !== nothing ? :($Chain($tr, $tr′)) : tr′
end
return tr
end
return tr
elseif @dissect(ex, Expr(:if, (local arg1), (local arg2)))
tr1 = transliterate(arg1, ctx)
tr2 = transliterate(arg2, ctx)
Expand All @@ -841,6 +855,50 @@ function transliterate(@nospecialize(ex), ctx::TransliterateContext)
throw(TransliterationError(ex, ctx.line))
end

function transliterate_definition(@nospecialize(ex), ctx)
if ex isa Expr
if @dissect(ex, Expr(:macrocall, (local ref = GlobalRef($Core, $(Symbol("@doc")))), (local ln)::LineNumberNode, (local doc), (local arg)))
# "..." ...
if @dissect(arg, (local name)::Symbol || Expr(:macrocall, GlobalRef($Core, $(Symbol("@cmd"))), ::LineNumberNode, (local name)::String))
arg = Symbol("funsql_$name")
else
ctx = TransliterateContext(ctx, line = ln)
arg = transliterate_definition(arg, ctx)
end
return Expr(:macrocall, ref, ln, doc, arg)
elseif @dissect(ex, Expr(:(=), Expr(:call, (local name)::Symbol || Expr(:macrocall, GlobalRef($Core, $(Symbol("@cmd"))), ::LineNumberNode, (local name)::String), (local args)...), (local body)))
# name(args...) = body
ctx = TransliterateContext(ctx, decl = true)
trs = Any[transliterate(arg, ctx) for arg in args]
ctx = TransliterateContext(ctx, def = Symbol(name), decl = false)
return Expr(:(=),
:($(esc(Symbol("funsql_$name")))($(trs...))),
transliterate_toplevel(body, ctx))
elseif @dissect(ex, Expr(:block, (local args)...))
# begin; args...; end
trs = Any[]
for arg in args
if arg isa LineNumberNode
ctx = TransliterateContext(ctx, base = arg, line = arg)
push!(trs, arg)
else
push!(trs, transliterate_definition(arg, ctx))
end
end
return Expr(:block, trs...)
end
end
throw(TransliterationError(ex, ctx.line))
end

function transliterate_parameter(@nospecialize(ex), ctx)
if @dissect(ex, Expr(:kw || :(=), (local key), (local arg)))
Expr(:kw, esc(key), esc(arg))
else
esc(ex)
end
end


# Concrete node types.

Expand Down
Loading