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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ LQ_MSSQL_DB=master
LQ_REDIS_HOST=
LQ_REDIS_PORT=6379
LQ_REDIS_PASSWORD=

LQ_CASSANDRA_HOST=
LQ_CASSANDRA_PORT=9042
LQ_CASSANDRA_USER=
LQ_CASSANDRA_PASSWORD=
LQ_CASSANDRA_DC=
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ body:
- SQL Server / Azure SQL
- SQLite
- Redis / Valkey
- Apache Cassandra
- Not related to a specific database
validations:
required: true
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,47 @@ jobs:
- name: MCP tests against a real server
run: cargo test --locked --lib --manifest-path src-tauri/Cargo.toml mcp_redis -- --ignored --test-threads=1

cassandra:
name: Cassandra ${{ matrix.version }} integration
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
version: ["4.1", "5.0"]
services:
cassandra:
image: cassandra:${{ matrix.version }}
env:
MAX_HEAP_SIZE: 1G
HEAP_NEWSIZE: 200M
ports: ["9042:9042"]
# The server takes about a minute to accept CQL connections.
options: >-
--health-cmd "cqlsh -e 'SELECT release_version FROM system.local'"
--health-interval 15s --health-timeout 10s --health-retries 20 --health-start-period 40s
env:
LQ_CASSANDRA_HOST: 127.0.0.1
LQ_CASSANDRA_PORT: "9042"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
workspaces: src-tauri
key: cassandra
- name: Install Linux desktop dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libssl-dev libdbus-1-dev
- run: mkdir -p dist
- name: Integration tests against a real server
run: cargo test --locked --lib --manifest-path src-tauri/Cargo.toml drivers::cassandra -- --ignored --test-threads=1
- name: MCP tests against a real server
run: cargo test --locked --lib --manifest-path src-tauri/Cargo.toml mcp_cassandra -- --ignored --test-threads=1
- name: Export and import round trip
run: cargo test --locked --lib --manifest-path src-tauri/Cargo.toml cassandra_export_import -- --ignored --test-threads=1

# Builds the native Arch package the release publishes. Manual runs only:
# it compiles the whole app, which pull requests do not need.
arch-package:
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to LowQuery are documented here. The format follows [Keep a

## [Unreleased]

### Added
- **Apache Cassandra** (CQL) as a connection type, on the `scylla` native driver with TLS through rustls; it works with Cassandra 3.x, 4.x and 5.x.
- Contact points (several, comma separated), port, optional user and password (kept in the OS credential store), default keyspace, local datacenter and TLS mode.
- Schema browser from `system_schema`: keyspaces (system ones last), tables, materialized views, types, functions, aggregates and indexes, with partition key, clustering and static column markers. Show DDL builds `CREATE` statements from the catalog.
- Exact values: `varint` and `decimal` keep every digit, `timestamp`, `date`, `time` and `duration`, `uuid`, `inet`, blobs as hex, and collections, tuples, UDTs and Cassandra 5 vectors as CQL literals that can be pasted back into an `INSERT`.
- Row limits use paging, so a `SELECT` on a big table reads one page, not the table; server warnings show as messages.
- CQL editor mode, with `BEGIN BATCH ... APPLY BATCH` run as one statement and `$$`, `--`, `//` and `/* */` handled by the script splitter. Cassandra has no transactions: the auto-commit, commit and rollback controls are hidden, and Cancel stops waiting for the answer (CQL has no server-side cancel).
- Grid editing for a single-table `SELECT` that includes the whole primary key; changes are saved as one logged batch.
- Export to SQL, CSV and JSON and import from CSV and JSON (imports run one `INSERT` at a time, with no transaction to roll back), and the AI agents (MCP) tools with a CQL-aware statement classifier.
- DBeaver and DataGrip connection import maps Cassandra connections.

### Fixed
- Importing a JSON file no longer rounds numbers with more than 17 digits.

## [0.5.3] - 2026-10-24

### Added
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ set -a; source ../.env.test; set +a
cargo test --lib -- --ignored --test-threads=1
```

SQLite tests need no server and always run. CI runs the server tests against PostgreSQL 13 and 17, Oracle Database Free, MySQL 8.4, MariaDB 11.4, SQL Server 2022, Redis 7.4 and Valkey 8 in containers, so a pull request shows whether a change works on every engine. For a bug fix, add a regression test that fails without the fix.
SQLite tests need no server and always run. CI runs the server tests against PostgreSQL 13 and 17, Oracle Database Free, MySQL 8.4, MariaDB 11.4, SQL Server 2022, Redis 7.4, Valkey 8 and Apache Cassandra 4.1 and 5.0 in containers, so a pull request shows whether a change works on every engine. For a bug fix, add a regression test that fails without the fix.

## Open a pull request

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

---

LowQuery is an **open-source desktop SQL client** for Windows, Linux and macOS. It connects to **PostgreSQL, Oracle, MySQL, MariaDB, SQL Server, SQLite and Redis**, and treats each one as itself: every engine goes through its own native driver and protocol, with server-side row limits, exact values and the script rules of that engine. Oracle needs **no Oracle Client or Instant Client**. The app is built with Tauri and Rust, so it starts fast and stays small.
LowQuery is an **open-source desktop SQL client** for Windows, Linux and macOS. It connects to **PostgreSQL, Oracle, MySQL, MariaDB, SQL Server, SQLite, Redis and Apache Cassandra**, and treats each one as itself: every engine goes through its own native driver and protocol, with server-side row limits, exact values and the script rules of that engine. Oracle needs **no Oracle Client or Instant Client**. The app is built with Tauri and Rust, so it starts fast and stays small.

| Database | How LowQuery connects |
| --- | --- |
Expand All @@ -34,6 +34,7 @@ LowQuery is an **open-source desktop SQL client** for Windows, Linux and macOS.
| SQL Server and Azure SQL | TDS protocol with [`tiberius`](https://crates.io/crates/tiberius), SQL logins or Windows accounts |
| SQLite | Embedded engine ([`rusqlite`](https://crates.io/crates/rusqlite)), opens local database files |
| Redis and Valkey | RESP3 with [`redis-rs`](https://crates.io/crates/redis): redis-cli commands, results as tables, keys browsed by type |
| Apache Cassandra | CQL native protocol with [`scylla`](https://crates.io/crates/scylla) (Cassandra 3.x to 5.x): keyspaces, tables, views, types and functions from `system_schema`, exact CQL values |

![LowQuery on Windows with the schema browser, SQL editor and result grid](assets/screenshots/lowquery-desktop.png)

Expand Down Expand Up @@ -120,8 +121,9 @@ gh attestation verify LowQuery-<version>-linux-x64.AppImage --repo LowBloat/LowQ
| SQL Server, Azure SQL | `SET ROWCOUNT` for plain SELECT batches | Exact decimal, datetime2 and datetimeoffset precision, uniqueidentifier, binary as hex | Not exposed by the driver yet | TDS attention | `GO` batches, `BEGIN...END` blocks, `[brackets]` |
| SQLite | Stops stepping at the limit | Storage class and declared type; blobs as hex | — | `sqlite3_interrupt` | Trigger bodies, `[brackets]`, backticks |
| Redis, Valkey | Long replies cut at the limit; the tree uses `SCAN` | RESP3 types: maps as field/value, `WITHSCORES` as member/score, `INFO` as rows | — | `CLIENT UNBLOCK` for blocking commands | One redis-cli command per line, `#` comments |
| Apache Cassandra | Paging: the page size comes from the limit and reading stops there | Exact `varint` and `decimal`, `timestamp`, `date`, `time`, `duration`, `uuid`, blobs as hex; collections, tuples, UDTs and vectors as CQL literals | Server warnings (tombstones, aggregations without a partition key) | Stops waiting; CQL cannot cancel on the server | `;`, `BEGIN BATCH ... APPLY BATCH` as one statement, `$$` bodies, `--`, `//` and `/* */` comments |

**Network configuration.** PostgreSQL, MySQL, MariaDB and SQL Server use host, port and database, with `disable`, `prefer`, `require` or `verify-full` TLS. SQL Server also takes a named instance (found through SQL Server Browser) and can sign in with the Windows account running LowQuery. Oracle reads the `tnsnames.ora` found through `TNS_ADMIN`, `ORACLE_HOME`, registered Oracle homes or an Instant Client on `PATH`, or a folder you pick (`IFILE`, `NAMES.DEFAULT_DOMAIN` and Latin-1 files are supported), and accepts TCPS descriptors. SQLite opens or creates a local file. Redis takes host, port, an optional ACL user and the database index.
**Network configuration.** PostgreSQL, MySQL, MariaDB and SQL Server use host, port and database, with `disable`, `prefer`, `require` or `verify-full` TLS. SQL Server also takes a named instance (found through SQL Server Browser) and can sign in with the Windows account running LowQuery. Oracle reads the `tnsnames.ora` found through `TNS_ADMIN`, `ORACLE_HOME`, registered Oracle homes or an Instant Client on `PATH`, or a folder you pick (`IFILE`, `NAMES.DEFAULT_DOMAIN` and Latin-1 files are supported), and accepts TCPS descriptors. SQLite opens or creates a local file. Redis takes host, port, an optional ACL user and the database index. Cassandra takes one or more contact points (comma separated), the port (9042), an optional user and password, a default keyspace, the local datacenter and the TLS mode; it has no transactions, so every statement commits on its own.

Connection profiles are stored in `connections.json` in the user configuration folder (`%APPDATA%\LowBloat\LowQuery\config` on Windows, `~/Library/Application Support/dev.LowBloat.LowQuery` on macOS, `~/.config/lowquery` on Linux). Open query tabs and their SQL are restored on the next start.

Expand Down
15 changes: 8 additions & 7 deletions docs/AI.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ The server is the LowQuery executable started as `lowquery mcp`. It speaks MCP o
| Tool | What it does |
| --- | --- |
| `list_connections` | Connections open to agents: id, name, engine, where it points (never passwords), access and dialect notes |
| `list_schemas` | Schemas of a connection |
| `list_schemas` | Schemas of a connection (keyspaces on Cassandra) |
| `list_tables` | Tables and views of a schema (the current schema by default) |
| `describe_table` | Columns with types, nullability and primary key, plus foreign keys in both directions |
| `search_objects` | Tables, views and columns whose name contains a text, across schemas |
| `describe_table` | Columns with types, nullability and primary key (Cassandra: partition, clustering and static roles), plus foreign keys in both directions |
| `search_objects` | Tables, views and columns whose name contains a text, across schemas (Cassandra: also materialized views and types, from `system_schema`) |
| `query` | One read statement; 100 rows by default, up to 1,000 |
| `explain` | The engine's execution plan, without running the statement |
| `explain` | The engine's execution plan, without running the statement (not available on Redis or Cassandra, which have none) |
| `execute` | One statement of any kind, only on **Read and write** connections |

Results come as a compact table for the model plus structured JSON for clients that use it. Long values are cut at 1,000 characters and a reply at about 60,000, with a note saying what was left out.
Expand All @@ -38,8 +38,8 @@ Results come as a compact table for the model plus structured JSON for clients t

Read-only access does not rely on the agent's good behaviour. Every call goes through several independent checks:

1. **One statement per call.** A lexer that knows each dialect's strings, quoted names and comments refuses text with more than one statement (MySQL executable comments `/*! */` included).
2. **A read classifier.** On read-only connections the statement must start with `SELECT`, `WITH`, `SHOW`, `DESCRIBE`, `EXPLAIN` (no `ANALYZE`), `VALUES`, `TABLE` or a reporting `PRAGMA`. Write keywords anywhere in the statement and functions with side effects (`nextval`, `set_config`, `load_extension`...) are refused, and so are `EXEC` and `SET` on SQL Server.
1. **One statement per call.** A lexer that knows each dialect's strings, quoted names and comments refuses text with more than one statement (MySQL executable comments `/*! */` included). On Cassandra a `BEGIN [UNLOGGED|COUNTER] BATCH ... APPLY BATCH` block counts as one statement, and `$$...$$` strings and `//` comments are understood.
2. **A read classifier.** On read-only connections the statement must start with `SELECT`, `WITH`, `SHOW`, `DESCRIBE`, `EXPLAIN` (no `ANALYZE`), `VALUES`, `TABLE` or a reporting `PRAGMA`; on Cassandra only `SELECT` and `LIST` (`DESCRIBE` is a cqlsh command, not CQL: use the schema tools). Write keywords anywhere in the statement and functions with side effects (`nextval`, `set_config`, `load_extension`...) are refused, and so are `EXEC` and `SET` on SQL Server.
3. **A read-only session in the database itself**, separate from the app's sessions:

| Database | Session | Each call |
Expand All @@ -50,8 +50,9 @@ Read-only access does not rely on the agent's good behaviour. Every call goes th
| SQL Server | implicit transactions | always rolled back |
| SQLite | file opened read-only (`mode=ro`) and `PRAGMA query_only` | rolled back |
| Redis | | allowlist of read commands; `KEYS` refused in favour of `SCAN` |
| Cassandra | | CQL has no read-only transaction: only `SELECT` and `LIST` pass the classifier, and `USE` is refused so no call changes the session keyspace (names are written `keyspace.table`) |

On **Read and write** connections, `execute` runs with autocommit. Transaction control (`BEGIN`, `COMMIT`, `SET autocommit`...) is refused, and a statement that leaves a transaction open is rolled back.
On **Read and write** connections, `execute` runs with autocommit. Transaction control (`BEGIN`, `COMMIT`, `SET autocommit`...) is refused (on Cassandra: `BEGIN TRANSACTION`, `COMMIT`, `ROLLBACK`; `BEGIN BATCH` is allowed), and a statement that leaves a transaction open is rolled back.

Every call has a time limit (30 seconds, or `LOWQUERY_MCP_TIMEOUT` in seconds), after which the statement is cancelled on the server.

Expand Down
Loading
Loading