# Changelog The version lives in `rowset-studio/package.json`. `scripts/build-local-binary.sh` and `scripts/package-macos.sh` stamp it into Studio (sidebar), the `rowset` executable and the macOS app. Every change set bumps the patch version and adds an entry here; after x.y.99 the next version is x.(y+1).0. ## 0.1.11 — 2026-10-11 - Fixed: a statement without a WHERE clause ran unguarded when another statement in the same script had one. A script is read as one statement carrying the riskiest kind, and update, insert and delete rank the same, so `UPDATE a SET x=1 WHERE id=1; DELETE FROM b` was judged an update with a WHERE and the delete emptied a table the guardrail would have refused on its own. Every statement's own missing WHERE is now recorded and judged, so the rules against a DELETE, an UPDATE or a SELECT without one apply to each statement of a script. - Fixed: result column lineage on MySQL and MariaDB dialed the database directly instead of going through the connection's SSH tunnel, so over a tunnel no result was editable and every statement paid a failed connection attempt first. It now reaches the database the same way the pool does, and a resolved lineage is cached per connection and statement and dropped with the schema it describes - one prepare, catalog read or describe for a statement that is run again rather than one per run, on every engine. - Fixed: a query whose columns could not be read left its pinned database connection behind. One connection of the pool was lost for the life of the process each time. - Fixed: a MongoDB find or aggregate, a Redis scan and an Elasticsearch search were evaluated as if an administrator had asked for them, so a role-scoped rule and a read-only connection were not applied to document reads the way they are to SQL. All of them now go through the caller's own role, like every write already did. - Fixed: a row backup taken inside a manual transaction on CockroachDB was not protected by a savepoint, so a backup read that failed left the caller's transaction aborted. - Fixed: a write that returns rows - `UPDATE … RETURNING`, `DELETE … OUTPUT` - kept its row backup when the statement failed, leaving a saved copy of rows nothing had changed. - Fixed: a request could use a manual transaction that the idle reaper rolled back in the same moment, because the transaction was only marked in use after its request body had been read. The claim is now taken under the lock the idle check uses, for SQL and MongoDB transactions alike. - Fixed: a CSV import wrote binary values into SQLite and DuckDB as the text `'\x…'` instead of bytes. Both now use the engine's own binary literal, the one a row-backup restore already used. - Fixed: shutdown could return while a notification or a background loop was still reading the store, which left a half-written database file behind. Close now waits for them. - Changed: a command-line assistant (`claude`, `codex`) is offered and accepted only on a personal workspace. On a shared server the command would run on the server, as the server, for whoever asked. - Changed: a full queue no longer holds shutdown up for the whole activity enqueue wait; a request waiting for room stops waiting and writes its own record. - Performance: listing connections read every connection's nodes and resolved its grant one connection at a time, twice over - a workspace with twenty connections spent dozens of round trips on one listing. Nodes are read once and each grant resolved once. - Performance: a result read allocates one slice per row instead of two, and dragging the editor's panel borders no longer re-renders the result grid or writes to local storage on every pointer move. The grid itself only renders when its result changes. - Performance: a run reloads the schema when the statement really creates, alters, drops or truncates something; the word inside a string, a quoted name or a comment used to be enough. - Fixed: the result grid measured its column widths from the first rows of a streaming result and never again, so columns stayed as wide as that first chunk made them. - Internal: one implementation each for decrypting a row backup (five copies), quoting an identifier (two in Go, two in TypeScript), the NoSQL read guard (four), the policy scope picker (two) and the panel drag handle (two). Removed `writeDatabaseError`, `nullable`, `placeholders`, `mongoUpdateQuery` and `mongoDeleteQuery`, none of which had a caller. ## 0.1.10 — 2026-10-10 - Fixed: a statement held for review and then approved ran without a row backup. `ExecuteDeferred` took the live query path's access checks, policies, hooks, row limits and activity recording, but not its backup, so the one write that had been looked at by a second person was the one write that could not be put back. It now saves the rows an eligible UPDATE or DELETE is about to change, and a write whose rows cannot be saved does not run: nobody is waiting at that point to be asked whether to go on without one. A failed statement discards its backup, as on the live path. - Changed: a DELETE with no WHERE clause is backed up as the whole table, where previously no backup was taken. The default policy refuses that statement, so the only way it runs is that someone deliberately allowed it, which makes it the write most worth being able to undo. `DELETE … LIMIT` and `DELETE … ORDER BY … LIMIT` keep no backup: the engine removes an arbitrary few rows, so the rows read beforehand are not the rows deleted and putting them back would collide with the ones that stayed. - Changed: an administrator reaches every row backup in the organisation - the list, Restore and the script - where before each backup was readable only by the person whose statement made it. An approved write runs for its author, and on a production connection that author's own Restore is itself a production write and is held, so the person who cleared the write was the one person who could not undo it. The list says who each backup belongs to; the values stay encrypted until a restore or the admin-only script. - Added: `Kit.EvaluateStatement` reports what the policies say about a statement the caller has not run. An escalation handler uses it to record why a statement needs review, so whoever reviews it sees the rule that stopped it instead of only the requester's own words. ## 0.1.9 — 2026-10-06 - Fixed: a result on screen kept a processor core busy for as long as the window stayed open. The grid reported how many rows a filter leaves from an effect, and the editor answered with a fresh object through a callback it rebuilt on every render: the effect re-ran because the callback was new, the answer re-rendered the editor because the object was new, and the two drove each other at full speed. Nothing looked wrong, which is why it went unnoticed. The count is now reported through a callback that keeps its identity, and it leaves the state alone when the numbers have not moved. `npm run test:e2e:idle` measures what the editor spends with a result shown and nobody typing, and fails if a loop comes back. ## 0.1.8 — 2026-10-06 - Fixed: a script's token positions pointed into the statements it was made of rather than into the script, so anything that reads a statement back out of its own text by position would have sliced the wrong text, silently. The whole script is lexed instead, and a test holds every token to the text it claims. Nothing in Studio was affected - the row backup leaves a script alone - but `sqlguard.Info` is a public contract, and a plugin that rewrites statements reads exactly those positions. ## 0.1.7 — 2026-10-05 - Fixed: a batch that returns several results showed only the first. SQL Server runs everything up to `GO` as one batch, so a script of several SELECTs is one statement to the server and returns a result per SELECT; 0.1.6 read the first and stopped, which lost the rest of what had already run. Every result now arrives, each with its own columns, and the editor lists them as Result 1, Result 2 and so on, the way a run of separate statements already did. A statement inside the batch that changed rows rather than returning them is passed over rather than shown as an empty result. ## 0.1.6 — 2026-10-05 - Fixed: a maintenance script no longer has to be rewritten to run. SQL that leaves state on its connection - `SET`, `DECLARE`, a cursor, `PRINT`, `RAISERROR`, `WAITFOR`, a temporary table - was refused outright with "Session-control SQL is not supported in the web editor". It now runs, on a connection of its own that is discarded afterwards, so no setting or variable can reach the next request through the pool. A manual transaction's connection is discarded the same way. `BEGIN`, `COMMIT`, `ROLLBACK`, `SAVEPOINT` and a bare `USE` are still refused, and now say which control does the job instead of naming the whole category. - Changed: several statements sent together are read as one rather than refused whatever they contain. The script takes the riskiest statement's kind and the union of its flags, so a `DROP` or a `DELETE` without a `WHERE` buried in a script is refused by the rules that already existed - which a blanket refusal never had to get right - while the batched delete and the reindex people write by hand now run. `deny_multi_statement` in My policies brings the old behaviour back; an installation several people share keeps it on. A script is not backed up row by row and has no execution plan, and both say so. - Changed: on SQL Server the editor splits a script on `GO`, the batch separator sqlcmd and SSMS use, instead of on every semicolon. A `DECLARE` and the statements that use its variable now belong to one batch. Every other engine still splits on the semicolon. - Changed: a result grid shows about twice as many columns on the same screen. Each column is now measured from what it holds instead of being given the same width, rows are tighter, and the column type moves into the header's tooltip. The Compact/Roomy control in the result toolbar brings the roomier rows and the visible types back, and remembers which was chosen. - Added: an XML value is shown as a link, as it is in SQL Server's own client, and opens laid out one element per line instead of as a single long line. - Added: Console, a shell for every engine Rowset connects to, next to Query in the sidebar. It is modelled on the client each engine's users already know - psql, mysql, sqlcmd, sqlite3, duckdb, clickhouse-client, mongosh, redis-cli, cqlsh and the Kibana console - so `\dt`, `.tables`, `show collections`, `DESCRIBE TABLES`, `use`, `\x`, `\timing` and `\G` behave as they do there, with aligned columns, NULL shown explicitly and a colour per value kind. It is a client over the same API the Query editor uses, not a terminal: no process is started and nothing connects to a database directly, so a statement typed in the console gets the same policy decision, row backup and audit entry as one run from the editor. Commands that would need a shell or the local filesystem (`\!`, `\i`, `\copy`) are not offered rather than silently ignored, and an engine Rowset has no shell for says so instead of presenting an empty prompt. ## 0.1.5 — 2026-09-29 - Fixed: a connection could not be saved with the middle environment. Studio offered dev, preprod and prod while the server accepted only dev or prod, so picking the middle one failed with "environment must be dev or prod". There are now three environments - dev, test (staging, pre-production, UAT or QA) and prod - and the server accepts the usual spellings of each. - Changed: when the encryption key of an installation no longer matches its database - a restored or regenerated `ROWSET_ENC_KEY` - the server says so once at startup, and Studio explains it where it used to show only "Workspace could not be decrypted or validated". Saved drafts are left untouched; putting the original key back (or naming it in `ROWSET_ENC_KEY_PREVIOUS`) opens them again, and the card offers to start an empty workspace instead. - Fixed: the Test filter on Connections could never match anything, because a connection saved as test was stored as dev; the yellow badge and rail for that environment were unreachable for the same reason. ## 0.1.4 — 2026-09-25 - Changed: the backup taken before an UPDATE now keeps the row's key and the columns that statement writes, instead of the whole row. Restoring it can no longer undo a change someone else made afterwards to a column the statement never touched, and values it never read stay out of the backup. DELETE still keeps the whole row, since putting it back needs all of it. A SET list Rowset cannot read column by column, and an UPDATE that writes a key column, keep the whole row as before. MySQL columns with ON UPDATE CURRENT_TIMESTAMP are kept too, because the engine rewrites them without the statement naming them. The same applies to Cassandra. ## 0.1.3 — 2026-09-25 - Changed: a workspace no longer blocks statements the parser has not learned yet. Existing personal workspaces are relaxed once on upgrade; installations several people share keep failing closed. The guardrails that matter on your own database - DROP, TRUNCATE, and UPDATE or DELETE without a WHERE - are unchanged; "Block SQL statements the parser cannot classify" now starts off and can be turned on in My policies. A shared installation still starts with it on. - Added: a statement Rowset does not recognise is marked in the result ("Not recognised") with a link that opens a prefilled issue, so the parser can learn it instead of the statement being worked around. Nothing is sent from Studio; the link only opens the form. - Fixed: T-SQL scripts were blocked by "Block SQL statements the parser cannot classify". `DECLARE` (a variable, a table variable or a cursor), `PRINT`, `OPEN`, `CLOSE` and `DEALLOCATE` are session statements, and `FETCH` is a read, so row limits and masking apply to its rows. - Fixed: `EXPLAIN ANALYZE` runs the statement it explains on PostgreSQL, so `EXPLAIN ANALYZE DELETE FROM orders` now counts as a DELETE instead of a read. Plain `EXPLAIN` still only plans. - Fixed: `SET GLOBAL` and `SET PERSIST` change the server for everyone - one of them switches the audit log off - and now count as administration instead of session state. `SET SESSION` and plain `SET` are unchanged. - Fixed: MySQL executable comments hid whatever they carried. MySQL runs the SQL inside `/*! … */` and `/*!50100 … */`, so `SELECT 1 /*! ; DROP TABLE users */` reached the database as a second statement while Rowset saw a comment. That text is now lexed as SQL on MySQL and MariaDB and still ignored on the other engines. - Fixed: `SELECT … INTO` is a write. It creates a table on SQL Server and PostgreSQL and writes a file on MySQL (`INTO OUTFILE`, `INTO DUMPFILE`), so it no longer passes the rules as a read. `SELECT … INTO @variable` stays a read, and an output file is no longer reported as a table. - Fixed: identifiers outside ASCII were split at the first non-ASCII byte, so `müşteri` was read as a table called `m` and masking, row filters and table-scoped policies never matched it. - Fixed: MySQL string and comment rules. A backslash escapes the next character inside a string, and block comments do not nest, so `'\''` and `/* /* */ DELETE …` are read the way MySQL reads them. - Fixed: PostgreSQL's `TABLE users` now names its table, and a keyword after a table name (`SELECT * INTO archive FROM users`) is no longer taken as its alias. - Fixed: a T-SQL block could hide a write. Statement splitting cuts on semicolons, so `BEGIN TRY DELETE FROM orders` or `IF EXISTS (…) DROP TABLE tmp` arrived as one statement and was read as session control; a block, IF or WHILE now takes the kind of the riskiest statement inside it, and a DROP or TRUNCATE inside one is recognised as such. - Changed: server and storage administration (`BACKUP`, `RESTORE`, `CHECKPOINT`, `SHUTDOWN`, `RECONFIGURE`, `FLUSH`, `RESET`, `LOCK`, `UNLOCK`, `REPAIR`) counts as a write: it changes the installation even when it changes no row. `PREPARE` and `WAITFOR` stay unclassified on purpose - the first hides its statement in a string, the second holds a session open. - Changed: a write whose only target is a table variable and which reads no table (`INSERT INTO @Tables VALUES …`) counts as session state, since it changes nothing in the database; read-only roles may run it. A write that reads a table keeps its kind, so masking and row filters still apply. ## 0.1.2 — 2026-09-22 - Fixed: the editor's auto-refresh and "more" menus did nothing when an item was clicked. Both menus render through a portal, so the click-outside check matched their own items: the menu closed on mousedown and the item never received the click. ## 0.1.1 — 2026-09-19 - Changed: a shared installation now has everything a personal workspace has — scheduled queries, row backups, the assistant and Slack settings, MongoDB/Redis/Valkey/Elasticsearch reads and writes, and the SQLite, DuckDB, ClickHouse and CockroachDB connections. On a shared server, scheduled results are kept in a server folder per user (`ROWSET_SCHEDULE_OUTPUT_DIR`, default `scheduled-results` next to the database) and downloaded from the run list, and only administrators can open a row backup's script, since it holds stored values; everyone can restore their own backups. - Changed: extensions add navigation groups next to Studio's own pages instead of replacing them. - Fixed: row backups on SQLite and DuckDB connections were always skipped ("the changed rows could not be read"): the primary-key and column lookups used PostgreSQL's catalog. Both now back up and restore. ## 0.1.0 — 2026-09-19 - Changed: Rowset can now be extended from another Go module and another front-end project instead of by editing this repository. - Go: the module path is `github.com/rowsetdev/rowset-studio/rowset-core` (the SQL parser moved into it as `rowset-core/sqlguard`). The public package `rowset-core/rowset` builds a Rowset executable with plugins (`rowset.Main`): commands, server set-up, access rules (`Server.SetAccess`), sign-in routes (`Kit.IssueSession`), policy kinds, tables in the control database (`Setup.AddStorage`), activity backends, a served Studio build (`Server.SetWebUI`) and `Server.Run` / `OpenSession` for clients other than the HTTP API. Everything else stays internal. - Studio: the repository-root `package.json` publishes Studio as `@rowsetdev/studio`; `mountStudio(root, { extensions })` renders it with extensions, which can add pages, a sign-in form, notices, Account sections and policy roles. - Changed: password sign-in, password changes, user and role administration and sign-in lockout are no longer part of the core; a desktop workspace never used them. Its sign-in, sessions and data are unchanged, and existing databases open as before. - Fixed: the daily audit-chain check did not include an entry's reference in the hash it re-computed, so an installation that stores references would have reported intact entries as tampered. ## 0.0.101 — 2026-09-19 - Fixed: the SSH tunnel of a MongoDB, Redis/Valkey or Elasticsearch connection was dropped on save — the form showed the SSH section for these engines but still cleared its host before sending, so the connection was saved without a tunnel. - Fixed: the WHERE guardrails (`deny_select_without_where`, `deny_update_without_where`, `deny_delete_without_where`) could be bypassed with a quoted identifier named like a keyword, e.g. `DELETE FROM t AS "where"` or `SELECT * FROM secrets AS "where"`. Quoted identifiers are no longer read as keywords anywhere in WHERE detection. A column compared with a literal (`NOT id = 5`) is also no longer taken as a constant, which had made such filtered writes look unfiltered. - Fixed: in a MongoDB manual transaction begun on a database other than the connection's default, policies were evaluated — and row backups recorded — against the default database instead of the one being written, so a per-database policy did not apply and Restore targeted the wrong database. - Fixed: restoring an UPDATE row backup reported success while restoring nothing when a backed-up row no longer existed under its key (it was deleted, or the UPDATE changed the key). The restore now stops, rolls back and says so; restoring values a row already holds still succeeds, including on MySQL, which counts only changed rows. - Fixed: Redis/Valkey bulk writes reported total failure with 0 writes when one command failed, although the others had been written. A hash write onto an existing non-hash key is now refused before anything runs, and any other server-side failure reports how many writes went through. - Fixed: the MongoDB, Redis and Elasticsearch query bars kept their fields from the previous query when the editor text changed in the same tab (history pick, typing in the editor); editing a field then rebuilt the old query over the new one. The bars now re-read any change they did not make. - Fixed: SQL-format exports (and scheduled SQL exports) rounded floating-point values to 6 decimal places; they now keep the exact value. - Fixed: a deferred statement run for its author ignored the row limit of a `limit_rows` policy while reading its result. - Changed: the macOS release downloads (archives and app) now include DuckDB; Linux and Windows downloads still omit it. ## 0.0.100 — 2026-09-18 - Fixed: exporting a binary/blob/bit column to SQL format (or a scheduled SQL export) on MySQL or MSSQL produced a literal that re-imported as the wrong bytes — `sqlValueLiteral` quoted the hex-encoded value as a plain string (`'\xdeadbeef'` on MySQL, `N'\xdeadbeef'` on MSSQL) instead of that engine's binary-literal syntax (`X'deadbeef'`, `0xdeadbeef`), unlike the row-backup restore and CSV import code paths which already handled this correctly. Verified live against real MySQL and MSSQL containers: a `VARBINARY` column now round-trips byte-for-byte through export → reimport. - Fixed: Cassandra's "Show DDL" for a table dropped `DESC` clustering order entirely — it read the clustering column names but never their `ClusteringOrder`, so re-running the generated DDL silently recreated the table with ascending order. Now emits `WITH CLUSTERING ORDER BY (...)`. Verified live against a real Cassandra container. - Fixed: the audit log's SHA-256 hash chain (tamper-evidence) was written correctly but never verified — `VerifyPreparedAudit` existed but nothing ever called it. Added `Store.VerifyAuditChain`, wired into a daily background check (`auditIntegrityCheck`) that walks every organization's chain and logs an error if an entry no longer matches what it was written with. Verified with a direct-database tampering test. ## 0.0.99 — 2026-09-18 - Added: bulk write endpoints for MongoDB (`insertMany`), Redis/Valkey (`bulkWrite`, pipelined) and Elasticsearch (`bulkIndex`, via `_bulk`) — up to 10,000 items per call, the same cap row backups and CSV import use. Redis validates every write before any of them run, so a bad entry never leaves some keys written and others not; Elasticsearch reports per-document failures instead of failing the whole batch. Wired into the write dialog: MongoDB/Elasticsearch get a "Bulk insert" checkbox that swaps the document textarea for a JSON array, Redis/Valkey gets its own "Bulk" tab. Not available inside an open MongoDB transaction, and Redis bulk writes skip row backup (a DUMP per key would mean thousands of snapshots for one call). - Also fixed: the auto-refresh and "more" toolbar menus in the query editor were `absolute`-positioned inside the editor pane, so on a narrow pane they got clipped by the pane's own edge instead of the window's. Both now render into a body-level portal at a viewport-clamped fixed position. ## 0.0.98 — 2026-09-18 - Added: SSH tunnel support for MongoDB, Redis/Valkey and Elasticsearch, matching what the SQL engines and Cassandra already had. All three route every connection they open (Mongo's replica-set discovery included, ES's HTTP transport, Redis's single connection) through one SSH tunnel via a `DialContext`/`Dialer` adapter — the same pattern Cassandra already used. Removed the explicit "not supported yet" rejections and connection-form gates. Verified live: an in-process SSH test server forwarding real connections to Mongo/Redis/Elasticsearch containers, covering untrusted-host-key rejection, host-key learning, wrong-host-key rejection, and a working query through the tunnel — the same regression test the SQL engines already had, now table-driven to cover both. ## 0.0.97 — 2026-09-18 - Removed: `reports/database-client-audit-2026-09-17.md` and its `reports/audit-evidence/` — the report predated this session's Snowflake removal (still listed it as a live finding) and its MongoDB/Redis/Valkey/Elasticsearch write rows were marked unimplemented, which this session's row-backup work made false. `scripts/audit/run.py` still works to produce a fresh one. ## 0.0.96 — 2026-09-18 - Added: row backups for Redis/Valkey and Elasticsearch, completing row backups across every supported engine. Redis/Valkey capture a key's exact state with `DUMP`/`PTTL` before a write or delete and restore it byte-for-byte (with its original TTL) via `RESTORE`, or remove it if it didn't exist before — Redis has no distinct update mode, so a `Write` "Set" is backed up the same as a delete, unlike Mongo/Elasticsearch insert. Elasticsearch fetches the document's `_source` before an update or delete and restores it with a full re-index. Verified live: Redis Set on a new key, Set overwriting an existing key (with TTL), and Delete, each captured and restored correctly; Elasticsearch Insert (not backed up, matching Mongo), Update and Delete, each captured and restored correctly. ## 0.0.95 — 2026-09-18 - Added: row backups for Cassandra. A governed UPDATE/DELETE with a WHERE clause (USING TTL/TIMESTAMP and IF are tolerated) saves the matched rows first; Activity → Row backups restores them in one click, in CQL batches of up to 50, or opens a real, runnable BEGIN BATCH/APPLY BATCH script to review first. Supports the common scalar CQL types (text, int family, boolean, uuid/timeuuid, timestamp, blob, float/double, decimal, varint, inet); collections, tuples, UDTs, counters, duration, date and time-of-day columns are deliberately skipped rather than guessed at, with a reason shown in the run result, and counter tables are skipped entirely. Verified live: UPDATE and DELETE capture, both restore paths, and both skip cases (unsupported column type, counter table), round-trip a uuid/text/boolean/decimal/blob/timestamp row exactly. - Docs: repo hygiene files added — CODE_OF_CONDUCT.md, CONTRIBUTING.md, issue templates and a pull request template. ## 0.0.94 — 2026-09-17 - Added: the MongoDB editor's Run button now executes `updateOne()`/ `deleteOne()` typed directly into it, in shell syntax (`db.col.updateOne(filter, update)` / `db.col.deleteOne(filter)`) or the raw request JSON (`{"collection","filter","update"}`, or `{"collection", "filter","delete":true}`) — the same two forms `find()`/`aggregate()` already accepted. Previously only the Write dialog could run these; typing one into the editor and hitting Run failed with "Unsupported query field". Row backups, the WHERE guardrail and manual-commit transactions all apply exactly as they do from the Write dialog. - Docs: updated README — CockroachDB gets actual (not just estimated) plans since it has `ExplainAnalyze`, noted MongoDB's row backups (the only NoSQL engine with them so far), and fixed the client-audit link's stale "14-engine" count after Snowflake's removal. ## 0.0.93 — 2026-09-17 - Fixed: reopening a MongoDB `find()` from Activity/History failed with "Max time must be between 1 and 600000 ms." The saved request always includes `maxTimeMs`, and 0 (the backend's own "no limit" default) was being rejected as out of range instead of accepted. - Fixed: reopening an `aggregate()` from Activity/History failed outright ("Unsupported query field: pipeline"), because only its shell-syntax form was recognized — the raw `{collection,pipeline,...}` object History actually saves fell through to `find()`'s field validation. The query bar now also switches to Aggregate mode when reopening one. ## 0.0.92 — 2026-09-17 - Added MongoDB row backups: an update/delete from the Write dialog (or inside a manual transaction) can save the documents it is about to change first, the same "back up before UPDATE/DELETE" preference SQL writes already use. Activity → Row backups lists them with a Mongo shell script for review and a one-click Restore, which re-inserts (for a delete) or fully replaces by _id (for an update) — not a partial `$set` replay, since that can't be inverted field by field. Restore runs document by document rather than in one transaction, since MongoDB transactions need a replica set. Verified against a live server end to end, including clicking through Restore in the browser. ## 0.0.91 — 2026-09-17 - Added pagination to the Activity statements table: 20 rows per page with Previous/Next, resetting to page 1 whenever a filter changes. ## 0.0.90 — 2026-09-17 - Added MongoDB manual-commit transactions: the toolbar's manual-commit toggle begins a session/transaction, the Write dialog's insert/update/ delete join it, and Commit/Rollback end it — same UI as SQL engines. Requires a replica set or mongos; a standalone server's rejection surfaces as the begin call's error. - Fixed: MongoDB document insert returned an empty, unusable `_id` in its response (`bson.MarshalExtJSON` errors on a bare scalar value like an ObjectID; the error was silently swallowed). Found via manual browser testing of the transaction feature above, not a directed check — none of the existing tests asserted on the returned id's content, only that the call didn't error. Fixed and covered by a new regression check. - ClickHouse's Go driver silently no-ops `Rollback()` — a transaction "succeeds" without reverting anything. Confirmed live and left disabled; MongoDB got the same check and passed for genuine (replica-set) support. ## 0.0.89 — 2026-09-17 - Added an Aggregate mode to the MongoDB query bar: a Find/Aggregate toggle and a pipeline editor, kept in sync with `db.collection.aggregate([...])` in the editor below it (previously only reachable by hand-typing shell syntax). - Enabled CockroachDB's "Explain with actual rows" (the backend already supported EXPLAIN ANALYZE; only the capability flag hid it). - Added a live CockroachDB transaction test (Begin/Execute/Rollback and Begin/Execute/Commit) and a live DuckDB one — both engines advertised Transactions but neither was exercised live before, unlike PostgreSQL, MySQL, MariaDB, SQL Server and SQLite. ## 0.0.88 — 2026-09-17 - Removed Snowflake support (connection type, driver, DSN/schema/DDL handling, engine picker and logo) to focus engine coverage elsewhere. - Added Elasticsearch `sort`/`search_after` pagination past the 10,000-hit window and `aggs` in the search bar's raw JSON, with aggregation results and the next page's `search_after` values shown in the grid. - Added MongoDB read-only aggregation: `db..aggregate([...])` in the editor runs a pipeline, with `$out`, `$merge`, `$lookup` and other writing/cross-collection/JavaScript stages rejected before it runs. - Added a "Test all" action on the Connections page to test every filtered connection at once. ## 0.0.87 — 2026-09-17 - Added MongoDB document insert/update/delete and Elasticsearch document index/update/delete, behind the same policy/read-only/audit guardrails as SQL writes; update requires operator documents ($set/$unset/…), never a full-document replace. - Added Redis/Valkey string and hash-field writes and key deletion, with optional TTL. - Added a "Write" action in the query toolbar for MongoDB, Redis/Valkey and Elasticsearch connections to drive these from Studio. - Added CockroachDB and ClickHouse EXPLAIN (text plan, no ANALYZE yet). - Added SQLite and DuckDB CSV import. - Schema Compare's connection pickers now hide MongoDB, Redis, Valkey and Elasticsearch, which have no table/column metadata to diff. ## 0.0.86 — 2026-09-16 - Expanded Cassandra from personal read-only queries to policy-governed CQL reads, writes, DDL and batches in personal and shared workspaces, with audit logging, read-only enforcement and automatic schema cache invalidation. - Added Cassandra contact points, consistency and page-size settings, plus TLS and SSH tunnel support. - Added Cassandra table DDL viewing, CSV/JSON export and typed CSV import in bounded logged batches, with explicit partial-failure behavior. - Added CQL-aware classification and automated/live integration coverage for comments, strings, multi-statements, batches, writes, DDL and imports. ## 0.0.85 — 2026-09-16 - Fixed sidebar navigation occasionally leaving the SQL editor visible after a query completed even though browser history had moved to the selected page. Rowset now detects that router/history desynchronization and recovers immediately without interfering with running-query or transaction prompts. - Updated the Studio runtime and build chain to React 19.3, React Router 8.4, Vite 8.3 and the matching React Vite plugin and type definitions. ## 0.0.83 — 2026-09-15 - Server (Host/Port, or HA nodes) moved up next to username/password in the connection form, instead of near the bottom. - Dropped the separate Alias field - it's derived from Name automatically now, same as it always was when left blank. - Valkey has its real logo instead of a colored-initial badge. ## 0.0.82 — 2026-09-15 - HA nodes are opt-in now: a new connection shows a plain Host + Port row instead of always showing the multi-node "name / host / port / Remove" editor. A "This connection has more than one node" checkbox reveals the full editor when it's actually needed. ## 0.0.81 — 2026-09-15 - **Fixed Windows opening Internet Explorer instead of the default browser** for Rowset Studio, found on a real Windows Server: the old `rundll32 url.dll,FileProtocolHandler` trick goes through IE's own URL handler on some Windows builds. Switched to `cmd /c start`, which respects the actual default-browser association. - Auto-refresh now allows `EXEC`/`EXECUTE`/`CALL` (a stored procedure call isn't a plain write), so watching a diagnostic proc like `sp_whoisactive` on an interval - the original motivating use case - actually works; plain DML/DDL keywords are still excluded. - Query results show which physical node they ran against (as a small badge next to Completed), for connections with more than one node. - **Added Valkey support**, wire-compatible with Redis so it reuses the same query editor and schema browsing. - The New/Edit connection form: TLS now defaults to Off instead of full certificate verification; added "preprod" to Environment; username and password are next to each other instead of opposite ends of the form, and the form is visibly more compact overall. ## 0.0.80 — 2026-09-15 - **`rowset desktop` now survives closing the terminal it was started from**, on every platform. It used to run attached to whatever console launched it (typing `rowset` in cmd.exe/PowerShell and closing that window killed it, since Windows terminates a console process tree on close, and the same applies on Linux/macOS without something detaching it). It now re-execs itself once, detached (`Setsid` on macOS/Linux, `DETACHED_PROCESS` on Windows), and the original invocation waits for the detached copy to report itself ready before returning - so scripts calling `rowset desktop` synchronously still see a real failure if startup fails, but otherwise get their prompt back immediately. - The macOS menu-bar app opts out of this (`ROWSET_DETACHED=1`) since it already manages the server process directly and needs to notice it crashing later, not just a failed launch - its behavior is unchanged. ## 0.0.79 — 2026-09-15 - **License changed from Apache 2.0 to the PolyForm Noncommercial License 1.0.0.** The source stays open to read, fork and modify, and free for any noncommercial use, but commercial use now needs a separate license. ## 0.0.78 — 2026-09-15 - Auto-refresh's active state is amber now (a better fit for "live" than the info-blue used elsewhere), and its pulsing dot no longer clips against the button's corner as it fades — it's a proper ping-ring indicator inline with the interval label instead. ## 0.0.77 — 2026-09-15 - Auto-refresh is now a single clock-icon button next to Run instead of a full-width "Auto-refresh: …" dropdown. - Explain's icon is now a small node diagram instead of a generic circled-i. ## 0.0.76 — 2026-09-15 - **Auto-refresh**: a dropdown next to Run (Off/3s/5s/10s/30s) re-runs the current statement on an interval and updates the result in place — for watching a running process, a queue, or anything else worth checking every few seconds without hitting Run by hand. Only enabled for statements that look read-only; editing the statement into a write turns it back off. - Fixed a real "index/PK/FK metadata not yet loaded" warning that was shown for every DuckDB and ClickHouse schema load regardless of whether that metadata was actually missing — it wasn't, for either of them. Both now load real primary keys, and DuckDB also loads real foreign keys and indexes; ClickHouse has no foreign keys at all, so there's nothing left to warn about there. Cassandra now also loads its secondary indexes and materialized views, which it previously flagged as simply not implemented. - The sidebar and the connections list's "Open" link no longer trigger the browser's own link-target preview on hover — they navigate with the app's router instead of a real ``, since there's no actual new-tab use case for a one-use local session. ## 0.0.75 — 2026-09-15 - **Results filter redesign**: Filter and Edit rows are real bordered buttons now; Filter toggles a panel instead of adding a new condition on every click, with a dedicated "+ Add condition" button for additional ones; "Clear filters" reads as a (subdued) destructive action; and the row count moved out of the toolbar into the status bar, where it only lights up as "X of Y rows filtered" once a filter actually narrows something. - The Primary/Secondary node picker no longer shows (disabled) for every single-node connection — only when the connection actually has more than one node to route between. ## 0.0.74 — 2026-09-15 - **Results Filter button is now visible**: it was plain text with no icon or hover state next to the properly-styled Edit rows button; now it matches, and highlights when a filter is active. - The "X / Y loaded rows" counter only stands out (a highlighted "X of Y rows match" badge) once a filter actually narrows the result; otherwise it's a quiet "N rows loaded", so a filter matching every row (e.g. "any column contains 2" when a date column has a year like 2026) doesn't read as broken. ## 0.0.73 — 2026-09-15 - **JSON view for query results**: a third view next to Grid and Text, syntax-colored, showing one JSON object per row. MongoDB and Elasticsearch results open in it by default, since their Grid view is a single "document" column of stringified JSON; every other engine still opens in Grid and can switch to JSON from the same toolbar. - Fixed two lint errors from the previous change set (a useless assignment in the Elasticsearch query bar, and `eslint-disable` comments referencing a rule this project's config never registers). ## 0.0.72 — 2026-09-15 - **Query editors for Redis, Cassandra and Elasticsearch**: all three engines had a schema browser and a "not available yet" placeholder where the query editor should be; now they have real editors (a scan bar for Redis, CQL for Cassandra, a search bar for Elasticsearch), wired into `Run`, `Run all`, Monaco syntax highlighting and the "Open" row action in the schema tree, the same as every other engine. - Fixed the Elasticsearch query bar sending an empty `{}` query clause (which Elasticsearch rejects as malformed) when the bar's fields hadn't been edited yet — it now defaults to `{"match_all":{}}`. ## 0.0.71 — 2026-09-15 - **Real logos** for CockroachDB, Snowflake, Redis, Cassandra and Elasticsearch (Simple Icons marks, recolored to each brand's color), replacing the colored-initial badges from the previous release. - **ClickHouse no longer lists `INFORMATION_SCHEMA` and `information_schema` as two separate databases** — they're the same schema under a case-variant alias ClickHouse keeps for MySQL compatibility; only the canonical lowercase one is shown now. - **Saved query snippets**: a "Snippets" menu in the SQL toolbar saves the current statement by name, per connection, and inserts a saved one back into the editor. The backend and API client already existed from an earlier change but had no UI until now. - **Read-only connections**: a "Safe mode" checkbox on a connection blocks every write statement (INSERT/UPDATE/DELETE/DDL) on it, the same way a read-only role already does, but without needing to set one up. Shown as a "Safe mode" badge in the SQL toolbar and a lock badge in the connections list. ## 0.0.70 — 2026-09-15 - Redis, Cassandra and Elasticsearch pseudo-tables no longer show "Open SELECT", "Show DDL" or export actions in the schema tree — none of those paths work yet for these engines, and showing them just led to a confusing error. The auto-commit/transaction toggle is likewise hidden for them. - CockroachDB, Snowflake, Redis, Cassandra and Elasticsearch get a colored initial badge instead of all sharing the same generic gray icon, so they are tellable apart in the connection list, sidebar and command palette. ## 0.0.69 — 2026-09-15 - **MongoDB query bar** now also populates itself from a query opened from Activity or History, not just typed shell syntax; those entries are saved as the raw find-request JSON, which the bar previously failed to parse and silently left at its defaults. ## 0.0.68 — 2026-09-15 - **Five new engines**: CockroachDB and Snowflake connect through the normal SQL editor (CockroachDB reuses PostgreSQL's wire protocol and catalogs directly; Snowflake uses its own `database/sql` driver — enter your account identifier as the server). Redis, Cassandra and Elasticsearch have working backends (key scan with type/TTL preview for Redis, read-only CQL SELECT for Cassandra, `_search` for Elasticsearch) reachable through the API, but their dedicated query screens are not built yet — for now their connections can be created, tested and schema-browsed from the sidebar; the query editor shows a notice instead of the SQL/Mongo editor. HA nodes and SSH tunnelling are not available for Redis, Cassandra, Elasticsearch or Snowflake yet. - **Command palette** (⇧⌘K / Ctrl+Shift+K): jump to any page or saved connection from anywhere in Studio. Uses Shift so it doesn't collide with the schema explorer's existing ⌘K search-box shortcut. ## 0.0.67 — 2026-09-15 - **MongoDB query bar**: added Max Time MS to Options, mapped to the shell's `.maxTimeMS()` and enforced server-side as a per-query timeout. ## 0.0.66 — 2026-09-14 - **MongoDB query bar**, Compass-style, above the `db.collection.find()` editor: a Filter field plus an Options panel for Project, Sort, Skip and Limit. Editing the bar rewrites the editor's shell syntax; switching tabs re-reads the bar from the editor. `find()` now also accepts a projection as its second argument and `.skip()` in the chain. Collation and max time are left out of this pass; add them later if needed. ## 0.0.65 — 2026-09-14 - Fixed the MongoDB and ClickHouse logos rendering larger than the other engine logos in the schema tree; a global `height: auto` rule was overriding their `height` attribute for non-square source SVGs. ## 0.0.64 — 2026-09-14 - **MongoDB query editor** accepts shell syntax (`db.customers.find({}).sort({...}).limit(n)`) in addition to the JSON find form; removed the redundant explanation strip and SQL-only tools (Explain, "Explain with actual rows") from the Mongo editor toolbar. - **Connection names must be unique** per org, checked case- and whitespace-insensitively on both create and update, with a clear error in the form. Existing duplicates are left as-is; connection pickers now show host/port/database next to a name that collides with another connection. - **New Connection form**: Engine selector moved to the top of the form, above the engine-specific fields. ## 0.0.63 — 2026-09-13 - **Schema comparison** in the sidebar and a database's menu compares two selected schemas, showing added, changed and removed tables, columns and index summaries. Inspect the table DDL side by side. Migration drafts open in the existing SQL editor on the target connection without running them. Only simple nullable column additions are generated automatically; other changes are marked MANUAL. Incomplete metadata disables draft generation. CHECK constraints, full index/FK definitions, views and routines are outside this initial metadata comparison. - **SQLite** connections open an existing local file, with SQL queries, transactions, table/view/trigger discovery, keys, indexes and original DDL. - **DuckDB** connections open an existing local file, with SQL queries, transactions, table/view discovery and DDL. Native builds include the Go driver using CGO; portable non-CGO builds hide DuckDB in the new-connection form and reject it on the API. Index/key discovery is not yet available. - **ClickHouse** connections use the native TCP protocol (9440 with TLS, 9000 without), with database/table/view discovery, DDL and SQL queries. Interactive transactions and index/key discovery are not yet available. - **Local result filters** combine column conditions without rerunning SQL; filtered CSV/JSON export and original row identities are preserved. - **SQL value parameters** use `{{name}}` with text, number, boolean or NULL values; scripts validate all parameters before starting. - **MongoDB in the Query editor** lists databases and collections, finds and sorts documents with Extended JSON filters, cancels reads and exports results as JSON. BSON types are preserved as canonical Extended JSON. SELECT, table, schema, timeout and row-limit policies apply to finds. This initial version is read-only, uses authSource=admin, and does not support aggregation, SRV URLs or SSH tunnels. - Additional engines currently run in personal workspaces with one configured endpoint. SQL INSERT export, CSV import and inline row editing are not yet available for the new engines; SQL engines can export CSV/JSON. Application settings and query-result storage are unchanged. ## 0.0.62 — 2026-09-13 - `#` starts a comment only on MySQL and MariaDB. On PostgreSQL and SQL Server it is read as written, so the `#>` operator works and a `;` after it is seen as a second statement — which the multiple-statements guard then refuses, rather than the parser hiding it behind a comment. The statement classifier and the editor both split by the connection's engine. - Results are no longer cut off at a hidden size. The 16 MB per-row and 32 MB per-result display limits, which stopped a large result mid-stream, are gone; the row-count policy is what bounds a result. - Updated the build dependencies flagged by npm audit (browserslist, nanoid); they are development-only and not part of the app. - Export a table as SQL: **Export as SQL (INSERT)** writes multi-row INSERT statements that quote the table and columns for the engine, write NULL for null and keep every value's type; a database can replay them. CSV exports now begin with a UTF-8 byte-order mark so Excel shows non-ASCII text (Turkish, say) correctly. - The dialect-aware "#" handling now reaches every place statements are split: the editor's run and quick-fix paths, inline problem checks, autocomplete and Run on several connections. So a SQL Server "#temp" table and PostgreSQL "#>" are never mistaken for a comment, on any screen. ## 0.0.61 — 2026-09-13 - Fixed: Run on several connections and the SQL assistant were given the 60-second limit ordinary API requests have. A script that ran longer was cut off, its statements cancelled, and progress reached the page only when the whole run had finished. Both now stream and run for as long as they need, like a single query. ## 0.0.60 — 2026-09-13 - A history read waits at most two seconds for statements still being recorded, then shows what is stored. Against the local database that wait is a few milliseconds; it keeps the history page from hanging when a remote activity store is slow or unreachable. ## 0.0.59 — 2026-09-13 - The database explorer keeps room at its right edge for the scrollbar macOS draws over the content while scrolling, so it no longer covers the column types aligned to that edge. ## 0.0.58 — 2026-09-13 - **Run on several connections** now runs on the server, up to ten connections at once (chosen in the dialog, four by default). A browser opens at most six connections to one address and shares them with the rest of Studio, so running from the page capped real concurrency at six and stalled everything else meanwhile. One request now streams each connection's progress; every statement still goes through the query handler as the same person, so policies, row backups, timeouts and history apply per connection, and results reach the page unchanged, large integers included. Stop all ends the statements on the databases themselves. - A trigger on a table or view is listed under that table, after its columns and indexes, and the table shows that it has triggers. The Triggers group keeps only triggers without such a table. - Definitions a database returns on one line — MySQL and MariaDB triggers and views, PostgreSQL triggers, SQL Server views and functions — are laid out on lines in Show DDL. Only whitespace between tokens changes; a definition the author wrote on lines is shown exactly as written. Copy and Open in editor take the text as shown. ## 0.0.57 — 2026-09-12 - The editor marks problems as you type, from the text and the schema already loaded, with no round trip to the database: - a string, quoted name or comment that is never closed, and a bracket that closes nothing (errors); - a table or view that is not in the loaded schema, and `alias.column` where the table has no such column, each with close names to change to; - a column two tables of the same query both have, used without saying which, with a fix to qualify it for each; - UPDATE or DELETE without WHERE. - Checks stay quiet when a name cannot be verified: CTEs, tables the script creates, table functions, system catalogues, other databases, subquery aliases, and a table name that several schemas share where the search path decides — a column is missing only if no candidate has it. A subquery's columns are checked against its own tables, not the outer query's. - `#` starts a comment only on MySQL and MariaDB when inspecting, so SQL Server temporary tables and PostgreSQL's `#>` no longer look like broken brackets. - Fixed: the GROUP BY quick fix inserted at the wrong place in a statement that had blank lines before it. - The quick-fix menu is wide enough to show the whole fix. ## 0.0.56 — 2026-09-12 - **Run on several connections** (editor ⋯ menu) runs the selection, or the whole editor, on the connections you pick, each in its own database. Up to four run at a time; on each one the statements run in order in auto-commit and stop at the first error without stopping the others. Every statement goes through the normal query endpoint, so policies, row backups and history apply to each connection as usual. - A script that changes data or schema asks for confirmation first and names the production connections among the targets; changing the choice asks again. Anything not recognisably a read counts as a change — procedure calls, SET, EXPLAIN ANALYZE, SELECT … INTO, a CTE ending in DELETE. - The Connections tab lists every connection with its status, statements, rows and time next to the result of the one picked, showing a failure first. Results whose columns match can be combined into one grid with the connection in the first column. Stop all cancels every running statement. ## 0.0.55 — 2026-09-12 - Statements are recorded by one background writer in batches instead of two writes on every request. With 48 concurrent writers the local store went from 613 to about 46,000 records a second. A record is never dropped: when the queue is full a request waits for room rather than competing with the writer for SQLite's lock, and history still shows a statement as soon as it has run. - The audit chain reads its newest entry through an index. At 200,000 audit entries that read took 40 ms on every statement, behind a lock; it now takes 0.01 ms. - History is listed through its index instead of sorting every statement a person ever ran (20 ms at 200,000 rows). - Retention periods are applied once a day on a shared server. A personal workspace keeps its history and audit log unless a period is set explicitly. Old entries are removed in small batches, so statements keep being recorded meanwhile, and the cutoff is compared in the same format the entries use. - Snapshots are taken at most once a day, after Rowset is already answering. Taking one on every start meant a few restarts in a day rotated out last week's copy, and a large database held up opening the app. - Before a new version applies migrations to an existing database, Rowset copies it to `snapshots/before--