Skip to content

Commit 088a8a0

Browse files
maltesanderclaude
andcommitted
docs: rewrite the README around the Stackable identity, and correct seven stale claims
The README now follows the same shape as stackable-odbc-trino's: logo, badges, Stackable links line, then what the thing is before how to build it. It is written for a reader who does not already know what ODBC, a driver or SQLite is, since that is who arrives at a driver repository. Two departures from the Trino README, both because the alternative would be false here: it leads with building from source rather than a releases page, there being no tags yet, and every entry under Highlights names something the tests actually exercise. The stale claims, all verified against the code rather than assumed: - info.rs described MAX_FRACTIONAL_SECONDS_PRECISION as 0 in four places ("scale is fixed at 0", "'HH:MM:SS'", "column_size intentionally excludes a fractional-seconds allowance"). It is 3, and the test asserting default_precision_for_type(TIME) == 12 proves the fraction is budgeted. - The C ABI entry points are 60 SQL* functions plus ConfigDSNW on Windows, not 73. - The cancellation section's reason for having no query timeout, that the synchronous execute path has no deadline to arm, is falsified by core's query_timer.rs: QueryTimeout::CoreCancels arms one and calls Backend::cancel, which this driver implements for real. The behaviour is unchanged, so the text now records it as a gap with the preconditions for closing it. - params.rs is a doc-only stub; binding is inline in execute.rs. - CLAUDE.md's file sizes were off by up to 900 lines, and omitted backend.rs. - The Windows "Add" button is not inert: core exports ConfigDSNW and both installers register Setup=, so Add writes a DSN headlessly, silently missing Database. - Core's fetch benchmark moved to bench/benches/. Prose double dashes and em dashes are recast as commas, colons, full stops or parentheses throughout the comments and docs. backend.rs, execute.rs and escape_dialect.rs already used one style and info.rs and the FFI tests the other; now none of them use either. The SQL comments inside query literals and the banner rules are untouched. No CHANGELOG entry: nothing an application can observe changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 9d6b2ee commit 088a8a0

14 files changed

Lines changed: 561 additions & 351 deletions

File tree

Lines changed: 20 additions & 0 deletions
Loading

AGENTS.md

Lines changed: 65 additions & 53 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Project Rules
22

3-
Read and follow @AGENTS.md — it contains architecture, patterns, and procedures.
3+
Read and follow @AGENTS.md, which contains architecture, patterns, and procedures.
44

55
## Non-Negotiable Rules
66

@@ -10,7 +10,7 @@ Read and follow @AGENTS.md — it contains architecture, patterns, and procedure
1010
`get_info_raw`, the catalog functions and the type-conversion paths is
1111
directly observable by applications, and each has a spec-defined shape and
1212
value range. Never claim a SQLSTATE or an info value is wrong without checking
13-
the actual spec table first. Pay attention to **(DM)** annotations those
13+
the actual spec table first. Pay attention to **(DM)** annotations: those
1414
SQLSTATEs are returned by the Driver Manager, not the driver.
1515
- **Route every client error through `map_sqlite_error`.** Never hand-build an
1616
`OdbcError` or `SqliteError` from a `rusqlite::Error` at the call site; that
@@ -19,17 +19,17 @@ Read and follow @AGENTS.md — it contains architecture, patterns, and procedure
1919
diagnostic reports native code `0`.
2020
- **One error type.** Every `Backend` and `StatementBackend` method returns
2121
`Result<_, SqliteError>`. An `OdbcError` core produced travels back through
22-
`SqliteError::Odbc` via `.into()` — never reclassify it, which would discard
22+
`SqliteError::Odbc` via `.into()`. Never reclassify it, which would discard
2323
the SQLSTATE core chose.
2424
- **Declare each capability once.** A `SQLGetInfo` value with a `Backend` hook
2525
is answered through the hook only, never also in `get_info_raw`. Two answers
2626
are a value that can disagree with itself.
27-
- **Use `odbc-sys` types** — never redefine enums, structs, or constants it
27+
- **Use `odbc-sys` types.** Never redefine enums, structs, or constants it
2828
already provides. Reach them through `stackable_odbc_core::types`, or through
2929
`stackable_odbc_core::odbc_sys` for anything `types` does not re-export. Do
3030
**not** add `odbc-sys` as a direct dependency, and do not hand-roll a
3131
`#[repr(C)]` mirror of one of its structs.
32-
- **Convert raw integers to typed enums at the boundary** — use the
32+
- **Convert raw integers to typed enums at the boundary.** Use the
3333
`xxx_from_raw()` functions from core, never `transmute`.
3434
- **Do not make result-set fetching lazy.** `exec_direct` materialises every row
3535
before returning, and two reported ODBC capabilities
@@ -48,20 +48,21 @@ Read and follow @AGENTS.md — it contains architecture, patterns, and procedure
4848

4949
Never read entire files by default. Survey, locate, then extract.
5050

51-
1. **Survey first** — check file size before reading (`stat -c%s file`). Files
52-
>50 KB must be sliced, not read whole. `src/ffi_integration_tests.rs` (~4600
53-
lines), `src/backend/metadata.rs` (~1700), `src/backend/info.rs` (~1600) and
54-
`src/type_conversion.rs` (~1000) are all well over that.
55-
2. **Navigate definitions with ctags** — run `ctags -R .` once to build a tags
51+
1. **Survey first.** Check file size before reading (`stat -c%s file`). Files
52+
>50 KB must be sliced, not read whole. `src/ffi_integration_tests.rs` (~5100
53+
lines), `src/backend/info.rs` (~2500), `src/backend.rs` (~1700),
54+
`src/backend/metadata.rs` (~1500) and `src/type_conversion.rs` (~1000) are
55+
all well over that.
56+
2. **Navigate definitions with ctags.** Run `ctags -R .` once to build a tags
5657
index, then `grep "^SymbolName" tags` to find the exact file and line of any
57-
function, struct, or trait no file reading needed.
58-
3. **Locate with Grep** — find patterns, keywords, or usages before reading. Use
58+
function, struct, or trait, with no file reading needed.
59+
3. **Locate with Grep.** Find patterns, keywords, or usages before reading. Use
5960
`-C` for context lines.
60-
4. **Extract with Read (offset + limit)** — once you know the line range, read
61+
4. **Extract with Read (offset + limit).** Once you know the line range, read
6162
only that slice.
62-
5. **Structured data** — use `jq` for JSON, `yq` for YAML; never read raw markup
63+
5. **Structured data.** Use `jq` for JSON, `yq` for YAML; never read raw markup
6364
whole.
64-
6. **Filesystem survey** — use `tree -L 2 -I '.git|target|node_modules'` instead
65+
6. **Filesystem survey.** Use `tree -L 2 -I '.git|target|node_modules'` instead
6566
of recursive `ls`.
66-
7. **Verify edits with diff** — after editing, `git diff -u` to confirm changes
67+
7. **Verify edits with diff.** After editing, `git diff -u` to confirm changes
6768
instead of re-reading.

README.md

Lines changed: 209 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,246 @@
1-
# stackable-odbc-sqlite
1+
<!-- markdownlint-disable MD041 MD033 -->
22

3-
ODBC 3.x driver for [SQLite](https://sqlite.org), built on the
4-
[stackable-odbc-core](https://github.com/stackabletech/stackable-odbc-core)
5-
framework.
3+
<p align="center">
4+
<img width="150" src="./.readme/static/borrowed/Icon_Stackable.svg" alt="Stackable Logo"/>
5+
</p>
66

7-
The driver compiles to a C dynamic library that an ODBC Driver Manager
8-
(unixODBC on Linux, the built-in Driver Manager on Windows) loads at runtime.
9-
It opens a local SQLite database file through `rusqlite` with the bundled
10-
SQLite library, so it needs no server and no external SQLite installation.
7+
<h1 align="center">Stackable ODBC Driver for SQLite</h1>
118

12-
## Requirements
9+
<p align="center"><em>Open a SQLite file from Excel, DBeaver, LibreOffice or Python, with no server to run.</em></p>
1310

14-
- Rust 1.95.0+ (pinned in `rust-toolchain.toml`)
15-
- Linux: `unixODBC` and `isql` (`pacman -S unixodbc-dev` / `apt install unixodbc-dev`)
16-
- `sqlite3` CLI for creating the test database (`pacman -S sqlite` / `apt install sqlite3`)
11+
[![Build and Test](https://github.com/stackabletech/stackable-odbc-sqlite/actions/workflows/build.yaml/badge.svg)](https://github.com/stackabletech/stackable-odbc-sqlite/actions/workflows/build.yaml)
12+
[![Security Audit](https://github.com/stackabletech/stackable-odbc-sqlite/actions/workflows/security_audit.yaml/badge.svg)](https://github.com/stackabletech/stackable-odbc-sqlite/actions/workflows/security_audit.yaml)
13+
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://docs.stackable.tech/home/stable/contributor/index.html)
14+
[![Apache License 2.0](https://img.shields.io/badge/license-Apache--2.0-green)](./LICENSE)
15+
[![ODBC 3.80](https://img.shields.io/badge/ODBC-3.80-blue)](#what-it-deliberately-does-not-do)
16+
[![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20Windows-blue)](#quick-start)
17+
[![SQLite bundled](https://img.shields.io/badge/SQLite-3.53.2%20bundled-blue)](https://sqlite.org)
1718

18-
## Building
19+
[Stackable Data Platform](https://stackable.tech/) | [Platform Docs](https://docs.stackable.tech/) | [Discussions](https://github.com/orgs/stackabletech/discussions) | [Discord](https://discord.gg/7kZ3BNnCAF)
20+
21+
## What is this?
22+
23+
[SQLite](https://sqlite.org) is a database that lives in a single file. There
24+
is nothing to install and nothing to start: the whole database is one `.db`
25+
file you can copy onto a USB stick. Your phone is running several of them right
26+
now.
27+
28+
Most desktop tools cannot open one of those files directly, but nearly all of
29+
them speak **ODBC**. ODBC is a widely supported standard: a tool loads a small library called a *driver*, calls a fixed set of functions on it, and the driver translates those calls into whatever the actual database understands. Write one driver, and every ODBC-speaking tool on the machine can talk to that database.
30+
31+
This repository is that driver for SQLite. Install it, and Excel, LibreOffice
32+
Base, DBeaver, `isql` and Python's `pyodbc` can query a SQLite file as if it
33+
were a full database server. Linux and Windows are both supported.
34+
35+
Two things make it unusual:
36+
37+
- **It carries its own SQLite.** Version 3.53.2 is compiled straight into the
38+
driver, so there is no separate SQLite to install and no version of it on the
39+
machine that could disagree with the one the driver actually uses.
40+
- **It is a testbed.** Everything generic about being an ODBC driver lives in
41+
[`stackable-odbc-core`](https://github.com/stackabletech/stackable-odbc-core),
42+
which also powers the
43+
[Trino driver](https://github.com/stackabletech/stackable-odbc-trino). SQLite
44+
is small, fast and needs no server, which makes it the ideal backend for
45+
proving that shared framework behaves.
46+
47+
## Quick start
48+
49+
No release has been cut yet, so build the driver yourself. You need Rust (the
50+
version in `rust-toolchain.toml` is installed automatically by `rustup`) and
51+
the unixODBC development headers, because the ODBC bindings link against them:
52+
53+
```bash
54+
sudo apt-get install unixodbc-dev # Debian/Ubuntu
55+
sudo pacman -S unixodbc # Arch
56+
```
57+
58+
Clone this repository:
1959

2060
```bash
21-
cargo build
61+
git clone https://github.com/stackabletech/stackable-odbc-sqlite
62+
cd stackable-odbc-sqlite
63+
cargo build --release
2264
```
2365

24-
Linux output: `target/debug/libstackable_odbc_sqlite.so`.
66+
Output: `target/release/libstackable_odbc_sqlite.so`.
2567

26-
## Connection string parameters
68+
For Windows, cross-compile with MinGW (`gcc-mingw-w64-x86-64`):
2769

28-
| Parameter | Required | Default | Description |
29-
|-----------|----------|---------|-------------|
30-
| Database | Yes | -- | Path to the SQLite database file (`:memory:` for an in-memory database) |
70+
```bash
71+
rustup target add x86_64-pc-windows-gnu
72+
cargo build --release --target x86_64-pc-windows-gnu
73+
```
3174

32-
## Testing
75+
Output: `target/x86_64-pc-windows-gnu/release/stackable_odbc_sqlite.dll`.
3376

34-
Run all commands from the repository root.
77+
### Installing it
78+
79+
`packaging/build-archives.sh` turns those binaries into the same release
80+
archives CI publishes, each with an installer inside:
3581

3682
```bash
37-
# Build the driver, create the test database, write the ODBC config
38-
./test/setup.sh
83+
VERSION=0.0.1 ./packaging/build-archives.sh
84+
```
85+
86+
On Linux, unpack `stackable-odbc-sqlite-<version>-linux-x64.tar.gz` and run
87+
`sudo ./install.sh`. It copies the library into place and registers it with
88+
unixODBC; check it worked with `odbcinst -q -d`, which should list
89+
`[stackable_odbc_sqlite]`.
90+
91+
On Windows, unpack the `.zip` and run `install.bat` from an Administrator
92+
Command Prompt, then look for `stackable_odbc_sqlite` on the Drivers tab of
93+
**ODBC Data Sources (64-bit)**.
94+
95+
The full install, uninstall and DSN reference is in
96+
[`packaging/README.md`](packaging/README.md).
3997

40-
# Connect interactively
41-
export ODBCSYSINI=$(pwd)/test
42-
export ODBCINI=$(pwd)/test/odbc.ini
43-
isql -3 test_sqlite -v
98+
### Then use it
99+
100+
```python
101+
import pyodbc
102+
103+
conn = pyodbc.connect("Driver=stackable_odbc_sqlite;Database=/path/to/your.db")
104+
for row in conn.cursor().execute("SELECT name FROM sqlite_master WHERE type = 'table'"):
105+
print(row.name)
44106
```
45107

46-
Or with a DSN-less connection string:
108+
Or straight from a source checkout, without installing anything at all:
47109

48110
```bash
49-
isql -3 -k "Driver=$(pwd)/target/debug/libstackable_odbc_sqlite.so;Database=$(pwd)/test/test.db" -v
111+
isql -3 -k "Driver=$(pwd)/target/release/libstackable_odbc_sqlite.so;Database=$(pwd)/test/test.db" -v
112+
```
113+
114+
## Highlights
115+
116+
- **The stop button actually stops the query.** Cancelling from your tool calls
117+
SQLite's `sqlite3_interrupt` on the connection, so a runaway query really
118+
stops instead of quietly running to the end while your tool pretends it was
119+
cancelled. The statement reports "operation canceled" and can be re-run.
120+
121+
- **Real transactions.** Turn autocommit off and the driver opens a transaction
122+
for you, then commits or rolls back when you say so and immediately opens the
123+
next one. Your open result sets survive both, because the driver has already
124+
read every row into memory by the time you commit.
125+
126+
- **Foreign keys are switched on.** SQLite ships with foreign-key enforcement
127+
*off* for backwards compatibility, which surprises almost everyone. This
128+
driver turns it on for every connection, so a `REFERENCES` clause in your
129+
schema is a rule the database enforces rather than a comment.
130+
131+
- **Your tool can browse the database.** Tables, views, columns, primary keys,
132+
foreign keys, indexes and row identifiers all show up in the object browser,
133+
read out of SQLite's own `PRAGMA` introspection. So you can click through what
134+
is there instead of guessing table names.
135+
136+
- **Columns get sensible types even though SQLite has almost none.** SQLite is
137+
dynamically typed: any value can go in any column, and there is no `DATE` or
138+
`BOOLEAN` type at all. The driver reads each column's declared type and its
139+
actual storage class and maps them onto proper ODBC types, including the
140+
three different ways SQLite people store a timestamp (ISO text, Unix seconds,
141+
Julian day numbers).
142+
143+
- **Nothing is claimed that was not measured.** What a driver reports about
144+
itself is how tools decide which SQL to send, so guessing wrong there breaks
145+
things in confusing ways. The tests here run the actual SQL to check: the list
146+
of `ALTER TABLE` clauses is verified by executing each one, and the list of
147+
reserved words is read out of the linked SQLite library at runtime instead of
148+
being copied from documentation that can drift.
149+
150+
- **Windows is a real target, not an afterthought.** It gets its own installer,
151+
the DLL is cross-compiled and export-checked on every pull request, and the
152+
test suite can be run through the Windows Driver Manager in a VM, which is far
153+
stricter than unixODBC and tends to fail silently rather than loudly.
154+
155+
## Connecting
156+
157+
Connection strings are `Key=Value` pairs joined by `;`. Keys are
158+
case-insensitive. There is exactly one key:
159+
160+
| Key | Required | Meaning |
161+
|-----|----------|---------|
162+
| `Database` | Yes | Path to the SQLite file, or `:memory:` for a throwaway in-memory database |
163+
164+
```text
165+
Driver=stackable_odbc_sqlite;Database=/path/to/your.db
166+
```
167+
168+
Instead of typing that every time you can save it as a **DSN**, which is just a
169+
named, stored connection, like a browser bookmark. On Linux, add a section to
170+
`~/.odbc.ini`:
171+
172+
```ini
173+
[SQLite Test]
174+
Driver = stackable_odbc_sqlite
175+
Database = /path/to/your.db
50176
```
51177

52-
The test database (`test/test.db`) has a `types_test` table with integer, text,
53-
real, boolean, blob, and text-based datetime columns (see
54-
`test/create_test_db.sql`). The full integration suite runs via
55-
`./test/run-tests.sh` (add `--windows` for the VM suite); see
56-
[AGENTS.md](AGENTS.md#testing) for the complete matrix.
178+
On Windows, see [`packaging/README.md`](packaging/README.md).
57179

58180
### Logging
59181

182+
Two environment variables turn on tracing, which is by far the fastest way to
183+
see which ODBC functions your tool actually calls, and in what order:
184+
60185
```bash
61-
# Log to stderr at debug level
186+
# Levels: trace, debug, info, warn, error
62187
ODBC_LOG_LEVEL=debug isql -3 test_sqlite -v
63188

64-
# Log to a file (levels: trace, debug, info, warn, error)
189+
# Or send it to a file instead of stderr
65190
ODBC_LOG_LEVEL=debug ODBC_LOG_FILE=/tmp/odbc.log isql -3 test_sqlite -v
66191
```
67192

68-
This is invaluable for seeing which ODBC functions are called, and in what order.
193+
## What it deliberately does not do
194+
195+
Every one of these is reported to the application as unsupported rather than
196+
quietly faked, so a tool can react to it instead of trusting a wrong answer.
197+
198+
- **No catalogs and no schemas.** SQLite has neither, so the driver says so
199+
rather than inventing a fake one-level hierarchy for the sake of looking
200+
familiar.
201+
- **No stored procedures.** SQLite has none, so those lookups return nothing.
202+
- **No query timeout.** You can cancel a running statement from another thread,
203+
but asking for "give up after 30 seconds" is answered with "you have no
204+
timeout" and a warning, instead of a promise that would never be kept.
205+
- **Result sets are read into memory in one go.** Simple, and it is what makes
206+
cursors survive a commit or rollback, but a `SELECT` over a table larger than
207+
your RAM is not going to work.
208+
- **One isolation level.** SQLite gives you serializable transactions, so that
209+
is the only level offered, and asking for a weaker one is refused up front
210+
rather than accepted and silently ignored.
211+
- **No setup dialog.** The driver has no GUI, so the **Add** button in Windows'
212+
ODBC administrator stores whatever it was handed without prompting you for a
213+
database path. Create DSNs with `odbcconf` or by editing `odbc.ini` instead.
214+
215+
## Testing
216+
217+
```bash
218+
cargo test # unit and FFI tests; needs no database file and no setup
219+
cargo bench # Criterion fetch-throughput benchmark against :memory:
220+
```
221+
222+
`cargo test` drives the real exported C entry points against real handles, so
223+
it catches the marshalling bugs that ordinary Rust tests cannot.
224+
225+
The integration suite goes one layer further out and runs through real
226+
unixODBC, using Python's `pyodbc` exactly like a normal application would:
227+
228+
```bash
229+
./test/setup.sh # build the driver, create test/test.db, write the ODBC config
230+
./test/run-tests.sh # run the pyodbc suite, then cargo test
231+
```
232+
233+
Both are run on every pull request. `./test/run-tests.sh --windows` additionally
234+
runs the same suite inside a Windows VM; see
235+
[windows/WINDOWS.md](windows/WINDOWS.md) for how to provision one.
236+
237+
For the architecture, the conventions and the full testing reference, see
238+
[AGENTS.md](AGENTS.md).
69239

70240
## Releasing
71241

72-
See [packaging/README.md](packaging/README.md) for building release archives,
73-
and `release.toml` for the `cargo-release` configuration.
242+
See [packaging/README.md](packaging/README.md) for building the release
243+
archives, and `release.toml` for the `cargo-release` configuration.
74244

75245
## License
76246

0 commit comments

Comments
 (0)