Other Tests
SQLConnection
and SQLStatement
A SQLConnection
object encapsulates a raw database connection together with the database catalog.
using FunSQL: SQLConnection, SQLCatalog, SQLTable
using Pkg.Artifacts, LazyArtifacts
using SQLite
const DATABASE = joinpath(artifact"synpuf-10p", "synpuf-10p.sqlite")
raw_conn = DBInterface.connect(SQLite.DB, DATABASE)
person = SQLTable(:person, columns = [:person_id, :year_of_birth])
catalog = SQLCatalog(person, dialect = :sqlite)
conn = SQLConnection(raw_conn, catalog = catalog)
#-> SQLConnection(SQLite.DB( … ), catalog = SQLCatalog(…1 table…, dialect = SQLDialect(:sqlite)))
SQLConnection
delegates DBInterface
calls to the raw connection object.
DBInterface.prepare(conn, "SELECT * FROM person")
#-> SQLite.Stmt( … )
DBInterface.execute(conn, "SELECT * FROM person")
#-> SQLite.Query{false}( … )
When DBInterface.prepare
is applied to a query node, it returns a FunSQL-specific SQLStatement
object.
using FunSQL: From
q = From(:person)
stmt = DBInterface.prepare(conn, q)
#-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ))
DBInterface.getconnection(stmt)
#-> SQLConnection( … )
DBInterface.execute(stmt)
#-> SQLite.Query{false}( … )
DBInterface.close!(stmt)
For a query with parameters, this allows us to specify the parameter values by name.
using FunSQL: Get, Var, Where
q = From(:person) |>
Where(Get.year_of_birth .>= Var.YEAR)
stmt = DBInterface.prepare(conn, q)
#-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ), vars = [:YEAR])
DBInterface.execute(stmt, YEAR = 1950)
#-> SQLite.Query{false}( … )
DBInterface.close!(stmt)
DBInterface.close!(conn)
SQLCatalog
and SQLTable
In FunSQL, tables and table-like entities are represented using SQLTable
objects. A collection of SQLTable
objects is represented as a SQLCatalog
object.
using FunSQL: SQLCatalog, SQLTable
A SQLTable
constructor takes the table name, a vector of column names, and, optionally, the name of the table schema and other qualifiers. A name could be provided either as a Symbol
or as a String
value.
location = SQLTable(qualifiers = [:public],
name = :location,
columns = [:location_id, :address_1, :address_2,
:city, :state, :zip])
#-> SQLTable(:location, qualifiers = [:public], …)
person = SQLTable(name = "person",
columns = ["person_id", "year_of_birth", "location_id"])
#-> SQLTable(:person, …)
The table and the column names could be provided as positional arguments.
vocabulary = SQLTable(:vocabulary,
columns = [:vocabulary_id, :vocabulary_name])
#-> SQLTable(:vocabulary, …)
concept = SQLTable("concept", "concept_id", "concept_name", "vocabulary_id")
#-> SQLTable(:concept, …)
A SQLTable
object is displayed as a Julia expression that created the object.
display(location)
#=>
SQLTable(:location,
qualifiers = [:public],
columns = [:location_id, :address_1, :address_2, :city, :state, :zip])
=#
display(person)
#=>
SQLTable(:person, columns = [:person_id, :year_of_birth, :location_id])
=#
A SQLCatalog
constructor takes a collection of SQLTable
objects, the target dialect, and the size of the query cache.
catalog = SQLCatalog(tables = [person, location, vocabulary, concept],
dialect = :sqlite,
cache = 128)
#-> SQLCatalog(…4 tables…, dialect = SQLDialect(:sqlite), cache = 128)
display(catalog)
#=>
SQLCatalog(
:concept => SQLTable(:concept,
columns =
[:concept_id, :concept_name, :vocabulary_id]),
:location =>
SQLTable(
:location,
qualifiers = [:public],
columns =
[:location_id, :address_1, :address_2, :city, :state, :zip]),
:person => SQLTable(:person,
columns = [:person_id, :year_of_birth, :location_id]),
:vocabulary => SQLTable(:vocabulary,
columns = [:vocabulary_id, :vocabulary_name]),
dialect = SQLDialect(:sqlite),
cache = 128)
=#
Number of tables in the catalog affects its representation.
SQLCatalog(tables = [:person => person])
#-> SQLCatalog(…1 table…, dialect = SQLDialect())
SQLCatalog()
#-> SQLCatalog(dialect = SQLDialect())
The query cache can be completely disabled.
cacheless_catalog = SQLCatalog(cache = nothing)
#-> SQLCatalog(dialect = SQLDialect(), cache = nothing)
display(cacheless_catalog)
#-> SQLCatalog(dialect = SQLDialect(), cache = nothing)
Any Dict
-like object can serve as a query cache.
customcache_catalog = SQLCatalog(cache = Dict())
#-> SQLCatalog(dialect = SQLDialect(), cache = Dict{Any, Any}())
display(customcache_catalog)
#-> SQLCatalog(dialect = SQLDialect(), cache = (Dict{Any, Any})())
The catalog behaves as a read-only Dict
object.
catalog[:person]
#-> SQLTable(:person, …)
catalog["person"]
#-> SQLTable(:person, …)
catalog[:visit_occurrence]
#-> ERROR: KeyError: key :visit_occurrence not found
get(catalog, :person, nothing)
#-> SQLTable(:person, …)
get(catalog, "person", nothing)
#-> SQLTable(:person, …)
get(catalog, :visit_occurrence, missing)
#-> missing
get(() -> missing, catalog, :visit_occurrence)
#-> missing
length(catalog)
#-> 4
sort(collect(keys(catalog)))
#-> [:concept, :location, :person, :vocabulary]
SQLDialect
In FunSQL, properties and capabilities of a particular SQL dialect are encapsulated in a SQLDialect
object.
using FunSQL: SQLDialect
The desired dialect can be specified by name.
postgresql_dialect = SQLDialect(:postgresql)
#-> SQLDialect(:postgresql)
display(postgresql_dialect)
#-> SQLDialect(:postgresql)
If necessary, the dialect can be customized.
postgresql_odbc_dialect = SQLDialect(:postgresql,
variable_prefix = '?',
variable_style = :positional)
#-> SQLDialect(:postgresql, …)
display(postgresql_odbc_dialect)
#-> SQLDialect(:postgresql, variable_prefix = '?', variable_style = :POSITIONAL)
The default dialect does not correspond to any particular database server.
default_dialect = SQLDialect()
#-> SQLDialect()
display(default_dialect)
#-> SQLDialect()
A completely custom dialect can be specified.
my_dialect = SQLDialect(:my, identifier_quotes = ('<', '>'))
#-> SQLDialect(name = :my, …)
display(my_dialect)
#-> SQLDialect(name = :my, identifier_quotes = ('<', '>'))
SQLString
SQLString
represents a serialized SQL query.
using FunSQL: SQLString, pack
sql = SQLString("SELECT * FROM person")
#-> SQLString("SELECT * FROM person")
display(sql)
#-> SQLString("SELECT * FROM person")
SQLString
implements the AbstractString
interface.
ncodeunits(sql)
#-> 20
codeunit(sql)
#-> UInt8
codeunit(sql, 1)
#-> 0x53
isvalid(sql, 1)
#-> true
join(collect(sql))
#-> "SELECT * FROM person"
print(sql)
#-> SELECT * FROM person
write(IOBuffer(), sql)
#-> 20
String(sql)
#-> "SELECT * FROM person"
When the query has parameters, SQLString
should include a vector of parameter names in the order they should appear in DBInterface.execute
call.
sql = SQLString("SELECT * FROM person WHERE year_of_birth >= ?", vars = [:YEAR])
#-> SQLString("SELECT * FROM person WHERE year_of_birth >= ?", vars = [:YEAR])
display(sql)
#-> SQLString("SELECT * FROM person WHERE year_of_birth >= ?", vars = [:YEAR])
Function pack
converts named parameters to the positional form suitable for use with DBInterface.execute
.
pack(sql, (; YEAR = 1950))
#-> Any[1950]
pack(sql, Dict(:YEAR => 1950))
#-> Any[1950]
pack(sql, Dict("YEAR" => 1950))
#-> Any[1950]
pack
can also be applied to a regular string, in which case it returns the parameters unchanged.
pack("SELECT * FROM person WHERE year_of_birth >= ?", (1950,))
#-> (1950,)