v1.1.0
The parser now follows ClickHouse 26.8 much more closely.
- Silent SQL losses fixed: it no longer drops or changes clauses when printing. For example,
DETACHused to print asDROP,SELECT 1, limit(1)becameLIMIT (1), andINTERVAL … PRECEDINGlostPRECEDING. - Much more valid SQL accepted:
EXPLAINin all forms,INTERSECT,PASTE/NATURAL JOIN,CREATE/DROP INDEX,{name:Identifier}parameters, table-function expressions, and every SYSTEMSYNC/FLUSH/START|STOPform. - SQL that ClickHouse rejects is rejected too.
- Formatting:
PrintVisitoris about twice as fast. - Traversal:
Walkand the visitor reach every node.
⚠️ This minor release contains breaking API changes. Go 1.27 is required,ASTVisitorhas new methods, some fields changed type or can now be nil, and positions and AST shapes changed. Read the Breaking section below (also inCOMPATIBILITY.md) before upgrading.
Breaking
- Go 1.27 is required (
go.modsaidgo 1.21.0). ASTVisitorhas new methods:VisitStreamClause(#102),VisitCreateIndex
andVisitDropIndex(#135). Visitors embeddingDefaultASTVisitoror
PrintVisitorinherit them; direct implementations must add them.
StreamClause.Acceptnow dispatches toVisitStreamClause.- Changed types:
ShowStmt.Formatis*FormatClause, from the embedded
OutputClauses(name inFormat.Format.Name; was*StringLiteral), and
prints unquoted,FORMAT JSONrather thanFORMAT 'JSON'(#87, #49). - Fields that can now be nil, or hold other node types:
SubQuery.Selectis nil for(EXPLAIN …); seeSubQuery.Explain(#133);TableIndex.Granularityis nil whenGRANULARITYis omitted (#134);ExplainStmt.Statementcan be any statement, not only*SelectQuery, and
is nil forEXPLAIN CURRENT TRANSACTION;ExplainStmt.Typeis empty when
the kind is omitted (#86);SystemCtrlExpr.Cluster(the table) is nil when none is given (#108);TableArgListExpr.Argscan hold any expression (#129).
- Positions (#92):
Pos()/End()of a quotedStringLiteral/Identinclude the quotes; the
LiteralPos/LiteralEnd/NamePos/NameEndfields still exclude them;End()of nodes closed by),]or}points after the delimiter
(ParamExprList,FunctionExpr,ArrayParamList,MapLiteral,
TableSchemaClause,TableArgListExpr,ComplexType,QueryParam, …);- a statement's
[Pos(), End())covers exactly its text (it used to stop
early or include the;); - corrected fields:
CompressionCodec.RightParenPos(the)), column
COMMENTLiteralPos(the string),TypedPlaceholder.RightBracePos(the
}),Fill.FillPos(FILL),IsNullExpr/IsNotNullExpr.IsPos(IS;
Pos()is the operand),ShowStmt/GrantPrivilegeStmt.StatementEnd; SystemSyncExpr.End(),SystemFlushExpr.End(),ExplainStmt.End()and
TableIndex.End()cover the whole node (#62, #114, #86, #134).
- AST shapes:
ATTACH/DETACHkeep their verb (IsAttach/IsDetach;DropStmt.Type()
returnsDETACH TABLE);DETACH TABLE tused to print asDROP TABLE t
(#43, #52, #68);- a parenthesised set-operation operand is a group node: a
SelectQuerywith
HasParenand the inner query inGroup(#79, #93); - trailing
FORMAT/query-levelSETTINGSof a statement live in the embedded
OutputClauses; afterFORMATon a SELECT inSelectQuery.OutputSettings
(#87); - all parts of one TimeSeries target form a single
TimeSeriesTargetClause
(#42); FROM t STREAMisTableExpr.Stream, not the aliasSTREAM(#81);JoinExpr.ModifierskeepsGLOBAL, and can holdNATURAL/PASTE;
a LOCAL JOIN baliasesaasLOCAL(#74, #89);- lower-case
array joinis an ARRAY JOIN (JoinExpr.Leftis a
*ColumnExprList), not a join on a table named by its first expression
(#50); SystemCtrlExpr.Typeis the whole target, e.g."REPLICATION QUEUES"
(#108).
Changed behavior
- Traversal:
Walk(andFind,FindAll,WalkWithBreak,Transform)
andDefaultASTVisitornow reach every child node and agree with each other
(#105, #106). They used to skip, among others,EXCEPToperands,INSERT … VALUESrows, TTL actions, named-collection parameters,DISTINCT ON
columns, projectionORDER BYcolumns andRENAMEpairs. Map keys are
walked in place, so rewriting one changes the AST.IntervalFromand
WindowFrameParamno longer visit their child twice (#98). PrintVisitorprints every node like itsString()(#98) and streams
into one builder (#99).BeautifyVisitoremits every statement type (#51).- Printed SQL fixed (the old output was invalid or meant something else):
WINDOWafterHAVING(#88);IF [NOT] EXISTSbefore the name inDROP INDEX|PROJECTIONandADD INDEX(#64);PRECEDING/FOLLOWINGafter anINTERVALframe bound (#109);SYSTEM SYNC REPLICA(#62),SYSTEM FLUSH DISTRIBUTED(#114),SYSTEM STOP|START DISTRIBUTED SENDSwithout… SENDS SENDS(#108);SELECT 1, limit(1)keeps the column instead of becomingLIMIT (1)
(#138);PASTE/NATURAL JOINare joins, not aliases (#89);(3,)keeps its comma (#70),f(p)(DISTINCT x)itsDISTINCT(#71),
INSERT INTO FUNCTIONitsFUNCTION(#72),PARTITION IDitsID(#76),
WITH TOTALSwithoutGROUP BY(#77),WITH FILL(#47);USINGis always parenthesised and comma joins print,(#74);-1::T
keeps the sign on the literal (#83);TOP n WITH TIES(#90); no double
spaces inCREATE TABLE t AS …/CREATE DATABASE … ENGINE(#45, #68);CREATE TABLEkeeps its query-levelSETTINGS, and repeatedCREATE USER
HOST/SETTINGSclauses are merged (#37).
- Parsing:
INTERSECTis a keyword, not an implicit alias (#107);
SELECT 1 EXCEPT (SELECT 2)is a set operation, not a column transformer
(#107); in an expression{always starts a query parameter, and a
{'k': v}map literal is valid only inINSERT … VALUESrows and as a
SETTINGSvalue (#50).
Newly rejected input
- Select list and joins: a missing select list (
SELECT,SELECT FROM t;
a loneSELECTused to parse to zero statements) (#82); a comma before a
clause other thanFROM(SELECT a, ORDER BY a) (#138); aJOINwithout
ON/USINGexceptCROSS,NATURAL,PASTE,ARRAY(#50);PASTE/
NATURALas an implicit alias, or withON/USING, a strictness,CROSS
orARRAY(#89);GLOBAL ARRAY JOINandLEFT GLOBAL JOIN(#74). - Expressions: column transformers (
EXCEPT,APPLY,REPLACE) after
anything but*,t.*,COLUMNS(…)or* LIKE 'p'(#50);WITH CUBE/ROLLUP
afterGROUP BY CUBE(…)/ROLLUP(…)(#50); a map literal in an expression
(#50); a trailing comma except in a one-element tuple (f(a,),[1,])
(#70). - Repeated or misplaced clauses: repeated engine clauses in
CREATE TABLE,
repeatedCREATE USERclauses,GROUP BYmodifiers out of order (#37);
repeated TimeSeries target parts andRECENTwithoutSAMPLES(#42); an
alias,FINALorSAMPLEafterSTREAM(#81); repeatedWITH TIESand
LIMIT … BY … WITH TIES(#90);FORMAT/SETTINGSafter statements that
take none (GRANT, SYSTEM, USE, SET, DELETE, USER/ROLE, CREATE FUNCTION/NAMED
COLLECTION; used to be dropped silently), a quotedSHOW … FORMAT 'x', and
INSERT … VALUES (…) FORMAT x(#87). - DDL: an empty column list
()(#45);ATTACH OR REPLACE,
ATTACH/DETACHof FUNCTION/ROLE/USER/NAMED COLLECTION,DROP … PERMANENTLY,CREATE DICTIONARY dwithout a definition (#68);OR REPLACE
withIF NOT EXISTSinCREATE TABLE/VIEW(#50); several pairs inRENAME DATABASE(#50); aPARTITIONvalue other than a literal, query parameter,
tuple or CAST of one (DROP PARTITION p,PARTITION toDate(…)) (#50); a
malformedUUID '…'(ClickHouse:CANNOT_PARSE_UUID) (#50). - SYSTEM:
START|STOP DISTRIBUTED MERGES|FETCHES|TTL MERGES(used to print
as… MERGES MERGES) (#108). - Access control (#50): several roles in
ALTER ROLE … RENAME TO;ON CLUSTERbefore the last role name;WITH ADMIN OPTIONon a privilege grant;
a column list on a wildcard target; the target*.table;GRANT ADMIN OPTION ON ….
Additive
- New types:
OutputClauses(#87),StreamClause(#81),CreateIndex,
DropIndex(#135). - New fields:
CreateTable.Settings,WithTimeoutClause.WithTimeoutEnd
(#37);TimeSeriesTargetClause.InnerUUID,EngineShorthand(#42);
IsAttach/IsDetach/Permanently,DropDatabase.Modifier(#68);
ColumnExprList.HasTrailingComma(#70);AlterTableDelete.InPartition
(#76);SelectQuery.HasParen,Group,OutputSettings,Union,
Intersect,IntersectModifier,ExceptModifier(#79, #87, #93, #107,
#125);TableExpr.Stream(#81);LimitClause.WithTies,WithTiesEnd(#90);
OrderExpr.Nulls,Collate(#47);SystemSyncExpr.SyncEnd,Target,
OnCluster,Database,IfExists,Mode,From,CacheName(#62);
SystemCtrlExpr.OnCluster(#108);SystemFlushExpr.AsyncInsertQueue,
OnCluster,Tables,Settings(#114);ExplainStmt.Settings,
ExplainEnd(#86);Ident.Paramfor{name:Identifier}(#131);
SubQuery.Explain(#133); span fieldsCastExpr.RightParenPos,
SubQuery.RightParenPos,UsingClause.UsingEnd,OrderExpr.OrderEnd,
NamedCollectionParam.ParamEnd,IsNullExpr/IsNotNullExpr.NullEnd(#92). - New methods:
VisitStreamClause,VisitCreateIndex,VisitDropIndexon
DefaultASTVisitorandPrintVisitor;InsertStmt.IsTableFunction()(#72). - Newly accepted input:
- SELECT:
INTERSECT [DISTINCT|ALL],EXCEPT DISTINCT|ALL, bareUNION
(#107, #125);(SELECT …) UNION …and a statement starting with(
(#93);EXPLAINwithout a kind, with settings, of any statement, and as a
subqueryFROM (EXPLAIN …)(#86, #133); any expression as a
table-function argument (#129);{name:Identifier}as a table, database or
other name (#131);PASTE JOIN,NATURAL … JOIN,GLOBAL <kind> JOIN
(#74, #89);FROM t STREAM [BOUNDED] [UNORDERED](#81);LIMIT n WITH TIES(#90);NULLS FIRST|LASTandCOLLATEin ORDER BY (#47); a
keyword-named function after a comma (SELECT 1, format(…)) (#138); map
literals inINSERT … VALUES(#50). - DDL:
CREATE [UNIQUE] INDEXandDROP INDEX(#135); an index without
GRANULARITY(#134);CREATE TABLE … SETTINGS … SETTINGS …(#37);
TimeSeriesRECENT SAMPLES,INNER UUID,INNER ENGINE(#42);DETACH … PERMANENTLY,ATTACH DICTIONARY d,DROP|DETACH DATABASE … SYNC(#68);
DROP [DETACHED] PARTITION ID,DELETE IN PARTITION(#76);FORMAT … SETTINGSon non-SELECT statements (#87); a final single-token clause at
end of input, e.g.ALTER TABLE t FREEZE(#46). - SYSTEM: every
SYNCform (#62),START|STOPtargets withON CLUSTER
and table (#108),FLUSH LOGS [log, …],FLUSH DISTRIBUTED … SETTINGS,
FLUSH ASYNC INSERT QUEUE(#114).
- SELECT:
What's Changed
- Parser: keep query-level CREATE TABLE SETTINGS and reject repeated clauses by @orian in #37
- Parser: support TimeSeries RECENT SAMPLES and INNER UUID targets by @orian in #42
- Parser: reject empty column list and fix double space before AS schema by @orian in #45
- BeautifyVisitor: emit every statement type instead of dropping it by @orian in #51
- Parser: add missing space after CASE operand before WHEN by @orian in #69
- Parser: keep ATTACH and DETACH instead of printing CREATE and DROP by @orian in #68
- Parser: keep the trailing comma of a one-element tuple by @orian in #70
- Parser: print DISTINCT in a parametric aggregate's argument list by @orian in #71
- Parser: keep FUNCTION in INSERT INTO [TABLE] FUNCTION by @orian in #72
- Parser: keep GLOBAL on joins and parenthesise USING by @orian in #74
- Parser: keep ID in PARTITION ID and parse it in DROP and DELETE by @orian in #76
- Parser: print WITH TOTALS when a SELECT has no GROUP BY by @orian in #77
- Parser: keep parentheses around a UNION/EXCEPT operand by @orian in #79
- Parser: parse FROM t STREAM instead of reading STREAM as an alias by @orian in #81
- Parser: print a sign directly against a numeric literal by @orian in #83
- Parser: keep query output FORMAT/SETTINGS instead of dropping FORMAT by @orian in #87
- Parser: support LIMIT n WITH TIES and keep TOP n in TOP n WITH TIES by @orian in #90
- Parser: fix statement Pos/End spans and check them for every fixture by @orian in #92
- Docs: add COMPATIBILITY.md for public API changes by @orian in #94
- Parser: model parenthesised set-operation operands as group nodes by @orian in #96
- Parser: support NULLS FIRST/LAST and COLLATE in ORDER BY elements by @orian in #97
- PrintVisitor: print every subtree like String() and stop crashing by @orian in #100
- PrintVisitor: stream expression nodes instead of calling String() (#99, part 1) by @orian in #101
- PrintVisitor: stream SELECT-area nodes instead of calling String() (#99, part 2) by @orian in #103
- PrintVisitor: stream DDL nodes instead of calling String() (#99, part 3) by @orian in #104
- PrintVisitor: keep PRECEDING/FOLLOWING after an INTERVAL frame bound (#109, P1) by @orian in #110
- Parser: keep REPLICA in SYSTEM SYNC and support the other SYNC forms (#62) by @orian in #113
- PrintVisitor: stream ALTER nodes instead of calling String() (#99, part 4) by @orian in #112
- Parser: fix SYSTEM START|STOP targets; stop reading SENDS as a table (#108, P1) by @orian in #116
- Walk: visit every child the visitor reaches (#105, P1) by @orian in #117
- Visitor: recurse into every child Walk reaches; add VisitStreamClause (#106, #102) by @orian in #118
- Print WINDOW after HAVING, where ClickHouse expects it (#88) by @orian in #119
- Print IF [NOT] EXISTS before the name in ALTER DROP/ADD INDEX|PROJECTION (#64) by @orian in #121
- Parser: keep DISTRIBUTED in SYSTEM FLUSH and support the other FLUSH forms (#114) by @orian in #122
- Parser: accept EXPLAIN without a kind, all kinds, settings and any statement (#86) by @orian in #124
- Parser: support INTERSECT, EXCEPT DISTINCT|ALL and bare UNION (#107, #125) by @orian in #127
- PrintVisitor: stream the remaining statements (#99, part 5, final) by @orian in #128
- Parser: fix end-of-input token skipping, empty select lists, and keyword calls after a comma (#46, #82, #138 P1) by @orian in #139
- Add a gofmt pre-commit hook and a gofmt check in CI by @orian in #140
- Make the test suite faster: no coverage in make test; require Go 1.27 by @orian in #142
- Parser: accept any expression as a table-function argument (#129) by @orian in #141
- Parser: accept {name:Identifier} query parameters as names (#131) by @orian in #144
- Parser: make GRANULARITY optional in index definitions (#134) by @orian in #145
- Parser: accept EXPLAIN as a subquery, FROM (EXPLAIN ...) (#133) by @orian in #146
- Add String/PrintVisitor benchmarks and a cross-version benchmark script by @orian in #148
- Parser: support PASTE JOIN and NATURAL JOIN (#89) by @orian in #147
- Parser: support CREATE INDEX and DROP INDEX statements (#135) by @orian in #149
- Parser: reject SELECT forms ClickHouse rejects; accept maps in VALUES (#50, part 1) by @orian in #150
- Parser: reject DDL forms ClickHouse rejects (#50, part 2) by @orian in #151
- Parser: reject role and GRANT forms ClickHouse rejects (#50, part 3) by @orian in #153
- Release v1.1.0 by @orian in #154
Full Changelog: v1.0.4...v1.1.0