API reference
Frostlake — Module
FrostlakeA driver for Frostlake, speaking the engine's HTTP protocol against a running DatabaseHttpServer.
using Frostlake
conn = Connection("frostlake://localhost:18082/MY_DB?schema=PUBLIC")
execute(conn, "CREATE TABLE people (id INTEGER, name VARCHAR)")
execute(conn, "INSERT INTO people VALUES (?, ?)", [1, "Ada"])
result = execute(conn, "SELECT name FROM people WHERE id = ?", [1])
scalar(result) # "Ada"
close(conn)Nothing outside the standard library is needed: the JSON reader, the DSN parser and the SQL scanner are all in the package, and the transport is Downloads.
Connecting
Frostlake.Connection — Type
ConnectionA connection to a Frostlake HTTP server, and the engine session behind it.
Statements are serialized: a statement holds the connection until it has an answer, so session state — USE, session variables, an open transaction — carries from one statement to the next, which is exactly what a half-interleaved second statement would break.
The engine can lose the session: it expires after 30 idle minutes, and a restart ends them all. A statement that finds its session gone is sent once more on a fresh session with the DSN's scope, unless the lost session held an open transaction or context of the caller's own — then it throws SessionLostError instead.
Build one with Connection(dsn) and release it with close, which also releases the engine session.
Frostlake.connect — Function
Connection(dsn; kwargs...) -> Connection
Connection(f, dsn; kwargs...)Opens a connection to a Frostlake HTTP server. Frostlake.connect is the same function under the name other drivers use; it is not exported, because connect is Sockets' name too.
conn = Connection("frostlake://localhost:18082/MY_DB?schema=PUBLIC")The server is contacted before this returns: its health endpoint is called, and the database, schema, role and warehouse the DSN names are selected. A name that does not exist is therefore reported here, rather than surfacing later on whichever query happened to run first.
Given a function, the connection is passed to it and closed afterwards:
Connection("frostlake://localhost:18082") do conn
execute(conn, "SELECT 1")
endEvery keyword may also be given in the DSN query string, where an explicit keyword outranks it. Timeouts are seconds, or a fixed-length Dates.Period such as Minute(10); 0 removes the bound.
| Keyword | Meaning |
|---|---|
timeout | how long one statement may take |
connect_timeout | how long to wait for the socket |
idle_limit | against an engine before 0.1.0, how long a connection may idle before its scope is re-applied |
cacert | a CA bundle for HTTPS, instead of the system's |
verify_certificate | false to accept any HTTPS certificate |
Frostlake.ping — Function
ping(conn)Checks that a Frostlake engine is answering, via GET /api/health.
A 200 on its own only says something is listening — anything can serve that. The health payload is what says it is an engine, so a body that is not one is reported rather than passed off as healthy.
Frostlake.apply_dsn_scope — Function
apply_dsn_scope(conn)Selects the database, schema, role and warehouse the DSN names. connect calls this, so it is only worth calling again after the session has been moved somewhere else deliberately.
Frostlake.session_id — Function
The engine's id for this connection's session, once it has one.
Frostlake.in_transaction — Function
Whether a transaction is open — begin_transaction without a matching commit or rollback.
Frostlake.base_url — Function
The base URL of the server, without a trailing slash.
The server this connection speaks to, as scheme://host:port.
Statements
Frostlake.execute — Function
execute(conn, sql; multi_statement_count=nothing) -> Result
execute(conn, sql, parameters; multi_statement_count=nothing) -> ResultRuns one statement and returns its first result set.
parameters fill the statement's placeholders: a vector or tuple for positional ? markers, a dictionary or named tuple for :name markers. The count has to match exactly whenever arguments are supplied; with none at all the markers pass through to the server, where Snowflake Scripting binds them (OPEN c USING (...), EXECUTE IMMEDIATE :v).
execute(conn, "INSERT INTO people VALUES (?, ?)", [1, "Ada"])
execute(conn, "SELECT :a + :b AS total", (a=2, b=40))multi_statement_count says how many statements this one request carries; see execute_all.
A string holding several ;-separated statements answers with the first one's result — use execute_all for the rest.
Frostlake.execute_all — Function
execute_all(conn, sql; multi_statement_count=nothing) -> Vector{Result}
execute_all(conn, sql, parameters; multi_statement_count=nothing) -> Vector{Result}Runs a statement string and returns every result set it produced, in order. A single statement gives a one-element vector.
The engine refuses a request holding more statements than it was told to expect, so a pack says how many it holds:
execute_all(conn, "SELECT 1; SELECT 2"; multi_statement_count=2)The count travels with this one request. It outranks the session's MULTI_STATEMENT_COUNT without changing it, so there is nothing to put back afterwards, and 0 allows any number. Left out, no count is sent at all and the session's value decides.
Transactions
Frostlake.transaction — Function
transaction(f, conn)Runs f(conn) inside BEGIN ... COMMIT, rolling back if it throws and re-raising the original error either way.
transaction(conn) do c
execute(c, "INSERT INTO acc VALUES (1)")
endThe connection is not held for the duration: a transaction lives on the session, so anything else run on this same connection meanwhile joins the transaction. Give a transaction its own connection if that is not what you want.
Frostlake.begin_transaction — Function
begin_transaction(conn)Opens a transaction: autocommit goes off and BEGIN is sent.
Frostlake.commit — Function
Commits the open transaction and restores autocommit.
Frostlake.rollback — Function
Rolls the open transaction back and restores autocommit.
Results
Frostlake.Result — Type
ResultOne result set: what a single statement answered with.
| Field | What it holds |
|---|---|
columns | a ColumnInfo per column, in order |
values | every cell, positionally aligned with columns — the lossless view |
updatecount | rows affected by DML, or -1 when the statement returned data |
counters | the raw number of rows ... counters behind updatecount |
A Result iterates and indexes over its rows, so for row in result and result[1][2] work directly, and rows gives the same rows keyed by column name.
A DML statement answers with a status grid rather than data — one row of number of rows inserted-style counters — and that grid is reported here as it arrived. updatecount is the friendly reading of it.
Frostlake.ColumnInfo — Type
ColumnInfoWhat the engine reported about one column of a result set: its name as the engine cased it, the declared datatype (NUMBER, VARCHAR, TIMESTAMP_NTZ, ...), whether it is nullable, the precision and scale of the fixed-point numerics, and the length of the text and binary ones.
length counts characters for a VARCHAR and bytes for a BINARY, and is what the account's own driver reports as such a column's precision and its display size: VARCHAR(9) is 9. A table column declared without a width carries 16777216 characters or 8388608 bytes; a cast without one, such as 'abc'::VARCHAR, reports 134217728 characters or 67108864 bytes, and a string literal its own length. Only those two families carry it; every other type leaves it nothing.
nullable, precision, scale and length are nothing when the server did not say — an engine that predates a field reports nothing rather than false or 0.
Frostlake.rows — Function
rows(result) -> Vector{Dict{String,Any}}Each row keyed by column name, built on first use and kept.
A dictionary cannot represent two columns called the same thing — a self-join reports ID twice and the later one wins — so result.values stays the lossless view.
Frostlake.scalar — Function
scalar(result) -> AnyThe first cell of the first row, or nothing when there is none — what a single-value query (SELECT COUNT(*), SELECT CURRENT_VERSION()) is after.
Frostlake.rowcount — Function
Rows returned, or rows affected for a DML statement.
Frostlake.isupdate — Function
Whether this result came from a DML statement rather than a query.
Frostlake.columnnames — Function
The column names, in order.
Frostlake.columnindex — Function
columnindex(result, name) -> IntThe position of a column, matched exactly first and case-insensitively after, or 0 when the result has no such column.
Values
Frostlake.ZonedTimestamp — Type
ZonedTimestampA TIMESTAMP_TZ / TIMESTAMP_LTZ value: a wall clock together with the offset it was written at.
Julia's standard library has no zoned type — a DateTime is a wall clock with nowhere to keep an offset — and folding the offset in would lose it, so the two halves are kept side by side. utc(z) gives the instant, which is what to compare two of these by; z.datetime gives the local reading, which is what the engine prints.
z = ZonedTimestamp(DateTime(2024, 1, 15, 10, 30), Dates.Second(3600))
utc(z) # 2024-01-15T09:30:00Frostlake.utc — Function
utc(z::ZonedTimestamp) -> DateTimeThe instant z names, as a UTC wall clock. Two ZonedTimestamps written at different offsets are the same moment when their utc agree.
Errors
Frostlake.FrostlakeError — Type
FrostlakeErrorSupertype of every failure this driver raises, so a single
try
execute(conn, sql)
catch e
e isa FrostlakeError || rethrow()
endcatches the lot. The subtypes say which kind it was.
Frostlake.QueryError — Type
QueryError <: FrostlakeErrorThe engine rejected a statement: it compiled badly, referenced something that does not exist, or failed while running. message is the engine's own wording, unmodified.
Frostlake.ConnectionError — Type
ConnectionError <: FrostlakeErrorThe server could not be reached, the request died mid-flight, or what came back was not a Frostlake response at all.
A statement that failed this way has an unknown fate: the request may have arrived and run before the connection broke, so it must not be blindly retried — re-running an INSERT would duplicate it.
Fields: message, endpoint (the URL called, when the failure names one), status (the HTTP status, or nothing when the request never completed) and cause (the underlying transport error, when there was one).
Frostlake.SessionLostError — Type
SessionLostError <: FrostlakeErrorThe engine no longer holds the connection's session — it expired, was released, or the server restarted — and the statement did not run.
A lost session is replaced without a word when nothing went with it: the DSN's scope goes onto a fresh session and the statement is sent once more. This is thrown instead when the lost session held something a fresh one cannot reproduce — an open transaction, or context set up with USE, SET, ALTER SESSION or a temporary object — because re-running the statement would put it somewhere its author did not intend.
The connection stays usable: the next statement starts a fresh session on the DSN's scope. Whatever the unit of work had done in the lost session is gone, so it is the unit of work that has to start over.
statement is the SQL that did not run, after parameter substitution — as sensitive as QueryError.statement.
Frostlake.UsageError — Type
UsageError <: FrostlakeErrorThe driver was asked for something impossible and never sent a request: a malformed DSN, a closed connection, a bind value with no SQL equivalent, an argument count that does not match the placeholders.