Releases: codedeviate/sqlt
v0.3.5
Full Changelog: v0.3.4...v0.3.5
v0.3.4
Full Changelog: v0.3.3...v0.3.4
v0.3.3
[0.3.3] - 2026-05-19
Added
man/sqlt.1system man page shipped with the crate tarball. Afterbrew install codedeviate/cli/sqlt(or any other install path that drops the file onto$MANPATH)man sqltrenders the full reference: NAME, SYNOPSIS, DESCRIPTION, COMMANDS, DIALECTS, ENCODINGS, LINT RULE CATEGORIES, TRANSLATION WARNINGS, EXAMPLES, EXIT STATUS, FILES, ENVIRONMENT, SEE ALSO, BUGS, AUTHOR.- Colorized
--examplesoutput across every subcommand, matching the recon-style scheme (yellow-bold section headers, bold sub-headings, cyan commands, green shell comments, dimmed dividers and notes). RespectsNO_COLOR/CLICOLOR_FORCE. README.mdheader now ships a badge row (GitHub, latest release, crates.io, Homebrew tap, Rust edition / MSRV, MIT license).
Changed
- Default SIGPIPE behaviour is restored at startup on Unix, so
sqlt man | less(or any other pager /head/grep) closing early ends the process quietly instead of panicking on the nextprintln!.
v0.3.2
[0.3.2] - 2026-05-08
Added
LICENSEfile containing the full MIT License text. Previously the repo declared a license inCargo.tomlbut shipped no LICENSE file.Cargo.tomlmetadata required forcargo publishto crates.io:authors,readme,homepage,documentation,keywords(sql,parser,translator,dialect,lint),categories(command-line-utilities,database,parser-implementations), and anexcludelist (.github/**,.idea/**,target/**,/.gitignore,OUT-OF-SCOPE.md,CLAUDE.md,BREW.md) that keeps maintainer-facing files out of the published tarball.[profile.release]settings (lto = true,codegen-units = 1,strip = "symbols") so binaries built by Homebrew (and anyone runningcargo install --path .) are smaller. Trade-off: longer release build (~3 minutes vs. seconds).README.mdinstall section now documents Homebrew (brew install codedeviate/cli/sqlt) and crates.io (cargo install sqlt) alongside the existingcargo install --path .source build.
Changed
- Homebrew distribution now lives in the consolidated
codedeviate/homebrew-clitap; the in-repoFormula/sqlt.rbtemplate was retired in favour of the tap's authoritative copy. The crate tarball no longer carries a Homebrew formula. Cargo.tomllicense field narrowed fromMIT OR Apache-2.0toMITto match the single LICENSE file in the repository.Cargo.tomlrepositoryfield corrected fromhttps://github.com/thomasbjork/sqlttohttps://github.com/codedeviate/sqlt(the actual remote).LICENSEcopyright holder set tocodedeviateto match the publishing identity used on crates.io and Homebrew.README.mdLicense section updated from the stale "Dual-licensed under MIT or Apache-2.0" text to a single MIT note that links toLICENSE.
v0.3.1
[0.3.1] - 2026-05-03
Added
- Top-level
sqlt --examplesflag. Prints a cross-cutting overview with example invocations of every subcommand. Complements the existing per-subcommandsqlt <COMMAND> --examples.
Changed
sqlt build-schema --helpnow renders the full long-form description (use cases, what's tracked, version handling) via aBUILD_SCHEMA_LONG_ABOUTconstant. Previously only the one-line summary was visible from--help, requiring users to guess the syntax.- Per-flag descriptions on
BuildSchemaArgs(every flag) describe accepted values, defaults, and interactions in line with the help-text policy inCLAUDE.md. sqlt build-schema --examplesrewritten to a four-section format: Flag reference (every flag, with defaults), Common workflows (covering each commonly-used flag combination), What gets tracked, Versioning, Exit codes. Includes process-substitution and jq-piping recipes.CLAUDE.md"Help & examples maintenance rule" expanded with concrete standards: every subcommand variant must have along_about(not just a one-line///), every flag mentioned in--helpmust appear in--examples, bothsqlt --examplesandsqlt <COMMAND> --examplesmust work. Listed as mandatory for every change that affects how the utility is used.
v0.3.0
[0.3.0] - 2026-05-02
Added
--schema <file>flag onsqlt lint(repeatable). Parses each file as schema input —CREATE TABLE,ALTER TABLE,DROP TABLE,CREATE INDEX, foreign-key constraints,CREATE DATABASE,USE. Replays them in CLI order so the schema reflects its current state, not its initial CREATE. Schema files are NOT linted (only feed the model). Statements that don't affect the schema (INSERT, GRANT, DELIMITER, stored-procedure bodies, …) emitnote: skipping <kind> at <file>:<line>on stderr. Files with.jsonextension are loaded as a previously built artifact (seebuild-schema).sqlt build-schemasubcommand. Compiles one or more--schemafiles into a JSON artifact that can be consumed by--schema schema.jsonlater. Use case: a long migration history that takes time to parse + replay can be compiled once, checked into the repo, and re-used by every CI lint run. Mixing.json+.sqlis supported:sqlt lint --schema base.json --schema migrations/late.sql query.sql.- Per-database namespacing. Schemas now track
CREATE DATABASEandUSE; tables live in per-database namespaces. Queries can use 3-partdb.table.colreferences; theUSEcursor persists across--schemafiles in CLI order. Same-named tables in different databases no longer collide (e.g.shop_db.ordersandglobal_db.orders). - Full DDL replay in
Schema::apply_statement: CREATE/ALTER/DROP TABLE, ADD/DROP/MODIFY/CHANGE/RENAME COLUMN, RENAME TO (within and across DB), CREATE [UNIQUE] INDEX, ALTER TABLE ADD INDEX/UNIQUE/PRIMARY KEY/FULLTEXT/SPATIAL, ADD CONSTRAINT FOREIGN KEY, DROP INDEX/CONSTRAINT/FOREIGN KEY/PRIMARY KEY, CREATE TABLE LIKE (column copy from referenced table), CREATE OR REPLACE TABLE.IF EXISTS/IF NOT EXISTSguards are honored. Missing-target ops emit a stderrnote:but never error. Schemais now serializable viaserdefor the JSON artifact path. Indexes and foreign keys are tracked in the model (no rule consumes them yet, but the data is available for future SQLT0503-style refinements).- ~17 new schema unit tests covering each DDL shape, plus 6 new CLI integration tests for
--schema, multi-DB namespacing, late-migration mixing, and the build-schema round-trip.
Added (earlier this cycle, still in 0.3.0)
- Schema-aware lint pass.
lint::lintnow builds aSchemamodel from everyCREATE TABLEstatement in the input before running rules and passes it to each rule viaLintCtx::schema. Lookups are case-insensitive;NOT NULLandPRIMARY KEYare tracked per column. - New rule SQLT0900 unknown-column (Schema category, error, default-on). Fires when a
table.colreference resolves to aCREATE TABLEin the same input but the column doesn't exist on that table. Also catches typos in theINSERT INTO Foo (col1, col2)column list. Conservative — never warns about CTEs, derived-table aliases, or tables defined elsewhere. - New rule category
Schema(SQLT09xxnamespace) for rules that consult the schema model.
Changed
- SQLT0505 count-of-nullable-column is now schema-aware: when a
CREATE TABLEfor the referenced column is present and the column is declaredNOT NULL, the warning is suppressed (sinceCOUNT(col)andCOUNT(*)are guaranteed equivalent). - SQLT0400 not-in-subquery-null-pitfall is now schema-aware: when the inner subquery projects a single column that resolves to a known
NOT NULLcolumn, the warning is suppressed (the NULL pitfall cannot trigger).
Fixed
- MariaDB parsing now uses
MySqlDialectdirectly instead of a forwarding wrapper. The wrapper faileddialect_of!(MySqlDialect)downcast checks scattered through sqlparser, which silently disabled MySQL-superset features that real MariaDB grammar relies on (ON UPDATEtimestamp column option, table hints,LIMIT a, b,LOCK/UNLOCK TABLES, etc.). Real-worldmariadb-dumpoutput now parses to typed AST instead of falling back to raw passthrough — verified on a 73 KB production schema where 49 raw fragments collapsed to 17 (only legitimately-tricky DELIMITER directives and stored-program bodies remain). - MariaDB inputs are pre-processed to inject a space after bare
--at end-of-line.mariadb-dumpemits--on a line by itself; sqlparser tokenized that as two minus operators becauseMySqlDialect::requires_single_line_comment_whitespaceistrue. The preprocessor tracks string-literal / quoted-identifier / block-comment / line-comment state so the substitution never corrupts data. RawStatementcarries astart_lineso SQLT0001 (raw-passthrough) lint diagnostics report the actual source line of each fragment instead of all firing at1:1. The field is metadata only —PartialEqforRawStatementignores it so round-trip parse → emit → parse still produces equal ASTs.- Raw classifier recognises
delimiterandstored_program_body(CREATE TRIGGER/FUNCTION/PROCEDURE) reasons so the diagnostic message is more actionable. - Raw classifier additionally recognises
optimization_hint(ALTER TABLE … DISABLE/ENABLE KEYS, the standard mariadb-dump wrapper around per-table INSERT blocks),definer_clause(CREATE DEFINER=…), andcreate_event. The classifier peeks through a leading conditional comment (/*!NNN …*/,/*M!NNN …*/) before pattern-matching, so wrapped statements are recognised by their inner content. - MariaDB/MySQL conditional comments (
/*!NNN … */,/*M!NNN … */, also their unversioned forms) are unwrapped during MariaDB preprocessing so the inner SQL parses to typed AST. Marker characters are replaced with spaces of equal length so source line/column positions are preserved end-to-end. - Per-fragment AST line numbers are now rebased to the original file's line space. The MariaDB fallback path prepends
start_line - 1newlines to each fragment before re-parsing, so everyLocation.linein the resulting AST refers to the actual source file (e.g. lint findings on aCREATE TABLEat file line 17981 now report17981, not1). - DDL rules (
SQLT0801 float-for-money,SQLT0802 varchar-without-length) now attach diagnostics to the column's identifier span (col.name.span) rather than the statement span.Statement::CreateTablehas noSpannedimpl upstream, so the previous span fell back to1:1; the column-name span is always populated.
Changed
- Text-format
help:lines are deduplicated by default. The same suggestion is emitted only once per(rule_id, suggestion)pair within a render — for SQLT0001 with hundreds of identical raw-passthrough findings the help shows once at the first occurrence. Output volume on a 7 MB MariaDB dump dropped from 1371 lines to 688 (~50%) with no information lost.
Added
--help-mode auto|always|neverflag onsqlt lintcontrolling text-format help rendering.auto(default) deduplicates per(rule_id, suggestion).alwaysrestores the per-finding rendering.neversuppresses help entirely.--no-helpflag as a shorthand for--help-mode never.--verbose/-vflag onsqlt lint. SQLT0001 (raw-passthrough) is now off by default — realmariadb-dumpoutput emits hundreds of fragments sqlparser can't parse (DISABLE/ENABLE KEYS, DELIMITER directives, CREATE DEFINER prefixes), and the flood of identical warnings drowned the typed-AST rule findings.--verboseenables SQLT0001 (equivalent to--rule SQLT0001).--examplesflag on every subcommand (parse,emit,translate,lint). Prints in-depth examples and exits 0 without parsing any input. Examples cover the basic invocation, every commonly-used flag, file-vs-stdin handling, encoding handling, and (forlint) at least one workflow per rule category. Source lives insrc/cli/examples.rs.--list-rulesflag onsqlt lint. Prints a sortable table of every registered rule with id, slug, category, default severity, default-enabled state, and one-line summary, plus a footer counting total / on / off. Exits 0 without parsing input.- Greatly expanded long-help text on every subcommand and flag. Each flag now documents its accepted values, default, and interaction with other flags. The terse short-help (
-h) is unchanged; the full long-help (--help) is the new in-depth surface.
Removed
src/dialect/mariadb.rs(theMariaDbDialectwrapper struct). It was the source of the silent feature-flag disablement above. MariaDB-specific parser tweaks now live insrc/parse/mod.rs::preprocess_mariadb.
v0.2.0
[0.2.0] - 2026-05-02
Added
--encoding/-eflag onparse,emit, andtranslatefor non-UTF-8 input/output. Supported values:utf-8(default),iso-8859-1(aliaslatin1),windows-1252(aliascp1252). Decoding is strict — invalid byte sequences are rejected with exit code 1 rather than substituted withU+FFFD. JSON I/O is always UTF-8 (per spec); the flag governs SQL bytes only.Encodingtype insrc/encoding.rswrappingencoding_rswith strict decode/encode semantics and aliases.- CLI tests for Latin-1 round-trip (high-bit byte preservation through
translate), default-mode rejection of non-UTF-8 input, and unknown-encoding exit code. sqlt lintsubcommand with 38 active rules across 8 categories: raw passthrough, dialect cross-contamination, translation pre-flight (driven bydialect/caps.rswhen--tois set), join hygiene, subquery improvements, performance pitfalls, correctness pitfalls, style/readability, and DDL hygiene. Every rule has a stable id (SQLT0500), a slug (select-star), inline summary + long-form explanation accessible viasqlt lint --explain, and per-rule fixtures undertests/fixtures/lint/.- Four lint output formats:
text(default, single-line grep-friendly),pretty(grouped per file with snippet pointer and inline rule explanation on first occurrence),json, and SARIF 2.1.0 for GitHub code-scanning integration. - Lint CLI flags:
--rule/--no-rule(repeatable, accept full id / short numeric / slug),--severity(output filter),--exit-on(exit-code threshold, defaulterror),--explain <id>(print rule docs and exit 0). - Lint architecture:
Ruletrait whose implementors register only the AST-shape callbacks they need; a shared driver insrc/lint/walk.rsdoes one traversal per statement (sqlparser'sVisitordoesn't fire onSelect, so the driver does manual descent intoSetExpr::Select/TableWithJoins).check_queryreceives a depth parameter so rules likeSQLT0403 order-by-in-subquery-without-limitonly fire on nested queries. tests/lint.rsper-rule fixture walker plusinstasnapshot tests for json/sarif output.
v0.1.0
0.1.0 - 2026-05-02
Added
-
Initial project scaffolding: Cargo manifest, module layout, conventions documentation.
-
sqlt parse --from <dialect> [--pretty] [file|-]subcommand. Reads SQL from a file or stdin and emits a JSON envelope{ sqlt_version, dialect, statements }using the upstream sqlparser AST's serde representation. -
Dialects supported by
parse:mysql,postgres(aliasespostgresql,pg),mssql(aliasestsql,sqlserver),sqlite,generic. -
Smoke parse fixtures under
tests/fixtures/<dialect>/*.sqlcoveringSELECT,INSERT,RETURNING,ON CONFLICT,TOP, and bracketed identifiers. -
sqlt emit --to <dialect> [file|-]subcommand. Reads a JSON envelope and emits SQL using the upstream sqlparserDisplayimpls (per-dialect overrides land later as round-trip tests find infidelities). -
Round-trip integration suite (
tests/roundtrip.rs) assertingparse → emit → parseproduces an identical AST for every fixture across mysql/postgres/mssql/sqlite, plus JSON serde round-trip equivalence. -
MariaDB dialect (
mariadb, aliasmaria) as a first-class target.MariaDbDialectwraps the upstreamMySqlDialectand the parser falls back to a raw-passthrough representation (SqltStatement::Raw { sqlt_raw, reason }) for MariaDB-specific syntax with no upstream AST node —WITH SYSTEM VERSIONING,FOR SYSTEM_TIME,CREATE PACKAGE, MariaDB sequence option ordering, vector types. Same-dialect round-trip (parse → emit) preserves the original SQL verbatim for these. -
SqltStatementenum (Std(Box<Statement>) | Raw(RawStatement)) with#[serde(untagged)]so the JSON wire format for typed statements is unchanged. -
Heuristic statement splitter (
parse::split) used by the MariaDB fallback path. Respects single/double quotes, backticks, and line/block comments. -
sqlt translate --from <src> --to <dst> [--strict] [file|-]subcommand. Parses the input, rewrites the AST against the target dialect's capability table, and emits SQL. Warnings are printed to stderr;--strictmakes any warning a non-zero exit (code 3). -
Per-dialect capability tables (
dialect/caps.rs) coveringRETURNINGon INSERT/UPDATE/DELETE,CREATE SEQUENCE,ON DUPLICATE KEY UPDATE,ON CONFLICT, and MariaDB raw fallback support. -
Translation rewriter (
translate/rewrite.rs) that dropsRETURNINGwhen the target lacks it, warns whenCREATE SEQUENCEcannot be represented, and warns when a MariaDB raw fragment passes through to a non-MariaDB target. -
WarnCodeenum (RETURNING_DROPPED,SEQUENCE_DROPPED,ON_DUPLICATE_KEY_UNSUPPORTED,RAW_PASSTHROUGH) withWarnSinktrait,StderrSinkfor the CLI, andCollectingSinkfor golden tests. -
Golden translation test harness (
tests/translate.rs) walkingtests/fixtures/translations/<src>__<dst>/<case>.{in.sql,expected.sql,expected.warn}. Coversmariadb→mysql(RETURNING dropped),mariadb→postgres(RETURNING through, system-versioning passes raw with warning),postgres→mariadb(RETURNING through cleanly). -
End-to-end CLI integration suite (
tests/cli.rs) exercising the builtsqltbinary: parse→emit pipe round-trip, translate warning emission,--strictexit code 3, parse error exit code 1, unknown dialect exit code 2, and multi-statement input parsing.
Changed
- The
statementsfield of the JSON envelope is nowVec<SqltStatement>instead ofVec<Statement>. For typed statements the on-the-wire shape is unchanged thanks to#[serde(untagged)]; only raw fallback fragments introduce a new shape.