Editing tools
functionalTransform and generate cell values, find and replace across the grid, and copy staged changes as SQL.
Staged grid editing comes with the everyday tools that make bulk cleanup quick: text macros and value generators on any cell, find & replace across the grid, and a one-click copy of your staged changes as a SQL script. Every one of them produces ordinary staged changes, so they show up in the pending-changes bar, preview in Preview SQL, undo with Undo, and commit in the same single transaction as the edits you type by hand.
Transform a value
Right-click a text cell and pick a Transform: entry:
UPPERCASE, lowercase, Title Case,
Trim whitespace, or Remove diacritics (Crème Brûlée
becomes Creme Brulee, and letters with no decomposition, such as
Ł, Ø, and ß, fold to their ASCII base too).
Remove diacritics strips accents from Latin, Greek, and Cyrillic letters only.
In other scripts, such as Devanagari, Thai, and Hangul, the combining marks are part
of the spelling, so that text is left exactly as it was. Title Case capitalizes
the first letter of each word with proper title-case forms, so a word starting with
ß becomes Ss, and an accent never starts a new word.
If the cell already has a staged value, the macro rewrites that value, so you can
trim and then title-case before you save. Transforms apply only to editable
plain-text columns. Keys, computed columns, enum and SET columns, JSON
and XML columns, and non-text types are refused with a message that says why, as is
a result column that is an expression rather than a table column.
To clean up a whole batch at once, open the Transform menu on the
pending-changes bar. It applies a macro to every staged value in a plain-text column,
both edits and new rows, as a single undo step. Staged values in JSON, XML, enum, and
SET columns are left alone, because rewriting them can make them invalid.
In the row editor, every plain-text field has its own Transform menu
that rewrites the draft in place.

Insert a generated value or SQL function
The same menu fills a cell with a generated value:
- Set to new UUID writes a fresh random UUID generated on your machine. It is offered on UUID columns and on plain-text columns wide enough for the 36-character form (not on JSON, XML, enum, or
SETcolumns). - Set to current timestamp (local clock) writes your computer's current date and time, shaped to the column (date, time, or date and time).
- SQL functions stage an expression that the server evaluates when you save, so the value comes from the database rather than from your computer: server time, server date (
CURRENT_DATE,CURDATE(),CAST(SYSDATETIME() AS date)), a server-generated UUID (NEWID(),gen_random_uuid(),UUID(),SYS_GUID(),uuid()), and the current user (SUSER_SNAME(),CURRENT_USER,USER).
Each engine gets its own spelling of a function, and the preview script shows the
exact SQL. The grid marks a staged function as <server date>,
<server UUID>, and so on until you save. In the row editor, the
per-field Insert menu lists the same generators, with each
function's dialect SQL in its label.
SQLite, libSQL, and DuckDB have no login, so current user is not offered there. SQLite and libSQL have no UUID function, so a server-generated UUID is written as 32 random hex characters (lower(hex(randomblob(16)))). Oracle's SYS_GUID() likewise produces 32 uppercase hex characters with no hyphens, not the 36-character hyphenated form.
On a connection that goes through a relay bridge, the bridge says which features its SQLLY version supports when the session opens. Server date, server-generated UUID, and current user are offered only when the bridge supports them; with an older bridge, which would reject the whole edit, the grid menu hides them and the row editor lists them disabled with a note to update the bridge. Server time, Set to new UUID, and the local-clock timestamp work with any bridge. Staged grid edits are saved over a relay the same way as locally, in one transaction that rolls back if any row changed underneath, when the bridge supports it; with an older bridge the pending-changes bar says Saving needs a newer bridge and Save Changes stays disabled, while the row editor still saves rows one at a time.
Find & replace in the grid
Press the Find in Grid shortcut, or right-click and choose Find & Replace in Grid…, and type in the find field. The replace field sits underneath. Replace rewrites the focused match and Replace All rewrites every match. Matching is case-insensitive, and every occurrence inside a matched cell is replaced. The find text is used exactly as you typed it, including leading and trailing spaces, so replace rewrites the same text the grid highlighted.
Replacements become staged cell edits and are never written straight to the
database. A Replace All is one undo step, and the Messages tab reports how many
cells and rows were staged. Replace changes only cells it can safely rewrite:
editable plain-text columns. Matches in keys, identity, computed, rowversion,
enum-constrained, numeric, date, JSON, and XML columns are skipped, and so are NULL
cells, even though the grid displays them as NULL. A result column that
is an expression is skipped too, even when it has the same name as a table column
(SELECT id, left(body, 50) AS body): only columns the query selects
directly from the table are written. Rows already staged for deletion are skipped,
and so are cells holding a staged SQL function such as <server UUID>.
A replace works on the value you see, so it builds on anything you have already
staged in that cell. Every row keeps its own optimistic-concurrency guard: its
original values, and its own rowversion when the result includes one.
Replace All covers the rows the find bar searched. On a very large result that SQLLY keeps on disk, only the rows currently loaded around your scroll position are searched, and a search stops after 100,000 matches. When either limit applies, the Messages tab says exactly which rows were covered, for example the 100 loaded rows (3001–3100 of 5000). Scroll, or run Replace All again, to reach the rest.

Copy staged changes as SQL
Sometimes you want the script, not the save: to paste into a ticket, run it through a deployment process, or review it with a colleague first. With changes staged, click Copy as SQL on the pending-changes bar, on the Preview SQL tab, or in the Confirm Staged Changes dialog. You can also run Results: Copy Staged Changes as SQL from the command palette. The script is rendered at the moment you click, so an edit you made a split second earlier is always included. Copying never saves or discards anything. Your changes stay staged until you save or discard them.
The copied script has the same statements as Preview SQL, in the
same transaction wrapper, but it is not byte-for-byte what a save sends. A save
binds values as parameters, while the script inlines them as literals. A save also
requires every UPDATE and DELETE to change exactly one row,
and rolls the whole batch back if one doesn't, because the row changed or was
deleted since you loaded it. The script carries that check where the engine can
express it:
- SQL Server: each statement is followed by
IF @ROWCOUNT <> 1 THROW …, andSET XACT_ABORT ONrolls the transaction back. - PostgreSQL and compatible engines (except CockroachDB): each statement runs in a
DOblock that checks the row count and raises an error, which aborts the transaction. - Oracle: each statement runs in a PL/SQL block that checks
SQL%ROWCOUNT. Run the script withWHENEVER SQLERROR EXIT ROLLBACK(SQL*Plus or SQLcl) so a failed check also undoes the earlier statements. - MySQL, MariaDB, SQLite, libSQL, DuckDB, and CockroachDB have no way to check a row count in a plain script. Each statement is marked
-- expect 1 row, so check the counts yourself.
On SQL Server, a save also reads inserted rows back with OUTPUT INSERTED.*.
The script leaves that out. A header comment at the top of the script repeats these
differences.
