Metadata-Version: 2.4
Name: frostlake
Version: 0.2.1
Summary: Pure-stdlib DB-API 2.0 (PEP 249) driver for Frostlake over its HTTP protocol
Author: MLorek
License-Expression: Apache-2.0
Project-URL: Homepage, https://frostlake.dev
Project-URL: Source, https://github.com/Frostlake-DB/frostlake-python
Project-URL: Issues, https://github.com/Frostlake-DB/frostlake-python/issues
Keywords: frostlake,sql,database,dbapi,pep249,driver,client
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Database
Classifier: Topic :: Database :: Front-Ends
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# frostlake (Python driver)

A pure-stdlib DB-API 2.0 (PEP 249) driver for [Frostlake](https://frostlake.dev), speaking
the engine's HTTP protocol against a running `DatabaseHttpServer`. No JVM, no dependencies.

## Engine version

Requires a Frostlake engine **0.0.7 or newer**. Ask a running server which one it is with
`SELECT CURRENT_VERSION()` — every release answers it, so the check works against any engine.

The driver versions independently of the engine: it speaks the HTTP protocol, not
the jar, so this is a floor rather than a lockstep pin.

## Usage

```python
import frostlake

conn = frostlake.connect("frostlake://localhost:18082/MY_DB?schema=PUBLIC")
cur = conn.cursor()
cur.execute("SELECT id, name FROM people WHERE id = ?", (1,))
for row in cur:
    print(row)
```

`connect()` accepts a DSN (`frostlake://host:port[/DATABASE][?schema=SCHEMA]`) or
`host=`/`port=`/`database=`/`schema=` keywords. The DSN's database/schema apply as `USE`
statements on the connection's session before its first statement.

Those names follow SQL's own rule: written plainly, a name folds to upper case, so
`database="my_db"` selects `MY_DB`. To reach an object whose real name is lower- or
mixed-case, include the double quotes — `database='"my_db"'`, or
`frostlake://host:port/"my_db"` — and it is used exactly as written. A name that cannot
be written bare (a space, a leading digit) is quoted for you.

## Semantics

- `paramstyle = "qmark"`; parameters are inlined client-side with the same rules as
  Frostlake's JDBC driver (strings escape backslashes and quotes; `bytes` binds as a hex
  `BINARY` literal; `datetime`/`date`/`time` as typed literals; lists as array literals).
  A `?` inside a string literal, quoted identifier or comment is never a placeholder.
- **Types**: fixed-point `NUMBER`/`DECIMAL` with a scale → `decimal.Decimal` carrying the
  exact digits the server sent; scale-0 numerics → `int`; `FLOAT`/`DOUBLE`/`REAL` →
  `float`; `BOOLEAN` → `bool`; `DATE`/`TIME`/`TIMESTAMP*` → `datetime.date`/`time`/
  `datetime`; semi-structured cells (`VARIANT`/`OBJECT`/`ARRAY`) as the JSON text the
  engine returns.
- **Column sizes**: `description[i][3]` (`internal_size`) is the column's length —
  characters for a text column, bytes for a binary one — when the server sends one, and
  `None` for every other type and for engines predating the field. `display_size` stays
  `None` throughout, as it does in the account's own Python client: the server sends no
  display width, so there is none to report.
- **Type objects and constructors**: the PEP 249 singletons `STRING`, `BINARY`, `NUMBER`,
  `DATETIME`, `ROWID` compare equal to the engine type names in their family, so
  `cur.description[i][1] == frostlake.NUMBER` works (parameterized spellings like
  `NUMBER(38,10)` included). `Date`, `Time`, `Timestamp`, the `*FromTicks` variants and
  `Binary` are all present. `ROWID` matches nothing — the engine has no rowid.
- **Multi-statement**, once the call or the session asks for it: as on the account, a request
  carries one statement unless something says otherwise, and a pack sent without asking is
  refused. `cursor.execute(sql, num_statements=n)` declares how many statements that one call
  carries — `0` for any number — the way the account's own connector spells it: the count
  travels with that request, outranks the session's `MULTI_STATEMENT_COUNT` for it, and moves
  no session state, so there is nothing to put back and other cursors on the connection are
  unaffected. `ALTER SESSION SET MULTI_STATEMENT_COUNT = n` still sets it for the session;
  left out, nothing is sent and the session's value decides, which is 1 until it is told
  otherwise. `execute()` exposes the first result set; `cursor.nextset()` steps to the next
  and returns `None` once the last one is current.
- **Transactions**: connections start in autocommit rather than the strict DB-API
  default; `conn.begin()` or `conn.autocommit = False` for explicit transactions, then
  `commit()`/`rollback()`. With autocommit off the connection stays transactional —
  ending one transaction opens the next.
- `cursor.rowcount` is derived from the engine's one-cell DML result
  (`number of rows inserted` / `updated` / `deleted`). `cursor.lastrowid` is always `None`.
- `cursor.callproc(name, params)` issues `CALL name(...)` and returns the input sequence
  unchanged; the engine has no OUT parameters, so any result is read with the fetch methods.
- Using a closed cursor or connection raises `InterfaceError`; fetching before any
  `execute()` raises `ProgrammingError`.
- The exception classes are also reachable as connection attributes (`conn.Error`, …).
- One HTTP session per connection.

## Tests

`test_frostlake_unit.py` needs no server and no JVM:

```bash
python3 -m unittest test_frostlake_unit -v
```

`test_frostlake.py` boots a real engine and needs both:

```bash
JAVA_HOME=~/.jdks/liberica-17.0.18 \
FROSTLAKE_CLASSPATH="<engine classes>:<dependency classpath>" \
python3 -m unittest -v
```

Unset `FROSTLAKE_CLASSPATH` skips the integration suite; the unit suite still runs.
