@hdnax/sqlingo.js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COPYRIGHT_NOTICE +31 -0
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/index.cjs +46 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +14429 -0
- package/dist/index.d.ts +14429 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/huydna/projects/sqlingo.js/dist/index.cjs","../package.json","../src/errors.ts","../src/port_internals/ops_utils.ts","../src/parser/index.ts","../src/generator.ts","../src/dialects/dialect.ts","../src/tokens.ts","../src/optimizer/qualify_columns.ts","../src/lineage.ts","../src/optimizer/merge_subqueries.ts","../src/dialects/mysql.ts","../src/planner.ts","../src/executor/env.ts","../src/executor/javascript.ts"],"names":["package_default","ANSI_UNDERLINE","ANSI_RESET","ErrorLevel","SqlglotError","message","UnsupportedError","ParseError","_ParseError","errors","description","line","col","startContext","highlight","endContext","intoExpression","TokenError","OptimizeError","SchemaError","ExecuteError","highlightSql","options","sql","positions","contextLength","firstHighlightStart","formattedParts","previousPartEnd","sortedPositions","a","b","start","end","highlightStart","highlightEnd","source","Scope"],"mappings":"AAAA,qrBAAI,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,kCAAC,CAAC,4BAAE,CAAC,EAAE,CAAC,UAAU,CAAC,GAAC,SAAE,MAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,4CAA4C,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CCA97D,IAAAA,EAAAA,CAAA,CACE,OAAA,CAAW,2CAAA,CACX,IAAA,CAAQ,mBAAA,CACR,OAAA,CAAW,CACT,MAAA,CAAU,0CAAA,CACV,OAAA,CAAW,SAAA,CACX,GAAA,CAAO,oCACT,CAAA,CACA,OAAA,CAAW,OAAA,CACX,IAAA,CAAQ,QAAA,CACR,WAAA,CAAe,8EAAA,CACf,IAAA,CAAQ,gBAAA,CACR,MAAA,CAAU,eAAA,CACV,KAAA,CAAS,iBAAA,CACT,OAAA,CAAW,CACT,GAAA,CAAK,CACH,KAAA,CAAS,mBAAA,CACT,MAAA,CAAU,iBAAA,CACV,OAAA,CAAW,kBACb,CACF,CAAA,CACA,OAAA,CAAW,CACT,IAAA,CAAQ,QAAA,CACR,SAAA,CAAW,aAAA,CACX,eAAA,CAAiB,mBAAA,CACjB,KAAA,CAAS,MAAA,CACT,GAAA,CAAO,cAAA,CACP,SAAA,CAAa,cAAA,CACb,IAAA,CAAQ,UAAA,CACR,UAAA,CAAY,gBAAA,CACZ,IAAA,CAAQ,SAAA,CACR,YAAA,CAAc,iBAAA,CACd,YAAA,CAAc,oCAAA,CACd,SAAA,CAAa,WAAA,CACb,OAAA,CAAW,mBAAA,CACX,OAAA,CAAW,2DACb,CAAA,CACA,QAAA,CAAY,CACV,KAAA,CACA,QAAA,CACA,YAAA,CACA,WAAA,CACA,SAAA,CACA,YAAA,CACA,gBACF,CAAA,CACA,MAAA,CAAU,SAAA,CACV,OAAA,CAAW,KAAA,CACX,cAAA,CAAkB,cAAA,CAClB,KAAA,CAAS,CACP,MAAA,CACA,SAAA,CACA,WAAA,CACA,kBACF,CAAA,CACA,eAAA,CAAmB,CACjB,iBAAA,CAAmB,SAAA,CACnB,gBAAA,CAAkB,QAAA,CAClB,cAAA,CAAgB,QAAA,CAChB,aAAA,CAAe,SAAA,CACf,MAAA,CAAU,SAAA,CACV,IAAA,CAAQ,QAAA,CACR,OAAA,CAAW,UAAA,CACX,sBAAA,CAAwB,QAAA,CACxB,UAAA,CAAc,QAAA,CACd,MAAA,CAAU,SACZ,CAAA,CACA,gBAAA,CAAoB,CAClB,KAAA,CAAS,QACX,CACF,CAAA,CCpEA,IAAMC,EAAAA,CAAiB,SAAA,CACjBC,EAAAA,CAAa,SAAA,CAGZ,IAAWC,EAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAChBA,CAAAA,CAAAA,CAAAA,CAAA,MAAA,CAAA,CAAA,CAAA,CAAA,QAAA,CACAA,CAAAA,CAAAA,CAAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA,MAAA,CACAA,CAAAA,CAAAA,CAAAA,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,OAAA,CACAA,CAAAA,CAAAA,CAAAA,CAAA,SAAA,CAAA,CAAA,CAAA,CAAA,WAAA,CAJgBA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,EAAA,CAAA,CAAA,CAAA,CAiBLC,EAAAA,wBAAN,MAAA,QAA2B,KAAM,CACtC,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,cAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAEaC,EAAAA,4BAAN,MAAA,QAA+BF,EAAa,CACjD,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,kBAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAEaE,EAAAA,sBAAN,MAAMC,EAAAA,QAAmBJ,EAAa,CAG3C,WAAA,CAAaC,CAAAA,CAAiBI,CAAAA,CAAwB,CACpD,KAAA,CAAMJ,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,YAAA,CACZ,IAAA,CAAK,MAAA,CAASI,CAAAA,EAAU,CAAC,CAAA,CACzB,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CAEA,OAAO,GAAA,CACLJ,CAAAA,CACAK,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACY,CACZ,OAAO,IAAIR,CAAAA,CAAWH,CAAAA,CAAS,CAC7B,CACE,WAAA,CAAAK,CAAAA,CACA,IAAA,CAAAC,CAAAA,CACA,GAAA,CAAAC,CAAAA,CACA,YAAA,CAAAC,CAAAA,CACA,SAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,cAAA,CAAAC,CACF,CACF,CAAC,CACH,CACF,CAAA,CAEaC,EAAAA,sBAAN,MAAA,QAAyBb,EAAa,CAC3C,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,YAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAEaa,EAAAA,CAAN,MAAA,QAA4Bd,EAAa,CAC9C,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,eAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAEac,EAAAA,CAAN,MAAA,QAA0Bf,EAAa,CAC5C,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,aAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAEae,EAAAA,CAAN,MAAA,QAA2BhB,EAAa,CAC7C,WAAA,CAAaC,CAAAA,CAAiB,CAC5B,KAAA,CAAMA,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,cAAA,CACZ,KAAA,CAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,CAChD,CACF,CAAA,CAGO,SAASgB,EAAAA,CAAcC,CAAAA,CAON,CACtB,GAAM,CACJ,GAAA,CAAAC,CAAAA,CAAK,SAAA,CAAAC,CAAAA,CAAW,aAAA,CAAAC,CAAAA,CAAgB,GAClC,CAAA,CAAIH,CAAAA,CACJ,EAAA,CAAIE,CAAAA,CAAU,MAAA,GAAW,CAAA,CACvB,MAAM,IAAI,KAAA,CAAM,wDAAwD,CAAA,CAG1E,IAAIX,CAAAA,CAAe,EAAA,CACfE,CAAAA,CAAa,EAAA,CACbW,CAAAA,CAAsB,CAAA,CACpBC,CAAAA,CAA2B,CAAC,CAAA,CAC9BC,CAAAA,CAAkB,CAAA,CAChBC,CAAAA,CAAkB,CAAC,GAAGL,CAAS,CAAA,CAAE,IAAA,CAAK,CAACM,CAAAA,CAAGC,CAAAA,CAAAA,EAAMD,CAAAA,CAAE,CAAC,CAAA,CAAIC,CAAAA,CAAE,CAAC,CAAC,CAAA,CAE7D,CAAA,CAAIF,CAAAA,CAAgB,CAAC,CAAA,CAAE,CAAC,CAAA,EAAA,CAC1BH,CAAAA,CAAsBG,CAAAA,CAAgB,CAAC,CAAA,CAAE,CAAC,CAAA,CAC1ChB,CAAAA,CAAeU,CAAAA,CAAI,KAAA,CACjB,IAAA,CAAK,GAAA,CAAI,CAAA,CAAGG,CAAAA,CAAsBD,CAAa,CAAA,CAC/CC,CACF,CAAA,CACAC,CAAAA,CAAe,IAAA,CAAKd,CAAY,CAAA,CAChCe,CAAAA,CAAkBF,CAAAA,CAAAA,CAGpB,GAAA,CAAA,GAAW,CAACM,CAAAA,CAAOC,CAAG,CAAA,GAAKJ,CAAAA,CAAiB,CAC1C,IAAMK,CAAAA,CAAiB,IAAA,CAAK,GAAA,CAAIF,CAAAA,CAAOJ,CAAe,CAAA,CAChDO,CAAAA,CAAeF,CAAAA,CAAM,CAAA,CACvBE,CAAAA,EAAgBD,CAAAA,EAAAA,CAGhBN,CAAAA,CAAkBM,CAAAA,EACpBP,CAAAA,CAAe,IAAA,CAAKJ,CAAAA,CAAI,KAAA,CAAMK,CAAAA,CAAiBM,CAAc,CAAC,CAAA,CAEhEP,CAAAA,CAAe,IAAA,CACb,CAAA,EAAA;AA4BY;AC4HK;ACuoSnB,EAAA;ACrkP4B;AAkGJ;AAWhB;AAqGuC;AA4V6B;AAkqF1E;AA4hBsC;AAoYnB;AAAW;AAsFsB;AAkSnC;AChiLV;AAs0CuB,4CAAA;ACxZhC;ACz+Cc,EAAA;AA+bNS,EAAAA;AC7KD;AALH,EAAA;AAAA;AAOO,2GAAA;AAAA;AAE4B,gCAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASG,mCAAA;AAAA;AAEzB,QAAA;AAAA;AAAA;ACrbYC,MAAAA;ACobvB,UAAA;AC5cC;AAgTW;ACpNlB;ACuN2B","file":"/home/huydna/projects/sqlingo.js/dist/index.cjs","sourcesContent":[null,"{\n \"$schema\": \"https://json.schemastore.org/package.json\",\n \"name\": \"@hdnax/sqlingo.js\",\n \"sqlglot\": {\n \"commit\": \"264e95f04d95f2cd7bcf255ee7ae160db36882a7\",\n \"version\": \"28.10.0\",\n \"url\": \"https://github.com/tobymao/sqlglot\"\n },\n \"version\": \"0.0.1\",\n \"type\": \"module\",\n \"description\": \"A JavaScript port of SQLGlot - SQL parser, transpiler, optimizer, and engine\",\n \"main\": \"dist/index.cjs\",\n \"module\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"scripts\": {\n \"test\": \"vitest\",\n \"test:ui\": \"vitest --ui\",\n \"test:coverage\": \"vitest --coverage\",\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"docs\": \"typedoc\",\n \"docs:watch\": \"typedoc --watch\",\n \"docs:serve\": \"typedoc && npx http-server docs -o\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"pnpm build && changeset publish && git push --follow-tags\"\n },\n \"keywords\": [\n \"sql\",\n \"parser\",\n \"transpiler\",\n \"optimizer\",\n \"sqlingo\",\n \"sql-parser\",\n \"sql-transpiler\"\n ],\n \"author\": \"Huy DNA\",\n \"license\": \"MIT\",\n \"packageManager\": \"pnpm@10.26.1\",\n \"files\": [\n \"dist\",\n \"LICENSE\",\n \"README.md\",\n \"COPYRIGHT_NOTICE\"\n ],\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.29.8\",\n \"@hdnax/nuclint\": \"^0.9.1\",\n \"@types/luxon\": \"^3.7.1\",\n \"@types/node\": \"^25.2.1\",\n \"eslint\": \"^9.39.2\",\n \"tsup\": \"^8.5.1\",\n \"typedoc\": \"^0.28.16\",\n \"typedoc-github-theme\": \"^0.4.0\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\n },\n \"peerDependencies\": {\n \"luxon\": \"^3.7.2\"\n }\n}\n","// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/errors.py\n\nconst ANSI_UNDERLINE = '\\x1b[4m';\nconst ANSI_RESET = '\\x1b[0m';\nconst ERROR_MESSAGE_CONTEXT_DEFAULT = 100;\n\nexport const enum ErrorLevel {\n IGNORE,\n WARN,\n RAISE,\n IMMEDIATE,\n}\n\nexport interface ErrorDetail {\n description?: string;\n line?: number;\n col?: number;\n startContext?: string;\n highlight?: string;\n endContext?: string;\n intoExpression?: string;\n}\n\nexport class SqlglotError extends Error {\n constructor (message: string) {\n super(message);\n this.name = 'SqlglotError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class UnsupportedError extends SqlglotError {\n constructor (message: string) {\n super(message);\n this.name = 'UnsupportedError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class ParseError extends SqlglotError {\n errors: ErrorDetail[];\n\n constructor (message: string, errors?: ErrorDetail[]) {\n super(message);\n this.name = 'ParseError';\n this.errors = errors || [];\n Error.captureStackTrace(this, this.constructor);\n }\n\n static new (\n message: string,\n description?: string,\n line?: number,\n col?: number,\n startContext?: string,\n highlight?: string,\n endContext?: string,\n intoExpression?: string,\n ): ParseError {\n return new ParseError(message, [\n {\n description,\n line,\n col,\n startContext,\n highlight,\n endContext,\n intoExpression,\n },\n ]);\n }\n}\n\nexport class TokenError extends SqlglotError {\n constructor (message: string) {\n super(message);\n this.name = 'TokenError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class OptimizeError extends SqlglotError {\n constructor (message: string) {\n super(message);\n this.name = 'OptimizeError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class SchemaError extends SqlglotError {\n constructor (message: string) {\n super(message);\n this.name = 'SchemaError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\nexport class ExecuteError extends SqlglotError {\n constructor (message: string) {\n super(message);\n this.name = 'ExecuteError';\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/errors.py#L90-L150\nexport function highlightSql (options: {\n sql: string;\n positions: [number, number][];\n contextLength?: number;\n}): { formattedSql: string;\n startContext: string;\n highlight: string;\n endContext: string; } {\n const {\n sql, positions, contextLength = ERROR_MESSAGE_CONTEXT_DEFAULT,\n } = options;\n if (positions.length === 0) {\n throw new Error('positions must contain at least one [start, end] tuple');\n }\n\n let startContext = '';\n let endContext = '';\n let firstHighlightStart = 0;\n const formattedParts: string[] = [];\n let previousPartEnd = 0;\n const sortedPositions = [...positions].sort((a, b) => a[0] - b[0]);\n\n if (0 < sortedPositions[0][0]) {\n firstHighlightStart = sortedPositions[0][0];\n startContext = sql.slice(\n Math.max(0, firstHighlightStart - contextLength),\n firstHighlightStart,\n );\n formattedParts.push(startContext);\n previousPartEnd = firstHighlightStart;\n }\n\n for (const [start, end] of sortedPositions) {\n const highlightStart = Math.max(start, previousPartEnd);\n const highlightEnd = end + 1;\n if (highlightEnd <= highlightStart) {\n continue;\n }\n if (previousPartEnd < highlightStart) {\n formattedParts.push(sql.slice(previousPartEnd, highlightStart));\n }\n formattedParts.push(\n `${ANSI_UNDERLINE}${sql.slice(highlightStart, highlightEnd)}${ANSI_RESET}`,\n );\n previousPartEnd = highlightEnd;\n }\n\n if (previousPartEnd < sql.length) {\n endContext = sql.slice(previousPartEnd, previousPartEnd + contextLength);\n formattedParts.push(endContext);\n }\n\n const formattedSql = formattedParts.join('');\n const highlight = sql.slice(firstHighlightStart, previousPartEnd);\n\n return {\n formattedSql,\n startContext,\n highlight,\n endContext,\n };\n}\n\n// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/errors.py#L153-L158\nexport function concatMessages (errors: unknown[], maximum: number): string {\n const msg = errors.slice(0, maximum).map((e) => String(e));\n const remaining = errors.length - maximum;\n if (0 < remaining) {\n msg.push(`... and ${remaining} more`);\n }\n return msg.join('\\n\\n');\n}\n\n// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/errors.py#L161-L162\nexport function mergeErrors (errors: ParseError[]): ErrorDetail[] {\n return errors.flatMap((error) => error.errors);\n}\n","/**\n * Simulates Python's arithmetic dunder methods (__add__, __sub__, __mul__, etc.)\n * and bitwise/set dunder methods (__or__, __and__, __xor__, __lshift__, __rshift__).\n * Each interface represents a single operation and can be independently implemented\n * by a class. Built-in types (number, string, Array, Set) are handled transparently\n * by the helper functions which fall back to native operators when the operand does\n * not implement the interface. Dispatch order matches Python:\n * 1. a.op(b) (__op__)\n * 2. b.rop(a) (__rop__, reflected)\n * 3. native fallback\n */\n\nexport interface AddableObject<TOther = unknown, TReturn = unknown> {\n add(other: TOther): TReturn;\n}\nexport type Addable = AddableObject | Array<unknown> | number | string;\nexport function isAddable (a: unknown): a is Addable {\n return typeof a === 'number' || typeof a === 'string' || Array.isArray(a) || isAddableObj(a);\n}\nexport function isAddableObj (a: unknown): a is AddableObject {\n return a !== null && typeof a === 'object' && typeof (a as AddableObject).add === 'function';\n}\n\nexport interface RaddableObject<TOther = unknown, TReturn = unknown> {\n radd(other: TOther): TReturn;\n}\nexport type Raddable = RaddableObject | Array<unknown> | number | string;\nexport function isRaddable (a: unknown): a is Raddable {\n return typeof a === 'number' || typeof a === 'string' || Array.isArray(a) || isRaddableObj(a);\n}\nexport function isRaddableObj (a: unknown): a is RaddableObject {\n return a !== null && typeof a === 'object' && typeof (a as RaddableObject).radd === 'function';\n}\n\nexport function add<A extends AddableObject<B, R>, B, R> (a: A, b: B): R;\nexport function add<A, B extends RaddableObject<A, R>, R> (a: A, b: B): R;\nexport function add (a: unknown[], b: unknown[]): unknown[];\nexport function add (a: unknown, b: unknown): unknown;\nexport function add (a: unknown, b: unknown): unknown {\n if (isAddableObj(a)) return (a as AddableObject).add(b);\n if (isRaddableObj(b)) return (b as RaddableObject).radd(a);\n if (Array.isArray(a) && Array.isArray(b)) return [...a, ...(b as unknown[])];\n // @ts-expect-error \"Fallback to primitive operation\"\n return a + b;\n}\n\nexport interface SubtractableObject<TOther = unknown, TReturn = unknown> {\n sub(other: TOther): TReturn;\n}\nexport type Subtractable = SubtractableObject | Set<unknown> | number;\nexport function isSubtractable (a: unknown): a is Subtractable {\n return typeof a === 'number' || a instanceof Set || isSubtractableObj(a);\n}\nexport function isSubtractableObj (a: unknown): a is SubtractableObject {\n return a !== null && typeof a === 'object' && typeof (a as SubtractableObject).sub === 'function';\n}\n\nexport interface RsubtractableObject<TOther = unknown, TReturn = unknown> {\n rsub(other: TOther): TReturn;\n}\nexport type Rsubtractable = RsubtractableObject | number;\nexport function isRsubtractable (a: unknown): a is Rsubtractable {\n return typeof a === 'number' || isRsubtractableObj(a);\n}\nexport function isRsubtractableObj (a: unknown): a is RsubtractableObject {\n return a !== null && typeof a === 'object' && typeof (a as RsubtractableObject).rsub === 'function';\n}\n\nexport function sub<A extends SubtractableObject<B, R>, B, R> (a: A, b: B): R;\nexport function sub<A, B extends RsubtractableObject<A, R>, R> (a: A, b: B): R;\nexport function sub (a: Set<unknown>, b: Set<unknown>): Set<unknown>;\nexport function sub (a: unknown, b: unknown): unknown;\nexport function sub (a: unknown, b: unknown): unknown {\n if (isSubtractableObj(a)) return (a as SubtractableObject).sub(b);\n if (isRsubtractableObj(b)) return (b as RsubtractableObject).rsub(a);\n if (a instanceof Set && b instanceof Set) return new Set([...(a as Set<unknown>)].filter((x) => !(b as Set<unknown>).has(x)));\n // @ts-expect-error \"Fallback to primitive operation\"\n return a - b;\n}\n\nexport interface MultipliableObject<TOther = unknown, TReturn = unknown> {\n mul(other: TOther): TReturn;\n}\nexport type Multipliable = MultipliableObject | number;\nexport function isMultipliable (a: unknown): a is Multipliable {\n return typeof a === 'number' || isMultipliableObj(a);\n}\nexport function isMultipliableObj (a: unknown): a is MultipliableObject {\n return a !== null && typeof a === 'object' && typeof (a as MultipliableObject).mul === 'function';\n}\n\nexport interface RmultipliableObject<TOther = unknown, TReturn = unknown> {\n rmul(other: TOther): TReturn;\n}\nexport type Rmultipliable = RmultipliableObject | number;\nexport function isRmultipliable (a: unknown): a is Rmultipliable {\n return typeof a === 'number' || isRmultipliableObj(a);\n}\nexport function isRmultipliableObj (a: unknown): a is RmultipliableObject {\n return a !== null && typeof a === 'object' && typeof (a as RmultipliableObject).rmul === 'function';\n}\n\nexport function mul<A extends MultipliableObject<B, R>, B, R> (a: A, b: B): R;\nexport function mul<A, B extends RmultipliableObject<A, R>, R> (a: A, b: B): R;\nexport function mul (a: unknown, b: unknown): unknown;\nexport function mul (a: unknown, b: unknown): unknown {\n if (isMultipliableObj(a)) return (a as MultipliableObject).mul(b);\n if (isRmultipliableObj(b)) return (b as RmultipliableObject).rmul(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a * b;\n}\n\nexport interface TrueDivisibleObject<TOther = unknown, TReturn = unknown> {\n truediv(other: TOther): TReturn;\n}\nexport type TrueDivisible = TrueDivisibleObject | number;\nexport function isTrueDivisible (a: unknown): a is TrueDivisible {\n return typeof a === 'number' || isTrueDivisibleObj(a);\n}\nexport function isTrueDivisibleObj (a: unknown): a is TrueDivisibleObject {\n return a !== null && typeof a === 'object' && typeof (a as TrueDivisibleObject).truediv === 'function';\n}\n\nexport interface RtrueDivisibleObject<TOther = unknown, TReturn = unknown> {\n rtruediv(other: TOther): TReturn;\n}\nexport type RtrueDivisible = RtrueDivisibleObject | number;\nexport function isRtrueDivisible (a: unknown): a is RtrueDivisible {\n return typeof a === 'number' || isRtrueDivisibleObj(a);\n}\nexport function isRtrueDivisibleObj (a: unknown): a is RtrueDivisibleObject {\n return a !== null && typeof a === 'object' && typeof (a as RtrueDivisibleObject).rtruediv === 'function';\n}\n\nexport function truediv<A extends TrueDivisibleObject<B, R>, B, R> (a: A, b: B): R;\nexport function truediv<A, B extends RtrueDivisibleObject<A, R>, R> (a: A, b: B): R;\nexport function truediv (a: unknown, b: unknown): unknown;\nexport function truediv (a: unknown, b: unknown): unknown {\n if (isTrueDivisibleObj(a)) return (a as TrueDivisibleObject).truediv(b);\n if (isRtrueDivisibleObj(b)) return (b as RtrueDivisibleObject).rtruediv(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a / b;\n}\n\nexport interface FloorDivisibleObject<TOther = unknown, TReturn = unknown> {\n floorDiv(other: TOther): TReturn;\n}\nexport type FloorDivisible = FloorDivisibleObject | number;\nexport function isFloorDivisible (a: unknown): a is FloorDivisible {\n return typeof a === 'number' || isFloorDivisibleObj(a);\n}\nexport function isFloorDivisibleObj (a: unknown): a is FloorDivisibleObject {\n return a !== null && typeof a === 'object' && typeof (a as FloorDivisibleObject).floorDiv === 'function';\n}\n\nexport interface RfloorDivisibleObject<TOther = unknown, TReturn = unknown> {\n rfloorDiv(other: TOther): TReturn;\n}\nexport type RfloorDivisible = RfloorDivisibleObject | number;\nexport function isRfloorDivisible (a: unknown): a is RfloorDivisible {\n return typeof a === 'number' || isRfloorDivisibleObj(a);\n}\nexport function isRfloorDivisibleObj (a: unknown): a is RfloorDivisibleObject {\n return a !== null && typeof a === 'object' && typeof (a as RfloorDivisibleObject).rfloorDiv === 'function';\n}\n\nexport function floorDiv<A extends FloorDivisibleObject<B, R>, B, R> (a: A, b: B): R;\nexport function floorDiv<A, B extends RfloorDivisibleObject<A, R>, R> (a: A, b: B): R;\nexport function floorDiv (a: unknown, b: unknown): unknown;\nexport function floorDiv (a: unknown, b: unknown): unknown {\n if (isFloorDivisibleObj(a)) return (a as FloorDivisibleObject).floorDiv(b);\n if (isRfloorDivisibleObj(b)) return (b as RfloorDivisibleObject).rfloorDiv(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return Math.trunc(a / b);\n}\n\nexport interface ModableObject<TOther = unknown, TReturn = unknown> {\n mod(other: TOther): TReturn;\n}\nexport type Modable = ModableObject | number;\nexport function isModable (a: unknown): a is Modable {\n return typeof a === 'number' || isModableObj(a);\n}\nexport function isModableObj (a: unknown): a is ModableObject {\n return a !== null && typeof a === 'object' && typeof (a as ModableObject).mod === 'function';\n}\n\nexport interface RmodableObject<TOther = unknown, TReturn = unknown> {\n rmod(other: TOther): TReturn;\n}\nexport type Rmodable = RmodableObject | number;\nexport function isRmodable (a: unknown): a is Rmodable {\n return typeof a === 'number' || isRmodableObj(a);\n}\nexport function isRmodableObj (a: unknown): a is RmodableObject {\n return a !== null && typeof a === 'object' && typeof (a as RmodableObject).rmod === 'function';\n}\n\nexport function mod<A extends ModableObject<B, R>, B, R> (a: A, b: B): R;\nexport function mod<A, B extends RmodableObject<A, R>, R> (a: A, b: B): R;\nexport function mod (a: unknown, b: unknown): unknown;\nexport function mod (a: unknown, b: unknown): unknown {\n if (isModableObj(a)) return (a as ModableObject).mod(b);\n if (isRmodableObj(b)) return (b as RmodableObject).rmod(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a % b;\n}\n\nexport interface PowableObject<TOther = unknown, TReturn = unknown> {\n pow(other: TOther): TReturn;\n}\nexport type Powable = PowableObject | number;\nexport function isPowable (a: unknown): a is Powable {\n return typeof a === 'number' || isPowableObj(a);\n}\nexport function isPowableObj (a: unknown): a is PowableObject {\n return a !== null && typeof a === 'object' && typeof (a as PowableObject).pow === 'function';\n}\n\nexport interface RpowableObject<TOther = unknown, TReturn = unknown> {\n rpow(other: TOther): TReturn;\n}\nexport type Rpowable = RpowableObject | number;\nexport function isRpowable (a: unknown): a is Rpowable {\n return typeof a === 'number' || isRpowableObj(a);\n}\nexport function isRpowableObj (a: unknown): a is RpowableObject {\n return a !== null && typeof a === 'object' && typeof (a as RpowableObject).rpow === 'function';\n}\n\nexport function pow<A extends PowableObject<B, R>, B, R> (a: A, b: B): R;\nexport function pow<A, B extends RpowableObject<A, R>, R> (a: A, b: B): R;\nexport function pow (a: unknown, b: unknown): unknown;\nexport function pow (a: unknown, b: unknown): unknown {\n if (isPowableObj(a)) return (a as PowableObject).pow(b);\n if (isRpowableObj(b)) return (b as RpowableObject).rpow(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a ** b;\n}\n\nexport interface NegatableObject<TReturn = unknown> {\n neg(): TReturn;\n}\nexport type Negatable = NegatableObject | number;\nexport function isNegatable (a: unknown): a is Negatable {\n return typeof a === 'number' || isNegatableObj(a);\n}\nexport function isNegatableObj (a: unknown): a is NegatableObject {\n return a !== null && typeof a === 'object' && typeof (a as NegatableObject).neg === 'function';\n}\n\nexport function neg<A extends NegatableObject<R>, R> (a: A): R;\nexport function neg (a: unknown): unknown;\nexport function neg (a: unknown): unknown {\n if (isNegatableObj(a)) return (a as NegatableObject).neg();\n // @ts-expect-error \"Fallback to primitive operation\"\n return -a;\n}\n\nexport interface LtComparableObject<TOther = unknown> {\n lt(other: TOther): boolean;\n}\nexport type LtComparable = LtComparableObject | number | string;\nexport function isLtComparableObj (a: unknown): a is LtComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as LtComparableObject).lt === 'function';\n}\n\nexport function lt<A extends LtComparableObject<B>, B> (a: A, b: B): boolean;\nexport function lt<A, B extends GtComparableObject<A>> (a: A, b: B): boolean;\nexport function lt (a: unknown, b: unknown): boolean;\nexport function lt (a: unknown, b: unknown): boolean {\n if (isLtComparableObj(a)) return (a as LtComparableObject).lt(b);\n if (isGtComparableObj(b)) return (b as GtComparableObject).gt(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a < b;\n}\n\nexport interface LteComparableObject<TOther = unknown> {\n lte(other: TOther): boolean;\n}\nexport type LteComparable = LteComparableObject | number | string;\nexport function isLteComparableObj (a: unknown): a is LteComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as LteComparableObject).lte === 'function';\n}\n\nexport function lte<A extends LteComparableObject<B>, B> (a: A, b: B): boolean;\nexport function lte<A, B extends GteComparableObject<A>> (a: A, b: B): boolean;\nexport function lte (a: unknown, b: unknown): boolean;\nexport function lte (a: unknown, b: unknown): boolean {\n if (isLteComparableObj(a)) return (a as LteComparableObject).lte(b);\n if (isGteComparableObj(b)) return (b as GteComparableObject).gte(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a <= b;\n}\n\nexport interface GtComparableObject<TOther = unknown> {\n gt(other: TOther): boolean;\n}\nexport type GtComparable = GtComparableObject | number | string;\nexport function isGtComparableObj (a: unknown): a is GtComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as GtComparableObject).gt === 'function';\n}\n\nexport function gt<A extends GtComparableObject<B>, B> (a: A, b: B): boolean;\nexport function gt<A, B extends LtComparableObject<A>> (a: A, b: B): boolean;\nexport function gt (a: unknown, b: unknown): boolean;\nexport function gt (a: unknown, b: unknown): boolean {\n if (isGtComparableObj(a)) return (a as GtComparableObject).gt(b);\n if (isLtComparableObj(b)) return (b as LtComparableObject).lt(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return b < a;\n}\n\nexport interface GteComparableObject<TOther = unknown> {\n gte(other: TOther): boolean;\n}\nexport type GteComparable = GteComparableObject | number | string;\nexport function isGteComparableObj (a: unknown): a is GteComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as GteComparableObject).gte === 'function';\n}\n\nexport function gte<A extends GteComparableObject<B>, B> (a: A, b: B): boolean;\nexport function gte<A, B extends LteComparableObject<A>> (a: A, b: B): boolean;\nexport function gte (a: unknown, b: unknown): boolean;\nexport function gte (a: unknown, b: unknown): boolean {\n if (isGteComparableObj(a)) return (a as GteComparableObject).gte(b);\n if (isLteComparableObj(b)) return (b as LteComparableObject).lte(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return b <= a;\n}\n\nexport interface EqComparableObject<TOther = unknown> {\n eq(other: TOther): boolean;\n}\nexport type EqComparable = EqComparableObject | number | string | boolean;\nexport function isEqComparableObj (a: unknown): a is EqComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as EqComparableObject).eq === 'function';\n}\n\nexport function eq<A extends EqComparableObject<B>, B> (a: A, b: B): boolean;\nexport function eq<A, B extends EqComparableObject<A>> (a: A, b: B): boolean;\nexport function eq (a: unknown, b: unknown): boolean;\nexport function eq (a: unknown, b: unknown): boolean {\n if (isEqComparableObj(a)) return (a as EqComparableObject).eq(b);\n if (isEqComparableObj(b)) return (b as EqComparableObject).eq(a);\n return a === b;\n}\n\nexport interface NeqComparableObject<TOther = unknown> {\n neq(other: TOther): boolean;\n}\nexport type NeqComparable = NeqComparableObject | number | string | boolean;\nexport function isNeqComparableObj (a: unknown): a is NeqComparableObject {\n return a !== null && typeof a === 'object' && typeof (a as NeqComparableObject).neq === 'function';\n}\n\nexport function neq<A extends NeqComparableObject<B>, B> (a: A, b: B): boolean;\nexport function neq<A, B extends NeqComparableObject<A>> (a: A, b: B): boolean;\nexport function neq (a: unknown, b: unknown): boolean;\nexport function neq (a: unknown, b: unknown): boolean {\n if (isNeqComparableObj(a)) return (a as NeqComparableObject).neq(b);\n if (isNeqComparableObj(b)) return (b as NeqComparableObject).neq(a);\n return !eq(a, b);\n}\n\nexport interface InvertableObject<TReturn = unknown> {\n invert(): TReturn;\n}\nexport type Invertable = InvertableObject | number;\nexport function isInvertable (a: unknown): a is Invertable {\n return typeof a === 'number' || isInvertableObj(a);\n}\nexport function isInvertableObj (a: unknown): a is InvertableObject {\n return a !== null && typeof a === 'object' && typeof (a as InvertableObject).invert === 'function';\n}\n\nexport function invert<A extends InvertableObject<R>, R> (a: A): R;\nexport function invert (a: unknown): unknown;\nexport function invert (a: unknown): unknown {\n if (isInvertableObj(a)) return (a as InvertableObject).invert();\n // @ts-expect-error \"Fallback to primitive operation\"\n return ~a;\n}\n\nexport interface OrableObject<TOther = unknown, TReturn = unknown> {\n or(other: TOther): TReturn;\n}\nexport type Orable = OrableObject | Set<unknown> | number;\nexport function isOrable (a: unknown): a is Orable {\n return typeof a === 'number' || a instanceof Set || isOrableObj(a);\n}\nexport function isOrableObj (a: unknown): a is OrableObject {\n return a !== null && typeof a === 'object' && typeof (a as OrableObject).or === 'function';\n}\n\nexport interface RorableObject<TOther = unknown, TReturn = unknown> {\n ror(other: TOther): TReturn;\n}\nexport type Rorable = RorableObject | Set<unknown> | number;\nexport function isRorable (a: unknown): a is Rorable {\n return typeof a === 'number' || a instanceof Set || isRorableObj(a);\n}\nexport function isRorableObj (a: unknown): a is RorableObject {\n return a !== null && typeof a === 'object' && typeof (a as RorableObject).ror === 'function';\n}\n\nexport function or<A extends OrableObject<B, R>, B, R> (a: A, b: B): R;\nexport function or<A, B extends RorableObject<A, R>, R> (a: A, b: B): R;\nexport function or (a: Set<unknown>, b: Set<unknown>): Set<unknown>;\nexport function or (a: unknown, b: unknown): unknown;\nexport function or (a: unknown, b: unknown): unknown {\n if (isOrableObj(a)) return (a as OrableObject).or(b);\n if (isRorableObj(b)) return (b as RorableObject).ror(a);\n if (a instanceof Set && b instanceof Set) return new Set([...(a as Set<unknown>), ...(b as Set<unknown>)]);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a | b;\n}\n\nexport interface AndableObject<TOther = unknown, TReturn = unknown> {\n and(other: TOther): TReturn;\n}\nexport type Andable = AndableObject | Set<unknown> | number;\nexport function isAndable (a: unknown): a is Andable {\n return typeof a === 'number' || a instanceof Set || isAndableObj(a);\n}\nexport function isAndableObj (a: unknown): a is AndableObject {\n return a !== null && typeof a === 'object' && typeof (a as AndableObject).and === 'function';\n}\n\nexport interface RandableObject<TOther = unknown, TReturn = unknown> {\n rand(other: TOther): TReturn;\n}\nexport type Randable = RandableObject | Set<unknown> | number;\nexport function isRandable (a: unknown): a is Randable {\n return typeof a === 'number' || a instanceof Set || isRandableObj(a);\n}\nexport function isRandableObj (a: unknown): a is RandableObject {\n return a !== null && typeof a === 'object' && typeof (a as RandableObject).rand === 'function';\n}\n\nexport function and<A extends AndableObject<B, R>, B, R> (a: A, b: B): R;\nexport function and<A, B extends RandableObject<A, R>, R> (a: A, b: B): R;\nexport function and (a: Set<unknown>, b: Set<unknown>): Set<unknown>;\nexport function and (a: unknown, b: unknown): unknown;\nexport function and (a: unknown, b: unknown): unknown {\n if (isAndableObj(a)) return (a as AndableObject).and(b);\n if (isRandableObj(b)) return (b as RandableObject).rand(a);\n if (a instanceof Set && b instanceof Set) return new Set([...(a as Set<unknown>)].filter((x) => (b as Set<unknown>).has(x)));\n // @ts-expect-error \"Fallback to primitive operation\"\n return a & b;\n}\n\nexport interface XorableObject<TOther = unknown, TReturn = unknown> {\n xor(other: TOther): TReturn;\n}\nexport type Xorable = XorableObject | Set<unknown> | number;\nexport function isXorable (a: unknown): a is Xorable {\n return typeof a === 'number' || a instanceof Set || isXorableObj(a);\n}\nexport function isXorableObj (a: unknown): a is XorableObject {\n return a !== null && typeof a === 'object' && typeof (a as XorableObject).xor === 'function';\n}\n\nexport interface RxorableObject<TOther = unknown, TReturn = unknown> {\n rxor(other: TOther): TReturn;\n}\nexport type Rxorable = RxorableObject | Set<unknown> | number;\nexport function isRxorable (a: unknown): a is Rxorable {\n return typeof a === 'number' || a instanceof Set || isRxorableObj(a);\n}\nexport function isRxorableObj (a: unknown): a is RxorableObject {\n return a !== null && typeof a === 'object' && typeof (a as RxorableObject).rxor === 'function';\n}\n\nexport function xor<A extends XorableObject<B, R>, B, R> (a: A, b: B): R;\nexport function xor<A, B extends RxorableObject<A, R>, R> (a: A, b: B): R;\nexport function xor (a: Set<unknown>, b: Set<unknown>): Set<unknown>;\nexport function xor (a: unknown, b: unknown): unknown;\nexport function xor (a: unknown, b: unknown): unknown {\n if (isXorableObj(a)) return (a as XorableObject).xor(b);\n if (isRxorableObj(b)) return (b as RxorableObject).rxor(a);\n if (a instanceof Set && b instanceof Set) {\n const sa = a as Set<unknown>, sb = b as Set<unknown>;\n return new Set([...[...sa].filter((x) => !sb.has(x)), ...[...sb].filter((x) => !sa.has(x))]);\n }\n // @ts-expect-error \"Fallback to primitive operation\"\n return a ^ b;\n}\n\nexport interface LshiftableObject<TOther = unknown, TReturn = unknown> {\n lshift(other: TOther): TReturn;\n}\nexport type Lshiftable = LshiftableObject | number;\nexport function isLshiftable (a: unknown): a is Lshiftable {\n return typeof a === 'number' || isLshiftableObj(a);\n}\nexport function isLshiftableObj (a: unknown): a is LshiftableObject {\n return a !== null && typeof a === 'object' && typeof (a as LshiftableObject).lshift === 'function';\n}\n\nexport interface RlshiftableObject<TOther = unknown, TReturn = unknown> {\n rlshift(other: TOther): TReturn;\n}\nexport type Rlshiftable = RlshiftableObject | number;\nexport function isRlshiftable (a: unknown): a is Rlshiftable {\n return typeof a === 'number' || isRlshiftableObj(a);\n}\nexport function isRlshiftableObj (a: unknown): a is RlshiftableObject {\n return a !== null && typeof a === 'object' && typeof (a as RlshiftableObject).rlshift === 'function';\n}\n\nexport function lshift<A extends LshiftableObject<B, R>, B, R> (a: A, b: B): R;\nexport function lshift<A, B extends RlshiftableObject<A, R>, R> (a: A, b: B): R;\nexport function lshift (a: unknown, b: unknown): unknown;\nexport function lshift (a: unknown, b: unknown): unknown {\n if (isLshiftableObj(a)) return (a as LshiftableObject).lshift(b);\n if (isRlshiftableObj(b)) return (b as RlshiftableObject).rlshift(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a << b;\n}\n\nexport interface RshiftableObject<TOther = unknown, TReturn = unknown> {\n rshift(other: TOther): TReturn;\n}\nexport type Rshiftable = RshiftableObject | number;\nexport function isRshiftable (a: unknown): a is Rshiftable {\n return typeof a === 'number' || isRshiftableObj(a);\n}\nexport function isRshiftableObj (a: unknown): a is RshiftableObject {\n return a !== null && typeof a === 'object' && typeof (a as RshiftableObject).rshift === 'function';\n}\n\nexport interface RrshiftableObject<TOther = unknown, TReturn = unknown> {\n rrshift(other: TOther): TReturn;\n}\nexport type Rrshiftable = RrshiftableObject | number;\nexport function isRrshiftable (a: unknown): a is Rrshiftable {\n return typeof a === 'number' || isRrshiftableObj(a);\n}\nexport function isRrshiftableObj (a: unknown): a is RrshiftableObject {\n return a !== null && typeof a === 'object' && typeof (a as RrshiftableObject).rrshift === 'function';\n}\n\nexport function rshift<A extends RshiftableObject<B, R>, B, R> (a: A, b: B): R;\nexport function rshift<A, B extends RrshiftableObject<A, R>, R> (a: A, b: B): R;\nexport function rshift (a: unknown, b: unknown): unknown;\nexport function rshift (a: unknown, b: unknown): unknown {\n if (isRshiftableObj(a)) return (a as RshiftableObject).rshift(b);\n if (isRrshiftableObj(b)) return (b as RrshiftableObject).rrshift(a);\n // @ts-expect-error \"Fallback to primitive operation\"\n return a >> b;\n}\n\nexport interface IndexableObject<TOther = unknown, TReturn = unknown> {\n getItem(other: TOther): TReturn;\n}\nexport type Indexable = IndexableObject | Array<unknown>;\nexport function isIndexable (a: unknown): a is Indexable {\n return Array.isArray(a) || isIndexableObj(a);\n}\nexport function isIndexableObj (a: unknown): a is IndexableObject {\n return a !== null && typeof a === 'object' && typeof (a as IndexableObject).getItem === 'function';\n}\n\nexport function getitem<A extends IndexableObject<B, R>, B, R> (a: A, b: B): R;\nexport function getitem (a: unknown[], b: number): unknown;\nexport function getitem (a: unknown, b: unknown): unknown;\nexport function getitem (a: unknown, b: unknown): unknown {\n if (isIndexableObj(a)) return (a as IndexableObject).getItem(b);\n if (Array.isArray(a)) return (a as unknown[])[b as number];\n throw new TypeError(`'${typeof a}' object is not subscriptable`);\n}\n","// https://github.com/tobymao/sqlglot/blob/main/sqlglot/parser.py\n\nimport {\n cache,\n assertIsInstanceOf, filterInstanceOf, isInstanceOf, enumFromString,\n} from '../port_internals';\nimport {\n AddConstraintExpr,\n AddExpr,\n AddPartitionExpr,\n AdjacentExpr,\n AggFuncExpr,\n AlgorithmPropertyExpr,\n AliasExpr,\n AliasesExpr,\n AllExpr,\n AllowedValuesPropertyExpr,\n AlterColumnExpr,\n AlterDistStyleExpr,\n AlterExpr,\n AlterRenameExpr,\n AlterSessionExpr,\n AlterSetExpr,\n AlterSortKeyExpr,\n AnalyzeColumnsExpr,\n AnalyzeDeleteExpr,\n AnalyzeExpr,\n AnalyzeHistogramExpr,\n AnalyzeListChainedRowsExpr,\n AnalyzeSampleExpr,\n AnalyzeStatisticsExpr,\n AnalyzeValidateExpr,\n AnalyzeWithExpr,\n AndExpr,\n AnonymousExpr,\n AnyExpr,\n ArgMaxExpr,\n ArgMinExpr,\n ArrayAggExpr,\n ArrayAppendExpr,\n ArrayConcatExpr,\n ArrayContainsAllExpr,\n ArrayExpr,\n ArrayPrependExpr,\n ArrayRemoveExpr,\n AtIndexExpr,\n AtTimeZoneExpr,\n AutoIncrementColumnConstraintExpr,\n AutoIncrementPropertyExpr,\n AutoRefreshPropertyExpr,\n BackupPropertyExpr,\n BetweenExpr,\n BinaryExpr,\n BitStringExpr,\n BitwiseAndExpr,\n BitwiseLeftShiftExpr,\n BitwiseNotExpr,\n BitwiseOrExpr,\n BitwiseRightShiftExpr,\n BitwiseXorExpr,\n BlockCompressionPropertyExpr,\n BooleanExpr,\n BracketExpr,\n ByteStringExpr,\n CteExpr,\n CacheExpr,\n CaseExpr,\n CaseSpecificColumnConstraintExpr,\n CastExpr,\n CastToStrTypeExpr,\n CbrtExpr,\n CeilExpr,\n ChangesExpr,\n CharacterSetColumnConstraintExpr,\n CharacterSetExpr,\n CharacterSetPropertyExpr,\n CheckColumnConstraintExpr,\n ChecksumPropertyExpr,\n ChrExpr,\n CloneExpr,\n ClusterExpr,\n ClusteredByPropertyExpr,\n ClusteredColumnConstraintExpr,\n CoalesceExpr,\n CollateColumnConstraintExpr,\n CollateExpr,\n CollatePropertyExpr,\n ColumnConstraintExpr,\n ColumnDefExpr,\n ColumnExpr,\n ColumnPositionExpr,\n ColumnsExpr,\n CommandExpr,\n CommentColumnConstraintExpr,\n CommentExpr,\n CommitExpr,\n ComprehensionExpr,\n CompressColumnConstraintExpr,\n ComputedColumnConstraintExpr,\n ConcatExpr,\n ConcatWsExpr,\n ConditionalInsertExpr,\n ConnectByRootExpr,\n ConnectExpr,\n ConstraintExpr,\n ConvertTimezoneExpr,\n CopyExpr,\n CopyGrantsPropertyExpr,\n CopyParameterExpr,\n CountExpr,\n CreateExpr,\n CredentialsExpr,\n CubeExpr,\n CurrentDateExpr,\n CurrentRoleExpr,\n CurrentTimeExpr,\n CurrentTimestampExpr,\n CurrentUserExpr,\n DPipeExpr,\n DataBlocksizePropertyExpr,\n DataDeletionPropertyExpr,\n DataTypeExpr,\n DataTypeExprKind,\n DataTypeParamExpr,\n DateAddExpr,\n DateFormatColumnConstraintExpr,\n DateSubExpr,\n DeclareExpr,\n DeclareItemExpr,\n DecodeCaseExpr,\n DecodeExpr,\n DefaultColumnConstraintExpr,\n DefinerPropertyExpr,\n DeleteExpr,\n DescribeExpr,\n DictPropertyExpr,\n DictRangeExpr,\n DictSubPropertyExpr,\n DirectoryExpr,\n DistKeyPropertyExpr,\n DistStylePropertyExpr,\n DistanceExpr,\n DistinctExpr,\n DistributeExpr,\n DistributedByPropertyExpr,\n DivExpr,\n DotExpr,\n DropExpr,\n DropPartitionExpr,\n DuplicateKeyPropertyExpr,\n DynamicPropertyExpr,\n EqExpr,\n EmptyPropertyExpr,\n EncodeColumnConstraintExpr,\n EnginePropertyExpr,\n EnviromentPropertyExpr,\n EphemeralColumnConstraintExpr,\n EscapeExpr,\n ExceptExpr,\n ExcludeColumnConstraintExpr,\n ExecuteAsPropertyExpr,\n ExistsExpr,\n Expression, GrantPrivilegeExpr, OverlayExpr, RevokeExpr,\n ExpressionKey,\n ExtendsLeftExpr,\n ExtendsRightExpr,\n ExternalPropertyExpr,\n ExtractExpr,\n FallbackPropertyExpr,\n FetchExpr,\n FileFormatPropertyExpr,\n FilterExpr,\n FloorExpr,\n ForeignKeyExpr,\n FormatJsonExpr,\n FreespacePropertyExpr,\n FromExpr,\n FuncExpr,\n GteExpr,\n GtExpr,\n GapFillExpr,\n GenerateDateArrayExpr,\n GeneratedAsIdentityColumnConstraintExpr,\n GeneratedAsRowColumnConstraintExpr,\n GlobExpr,\n GlobalPropertyExpr,\n GrantExpr,\n GrantPrincipalExpr,\n GreatestExpr,\n GroupExpr,\n GroupConcatExpr,\n GroupingSetsExpr,\n HavingExpr,\n HavingMaxExpr,\n HeapPropertyExpr,\n HeredocExpr,\n HexExpr,\n HexStringExpr,\n HintExpr,\n HistoricalDataExpr,\n ILikeExpr,\n IcebergPropertyExpr,\n IdentifierExpr,\n IfExpr,\n IgnoreNullsExpr,\n InExpr,\n InOutColumnConstraintExpr,\n IndexExpr,\n IndexParametersExpr,\n IndexTableHintExpr,\n InheritsPropertyExpr,\n InitcapExpr,\n InlineLengthColumnConstraintExpr,\n InputModelPropertyExpr,\n InputOutputFormatExpr,\n InsertExpr,\n IntDivExpr,\n IntersectExpr,\n IntervalExpr,\n IntervalSpanExpr,\n IntoExpr,\n IntroducerExpr,\n IsExpr,\n IsolatedLoadingPropertyExpr,\n JsonbContainsAllTopKeysExpr,\n JsonbContainsAnyTopKeysExpr,\n JsonbContainsExpr,\n JsonbDeleteAtPathExpr,\n JsonbExtractExpr,\n JsonbExtractScalarExpr,\n JsonCastExpr,\n JsonColumnDefExpr,\n JsonExpr,\n JsonExtractExpr,\n JsonExtractScalarExpr,\n JsonKeyValueExpr,\n JsonKeysExpr,\n JsonObjectAggExpr,\n JsonObjectExpr,\n JsonSchemaExpr,\n JsonTableExpr,\n JsonValueExpr,\n JoinExpr,\n JoinHintExpr,\n JournalPropertyExpr,\n KillExpr,\n KwargExpr,\n LteExpr,\n LtExpr,\n LambdaExpr,\n LanguagePropertyExpr,\n LateralExpr,\n LeastExpr,\n LikeExpr,\n LikePropertyExpr,\n LimitExpr,\n LimitOptionsExpr,\n ListExpr,\n LiteralExpr,\n LnExpr,\n LoadDataExpr,\n LocaltimeExpr,\n LocaltimestampExpr,\n LocationPropertyExpr,\n LockExpr,\n LockingPropertyExpr,\n LogExpr,\n LogPropertyExpr,\n LowerExpr,\n LowerHexExpr,\n MatchAgainstExpr,\n MatchRecognizeExpr,\n MatchRecognizeMeasureExpr,\n MaterializedPropertyExpr,\n MergeBlockRatioPropertyExpr,\n MergeExpr,\n MergeTreeTtlActionExpr,\n MergeTreeTtlExpr,\n ModExpr,\n MulExpr,\n MultitableInsertsExpr,\n NeqExpr,\n NationalExpr,\n NegExpr,\n NextValueForExpr,\n NoPrimaryIndexPropertyExpr,\n NonClusteredColumnConstraintExpr,\n NormalizeExpr,\n NotExpr,\n NotForReplicationColumnConstraintExpr,\n NotNullColumnConstraintExpr,\n NullExpr,\n NullSafeEqExpr,\n NullSafeNeqExpr,\n ObjectIdentifierExpr,\n OffsetExpr,\n OnCommitPropertyExpr,\n OnConditionExpr,\n OnConflictExpr,\n OnPropertyExpr,\n OnUpdateColumnConstraintExpr,\n OpclassExpr,\n OpenJsonColumnDefExpr,\n OpenJsonExpr,\n OperatorExpr,\n OrExpr,\n OrderExpr,\n OrderedExpr,\n OutputModelPropertyExpr,\n OverlapsExpr,\n OverflowTruncateBehaviorExpr,\n PadExpr,\n ParameterExpr,\n ParenExpr,\n ParseJsonExpr,\n PartitionBoundSpecExpr,\n PartitionByTruncateExpr,\n PartitionExpr,\n PartitionedByBucketExpr,\n PartitionedByPropertyExpr,\n PartitionedOfPropertyExpr,\n PathColumnConstraintExpr,\n PeriodForSystemTimeConstraintExpr,\n PivotAliasExpr,\n PivotAnyExpr,\n PivotExpr,\n PlaceholderExpr,\n PragmaExpr,\n PreWhereExpr,\n PrimaryKeyColumnConstraintExpr,\n PrimaryKeyExpr,\n PriorExpr,\n PropertiesExpr,\n PropertyEqExpr,\n PropertyExpr,\n PseudoTypeExpr,\n QualifyExpr,\n QueryExpr,\n RawStringExpr,\n RecursiveWithSearchExpr,\n ReferenceExpr,\n RefreshExpr,\n RegexpILikeExpr,\n RegexpLikeExpr,\n RemoteWithConnectionModelPropertyExpr,\n RenameColumnExpr,\n RespectNullsExpr,\n ReturnExpr,\n ReturningExpr,\n ReturnsPropertyExpr,\n RollbackExpr,\n RollupExpr,\n RowFormatDelimitedPropertyExpr,\n RowFormatPropertyExpr,\n RowFormatSerdePropertyExpr,\n SQLGLOT_ANONYMOUS,\n SamplePropertyExpr,\n SchemaCommentPropertyExpr,\n SchemaExpr,\n ScopeResolutionExpr,\n SecurePropertyExpr,\n SecurityPropertyExpr,\n SelectExpr,\n SemicolonExpr,\n SequencePropertiesExpr,\n SerdePropertiesExpr,\n SessionParameterExpr,\n SetExpr,\n SetItemExpr,\n SetOperationExpr,\n SetPropertyExpr,\n SettingsPropertyExpr,\n SharingPropertyExpr,\n SimilarToExpr,\n SliceExpr,\n SortExpr,\n SortKeyPropertyExpr,\n SqlReadWritePropertyExpr,\n SqlSecurityPropertyExpr,\n SqrtExpr,\n StabilityPropertyExpr,\n StarExpr,\n StarMapExpr,\n StorageHandlerPropertyExpr,\n StrPositionExpr,\n StrToDateExpr,\n StrToTimeExpr,\n StreamExpr,\n StreamingTablePropertyExpr,\n StrictPropertyExpr,\n StructExpr,\n SubExpr,\n SubqueryExpr,\n SubstringExpr,\n SummarizeExpr,\n SwapTableExpr,\n TableAliasExpr,\n TableExpr,\n TableFromRowsExpr,\n TableSampleExpr,\n TemporaryPropertyExpr,\n TitleColumnConstraintExpr,\n ToTablePropertyExpr,\n TransactionExpr,\n TransformModelPropertyExpr,\n TransientPropertyExpr,\n TrimExpr,\n TrimPosition,\n TruncateTableExpr,\n TryCastExpr,\n TupleExpr,\n UncacheExpr,\n UnicodeStringExpr,\n UnionExpr,\n UniqueColumnConstraintExpr,\n UnloggedPropertyExpr,\n UnnestExpr,\n UnpivotColumnsExpr,\n UpdateExpr,\n UpperExpr,\n UppercaseColumnConstraintExpr,\n UseExpr,\n UserDefinedFunctionExpr,\n UsingDataExpr,\n UuidExpr,\n ValuesExpr,\n VarExpr,\n VarMapExpr,\n VersionExpr,\n ViewAttributePropertyExpr,\n VolatilePropertyExpr,\n WhenExpr,\n WhensExpr,\n WhereExpr,\n WindowExpr,\n WindowSpecExpr,\n WithDataPropertyExpr,\n WithExpr,\n WithFillExpr,\n WithJournalTablePropertyExpr,\n WithOperatorExpr,\n WithProcedureOptionsExpr,\n WithSchemaBindingPropertyExpr,\n WithSystemVersioningPropertyExpr,\n WithTableHintExpr,\n WithinGroupExpr,\n XmlElementExpr,\n XmlNamespaceExpr,\n XmlTableExpr,\n array,\n column,\n INTERVAL_DAY_TIME_RE,\n INTERVAL_STRING_RE,\n select,\n toIdentifier,\n UNWRAPPED_QUERIES,\n var_,\n alias,\n cast,\n maybeParse,\n AlterExprKind,\n CreateExprKind,\n DropExprKind,\n JoinExprKind,\n null_,\n SetOperationExprKind,\n} from '../expressions';\nimport type {\n JoinExprArgs, StringExpr,\n ExpressionValue,\n} from '../expressions';\nimport { formatTime } from '../time';\nimport {\n applyIndexOffset, camelToScreamingSnakeCase, ensureList, seqGet,\n} from '../helper';\nimport {\n Dialect, type DialectType, NullOrdering, buildDateDelta,\n} from '../dialects/dialect';\nimport {\n concatMessages,\n ErrorLevel,\n highlightSql,\n mergeErrors,\n ParseError,\n} from '../errors';\nimport {\n Token,\n Tokenizer, TokenType,\n} from '../tokens';\nimport {\n newTrie, type TrieNode, inTrie, TrieResult,\n} from '../trie';\nimport { normalizeIdentifiers } from '../optimizer/normalize_identifiers';\nimport { FUNCTION_BY_NAME } from './function_registry';\n\n/**\n * Parses the given SQL string into a collection of syntax trees, one per parsed SQL statement.\n *\n * @param sql - The SQL code string to parse\n * @param opts - Parse options including:\n * - read: the SQL dialect to apply during parsing (eg. \"spark\", \"hive\", \"presto\", \"mysql\")\n * - dialect: the SQL dialect (alias for read)\n * - into: the SQLGlot Expression type to parse into\n * - other Parser options\n * @returns The resulting syntax tree collection\n */\nexport function parse<IntoT extends typeof Expression = typeof Expression> (\n sql: string,\n opts?: ParseOptions<IntoT>,\n): (InstanceType<IntoT> | undefined)[] {\n return Dialect.getOrRaise(opts?.read ?? opts?.dialect).parse(sql, opts) as (InstanceType<IntoT> | undefined)[];\n}\n\n/**\n * Parses the given SQL string and returns a syntax tree for the first parsed SQL statement.\n *\n * @param sql - The SQL code string to parse\n * @param options - Parse options including:\n * - read: the SQL dialect to apply during parsing (eg. \"spark\", \"hive\", \"presto\", \"mysql\")\n * - dialect: the SQL dialect (alias for read)\n * - into: the SQLGlot Expression type to parse into\n * - other Parser options\n * @returns The syntax tree for the first parsed statement\n * @throws ParseError if no expression was parsed\n */\nexport function parseOne<IntoT extends typeof Expression = typeof Expression> (\n sql: string,\n options?: ParseOptions<IntoT>,\n): InstanceType<IntoT> {\n const dialect = Dialect.getOrRaise(options?.read ?? options?.dialect);\n\n const result = options?.into\n ? dialect.parseIntoTypes(options.into as string | typeof Expression | (string | typeof Expression)[], sql, options)\n : dialect.parse(sql, options);\n\n for (const expression of result) {\n if (!expression) {\n throw new ParseError(`No expression was parsed from '${sql}'`);\n }\n return expression as InstanceType<IntoT>;\n }\n\n throw new ParseError(`No expression was parsed from '${sql}'`);\n}\n\nexport type OptionsType = Record<string, (string[] | string)[]>;\n\n// Used to detect alphabetical characters and +/- in timestamp literals\nexport const TIME_ZONE_RE: RegExp = /:.*?[a-zA-Z+\\-]/;\n\nexport function buildVarMap (args: Expression[]): StarMapExpr | VarMapExpr {\n if (args.length < 1) {\n throw new Error('buildVarMap only accepts an expression list with at least one expression');\n }\n\n if (args.length === 1 && args[0].isStar) {\n return new StarMapExpr({ this: args[0] });\n }\n\n const keys: Expression[] = [];\n const values: Expression[] = [];\n for (let i = 0; i < args.length; i += 2) {\n keys.push(args[i]);\n values.push(args[i + 1]);\n }\n\n return new VarMapExpr({\n keys: array(keys, { copy: false }),\n values: array(values, { copy: false }),\n });\n}\n\nexport function buildLike (args: Expression[]): EscapeExpr | LikeExpr {\n if (args.length < 2) {\n throw new Error('buildLike only accept expression lists with at least 2 expressions');\n }\n const like = new LikeExpr({\n this: args[1],\n expression: args[0],\n });\n return 2 < args.length\n ? new EscapeExpr({\n this: like,\n expression: args[2],\n })\n : like;\n}\n\nexport function binaryRangeParser (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n exprType: new (args: any) => Expression,\n options: { reverseArgs?: boolean } = {},\n): (this: Parser, thisExpr: Expression | undefined) => Expression | undefined {\n const { reverseArgs = false } = options;\n\n return function parseBinaryRange (\n this: Parser,\n thisExpr: Expression | undefined,\n ): Expression | undefined {\n let expression = this.parseBitwise();\n let thisArg = thisExpr;\n\n if (reverseArgs) {\n [thisArg, expression] = [expression, thisArg];\n }\n\n return this.parseEscape(\n this.expression(exprType, {\n this: thisArg,\n expression,\n }),\n );\n };\n}\n\nexport function buildLogarithm (args: Expression[], { dialect }: { dialect: Dialect }): LogExpr | LnExpr {\n if (args.length < 1) {\n throw new Error('buildAlgorithm only accepts an expression list with at least one expression');\n }\n // Default argument order is base, expression\n let thisArg = seqGet(args, 0);\n let expression = seqGet(args, 1);\n\n if (thisArg && expression) {\n if (!dialect._constructor.LOG_BASE_FIRST) {\n [thisArg, expression] = [expression, thisArg];\n }\n return new LogExpr({\n this: thisArg,\n expression,\n });\n }\n\n // Check if dialect's parser class has LOG_DEFAULTS_TO_LN property\n const parserClass = dialect._constructor.parserClass;\n const logDefaultsToLn = parserClass?.LOG_DEFAULTS_TO_LN ?? false;\n\n return logDefaultsToLn\n ? new LnExpr({ this: thisArg })\n : new LogExpr({ this: thisArg });\n}\n\nexport function buildHex (args: Expression[], { dialect }: { dialect: Dialect }): HexExpr | LowerHexExpr {\n if (args.length < 1) {\n throw new Error('buildHex only accepts an expression list with at least one expression');\n }\n const arg = args[0];\n return dialect._constructor.HEX_LOWERCASE\n ? new LowerHexExpr({ this: arg })\n : new HexExpr({ this: arg });\n}\n\nexport function buildLower (args: Expression[]): LowerExpr | LowerHexExpr {\n if (args.length < 1) {\n throw new Error('buildLower only accepts an expression list with at least one expression');\n }\n // LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation\n const arg = args[0];\n return arg instanceof HexExpr\n ? new LowerHexExpr({ this: arg.args.this })\n : new LowerExpr({ this: arg });\n}\n\nexport function buildUpper (args: Expression[]): UpperExpr | HexExpr {\n if (args.length < 1) {\n throw new Error('buildUpper only accepts an expression list with at least one expression');\n }\n // UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation\n const arg = args[0];\n return arg instanceof LowerHexExpr\n ? new HexExpr({ this: arg.args.this })\n : new UpperExpr({ this: arg });\n}\n\nexport function buildExtractJsonWithPath<E extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n exprType: new (args: any) => E,\n): (args: Expression[], options: { dialect: Dialect }) => E {\n return function builder (args: Expression[], { dialect }: { dialect: Dialect }): E {\n if (args.length < 2) {\n throw new Error('buildExtractJsonWithPath only accepts an expression list with at least two expressions');\n }\n const expression = new exprType({\n this: args[0],\n expression: dialect.toJsonPath(seqGet(args, 1)),\n });\n\n if (2 < args.length && expression instanceof JsonExtractExpr) {\n expression.setArgKey('expressions', args.slice(2));\n }\n\n if (expression instanceof JsonExtractScalarExpr) {\n expression.setArgKey('scalarOnly', dialect._constructor.JSON_EXTRACT_SCALAR_SCALAR_ONLY);\n }\n\n return expression;\n };\n}\n\nexport function buildMod (args: Expression[]): ModExpr {\n if (args.length < 2) {\n throw new Error('buildMod only accepts an expression list with at least two expressions');\n }\n let thisArg = args[0];\n let expression = args[1];\n\n // Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7\n thisArg = thisArg instanceof BinaryExpr ? new ParenExpr({ this: thisArg }) : thisArg;\n expression = expression instanceof BinaryExpr ? new ParenExpr({ this: expression }) : expression;\n\n return new ModExpr({\n this: thisArg,\n expression,\n });\n}\n\nexport function buildPad (args: Expression[], options: { isLeft?: boolean } = {}): PadExpr {\n if (args.length < 2) {\n throw new Error('buildPad only accepts an expression list with at least two expressions');\n }\n const { isLeft = true } = options;\n\n return new PadExpr({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n fillPattern: seqGet(args, 2),\n isLeft,\n });\n}\n\nexport function buildArrayConstructor<E extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n exprClass: new (args: any) => E,\n args: Expression[],\n bracketKind: TokenType,\n dialect: Dialect,\n): E {\n const arrayExpr = new exprClass({ expressions: args });\n\n if (arrayExpr instanceof ArrayExpr && dialect._constructor.HAS_DISTINCT_ARRAY_CONSTRUCTORS) {\n arrayExpr.setArgKey('bracketNotation', bracketKind === TokenType.L_BRACKET);\n }\n\n return arrayExpr;\n}\n\nexport function buildConvertTimezone (\n args: (Expression | string)[],\n options: { defaultSourceTz?: string } = {},\n): ConvertTimezoneExpr {\n if (args.length < 2) {\n throw new Error('buildConvertTimezone only accepts an expression list with at least two expressions');\n }\n const { defaultSourceTz } = options;\n\n if (args.length === 2) {\n const sourceTz = defaultSourceTz ? LiteralExpr.string(defaultSourceTz) : undefined;\n const firstArg = seqGet(args, 0);\n const targetTz = typeof firstArg === 'string' ? LiteralExpr.string(firstArg) : firstArg;\n const secondArg = seqGet(args, 1);\n const timestamp = typeof secondArg === 'string' ? LiteralExpr.string(secondArg) : secondArg;\n return new ConvertTimezoneExpr({\n sourceTz,\n targetTz,\n timestamp,\n });\n }\n\n return ConvertTimezoneExpr.fromArgList(args);\n}\n\nexport function buildTrim (\n args: Expression[],\n options: {\n isLeft?: boolean;\n reverseArgs?: boolean;\n } = {},\n): TrimExpr {\n if (args.length < 1) {\n throw new Error('buildTrim only accepts an expression list with at least one expression');\n }\n const {\n isLeft = true, reverseArgs = false,\n } = options;\n\n let thisArg = seqGet(args, 0);\n let expression = seqGet(args, 1);\n\n if (expression && reverseArgs) {\n [thisArg, expression] = [expression, thisArg];\n }\n\n return new TrimExpr({\n this: thisArg,\n expression,\n position: isLeft ? TrimPosition.LEADING : TrimPosition.TRAILING,\n });\n}\n\nexport function buildCoalesce (\n args: Expression[],\n options: {\n isNvl?: boolean;\n isNull?: boolean;\n } = {},\n): CoalesceExpr {\n if (args.length < 1) {\n throw new Error('buildCoalesce only accepts an expression list with at least one expression');\n }\n const {\n isNvl, isNull,\n } = options;\n\n return new CoalesceExpr({\n this: seqGet(args, 0),\n expressions: args.slice(1),\n isNvl,\n isNull,\n });\n}\n\nexport function buildLocateStrPosition (args: Expression[]): StrPositionExpr {\n if (args.length < 2) {\n throw new Error('buildLocateStrposition only accepts an expression list with at least two expressions');\n }\n\n return new StrPositionExpr({\n this: seqGet(args, 1),\n substr: seqGet(args, 0),\n position: seqGet(args, 2),\n });\n}\n\nexport function buildArrayAppend (args: Expression[], { dialect }: { dialect: Dialect }): ArrayAppendExpr {\n if (args.length < 2) {\n throw new Error('buildArrayAppend only accepts an expression list with at least two expressions');\n }\n\n /**\n * Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.\n *\n * Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.\n * Others (DuckDB, PostgreSQL) create a new single-element array instead.\n */\n return new ArrayAppendExpr({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n nullPropagation: dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS,\n });\n}\n\nexport function buildArrayPrepend (args: Expression[], { dialect }: { dialect: Dialect }): ArrayPrependExpr {\n if (args.length < 2) {\n throw new Error('buildArrayPrepend only accepts an expression list with at least two expressions');\n }\n\n /**\n * Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.\n *\n * Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.\n * Others (DuckDB, PostgreSQL) create a new single-element array instead.\n */\n return new ArrayPrependExpr({\n this: args[0],\n expression: args[1],\n nullPropagation: dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS,\n });\n}\n\nexport function buildArrayConcat (args: Expression[], { dialect }: { dialect: Dialect }): ArrayConcatExpr {\n if (args.length < 1) {\n throw new Error('buildArrayConcat only accepts an expression list with at least one expression');\n }\n\n /**\n * Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.\n *\n * Some dialects (Redshift, Snowflake) return NULL when any input array is NULL.\n * Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.\n */\n return new ArrayConcatExpr({\n this: args[0],\n expressions: args.slice(1),\n nullPropagation: dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS,\n });\n}\n\nexport function buildArrayRemove (args: Expression[], { dialect }: { dialect: Dialect }): ArrayRemoveExpr {\n if (args.length < 2) {\n throw new Error('buildArrayRemove only accepts an expression list with at least two expressions');\n }\n\n /**\n * Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.\n *\n * Some dialects (Snowflake) return NULL when the removal value is NULL.\n * Others (DuckDB) may return empty array due to NULL comparison semantics.\n */\n return new ArrayRemoveExpr({\n this: args[0],\n expression: args[1],\n nullPropagation: dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS,\n });\n}\n\nexport interface ParseOptions<IntoT extends typeof Expression = typeof Expression> {\n read?: DialectType;\n dialect?: DialectType;\n errorLevel?: ErrorLevel;\n errorMessageContext?: number;\n maxErrors?: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n into?: string | IntoT | (string | IntoT)[] | (new (args: any) => any) | (new (args: any) => any)[];\n [key: string]: unknown;\n}\n\n/**\n * Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.\n *\n * Args:\n * errorLevel: The desired error level. Default: ErrorLevel.IMMEDIATE\n * errorMessageContext: The amount of context to capture from a query string when displaying\n * the error message (in number of characters). Default: 100\n * maxErrors: Maximum number of error messages to include in a raised ParseError.\n * This is only relevant if error_level is ErrorLevel.RAISE. Default: 3\n */\nexport class Parser {\n private static _showTrie?: TrieNode;\n static get SHOW_TRIE (): TrieNode {\n if (!this._showTrie) {\n this._showTrie = newTrie(\n Object.keys(this.SHOW_PARSERS).map((key) => key.split(' ')),\n );\n }\n return this._showTrie;\n }\n\n private static _setTrie?: TrieNode;\n static get SET_TRIE (): TrieNode {\n if (!this._setTrie) {\n this._setTrie = newTrie(\n Object.keys(this.SET_PARSERS).map((key) => key.split(' ')),\n );\n }\n return this._setTrie;\n }\n\n // Function name to builder mapping\n @cache\n static get FUNCTIONS (): Record<string, (args: Expression[], options: { dialect: Dialect }) => Expression> {\n return {\n // Spread all fromArgList functions from FUNCTION_BY_NAME\n ...Object.fromEntries(\n Array.from(FUNCTION_BY_NAME.entries()).map(([name, func]) => [name, (args: Expression[], _options: { dialect: Dialect }) => func.fromArgList(args)]),\n ),\n\n // Coalesce variants\n ...Object.fromEntries(\n [\n 'COALESCE',\n 'IFNULL',\n 'NVL',\n ].map((name) => [name, (args: Expression[], _options: { dialect: Dialect }) => buildCoalesce(args)]),\n ),\n\n // Array functions\n ARRAY: (args, _options) => new ArrayExpr({ expressions: args }),\n\n ARRAYAGG: (args, { dialect }) => new ArrayAggExpr({\n this: args[0],\n nullsExcluded: dialect._constructor.ARRAY_AGG_INCLUDES_NULLS === undefined ? true : undefined,\n }),\n\n ARRAY_AGG: (args, { dialect }) => new ArrayAggExpr({\n this: args[0],\n nullsExcluded: dialect._constructor.ARRAY_AGG_INCLUDES_NULLS === undefined ? true : undefined,\n }),\n\n ARRAY_APPEND: buildArrayAppend,\n ARRAY_CAT: buildArrayConcat,\n ARRAY_CONCAT: buildArrayConcat,\n ARRAY_PREPEND: buildArrayPrepend,\n ARRAY_REMOVE: buildArrayRemove,\n\n // Aggregate functions\n COUNT: (args, _options) => new CountExpr({\n this: args[0],\n expressions: args.slice(1),\n bigInt: true,\n }),\n\n // String functions\n CONCAT: (args, { dialect }) => new ConcatExpr({\n expressions: args,\n safe: !dialect._constructor.STRICT_STRING_CONCAT,\n coalesce: dialect._constructor.CONCAT_COALESCE,\n }),\n\n CONCAT_WS: (args, { dialect }) => new ConcatWsExpr({\n expressions: args,\n safe: !dialect._constructor.STRICT_STRING_CONCAT,\n coalesce: dialect._constructor.CONCAT_COALESCE,\n }),\n\n // Conversion functions\n CONVERT_TIMEZONE: (args, _options) => buildConvertTimezone(args),\n\n DATE_ADD: (args: Expression[]) => {\n // DATE_ADD expects arguments in order: this, expression, unit (optional)\n // buildDateDelta expects them as: unit, expression, this (for unitBased)\n // So we need to reorder if there are 3 arguments\n if (args.length === 3) {\n return buildDateDelta(DateAddExpr)([\n args[2],\n args[1],\n args[0],\n ]);\n }\n return new DateAddExpr({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n });\n },\n DATE_SUB: (args: Expression[]) => {\n // DATE_SUB expects arguments in order: this, expression, unit (optional)\n // buildDateDelta expects them as: unit, expression, this (for unitBased)\n // So we need to reorder if there are 3 arguments\n if (args.length === 3) {\n return buildDateDelta(DateSubExpr)([\n args[2],\n args[1],\n args[0],\n ]);\n }\n return new DateSubExpr({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n });\n },\n\n DATE_TO_DATE_STR: (args, _options) => new CastExpr({\n this: args[0],\n to: new DataTypeExpr({ this: DataTypeExprKind.TEXT }),\n }),\n\n TIME_TO_TIME_STR: (args, _options) => new CastExpr({\n this: args[0],\n to: new DataTypeExpr({ this: DataTypeExprKind.TEXT }),\n }),\n\n // Generator functions\n GENERATE_DATE_ARRAY: (args, _options) => new GenerateDateArrayExpr({\n start: args[0],\n end: args[1],\n step: seqGet(args, 2) || new IntervalExpr({\n this: LiteralExpr.string('1'),\n unit: var_('DAY'),\n }),\n }),\n\n GENERATE_UUID: (args, { dialect }) => new UuidExpr({\n isString: dialect._constructor.UUID_IS_STRING_TYPE || undefined,\n }),\n\n // Pattern matching\n GLOB: (args, _options) => new GlobExpr({\n this: args[1],\n expression: args[0],\n }),\n\n LIKE: (args, _options) => buildLike(args),\n\n // Comparison functions\n GREATEST: (args, { dialect }) => new GreatestExpr({\n this: args[0],\n expressions: args.slice(1),\n ignoreNulls: dialect._constructor.LEAST_GREATEST_IGNORES_NULLS,\n }),\n\n LEAST: (args, { dialect }) => new LeastExpr({\n this: args[0],\n expressions: args.slice(1),\n ignoreNulls: dialect._constructor.LEAST_GREATEST_IGNORES_NULLS,\n }),\n\n // Encoding functions\n HEX: (args, { dialect }) => buildHex(args, { dialect }),\n TO_HEX: (args, { dialect }) => buildHex(args, { dialect }),\n\n // JSON functions\n JSON_EXTRACT: buildExtractJsonWithPath(JsonExtractExpr),\n JSON_EXTRACT_SCALAR: buildExtractJsonWithPath(JsonExtractScalarExpr),\n JSON_EXTRACT_PATH_TEXT: buildExtractJsonWithPath(JsonExtractScalarExpr),\n\n JSON_KEYS: (args, { dialect }) => new JsonKeysExpr({\n this: args[0],\n expression: dialect.toJsonPath(seqGet(args, 1)),\n }),\n\n // Math functions\n LOG: (args, { dialect }) => buildLogarithm(args, { dialect }),\n LOG2: (args, _options) => new LogExpr({\n this: LiteralExpr.number(2),\n expression: seqGet(args, 0),\n }),\n LOG10: (args, _options) => new LogExpr({\n this: LiteralExpr.number(10),\n expression: seqGet(args, 0),\n }),\n MOD: (args, _options) => buildMod(args),\n\n // String manipulation\n LOWER: (args, _options) => buildLower(args),\n UPPER: (args, _options) => buildUpper(args),\n\n LPAD: (args, _options) => buildPad(args),\n LEFTPAD: (args, _options) => buildPad(args),\n RPAD: (args, _options) => buildPad(args, { isLeft: false }),\n RIGHTPAD: (args, _options) => buildPad(args, { isLeft: false }),\n\n LTRIM: (args, _options) => buildTrim(args),\n RTRIM: (args, _options) => buildTrim(args, { isLeft: false }),\n\n // String search\n STRPOS: (args, _options) => StrPositionExpr.fromArgList(args),\n INSTR: (args, _options) => StrPositionExpr.fromArgList(args),\n CHARINDEX: (args, _options) => buildLocateStrPosition(args),\n LOCATE: (args, _options) => buildLocateStrPosition(args),\n\n // Scope resolution\n SCOPE_RESOLUTION: (args, _options) => args.length !== 2\n ? new ScopeResolutionExpr({ expression: args[0] })\n : new ScopeResolutionExpr({\n this: seqGet(args, 0),\n expression: args[1],\n }),\n\n // String operations\n TS_OR_DS_TO_DATE_STR: (args, _options) => new SubstringExpr({\n this: new CastExpr({\n this: args[0],\n to: new DataTypeExpr({ this: DataTypeExprKind.TEXT }),\n }),\n start: LiteralExpr.number(1),\n length: LiteralExpr.number(10),\n }),\n\n // Array operations\n UNNEST: (args, _options) => new UnnestExpr({\n expressions: [args[0]],\n }),\n\n // UUID\n UUID: (args, { dialect }) => new UuidExpr({\n isString: dialect._constructor.UUID_IS_STRING_TYPE || undefined,\n }),\n\n // Map operations\n VAR_MAP: (args, _options) => buildVarMap(args),\n };\n }\n\n // Function expressions that don't require parentheses\n @cache\n static get NO_PAREN_FUNCTIONS (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.CURRENT_DATE]: CurrentDateExpr,\n [TokenType.CURRENT_DATETIME]: CurrentDateExpr,\n [TokenType.CURRENT_TIME]: CurrentTimeExpr,\n [TokenType.CURRENT_TIMESTAMP]: CurrentTimestampExpr,\n [TokenType.CURRENT_USER]: CurrentUserExpr,\n [TokenType.LOCALTIME]: LocaltimeExpr,\n [TokenType.LOCALTIMESTAMP]: LocaltimestampExpr,\n [TokenType.CURRENT_ROLE]: CurrentRoleExpr,\n };\n }\n\n @cache\n static get STRUCT_TYPE_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.FILE,\n TokenType.NESTED,\n TokenType.OBJECT,\n TokenType.STRUCT,\n TokenType.UNION,\n ]);\n }\n\n @cache\n static get NESTED_TYPE_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.ARRAY,\n TokenType.LIST,\n TokenType.LOWCARDINALITY,\n TokenType.MAP,\n TokenType.NULLABLE,\n TokenType.RANGE,\n ...Parser.STRUCT_TYPE_TOKENS,\n ]);\n }\n\n @cache\n static get ENUM_TYPE_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.DYNAMIC,\n TokenType.ENUM,\n TokenType.ENUM8,\n TokenType.ENUM16,\n ]);\n }\n\n @cache\n static get AGGREGATE_TYPE_TOKENS (): Set<TokenType> {\n return new Set([TokenType.AGGREGATEFUNCTION, TokenType.SIMPLEAGGREGATEFUNCTION]);\n }\n\n @cache\n static get TYPE_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.BIT,\n TokenType.BOOLEAN,\n TokenType.TINYINT,\n TokenType.UTINYINT,\n TokenType.SMALLINT,\n TokenType.USMALLINT,\n TokenType.INT,\n TokenType.UINT,\n TokenType.BIGINT,\n TokenType.UBIGINT,\n TokenType.BIGNUM,\n TokenType.INT128,\n TokenType.UINT128,\n TokenType.INT256,\n TokenType.UINT256,\n TokenType.MEDIUMINT,\n TokenType.UMEDIUMINT,\n TokenType.FIXEDSTRING,\n TokenType.FLOAT,\n TokenType.DOUBLE,\n TokenType.UDOUBLE,\n TokenType.CHAR,\n TokenType.NCHAR,\n TokenType.VARCHAR,\n TokenType.NVARCHAR,\n TokenType.BPCHAR,\n TokenType.TEXT,\n TokenType.MEDIUMTEXT,\n TokenType.LONGTEXT,\n TokenType.BLOB,\n TokenType.MEDIUMBLOB,\n TokenType.LONGBLOB,\n TokenType.BINARY,\n TokenType.VARBINARY,\n TokenType.JSON,\n TokenType.JSONB,\n TokenType.INTERVAL,\n TokenType.TINYBLOB,\n TokenType.TINYTEXT,\n TokenType.TIME,\n TokenType.TIMETZ,\n TokenType.TIME_NS,\n TokenType.TIMESTAMP,\n TokenType.TIMESTAMP_S,\n TokenType.TIMESTAMP_MS,\n TokenType.TIMESTAMP_NS,\n TokenType.TIMESTAMPTZ,\n TokenType.TIMESTAMPLTZ,\n TokenType.TIMESTAMPNTZ,\n TokenType.DATETIME,\n TokenType.DATETIME2,\n TokenType.DATETIME64,\n TokenType.SMALLDATETIME,\n TokenType.DATE,\n TokenType.DATE32,\n TokenType.INT4RANGE,\n TokenType.INT4MULTIRANGE,\n TokenType.INT8RANGE,\n TokenType.INT8MULTIRANGE,\n TokenType.NUMRANGE,\n TokenType.NUMMULTIRANGE,\n TokenType.TSRANGE,\n TokenType.TSMULTIRANGE,\n TokenType.TSTZRANGE,\n TokenType.TSTZMULTIRANGE,\n TokenType.DATERANGE,\n TokenType.DATEMULTIRANGE,\n TokenType.DECIMAL,\n TokenType.DECIMAL32,\n TokenType.DECIMAL64,\n TokenType.DECIMAL128,\n TokenType.DECIMAL256,\n TokenType.DECFLOAT,\n TokenType.UDECIMAL,\n TokenType.BIGDECIMAL,\n TokenType.UUID,\n TokenType.GEOGRAPHY,\n TokenType.GEOGRAPHYPOINT,\n TokenType.GEOMETRY,\n TokenType.POINT,\n TokenType.RING,\n TokenType.LINESTRING,\n TokenType.MULTILINESTRING,\n TokenType.POLYGON,\n TokenType.MULTIPOLYGON,\n TokenType.HLLSKETCH,\n TokenType.HSTORE,\n TokenType.PSEUDO_TYPE,\n TokenType.SUPER,\n TokenType.SERIAL,\n TokenType.SMALLSERIAL,\n TokenType.BIGSERIAL,\n TokenType.XML,\n TokenType.YEAR,\n TokenType.USERDEFINED,\n TokenType.MONEY,\n TokenType.SMALLMONEY,\n TokenType.ROWVERSION,\n TokenType.IMAGE,\n TokenType.VARIANT,\n TokenType.VECTOR,\n TokenType.VOID,\n TokenType.OBJECT,\n TokenType.OBJECT_IDENTIFIER,\n TokenType.INET,\n TokenType.IPADDRESS,\n TokenType.IPPREFIX,\n TokenType.IPV4,\n TokenType.IPV6,\n TokenType.UNKNOWN,\n TokenType.NOTHING,\n TokenType.NULL,\n TokenType.NAME,\n TokenType.TDIGEST,\n TokenType.DYNAMIC,\n ...Parser.ENUM_TYPE_TOKENS,\n ...Parser.NESTED_TYPE_TOKENS,\n ...Parser.AGGREGATE_TYPE_TOKENS,\n ]);\n }\n\n @cache\n static get SIGNED_TO_UNSIGNED_TYPE_TOKEN (): Partial<Record<TokenType, TokenType>> {\n return {\n [TokenType.BIGINT]: TokenType.UBIGINT,\n [TokenType.INT]: TokenType.UINT,\n [TokenType.MEDIUMINT]: TokenType.UMEDIUMINT,\n [TokenType.SMALLINT]: TokenType.USMALLINT,\n [TokenType.TINYINT]: TokenType.UTINYINT,\n [TokenType.DECIMAL]: TokenType.UDECIMAL,\n [TokenType.DOUBLE]: TokenType.UDOUBLE,\n };\n }\n\n @cache\n static get SUBQUERY_PREDICATES (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.ANY]: AnyExpr,\n [TokenType.ALL]: AllExpr,\n [TokenType.EXISTS]: ExistsExpr,\n [TokenType.SOME]: AnyExpr,\n };\n }\n\n @cache\n static get RESERVED_TOKENS (): Set<TokenType> {\n return new Set(\n [...Object.values(Tokenizer.SINGLE_TOKENS), TokenType.SELECT].filter((t) => t !== TokenType.IDENTIFIER),\n );\n }\n\n @cache\n static get DB_CREATABLES (): Set<TokenType> {\n return new Set([\n TokenType.DATABASE,\n TokenType.DICTIONARY,\n TokenType.FILE_FORMAT,\n TokenType.MODEL,\n TokenType.NAMESPACE,\n TokenType.SCHEMA,\n TokenType.SEMANTIC_VIEW,\n TokenType.SEQUENCE,\n TokenType.SINK,\n TokenType.SOURCE,\n TokenType.STAGE,\n TokenType.STORAGE_INTEGRATION,\n TokenType.STREAMLIT,\n TokenType.TABLE,\n TokenType.TAG,\n TokenType.VIEW,\n TokenType.WAREHOUSE,\n ]);\n }\n\n @cache\n static get CREATABLES (): Set<TokenType> {\n return new Set([\n TokenType.COLUMN,\n TokenType.CONSTRAINT,\n TokenType.FOREIGN_KEY,\n TokenType.FUNCTION,\n TokenType.INDEX,\n TokenType.PROCEDURE,\n ...Parser.DB_CREATABLES,\n ]);\n }\n\n @cache\n static get ALTERABLES (): Set<TokenType> {\n return new Set([\n TokenType.INDEX,\n TokenType.TABLE,\n TokenType.VIEW,\n TokenType.SESSION,\n ]);\n }\n\n @cache\n static get ID_VAR_TOKENS (): Set<TokenType> {\n return (() => {\n const tokens = new Set([\n TokenType.ALL,\n TokenType.ANALYZE,\n TokenType.ATTACH,\n TokenType.VAR,\n TokenType.ANTI,\n TokenType.APPLY,\n TokenType.ASC,\n TokenType.ASOF,\n TokenType.AUTO_INCREMENT,\n TokenType.BEGIN,\n TokenType.BPCHAR,\n TokenType.CACHE,\n TokenType.CASE,\n TokenType.COLLATE,\n TokenType.COMMAND,\n TokenType.COMMENT,\n TokenType.COMMIT,\n TokenType.CONSTRAINT,\n TokenType.COPY,\n TokenType.CUBE,\n TokenType.CURRENT_SCHEMA,\n TokenType.DEFAULT,\n TokenType.DELETE,\n TokenType.DESC,\n TokenType.DESCRIBE,\n TokenType.DETACH,\n TokenType.DICTIONARY,\n TokenType.DIV,\n TokenType.END,\n TokenType.EXECUTE,\n TokenType.EXPORT,\n TokenType.ESCAPE,\n TokenType.FALSE,\n TokenType.FIRST,\n TokenType.FILTER,\n TokenType.FINAL,\n TokenType.FORMAT,\n TokenType.FULL,\n TokenType.GET,\n TokenType.IDENTIFIER,\n TokenType.INOUT,\n TokenType.IS,\n TokenType.ISNULL,\n TokenType.INTERVAL,\n TokenType.KEEP,\n TokenType.KILL,\n TokenType.LEFT,\n TokenType.LIMIT,\n TokenType.LOAD,\n TokenType.LOCK,\n TokenType.MATCH,\n TokenType.MERGE,\n TokenType.NATURAL,\n TokenType.NEXT,\n TokenType.OFFSET,\n TokenType.OPERATOR,\n TokenType.ORDINALITY,\n TokenType.OVER,\n TokenType.OVERLAPS,\n TokenType.OVERWRITE,\n TokenType.PARTITION,\n TokenType.PERCENT,\n TokenType.PIVOT,\n TokenType.PRAGMA,\n TokenType.PUT,\n TokenType.RANGE,\n TokenType.RECURSIVE,\n TokenType.REFERENCES,\n TokenType.REFRESH,\n TokenType.RENAME,\n TokenType.REPLACE,\n TokenType.RIGHT,\n TokenType.ROLLUP,\n TokenType.ROW,\n TokenType.ROWS,\n TokenType.SEMI,\n TokenType.SET,\n TokenType.SETTINGS,\n TokenType.SHOW,\n TokenType.TEMPORARY,\n TokenType.TOP,\n TokenType.TRUE,\n TokenType.TRUNCATE,\n TokenType.UNIQUE,\n TokenType.UNNEST,\n TokenType.UNPIVOT,\n TokenType.UPDATE,\n TokenType.USE,\n TokenType.VOLATILE,\n TokenType.WINDOW,\n ...Parser.ALTERABLES,\n ...Parser.CREATABLES,\n ...Object.keys(Parser.SUBQUERY_PREDICATES) as TokenType[],\n ...Parser.TYPE_TOKENS,\n ...Object.keys(Parser.NO_PAREN_FUNCTIONS) as TokenType[],\n ]);\n tokens.delete(TokenType.UNION);\n return tokens;\n })();\n }\n\n @cache\n static get TABLE_ALIAS_TOKENS (): Set<TokenType> {\n return new Set(\n [...Parser.ID_VAR_TOKENS].filter((t) => ![\n TokenType.ANTI,\n TokenType.ASOF,\n TokenType.FULL,\n TokenType.LEFT,\n TokenType.LOCK,\n TokenType.NATURAL,\n TokenType.RIGHT,\n TokenType.SEMI,\n TokenType.WINDOW,\n ].includes(t)),\n );\n }\n\n @cache\n static get ALIAS_TOKENS (): Set<TokenType> {\n return Parser.ID_VAR_TOKENS;\n }\n\n @cache\n static get COLON_PLACEHOLDER_TOKENS (): Set<TokenType> {\n return Parser.ID_VAR_TOKENS;\n }\n\n @cache\n static get ARRAY_CONSTRUCTORS (): Record<string, typeof Expression> {\n return {\n ARRAY: ArrayExpr,\n LIST: ListExpr,\n };\n }\n\n @cache\n static get COMMENT_TABLE_ALIAS_TOKENS (): Set<TokenType> {\n return new Set(\n [...Parser.TABLE_ALIAS_TOKENS].filter((t) => t !== TokenType.IS),\n );\n }\n\n @cache\n static get UPDATE_ALIAS_TOKENS (): Set<TokenType> {\n return new Set(\n [...Parser.TABLE_ALIAS_TOKENS].filter((t) => t !== TokenType.SET),\n );\n }\n\n @cache\n static get TRIM_TYPES (): Set<string> {\n return new Set([\n TrimPosition.LEADING.toUpperCase(),\n TrimPosition.TRAILING.toUpperCase(),\n TrimPosition.BOTH.toUpperCase(),\n ]);\n }\n\n @cache\n static get FUNC_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.COLLATE,\n TokenType.COMMAND,\n TokenType.CURRENT_DATE,\n TokenType.CURRENT_DATETIME,\n TokenType.CURRENT_SCHEMA,\n TokenType.CURRENT_TIMESTAMP,\n TokenType.CURRENT_TIME,\n TokenType.CURRENT_USER,\n TokenType.CURRENT_CATALOG,\n TokenType.FILTER,\n TokenType.FIRST,\n TokenType.FORMAT,\n TokenType.GET,\n TokenType.GLOB,\n TokenType.IDENTIFIER,\n TokenType.INDEX,\n TokenType.ISNULL,\n TokenType.ILIKE,\n TokenType.INSERT,\n TokenType.LIKE,\n TokenType.LOCALTIME,\n TokenType.LOCALTIMESTAMP,\n TokenType.MERGE,\n TokenType.NEXT,\n TokenType.OFFSET,\n TokenType.PRIMARY_KEY,\n TokenType.RANGE,\n TokenType.REPLACE,\n TokenType.RLIKE,\n TokenType.ROW,\n TokenType.SESSION_USER,\n TokenType.UNNEST,\n TokenType.VAR,\n TokenType.LEFT,\n TokenType.RIGHT,\n TokenType.SEQUENCE,\n TokenType.DATE,\n TokenType.DATETIME,\n TokenType.TABLE,\n TokenType.TIMESTAMP,\n TokenType.TIMESTAMPTZ,\n TokenType.TRUNCATE,\n TokenType.UTC_DATE,\n TokenType.UTC_TIME,\n TokenType.UTC_TIMESTAMP,\n TokenType.WINDOW,\n TokenType.XOR,\n ...Parser.TYPE_TOKENS,\n ...Object.keys(Parser.SUBQUERY_PREDICATES) as TokenType[],\n ]);\n }\n\n @cache\n static get CONJUNCTION (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.AND]: AndExpr,\n };\n }\n\n @cache\n static get ASSIGNMENT (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.COLON_EQ]: PropertyEqExpr,\n };\n }\n\n @cache\n static get DISJUNCTION (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.OR]: OrExpr,\n };\n }\n\n @cache\n static get EQUALITY (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.EQ]: EqExpr,\n [TokenType.NEQ]: NeqExpr,\n [TokenType.NULLSAFE_EQ]: NullSafeEqExpr,\n };\n }\n\n @cache\n static get COMPARISON (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.GT]: GtExpr,\n [TokenType.GTE]: GteExpr,\n [TokenType.LT]: LtExpr,\n [TokenType.LTE]: LteExpr,\n };\n }\n\n @cache\n static get BITWISE (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.AMP]: BitwiseAndExpr,\n [TokenType.CARET]: BitwiseXorExpr,\n [TokenType.PIPE]: BitwiseOrExpr,\n };\n }\n\n @cache\n static get TERM (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.DASH]: SubExpr,\n [TokenType.PLUS]: AddExpr,\n [TokenType.MOD]: ModExpr,\n [TokenType.COLLATE]: CollateExpr,\n };\n }\n\n @cache\n static get FACTOR (): Partial<Record<TokenType, typeof Expression>> {\n return {\n [TokenType.DIV]: IntDivExpr,\n [TokenType.LR_ARROW]: DistanceExpr,\n [TokenType.SLASH]: DivExpr,\n [TokenType.STAR]: MulExpr,\n };\n }\n\n @cache\n static get EXPONENT (): Partial<Record<TokenType, typeof Expression>> {\n return {};\n }\n\n @cache\n static get TIMES (): Set<TokenType> {\n return new Set([TokenType.TIME, TokenType.TIMETZ]);\n }\n\n @cache\n static get TIMESTAMPS (): Set<TokenType> {\n return new Set([\n TokenType.TIMESTAMP,\n TokenType.TIMESTAMPNTZ,\n TokenType.TIMESTAMPTZ,\n TokenType.TIMESTAMPLTZ,\n ...Parser.TIMES,\n ]);\n }\n\n @cache\n static get SET_OPERATIONS (): Set<TokenType> {\n return new Set([\n TokenType.UNION,\n TokenType.INTERSECT,\n TokenType.EXCEPT,\n ]);\n }\n\n @cache\n static get JOIN_METHODS (): Set<TokenType> {\n return new Set([\n TokenType.ASOF,\n TokenType.NATURAL,\n TokenType.POSITIONAL,\n ]);\n }\n\n @cache\n static get JOIN_SIDES (): Set<TokenType> {\n return new Set([\n TokenType.LEFT,\n TokenType.RIGHT,\n TokenType.FULL,\n ]);\n }\n\n @cache\n static get JOIN_KINDS (): Set<TokenType> {\n return new Set([\n TokenType.ANTI,\n TokenType.CROSS,\n TokenType.INNER,\n TokenType.OUTER,\n TokenType.SEMI,\n TokenType.STRAIGHT_JOIN,\n ]);\n }\n\n static JOIN_HINTS: Set<string> = new Set();\n @cache\n static get LAMBDAS (): Partial<Record<TokenType, (this: Parser, expressions: Expression[]) => Expression>> {\n return {\n [TokenType.ARROW]: function (this: Parser, expressions: Expression[]) {\n return this.expression(\n LambdaExpr,\n {\n this: this.replaceLambda(\n this.parseDisjunction(),\n expressions,\n ),\n expressions: expressions,\n },\n );\n },\n [TokenType.FARROW]: function (this: Parser, expressions: Expression[]) {\n return this.expression(\n KwargExpr,\n {\n this: var_(expressions[0].name),\n expression: this.parseDisjunction(),\n },\n );\n },\n };\n }\n\n @cache\n static get COLUMN_OPERATORS (): Partial<Record<TokenType, undefined | ((this: Parser, this_?: Expression, to?: Expression) => Expression)>> {\n return {\n [TokenType.DOT]: undefined,\n [TokenType.DOTCOLON]: function (this: Parser, this_?: Expression, to?: Expression) {\n return this.expression(\n JsonCastExpr,\n {\n this: this_,\n to: to,\n },\n );\n },\n [TokenType.DCOLON]: function (this: Parser, this_?: Expression, to?: Expression) {\n return this.buildCast({\n strict: this._constructor.STRICT_CAST,\n this: this_,\n to: to,\n });\n },\n [TokenType.ARROW]: function (this: Parser, this_?: Expression, path?: Expression) {\n return this.expression(\n JsonExtractExpr,\n {\n this: this_,\n expression: this.dialect.toJsonPath(path),\n onlyJsonTypes: this._constructor.JSON_ARROWS_REQUIRE_JSON_TYPE,\n },\n );\n },\n [TokenType.DARROW]: function (this: Parser, this_?: Expression, path?: Expression) {\n return this.expression(\n JsonExtractScalarExpr,\n {\n this: this_,\n expression: this.dialect.toJsonPath(path),\n onlyJsonTypes: this._constructor.JSON_ARROWS_REQUIRE_JSON_TYPE,\n scalarOnly: this._dialectConstructor.JSON_EXTRACT_SCALAR_SCALAR_ONLY,\n },\n );\n },\n [TokenType.HASH_ARROW]: function (this: Parser, this_?: Expression, path?: Expression) {\n return this.expression(\n JsonbExtractExpr,\n {\n this: this_,\n expression: path,\n },\n );\n },\n [TokenType.DHASH_ARROW]: function (this: Parser, this_?: Expression, path?: Expression) {\n return this.expression(\n JsonbExtractScalarExpr,\n {\n this: this_,\n expression: path,\n },\n );\n },\n [TokenType.PLACEHOLDER]: function (this: Parser, this_?: Expression, key?: Expression) {\n return this.expression(\n JsonbContainsExpr,\n {\n this: this_,\n expression: key,\n },\n );\n },\n };\n }\n\n @cache\n static get CAST_COLUMN_OPERATORS (): Set<TokenType> {\n return new Set([TokenType.DOTCOLON, TokenType.DCOLON]);\n }\n\n @cache\n static get EXPRESSION_PARSERS (): Record<string, (this: Parser) => Expression | undefined> {\n return {\n [ExpressionKey.CLUSTER]: function (this: Parser) {\n return this.parseSort(ClusterExpr, TokenType.CLUSTER_BY);\n },\n [ExpressionKey.COLUMN]: function (this: Parser) {\n return this.parseColumn();\n },\n [ExpressionKey.COLUMN_DEF]: function (this: Parser) {\n return this.parseColumnDef(this.parseColumn());\n },\n [ExpressionKey.CONDITION]: function (this: Parser) {\n return this.parseDisjunction();\n },\n [ExpressionKey.DATA_TYPE]: function (this: Parser) {\n return this.parseTypes({\n allowIdentifiers: false,\n schema: true,\n });\n },\n [ExpressionKey.EXPRESSION]: function (this: Parser) {\n return this.parseExpression();\n },\n [ExpressionKey.FROM]: function (this: Parser) {\n return this.parseFrom({ joins: true });\n },\n [ExpressionKey.GRANT_PRINCIPAL]: function (this: Parser) {\n return this.parseGrantPrincipal();\n },\n [ExpressionKey.GRANT_PRIVILEGE]: function (this: Parser) {\n return this.parseGrantPrivilege();\n },\n [ExpressionKey.GROUP]: function (this: Parser) {\n return this.parseGroup();\n },\n [ExpressionKey.HAVING]: function (this: Parser) {\n return this.parseHaving();\n },\n [ExpressionKey.HINT]: function (this: Parser) {\n return this.parseHintBody();\n },\n [ExpressionKey.IDENTIFIER]: function (this: Parser) {\n return this.parseIdVar();\n },\n [ExpressionKey.JOIN]: function (this: Parser) {\n return this.parseJoin();\n },\n [ExpressionKey.LAMBDA]: function (this: Parser) {\n return this.parseLambda();\n },\n [ExpressionKey.LATERAL]: function (this: Parser) {\n return this.parseLateral();\n },\n [ExpressionKey.LIMIT]: function (this: Parser) {\n return this.parseLimit();\n },\n [ExpressionKey.OFFSET]: function (this: Parser) {\n return this.parseOffset();\n },\n [ExpressionKey.ORDER]: function (this: Parser) {\n return this.parseOrder();\n },\n [ExpressionKey.ORDERED]: function (this: Parser) {\n return this.parseOrdered();\n },\n [ExpressionKey.PROPERTIES]: function (this: Parser) {\n return this.parseProperties();\n },\n [ExpressionKey.PARTITIONED_BY_PROPERTY]: function (this: Parser) {\n return this.parsePartitionedBy();\n },\n [ExpressionKey.QUALIFY]: function (this: Parser) {\n return this.parseQualify();\n },\n [ExpressionKey.RETURNING]: function (this: Parser) {\n return this.parseReturning();\n },\n [ExpressionKey.SELECT]: function (this: Parser) {\n return this.parseSelect();\n },\n [ExpressionKey.SORT]: function (this: Parser) {\n return this.parseSort(SortExpr, TokenType.SORT_BY);\n },\n [ExpressionKey.TABLE]: function (this: Parser) {\n return this.parseTableParts();\n },\n [ExpressionKey.TABLE_ALIAS]: function (this: Parser) {\n return this.parseTableAlias();\n },\n [ExpressionKey.TUPLE]: function (this: Parser) {\n return this.parseValue({ values: false });\n },\n [ExpressionKey.WHENS]: function (this: Parser) {\n return this.parseWhenMatched();\n },\n [ExpressionKey.WHERE]: function (this: Parser) {\n return this.parseWhere();\n },\n [ExpressionKey.WINDOW]: function (this: Parser) {\n return this.parseNamedWindow();\n },\n [ExpressionKey.WITH]: function (this: Parser) {\n return this.parseWith();\n },\n JOIN_TYPE: function (this: Parser) {\n const {\n method, side, kind,\n } = this.parseJoinParts();\n return new JoinExpr({\n method: method?.text,\n side: enumFromString(JoinExprKind, side?.text),\n kind: enumFromString(JoinExprKind, kind?.text),\n });\n },\n };\n }\n\n @cache\n static get STATEMENT_PARSERS (): Partial<Record<TokenType, (this: Parser) => Expression | undefined>> {\n return {\n [TokenType.ALTER]: function (this: Parser) {\n return this.parseAlter();\n },\n [TokenType.ANALYZE]: function (this: Parser) {\n return this.parseAnalyze();\n },\n [TokenType.BEGIN]: function (this: Parser) {\n return this.parseTransaction();\n },\n [TokenType.CACHE]: function (this: Parser) {\n return this.parseCache();\n },\n [TokenType.COMMENT]: function (this: Parser) {\n return this.parseComment();\n },\n [TokenType.COMMIT]: function (this: Parser) {\n return this.parseCommitOrRollback();\n },\n [TokenType.COPY]: function (this: Parser) {\n return this.parseCopy();\n },\n [TokenType.CREATE]: function (this: Parser) {\n return this.parseCreate();\n },\n [TokenType.DELETE]: function (this: Parser) {\n return this.parseDelete();\n },\n [TokenType.DESC]: function (this: Parser) {\n return this.parseDescribe();\n },\n [TokenType.DESCRIBE]: function (this: Parser) {\n return this.parseDescribe();\n },\n [TokenType.DROP]: function (this: Parser) {\n return this.parseDrop();\n },\n [TokenType.GRANT]: function (this: Parser) {\n return this.parseGrant();\n },\n [TokenType.REVOKE]: function (this: Parser) {\n return this.parseRevoke();\n },\n [TokenType.INSERT]: function (this: Parser) {\n return this.parseInsert();\n },\n [TokenType.KILL]: function (this: Parser) {\n return this.parseKill();\n },\n [TokenType.LOAD]: function (this: Parser) {\n return this.parseLoad();\n },\n [TokenType.MERGE]: function (this: Parser) {\n return this.parseMerge();\n },\n [TokenType.PIVOT]: function (this: Parser) {\n return this.parseSimplifiedPivot();\n },\n [TokenType.PRAGMA]: function (this: Parser) {\n return this.expression(PragmaExpr, { this: this.parseExpression() });\n },\n [TokenType.REFRESH]: function (this: Parser) {\n return this.parseRefresh();\n },\n [TokenType.ROLLBACK]: function (this: Parser) {\n return this.parseCommitOrRollback();\n },\n [TokenType.SET]: function (this: Parser) {\n return this.parseSet();\n },\n [TokenType.TRUNCATE]: function (this: Parser) {\n return this.parseTruncateTable();\n },\n [TokenType.UNCACHE]: function (this: Parser) {\n return this.parseUncache();\n },\n [TokenType.UNPIVOT]: function (this: Parser) {\n return this.parseSimplifiedPivot({ isUnpivot: true });\n },\n [TokenType.UPDATE]: function (this: Parser) {\n return this.parseUpdate();\n },\n [TokenType.USE]: function (this: Parser) {\n return this.parseUse();\n },\n [TokenType.SEMICOLON]: () => new SemicolonExpr({}),\n };\n }\n\n @cache\n static get UNARY_PARSERS (): Partial<Record<TokenType, (this: Parser) => Expression | undefined>> {\n return {\n [TokenType.PLUS]: function (this: Parser) {\n return this.parseUnary();\n },\n [TokenType.NOT]: function (this: Parser) {\n return this.expression(NotExpr, { this: this.parseEquality() });\n },\n [TokenType.TILDE]: function (this: Parser) {\n return this.expression(BitwiseNotExpr, { this: this.parseUnary() });\n },\n [TokenType.DASH]: function (this: Parser) {\n return this.expression(NegExpr, { this: this.parseUnary() });\n },\n [TokenType.PIPE_SLASH]: function (this: Parser) {\n return this.expression(SqrtExpr, { this: this.parseUnary() });\n },\n [TokenType.DPIPE_SLASH]: function (this: Parser) {\n return this.expression(CbrtExpr, { this: this.parseUnary() });\n },\n };\n }\n\n @cache\n static get STRING_PARSERS (): Partial<Record<TokenType, (this: Parser, token: Token) => Expression>> {\n return {\n [TokenType.HEREDOC_STRING]: function (this: Parser, token: Token) {\n return this.expression(RawStringExpr, { token });\n },\n [TokenType.NATIONAL_STRING]: function (this: Parser, token: Token) {\n return this.expression(NationalExpr, { token });\n },\n [TokenType.RAW_STRING]: function (this: Parser, token: Token) {\n return this.expression(RawStringExpr, { token });\n },\n [TokenType.STRING]: function (this: Parser, token: Token) {\n return this.expression(LiteralExpr, {\n token,\n isString: true,\n });\n },\n [TokenType.UNICODE_STRING]: function (this: Parser, token: Token) {\n return this.expression(\n UnicodeStringExpr,\n {\n token,\n escape: this.matchTextSeq('UESCAPE') && this.parseString(),\n },\n );\n },\n };\n }\n\n @cache\n static get NUMERIC_PARSERS (): Partial<Record<TokenType, (this: Parser, token: Token) => Expression>> {\n return {\n [TokenType.BIT_STRING]: function (this: Parser, token: Token) {\n return this.expression(BitStringExpr, { token });\n },\n [TokenType.BYTE_STRING]: function (this: Parser, token: Token) {\n return this.expression(\n ByteStringExpr,\n {\n token,\n isBytes: this._dialectConstructor.BYTE_STRING_IS_BYTES_TYPE || undefined,\n },\n );\n },\n [TokenType.HEX_STRING]: function (this: Parser, token: Token) {\n return this.expression(\n HexStringExpr,\n {\n token,\n isInteger: this._dialectConstructor.HEX_STRING_IS_INTEGER_TYPE || undefined,\n },\n );\n },\n [TokenType.NUMBER]: function (this: Parser, token: Token) {\n return this.expression(LiteralExpr, {\n token,\n isString: false,\n });\n },\n };\n }\n\n @cache\n static get PRIMARY_PARSERS (): Partial<Record<TokenType, (this: Parser, token: Token) => Expression | undefined>> {\n return {\n ...Parser.STRING_PARSERS,\n ...Parser.NUMERIC_PARSERS,\n [TokenType.INTRODUCER]: function (this: Parser, token: Token) {\n return this.parseIntroducer(token);\n },\n [TokenType.NULL]: function (this: Parser, _: Token) {\n return this.expression(NullExpr, {});\n },\n [TokenType.TRUE]: function (this: Parser, _: Token) {\n return this.expression(BooleanExpr, { this: true });\n },\n [TokenType.FALSE]: function (this: Parser, _: Token) {\n return this.expression(BooleanExpr, { this: false });\n },\n [TokenType.SESSION_PARAMETER]: function (this: Parser, _: Token) {\n return this.parseSessionParameter();\n },\n [TokenType.STAR]: function (this: Parser, _: Token) {\n return this.parseStarOps();\n },\n };\n }\n\n @cache\n static get PLACEHOLDER_PARSERS (): Partial<Record<TokenType, (this: Parser) => Expression | undefined>> {\n return {\n [TokenType.PLACEHOLDER]: function (this: Parser) {\n return this.expression(PlaceholderExpr);\n },\n [TokenType.PARAMETER]: function (this: Parser) {\n return this.parseParameter();\n },\n [TokenType.COLON]: function (this: Parser) {\n return (\n this.matchSet(this._constructor.COLON_PLACEHOLDER_TOKENS)\n ? this.expression(PlaceholderExpr, { this: this.prev?.text })\n : undefined\n );\n },\n };\n }\n\n @cache\n static get RANGE_PARSERS (): Partial<Record<TokenType, (this: Parser, this_: Expression) => Expression | undefined>> {\n return {\n [TokenType.AT_GT]: binaryRangeParser(ArrayContainsAllExpr),\n [TokenType.BETWEEN]: function (this: Parser, this_: Expression) {\n return this.parseBetween(this_);\n },\n [TokenType.GLOB]: binaryRangeParser(GlobExpr),\n [TokenType.ILIKE]: binaryRangeParser(ILikeExpr),\n [TokenType.IN]: function (this: Parser, this_: Expression) {\n return this.parseIn(this_);\n },\n [TokenType.IRLIKE]: binaryRangeParser(RegexpILikeExpr),\n [TokenType.IS]: function (this: Parser, this_: Expression) {\n return this.parseIs(this_);\n },\n [TokenType.LIKE]: binaryRangeParser(LikeExpr),\n [TokenType.LT_AT]: binaryRangeParser(ArrayContainsAllExpr, { reverseArgs: true }),\n [TokenType.OVERLAPS]: binaryRangeParser(OverlapsExpr),\n [TokenType.RLIKE]: binaryRangeParser(RegexpLikeExpr),\n [TokenType.SIMILAR_TO]: binaryRangeParser(SimilarToExpr),\n [TokenType.FOR]: function (this: Parser, this_: Expression) {\n return this.parseComprehension(this_);\n },\n [TokenType.QMARK_AMP]: binaryRangeParser(JsonbContainsAllTopKeysExpr),\n [TokenType.QMARK_PIPE]: binaryRangeParser(JsonbContainsAnyTopKeysExpr),\n [TokenType.HASH_DASH]: binaryRangeParser(JsonbDeleteAtPathExpr),\n [TokenType.ADJACENT]: binaryRangeParser(AdjacentExpr),\n [TokenType.OPERATOR]: function (this: Parser, this_: Expression) {\n return this.parseOperator(this_);\n },\n [TokenType.AMP_LT]: binaryRangeParser(ExtendsLeftExpr),\n [TokenType.AMP_GT]: binaryRangeParser(ExtendsRightExpr),\n };\n }\n\n @cache\n static get PIPE_SYNTAX_TRANSFORM_PARSERS (): Partial<Record<string, (this: Parser, query: SelectExpr) => SelectExpr>> {\n return {\n 'AGGREGATE': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxAggregate(query);\n },\n 'AS': function (this: Parser, query: SelectExpr) {\n return this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n aliasCte: this.parseTableAlias(),\n });\n },\n 'EXTEND': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxExtend(query);\n },\n 'LIMIT': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxLimit(query);\n },\n 'ORDER BY': function (this: Parser, query: SelectExpr) {\n return query.orderBy(\n this.parseOrder(),\n {\n append: false,\n copy: false,\n },\n );\n },\n 'PIVOT': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxPivot(query);\n },\n 'SELECT': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxSelect(query);\n },\n 'TABLESAMPLE': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxTablesample(query);\n },\n 'UNPIVOT': function (this: Parser, query: SelectExpr) {\n return this.parsePipeSyntaxPivot(query);\n },\n 'WHERE': function (this: Parser, query: SelectExpr) {\n return query.where(this.parseWhere(), { copy: false });\n },\n };\n }\n\n @cache\n static get PROPERTY_PARSERS (): Record<string, (this: Parser, ...args: unknown[]) => Expression | Expression[] | undefined> {\n return {\n 'ALLOWED_VALUES': function (this: Parser) {\n return this.expression(\n AllowedValuesPropertyExpr,\n { expressions: this.parseCsv(this.parsePrimary.bind(this)) },\n );\n },\n 'ALGORITHM': function (this: Parser) {\n return this.parsePropertyAssignment(AlgorithmPropertyExpr);\n },\n 'AUTO': function (this: Parser) {\n return this.parseAutoProperty();\n },\n 'AUTO_INCREMENT': function (this: Parser) {\n return this.parsePropertyAssignment(AutoIncrementPropertyExpr);\n },\n 'BACKUP': function (this: Parser) {\n return this.expression(\n BackupPropertyExpr,\n { this: this.parseVar({ anyToken: true }) },\n );\n },\n 'BLOCKCOMPRESSION': function (this: Parser) {\n return this.parseBlockCompression();\n },\n 'CHARSET': function (this: Parser, options?: unknown) {\n return this.parseCharacterSet(options as { default?: boolean });\n },\n 'CHARACTER SET': function (this: Parser, options?: unknown) {\n return this.parseCharacterSet(options as { default?: boolean });\n },\n 'CHECKSUM': function (this: Parser) {\n return this.parseChecksum();\n },\n 'CLUSTER BY': function (this: Parser) {\n return this.parseCluster();\n },\n 'CLUSTERED': function (this: Parser) {\n return this.parseClusteredBy();\n },\n 'COLLATE': function (this: Parser) {\n return this.parsePropertyAssignment(CollatePropertyExpr);\n },\n 'COMMENT': function (this: Parser) {\n return this.parsePropertyAssignment(SchemaCommentPropertyExpr);\n },\n 'CONTAINS': function (this: Parser) {\n return this.parseContainsProperty();\n },\n 'COPY': function (this: Parser) {\n return this.parseCopyProperty();\n },\n 'DATABLOCKSIZE': function (this: Parser, options?: unknown) {\n return this.parseDataBlocksize(options as {\n default?: boolean;\n minimum?: boolean;\n maximum?: boolean;\n });\n },\n 'DATA_DELETION': function (this: Parser) {\n return this.parseDataDeletionProperty();\n },\n 'DEFINER': function (this: Parser) {\n return this.parseDefiner();\n },\n 'DETERMINISTIC': function (this: Parser) {\n return this.expression(\n StabilityPropertyExpr,\n { this: LiteralExpr.string('IMMUTABLE') },\n );\n },\n 'DISTRIBUTED': function (this: Parser) {\n return this.parseDistributedProperty();\n },\n 'DUPLICATE': function (this: Parser) {\n return this.parseCompositeKeyProperty(DuplicateKeyPropertyExpr);\n },\n 'DYNAMIC': function (this: Parser) {\n return this.expression(DynamicPropertyExpr, {});\n },\n 'DISTKEY': function (this: Parser) {\n return this.parseDistkey();\n },\n 'DISTSTYLE': function (this: Parser) {\n return this.parsePropertyAssignment(DistStylePropertyExpr);\n },\n 'EMPTY': function (this: Parser) {\n return this.expression(EmptyPropertyExpr, {});\n },\n 'ENGINE': function (this: Parser) {\n return this.parsePropertyAssignment(EnginePropertyExpr);\n },\n 'ENVIRONMENT': function (this: Parser) {\n return this.expression(\n EnviromentPropertyExpr,\n { expressions: this.parseWrappedCsv(this.parseAssignment.bind(this)) },\n );\n },\n 'EXECUTE': function (this: Parser) {\n return this.parsePropertyAssignment(ExecuteAsPropertyExpr);\n },\n 'EXTERNAL': function (this: Parser) {\n return this.expression(ExternalPropertyExpr, {});\n },\n 'FALLBACK': function (this: Parser, options?: unknown) {\n return this.parseFallback(options as { no?: boolean });\n },\n 'FORMAT': function (this: Parser) {\n return this.parsePropertyAssignment(FileFormatPropertyExpr);\n },\n 'FREESPACE': function (this: Parser) {\n return this.parseFreespace();\n },\n 'GLOBAL': function (this: Parser) {\n return this.expression(GlobalPropertyExpr, {});\n },\n 'HEAP': function (this: Parser) {\n return this.expression(HeapPropertyExpr, {});\n },\n 'ICEBERG': function (this: Parser) {\n return this.expression(IcebergPropertyExpr, {});\n },\n 'IMMUTABLE': function (this: Parser) {\n return this.expression(\n StabilityPropertyExpr,\n { this: LiteralExpr.string('IMMUTABLE') },\n );\n },\n 'INHERITS': function (this: Parser) {\n return this.expression(\n InheritsPropertyExpr,\n { expressions: this.parseWrappedCsv(this.parseTable.bind(this)) },\n );\n },\n 'INPUT': function (this: Parser) {\n return this.expression(InputModelPropertyExpr, { this: this.parseSchema() });\n },\n 'JOURNAL': function (this: Parser, options?: unknown) {\n return this.parseJournal(options as Record<string, unknown>);\n },\n 'LANGUAGE': function (this: Parser) {\n return this.parsePropertyAssignment(LanguagePropertyExpr);\n },\n 'LAYOUT': function (this: Parser) {\n return this.parseDictProperty({ this: 'LAYOUT' });\n },\n 'LIFETIME': function (this: Parser) {\n return this.parseDictRange({ this: 'LIFETIME' });\n },\n 'LIKE': function (this: Parser) {\n return this.parseCreateLike();\n },\n 'LOCATION': function (this: Parser) {\n return this.parsePropertyAssignment(LocationPropertyExpr);\n },\n 'LOCK': function (this: Parser) {\n return this.parseLocking();\n },\n 'LOCKING': function (this: Parser) {\n return this.parseLocking();\n },\n 'LOG': function (this: Parser, options?: unknown) {\n return this.parseLog(options as { no?: boolean });\n },\n 'MATERIALIZED': function (this: Parser) {\n return this.expression(MaterializedPropertyExpr, {});\n },\n 'MERGEBLOCKRATIO': function (this: Parser, options?: unknown) {\n return this.parseMergeBlockRatio(options as {\n no?: boolean;\n default?: boolean;\n });\n },\n 'MODIFIES': function (this: Parser) {\n return this.parseModifiesProperty();\n },\n 'MULTISET': function (this: Parser) {\n return this.expression(SetPropertyExpr, { multi: true });\n },\n 'NO': function (this: Parser) {\n return this.parseNoProperty();\n },\n 'ON': function (this: Parser) {\n return this.parseOnProperty();\n },\n 'ORDER BY': function (this: Parser) {\n return this.parseOrder({ skipOrderToken: true });\n },\n 'OUTPUT': function (this: Parser) {\n return this.expression(OutputModelPropertyExpr, { this: this.parseSchema() });\n },\n 'PARTITION': function (this: Parser) {\n return this.parsePartitionedOf();\n },\n 'PARTITION BY': function (this: Parser) {\n return this.parsePartitionedBy();\n },\n 'PARTITIONED BY': function (this: Parser) {\n return this.parsePartitionedBy();\n },\n 'PARTITIONED_BY': function (this: Parser) {\n return this.parsePartitionedBy();\n },\n 'PRIMARY KEY': function (this: Parser) {\n return this.parsePrimaryKey({ inProps: true });\n },\n 'RANGE': function (this: Parser) {\n return this.parseDictRange({ this: 'RANGE' });\n },\n 'READS': function (this: Parser) {\n return this.parseReadsProperty();\n },\n 'REMOTE': function (this: Parser) {\n return this.parseRemoteWithConnection();\n },\n 'RETURNS': function (this: Parser) {\n return this.parseReturns();\n },\n 'STRICT': function (this: Parser) {\n return this.expression(StrictPropertyExpr, {});\n },\n 'STREAMING': function (this: Parser) {\n return this.expression(StreamingTablePropertyExpr, {});\n },\n 'ROW': function (this: Parser) {\n return this.parseRow();\n },\n 'ROW_FORMAT': function (this: Parser) {\n return this.parsePropertyAssignment(RowFormatPropertyExpr);\n },\n 'SAMPLE': function (this: Parser) {\n return this.expression(\n SamplePropertyExpr,\n { this: this.matchTextSeq('BY') && this.parseBitwise() },\n );\n },\n 'SECURE': function (this: Parser) {\n return this.expression(SecurePropertyExpr, {});\n },\n 'SECURITY': function (this: Parser) {\n return this.parseSecurity();\n },\n 'SET': function (this: Parser) {\n return this.expression(SetPropertyExpr, { multi: false });\n },\n 'SETTINGS': function (this: Parser) {\n return this.parseSettingsProperty();\n },\n 'SHARING': function (this: Parser) {\n return this.parsePropertyAssignment(SharingPropertyExpr);\n },\n 'SORTKEY': function (this: Parser) {\n return this.parseSortkey();\n },\n 'SOURCE': function (this: Parser) {\n return this.parseDictProperty({ this: 'SOURCE' });\n },\n 'STABLE': function (this: Parser) {\n return this.expression(\n StabilityPropertyExpr,\n { this: LiteralExpr.string('STABLE') },\n );\n },\n 'STORED': function (this: Parser) {\n return this.parseStored();\n },\n 'SYSTEM_VERSIONING': function (this: Parser) {\n return this.parseSystemVersioningProperty();\n },\n 'TBLPROPERTIES': function (this: Parser) {\n return this.parseWrappedProperties();\n },\n 'TEMP': function (this: Parser) {\n return this.expression(TemporaryPropertyExpr, {});\n },\n 'TEMPORARY': function (this: Parser) {\n return this.expression(TemporaryPropertyExpr, {});\n },\n 'TO': function (this: Parser) {\n return this.parseToTable();\n },\n 'TRANSIENT': function (this: Parser) {\n return this.expression(TransientPropertyExpr, {});\n },\n 'TRANSFORM': function (this: Parser) {\n return this.expression(\n TransformModelPropertyExpr,\n { expressions: this.parseWrappedCsv(this.parseExpression.bind(this)) },\n );\n },\n 'TTL': function (this: Parser) {\n return this.parseTtl();\n },\n 'USING': function (this: Parser) {\n return this.parsePropertyAssignment(FileFormatPropertyExpr);\n },\n 'UNLOGGED': function (this: Parser) {\n return this.expression(UnloggedPropertyExpr, {});\n },\n 'VOLATILE': function (this: Parser) {\n return this.parseVolatileProperty();\n },\n 'WITH': function (this: Parser) {\n return this.parseWithProperty();\n },\n };\n }\n\n @cache\n static get CONSTRAINT_PARSERS (): Partial<Record<string, (this: Parser, ...args: unknown[]) => Expression | Expression[] | undefined>> {\n return {\n 'AUTOINCREMENT': function (this: Parser) {\n return this.parseAutoIncrement();\n },\n 'AUTO_INCREMENT': function (this: Parser) {\n return this.parseAutoIncrement();\n },\n 'CASESPECIFIC': function (this: Parser) {\n return this.expression(CaseSpecificColumnConstraintExpr, { not: false });\n },\n 'CHARACTER SET': function (this: Parser) {\n return this.expression(\n CharacterSetColumnConstraintExpr,\n { this: this.parseVarOrString() },\n );\n },\n 'CHECK': function (this: Parser) {\n return this.parseCheckConstraint();\n },\n 'COLLATE': function (this: Parser) {\n return this.expression(\n CollateColumnConstraintExpr,\n { this: this.parseIdentifier() || this.parseColumn() },\n );\n },\n 'COMMENT': function (this: Parser) {\n return this.expression(\n CommentColumnConstraintExpr,\n { this: this.parseString() },\n );\n },\n 'COMPRESS': function (this: Parser) {\n return this.parseCompress();\n },\n 'CLUSTERED': function (this: Parser) {\n return this.expression(\n ClusteredColumnConstraintExpr,\n { this: this.parseWrappedCsv(() => this.parseOrdered()) },\n );\n },\n 'NONCLUSTERED': function (this: Parser) {\n return this.expression(\n NonClusteredColumnConstraintExpr,\n { this: this.parseWrappedCsv(() => this.parseOrdered()) },\n );\n },\n 'DEFAULT': function (this: Parser) {\n return this.expression(\n DefaultColumnConstraintExpr,\n { this: this.parseBitwise() },\n );\n },\n 'ENCODE': function (this: Parser) {\n return this.expression(EncodeColumnConstraintExpr, { this: this.parseVar() });\n },\n 'EPHEMERAL': function (this: Parser) {\n return this.expression(\n EphemeralColumnConstraintExpr,\n { this: this.parseBitwise() },\n );\n },\n 'EXCLUDE': function (this: Parser) {\n return this.expression(\n ExcludeColumnConstraintExpr,\n { this: this.parseIndexParams() },\n );\n },\n 'FOREIGN KEY': function (this: Parser) {\n return this.parseForeignKey();\n },\n 'FORMAT': function (this: Parser) {\n return this.expression(\n DateFormatColumnConstraintExpr,\n { this: this.parseVarOrString() },\n );\n },\n 'GENERATED': function (this: Parser) {\n return this.parseGeneratedAsIdentity();\n },\n 'IDENTITY': function (this: Parser) {\n return this.parseAutoIncrement();\n },\n 'INLINE': function (this: Parser) {\n return this.parseInline();\n },\n 'LIKE': function (this: Parser) {\n return this.parseCreateLike();\n },\n 'NOT': function (this: Parser) {\n return this.parseNotConstraint();\n },\n 'NULL': function (this: Parser) {\n return this.expression(NotNullColumnConstraintExpr, { allowNull: true });\n },\n 'ON': function (this: Parser) {\n return (\n this.match(TokenType.UPDATE)\n && this.expression(OnUpdateColumnConstraintExpr, { this: this.parseFunction() })\n )\n || this.expression(OnPropertyExpr, { this: this.parseIdVar() });\n },\n 'PATH': function (this: Parser) {\n return this.expression(PathColumnConstraintExpr, { this: this.parseString() });\n },\n 'PERIOD': function (this: Parser) {\n return this.parsePeriodForSystemTime();\n },\n 'PRIMARY KEY': function (this: Parser) {\n return this.parsePrimaryKey();\n },\n 'REFERENCES': function (this: Parser) {\n return this.parseReferences({ match: false });\n },\n 'TITLE': function (this: Parser) {\n return this.expression(\n TitleColumnConstraintExpr,\n { this: this.parseVarOrString() },\n );\n },\n 'TTL': function (this: Parser) {\n return this.expression(MergeTreeTtlExpr, { expressions: [this.parseBitwise()] });\n },\n 'UNIQUE': function (this: Parser) {\n return this.parseUnique();\n },\n 'UPPERCASE': function (this: Parser) {\n return this.expression(UppercaseColumnConstraintExpr);\n },\n 'WITH': function (this: Parser) {\n return this.expression(\n PropertiesExpr,\n { expressions: this.parseWrappedProperties() },\n );\n },\n 'BUCKET': function (this: Parser) {\n return this.parsePartitionedByBucketOrTruncate();\n },\n 'TRUNCATE': function (this: Parser) {\n return this.parsePartitionedByBucketOrTruncate();\n },\n };\n }\n\n @cache\n static get ALTER_PARSERS (): Partial<Record<string, (this: Parser) => Expression | Expression[] | undefined>> {\n return {\n 'ADD': function (this: Parser) {\n return this.parseAlterTableAdd();\n },\n 'AS': function (this: Parser) {\n return this.parseSelect();\n },\n 'ALTER': function (this: Parser) {\n return this.parseAlterTableAlter();\n },\n 'CLUSTER BY': function (this: Parser) {\n return this.parseCluster({ wrapped: true });\n },\n 'DELETE': function (this: Parser) {\n return this.expression(DeleteExpr, { where: this.parseWhere() });\n },\n 'DROP': function (this: Parser) {\n return this.parseAlterTableDrop();\n },\n 'RENAME': function (this: Parser) {\n return this.parseAlterTableRename();\n },\n 'SET': function (this: Parser) {\n return this.parseAlterTableSet();\n },\n 'SWAP': function (this: Parser) {\n return this.expression(\n SwapTableExpr,\n { this: this.match(TokenType.WITH) && this.parseTable({ schema: true }) },\n );\n },\n };\n }\n\n static ALTER_ALTER_PARSERS: Partial<Record<string, (this: Parser) => Expression>> = {\n DISTKEY: function (this: Parser) {\n return this.parseAlterDiststyle();\n },\n DISTSTYLE: function (this: Parser) {\n return this.parseAlterDiststyle();\n },\n SORTKEY: function (this: Parser) {\n return this.parseAlterSortkey();\n },\n COMPOUND: function (this: Parser) {\n return this.parseAlterSortkey({ compound: true });\n },\n };\n\n static SCHEMA_UNNAMED_CONSTRAINTS: Set<string> = new Set([\n 'CHECK',\n 'EXCLUDE',\n 'FOREIGN KEY',\n 'LIKE',\n 'PERIOD',\n 'PRIMARY KEY',\n 'UNIQUE',\n 'BUCKET',\n 'TRUNCATE',\n ]);\n\n @cache\n static get NO_PAREN_FUNCTION_PARSERS (): Partial<Record<string, (this: Parser) => Expression | undefined>> {\n return {\n ANY: function (this: Parser) {\n return this.expression(AnyExpr, { this: this.parseBitwise() });\n },\n CASE: function (this: Parser) {\n return this.parseCase();\n },\n CONNECT_BY_ROOT: function (this: Parser) {\n return this.expression(\n ConnectByRootExpr,\n { this: this.parseColumn() },\n );\n },\n IF: function (this: Parser) {\n return this.parseIf();\n },\n };\n }\n\n @cache\n static get INVALID_FUNC_NAME_TOKENS (): Set<TokenType> {\n return new Set([TokenType.IDENTIFIER, TokenType.STRING]);\n }\n\n static FUNCTIONS_WITH_ALIASED_ARGS: Set<string> = new Set(['STRUCT']);\n @cache\n static get KEY_VALUE_DEFINITIONS (): Set<typeof Expression> {\n return new Set([\n AliasExpr,\n EqExpr,\n PropertyEqExpr,\n SliceExpr,\n ]);\n }\n\n @cache\n static get FUNCTION_PARSERS (): Partial<Record<string, (this: Parser) => Expression | undefined>> {\n return {\n ...Object.fromEntries(\n ArgMaxExpr.sqlNames().map((name) => [\n name,\n function (this: Parser) {\n return this.parseMaxMinBy(ArgMaxExpr);\n },\n ]),\n ),\n ...Object.fromEntries(\n ArgMinExpr.sqlNames().map((name) => [\n name,\n function (this: Parser) {\n return this.parseMaxMinBy(ArgMinExpr);\n },\n ]),\n ),\n CAST: function (this: Parser) {\n return this.parseCast({ strict: this._constructor.STRICT_CAST });\n },\n CEIL: function (this: Parser) {\n return this.parseCeilFloor(CeilExpr);\n },\n CONVERT: function (this: Parser) {\n return this.parseConvert({ strict: this._constructor.STRICT_CAST });\n },\n CHAR: function (this: Parser) {\n return this.parseChar();\n },\n CHR: function (this: Parser) {\n return this.parseChar();\n },\n DECODE: function (this: Parser) {\n return this.parseDecode();\n },\n EXTRACT: function (this: Parser) {\n return this.parseExtract();\n },\n FLOOR: function (this: Parser) {\n return this.parseCeilFloor(FloorExpr);\n },\n GAP_FILL: function (this: Parser) {\n return this.parseGapFill();\n },\n INITCAP: function (this: Parser) {\n return this.parseInitcap();\n },\n JSON_OBJECT: function (this: Parser) {\n return this.parseJsonObject();\n },\n JSON_OBJECTAGG: function (this: Parser) {\n return this.parseJsonObject({ agg: true });\n },\n JSON_TABLE: function (this: Parser) {\n return this.parseJsonTable();\n },\n MATCH: function (this: Parser) {\n return this.parseMatchAgainst();\n },\n NORMALIZE: function (this: Parser) {\n return this.parseNormalize();\n },\n OPENJSON: function (this: Parser) {\n return this.parseOpenJson();\n },\n OVERLAY: function (this: Parser) {\n return this.parseOverlay();\n },\n POSITION: function (this: Parser) {\n return this.parsePosition();\n },\n SAFE_CAST: function (this: Parser) {\n return this.parseCast({\n strict: false,\n safe: true,\n });\n },\n STRING_AGG: function (this: Parser) {\n return this.parseStringAgg();\n },\n SUBSTRING: function (this: Parser) {\n return this.parseSubstring();\n },\n TRIM: function (this: Parser) {\n return this.parseTrim();\n },\n TRY_CAST: function (this: Parser) {\n return this.parseCast({\n strict: false,\n safe: true,\n });\n },\n TRY_CONVERT: function (this: Parser) {\n return this.parseConvert({\n strict: false,\n safe: true,\n });\n },\n XMLELEMENT: function (this: Parser) {\n return this.parseXmlElement();\n },\n XMLTABLE: function (this: Parser) {\n return this.parseXmlTable();\n },\n };\n }\n\n @cache\n static get QUERY_MODIFIER_PARSERS (): Partial<Record<TokenType, (this: Parser) => [string, Expression | Expression[] | undefined]>> {\n return {\n [TokenType.MATCH_RECOGNIZE]: function (this: Parser): [string, Expression | undefined] {\n return ['match', this.parseMatchRecognize()];\n },\n [TokenType.PREWHERE]: function (this: Parser): [string, Expression | undefined] {\n return ['prewhere', this.parsePrewhere()];\n },\n [TokenType.WHERE]: function (this: Parser): [string, Expression | undefined] {\n return ['where', this.parseWhere()];\n },\n [TokenType.GROUP_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['group', this.parseGroup()];\n },\n [TokenType.HAVING]: function (this: Parser): [string, Expression | undefined] {\n return ['having', this.parseHaving()];\n },\n [TokenType.QUALIFY]: function (this: Parser): [string, Expression | undefined] {\n return ['qualify', this.parseQualify()];\n },\n [TokenType.WINDOW]: function (this: Parser): [string, Expression[] | undefined] {\n return ['windows', this.parseWindowClause()];\n },\n [TokenType.ORDER_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['order', this.parseOrder()];\n },\n [TokenType.LIMIT]: function (this: Parser): [string, Expression | undefined] {\n return ['limit', this.parseLimit()];\n },\n [TokenType.FETCH]: function (this: Parser): [string, Expression | undefined] {\n return ['limit', this.parseLimit()];\n },\n [TokenType.OFFSET]: function (this: Parser): [string, Expression | undefined] {\n return ['offset', this.parseOffset()];\n },\n [TokenType.FOR]: function (this: Parser): [string, Expression[] | undefined] {\n return ['locks', this.parseLocks()];\n },\n [TokenType.LOCK]: function (this: Parser): [string, Expression[] | undefined] {\n return ['locks', this.parseLocks()];\n },\n [TokenType.TABLE_SAMPLE]: function (this: Parser): [string, Expression | undefined] {\n return ['sample', this.parseTableSample({ asModifier: true })];\n },\n [TokenType.USING]: function (this: Parser): [string, Expression | undefined] {\n return ['sample', this.parseTableSample({ asModifier: true })];\n },\n [TokenType.CLUSTER_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['cluster', this.parseSort(ClusterExpr, TokenType.CLUSTER_BY)];\n },\n [TokenType.DISTRIBUTE_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['distribute', this.parseSort(DistributeExpr, TokenType.DISTRIBUTE_BY)];\n },\n [TokenType.SORT_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['sort', this.parseSort(SortExpr, TokenType.SORT_BY)];\n },\n [TokenType.CONNECT_BY]: function (this: Parser): [string, Expression | undefined] {\n return ['connect', this.parseConnect({ skipStartToken: true })];\n },\n [TokenType.START_WITH]: function (this: Parser): [string, Expression | undefined] {\n return ['connect', this.parseConnect()];\n },\n };\n }\n\n @cache\n static get QUERY_MODIFIER_TOKENS (): Set<TokenType> {\n return new Set(\n Object.keys(Parser.QUERY_MODIFIER_PARSERS) as TokenType[],\n );\n }\n\n static SET_PARSERS: Record<string, (this: Parser) => Expression | undefined> = {\n GLOBAL: function (this: Parser) {\n return this.parseSetItemAssignment({ kind: 'GLOBAL' });\n },\n LOCAL: function (this: Parser) {\n return this.parseSetItemAssignment({ kind: 'LOCAL' });\n },\n SESSION: function (this: Parser) {\n return this.parseSetItemAssignment({ kind: 'SESSION' });\n },\n TRANSACTION: function (this: Parser) {\n return this.parseSetTransaction();\n },\n };\n\n static SHOW_PARSERS: Record<string, (this: Parser) => Expression> = {};\n @cache\n static get TYPE_LITERAL_PARSERS (): Partial<Record<DataTypeExprKind, (this: Parser, thisArg?: Expression, _?: unknown) => Expression>> {\n return {\n [DataTypeExprKind.JSON]: function (this: Parser, thisArg?: Expression, _?: unknown) {\n return this.expression(ParseJsonExpr, { this: thisArg });\n },\n };\n }\n\n @cache\n static get TYPE_CONVERTERS (): Partial<Record<DataTypeExprKind, (dataType: DataTypeExpr) => DataTypeExpr>> {\n return {};\n }\n\n @cache\n static get DDL_SELECT_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.SELECT,\n TokenType.WITH,\n TokenType.L_PAREN,\n ]);\n }\n\n @cache\n static get PRE_VOLATILE_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.CREATE,\n TokenType.REPLACE,\n TokenType.UNIQUE,\n ]);\n }\n\n static TRANSACTION_KIND: Set<string> = new Set([\n 'DEFERRED',\n 'IMMEDIATE',\n 'EXCLUSIVE',\n ]);\n\n static TRANSACTION_CHARACTERISTICS: OptionsType = {\n ISOLATION: [\n [\n 'LEVEL',\n 'REPEATABLE',\n 'READ',\n ],\n [\n 'LEVEL',\n 'READ',\n 'COMMITTED',\n ],\n [\n 'LEVEL',\n 'READ',\n 'UNCOMITTED',\n ],\n ['LEVEL', 'SERIALIZABLE'],\n ],\n READ: ['WRITE', 'ONLY'],\n };\n\n static CONFLICT_ACTIONS: OptionsType = {\n ...Object.fromEntries(\n [\n 'ABORT',\n 'FAIL',\n 'IGNORE',\n 'REPLACE',\n 'ROLLBACK',\n 'UPDATE',\n ].map((key) => [key, []]),\n ),\n DO: ['NOTHING', 'UPDATE'],\n };\n\n static CREATE_SEQUENCE: OptionsType = {\n SCALE: ['EXTEND', 'NOEXTEND'],\n ShaRD: ['EXTEND', 'NOEXTEND'],\n NO: [\n 'CYCLE',\n 'CACHE',\n 'MAXVALUE',\n 'MINVALUE',\n ],\n ...Object.fromEntries(\n [\n 'SESSION',\n 'GLOBAL',\n 'KEEP',\n 'NOKEEP',\n 'ORDER',\n 'NOORDER',\n 'NOCACHE',\n 'CYCLE',\n 'NOCYCLE',\n 'NOMINVALUE',\n 'NOMAXVALUE',\n 'NOSCALE',\n 'NOSHARD',\n ].map((key) => [key, []]),\n ),\n };\n\n static ISOLATED_LOADING_OPTIONS: OptionsType = {\n FOR: [\n 'ALL',\n 'INSERT',\n 'NONE',\n ],\n };\n\n static USABLES: OptionsType = Object.fromEntries(\n [\n 'ROLE',\n 'WAREHOUSE',\n 'DATABASE',\n 'SCHEMA',\n 'CATALOG',\n ].map((key) => [key, []]),\n );\n\n static CAST_ACTIONS: OptionsType = Object.fromEntries(\n ['RENAME', 'ADD'].map((key) => [key, ['FIELDS']]),\n );\n\n static SCHEMA_BINDING_OPTIONS: OptionsType = {\n TYPE: ['EVOLUTION'],\n ...Object.fromEntries(\n [\n 'BINDING',\n 'COMPENSATION',\n 'EVOLUTION',\n ].map((key) => [key, []]),\n ),\n };\n\n static PROCEDURE_OPTIONS: OptionsType = {};\n\n static EXECUTE_AS_OPTIONS: OptionsType = Object.fromEntries(\n [\n 'CALLER',\n 'SELF',\n 'OWNER',\n ].map((key) => [key, []]),\n );\n\n static KEY_CONSTRAINT_OPTIONS: OptionsType = {\n NOT: ['ENFORCED'],\n MATCH: [\n 'FULL',\n 'PARTIAL',\n 'SIMPLE',\n ],\n INITIALLY: ['DEFERRED', 'IMMEDIATE'],\n USING: ['BTREE', 'HASH'],\n ...Object.fromEntries(\n [\n 'DEFERRABLE',\n 'NORELY',\n 'RELY',\n ].map((key) => [key, []]),\n ),\n };\n\n static WINDOW_EXCLUDE_OPTIONS: OptionsType = {\n NO: ['OTHERS'],\n CURRENT: ['ROW'],\n ...Object.fromEntries(\n ['GROUP', 'TIES'].map((key) => [key, []]),\n ),\n };\n\n static INSERT_ALTERNATIVES: Set<string> = new Set([\n 'ABORT',\n 'FAIL',\n 'IGNORE',\n 'REPLACE',\n 'ROLLBACK',\n ]);\n\n static CLONE_KEYWORDS: Set<string> = new Set(['CLONE', 'COPY']);\n\n static HISTORICAL_DATA_PREFIX: Set<string> = new Set([\n 'AT',\n 'BEFORE',\n 'END',\n ]);\n\n static HISTORICAL_DATA_KIND: Set<string> = new Set([\n 'OFFSET',\n 'STATEMENT',\n 'STREAM',\n 'TIMESTAMP',\n 'VERSION',\n ]);\n\n static OPCLASS_FOLLOW_KEYWORDS: Set<string> = new Set([\n 'ASC',\n 'DESC',\n 'NULLS',\n 'WITH',\n ]);\n\n @cache\n static get OPTYPE_FOLLOW_TOKENS (): Set<TokenType> {\n return new Set([TokenType.COMMA, TokenType.R_PAREN]);\n }\n\n @cache\n static get TABLE_INDEX_HINT_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.FORCE,\n TokenType.IGNORE,\n TokenType.USE,\n ]);\n }\n\n static VIEW_ATTRIBUTES: Set<string> = new Set([\n 'ENCRYPTION',\n 'SCHEMABINDING',\n 'VIEW_METADATA',\n ]);\n\n @cache\n static get WINDOW_ALIAS_TOKENS (): Set<TokenType> {\n return (() => {\n const result = new Set(Parser.ID_VAR_TOKENS);\n result.delete(TokenType.RANGE);\n result.delete(TokenType.ROWS);\n return result;\n })();\n }\n\n @cache\n static get WINDOW_BEFORE_PAREN_TOKENS (): Set<TokenType> {\n return new Set([TokenType.OVER]);\n }\n\n static WINDOW_SIDES: Set<string> = new Set(['FOLLOWING', 'PRECEDING']);\n @cache\n static get JSON_KEY_VALUE_SEPARATOR_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.COLON,\n TokenType.COMMA,\n TokenType.IS,\n ]);\n }\n\n @cache\n static get FETCH_TOKENS (): Set<TokenType> {\n return (() => {\n const result = new Set(Parser.ID_VAR_TOKENS);\n result.delete(TokenType.ROW);\n result.delete(TokenType.ROWS);\n result.delete(TokenType.PERCENT);\n return result;\n })();\n }\n\n @cache\n static get ADD_CONSTRAINT_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.CONSTRAINT,\n TokenType.FOREIGN_KEY,\n TokenType.INDEX,\n TokenType.KEY,\n TokenType.PRIMARY_KEY,\n TokenType.UNIQUE,\n ]);\n }\n\n @cache\n static get DISTINCT_TOKENS (): Set<TokenType> {\n return new Set([TokenType.DISTINCT]);\n }\n\n @cache\n static get UNNEST_OFFSET_ALIAS_TOKENS (): Set<TokenType> {\n return (() => {\n const result = new Set(Parser.TABLE_ALIAS_TOKENS);\n for (const token of Parser.SET_OPERATIONS) {\n result.delete(token);\n }\n return result;\n })();\n }\n\n @cache\n static get SELECT_START_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.L_PAREN,\n TokenType.WITH,\n TokenType.SELECT,\n ]);\n }\n\n static COPY_INTO_VARLEN_OPTIONS: Set<string> = new Set([\n 'FILE_FORMAT',\n 'COPY_OPTIONS',\n 'FORMAT_OPTIONS',\n 'CREDENTIAL',\n ]);\n\n static IS_JSON_PREDICATE_KIND: Set<string> = new Set([\n 'VALUE',\n 'SCALAR',\n 'ARRAY',\n 'OBJECT',\n ]);\n\n static ODBC_DATETIME_LITERALS: Record<string, typeof Expression> = {};\n\n static ON_CONDITION_TOKENS: Set<string> = new Set([\n 'ERROR',\n 'NULL',\n 'TRUE',\n 'FALSE',\n 'EMPTY',\n ]);\n\n @cache\n static get PRIVILEGE_FOLLOW_TOKENS (): Set<TokenType> {\n return new Set([\n TokenType.ON,\n TokenType.COMMA,\n TokenType.L_PAREN,\n ]);\n }\n\n static DESCRIBE_STYLES: Set<string> = new Set([\n 'ANALYZE',\n 'EXTENDED',\n 'FORMATTED',\n 'HISTORY',\n ]);\n\n static SET_ASSIGNMENT_DELIMITERS: Set<string> = new Set([\n '=',\n ':=',\n 'TO',\n ]);\n\n static ANALYZE_STYLES: Set<string> = new Set([\n 'BUFFER_USAGE_LIMIT',\n 'FULL',\n 'LOCAL',\n 'NO_WRITE_TO_BINLOG',\n 'SAMPLE',\n 'SKIP_LOCKED',\n 'VERBOSE',\n ]);\n\n static ANALYZE_EXPRESSION_PARSERS: Record<string, (this: Parser) => Expression | undefined> = {\n ALL: function (this: Parser) {\n return this.parseAnalyzeColumns();\n },\n COMPUTE: function (this: Parser) {\n return this.parseAnalyzeStatistics();\n },\n DELETE: function (this: Parser) {\n return this.parseAnalyzeDelete();\n },\n DROP: function (this: Parser) {\n return this.parseAnalyzeHistogram();\n },\n ESTIMATE: function (this: Parser) {\n return this.parseAnalyzeStatistics();\n },\n LIST: function (this: Parser) {\n return this.parseAnalyzeList();\n },\n PREDICATE: function (this: Parser) {\n return this.parseAnalyzeColumns();\n },\n UPDATE: function (this: Parser) {\n return this.parseAnalyzeHistogram();\n },\n VALIDATE: function (this: Parser) {\n return this.parseAnalyzeValidate();\n },\n };\n\n static PARTITION_KEYWORDS: Set<string> = new Set(['PARTITION', 'SUBPARTITION']);\n @cache\n static get AMBIGUOUS_ALIAS_TOKENS () {\n return [TokenType.LIMIT, TokenType.OFFSET] as const;\n }\n\n static OPERATION_MODIFIERS: Set<string> = new Set();\n\n static RECURSIVE_CTE_SEARCH_KIND: Set<string> = new Set([\n 'BREADTH',\n 'DEPTH',\n 'CYCLE',\n ]);\n\n @cache\n static get MODIFIABLES (): (typeof Expression)[] {\n return [\n QueryExpr,\n TableExpr,\n TableFromRowsExpr,\n ValuesExpr,\n ];\n }\n\n static STRICT_CAST = true;\n\n static PREFIXED_PIVOT_COLUMNS = false;\n\n static IDENTIFY_PIVOT_STRINGS = false;\n\n static LOG_DEFAULTS_TO_LN = false;\n\n static TABLESAMPLE_CSV = false;\n\n static DEFAULT_SAMPLING_METHOD: string | undefined = undefined;\n\n static SET_REQUIRES_ASSIGNMENT_DELIMITER = true;\n\n static TRIM_PATTERN_FIRST = false;\n\n static STRING_ALIASES = false;\n\n static MODIFIERS_ATTACHED_TO_SET_OP = true;\n\n static SET_OP_MODIFIERS: Set<string> = new Set([\n 'order',\n 'limit',\n 'offset',\n ]);\n\n static NO_PAREN_IF_COMMANDS = true;\n\n static JSON_ARROWS_REQUIRE_JSON_TYPE = false;\n\n static COLON_IS_VARIANT_EXTRACT = false;\n\n static VALUES_FOLLOWED_BY_PAREN = true;\n\n static SUPPORTS_IMPLICIT_UNNEST = false;\n\n static INTERVAL_SPANS = true;\n\n static SUPPORTS_PARTITION_SELECTION = false;\n\n static WRAPPED_TRANSFORM_COLUMN_CONSTRAINT = true;\n\n static OPTIONAL_ALIAS_TOKEN_CTE = true;\n\n static ALTER_RENAME_REQUIRES_COLUMN = true;\n\n static ALTER_TABLE_PARTITIONS = false;\n\n static JOINS_HAVE_EQUAL_PRECEDENCE = false;\n\n static ZONE_AWARE_TIMESTAMP_CONSTRUCTOR = false;\n\n static MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS = false;\n\n static JSON_EXTRACT_REQUIRES_JSON_EXPRESSION = false;\n\n static ADD_JOIN_ON_TRUE = false;\n\n static SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = false;\n\n // Instance properties\n protected sql: string;\n protected dialect: Dialect;\n protected errorLevel: ErrorLevel;\n protected errorMessageContext: number;\n protected maxErrors: number;\n protected errors: ParseError[];\n protected tokens: Token[];\n protected index: number;\n protected curr: Token | undefined;\n protected next: Token | undefined;\n protected prev: Token | undefined;\n protected prevComments: string[] | undefined;\n protected pipeCteCounter: number;\n\n constructor (options: ParseOptions = {}) {\n const {\n errorLevel = ErrorLevel.IMMEDIATE,\n errorMessageContext = 100,\n maxErrors = 3,\n } = options;\n this.sql = '';\n this.dialect = Dialect.getOrRaise(options.dialect);\n this.errorLevel = errorLevel;\n this.errorMessageContext = errorMessageContext;\n this.maxErrors = maxErrors;\n this.errors = [];\n this.tokens = [];\n this.index = 0;\n this.curr = undefined;\n this.next = undefined;\n this.prev = undefined;\n this.prevComments = undefined;\n this.pipeCteCounter = 0;\n this.reset();\n }\n\n reset (): void {\n this.sql = '';\n this.errors = [];\n this.tokens = [];\n this.index = 0;\n this.curr = undefined;\n this.next = undefined;\n this.prev = undefined;\n this.prevComments = undefined;\n this.pipeCteCounter = 0;\n }\n\n /**\n * Parses a list of tokens and returns a list of syntax trees, one tree\n * per parsed SQL statement.\n *\n * @param rawTokens - The list of tokens.\n * @param sql - The original SQL string, used to produce helpful debug messages.\n * @returns The list of the produced syntax trees.\n */\n parse (rawTokens: Token[], sql?: string): (Expression | undefined)[] {\n return this._parse({\n parseMethod: function (this: Parser) {\n return this.parseStatement();\n },\n rawTokens,\n sql,\n });\n }\n\n protected _parse (options: {\n parseMethod: (this: Parser) => Expression | undefined;\n rawTokens: Token[];\n sql?: string;\n }): (Expression | undefined)[] {\n const {\n parseMethod, rawTokens, sql,\n } = options;\n\n this.reset();\n this.sql = sql || '';\n\n const total = rawTokens.length;\n const chunks: Token[][] = [[]];\n\n for (let i = 0; i < rawTokens.length; i++) {\n const token = rawTokens[i];\n if (token.tokenType === TokenType.SEMICOLON) {\n if (token.comments.length) {\n chunks.push([token]);\n }\n\n if (i < total - 1) {\n chunks.push([]);\n }\n } else {\n chunks[chunks.length - 1].push(token);\n }\n }\n\n const expressions: (Expression | undefined)[] = [];\n\n for (const tokens of chunks) {\n this.index = -1;\n this.tokens = tokens;\n this.advance();\n\n expressions.push(parseMethod.call(this));\n\n if (this.index < this.tokens.length) {\n this.raiseError('Invalid expression / Unexpected token');\n }\n\n this.checkErrors();\n }\n\n return expressions;\n }\n\n parseIntoTypes<T extends typeof Expression> (\n expressionTypes: string | T | Iterable<string | T>,\n rawTokens: Token[],\n sql?: string,\n ): (Expression | undefined)[] {\n const errors: ParseError[] = [];\n const types = ensureList(expressionTypes);\n\n for (const expressionType of types) {\n const parser = typeof expressionType === 'string'\n ? (this._constructor.EXPRESSION_PARSERS[expressionType] ?? this._constructor.EXPRESSION_PARSERS[expressionType.charAt(0).toLowerCase() + expressionType.slice(1)])\n : this._constructor.EXPRESSION_PARSERS[(expressionType as typeof Expression).key];\n\n if (!parser) {\n throw new TypeError(`No parser registered for ${expressionType}`);\n }\n\n try {\n return this._parse({\n parseMethod: parser,\n rawTokens,\n sql,\n });\n } catch (e) {\n if (e instanceof ParseError) {\n e.errors[0].intoExpression = typeof expressionType === 'string' ? expressionType : expressionType.name;\n errors.push(e);\n } else {\n throw e;\n }\n }\n }\n\n const typeList = ensureList(expressionTypes);\n const typeNames = `[${typeList.map((t) => (typeof t === 'string' ? t : (t as typeof Expression).name)).join(', ')}]`;\n throw new ParseError(\n `Failed to parse '${sql || rawTokens}' into ${typeNames}`,\n mergeErrors(errors),\n );\n }\n\n // Helper methods\n findSql (start?: Token, end?: Token): string {\n return this.sql.slice(start?.start ?? 0, (end?.end ?? 0) + 1);\n }\n\n isConnected (): boolean {\n return !!(\n this.prev\n && this.curr\n && (this.prev.end ?? 0) + 1 === (this.curr.start ?? 0)\n );\n }\n\n protected advance (times: number = 1): void {\n this.index += times;\n this.curr = seqGet(this.tokens, this.index);\n this.next = seqGet(this.tokens, this.index + 1);\n\n if (0 < this.index) {\n this.prev = this.tokens[this.index - 1];\n this.prevComments = this.prev.comments;\n } else {\n this.prev = undefined;\n this.prevComments = undefined;\n }\n }\n\n protected retreat (index: number): void {\n if (index !== this.index) {\n this.advance(index - this.index);\n }\n }\n\n warnUnsupported (): void {\n if (this.tokens.length <= 1) {\n return;\n }\n\n // We use findSql because this.sql may comprise multiple chunks, and we're only\n // interested in emitting a warning for the one being currently processed.\n const sql = this.findSql(\n this.tokens[0],\n this.tokens[this.tokens.length - 1],\n ).slice(0, this.errorMessageContext);\n\n console.warn(\n `'${sql}' contains unsupported syntax. Falling back to parsing as a 'Command'.`,\n );\n }\n\n protected match (tokenType: TokenType, options: {\n advance?: boolean;\n expression?: Expression;\n } = {}): boolean {\n const {\n advance = true, expression,\n } = options;\n\n if (this.curr?.tokenType === tokenType) {\n if (advance) {\n this.advance();\n }\n this.addComments(expression);\n return true;\n }\n return false;\n }\n\n tryParse<T extends Expression | Expression[] | undefined> (\n parseMethod: () => T,\n options: { retreat?: boolean } = {},\n ): T | undefined {\n const { retreat = false } = options;\n const index = this.index;\n const errorLevel = this.errorLevel;\n\n this.errorLevel = ErrorLevel.IMMEDIATE;\n let result: T | undefined;\n try {\n result = parseMethod();\n } catch (error) {\n if (error instanceof ParseError) {\n result = undefined;\n } else {\n throw error;\n }\n } finally {\n if (!result || retreat) {\n this.retreat(index);\n }\n this.errorLevel = errorLevel;\n }\n\n return result;\n }\n\n parseParen (): Expression | undefined {\n if (!this.match(TokenType.L_PAREN)) {\n return undefined;\n }\n\n const comments = this.prevComments;\n const query = this.parseSelect();\n\n let expressions: Expression[];\n if (query) {\n expressions = [query];\n } else {\n expressions = this.parseExpressions();\n }\n\n let thisExpr = seqGet(expressions, 0);\n\n if (!thisExpr && this.match(TokenType.R_PAREN, { advance: false })) {\n thisExpr = this.expression(TupleExpr, {});\n } else if (thisExpr && UNWRAPPED_QUERIES.some((cls) => thisExpr instanceof cls)) {\n thisExpr = this.parseSubquery(thisExpr, {\n parseAlias: false,\n });\n } else if (thisExpr instanceof SubqueryExpr || thisExpr instanceof ValuesExpr) {\n thisExpr = this.parseSubquery(this.parseQueryModifiers(this.parseSetOperations(thisExpr)), {\n parseAlias: false,\n });\n } else if (1 < expressions.length || this.prev?.tokenType === TokenType.COMMA) {\n thisExpr = this.expression(TupleExpr, { expressions });\n } else {\n thisExpr = this.expression(ParenExpr, { this: thisExpr });\n }\n\n if (thisExpr && comments) {\n thisExpr.addComments(comments);\n }\n\n this.matchRParen(thisExpr);\n\n if (thisExpr instanceof ParenExpr && thisExpr.args.this instanceof AggFuncExpr) {\n return this.parseWindow(thisExpr);\n }\n\n return thisExpr;\n }\n\n parseDrop (options: { exists?: boolean } = {}): DropExpr | CommandExpr {\n const { exists } = options;\n const start = this.prev;\n const temporary = this.match(TokenType.TEMPORARY) || undefined;\n const materialized = this.matchTextSeq('MATERIALIZED') || undefined;\n\n const kind = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev?.text.toUpperCase();\n if (!kind) {\n return this.parseAsCommand(start);\n }\n\n const concurrently = this.matchTextSeq('CONCURRENTLY') || undefined;\n const ifExists = exists || this.parseExists();\n\n let thisExpr: Expression | undefined;\n if (kind === 'COLUMN') {\n thisExpr = this.parseColumn();\n } else {\n thisExpr = this.parseTableParts({\n schema: true,\n isDbReference: this.prev?.tokenType === TokenType.SCHEMA,\n });\n }\n\n const cluster = this.match(TokenType.ON) ? this.parseOnProperty() : undefined;\n\n let expressions: Expression[] | undefined;\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n expressions = this.parseWrappedCsv(() => this.parseTypes());\n }\n\n return this.expression(DropExpr, {\n exists: ifExists,\n this: thisExpr,\n expressions,\n kind: enumFromString(DropExprKind, this._dialectConstructor.CREATABLE_KIND_MAPPING[kind] || kind) ?? (this._dialectConstructor.CREATABLE_KIND_MAPPING[kind] || kind),\n temporary,\n materialized,\n cascade: this.matchTextSeq('CASCADE'),\n constraints: this.matchTextSeq('CONSTRAINTS'),\n purge: this.matchTextSeq('PURGE'),\n cluster,\n concurrently,\n });\n }\n\n parseExists (options: { not?: boolean } = {}): boolean | undefined {\n const { not: notParam } = options;\n const result = (\n this.matchTextSeq('IF')\n && (!notParam || this.match(TokenType.NOT))\n && this.match(TokenType.EXISTS)\n );\n return result ? true : undefined;\n }\n\n parseCreate (): CreateExpr | CommandExpr {\n // Note: this can't be undefined because we've matched a statement parser\n const start = this.prev;\n\n const replace = (\n start?.tokenType === TokenType.REPLACE\n || this.matchPair(TokenType.OR, TokenType.REPLACE)\n || this.matchPair(TokenType.OR, TokenType.ALTER)\n );\n const refresh = this.matchPair(TokenType.OR, TokenType.REFRESH) || undefined;\n\n const unique = this.match(TokenType.UNIQUE) || undefined;\n\n let clustered: boolean | undefined;\n if (this.matchTextSeq(['CLUSTERED', 'COLUMNSTORE'])) {\n clustered = true;\n } else if (\n this.matchTextSeq(['NONCLUSTERED', 'COLUMNSTORE'])\n || this.matchTextSeq('COLUMNSTORE')\n ) {\n clustered = false;\n }\n\n if (this.matchPair(TokenType.TABLE, TokenType.FUNCTION, { advance: false })) {\n this.advance();\n }\n\n let properties: PropertiesExpr | undefined;\n let createToken = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev;\n\n if (!createToken) {\n // exp.Properties.Location.POST_CREATE\n properties = this.parseProperties();\n createToken = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev;\n\n if (!properties || !createToken) {\n return this.parseAsCommand(start);\n }\n }\n\n const concurrently = this.matchTextSeq('CONCURRENTLY') || undefined;\n const exists = this.parseExists({ not: true });\n let thisExpr: Expression | undefined;\n let expression: Expression | undefined;\n let indexes: Expression[] | undefined;\n let noSchemaBinding: boolean | undefined;\n let begin: boolean | undefined;\n let end: boolean | undefined;\n let clone: Expression | undefined;\n\n const extendProps = (tempProps: PropertiesExpr | undefined): void => {\n if (properties && tempProps) {\n properties.args.expressions?.push(...tempProps.args.expressions ?? []);\n } else if (tempProps) {\n properties = tempProps;\n }\n };\n\n if (\n createToken.tokenType === TokenType.FUNCTION\n || createToken.tokenType === TokenType.PROCEDURE\n ) {\n thisExpr = this.parseUserDefinedFunction({ kind: createToken.tokenType });\n\n // exp.Properties.Location.POST_SCHEMA (\"schema\" here is the UDF's type signature)\n extendProps(this.parseProperties());\n\n expression = (this.match(TokenType.ALIAS) && this.parseHeredoc()) || undefined;\n extendProps(this.parseProperties());\n\n if (!expression) {\n if (this.match(TokenType.COMMAND)) {\n expression = this.parseAsCommand(this.prev);\n } else {\n begin = this.match(TokenType.BEGIN) || undefined;\n const return_ = this.matchTextSeq('RETURN') || undefined;\n\n if (this.match(TokenType.STRING, { advance: false })) {\n // Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property\n // https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement\n expression = this.parseString();\n extendProps(this.parseProperties());\n } else {\n expression = this.parseUserDefinedFunctionExpression();\n }\n\n end = this.matchTextSeq('END') || undefined;\n\n if (return_) {\n expression = this.expression(ReturnExpr, { this: expression });\n }\n }\n }\n } else if (createToken.tokenType === TokenType.INDEX) {\n // Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)\n let index: Expression | undefined;\n let anonymous: boolean;\n\n if (!this.match(TokenType.ON)) {\n index = this.parseIdVar();\n anonymous = false;\n } else {\n index = undefined;\n anonymous = true;\n }\n\n thisExpr = this.parseIndex({\n index,\n anonymous,\n });\n } else if (this._constructor.DB_CREATABLES.has(createToken.tokenType)) {\n const tableParts = this.parseTableParts({\n schema: true,\n isDbReference: createToken.tokenType === TokenType.SCHEMA,\n });\n\n // exp.Properties.Location.POST_NAME\n this.match(TokenType.COMMA);\n extendProps(this.parseProperties({ before: true }));\n\n thisExpr = this.parseSchema({ this: tableParts });\n\n // exp.Properties.Location.POST_SCHEMA and POST_WITH\n extendProps(this.parseProperties());\n\n const hasAlias = this.match(TokenType.ALIAS) || undefined;\n if (!this.matchSet(this._constructor.DDL_SELECT_TOKENS, { advance: false })) {\n // exp.Properties.Location.POST_ALIAS\n extendProps(this.parseProperties());\n }\n\n if (createToken.tokenType === TokenType.SEQUENCE) {\n expression = this.parseTypes();\n const props = this.parseProperties();\n\n if (props) {\n const sequenceProps = new SequencePropertiesExpr({});\n const options: Expression[] = [];\n\n for (const prop of props.args.expressions ?? []) {\n if (prop instanceof SequencePropertiesExpr) {\n for (const [arg, value] of Object.entries(prop.args)) {\n if (arg === 'options') {\n options.push(...(value as Expression[]));\n } else {\n sequenceProps.setArgKey(arg, value);\n }\n }\n prop.pop();\n }\n }\n\n if (0 < options.length) {\n sequenceProps.setArgKey('options', options);\n }\n\n props.args.expressions?.push(sequenceProps);\n extendProps(props);\n }\n } else {\n expression = this.parseDdlSelect();\n\n // Some dialects also support using a table as an alias instead of a SELECT.\n // Here we fallback to this as an alternative.\n if (!expression && hasAlias) {\n expression = this.tryParse(() => this.parseTableParts());\n }\n }\n\n if (createToken.tokenType === TokenType.TABLE) {\n // exp.Properties.Location.POST_EXPRESSION\n extendProps(this.parseProperties());\n\n indexes = [];\n while (true) {\n const index = this.parseIndex();\n\n // exp.Properties.Location.POST_INDEX\n extendProps(this.parseProperties());\n if (!index) {\n break;\n } else {\n this.match(TokenType.COMMA);\n indexes.push(index);\n }\n }\n } else if (createToken.tokenType === TokenType.VIEW) {\n if (this.matchTextSeq([\n 'WITH',\n 'NO',\n 'SCHEMA',\n 'BINDING',\n ])) {\n noSchemaBinding = true;\n }\n } else if (\n createToken.tokenType === TokenType.SINK\n || createToken.tokenType === TokenType.SOURCE\n ) {\n extendProps(this.parseProperties());\n }\n\n const shallow = this.matchTextSeq('SHALLOW') || undefined;\n\n if (this.matchTexts(this._constructor.CLONE_KEYWORDS)) {\n const copy = this.prev?.text.toLowerCase() === 'copy';\n clone = this.expression(CloneExpr, {\n this: this.parseTable({ schema: true }),\n shallow,\n copy,\n });\n }\n }\n\n if (\n this.curr\n && !this.matchSet(new Set([TokenType.R_PAREN, TokenType.COMMA]), { advance: false })\n ) {\n return this.parseAsCommand(start);\n }\n\n const createKindText = createToken.text.toUpperCase();\n const createKindRaw = this._dialectConstructor.CREATABLE_KIND_MAPPING[createKindText] || createKindText;\n return this.expression(CreateExpr, {\n this: thisExpr,\n kind: enumFromString(CreateExprKind, createKindRaw) ?? createKindRaw,\n replace,\n refresh,\n unique,\n expression,\n exists,\n properties,\n indexes,\n noSchemaBinding,\n begin,\n end,\n clone,\n concurrently,\n clustered,\n });\n }\n\n parseCommand (): CommandExpr {\n this.warnUnsupported();\n return this.expression(CommandExpr, {\n comments: this.prevComments,\n this: this.prev?.text.toUpperCase(),\n expression: this.parseString(),\n });\n }\n\n parseSequenceProperties (): SequencePropertiesExpr | undefined {\n const seq = new SequencePropertiesExpr({});\n\n const options: Expression[] = [];\n const index = this.index;\n\n while (this.curr) {\n this.match(TokenType.COMMA);\n if (this.matchTextSeq('INCREMENT')) {\n this.matchTextSeq('BY');\n this.matchTextSeq('=');\n seq.setArgKey('increment', this.parseTerm());\n } else if (this.matchTextSeq('MINVALUE')) {\n seq.setArgKey('minvalue', this.parseTerm());\n } else if (this.matchTextSeq('MAXVALUE')) {\n seq.setArgKey('maxvalue', this.parseTerm());\n } else if (this.match(TokenType.START_WITH) || this.matchTextSeq('START')) {\n this.matchTextSeq('=');\n seq.setArgKey('start', this.parseTerm());\n } else if (this.matchTextSeq('CACHE')) {\n // T-SQL allows empty CACHE which is initialized dynamically\n seq.setArgKey('cache', this.parseNumber() || true);\n } else if (this.matchTextSeq(['OWNED', 'BY'])) {\n // \"OWNED BY NONE\" is the default\n seq.setArgKey('owned', this.matchTextSeq('NONE') ? undefined : this.parseColumn());\n } else {\n const opt = this.parseVarFromOptions(this._constructor.CREATE_SEQUENCE, { raiseUnmatched: false });\n if (opt) {\n options.push(opt);\n } else {\n break;\n }\n }\n }\n\n seq.setArgKey('options', 0 < options.length ? options : undefined);\n return this.index === index ? undefined : seq;\n }\n\n parsePropertyBefore (): Expression | Expression[] | undefined {\n // only used for teradata currently\n this.match(TokenType.COMMA);\n\n const kwargs: Record<string, boolean | string> = {\n no: this.matchTextSeq('NO') || false,\n dual: this.matchTextSeq('DUAL') || false,\n before: this.matchTextSeq('BEFORE') || false,\n default: this.matchTextSeq('DEFAULT') || false,\n local: (this.matchTextSeq('LOCAL') && 'LOCAL')\n || (this.matchTextSeq(['NOT', 'LOCAL']) && 'NOT LOCAL')\n || false,\n after: this.matchTextSeq('AFTER') || false,\n minimum: this.matchTexts(['MIN', 'MINIMUM']) || false,\n maximum: this.matchTexts(['MAX', 'MAXIMUM']) || false,\n };\n\n if (this.matchTexts(Object.keys(this._constructor.PROPERTY_PARSERS))) {\n const parser = this._constructor.PROPERTY_PARSERS[(this.prev?.text ?? '').toUpperCase()];\n try {\n const filteredKwargs = Object.fromEntries(\n Object.entries(kwargs).filter(([, v]) => v),\n );\n const result = parser.call(this, filteredKwargs);\n return result ?? undefined;\n } catch (e) {\n if (e instanceof ParseError) {\n throw e;\n }\n this.raiseError(`Cannot parse property '${this.prev?.text ?? ''}'`);\n }\n }\n\n return undefined;\n }\n\n parseWrappedProperties (): Expression[] {\n return this.parseWrappedCsv(() => this.parseProperty() as Expression | undefined);\n }\n\n parseProperty (): Expression | Expression[] | undefined {\n if (this.matchTexts(Object.keys(this._constructor.PROPERTY_PARSERS))) {\n const result = this._constructor.PROPERTY_PARSERS[(this.prev?.text ?? '').toUpperCase()].call(this);\n return result ?? undefined;\n }\n\n if (this.match(TokenType.DEFAULT) && this.matchTexts(Object.keys(this._constructor.PROPERTY_PARSERS))) {\n const result = this._constructor.PROPERTY_PARSERS[(this.prev?.text ?? '').toUpperCase()].call(this, { default: true });\n return result ?? undefined;\n }\n\n if (this.matchTextSeq(['COMPOUND', 'SORTKEY'])) {\n return this.parseSortkey({ compound: true });\n }\n\n if (this.matchTextSeq(['SQL', 'SECURITY'])) {\n return this.expression(\n SqlSecurityPropertyExpr,\n { this: this.matchTexts(['DEFINER', 'INVOKER']) && (this.prev?.text ?? '').toUpperCase() },\n );\n }\n\n const index = this.index;\n\n const seqProps = this.parseSequenceProperties();\n if (seqProps) {\n return seqProps;\n }\n\n this.retreat(index);\n let key = this.parseColumn();\n\n if (!this.match(TokenType.EQ)) {\n this.retreat(index);\n return undefined;\n }\n\n // Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise\n if (key instanceof ColumnExpr) {\n key = (key.parts && 1 < key.parts.length) ? (key.toDot() || var_(key.name)) : var_(key.name);\n }\n\n let value = this.parseBitwise() || this.parseVar({ anyToken: true });\n\n // Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())\n if (value instanceof ColumnExpr) {\n value = var_(value.name);\n }\n\n return this.expression(PropertyExpr, {\n this: key,\n value,\n });\n }\n\n parseStored (): FileFormatPropertyExpr | StorageHandlerPropertyExpr {\n if (this.matchTextSeq('BY')) {\n return this.expression(StorageHandlerPropertyExpr, { this: this.parseVarOrString() });\n }\n\n this.match(TokenType.ALIAS);\n const inputFormat = (this.matchTextSeq('INPUTFORMAT') || undefined) && this.parseString();\n const outputFormat = (this.matchTextSeq('OUTPUTFORMAT') || undefined) && this.parseString();\n\n return this.expression(\n FileFormatPropertyExpr,\n {\n this: (\n inputFormat || outputFormat\n ? this.expression(InputOutputFormatExpr, {\n inputFormat,\n outputFormat,\n })\n : this.parseVarOrString() || this.parseNumber() || this.parseIdVar()\n ),\n hiveFormat: true,\n },\n );\n }\n\n parseUnquotedField (): Expression | undefined {\n const field = this.parseField();\n if (field instanceof IdentifierExpr && !field.args.quoted) {\n return var_(field);\n }\n\n return field;\n }\n\n parsePropertyAssignment<E extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n expClass: new (args: any) => E,\n kwargs?: Record<string, unknown>,\n ): E {\n this.match(TokenType.EQ);\n this.match(TokenType.ALIAS);\n\n return this.expression(expClass, {\n this: this.parseUnquotedField(),\n ...kwargs,\n });\n }\n\n parseProperties (options?: { before?: boolean }): PropertiesExpr | undefined {\n const properties: Expression[] = [];\n while (true) {\n const prop = options?.before\n ? this.parsePropertyBefore()\n : this.parseProperty();\n if (prop === undefined || (Array.isArray(prop) && prop.length === 0)) {\n break;\n }\n const list = Array.isArray(prop) ? prop : [prop];\n for (const p of list) {\n properties.push(p);\n }\n }\n\n if (0 < properties.length) {\n return this.expression(PropertiesExpr, { expressions: properties });\n }\n\n return undefined;\n }\n\n parseFallback (options?: { no?: boolean }): FallbackPropertyExpr {\n return this.expression(\n FallbackPropertyExpr,\n {\n no: options?.no,\n protection: this.matchTextSeq('PROTECTION'),\n },\n );\n }\n\n parseSecurity (): SecurityPropertyExpr | undefined {\n if (this.matchTexts([\n 'NONE',\n 'DEFINER',\n 'INVOKER',\n ])) {\n const securitySpecifier = this.prev?.text.toUpperCase();\n return this.expression(SecurityPropertyExpr, { this: securitySpecifier });\n }\n return undefined;\n }\n\n parseSettingsProperty (): SettingsPropertyExpr {\n return this.expression(\n SettingsPropertyExpr,\n { expressions: this.parseCsv(() => this.parseAssignment()) },\n );\n }\n\n parseVolatileProperty (): VolatilePropertyExpr | StabilityPropertyExpr {\n let preVolatileToken: Token | undefined;\n if (2 <= this.index) {\n preVolatileToken = this.tokens[this.index - 2];\n }\n\n if (preVolatileToken && this._constructor.PRE_VOLATILE_TOKENS.has(preVolatileToken.tokenType)) {\n return new VolatilePropertyExpr({});\n }\n\n return this.expression(StabilityPropertyExpr, { this: LiteralExpr.string('VOLATILE') });\n }\n\n parseRetentionPeriod (): VarExpr {\n // Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}\n const number = this.parseNumber();\n const numberStr = number ? `${number} ` : '';\n const unit = this.parseVar({ anyToken: true });\n return var_(`${numberStr}${unit}`);\n }\n\n parseSystemVersioningProperty (options: { with?: boolean } = {}): WithSystemVersioningPropertyExpr {\n const { with: with_ = false } = options;\n this.match(TokenType.EQ);\n const prop = this.expression(\n WithSystemVersioningPropertyExpr,\n {\n on: true,\n with: with_,\n },\n );\n\n if (this.matchTextSeq('OFF')) {\n prop.setArgKey('on', false);\n return prop;\n }\n\n this.match(TokenType.ON);\n if (this.match(TokenType.L_PAREN)) {\n while (this.curr && !this.match(TokenType.R_PAREN)) {\n if (this.matchTextSeq(['HISTORY_TABLE', '='])) {\n prop.setArgKey('this', this.parseTableParts());\n } else if (this.matchTextSeq(['DATA_CONSISTENCY_CHECK', '='])) {\n this.advance();\n prop.setArgKey('dataConsistency', this.prev?.text.toUpperCase());\n } else if (this.matchTextSeq(['HISTORY_RETENTION_PERIOD', '='])) {\n prop.setArgKey('retentionPeriod', this.parseRetentionPeriod());\n }\n\n this.match(TokenType.COMMA);\n }\n }\n\n return prop;\n }\n\n parseDataDeletionProperty (): DataDeletionPropertyExpr {\n this.match(TokenType.EQ);\n const on = this.matchTextSeq('ON') || !this.matchTextSeq('OFF');\n const prop = this.expression(DataDeletionPropertyExpr, { on });\n\n if (this.match(TokenType.L_PAREN)) {\n while (this.curr && !this.match(TokenType.R_PAREN)) {\n if (this.matchTextSeq(['FILTER_COLUMN', '='])) {\n prop.setArgKey('filterColumn', this.parseColumn());\n } else if (this.matchTextSeq(['RETENTION_PERIOD', '='])) {\n prop.setArgKey('retentionPeriod', this.parseRetentionPeriod());\n }\n\n this.match(TokenType.COMMA);\n }\n }\n\n return prop;\n }\n\n parseDistributedProperty (): DistributedByPropertyExpr {\n let kind = 'HASH';\n let expressions: Expression[] | undefined;\n if (this.matchTextSeq(['BY', 'HASH'])) {\n expressions = this.parseWrappedCsv(() => this.parseIdVar());\n } else if (this.matchTextSeq(['BY', 'RANDOM'])) {\n kind = 'RANDOM';\n }\n\n // If the BUCKETS keyword is not present, the number of buckets is AUTO\n let buckets: Expression | undefined;\n if (this.matchTextSeq('BUCKETS') && !this.matchTextSeq('AUTO')) {\n buckets = this.parseNumber();\n }\n\n return this.expression(\n DistributedByPropertyExpr,\n {\n expressions,\n kind,\n buckets,\n order: this.parseOrder(),\n },\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseCompositeKeyProperty<E extends Expression> (exprType: new (args: any) => E): E {\n this.matchTextSeq('KEY');\n const expressions = this.parseWrappedIdVars();\n return this.expression(exprType, { expressions });\n }\n\n parseWithProperty (): Expression | Expression[] | undefined {\n if (this.matchTextSeq(['(', 'SYSTEM_VERSIONING'])) {\n const prop = this.parseSystemVersioningProperty({ with: true });\n this.matchRParen();\n return prop;\n }\n\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n return this.parseWrappedProperties();\n }\n\n if (this.matchTextSeq('JOURNAL')) {\n return this.parseWithjournaltable();\n }\n\n if (this.matchTexts(Array.from(this._constructor.VIEW_ATTRIBUTES))) {\n return this.expression(ViewAttributePropertyExpr, { this: this.prev?.text.toUpperCase() });\n }\n\n if (this.matchTextSeq('DATA')) {\n return this.parseWithdata({ no: false });\n } else if (this.matchTextSeq(['NO', 'DATA'])) {\n return this.parseWithdata({ no: true });\n }\n\n if (this.match(TokenType.SERDE_PROPERTIES, { advance: false })) {\n return this.parseSerdeProperties({ with: true });\n }\n\n if (this.match(TokenType.SCHEMA)) {\n return this.expression(\n WithSchemaBindingPropertyExpr,\n { this: this.parseVarFromOptions(this._constructor.SCHEMA_BINDING_OPTIONS) },\n );\n }\n\n if (this.matchTexts(Object.keys(this._constructor.PROCEDURE_OPTIONS), { advance: false })) {\n return this.expression(\n WithProcedureOptionsExpr,\n { expressions: this.parseCsv(() => this.parseProcedureOption()) },\n );\n }\n\n if (!this.next) {\n return undefined;\n }\n\n return this.parseWithIsolatedLoading();\n }\n\n parseProcedureOption (): Expression | undefined {\n if (this.matchTextSeq(['EXECUTE', 'AS'])) {\n return this.expression(\n ExecuteAsPropertyExpr,\n {\n this: this.parseVarFromOptions(this._constructor.EXECUTE_AS_OPTIONS, { raiseUnmatched: false })\n || this.parseString(),\n },\n );\n }\n\n return this.parseVarFromOptions(this._constructor.PROCEDURE_OPTIONS);\n }\n\n // https://dev.mysql.com/doc/refman/8.0/en/create-view.html\n parseDefiner (): DefinerPropertyExpr | undefined {\n this.match(TokenType.EQ);\n\n const user = this.parseIdVar();\n this.match(TokenType.PARAMETER);\n const host = this.parseIdVar() || (this.match(TokenType.MOD) && this.prev?.text);\n\n if (!user || !host) {\n return undefined;\n }\n\n return new DefinerPropertyExpr({ this: `${user}@${host}` });\n }\n\n parseWithjournaltable (): WithJournalTablePropertyExpr {\n this.match(TokenType.TABLE);\n this.match(TokenType.EQ);\n return this.expression(WithJournalTablePropertyExpr, { this: this.parseTableParts() });\n }\n\n parseLog (options: { no?: boolean } = {}): LogPropertyExpr {\n const { no = false } = options;\n return this.expression(LogPropertyExpr, { no });\n }\n\n parseJournal (kwargs?: Record<string, unknown>): JournalPropertyExpr {\n return this.expression(JournalPropertyExpr, kwargs);\n }\n\n parseChecksum (): ChecksumPropertyExpr {\n this.match(TokenType.EQ);\n\n let on: boolean | undefined;\n if (this.match(TokenType.ON)) {\n on = true;\n } else if (this.matchTextSeq('OFF')) {\n on = false;\n }\n\n return this.expression(ChecksumPropertyExpr, {\n on,\n default: this.match(TokenType.DEFAULT),\n });\n }\n\n parseCluster (options: { wrapped?: boolean } = {}): ClusterExpr {\n const {\n wrapped = false,\n } = options;\n return this.expression(\n ClusterExpr,\n {\n expressions: wrapped\n ? this.parseWrappedCsv(() => this.parseOrdered())\n : this.parseCsv(() => this.parseOrdered()),\n },\n );\n }\n\n parseClusteredBy (): ClusteredByPropertyExpr {\n this.matchTextSeq('BY');\n\n this.matchLParen();\n const expressions = this.parseCsv(() => this.parseColumn());\n this.matchRParen();\n\n let sortedBy: Expression[] | undefined;\n if (this.matchTextSeq(['SORTED', 'BY'])) {\n this.matchLParen();\n sortedBy = this.parseCsv(() => this.parseOrdered());\n this.matchRParen();\n }\n\n this.match(TokenType.INTO);\n const buckets = this.parseNumber();\n this.matchTextSeq('BUCKETS');\n\n return this.expression(\n ClusteredByPropertyExpr,\n {\n expressions,\n sortedBy,\n buckets,\n },\n );\n }\n\n parseCopyProperty (): CopyGrantsPropertyExpr | undefined {\n if (!this.matchTextSeq('GRANTS')) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n return this.expression(CopyGrantsPropertyExpr, {});\n }\n\n parseFreespace (): FreespacePropertyExpr {\n this.match(TokenType.EQ);\n return this.expression(\n FreespacePropertyExpr,\n {\n this: this.parseNumber(),\n percent: this.match(TokenType.PERCENT),\n },\n );\n }\n\n parseMergeBlockRatio (options: {\n no?: boolean;\n default?: boolean;\n } = {}): MergeBlockRatioPropertyExpr {\n const {\n no = false,\n default: default_ = false,\n } = options;\n\n if (this.match(TokenType.EQ)) {\n return this.expression(\n MergeBlockRatioPropertyExpr,\n {\n this: this.parseNumber(),\n percent: this.match(TokenType.PERCENT),\n },\n );\n }\n\n return this.expression(MergeBlockRatioPropertyExpr, {\n no,\n default: default_,\n });\n }\n\n parseDataBlocksize (options: {\n default?: boolean;\n minimum?: boolean;\n maximum?: boolean;\n } = {}): DataBlocksizePropertyExpr {\n const {\n default: default_,\n minimum,\n maximum,\n } = options;\n this.match(TokenType.EQ);\n const size = this.parseNumber();\n\n let units: string | undefined;\n if (this.matchTexts([\n 'BYTES',\n 'KBYTES',\n 'KILOBYTES',\n ])) {\n units = this.prev?.text ?? '';\n }\n\n return this.expression(\n DataBlocksizePropertyExpr,\n {\n size,\n units,\n default: default_,\n minimum,\n maximum,\n },\n );\n }\n\n parseBlockCompression (): BlockCompressionPropertyExpr {\n this.match(TokenType.EQ);\n const always = this.matchTextSeq('ALWAYS') || undefined;\n const manual = this.matchTextSeq('MANUAL') || undefined;\n const never = this.matchTextSeq('NEVER') || undefined;\n const default_ = this.matchTextSeq('DEFAULT') || undefined;\n\n let autotemp: Expression | undefined;\n if (this.matchTextSeq('AUTOTEMP')) {\n autotemp = this.parseSchema();\n }\n\n return this.expression(\n BlockCompressionPropertyExpr,\n {\n always,\n manual,\n never,\n default: default_,\n autotemp,\n },\n );\n }\n\n parseWithIsolatedLoading (): IsolatedLoadingPropertyExpr | undefined {\n const index = this.index;\n const no = this.matchTextSeq('NO') || undefined;\n const concurrent = this.matchTextSeq('CONCURRENT') || undefined;\n\n if (!this.matchTextSeq(['ISOLATED', 'LOADING'])) {\n this.retreat(index);\n return undefined;\n }\n\n const target = this.parseVarFromOptions(this._constructor.ISOLATED_LOADING_OPTIONS, { raiseUnmatched: false });\n return this.expression(\n IsolatedLoadingPropertyExpr,\n {\n no,\n concurrent,\n target,\n },\n );\n }\n\n parseLocking (): LockingPropertyExpr {\n let kind: string | undefined;\n if (this.match(TokenType.TABLE)) {\n kind = 'TABLE';\n } else if (this.match(TokenType.VIEW)) {\n kind = 'VIEW';\n } else if (this.match(TokenType.ROW)) {\n kind = 'ROW';\n } else if (this.matchTextSeq('DATABASE')) {\n kind = 'DATABASE';\n }\n\n let thisExpr: Expression | undefined;\n if (kind === 'DATABASE' || kind === 'TABLE' || kind === 'VIEW') {\n thisExpr = this.parseTableParts();\n }\n\n let forOrIn: string | undefined;\n if (this.match(TokenType.FOR)) {\n forOrIn = 'FOR';\n } else if (this.match(TokenType.IN)) {\n forOrIn = 'IN';\n }\n\n let lockType: string | undefined;\n if (this.matchTextSeq('ACCESS')) {\n lockType = 'ACCESS';\n } else if (this.matchTexts(['EXCL', 'EXCLUSIVE'])) {\n lockType = 'EXCLUSIVE';\n } else if (this.matchTextSeq('SHARE')) {\n lockType = 'SHARE';\n } else if (this.matchTextSeq('READ')) {\n lockType = 'READ';\n } else if (this.matchTextSeq('WRITE')) {\n lockType = 'WRITE';\n } else if (this.matchTextSeq('CHECKSUM')) {\n lockType = 'CHECKSUM';\n }\n\n const override = this.matchTextSeq('OVERRIDE') || undefined;\n\n return this.expression(\n LockingPropertyExpr,\n {\n this: thisExpr,\n kind,\n forOrIn,\n lockType,\n override,\n },\n );\n }\n\n parsePartitionBy (): Expression[] {\n if (this.match(TokenType.PARTITION_BY)) {\n return this.parseCsv(() => this.parseDisjunction());\n }\n return [];\n }\n\n parsePartitionBoundSpec (): PartitionBoundSpecExpr {\n const parsePartitionBoundExpr = (): Expression | undefined => {\n if (this.matchTextSeq('MINVALUE')) {\n return var_('MINVALUE');\n }\n if (this.matchTextSeq('MAXVALUE')) {\n return var_('MAXVALUE');\n }\n return this.parseBitwise();\n };\n\n let thisExpr: Expression | Expression[] | undefined;\n let expression: Expression | undefined;\n let fromExpressions: Expression[] | undefined;\n let toExpressions: Expression[] | undefined;\n\n if (this.match(TokenType.IN)) {\n thisExpr = this.parseWrappedCsv(() => this.parseBitwise());\n } else if (this.match(TokenType.FROM)) {\n fromExpressions = this.parseWrappedCsv(parsePartitionBoundExpr);\n this.matchTextSeq('TO');\n toExpressions = this.parseWrappedCsv(parsePartitionBoundExpr);\n } else if (this.matchTextSeq([\n 'WITH',\n '(',\n 'MODULUS',\n ])) {\n thisExpr = this.parseNumber();\n this.matchTextSeq([',', 'REMAINDER']);\n expression = this.parseNumber();\n this.matchRParen();\n } else {\n this.raiseError('Failed to parse partition bound spec.');\n }\n\n return this.expression(\n PartitionBoundSpecExpr,\n {\n this: thisExpr,\n expression,\n fromExpressions,\n toExpressions,\n },\n );\n }\n\n // https://www.postgresql.org/docs/current/sql-createtable.html\n parsePartitionedOf (): PartitionedOfPropertyExpr | undefined {\n if (!this.matchTextSeq('OF')) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n const thisExpr = this.parseTable({ schema: true });\n\n let expression: VarExpr | PartitionBoundSpecExpr;\n if (this.match(TokenType.DEFAULT)) {\n expression = var_('DEFAULT');\n } else if (this.matchTextSeq(['FOR', 'VALUES'])) {\n expression = this.parsePartitionBoundSpec();\n } else {\n this.raiseError('Expecting either DEFAULT or FOR VALUES clause.');\n expression = var_('DEFAULT'); // fallback\n }\n\n return this.expression(PartitionedOfPropertyExpr, {\n this: thisExpr,\n expression,\n });\n }\n\n parsePartitionedBy (): PartitionedByPropertyExpr {\n this.match(TokenType.EQ);\n return this.expression(\n PartitionedByPropertyExpr,\n { this: this.parseSchema() || this.parseBracket(this.parseField()) },\n );\n }\n\n parseWithdata (options: { no?: boolean } = {}): WithDataPropertyExpr {\n let statistics: boolean | undefined;\n if (this.matchTextSeq(['AND', 'STATISTICS'])) {\n statistics = true;\n } else if (this.matchTextSeq([\n 'AND',\n 'NO',\n 'STATISTICS',\n ])) {\n statistics = false;\n }\n\n return this.expression(WithDataPropertyExpr, {\n no: options?.no,\n statistics,\n });\n }\n\n parseContainsProperty (): SqlReadWritePropertyExpr | undefined {\n if (this.matchTextSeq('SQL')) {\n return this.expression(SqlReadWritePropertyExpr, { this: 'CONTAINS SQL' });\n }\n return undefined;\n }\n\n parseModifiesProperty (): SqlReadWritePropertyExpr | undefined {\n if (this.matchTextSeq(['SQL', 'DATA'])) {\n return this.expression(SqlReadWritePropertyExpr, { this: 'MODIFIES SQL DATA' });\n }\n return undefined;\n }\n\n parseNoProperty (): Expression | undefined {\n if (this.matchTextSeq(['PRIMARY', 'INDEX'])) {\n return new NoPrimaryIndexPropertyExpr({});\n }\n if (this.matchTextSeq('SQL')) {\n return this.expression(SqlReadWritePropertyExpr, { this: 'NO SQL' });\n }\n return undefined;\n }\n\n parseOnProperty (): Expression | undefined {\n if (this.matchTextSeq([\n 'COMMIT',\n 'PRESERVE',\n 'ROWS',\n ])) {\n return new OnCommitPropertyExpr({});\n }\n if (this.matchTextSeq([\n 'COMMIT',\n 'DELETE',\n 'ROWS',\n ])) {\n return new OnCommitPropertyExpr({ delete: true });\n }\n return this.expression(OnPropertyExpr, { this: this.parseSchema({ this: this.parseIdVar() }) });\n }\n\n parseReadsProperty (): SqlReadWritePropertyExpr | undefined {\n if (this.matchTextSeq(['SQL', 'DATA'])) {\n return this.expression(SqlReadWritePropertyExpr, { this: 'READS SQL DATA' });\n }\n return undefined;\n }\n\n parseDistkey (): DistKeyPropertyExpr {\n return this.expression(DistKeyPropertyExpr, { this: this.parseWrapped(() => this.parseIdVar()) });\n }\n\n parseCreateLike (): LikePropertyExpr | undefined {\n const table = this.parseTable({ schema: true });\n\n const options: Expression[] = [];\n while (this.matchTexts(['INCLUDING', 'EXCLUDING'])) {\n const thisText = (this.prev?.text ?? '').toUpperCase();\n\n const idVar = this.parseIdVar();\n if (!idVar) {\n return undefined;\n }\n\n const thisId = idVar.args.this;\n options.push(\n this.expression(PropertyExpr, {\n this: thisText,\n value: var_(typeof thisId === 'string' ? thisId.toUpperCase() : ''),\n }),\n );\n }\n\n return this.expression(LikePropertyExpr, {\n this: table,\n expressions: options,\n });\n }\n\n parseSortkey (options: { compound?: boolean } = {}): SortKeyPropertyExpr {\n const idVars = this.parseWrappedIdVars();\n const expr = idVars.length === 1 ? idVars[0] : this.expression(TupleExpr, { expressions: idVars });\n return this.expression(\n SortKeyPropertyExpr,\n {\n this: expr,\n compound: options?.compound,\n },\n );\n }\n\n parseCharacterSet (options: { default?: boolean } = {}): CharacterSetPropertyExpr {\n this.match(TokenType.EQ);\n return this.expression(\n CharacterSetPropertyExpr,\n {\n this: this.parseVarOrString(),\n default: options?.default,\n },\n );\n }\n\n parseRemoteWithConnection (): RemoteWithConnectionModelPropertyExpr {\n this.matchTextSeq(['WITH', 'CONNECTION']);\n return this.expression(\n RemoteWithConnectionModelPropertyExpr,\n { this: this.parseTableParts() },\n );\n }\n\n parseReturns (): ReturnsPropertyExpr {\n let value: Expression | undefined;\n let null_: boolean | undefined;\n const isTable = this.match(TokenType.TABLE) || undefined;\n\n if (isTable) {\n if (this.match(TokenType.LT)) {\n value = this.expression(\n SchemaExpr,\n {\n this: 'TABLE',\n expressions: this.parseCsv(() => this.parseStructTypes()),\n },\n );\n if (!this.match(TokenType.GT)) {\n this.raiseError('Expecting >');\n }\n } else {\n value = this.parseSchema({ this: var_('TABLE') });\n }\n } else if (this.matchTextSeq([\n 'NULL',\n 'ON',\n 'NULL',\n 'INPUT',\n ])) {\n null_ = true;\n value = undefined;\n } else {\n value = this.parseTypes();\n }\n\n return this.expression(ReturnsPropertyExpr, {\n this: value,\n isTable,\n null: null_,\n });\n }\n\n parseDescribe (): DescribeExpr {\n const kind = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev?.text;\n let style = (this.matchTexts(Array.from(this._constructor.DESCRIBE_STYLES)) || undefined) && this.prev?.text.toUpperCase();\n if (this.match(TokenType.DOT)) {\n style = undefined;\n this.retreat(this.index - 2);\n }\n\n const format = this.match(TokenType.FORMAT, { advance: false }) ? this.parseProperty() : undefined;\n\n let thisExpr: Expression | undefined;\n if (this.matchSet(new Set(Object.keys(this._constructor.STATEMENT_PARSERS)) as Set<TokenType>, { advance: false })) {\n thisExpr = this.parseStatement();\n } else {\n thisExpr = this.parseTable({ schema: true });\n }\n\n const properties = this.parseProperties();\n const expressions = properties?.args.expressions;\n const partition = this.parsePartition();\n return this.expression(\n DescribeExpr,\n {\n this: thisExpr,\n style,\n kind,\n expressions,\n partition,\n format,\n asJson: this.matchTextSeq(['AS', 'JSON']),\n },\n );\n }\n\n parseMultitableInserts (comments?: string[]): MultitableInsertsExpr {\n const kind = (this.prev?.text ?? '').toUpperCase();\n const expressions: Expression[] = [];\n\n const parseConditionalInsert = (): ConditionalInsertExpr | undefined => {\n let expression: Expression | undefined;\n if (this.match(TokenType.WHEN)) {\n expression = this.parseDisjunction();\n this.match(TokenType.THEN);\n }\n\n const else_ = this.match(TokenType.ELSE) || undefined;\n\n if (!this.match(TokenType.INTO)) {\n return undefined;\n }\n\n return this.expression(\n ConditionalInsertExpr,\n {\n this: this.expression(\n InsertExpr,\n {\n this: this.parseTable({ schema: true }),\n expression: this.parseDerivedTableValues(),\n },\n ),\n expression,\n else: else_,\n },\n );\n };\n\n let expr = parseConditionalInsert();\n while (expr !== undefined) {\n expressions.push(expr);\n expr = parseConditionalInsert();\n }\n\n return this.expression(\n MultitableInsertsExpr,\n {\n kind,\n comments,\n expressions,\n source: this.parseTable(),\n },\n );\n }\n\n parseInsert (): InsertExpr | MultitableInsertsExpr {\n const comments: string[] = [];\n const hint = this.parseHint();\n const overwrite = this.match(TokenType.OVERWRITE) || undefined;\n const ignore = this.match(TokenType.IGNORE) || undefined;\n const local = this.matchTextSeq('LOCAL') || undefined;\n let alternative: string | undefined;\n let isFunction: boolean | undefined;\n\n let thisExpr: Expression | undefined;\n if (this.matchTextSeq('DIRECTORY')) {\n thisExpr = this.expression(\n DirectoryExpr,\n {\n this: this.parseVarOrString(),\n local,\n rowFormat: this.parseRowFormat({ matchRow: true }),\n },\n );\n } else {\n if (this.matchSet(new Set([TokenType.FIRST, TokenType.ALL]))) {\n comments.push(...ensureList(this.prevComments));\n return this.parseMultitableInserts(comments);\n }\n\n if (this.match(TokenType.OR)) {\n alternative = (this.matchTexts(Array.from(this._constructor.INSERT_ALTERNATIVES)) && this.prev?.text) || undefined;\n }\n\n this.match(TokenType.INTO);\n comments.push(...ensureList(this.prevComments));\n this.match(TokenType.TABLE);\n isFunction = this.match(TokenType.FUNCTION) || undefined;\n\n thisExpr = isFunction ? this.parseFunction() : this.parseInsertTable();\n }\n\n const returning = this.parseReturning(); // TSQL allows RETURNING before source\n\n return this.expression(\n InsertExpr,\n {\n comments,\n hint,\n isFunction,\n this: thisExpr,\n stored: this.matchTextSeq('STORED') && this.parseStored(),\n byName: this.matchTextSeq(['BY', 'NAME']),\n exists: this.parseExists(),\n where: this.matchPair(TokenType.REPLACE, TokenType.WHERE) && this.parseDisjunction(),\n partition: this.match(TokenType.PARTITION_BY) && this.parsePartitionedBy(),\n settings: this.matchTextSeq('SETTINGS') && this.parseSettingsProperty(),\n default: this.matchTextSeq(['DEFAULT', 'VALUES']),\n expression: this.parseDerivedTableValues() || this.parseDdlSelect(),\n conflict: this.parseOnConflict(),\n returning: returning || this.parseReturning(),\n overwrite,\n alternative,\n ignore,\n source: this.match(TokenType.TABLE) && this.parseTable(),\n },\n );\n }\n\n parseInsertTable (): Expression | undefined {\n const thisExpr = this.parseTable({\n schema: true,\n parsePartition: true,\n });\n if (thisExpr instanceof TableExpr && this.match(TokenType.ALIAS, { advance: false })) {\n thisExpr.setArgKey('alias', this.parseTableAlias());\n }\n return thisExpr;\n }\n\n parseKill (): KillExpr {\n const kind = this.matchTexts(['CONNECTION', 'QUERY']) ? var_(this.prev?.text ?? '') : undefined;\n\n return this.expression(\n KillExpr,\n {\n this: this.parsePrimary(),\n kind,\n },\n );\n }\n\n parseOnConflict (): OnConflictExpr | undefined {\n const conflict = this.matchTextSeq(['ON', 'CONFLICT']) || undefined;\n const duplicate = this.matchTextSeq([\n 'ON',\n 'DUPLICATE',\n 'KEY',\n ]) || undefined;\n\n if (!conflict && !duplicate) {\n return undefined;\n }\n\n let conflictKeys: Expression[] | undefined;\n let constraint: Expression | undefined;\n\n if (conflict) {\n if (this.matchTextSeq(['ON', 'CONSTRAINT'])) {\n constraint = this.parseIdVar();\n } else if (this.match(TokenType.L_PAREN)) {\n conflictKeys = this.parseCsv(() => this.parseIdVar());\n this.matchRParen();\n }\n }\n\n const indexPredicate = this.parseWhere();\n\n const action = this.parseVarFromOptions(this._constructor.CONFLICT_ACTIONS);\n let expressions: Expression[] | undefined;\n if (this.prev?.tokenType === TokenType.UPDATE) {\n this.match(TokenType.SET);\n expressions = this.parseCsv(() => this.parseEquality());\n }\n\n return this.expression(\n OnConflictExpr,\n {\n duplicate,\n expressions,\n action,\n conflictKeys,\n indexPredicate,\n constraint,\n where: this.parseWhere(),\n },\n );\n }\n\n parseReturning (): ReturningExpr | undefined {\n if (!this.match(TokenType.RETURNING)) {\n return undefined;\n }\n return this.expression(\n ReturningExpr,\n {\n expressions: this.parseCsv(() => this.parseExpression()),\n into: this.match(TokenType.INTO) && this.parseTablePart(),\n },\n );\n }\n\n parseRow (): RowFormatSerdePropertyExpr | RowFormatDelimitedPropertyExpr | undefined {\n if (!this.match(TokenType.FORMAT)) {\n return undefined;\n }\n return this.parseRowFormat();\n }\n\n parseSerdeProperties (options: { with?: boolean } = {}): SerdePropertiesExpr | undefined {\n const index = this.index;\n const with_ = options?.with || this.matchTextSeq('WITH');\n\n if (!this.match(TokenType.SERDE_PROPERTIES)) {\n this.retreat(index);\n return undefined;\n }\n return this.expression(\n SerdePropertiesExpr,\n {\n expressions: this.parseWrappedProperties(),\n with: with_,\n },\n );\n }\n\n parseRowFormat (options: {\n matchRow?: boolean;\n } = {}): RowFormatSerdePropertyExpr | RowFormatDelimitedPropertyExpr | undefined {\n if (options?.matchRow && !this.matchPair(TokenType.ROW, TokenType.FORMAT)) {\n return undefined;\n }\n\n if (this.matchTextSeq('SERDE')) {\n const thisExpr = this.parseString();\n\n const serdeProperties = this.parseSerdeProperties();\n\n return this.expression(\n RowFormatSerdePropertyExpr,\n {\n this: thisExpr,\n serdeProperties,\n },\n );\n }\n\n this.matchTextSeq('DELIMITED');\n\n const kwargs: Record<string, Expression | undefined> = {};\n\n if (this.matchTextSeq([\n 'FIELDS',\n 'TERMINATED',\n 'BY',\n ])) {\n kwargs.fields = this.parseString();\n if (this.matchTextSeq(['ESCAPED', 'BY'])) {\n kwargs.escaped = this.parseString();\n }\n }\n if (this.matchTextSeq([\n 'COLLECTION',\n 'ITEMS',\n 'TERMINATED',\n 'BY',\n ])) {\n kwargs.collectionItems = this.parseString();\n }\n if (this.matchTextSeq([\n 'MAP',\n 'KEYS',\n 'TERMINATED',\n 'BY',\n ])) {\n kwargs.mapKeys = this.parseString();\n }\n if (this.matchTextSeq([\n 'LINES',\n 'TERMINATED',\n 'BY',\n ])) {\n kwargs.lines = this.parseString();\n }\n if (this.matchTextSeq([\n 'NULL',\n 'DEFINED',\n 'AS',\n ])) {\n kwargs.null = this.parseString();\n }\n\n return this.expression(RowFormatDelimitedPropertyExpr, kwargs);\n }\n\n parseSelect (options: {\n nested?: boolean;\n table?: boolean;\n parseSubqueryAlias?: boolean;\n parseSetOperation?: boolean;\n consumePipe?: boolean;\n from?: FromExpr;\n } = {}): Expression | undefined {\n const {\n nested = false,\n table = false,\n parseSubqueryAlias = true,\n parseSetOperation = true,\n consumePipe = true,\n from,\n } = options;\n\n let query = this.parseSelectQuery({\n nested,\n table,\n parseSubqueryAlias,\n parseSetOperation,\n });\n\n if (consumePipe && this.match(TokenType.PIPE_GT, { advance: false })) {\n if (!query && from) {\n query = select('*').from(from);\n }\n if (query instanceof QueryExpr) {\n query = this.parsePipeSyntaxQuery(query);\n query = (query && table) ? (query as SelectExpr).subquery(undefined, { copy: false }) : query;\n }\n }\n\n return query;\n }\n\n parseSelectQuery (options: {\n nested?: boolean;\n table?: boolean;\n parseSubqueryAlias?: boolean;\n parseSetOperation?: boolean;\n } = {}): Expression | undefined {\n const {\n nested = false,\n table = false,\n parseSubqueryAlias = true,\n parseSetOperation = true,\n } = options;\n\n const cte = this.parseWith();\n let thisExpr: Expression | undefined;\n\n if (cte) {\n thisExpr = this.parseStatement();\n\n if (!thisExpr) {\n this.raiseError('Failed to parse any statement following CTE');\n return cte;\n }\n\n while (thisExpr instanceof SubqueryExpr && thisExpr.isWrapper) {\n thisExpr = thisExpr.args.this;\n }\n\n if (thisExpr?._constructor.availableArgs.has('with')) {\n thisExpr.setArgKey('with', cte);\n } else {\n this.raiseError(`${thisExpr?._constructor.key || 'Unknown expression'} does not support CTE`);\n thisExpr = cte;\n }\n\n return thisExpr;\n }\n\n // duckdb supports leading with FROM x\n let from: FromExpr | undefined;\n if (this.match(TokenType.FROM, { advance: false })) {\n from = this.parseFrom({\n joins: true,\n consumePipe: true,\n }) as FromExpr | undefined;\n }\n\n if (this.match(TokenType.SELECT)) {\n const comments = this.prevComments;\n\n const hint = this.parseHint();\n\n let all: boolean | undefined;\n let distinct: DistinctExpr | undefined;\n if (this.next && this.next.tokenType !== TokenType.DOT) {\n all = this.match(TokenType.ALL) || undefined;\n distinct = this.matchSet(this._constructor.DISTINCT_TOKENS) ? new DistinctExpr() : undefined;\n }\n\n const kind = (\n this.match(TokenType.ALIAS)\n && this.matchTexts(['STRUCT', 'VALUE'])\n && this.prev?.text.toUpperCase()\n ) || undefined;\n\n if (distinct) {\n distinct = this.expression(\n DistinctExpr,\n { on: this.match(TokenType.ON) ? this.parseValue({ values: false }) : undefined },\n );\n }\n\n if (all && distinct) {\n this.raiseError('Cannot specify both ALL and DISTINCT after SELECT');\n }\n\n const operationModifiers: Expression[] = [];\n while (this.curr && this.matchTexts(Array.from(this._constructor.OPERATION_MODIFIERS))) {\n operationModifiers.push(var_((this.prev?.text ?? '').toUpperCase()));\n }\n\n const limit = this.parseLimit(undefined, { top: true });\n const projections = this.parseProjections();\n\n thisExpr = this.expression(\n SelectExpr,\n {\n kind,\n hint,\n distinct,\n expressions: projections,\n limit,\n operationModifiers: 0 < operationModifiers.length ? operationModifiers : undefined,\n },\n );\n thisExpr.comments = comments;\n\n const into = this.parseInto();\n if (into) {\n thisExpr.setArgKey('into', into);\n }\n\n if (!from) {\n from = this.parseFrom() as FromExpr | undefined;\n }\n\n if (from) {\n thisExpr.setArgKey('from', from);\n }\n\n thisExpr = this.parseQueryModifiers(thisExpr) as SelectExpr;\n } else if ((table || nested) && this.match(TokenType.L_PAREN)) {\n thisExpr = this.parseWrappedSelect({ table });\n\n // We return early here so that the UNION isn't attached to the subquery by the\n // following call to _parse_set_operations, but instead becomes the parent node\n this.matchRParen();\n return this.parseSubquery(thisExpr, { parseAlias: parseSubqueryAlias });\n } else if (this.match(TokenType.VALUES, { advance: false })) {\n thisExpr = this.parseDerivedTableValues();\n } else if (from) {\n thisExpr = select('*').from(from.args.this, { copy: false });\n } else if (this.match(TokenType.SUMMARIZE)) {\n const table = this.match(TokenType.TABLE) || undefined;\n thisExpr = this.parseSelect() || this.parseString() || this.parseTable();\n return this.expression(SummarizeExpr, {\n this: thisExpr,\n table,\n });\n } else if (this.match(TokenType.DESCRIBE)) {\n return this.parseDescribe();\n } else {\n thisExpr = undefined;\n }\n\n return parseSetOperation ? this.parseSetOperations(thisExpr) : thisExpr;\n }\n\n parseRecursiveWithSearch (): RecursiveWithSearchExpr | undefined {\n this.matchTextSeq('SEARCH');\n\n const kind = (this.matchTexts(Array.from(this._constructor.RECURSIVE_CTE_SEARCH_KIND)) || undefined) && this.prev?.text.toUpperCase();\n\n if (!kind) {\n return undefined;\n }\n\n this.matchTextSeq(['FIRST', 'BY']);\n\n return this.expression(\n RecursiveWithSearchExpr,\n {\n kind,\n this: this.parseIdVar(),\n expression: this.matchTextSeq('SET') && this.parseIdVar(),\n using: this.matchTextSeq('USING') && this.parseIdVar(),\n },\n );\n }\n\n parseWith (options: { skipWithToken?: boolean } = {}): WithExpr | undefined {\n const {\n skipWithToken = false,\n } = options;\n if (!skipWithToken && !this.match(TokenType.WITH)) {\n return undefined;\n }\n\n const comments = this.prevComments;\n const recursive = this.match(TokenType.RECURSIVE) || undefined;\n\n let lastComments: string[] | undefined;\n const expressions: CteExpr[] = [];\n while (true) {\n const cte = this.parseCte();\n if (cte instanceof CteExpr) {\n expressions.push(cte);\n if (lastComments) {\n cte.addComments(lastComments);\n }\n }\n\n if (!this.match(TokenType.COMMA) && !this.match(TokenType.WITH)) {\n break;\n } else {\n this.match(TokenType.WITH);\n }\n\n lastComments = this.prevComments;\n }\n\n return this.expression(\n WithExpr,\n {\n comments,\n expressions,\n recursive,\n search: this.parseRecursiveWithSearch(),\n },\n );\n }\n\n parseCte (): CteExpr | undefined {\n const index = this.index;\n\n const aliasExpr = this.parseTableAlias({ aliasTokens: this._constructor.ID_VAR_TOKENS });\n if (!aliasExpr || !aliasExpr.args.this) {\n this.raiseError('Expected CTE to have alias');\n }\n\n const keyExpressions = this.matchTextSeq(['USING', 'KEY'])\n ? this.parseWrappedIdVars()\n : undefined;\n\n if (!this.match(TokenType.ALIAS) && !this._constructor.OPTIONAL_ALIAS_TOKEN_CTE) {\n this.retreat(index);\n return undefined;\n }\n\n const comments = this.prevComments;\n\n let materialized: boolean | undefined;\n if (this.matchTextSeq(['NOT', 'MATERIALIZED'])) {\n materialized = false;\n } else if (this.matchTextSeq('MATERIALIZED')) {\n materialized = true;\n }\n\n const cte = this.expression(\n CteExpr,\n {\n this: this.parseWrapped(() => this.parseStatement()),\n alias: aliasExpr,\n materialized,\n keyExpressions,\n comments,\n },\n );\n\n const values = cte.args.this;\n if (values instanceof ValuesExpr) {\n if (values.alias) {\n cte.setArgKey('this', select('*').from(values));\n } else {\n cte.setArgKey('this', select('*').from(\n alias(values, '_values', { table: true }),\n ));\n }\n }\n\n return cte;\n }\n\n parseTableAlias (options: {\n aliasTokens?: Set<TokenType>;\n } = {}): TableAliasExpr | undefined {\n const {\n aliasTokens,\n } = options;\n // In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)\n // so this section tries to parse the clause version and if it fails, it treats the token\n // as an identifier (alias)\n if (this.canParseLimitOrOffset()) {\n return undefined;\n }\n\n const anyToken = this.match(TokenType.ALIAS);\n const alias = (\n this.parseIdVar({\n anyToken,\n tokens: aliasTokens || this._constructor.TABLE_ALIAS_TOKENS,\n })\n || this.parseStringAsIdentifier()\n );\n\n const index = this.index;\n let columns: Expression[] | undefined;\n if (this.match(TokenType.L_PAREN)) {\n columns = this.parseCsv(() => this.parseFunctionParameter());\n if (!columns || columns.length === 0) {\n this.retreat(index);\n } else {\n this.matchRParen();\n }\n } else {\n columns = undefined;\n }\n\n if (!alias && (!columns || columns.length === 0)) {\n return undefined;\n }\n\n const tableAlias = this.expression(TableAliasExpr, {\n this: alias,\n columns,\n });\n\n // We bubble up comments from the Identifier to the TableAlias\n if (alias instanceof IdentifierExpr) {\n tableAlias.addComments(alias.popComments());\n }\n\n return tableAlias;\n }\n\n parseSubquery (\n thisExpr: Expression | undefined,\n options: { parseAlias?: boolean } = {},\n ): SubqueryExpr | undefined {\n const {\n parseAlias = true,\n } = options;\n if (!thisExpr) {\n return undefined;\n }\n\n return this.expression(\n SubqueryExpr,\n {\n this: thisExpr,\n pivots: this.parsePivots(),\n alias: parseAlias ? this.parseTableAlias() : undefined,\n sample: this.parseTableSample(),\n },\n );\n }\n\n protected implicitUnnestsToExplicit<E extends Expression> (thisExpr: E): E {\n const refs = new Set<string>();\n const args = thisExpr.args;\n\n // Add the FROM clause table to refs\n if ('from' in args) {\n const fromExpr = args['from'] as FromExpr;\n const fromExprThis = fromExpr?.args.this;\n if (fromExprThis) {\n const normalized = normalizeIdentifiers(fromExprThis.copy(), { dialect: this.dialect });\n refs.add(normalized.aliasOrName || '');\n }\n }\n\n // Process JOINs\n if ('joins' in args) {\n const joins: JoinExpr[] | undefined = args['joins'] ? filterInstanceOf(args['joins'] as Expression[], JoinExpr) : undefined;\n if (joins) {\n for (const join of joins) {\n const table = join.args.this;\n\n if (table instanceof TableExpr && !join.args.on) {\n // Normalize the table with maybeColumn meta flag\n const normalizedTable = table.copy();\n normalizedTable.meta['maybeColumn'] = true;\n const normalized = normalizeIdentifiers(normalizedTable, { dialect: this.dialect });\n\n // Check if the first part of the table name is in refs\n const parts = normalized.parts;\n if (parts && 0 < parts.length && refs.has(parts[0].name || '')) {\n const tableAsColumn = table.toColumn();\n if (tableAsColumn) {\n const unnest = new UnnestExpr({ expressions: [tableAsColumn] });\n\n // Convert Alias to TableAlias if needed\n if (table.args.alias instanceof TableAliasExpr && tableAsColumn.args.this instanceof Expression) {\n tableAsColumn.replace(tableAsColumn.args.this);\n const aliasThis = table.args.alias?.args.this;\n const aliasArgs = {\n table: aliasThis ? (typeof aliasThis === 'string' ? [aliasThis] : (assertIsInstanceOf(aliasThis, IdentifierExpr), [aliasThis])) : undefined,\n copy: false,\n };\n alias(unnest, undefined, aliasArgs);\n }\n\n table.replace(unnest);\n }\n }\n\n refs.add(normalized.aliasOrName || '');\n }\n }\n }\n }\n\n return thisExpr;\n }\n\n parseQueryModifiers<E extends Expression> (thisExpr: E): E;\n parseQueryModifiers (thisExpr: undefined): undefined;\n parseQueryModifiers<E extends Expression> (thisExpr: E | undefined): E | undefined;\n parseQueryModifiers (thisExpr: Expression | undefined): Expression | undefined {\n if (thisExpr && this._constructor.MODIFIABLES.some((cls) => thisExpr instanceof cls)) {\n for (const join of this.parseJoins()) {\n thisExpr.append('joins', join);\n }\n\n let lateral = this.parseLateral();\n while (lateral) {\n thisExpr.append('laterals', lateral);\n lateral = this.parseLateral();\n }\n\n while (true) {\n if (this.matchSet(new Set(Object.keys(this._constructor.QUERY_MODIFIER_PARSERS)) as Set<TokenType>, { advance: false })) {\n const modifierToken = this.curr as Token;\n const parser = this._constructor.QUERY_MODIFIER_PARSERS[modifierToken.tokenType];\n const [key, expression] = parser?.call(this) || [];\n\n if (key !== undefined && expression !== undefined) {\n if (thisExpr.getArgKey(key)) {\n this.raiseError(\n `Found multiple '${modifierToken.text.toUpperCase()}' clauses`,\n modifierToken,\n );\n }\n\n thisExpr.setArgKey(key, expression);\n if (key === 'limit') {\n const limitExpression = expression as LimitExpr;\n const offset = (limitExpression as LimitExpr).args.offset;\n limitExpression.setArgKey('offset', undefined);\n\n if (offset) {\n const offsetExpr = new OffsetExpr({ expression: offset });\n thisExpr.setArgKey('offset', offsetExpr);\n\n const limitByExpressions = (expression as LimitExpr).args.expressions;\n limitExpression.setArgKey('expressions', undefined);\n offsetExpr.setArgKey('expressions', limitByExpressions);\n }\n }\n continue;\n }\n }\n break;\n }\n }\n\n if (this._constructor.SUPPORTS_IMPLICIT_UNNEST && thisExpr && thisExpr.args.from) {\n thisExpr = this.implicitUnnestsToExplicit(thisExpr);\n }\n\n return thisExpr;\n }\n\n parseHintFallbackToString (): HintExpr | undefined {\n const start = this.curr;\n while (this.curr) {\n this.advance();\n }\n\n const end = this.tokens[this.index - 1];\n return new HintExpr({ expressions: [this.findSql(start, end)] });\n }\n\n parseHintFunctionCall (): Expression | undefined {\n return this.parseFunctionCall();\n }\n\n parseHintBody (): HintExpr | undefined {\n const startIndex = this.index;\n let shouldFallbackToString = false;\n\n const hints: Expression[] = [];\n try {\n let hintBatch = this.parseCsv(() =>\n this.parseHintFunctionCall() || this.parseVar({ upper: true }));\n while (0 < hintBatch.length) {\n hints.push(...hintBatch);\n hintBatch = this.parseCsv(() =>\n this.parseHintFunctionCall() || this.parseVar({ upper: true }));\n }\n } catch {\n shouldFallbackToString = true;\n }\n\n if (shouldFallbackToString || this.curr) {\n this.retreat(startIndex);\n return this.parseHintFallbackToString();\n }\n\n return this.expression(HintExpr, { expressions: hints });\n }\n\n parseHint (): HintExpr | undefined {\n if (this.match(TokenType.HINT) && this.prevComments && 0 < this.prevComments.length) {\n // Parse hint from comment\n return maybeParse(\n this.prevComments[0],\n {\n into: ExpressionKey.HINT,\n dialect: this.dialect,\n },\n ) as HintExpr;\n }\n\n return undefined;\n }\n\n parseInto (): IntoExpr | undefined {\n if (!this.match(TokenType.INTO)) {\n return undefined;\n }\n\n const temp = this.match(TokenType.TEMPORARY) || undefined;\n const unlogged = this.matchTextSeq('UNLOGGED') || undefined;\n this.match(TokenType.TABLE);\n\n return this.expression(\n IntoExpr,\n {\n this: this.parseTable({ schema: true }),\n temporary: temp,\n unlogged,\n },\n );\n }\n\n parseFrom (options: {\n joins?: boolean;\n skipFromToken?: boolean;\n consumePipe?: boolean;\n } = {}): FromExpr | undefined {\n const {\n joins = false,\n skipFromToken = false,\n consumePipe = false,\n } = options;\n if (!skipFromToken && !this.match(TokenType.FROM)) {\n return undefined;\n }\n\n return this.expression(\n FromExpr,\n {\n comments: this.prevComments,\n this: this.parseTable({\n joins,\n consumePipe,\n }),\n },\n );\n }\n\n parseMatchRecognizeMeasure (): MatchRecognizeMeasureExpr {\n return this.expression(\n MatchRecognizeMeasureExpr,\n {\n windowFrame: this.matchTexts(['FINAL', 'RUNNING']) && (this.prev?.text ?? '').toUpperCase(),\n this: this.parseExpression(),\n },\n );\n }\n\n parseMatchRecognize (): MatchRecognizeExpr | undefined {\n if (!this.match(TokenType.MATCH_RECOGNIZE)) {\n return undefined;\n }\n\n this.matchLParen();\n\n const partition = this.parsePartitionBy();\n const order = this.parseOrder();\n\n const measures = this.matchTextSeq('MEASURES')\n ? this.parseCsv(() => this.parseMatchRecognizeMeasure())\n : undefined;\n\n let rows: VarExpr | undefined;\n if (this.matchTextSeq([\n 'ONE',\n 'ROW',\n 'PER',\n 'MATCH',\n ])) {\n rows = var_('ONE ROW PER MATCH');\n } else if (this.matchTextSeq([\n 'ALL',\n 'ROWS',\n 'PER',\n 'MATCH',\n ])) {\n let text = 'ALL ROWS PER MATCH';\n if (this.matchTextSeq([\n 'SHOW',\n 'EMPTY',\n 'MATCHES',\n ])) {\n text += ' SHOW EMPTY MATCHES';\n } else if (this.matchTextSeq([\n 'OMIT',\n 'EMPTY',\n 'MATCHES',\n ])) {\n text += ' OMIT EMPTY MATCHES';\n } else if (this.matchTextSeq([\n 'WITH',\n 'UNMATCHED',\n 'ROWS',\n ])) {\n text += ' WITH UNMATCHED ROWS';\n }\n rows = var_(text);\n }\n\n let after: VarExpr | undefined;\n if (this.matchTextSeq([\n 'AFTER',\n 'MATCH',\n 'SKIP',\n ])) {\n let text = 'AFTER MATCH SKIP';\n if (this.matchTextSeq([\n 'PAST',\n 'LAST',\n 'ROW',\n ])) {\n text += ' PAST LAST ROW';\n } else if (this.matchTextSeq([\n 'TO',\n 'NEXT',\n 'ROW',\n ])) {\n text += ' TO NEXT ROW';\n } else if (this.matchTextSeq(['TO', 'FIRST'])) {\n this.advance();\n text += ` TO FIRST ${this.prev?.text}`;\n } else if (this.matchTextSeq(['TO', 'LAST'])) {\n this.advance();\n text += ` TO LAST ${this.prev?.text}`;\n }\n after = var_(text);\n }\n\n let pattern: VarExpr | undefined;\n if (this.matchTextSeq('PATTERN')) {\n this.matchLParen();\n\n if (!this.curr) {\n this.raiseError('Expecting )', this.curr);\n }\n\n let paren = 1;\n const start = this.curr;\n let end = this.prev;\n\n while (this.curr && 0 < paren) {\n if (this.curr.tokenType === TokenType.L_PAREN) {\n paren++;\n }\n if (this.curr.tokenType === TokenType.R_PAREN) {\n paren--;\n }\n\n end = this.prev as Token;\n this.advance();\n }\n\n if (0 < paren) {\n this.raiseError('Expecting )', this.curr);\n }\n\n pattern = var_(this.findSql(start, end));\n }\n\n const define = this.matchTextSeq('DEFINE')\n ? this.parseCsv(() => this.parseNameAsExpression())\n : undefined;\n\n this.matchRParen();\n\n return this.expression(\n MatchRecognizeExpr,\n {\n partitionBy: partition,\n order,\n measures,\n rows,\n after,\n pattern,\n define,\n alias: this.parseTableAlias(),\n },\n );\n }\n\n parseLateral (): LateralExpr | undefined {\n let crossApply: boolean | undefined = this.matchPair(TokenType.CROSS, TokenType.APPLY) || undefined;\n if (!crossApply && this.matchPair(TokenType.OUTER, TokenType.APPLY)) {\n crossApply = false;\n }\n\n let thisExpr: Expression | undefined;\n let view: boolean | undefined;\n let outer: boolean | undefined;\n\n if (crossApply !== undefined) {\n thisExpr = this.parseSelect({ table: true });\n view = undefined;\n outer = undefined;\n } else if (this.match(TokenType.LATERAL)) {\n thisExpr = this.parseSelect({ table: true });\n view = this.match(TokenType.VIEW) || undefined;\n outer = this.match(TokenType.OUTER) || undefined;\n } else {\n return undefined;\n }\n\n if (!thisExpr) {\n thisExpr = (\n this.parseUnnest()\n || this.parseFunction()\n || this.parseIdVar({ anyToken: false })\n );\n\n while (this.match(TokenType.DOT)) {\n const expression = this.parseFunction() || this.parseIdVar({ anyToken: false });\n thisExpr = new DotExpr({\n this: thisExpr,\n expression,\n });\n }\n }\n\n let ordinality: boolean | undefined;\n let tableAlias: TableAliasExpr | undefined;\n\n if (view) {\n const table = this.parseIdVar({ anyToken: false });\n const columns = this.match(TokenType.ALIAS)\n ? this.parseCsv(() => this.parseIdVar())\n : [];\n tableAlias = this.expression(\n TableAliasExpr,\n {\n this: table,\n columns,\n },\n );\n } else if ((thisExpr instanceof SubqueryExpr || thisExpr instanceof UnnestExpr) && thisExpr.alias) {\n // We move the alias from the lateral's child node to the lateral itself\n tableAlias = thisExpr.args.alias?.pop() as TableAliasExpr | undefined;\n } else {\n ordinality = this.matchPair(TokenType.WITH, TokenType.ORDINALITY) || undefined;\n tableAlias = this.parseTableAlias();\n }\n\n return this.expression(\n LateralExpr,\n {\n this: thisExpr,\n view,\n outer,\n alias: tableAlias,\n crossApply,\n ordinality,\n },\n );\n }\n\n parseStream (): StreamExpr | undefined {\n const index = this.index;\n if (this.matchTextSeq('STREAM')) {\n const thisExpr = this.tryParse(this.parseTable.bind(this));\n if (thisExpr) {\n return this.expression(StreamExpr, { this: thisExpr });\n }\n }\n\n this.retreat(index);\n return undefined;\n }\n\n parseJoinParts (): {\n method: Token | undefined;\n side: Token | undefined;\n kind: Token | undefined;\n } {\n return {\n method: this.matchSet(this._constructor.JOIN_METHODS) ? this.prev : undefined,\n side: this.matchSet(this._constructor.JOIN_SIDES) ? this.prev : undefined,\n kind: this.matchSet(this._constructor.JOIN_KINDS) ? this.prev : undefined,\n };\n }\n\n parseUsingIdentifiers (): Expression[] {\n const parseColumnAsIdentifier = (): Expression | undefined => {\n const thisExpr = this.parseColumn();\n if (thisExpr instanceof ColumnExpr) {\n return thisExpr.args.this instanceof Expression ? thisExpr.args.this : toIdentifier(thisExpr.args.this);\n }\n return thisExpr;\n };\n\n return this.parseWrappedCsv(parseColumnAsIdentifier, { optional: true });\n }\n\n parseJoin (options: {\n skipJoinToken?: boolean;\n parseBracket?: boolean;\n } = {}): JoinExpr | undefined {\n const {\n skipJoinToken = false,\n parseBracket = false,\n } = options;\n if (this.match(TokenType.COMMA)) {\n const table = this.tryParse(this.parseTable.bind(this));\n const crossJoin = table ? this.expression(JoinExpr, { this: table }) : undefined;\n\n if (crossJoin && this._constructor.JOINS_HAVE_EQUAL_PRECEDENCE) {\n crossJoin.setArgKey('kind', JoinExprKind.CROSS);\n }\n\n return crossJoin;\n }\n\n const index = this.index;\n let {\n method,\n side,\n kind,\n } = this.parseJoinParts();\n const directed = this.matchTextSeq('DIRECTED') || undefined;\n const hint = (this.matchTexts(this._constructor.JOIN_HINTS) || undefined) && this.prev?.text;\n const join = this.match(TokenType.JOIN) || (kind?.tokenType === TokenType.STRAIGHT_JOIN);\n const joinComments = this.prevComments;\n\n if (!skipJoinToken && !join) {\n this.retreat(index);\n kind = undefined;\n method = undefined;\n side = undefined;\n }\n\n const outerApply = this.matchPair(TokenType.OUTER, TokenType.APPLY, { advance: false }) || undefined;\n const crossApply = this.matchPair(TokenType.CROSS, TokenType.APPLY, { advance: false }) || undefined;\n\n if (!skipJoinToken && !join && !outerApply && !crossApply) {\n return undefined;\n }\n\n const kwargs: JoinExprArgs = {\n this: (outerApply || crossApply) ? this.parseLateral() : this.parseTable({ parseBracket }),\n };\n\n if (kind?.tokenType === TokenType.ARRAY && this.match(TokenType.COMMA)) {\n kwargs.expressions = this.parseCsv(() =>\n this.parseTable({ parseBracket }));\n }\n\n if (method) kwargs.method = method.text.toUpperCase();\n if (side) kwargs.side = enumFromString(JoinExprKind, side.text);\n if (kind) kwargs.kind = kind.tokenType === TokenType.STRAIGHT_JOIN ? JoinExprKind.STRAIGHT_JOIN : enumFromString(JoinExprKind, kind.text);\n if (hint) kwargs.hint = hint;\n\n if (this.match(TokenType.MATCH_CONDITION)) {\n kwargs.matchCondition = this.parseWrapped(() => this.parseComparison());\n }\n\n if (this.match(TokenType.ON)) {\n kwargs.on = this.parseDisjunction();\n } else if (this.match(TokenType.USING)) {\n kwargs.using = this.parseUsingIdentifiers();\n } else if (\n !method\n && !(outerApply || crossApply)\n && !(kwargs.this instanceof UnnestExpr)\n && !(kind?.tokenType === TokenType.CROSS || kind?.tokenType === TokenType.ARRAY)\n ) {\n const nestedIndex = this.index;\n const joins = [...this.parseJoins()];\n\n if (0 < joins.length && this.match(TokenType.ON)) {\n kwargs.on = this.parseDisjunction();\n } else if (0 < joins.length && this.match(TokenType.USING)) {\n kwargs.using = this.parseUsingIdentifiers();\n } else {\n this.retreat(nestedIndex);\n }\n\n if (0 < joins.length && (kwargs.on || kwargs.using)) {\n kwargs.this?.setArgKey('joins', joins);\n }\n }\n\n kwargs.pivots = this.parsePivots();\n\n const comments = [\n ...(joinComments || []),\n ...[\n method,\n side,\n kind,\n ].flatMap((token) => token?.comments || []),\n ];\n\n if (\n this._constructor.ADD_JOIN_ON_TRUE\n && !kwargs.on\n && !kwargs.using\n && !kwargs.method\n && (!kwargs.kind || kwargs.kind === JoinExprKind.INNER || kwargs.kind === JoinExprKind.OUTER)\n ) {\n kwargs.on = new BooleanExpr({ this: true });\n }\n\n if (directed) {\n kwargs.directed = directed;\n }\n\n return this.expression(JoinExpr, {\n comments,\n ...kwargs,\n });\n }\n\n parseOpClass (): Expression | undefined {\n const thisExpr = this.parseDisjunction();\n\n if (this.matchTexts(Array.from(this._constructor.OPCLASS_FOLLOW_KEYWORDS), { advance: false })) {\n return thisExpr;\n }\n\n if (!this.matchSet(this._constructor.OPTYPE_FOLLOW_TOKENS, { advance: false })) {\n return this.expression(OpclassExpr, {\n this: thisExpr,\n expression: this.parseTableParts(),\n });\n }\n\n return thisExpr;\n }\n\n parseIndexParams (): IndexParametersExpr {\n const using = this.match(TokenType.USING)\n ? this.parseVar({ anyToken: true })\n : undefined;\n\n const columns = this.match(TokenType.L_PAREN, { advance: false })\n ? this.parseWrappedCsv(() => this.parseWithOperator())\n : undefined;\n\n const include = this.matchTextSeq('INCLUDE')\n ? this.parseWrappedIdVars()\n : undefined;\n\n const partitionBy = this.parsePartitionBy();\n const withStorage = (this.match(TokenType.WITH) || undefined) && this.parseWrappedProperties();\n const tablespace = this.matchTextSeq([\n 'USING',\n 'INDEX',\n 'TABLESPACE',\n ])\n ? this.parseVar({ anyToken: true })\n : undefined;\n\n const where = this.parseWhere();\n const on = this.match(TokenType.ON) ? this.parseField() : undefined;\n\n return this.expression(\n IndexParametersExpr,\n {\n using,\n columns,\n include,\n partitionBy,\n where,\n withStorage,\n tablespace,\n on,\n },\n );\n }\n\n parseIndex (\n options: {\n index?: Expression;\n anonymous?: boolean;\n } = {},\n ): IndexExpr | undefined {\n const {\n anonymous = false,\n index: indexOpt,\n } = options;\n let index = indexOpt;\n let unique: boolean | undefined;\n let primary: boolean | undefined;\n let amp: boolean | undefined;\n let table: TableExpr | undefined;\n\n if (index || anonymous) {\n unique = undefined;\n primary = undefined;\n amp = undefined;\n\n this.match(TokenType.ON);\n this.match(TokenType.TABLE); // hive\n table = this.parseTableParts({ schema: true });\n } else {\n unique = this.match(TokenType.UNIQUE) || undefined;\n primary = this.matchTextSeq('PRIMARY') || undefined;\n amp = this.matchTextSeq('AMP') || undefined;\n\n if (!this.match(TokenType.INDEX)) {\n return undefined;\n }\n\n index = this.parseIdVar();\n table = undefined;\n }\n\n const params = this.parseIndexParams();\n\n return this.expression(\n IndexExpr,\n {\n this: index,\n table,\n unique,\n primary,\n amp,\n params,\n },\n );\n }\n\n parseTableHints (): Expression[] | undefined {\n const hints: Expression[] = [];\n\n if (this.matchPair(TokenType.WITH, TokenType.L_PAREN)) {\n // https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16\n hints.push(\n this.expression(\n WithTableHintExpr,\n {\n expressions: this.parseCsv(() =>\n this.parseFunction() || this.parseVar({ anyToken: true })),\n },\n ),\n );\n this.matchRParen();\n } else {\n // https://dev.mysql.com/doc/refman/8.0/en/index-hints.html\n while (this.matchSet(this._constructor.TABLE_INDEX_HINT_TOKENS)) {\n const hint = new IndexTableHintExpr({ this: (this.prev?.text ?? '').toUpperCase() });\n\n this.matchSet(new Set([TokenType.INDEX, TokenType.KEY]));\n if (this.match(TokenType.FOR)) {\n this.advanceAny();\n hint.setArgKey('target', (this.prev?.text ?? '').toUpperCase());\n }\n\n hint.setArgKey('expressions', this.parseWrappedIdVars());\n hints.push(hint);\n }\n }\n\n return 0 < hints.length ? hints : undefined;\n }\n\n parseTablePart (options: { schema?: boolean } = {}): Expression | undefined {\n const {\n schema = false,\n } = options;\n return (\n (!schema && this.parseFunction({ optionalParens: false }))\n || this.parseIdVar({ anyToken: false })\n || this.parseStringAsIdentifier()\n || this.parsePlaceholder()\n );\n }\n\n parseTableParts (options: {\n schema?: boolean;\n isDbReference?: boolean;\n wildcard?: boolean;\n } = {}): TableExpr {\n const {\n schema = false,\n isDbReference = false,\n wildcard = false,\n } = options;\n let catalog: Expression | string | undefined;\n let db: Expression | string | undefined;\n let table: Expression | string | undefined = this.parseTablePart({ schema });\n\n while (this.match(TokenType.DOT)) {\n if (catalog) {\n // This allows nesting the table in arbitrarily many dot expressions if needed\n table = this.expression(\n DotExpr,\n {\n this: table,\n expression: this.parseTablePart({ schema }),\n },\n );\n } else {\n catalog = db;\n db = table;\n // \"\" used for tsql FROM a..b case\n table = this.parseTablePart({ schema }) || '';\n }\n }\n\n if (\n wildcard\n && this.isConnected()\n && (table instanceof IdentifierExpr || !table)\n && this.match(TokenType.STAR)\n ) {\n if (table instanceof IdentifierExpr) {\n table.args.this += '*';\n } else {\n table = new IdentifierExpr({ this: '*' });\n }\n }\n\n // We bubble up comments from the Identifier to the Table\n const comments = table instanceof Expression ? table.popComments() : undefined;\n\n if (isDbReference) {\n catalog = db;\n db = table;\n table = undefined;\n }\n\n if (!table && !isDbReference) {\n this.raiseError(`Expected table name but got ${this.curr}`, this.curr);\n }\n if (!db && isDbReference) {\n this.raiseError(`Expected database name but got ${this.curr}`, this.curr);\n }\n\n const tableExpr = this.expression(\n TableExpr,\n {\n comments,\n this: table,\n db,\n catalog,\n },\n );\n\n const changes = this.parseChanges();\n if (changes) {\n tableExpr.setArgKey('changes', changes);\n }\n\n const atBefore = this.parseHistoricalData();\n if (atBefore) {\n tableExpr.setArgKey('when', atBefore);\n }\n\n const pivots = this.parsePivots();\n if (pivots) {\n tableExpr.setArgKey('pivots', pivots);\n }\n\n return tableExpr;\n }\n\n parseTable (options: {\n schema?: boolean;\n joins?: boolean;\n aliasTokens?: Set<TokenType>;\n parseBracket?: boolean;\n isDbReference?: boolean;\n parsePartition?: boolean;\n consumePipe?: boolean;\n } = {}): Expression | undefined {\n const {\n schema = false,\n joins = false,\n aliasTokens,\n parseBracket = false,\n isDbReference = false,\n parsePartition = false,\n consumePipe = false,\n } = options;\n const stream = this.parseStream();\n if (stream) {\n return stream;\n }\n\n const lateral = this.parseLateral();\n if (lateral) {\n return lateral;\n }\n\n const unnest = this.parseUnnest();\n if (unnest) {\n return unnest;\n }\n\n const values = this.parseDerivedTableValues();\n if (values) {\n return values;\n }\n\n const subquery = this.parseSelect({\n table: true,\n consumePipe,\n });\n if (subquery) {\n if (!(subquery as SelectExpr).args.pivots) {\n subquery.setArgKey('pivots', this.parsePivots());\n }\n return subquery;\n }\n\n let bracket = parseBracket && this.parseBracket(undefined);\n bracket = bracket ? this.expression(TableExpr, { this: bracket }) : undefined;\n\n let rowsFrom: Expression | Expression[] | undefined = (this.matchTextSeq(['ROWS', 'FROM']) || undefined) && this.parseWrappedCsv(() => this.parseTable());\n rowsFrom = rowsFrom ? this.expression(TableExpr, { rowsFrom }) : undefined;\n\n const only = this.match(TokenType.ONLY) || undefined;\n\n const thisExpr = (\n bracket\n || rowsFrom\n || this.parseBracket(\n this.parseTableParts({\n schema,\n isDbReference,\n }),\n )\n );\n\n if (only) {\n thisExpr?.setArgKey('only', only);\n }\n\n // Postgres supports a wildcard (table) suffix operator, which is a no-op in this context\n this.matchTextSeq('*');\n\n const shouldParsePartition = parsePartition || this._constructor.SUPPORTS_PARTITION_SELECTION;\n if (shouldParsePartition && this.match(TokenType.PARTITION, { advance: false })) {\n thisExpr?.setArgKey('partition', this.parsePartition());\n }\n\n if (schema) {\n return this.parseSchema({ this: thisExpr });\n }\n\n const version = this.parseVersion();\n if (version) {\n thisExpr?.setArgKey('version', version);\n }\n\n if (this._dialectConstructor.ALIAS_POST_TABLESAMPLE) {\n thisExpr?.setArgKey('sample', this.parseTableSample());\n }\n\n const alias = this.parseTableAlias({ aliasTokens: aliasTokens || this._constructor.TABLE_ALIAS_TOKENS });\n if (alias) {\n thisExpr?.setArgKey('alias', alias);\n }\n\n if (this.match(TokenType.INDEXED_BY)) {\n thisExpr?.setArgKey('indexed', this.parseTableParts());\n } else if (this.matchTextSeq(['NOT', 'INDEXED'])) {\n thisExpr?.setArgKey('indexed', false);\n }\n\n if (thisExpr instanceof TableExpr && this.matchTextSeq('AT')) {\n return this.expression(\n AtIndexExpr,\n {\n this: thisExpr.toColumn?.({ copy: false }),\n expression: this.parseIdVar(),\n },\n );\n }\n\n thisExpr?.setArgKey('hints', this.parseTableHints());\n\n if (!thisExpr?.args.pivots) {\n thisExpr?.setArgKey('pivots', this.parsePivots());\n }\n\n if (!this._dialectConstructor.ALIAS_POST_TABLESAMPLE) {\n thisExpr?.setArgKey('sample', this.parseTableSample());\n }\n\n if (joins) {\n for (const join of this.parseJoins()) {\n thisExpr?.append('joins', join);\n }\n }\n\n if (this.matchPair(TokenType.WITH, TokenType.ORDINALITY)) {\n thisExpr?.setArgKey('ordinality', true);\n thisExpr?.setArgKey('alias', this.parseTableAlias());\n }\n\n return thisExpr;\n }\n\n parseVersion (): VersionExpr | undefined {\n let thisText: string;\n if (this.match(TokenType.TIMESTAMP_SNAPSHOT)) {\n thisText = 'TIMESTAMP';\n } else if (this.match(TokenType.VERSION_SNAPSHOT)) {\n thisText = 'VERSION';\n } else {\n return undefined;\n }\n\n let kind: string;\n let expression: Expression | undefined;\n\n if (this.matchSet(new Set([TokenType.FROM, TokenType.BETWEEN]))) {\n kind = (this.prev?.text ?? '').toUpperCase();\n const start = this.parseBitwise();\n this.matchTexts(['TO', 'AND']);\n const end = this.parseBitwise();\n expression = this.expression(TupleExpr, { expressions: [start, end] });\n } else if (this.matchTextSeq(['CONTAINED', 'IN'])) {\n kind = 'CONTAINED IN';\n expression = this.expression(\n TupleExpr,\n { expressions: this.parseWrappedCsv(() => this.parseBitwise()) },\n );\n } else if (this.match(TokenType.ALL)) {\n kind = 'ALL';\n expression = undefined;\n } else {\n this.matchTextSeq(['AS', 'OF']);\n kind = 'AS OF';\n expression = this.parseType();\n }\n\n return this.expression(VersionExpr, {\n this: thisText,\n expression,\n kind,\n });\n }\n\n parseHistoricalData (): HistoricalDataExpr | undefined {\n // https://docs.snowflake.com/en/sql-reference/constructs/at-before\n const index = this.index;\n let historicalData: HistoricalDataExpr | undefined;\n\n if (this.matchTexts(Array.from(this._constructor.HISTORICAL_DATA_PREFIX))) {\n const thisText = (this.prev?.text ?? '').toUpperCase();\n const kind = (\n this.match(TokenType.L_PAREN)\n && this.matchTexts(Array.from(this._constructor.HISTORICAL_DATA_KIND))\n && (this.prev?.text ?? '').toUpperCase()\n );\n const expression = (this.match(TokenType.FARROW) || undefined) && this.parseBitwise();\n\n if (expression) {\n this.matchRParen();\n historicalData = this.expression(\n HistoricalDataExpr,\n {\n this: thisText,\n kind,\n expression,\n },\n );\n } else {\n this.retreat(index);\n }\n }\n\n return historicalData;\n }\n\n parseChanges (): ChangesExpr | undefined {\n if (!this.matchTextSeq([\n 'CHANGES',\n '(',\n 'INFORMATION',\n '=>',\n ])) {\n return undefined;\n }\n\n const information = this.parseVar({ anyToken: true });\n this.matchRParen();\n\n return this.expression(\n ChangesExpr,\n {\n information,\n atBefore: this.parseHistoricalData(),\n end: this.parseHistoricalData(),\n },\n );\n }\n\n parseUnnest (options: { withAlias?: boolean } = {}): UnnestExpr | undefined {\n const {\n withAlias = true,\n } = options;\n\n if (!this.matchPair(TokenType.UNNEST, TokenType.L_PAREN, { advance: false })) {\n return undefined;\n }\n\n this.advance();\n\n const expressions = this.parseWrappedCsv(() => this.parseEquality());\n let offset: Expression | boolean | undefined = this.matchPair(TokenType.WITH, TokenType.ORDINALITY) || undefined;\n\n const alias = withAlias ? this.parseTableAlias() : undefined;\n\n if (alias) {\n if (this._dialectConstructor.UNNEST_COLUMN_ONLY) {\n const columns = alias.args.columns;\n if (columns) {\n this.raiseError('Unexpected extra column alias in unnest.');\n }\n\n alias.setArgKey('columns', [alias.args.this!]);\n alias.setArgKey('this', undefined);\n }\n\n const columns = alias.args.columns as Expression[] | undefined;\n if (offset && columns && expressions.length < columns.length) {\n offset = columns.pop()!;\n }\n }\n\n if (!offset && this.matchPair(TokenType.WITH, TokenType.OFFSET)) {\n this.match(TokenType.ALIAS);\n offset = this.parseIdVar({\n anyToken: false,\n tokens: this._constructor.UNNEST_OFFSET_ALIAS_TOKENS,\n }) || toIdentifier('offset');\n }\n\n return this.expression(UnnestExpr, {\n expressions,\n alias,\n offset,\n });\n }\n\n parseDerivedTableValues (): ValuesExpr | undefined {\n const isDerived = this.matchPair(TokenType.L_PAREN, TokenType.VALUES) || undefined;\n if (!isDerived && !(\n // ClickHouse's `FORMAT Values` is equivalent to `VALUES`\n this.matchTextSeq('VALUES') || this.matchTextSeq(['FORMAT', 'VALUES'])\n )) {\n return undefined;\n }\n\n const expressions = this.parseCsv(() => this.parseValue());\n const alias = this.parseTableAlias();\n\n if (isDerived) {\n this.matchRParen();\n }\n\n return this.expression(\n ValuesExpr,\n {\n expressions,\n alias: alias || this.parseTableAlias(),\n },\n );\n }\n\n parseTableSample (options: { asModifier?: boolean } = {}): TableSampleExpr | undefined {\n const {\n asModifier = false,\n } = options;\n if (!this.match(TokenType.TABLE_SAMPLE) && !(\n asModifier && this.matchTextSeq(['USING', 'SAMPLE'])\n )) {\n return undefined;\n }\n\n let bucketNumerator: Expression | undefined;\n let bucketDenominator: Expression | undefined;\n let bucketField: Expression | undefined;\n let percent: Expression | undefined;\n let size: Expression | undefined;\n let seed: Expression | undefined;\n\n let method = this.parseVar({\n tokens: new Set([TokenType.ROW]),\n upper: true,\n });\n const matchedLParen = this.match(TokenType.L_PAREN) || undefined;\n\n let expressions: Expression[] | undefined;\n let num: Expression | undefined;\n\n if (this._constructor.TABLESAMPLE_CSV) {\n num = undefined;\n expressions = this.parseCsv(() => this.parsePrimary());\n } else {\n expressions = undefined;\n num = (\n this.match(TokenType.NUMBER, { advance: false })\n ? this.parseFactor()\n : this.parsePrimary() || this.parsePlaceholder()\n );\n }\n\n if (this.matchTextSeq('BUCKET')) {\n bucketNumerator = this.parseNumber();\n this.matchTextSeq(['OUT', 'OF']);\n bucketDenominator = this.parseNumber();\n this.match(TokenType.ON);\n bucketField = this.parseField();\n } else if (this.matchSet(new Set([TokenType.PERCENT, TokenType.MOD]))) {\n percent = num;\n } else if (this.match(TokenType.ROWS) || !this._dialectConstructor.TABLESAMPLE_SIZE_IS_PERCENT) {\n size = num;\n } else {\n percent = num;\n }\n\n if (matchedLParen) {\n this.matchRParen();\n }\n\n if (this.match(TokenType.L_PAREN)) {\n method = this.parseVar({ upper: true });\n seed = (this.match(TokenType.COMMA) || undefined) && this.parseNumber();\n this.matchRParen();\n } else if (this.matchTexts(['SEED', 'REPEATABLE'])) {\n seed = this.parseWrapped(() => this.parseNumber());\n }\n\n if (!method && this._constructor.DEFAULT_SAMPLING_METHOD) {\n method = var_(this._constructor.DEFAULT_SAMPLING_METHOD);\n }\n\n return this.expression(\n TableSampleExpr,\n {\n expressions,\n method,\n bucketNumerator,\n bucketDenominator,\n bucketField,\n percent,\n size,\n seed,\n },\n );\n }\n\n parsePivots (): PivotExpr[] | undefined {\n const pivots: PivotExpr[] = [];\n let pivot = this.parsePivot();\n while (pivot) {\n pivots.push(pivot);\n pivot = this.parsePivot();\n }\n return 0 < pivots.length ? pivots : undefined;\n }\n\n parseJoins (): JoinExpr[] {\n const joins: JoinExpr[] = [];\n let join = this.parseJoin();\n while (join) {\n joins.push(join);\n join = this.parseJoin();\n }\n return joins;\n }\n\n parseUnpivotColumns (): UnpivotColumnsExpr | undefined {\n if (!this.match(TokenType.INTO)) {\n return undefined;\n }\n\n return this.expression(\n UnpivotColumnsExpr,\n {\n this: this.matchTextSeq('NAME') && this.parseColumn(),\n expressions: this.matchTextSeq('VALUE') && this.parseCsv(() => this.parseColumn()),\n },\n );\n }\n\n // https://duckdb.org/docs/sql/statements/pivot\n parseSimplifiedPivot (options: { isUnpivot?: boolean } = {}): PivotExpr {\n const { isUnpivot } = options;\n const parseOn = (): Expression | undefined => {\n const thisExpr = this.parseBitwise();\n\n if (this.match(TokenType.IN)) {\n // PIVOT ... ON col IN (row_val1, row_val2)\n return this.parseIn(thisExpr);\n }\n if (this.match(TokenType.ALIAS, { advance: false })) {\n // UNPIVOT ... ON (col1, col2, col3) AS row_val\n return this.parseAlias(thisExpr);\n }\n\n return thisExpr;\n };\n\n const thisExpr = this.parseTable();\n const expressions = (this.match(TokenType.ON) || undefined) && this.parseCsv(parseOn);\n const into = this.parseUnpivotColumns();\n const using = (this.match(TokenType.USING) || undefined) && this.parseCsv(() =>\n this.parseAlias(this.parseColumn()));\n const group = this.parseGroup();\n\n return this.expression(\n PivotExpr,\n {\n this: thisExpr,\n expressions,\n using,\n group,\n unpivot: isUnpivot,\n into,\n },\n );\n }\n\n parsePivotIn (): InExpr {\n const parseAliasedExpression = (): Expression | undefined => {\n const thisExpr = this.parseSelectOrExpression();\n\n this.match(TokenType.ALIAS);\n const alias = this.parseBitwise();\n if (alias) {\n let aliasExpr = alias;\n if (alias instanceof ColumnExpr && !alias.args.db) {\n assertIsInstanceOf(alias.args.this, Expression);\n aliasExpr = alias.args.this;\n }\n return this.expression(PivotAliasExpr, {\n this: thisExpr,\n alias: aliasExpr,\n });\n }\n\n return thisExpr;\n };\n\n const value = this.parseColumn();\n\n if (!this.match(TokenType.IN)) {\n this.raiseError('Expecting IN', this.curr);\n }\n\n let exprs: Expression[];\n\n if (this.match(TokenType.L_PAREN)) {\n if (this.match(TokenType.ANY)) {\n exprs = [...ensureList<Expression>(new PivotAnyExpr({ this: this.parseOrder() }))];\n } else {\n exprs = this.parseCsv(parseAliasedExpression);\n }\n this.matchRParen();\n return this.expression(InExpr, {\n this: value,\n expressions: exprs,\n });\n }\n\n const field = this.parseIdVar();\n return this.expression(InExpr, {\n this: value,\n field,\n });\n }\n\n parsePivotAggregation (): Expression | undefined {\n const func = this.parseFunction();\n if (!func) {\n if (this.prev && this.prev.tokenType === TokenType.COMMA) {\n return undefined;\n }\n this.raiseError('Expecting an aggregation function in PIVOT', this.curr);\n }\n\n return this.parseAlias(func);\n }\n\n parsePivot (): PivotExpr | undefined {\n const index = this.index;\n let includeNulls: boolean | undefined;\n let unpivot: boolean;\n\n if (this.match(TokenType.PIVOT)) {\n unpivot = false;\n } else if (this.match(TokenType.UNPIVOT)) {\n unpivot = true;\n\n // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax\n if (this.matchTextSeq(['INCLUDE', 'NULLS'])) {\n includeNulls = true;\n } else if (this.matchTextSeq(['EXCLUDE', 'NULLS'])) {\n includeNulls = false;\n }\n } else {\n return undefined;\n }\n\n if (!this.match(TokenType.L_PAREN)) {\n this.retreat(index);\n return undefined;\n }\n\n let expressions: Expression[];\n if (unpivot) {\n expressions = this.parseCsv(() => this.parseColumn());\n } else {\n expressions = this.parseCsv(() => this.parsePivotAggregation());\n }\n\n if (expressions.length === 0) {\n this.raiseError('Failed to parse PIVOT\\'s aggregation list', this.curr);\n }\n\n if (!this.match(TokenType.FOR)) {\n this.raiseError('Expecting FOR', this.curr);\n }\n\n const fields: InExpr[] = [];\n while (true) {\n const field = this.tryParse(() => this.parsePivotIn());\n if (!field) {\n break;\n }\n fields.push(field);\n }\n\n const defaultOnNull = (this.matchTextSeq([\n 'DEFAULT',\n 'ON',\n 'NULL',\n ]) || undefined) && this.parseWrapped(() =>\n this.parseBitwise());\n\n const group = this.parseGroup();\n\n this.matchRParen();\n\n const pivot = this.expression(\n PivotExpr,\n {\n expressions,\n fields,\n unpivot,\n includeNulls,\n defaultOnNull,\n group,\n },\n );\n\n if (!this.matchSet(new Set([TokenType.PIVOT, TokenType.UNPIVOT]), { advance: false })) {\n pivot.setArgKey('alias', this.parseTableAlias());\n }\n\n if (!unpivot) {\n const names = this.pivotColumnNames(expressions);\n\n const columns: Expression[] = [];\n const allFields: string[][] = [];\n\n for (const pivotField of pivot.args.fields as InExpr[]) {\n const pivotFieldExpressions = pivotField.args.expressions;\n\n // The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.\n if (pivotFieldExpressions?.[0] instanceof PivotAnyExpr) {\n continue;\n }\n\n if (pivotFieldExpressions) {\n allFields.push(\n pivotFieldExpressions.map((fld: Expression) =>\n this._constructor.IDENTIFY_PIVOT_STRINGS ? fld.sql() : fld.aliasOrName || ''),\n );\n }\n }\n\n if (0 < allFields.length) {\n if (0 < names.length) {\n allFields.push(names);\n }\n\n // Generate all possible combinations of the pivot columns\n // e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))\n // generates the product between [[2000, 2010], ['NL', 'US'], ['total']]\n for (const fldPartsTuple of this.product(allFields)) {\n const fldParts = [...fldPartsTuple];\n\n if (0 < names.length && this._constructor.PREFIXED_PIVOT_COLUMNS) {\n // Move the \"name\" to the front of the list\n fldParts.unshift(fldParts.pop()!);\n }\n\n columns.push(toIdentifier(fldParts.join('_'))!);\n }\n }\n\n pivot.setArgKey('columns', columns);\n }\n\n return pivot;\n }\n\n protected pivotColumnNames (aggregations: Expression[]): string[] {\n return aggregations.map((agg) => agg.alias).filter((alias) => alias);\n }\n\n // Helper method for generating cartesian product (like Python's itertools.product)\n protected product<T>(arrays: T[][]): T[][] {\n if (arrays.length === 0) return [[]];\n if (arrays.length === 1) return arrays[0].map((item) => [item]);\n\n const result: T[][] = [];\n const [first, ...rest] = arrays;\n const restProduct = this.product(rest);\n\n for (const item of first) {\n for (const restItems of restProduct) {\n result.push([item, ...restItems]);\n }\n }\n\n return result;\n }\n\n parsePrewhere (options: { skipWhereToken?: boolean } = {}): PreWhereExpr | undefined {\n const {\n skipWhereToken = false,\n } = options;\n if (!skipWhereToken && !this.match(TokenType.PREWHERE)) {\n return undefined;\n }\n\n return this.expression(\n PreWhereExpr,\n {\n comments: this.prevComments,\n this: this.parseDisjunction(),\n },\n );\n }\n\n parseWhere (options: { skipWhereToken?: boolean } = {}): WhereExpr | undefined {\n const {\n skipWhereToken = false,\n } = options;\n if (!skipWhereToken && !this.match(TokenType.WHERE)) {\n return undefined;\n }\n\n return this.expression(\n WhereExpr,\n {\n comments: this.prevComments,\n this: this.parseDisjunction(),\n },\n );\n }\n\n parseGroup (options: { skipGroupByToken?: boolean } = {}): GroupExpr | undefined {\n const {\n skipGroupByToken = false,\n } = options;\n if (!skipGroupByToken && !this.match(TokenType.GROUP_BY)) {\n return undefined;\n }\n const comments = this.prevComments;\n\n const elements: {\n expressions: Expression[];\n rollup: RollupExpr[];\n cube: CubeExpr[];\n groupingSets: GroupingSetsExpr[];\n all?: boolean;\n totals?: boolean;\n } = {\n expressions: [],\n rollup: [],\n cube: [],\n groupingSets: [],\n };\n\n if (this.match(TokenType.ALL)) {\n elements.all = true;\n } else if (this.match(TokenType.DISTINCT)) {\n elements.all = false;\n }\n\n if (this.matchSet(this._constructor.QUERY_MODIFIER_TOKENS, { advance: false })) {\n return this.expression(GroupExpr, {\n comments,\n ...elements,\n });\n }\n\n while (true) {\n const index = this.index;\n\n elements.expressions.push(\n ...this.parseCsv(() =>\n this.matchSet(new Set([TokenType.CUBE, TokenType.ROLLUP]), { advance: false })\n ? undefined\n : this.parseDisjunction()),\n );\n\n const beforeWithIndex = this.index;\n const withPrefix = this.match(TokenType.WITH) || undefined;\n\n const cubeOrRollup = this.parseCubeOrRollup({ withPrefix });\n if (cubeOrRollup) {\n const key = cubeOrRollup instanceof RollupExpr ? 'rollup' : 'cube';\n elements[key].push(cubeOrRollup);\n } else {\n const groupingSets = this.parseGroupingSets();\n if (groupingSets) {\n elements.groupingSets.push(groupingSets);\n } else if (this.matchTextSeq('TOTALS')) {\n elements.totals = true;\n }\n }\n\n if (beforeWithIndex <= this.index && this.index <= beforeWithIndex + 1) {\n this.retreat(beforeWithIndex);\n break;\n }\n\n if (index === this.index) {\n break;\n }\n }\n\n return this.expression(GroupExpr, {\n comments,\n ...elements,\n });\n }\n\n parseCubeOrRollup (options: { withPrefix?: boolean } = {}): CubeExpr | RollupExpr | undefined {\n const {\n withPrefix = false,\n } = options;\n let kind: typeof CubeExpr | typeof RollupExpr;\n\n if (this.match(TokenType.CUBE)) {\n kind = CubeExpr;\n } else if (this.match(TokenType.ROLLUP)) {\n kind = RollupExpr;\n } else {\n return undefined;\n }\n\n return this.expression(\n kind,\n { expressions: withPrefix ? [] : this.parseWrappedCsv(() => this.parseBitwise()) },\n );\n }\n\n parseGroupingSets (): GroupingSetsExpr | undefined {\n if (this.match(TokenType.GROUPING_SETS)) {\n return this.expression(\n GroupingSetsExpr,\n { expressions: this.parseWrappedCsv(() => this.parseGroupingSet()) },\n );\n }\n return undefined;\n }\n\n parseGroupingSet (): Expression | undefined {\n return this.parseGroupingSets() || this.parseCubeOrRollup() || this.parseBitwise();\n }\n\n parseHaving (options: { skipHavingToken?: boolean } = {}): HavingExpr | undefined {\n const {\n skipHavingToken = false,\n } = options;\n if (!skipHavingToken && !this.match(TokenType.HAVING)) {\n return undefined;\n }\n return this.expression(\n HavingExpr,\n {\n comments: this.prevComments,\n this: this.parseDisjunction(),\n },\n );\n }\n\n parseQualify (): QualifyExpr | undefined {\n if (!this.match(TokenType.QUALIFY)) {\n return undefined;\n }\n return this.expression(QualifyExpr, { this: this.parseDisjunction() });\n }\n\n parseConnectWithPrior (): Expression | undefined {\n this._constructor.NO_PAREN_FUNCTION_PARSERS['PRIOR'] = function (this: Parser) {\n return this.expression(PriorExpr, { this: this.parseBitwise() });\n };\n const connect = this.parseDisjunction();\n delete this._constructor.NO_PAREN_FUNCTION_PARSERS['PRIOR'];\n return connect;\n }\n\n parseConnect (options: { skipStartToken?: boolean } = {}): ConnectExpr | undefined {\n const {\n skipStartToken = false,\n } = options;\n let start: Expression | undefined;\n\n if (skipStartToken) {\n start = undefined;\n } else if (this.match(TokenType.START_WITH)) {\n start = this.parseDisjunction();\n } else {\n return undefined;\n }\n\n this.match(TokenType.CONNECT_BY);\n const nocycle = this.matchTextSeq('NOCYCLE') || undefined;\n const connect = this.parseConnectWithPrior();\n\n if (!start && this.match(TokenType.START_WITH)) {\n start = this.parseDisjunction();\n }\n\n return this.expression(ConnectExpr, {\n start,\n connect,\n nocycle,\n });\n }\n\n parseNameAsExpression (): Expression | undefined {\n let thisExpr: Expression | undefined = this.parseIdVar({ anyToken: true });\n if (this.match(TokenType.ALIAS)) {\n thisExpr = this.expression(AliasExpr, {\n alias: thisExpr,\n this: this.parseDisjunction(),\n });\n }\n return thisExpr;\n }\n\n parseInterpolate (): Expression[] | undefined {\n if (this.matchTextSeq('INTERPOLATE')) {\n return this.parseWrappedCsv(() => this.parseNameAsExpression());\n }\n return undefined;\n }\n\n parseOrder (\n options: {\n thisExpr?: Expression;\n skipOrderToken?: boolean;\n } = {},\n ): OrderExpr | undefined {\n const {\n thisExpr, skipOrderToken = false,\n } = options;\n let siblings: boolean | undefined;\n\n if (!skipOrderToken && !this.match(TokenType.ORDER_BY)) {\n if (!this.match(TokenType.ORDER_SIBLINGS_BY)) {\n return thisExpr as OrderExpr | undefined;\n }\n\n siblings = true;\n }\n\n return this.expression(\n OrderExpr,\n {\n comments: this.prevComments,\n this: thisExpr,\n expressions: this.parseCsv(() => this.parseOrdered()),\n siblings,\n },\n );\n }\n\n parseSort<E extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n expClass: new (args: any) => E,\n token: TokenType,\n ): E | undefined {\n if (!this.match(token)) {\n return undefined;\n }\n return this.expression(expClass, { expressions: this.parseCsv(() => this.parseOrdered()) });\n }\n\n parseOrdered (parseMethod?: () => Expression | undefined): OrderedExpr | undefined {\n const thisExpr = parseMethod ? parseMethod() : this.parseDisjunction();\n if (!thisExpr) {\n return undefined;\n }\n\n let orderedThis = thisExpr;\n if (thisExpr.name?.toUpperCase() === 'ALL' && this._dialectConstructor.SUPPORTS_ORDER_BY_ALL) {\n orderedThis = var_('ALL');\n }\n\n const asc = this.match(TokenType.ASC) || undefined;\n const desc = (this.match(TokenType.DESC) || undefined) || (asc && false);\n\n const isNullsFirst = this.matchTextSeq(['NULLS', 'FIRST']) || undefined;\n const isNullsLast = this.matchTextSeq(['NULLS', 'LAST']) || undefined;\n\n let nullsFirst = isNullsFirst || false;\n const explicitlyNullOrdered = isNullsFirst || isNullsLast;\n\n if (\n !explicitlyNullOrdered\n && (\n (!desc && this._dialectConstructor.NULL_ORDERING === NullOrdering.NULLS_ARE_SMALL)\n || (desc && this._dialectConstructor.NULL_ORDERING !== NullOrdering.NULLS_ARE_SMALL)\n )\n && this._dialectConstructor.NULL_ORDERING !== NullOrdering.NULLS_ARE_LAST\n ) {\n nullsFirst = true;\n }\n\n let withFill: WithFillExpr | undefined;\n if (this.matchTextSeq(['WITH', 'FILL'])) {\n withFill = this.expression(\n WithFillExpr,\n {\n fromValue: this.match(TokenType.FROM) && this.parseBitwise(),\n to: this.matchTextSeq('TO') && this.parseBitwise(),\n step: this.matchTextSeq('STEP') && this.parseBitwise(),\n interpolate: this.parseInterpolate(),\n },\n );\n }\n\n return this.expression(\n OrderedExpr,\n {\n this: orderedThis,\n desc,\n nullsFirst,\n withFill,\n },\n );\n }\n\n parseLimitOptions (): LimitOptionsExpr | undefined {\n const percent = this.matchSet(new Set([TokenType.PERCENT, TokenType.MOD])) || undefined;\n const rows = this.matchSet(new Set([TokenType.ROW, TokenType.ROWS])) || undefined;\n this.matchTextSeq('ONLY');\n const withTies = this.matchTextSeq(['WITH', 'TIES']) || undefined;\n\n if (!(percent || rows || withTies)) {\n return undefined;\n }\n\n return this.expression(LimitOptionsExpr, {\n percent,\n rows,\n withTies,\n });\n }\n\n parseLimit (\n thisExpr?: Expression,\n options: {\n top?: boolean;\n skipLimitToken?: boolean;\n } = {},\n ): Expression | undefined {\n const {\n top = false,\n skipLimitToken = false,\n } = options;\n if (skipLimitToken || this.match(top ? TokenType.TOP : TokenType.LIMIT)) {\n const comments = this.prevComments;\n let expression: Expression | undefined;\n\n if (top) {\n const limitParen = this.match(TokenType.L_PAREN) || undefined;\n expression = limitParen ? this.parseTerm() : this.parseNumber();\n\n if (limitParen) {\n this.matchRParen();\n }\n } else {\n // Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since\n // we try to build an exp.Mod expr. For that matter, we backtrack and instead\n // consume the factor plus parse the percentage separately\n const index = this.index;\n expression = this.tryParse(() => this.parseTerm());\n if (expression instanceof ModExpr) {\n this.retreat(index);\n expression = this.parseFactor();\n } else if (!expression) {\n expression = this.parseFactor();\n }\n }\n\n const limitOptions = this.parseLimitOptions();\n\n let offset: Expression | undefined;\n if (this.match(TokenType.COMMA)) {\n offset = expression;\n expression = this.parseTerm();\n }\n\n const limitExp = this.expression(\n LimitExpr,\n {\n this: thisExpr,\n expression,\n offset,\n comments,\n limitOptions,\n expressions: this.parseLimitBy(),\n },\n );\n\n return limitExp;\n }\n\n if (this.match(TokenType.FETCH)) {\n const direction = this.matchSet(new Set([TokenType.FIRST, TokenType.NEXT])) || undefined;\n const directionText = direction ? (this.prev?.text ?? '').toUpperCase() : 'FIRST';\n\n const count = this.parseField({ tokens: this._constructor.FETCH_TOKENS });\n\n return this.expression(\n FetchExpr,\n {\n direction: directionText,\n count,\n limitOptions: this.parseLimitOptions(),\n },\n );\n }\n\n return thisExpr;\n }\n\n parseOffset (thisExpr?: Expression): Expression | undefined {\n if (!this.match(TokenType.OFFSET)) {\n return thisExpr;\n }\n\n const count = this.parseTerm();\n this.matchSet(new Set([TokenType.ROW, TokenType.ROWS]));\n\n return this.expression(\n OffsetExpr,\n {\n this: thisExpr,\n expression: count,\n expressions: this.parseLimitBy(),\n },\n );\n }\n\n canParseLimitOrOffset (): boolean {\n if (!this.matchSet(new Set(this._constructor.AMBIGUOUS_ALIAS_TOKENS), { advance: false })) {\n return false;\n }\n\n const index = this.index;\n const result = !!(\n this.tryParse(() => this.parseLimit(), { retreat: true })\n || this.tryParse(() => this.parseOffset(), { retreat: true })\n );\n this.retreat(index);\n\n // MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset\n if (this.next && this.next.tokenType === TokenType.MATCH_CONDITION) {\n return false;\n }\n\n return result;\n }\n\n parseLimitBy (): Expression[] | undefined {\n return this.matchTextSeq('BY') ? this.parseCsv(() => this.parseBitwise()) : undefined;\n }\n\n parseLocks (): LockExpr[] {\n const locks: LockExpr[] = [];\n\n while (true) {\n let update: boolean | undefined;\n let key: boolean | undefined;\n\n if (this.matchTextSeq(['FOR', 'UPDATE'])) {\n update = true;\n } else if (this.matchTextSeq(['FOR', 'SHARE']) || this.matchTextSeq([\n 'LOCK',\n 'IN',\n 'SHARE',\n 'MODE',\n ])) {\n update = false;\n } else if (this.matchTextSeq([\n 'FOR',\n 'KEY',\n 'SHARE',\n ])) {\n update = false;\n key = true;\n } else if (this.matchTextSeq([\n 'FOR',\n 'NO',\n 'KEY',\n 'UPDATE',\n ])) {\n update = true;\n key = true;\n } else {\n break;\n }\n\n let expressions: Expression[] | undefined;\n if (this.matchTextSeq('OF')) {\n expressions = this.parseCsv(() => this.parseTable({ schema: true }));\n }\n\n let wait: boolean | Expression | undefined;\n if (this.matchTextSeq('NOWAIT')) {\n wait = true;\n } else if (this.matchTextSeq('WAIT')) {\n wait = this.parsePrimary();\n } else if (this.matchTextSeq(['SKIP', 'LOCKED'])) {\n wait = false;\n }\n\n locks.push(\n this.expression(\n LockExpr,\n {\n update,\n expressions,\n wait,\n key,\n },\n ),\n );\n }\n\n return locks;\n }\n\n protected parseSetOperation (\n thisExpr?: Expression,\n options: { consumePipe?: boolean } = {},\n ): Expression | undefined {\n const {\n consumePipe = false,\n } = options;\n const start = this.index;\n const {\n side: sideToken,\n kind: kindToken,\n } = this.parseJoinParts();\n\n const side = enumFromString(JoinExprKind, sideToken?.text);\n let kind = enumFromString(SetOperationExprKind, kindToken?.text);\n\n if (!this.matchSet(this._constructor.SET_OPERATIONS)) {\n this.retreat(start);\n return undefined;\n }\n\n const tokenType = this.prev?.tokenType;\n\n let operation: typeof Expression;\n if (tokenType === TokenType.UNION) {\n operation = UnionExpr;\n } else if (tokenType === TokenType.EXCEPT) {\n operation = ExceptExpr;\n } else {\n operation = IntersectExpr;\n }\n\n const comments = this.prev?.comments;\n\n let distinct: boolean | undefined;\n if (this.match(TokenType.DISTINCT)) {\n distinct = true;\n } else if (this.match(TokenType.ALL)) {\n distinct = false;\n } else {\n distinct = this._dialectConstructor.SET_OP_DISTINCT_BY_DEFAULT[operation.key];\n if (distinct === undefined) {\n this.raiseError(`Expected DISTINCT or ALL for ${operation.name}`, this.curr);\n }\n }\n\n let byName = this.matchTextSeq(['BY', 'NAME']) || this.matchTextSeq(['STRICT', 'CORRESPONDING']) || undefined;\n if (this.matchTextSeq('CORRESPONDING')) {\n byName = true;\n if (!side && !kind) {\n // Set default kind\n kind = SetOperationExprKind.INNER; // Uncomment if needed\n }\n }\n\n let onColumnList: Expression[] | undefined;\n if (byName && this.matchTexts(['ON', 'BY'])) {\n onColumnList = this.parseWrappedCsv(() => this.parseColumn());\n }\n\n const expression = this.parseSelect({\n nested: true,\n parseSetOperation: false,\n consumePipe,\n });\n\n return this.expression(\n operation,\n {\n comments,\n this: thisExpr,\n distinct,\n byName,\n expression,\n side,\n kind,\n on: onColumnList,\n },\n );\n }\n\n parseSetOperations (thisExpr?: Expression): Expression | undefined {\n let current = thisExpr;\n\n while (current) {\n const setop = this.parseSetOperation(current);\n if (!setop) {\n break;\n }\n current = setop;\n }\n\n if (current instanceof SetOperationExpr && this._constructor.MODIFIERS_ATTACHED_TO_SET_OP) {\n const expression = current.args.expression as Expression | undefined;\n\n if (expression) {\n for (const arg of this._constructor.SET_OP_MODIFIERS) {\n const expr = expression.args[arg as keyof typeof expression.args];\n if (expr instanceof Expression) {\n current.setArgKey(arg, expr.pop());\n }\n }\n }\n }\n\n return current;\n }\n\n parseExpression (): Expression | undefined {\n return this.parseAlias(this.parseAssignment());\n }\n\n parseAssignment (): Expression | undefined {\n let thisExpr: ExpressionValue | undefined = this.parseDisjunction();\n\n if (!thisExpr && this.next && this.next.tokenType in this._constructor.ASSIGNMENT) {\n // This allows us to parse <non-identifier token> := <expr>\n const token = this.advanceAny({ ignoreReserved: true });\n thisExpr = column({ col: token && this.prev?.text });\n }\n\n const assignmentTokens = Object.keys(this._constructor.ASSIGNMENT) as TokenType[];\n while (this.matchSet(assignmentTokens)) {\n if (thisExpr instanceof ColumnExpr && thisExpr.parts.length === 1) {\n thisExpr = thisExpr.args.this;\n }\n\n thisExpr = this.expression(\n this._constructor.ASSIGNMENT[this.prev!.tokenType]!,\n {\n this: thisExpr,\n comments: this.prevComments,\n expression: this.parseAssignment(),\n },\n );\n }\n return thisExpr as Expression | undefined;\n }\n\n parseDisjunction (): Expression | undefined {\n return this.parseTokens(() => this.parseConjunction(), this._constructor.DISJUNCTION);\n }\n\n parseConjunction (): Expression | undefined {\n return this.parseTokens(() => this.parseEquality(), this._constructor.CONJUNCTION);\n }\n\n parseEquality (): Expression | undefined {\n return this.parseTokens(() => this.parseComparison(), this._constructor.EQUALITY);\n }\n\n parseComparison (): Expression | undefined {\n return this.parseTokens(() => this.parseRange(), this._constructor.COMPARISON);\n }\n\n parseRange (thisExpr?: Expression): Expression | undefined {\n let current = thisExpr || this.parseBitwise();\n const negate = this.match(TokenType.NOT) || undefined;\n\n if (this.matchSet(Object.keys(this._constructor.RANGE_PARSERS) as TokenType[])) {\n const parser = this._constructor.RANGE_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN];\n if (parser && current) {\n const expression = parser.call(this, current);\n if (!expression) {\n return current;\n }\n current = expression;\n }\n } else if (this.match(TokenType.ISNULL) || (negate && this.match(TokenType.NULL))) {\n current = this.expression(IsExpr, {\n this: current,\n expression: null_(),\n });\n }\n\n // Postgres supports ISNULL and NOTNULL for conditions.\n // https://blog.andreiavram.ro/postgresql-null-composite-type/\n if (this.match(TokenType.NOTNULL)) {\n current = this.expression(IsExpr, {\n this: current,\n expression: null_(),\n });\n current = this.expression(NotExpr, { this: current });\n }\n\n if (negate) {\n current = this.negateRange(current);\n }\n\n if (this.match(TokenType.IS)) {\n current = this.parseIs(current);\n }\n\n return current;\n }\n\n protected negateRange (thisExpr?: Expression): Expression | undefined {\n if (!thisExpr) {\n return thisExpr;\n }\n\n return this.expression(NotExpr, { this: thisExpr });\n }\n\n parseIs (thisExpr?: Expression): Expression | undefined {\n const index = this.index - 1;\n const negate = this.match(TokenType.NOT) || undefined;\n\n if (this.matchTextSeq(['DISTINCT', 'FROM'])) {\n const klass = negate ? NullSafeEqExpr : NullSafeNeqExpr;\n return this.expression(klass, {\n this: thisExpr,\n expression: this.parseBitwise(),\n });\n }\n\n let expression: Expression | undefined;\n if (this.match(TokenType.JSON)) {\n const kind = (this.matchTexts(Array.from(this._constructor.IS_JSON_PREDICATE_KIND)) || undefined) && (this.prev?.text ?? '').toUpperCase();\n\n let with_: boolean | undefined;\n if (this.matchTextSeq('WITH')) {\n with_ = true;\n } else if (this.matchTextSeq('WITHOUT')) {\n with_ = false;\n }\n\n const unique = this.match(TokenType.UNIQUE) || undefined;\n this.matchTextSeq('KEYS');\n\n expression = this.expression(\n JsonExpr,\n {\n this: kind,\n with: with_,\n unique,\n },\n );\n } else {\n expression = this.parseNull() || this.parseBitwise();\n if (!expression) {\n this.retreat(index);\n return undefined;\n }\n }\n\n const result = this.expression(IsExpr, {\n this: thisExpr,\n expression,\n });\n if (negate) {\n return this.expression(NotExpr, { this: result });\n }\n return this.parseColumnOps(result);\n }\n\n parseIn (thisExpr?: Expression, options: {\n alias?: boolean;\n [index: string]: unknown;\n } = {}): InExpr {\n const {\n alias = false,\n } = options;\n const unnest = this.parseUnnest({ withAlias: false });\n let result: InExpr;\n\n if (unnest) {\n result = this.expression(InExpr, {\n this: thisExpr,\n unnest,\n });\n } else if (this.matchSet(new Set([TokenType.L_PAREN, TokenType.L_BRACKET]))) {\n const matchedLParen = this.prev?.tokenType === TokenType.L_PAREN;\n const expressions = this.parseCsv(() => this.parseSelectOrExpression({ alias }));\n\n if (expressions.length === 1 && expressions[0] instanceof QueryExpr) {\n const query = expressions[0];\n const queryModifiers = this.parseQueryModifiers(query);\n const subquery = queryModifiers.subquery(undefined, { copy: false });\n result = this.expression(\n InExpr,\n {\n this: thisExpr,\n query: subquery,\n },\n );\n } else {\n result = this.expression(InExpr, {\n this: thisExpr,\n expressions,\n });\n }\n\n if (matchedLParen) {\n this.matchRParen(result);\n } else if (!this.match(TokenType.R_BRACKET, { expression: result })) {\n this.raiseError('Expecting ]', this.curr);\n }\n } else {\n result = this.expression(InExpr, {\n this: thisExpr,\n field: this.parseColumn(),\n });\n }\n\n return result;\n }\n\n parseBetween (thisExpr?: Expression): BetweenExpr {\n let symmetric: boolean | undefined;\n if (this.matchTextSeq('SYMMETRIC')) {\n symmetric = true;\n } else if (this.matchTextSeq('ASYMMETRIC')) {\n symmetric = false;\n }\n\n const low = this.parseBitwise();\n this.match(TokenType.AND);\n const high = this.parseBitwise();\n\n return this.expression(\n BetweenExpr,\n {\n this: thisExpr,\n low,\n high,\n symmetric,\n },\n );\n }\n\n parseEscape (thisExpr?: Expression): Expression | undefined {\n if (!this.match(TokenType.ESCAPE)) {\n return thisExpr;\n }\n return this.expression(\n EscapeExpr,\n {\n this: thisExpr,\n expression: this.parseString() || this.parseNull(),\n },\n );\n }\n\n parseIntervalSpan (thisExpr: Expression): IntervalExpr {\n // handle day-time format interval span with omitted units:\n // INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>\n let intervalSpanUnitsOmitted: boolean | undefined;\n\n if (\n thisExpr\n && thisExpr.isString\n && this._constructor.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT\n && thisExpr.name?.match?.(INTERVAL_DAY_TIME_RE)\n ) {\n const index = this.index;\n\n // Var \"TO\" Var\n const firstUnit = this.parseVar({\n anyToken: true,\n upper: true,\n });\n let secondUnit: VarExpr | undefined;\n if (firstUnit && this.matchTextSeq('TO')) {\n secondUnit = this.parseVar({\n anyToken: true,\n upper: true,\n });\n }\n\n intervalSpanUnitsOmitted = !(firstUnit && secondUnit);\n\n this.retreat(index);\n }\n\n let unit: Expression | undefined = intervalSpanUnitsOmitted\n ? undefined\n : (\n this.parseFunction()\n || (\n !this.match(TokenType.ALIAS, { advance: false })\n && this.parseVar({\n anyToken: true,\n upper: true,\n })\n )\n || undefined\n );\n\n // Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse\n // each INTERVAL expression into this canonical form so it's easy to transpile\n let finalThis = thisExpr;\n if (thisExpr && thisExpr.isNumber) {\n finalThis = LiteralExpr.string(thisExpr.toValue() || thisExpr.sql());\n } else if (thisExpr && thisExpr.isString) {\n const parts = Array.from(thisExpr.name?.matchAll(new RegExp(INTERVAL_STRING_RE, 'g')) || []);\n if (0 < parts.length && unit) {\n // Unconsume the eagerly-parsed unit, since the real unit was part of the string\n unit = undefined;\n this.retreat(this.index - 1);\n }\n\n if (parts.length === 1) {\n finalThis = LiteralExpr.string(parts[0][1]);\n unit = this.expression(VarExpr, { this: parts[0][2].toUpperCase() });\n }\n }\n\n if (this._constructor.INTERVAL_SPANS && this.matchTextSeq('TO')) {\n unit = this.expression(\n IntervalSpanExpr,\n {\n this: unit,\n expression: this.parseFunction() || this.parseVar({\n anyToken: true,\n upper: true,\n }),\n },\n );\n }\n\n return this.expression(IntervalExpr, {\n this: finalThis,\n unit,\n });\n }\n\n parseInterval (options: { matchInterval?: boolean } = {}): AddExpr | IntervalExpr | undefined {\n const {\n matchInterval = true,\n } = options;\n\n const index = this.index;\n\n if (!this.match(TokenType.INTERVAL) && matchInterval) {\n return undefined;\n }\n\n let thisExpr: Expression | undefined;\n if (this.match(TokenType.STRING, { advance: false })) {\n thisExpr = this.parsePrimary();\n } else {\n thisExpr = this.parseTerm();\n }\n\n if (!thisExpr || (\n thisExpr instanceof ColumnExpr\n && !thisExpr.args.table\n && thisExpr.args.this instanceof IdentifierExpr\n && !thisExpr.args.this.args.quoted\n && this.curr\n && !this._dialectConstructor.VALID_INTERVAL_UNITS.has(this.curr.text.toUpperCase())\n )) {\n this.retreat(index);\n return undefined;\n }\n\n const interval = this.parseIntervalSpan(thisExpr);\n\n const index2 = this.index;\n this.match(TokenType.PLUS);\n\n // Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals\n if (this.matchSet(new Set([TokenType.STRING, TokenType.NUMBER]), { advance: false })) {\n return this.expression(\n AddExpr,\n {\n this: interval,\n expression: this.parseInterval({ matchInterval: false }),\n },\n );\n }\n\n this.retreat(index2);\n return interval;\n }\n\n parseBitwise (): Expression | undefined {\n let thisExpr = this.parseTerm();\n\n const bitwiseTokens = Object.keys(this._constructor.BITWISE) as TokenType[];\n while (true) {\n if (this.matchSet(bitwiseTokens)) {\n const ExprClass = this._constructor.BITWISE[this.prev?.tokenType ?? TokenType.UNKNOWN];\n if (ExprClass) {\n thisExpr = this.expression(\n ExprClass,\n {\n this: thisExpr,\n expression: this.parseTerm(),\n },\n );\n }\n } else if (this._dialectConstructor.DPIPE_IS_STRING_CONCAT && this.match(TokenType.DPIPE)) {\n thisExpr = this.expression(\n DPipeExpr,\n {\n this: thisExpr,\n expression: this.parseTerm(),\n safe: !this._dialectConstructor.STRICT_STRING_CONCAT,\n },\n );\n } else if (this.match(TokenType.DQMARK)) {\n thisExpr = this.expression(\n CoalesceExpr,\n {\n this: thisExpr,\n expressions: ensureList(this.parseTerm()),\n },\n );\n } else if (this.matchPair(TokenType.LT, TokenType.LT)) {\n thisExpr = this.expression(\n BitwiseLeftShiftExpr,\n {\n this: thisExpr,\n expression: this.parseTerm(),\n },\n );\n } else if (this.matchPair(TokenType.GT, TokenType.GT)) {\n thisExpr = this.expression(\n BitwiseRightShiftExpr,\n {\n this: thisExpr,\n expression: this.parseTerm(),\n },\n );\n } else {\n break;\n }\n }\n\n return thisExpr;\n }\n\n parseTerm (): Expression | undefined {\n let thisExpr = this.parseFactor();\n\n const termTokens = Object.keys(this._constructor.TERM) as TokenType[];\n while (this.matchSet(termTokens)) {\n const klass = this._constructor.TERM[this.prev?.tokenType ?? TokenType.UNKNOWN];\n const comments = this.prevComments;\n const expression = this.parseFactor();\n\n if (klass) {\n thisExpr = this.expression(klass, {\n this: thisExpr,\n comments,\n expression,\n });\n\n if (thisExpr instanceof CollateExpr) {\n const expr = thisExpr.args.expression as Expression;\n\n // Preserve collations such as pg_catalog.\"default\" (Postgres) as columns, otherwise\n // fallback to Identifier / Var\n if (expr instanceof ColumnExpr && expr.parts.length === 1) {\n const ident = expr.args.this;\n if (ident instanceof IdentifierExpr) {\n thisExpr.setArgKey('expression', ident.args.quoted ? ident : var_(ident.name));\n }\n }\n }\n }\n }\n\n return thisExpr;\n }\n\n parseFactor (): Expression | undefined {\n const parseMethod = this._constructor.EXPONENT ? () => this.parseExponent() : () => this.parseUnary();\n let thisExpr = this.parseAtTimeZone(parseMethod());\n\n const factorTokens = Object.keys(this._constructor.FACTOR) as TokenType[];\n while (this.matchSet(factorTokens)) {\n const klass = this._constructor.FACTOR[this.prev?.tokenType ?? TokenType.UNKNOWN];\n const comments = this.prevComments;\n const expression = parseMethod();\n\n if (!expression && klass === IntDivExpr && /^[a-zA-Z]/.test(this.prev?.text ?? '')) {\n this.retreat(this.index - 1);\n return thisExpr;\n }\n\n if (klass) {\n thisExpr = this.expression(klass, {\n this: thisExpr,\n comments,\n expression,\n });\n\n if (thisExpr instanceof DivExpr) {\n thisExpr.setArgKey('typed', this._dialectConstructor.TYPED_DIVISION);\n thisExpr.setArgKey('safe', this._dialectConstructor.SAFE_DIVISION);\n }\n }\n }\n\n return thisExpr;\n }\n\n parseExponent (): Expression | undefined {\n return this.parseTokens(() => this.parseUnary(), this._constructor.EXPONENT);\n }\n\n parseUnary (): Expression | undefined {\n if (this.matchSet(Object.keys(this._constructor.UNARY_PARSERS) as TokenType[])) {\n const parser = this._constructor.UNARY_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN];\n return parser ? parser.call(this) : undefined;\n }\n return this.parseType();\n }\n\n parseType (options: {\n parseInterval?: boolean;\n fallbackToIdentifier?: boolean;\n } = {}): Expression | undefined {\n const {\n parseInterval = true,\n fallbackToIdentifier = false,\n } = options;\n\n const interval = parseInterval && this.parseInterval();\n if (interval) {\n return this.parseColumnOps(interval);\n }\n\n const index = this.index;\n const dataType = this.parseTypes({\n checkFunc: true,\n allowIdentifiers: false,\n });\n\n // parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.\n // STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)\n if (dataType instanceof CastExpr) {\n // This constructor can contain ops directly after it, for instance struct unnesting:\n // STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*\n return this.parseColumnOps(dataType);\n }\n\n if (dataType) {\n const index2 = this.index;\n const thisExpr = this.parsePrimary();\n\n if (thisExpr instanceof LiteralExpr) {\n const literal = thisExpr.name;\n const thisWithOps = this.parseColumnOps(thisExpr);\n\n const parser = typeof dataType.args.this === 'string' ? this._constructor.TYPE_LITERAL_PARSERS[dataType.args.this as DataTypeExprKind] : undefined;\n if (parser) {\n return parser.call(this, thisWithOps, dataType as DataTypeExpr);\n }\n\n if (\n this._constructor.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR\n && (dataType as DataTypeExpr).isType?.([DataTypeExprKind.TIMESTAMP])\n && TIME_ZONE_RE.test(literal)\n ) {\n (dataType as DataTypeExpr).setArgKey('this', DataTypeExprKind.TIMESTAMPTZ);\n }\n\n return this.expression(CastExpr, {\n this: thisWithOps,\n to: dataType,\n });\n }\n\n // The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)\n // in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )\n //\n // If the index difference here is greater than 1, that means the parser itself must have\n // consumed additional tokens such as the DECIMAL scale and precision in the above example.\n //\n // If it's not greater than 1, then it must be 1, because we've consumed at least the type\n // keyword, meaning that the expressions arg of the DataType must have gotten set by a\n // callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to\n // DECIMAL(38, 0)) in order to facilitate the data type's transpilation.\n //\n // In these cases, we don't really want to return the converted type, but instead retreat\n // and try to parse a Column or Identifier in the section below.\n if ((dataType as DataTypeExpr).args.expressions && 1 < index2 - index) {\n this.retreat(index2);\n return this.parseColumnOps(dataType);\n }\n\n this.retreat(index);\n }\n\n if (fallbackToIdentifier) {\n return this.parseIdVar();\n }\n\n const thisExpr = this.parseColumn();\n return thisExpr && this.parseColumnOps(thisExpr);\n }\n\n parseTypeSize (): DataTypeParamExpr | undefined {\n let thisExpr: Expression | undefined = this.parseType();\n if (!thisExpr) {\n return undefined;\n }\n\n if (thisExpr instanceof ColumnExpr && !thisExpr.args.table) {\n thisExpr = var_(thisExpr.name.toUpperCase());\n }\n\n return this.expression(\n DataTypeParamExpr,\n {\n this: thisExpr,\n expression: this.parseVar({ anyToken: true }),\n },\n );\n }\n\n parseUserDefinedType (identifier: IdentifierExpr): Expression | undefined {\n let typeName = identifier.name;\n\n while (this.match(TokenType.DOT)) {\n this.advanceAny();\n typeName = `${typeName}.${this.prev?.text ?? ''}`;\n }\n\n return DataTypeExpr.build(typeName, {\n dialect: this.dialect,\n udt: true,\n });\n }\n\n parseTypes (options: {\n checkFunc?: boolean;\n schema?: boolean;\n allowIdentifiers?: boolean;\n } = {}): Expression | undefined {\n const {\n checkFunc = false,\n schema = false,\n allowIdentifiers = true,\n } = options;\n\n const index = this.index;\n\n let thisExpr: Expression | undefined;\n const prefix = this.matchTextSeq(['SYSUDTLIB', '.']) || undefined;\n\n let typeToken: TokenType | undefined;\n let _typeTokenText: string | undefined;\n if (this.matchSet(this._constructor.TYPE_TOKENS)) {\n typeToken = this.prev?.tokenType;\n _typeTokenText = this.prev?.text;\n // Store original text for fallback lookup; will also use token key\n } else {\n const identifier = allowIdentifiers && this.parseIdVar({\n anyToken: false,\n tokens: new Set([TokenType.VAR]),\n });\n\n if (identifier instanceof IdentifierExpr) {\n let tokens: Token[] | undefined;\n try {\n tokens = this.dialect.tokenize?.(identifier.name);\n } catch {\n tokens = undefined;\n }\n\n if (tokens && tokens.length === 1 && this._constructor.TYPE_TOKENS.has(tokens[0].tokenType)) {\n typeToken = tokens[0].tokenType;\n _typeTokenText = tokens[0].text;\n } else if (this._dialectConstructor.SUPPORTS_USER_DEFINED_TYPES) {\n thisExpr = this.parseUserDefinedType(identifier);\n } else {\n this.retreat(this.index - 1);\n return undefined;\n }\n } else {\n return undefined;\n }\n }\n\n if (typeToken === TokenType.PSEUDO_TYPE) {\n return this.expression(PseudoTypeExpr, { this: (this.prev?.text ?? '').toUpperCase() });\n }\n\n if (typeToken === TokenType.OBJECT_IDENTIFIER) {\n return this.expression(ObjectIdentifierExpr, { this: (this.prev?.text ?? '').toUpperCase() });\n }\n\n // https://materialize.com/docs/sql/types/map/\n if (typeToken === TokenType.MAP && this.match(TokenType.L_BRACKET)) {\n const keyType = this.parseTypes({\n checkFunc,\n schema,\n allowIdentifiers,\n });\n if (!this.match(TokenType.FARROW)) {\n this.retreat(index);\n return undefined;\n }\n\n const valueType = this.parseTypes({\n checkFunc,\n schema,\n allowIdentifiers,\n });\n if (!this.match(TokenType.R_BRACKET)) {\n this.retreat(index);\n return undefined;\n }\n\n return new DataTypeExpr({\n this: DataTypeExprKind.MAP,\n expressions: [keyType as DataTypeExpr, valueType as DataTypeExpr],\n nested: true,\n prefix,\n });\n }\n\n const nested = typeToken && this._constructor.NESTED_TYPE_TOKENS.has(typeToken);\n const isStruct = typeToken && this._constructor.STRUCT_TYPE_TOKENS.has(typeToken);\n const isAggregate = typeToken && this._constructor.AGGREGATE_TYPE_TOKENS.has(typeToken);\n let expressions: Expression[] | undefined;\n let maybeFunc = false;\n\n if (this.match(TokenType.L_PAREN)) {\n if (isStruct) {\n expressions = this.parseCsv(() => this.parseStructTypes({ typeRequired: true }));\n } else if (nested) {\n expressions = this.parseCsv(() => this.parseTypes({\n checkFunc,\n schema,\n allowIdentifiers,\n }));\n\n if (typeToken === TokenType.NULLABLE && expressions.length === 1) {\n thisExpr = expressions[0];\n thisExpr.setArgKey('nullable', true);\n this.matchRParen();\n return thisExpr;\n }\n } else if (typeToken && this._constructor.ENUM_TYPE_TOKENS.has(typeToken)) {\n expressions = this.parseCsv(() => this.parseEquality());\n } else if (isAggregate) {\n const funcOrIdent = this.parseFunction({ anonymous: true }) || this.parseIdVar({\n anyToken: false,\n tokens: new Set([TokenType.VAR, TokenType.ANY]),\n });\n if (!funcOrIdent) {\n return undefined;\n }\n expressions = [funcOrIdent];\n if (this.match(TokenType.COMMA)) {\n expressions.push(...this.parseCsv(() => this.parseTypes({\n checkFunc,\n schema,\n allowIdentifiers,\n })));\n }\n } else {\n expressions = this.parseCsv(() => this.parseTypeSize());\n\n // https://docs.snowflake.com/en/sql-reference/data-types-vector\n if (typeToken === TokenType.VECTOR && expressions.length === 2) {\n expressions = this.parseVectorExpressions(expressions);\n }\n }\n\n if (!this.match(TokenType.R_PAREN)) {\n this.retreat(index);\n return undefined;\n }\n\n maybeFunc = true;\n }\n\n let values: Expression[] | undefined;\n\n if (nested && this.match(TokenType.LT)) {\n if (isStruct) {\n expressions = this.parseCsv(() => this.parseStructTypes({ typeRequired: true }));\n } else {\n expressions = this.parseCsv(() => this.parseTypes({\n checkFunc,\n schema,\n allowIdentifiers,\n }));\n }\n\n if (!this.match(TokenType.GT)) {\n this.raiseError('Expecting >', this.curr);\n }\n\n if (this.matchSet(new Set([TokenType.L_BRACKET, TokenType.L_PAREN]))) {\n values = this.parseCsv(() => this.parseDisjunction());\n if (!values && isStruct) {\n values = undefined;\n this.retreat(this.index - 1);\n } else {\n this.matchSet(new Set([TokenType.R_BRACKET, TokenType.R_PAREN]));\n }\n }\n }\n\n if (typeToken && this._constructor.TIMESTAMPS.has(typeToken)) {\n if (this.matchTextSeq([\n 'WITH',\n 'TIME',\n 'ZONE',\n ])) {\n maybeFunc = false;\n const tzType = this._constructor.TIMES.has(typeToken)\n ? DataTypeExprKind.TIMETZ\n : DataTypeExprKind.TIMESTAMPTZ;\n thisExpr = new DataTypeExpr({\n this: tzType,\n expressions: expressions as DataTypeExpr[],\n });\n } else if (this.matchTextSeq([\n 'WITH',\n 'LOCAL',\n 'TIME',\n 'ZONE',\n ])) {\n maybeFunc = false;\n thisExpr = new DataTypeExpr({\n this: DataTypeExprKind.TIMESTAMPLTZ,\n expressions: expressions as DataTypeExpr[],\n });\n } else if (this.matchTextSeq([\n 'WITHOUT',\n 'TIME',\n 'ZONE',\n ])) {\n maybeFunc = false;\n }\n } else if (typeToken === TokenType.INTERVAL) {\n if (this.curr && this._dialectConstructor.VALID_INTERVAL_UNITS.has(this.curr.text.toUpperCase())) {\n let unit = this.parseVar({ upper: true });\n if (this.matchTextSeq('TO')) {\n unit = new IntervalSpanExpr({\n this: unit,\n expression: this.parseVar({ upper: true }),\n });\n }\n\n thisExpr = this.expression(DataTypeExpr, { this: new IntervalExpr({ unit }) });\n } else {\n thisExpr = this.expression(DataTypeExpr, { this: DataTypeExprKind.INTERVAL });\n }\n } else if (typeToken === TokenType.VOID) {\n thisExpr = new DataTypeExpr({ this: DataTypeExprKind.NULL });\n }\n\n if (maybeFunc && checkFunc) {\n const index2 = this.index;\n const peek = this.parseString();\n\n if (!peek) {\n this.retreat(index);\n return undefined;\n }\n\n this.retreat(index2);\n }\n\n if (!thisExpr) {\n if (this.matchTextSeq('UNSIGNED')) {\n const unsignedTypeToken = typeToken && this._constructor.SIGNED_TO_UNSIGNED_TYPE_TOKEN[typeToken];\n if (!unsignedTypeToken) {\n this.raiseError(`Cannot convert ${typeToken?.valueOf()} to unsigned.`, this.curr);\n }\n\n typeToken = unsignedTypeToken || typeToken;\n }\n\n // NULLABLE without parentheses can be a column (Presto/Trino)\n if (typeToken === TokenType.NULLABLE && !expressions) {\n this.retreat(index);\n return undefined;\n }\n\n if (typeToken) {\n thisExpr = new DataTypeExpr({\n this: DataTypeExprKind[camelToScreamingSnakeCase(typeToken!) as keyof typeof DataTypeExprKind],\n expressions: expressions as DataTypeExpr[],\n nested,\n prefix,\n });\n\n // Empty arrays/structs are allowed\n if (values !== undefined) {\n const cls = isStruct ? StructExpr : ArrayExpr;\n thisExpr = cast(\n new cls({ expressions: values }),\n thisExpr as DataTypeExpr,\n { copy: false },\n );\n }\n }\n } else if (expressions) {\n thisExpr.setArgKey('expressions', expressions);\n }\n\n // https://materialize.com/docs/sql/types/list/#type-name\n while (this.match(TokenType.LIST)) {\n thisExpr = new DataTypeExpr({\n this: DataTypeExprKind.LIST,\n expressions: [thisExpr as DataTypeExpr],\n nested: true,\n });\n }\n\n const index3 = this.index;\n\n // Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]\n let matchedArray = this.match(TokenType.ARRAY) || false;\n\n while (this.curr) {\n const datatypeToken = this.prev?.tokenType;\n const matchedLBracket = this.match(TokenType.L_BRACKET) || undefined;\n\n if ((!matchedLBracket && !matchedArray) || (\n datatypeToken === TokenType.ARRAY && this.match(TokenType.R_BRACKET)\n )) {\n // Postgres allows casting empty arrays such as ARRAY[]::INT[],\n // not to be confused with the fixed size array parsing\n break;\n }\n\n matchedArray = false;\n const valuesInBracket = this.parseCsv(() => this.parseDisjunction());\n\n if (\n 0 < valuesInBracket.length\n && !schema\n && (\n !this._dialectConstructor.SUPPORTS_FIXED_SIZE_ARRAYS\n || datatypeToken === TokenType.ARRAY\n || !this.match(TokenType.R_BRACKET, { advance: false })\n )\n ) {\n // Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB\n // ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type\n this.retreat(index3);\n break;\n }\n\n thisExpr = new DataTypeExpr({\n this: DataTypeExprKind.ARRAY,\n expressions: [thisExpr as DataTypeExpr],\n values: valuesInBracket,\n nested: true,\n });\n this.match(TokenType.R_BRACKET);\n }\n\n if (thisExpr && this._constructor.TYPE_CONVERTERS && typeof (thisExpr as DataTypeExpr).args.this === 'string') {\n const converter = this._constructor.TYPE_CONVERTERS[(thisExpr as DataTypeExpr).args.this as DataTypeExprKind];\n if (converter) {\n thisExpr = converter(thisExpr as DataTypeExpr);\n }\n }\n\n return thisExpr;\n }\n\n parseVectorExpressions (expressions: Expression[]): Expression[] {\n const dataType = DataTypeExpr.build(expressions[0].name, { dialect: this.dialect });\n return [...(dataType ? [dataType] : []), ...expressions.slice(1)];\n }\n\n parseStructTypes (options: { typeRequired?: boolean } = {}): Expression | undefined {\n const {\n typeRequired = false,\n } = options;\n\n const index = this.index;\n\n let thisExpr: Expression | undefined;\n if (\n this.curr\n && this.next\n && this._constructor.TYPE_TOKENS.has(this.curr.tokenType)\n && this._constructor.TYPE_TOKENS.has(this.next.tokenType)\n ) {\n // Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a\n // type token. Without this, the list will be parsed as a type and we'll eventually crash\n thisExpr = this.parseIdVar();\n } else {\n thisExpr = (\n this.parseType({\n parseInterval: false,\n fallbackToIdentifier: true,\n })\n || this.parseIdVar()\n );\n }\n\n this.match(TokenType.COLON);\n\n if (\n typeRequired\n && !(thisExpr instanceof DataTypeExpr)\n && !this.matchSet(this._constructor.TYPE_TOKENS, { advance: false })\n ) {\n this.retreat(index);\n return this.parseTypes();\n }\n\n return this.parseColumnDef(thisExpr);\n }\n\n parseAtTimeZone (thisExpr?: Expression): Expression | undefined {\n if (!this.matchTextSeq([\n 'AT',\n 'TIME',\n 'ZONE',\n ])) {\n return thisExpr;\n }\n return this.parseAtTimeZone(\n this.expression(AtTimeZoneExpr, {\n this: thisExpr,\n zone: this.parseUnary(),\n }),\n );\n }\n\n parseColumn (): Expression | undefined {\n const thisExpr = this.parseColumnReference();\n const column = thisExpr ? this.parseColumnOps(thisExpr) : this.parseBracket(thisExpr);\n\n if (this._dialectConstructor.SUPPORTS_COLUMN_JOIN_MARKS && column) {\n column.setArgKey('joinMark', this.match(TokenType.JOIN_MARKER));\n }\n\n return column;\n }\n\n parseColumnReference (): Expression | undefined {\n let thisExpr = this.parseField();\n\n if (\n !thisExpr\n && this.match(TokenType.VALUES, { advance: false })\n && this._constructor.VALUES_FOLLOWED_BY_PAREN\n && (!this.next || this.next.tokenType !== TokenType.L_PAREN)\n ) {\n thisExpr = this.parseIdVar();\n }\n\n if (thisExpr instanceof IdentifierExpr) {\n // We bubble up comments from the Identifier to the Column\n thisExpr = this.expression(ColumnExpr, {\n comments: thisExpr.popComments(),\n this: thisExpr,\n });\n }\n\n return thisExpr;\n }\n\n parseColonAsVariantExtract (thisExpr?: Expression): Expression | undefined {\n const casts: DataTypeExpr[] = [];\n const jsonPath: string[] = [];\n let escape: boolean | undefined;\n\n while (this.match(TokenType.COLON)) {\n const startIndex = this.index;\n\n // Snowflake allows reserved keywords as json keys but advance_any() excludes TokenType.SELECT from any_tokens=True\n let path: ExpressionValue | undefined = this.parseColumnOps(\n this.parseField({\n anyToken: true,\n tokens: new Set([TokenType.SELECT]),\n }),\n );\n\n // The cast :: operator has a lower precedence than the extraction operator :, so\n // we rearrange the AST appropriately to avoid casting the JSON path\n while (path instanceof CastExpr) {\n casts.push(path.args.to as DataTypeExpr);\n path = path.args.this;\n }\n\n let endToken: Token;\n if (0 < casts.length) {\n const dcolonOffset = this.tokens.slice(startIndex).findIndex(\n (t) => t.tokenType === TokenType.DCOLON,\n );\n endToken = this.tokens[startIndex + dcolonOffset - 1];\n } else {\n endToken = this.prev as Token;\n }\n\n if (path) {\n // Escape single quotes from Snowflake's colon extraction (e.g. col:\"a'b\") as\n // it'll roundtrip to a string literal in GET_PATH\n if (path instanceof IdentifierExpr && path.args.quoted) {\n escape = true;\n }\n\n jsonPath.push(this.findSql(this.tokens[startIndex], endToken));\n }\n }\n\n // The VARIANT extract in Snowflake/Databricks is parsed as a JsonExtract; Snowflake uses the json_path in GET_PATH() while\n // Databricks transforms it back to the colon/dot notation\n if (0 < jsonPath.length) {\n const jsonPathExpr = this.dialect.toJsonPath?.(LiteralExpr.string('.' + jsonPath.join('.')));\n\n if (jsonPathExpr) {\n jsonPathExpr.setArgKey('escape', escape);\n }\n\n thisExpr = this.expression(\n JsonExtractExpr,\n {\n this: thisExpr,\n expression: jsonPathExpr,\n variantExtract: true,\n requiresJson: this._constructor.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,\n },\n );\n\n while (0 < casts.length) {\n thisExpr = this.expression(CastExpr, {\n this: thisExpr,\n to: casts.pop(),\n });\n }\n }\n\n return thisExpr;\n }\n\n parseDcolon (): Expression | undefined {\n return this.parseTypes();\n }\n\n parseColumnOps (thisExpr?: Expression): Expression | undefined {\n let current = this.parseBracket(thisExpr);\n\n while (this.matchSet(Object.keys(this._constructor.COLUMN_OPERATORS) as TokenType[])) {\n const opToken = this.prev?.tokenType ?? TokenType.UNKNOWN;\n const op = this._constructor.COLUMN_OPERATORS[opToken];\n\n let field: Expression | undefined;\n if (this._constructor.CAST_COLUMN_OPERATORS.has(opToken)) {\n field = this.parseDcolon();\n if (!field) {\n this.raiseError('Expected type', this.curr);\n }\n } else if (op && this.curr) {\n field = this.parseColumnReference() || this.parseBitwise();\n if (field instanceof ColumnExpr && this.match(TokenType.DOT, { advance: false })) {\n field = this.parseColumnOps(field);\n }\n } else {\n field = this.parseField({\n anyToken: true,\n anonymousFunc: true,\n });\n }\n\n // Function calls can be qualified, e.g., x.y.FOO()\n // This converts the final AST to a series of Dots leading to the function call\n // https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules\n if ((field instanceof FuncExpr || field instanceof WindowExpr) && current) {\n current = current.transform(\n (n: Expression) => n instanceof ColumnExpr ? n.toDot?.({ includeDots: false }) || n : n,\n );\n }\n\n if (op) {\n current = op.call(this, current, field);\n } else if (current instanceof ColumnExpr && !current.args.catalog) {\n current = this.expression(\n ColumnExpr,\n {\n comments: current.comments,\n this: field,\n table: current.args.this,\n db: current.args.table,\n catalog: current.args.db,\n },\n );\n } else if (field instanceof WindowExpr) {\n // Move the exp.Dot's to the window's function\n const windowFunc = this.expression(DotExpr, {\n this: current,\n expression: field.args.this,\n });\n field.setArgKey('this', windowFunc);\n current = field;\n } else {\n current = this.expression(DotExpr, {\n this: current,\n expression: field,\n });\n }\n\n if (field?.comments) {\n current?.addComments?.(field.popComments());\n }\n\n current = this.parseBracket(current);\n }\n\n return this._constructor.COLON_IS_VARIANT_EXTRACT\n ? this.parseColonAsVariantExtract(current)\n : current;\n }\n\n parseComment (options: { allowExists?: boolean } = {}): Expression {\n const { allowExists = true } = options;\n const start = this.prev;\n const exists = allowExists ? this.parseExists() : undefined;\n\n this.match(TokenType.ON);\n\n const materialized = this.matchTextSeq('MATERIALIZED') || undefined;\n const kind = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev;\n if (!kind) {\n return this.parseAsCommand(start);\n }\n\n let thisExpr: Expression | undefined;\n if (kind.tokenType === TokenType.FUNCTION || kind.tokenType === TokenType.PROCEDURE) {\n thisExpr = this.parseUserDefinedFunction({ kind: kind.tokenType });\n } else if (kind.tokenType === TokenType.TABLE) {\n thisExpr = this.parseTable({ aliasTokens: this._constructor.COMMENT_TABLE_ALIAS_TOKENS });\n } else if (kind.tokenType === TokenType.COLUMN) {\n thisExpr = this.parseColumn();\n } else {\n thisExpr = this.parseIdVar();\n }\n\n this.match(TokenType.IS);\n\n return this.expression(CommentExpr, {\n this: thisExpr,\n kind: kind.text,\n expression: this.parseString(),\n exists,\n materialized,\n });\n }\n\n parseToTable (): ToTablePropertyExpr {\n const table = this.parseTableParts({ schema: true });\n return this.expression(ToTablePropertyExpr, { this: table });\n }\n\n parseTtl (): Expression {\n // https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl\n const parseTtlAction = (): Expression | undefined => {\n const thisExpr = this.parseBitwise();\n\n if (this.matchTextSeq('DELETE')) {\n return this.expression(MergeTreeTtlActionExpr, {\n this: thisExpr,\n delete: true,\n });\n }\n if (this.matchTextSeq('RECOMPRESS')) {\n return this.expression(MergeTreeTtlActionExpr, {\n this: thisExpr,\n recompress: this.parseBitwise(),\n });\n }\n if (this.matchTextSeq(['TO', 'DISK'])) {\n return this.expression(MergeTreeTtlActionExpr, {\n this: thisExpr,\n toDisk: this.parseString(),\n });\n }\n if (this.matchTextSeq(['TO', 'VOLUME'])) {\n return this.expression(MergeTreeTtlActionExpr, {\n this: thisExpr,\n toVolume: this.parseString(),\n });\n }\n\n return thisExpr;\n };\n\n const expressions = this.parseCsv(parseTtlAction);\n const where = this.parseWhere();\n const group = this.parseGroup();\n\n let aggregates: Expression[] | undefined;\n if (group && this.match(TokenType.SET)) {\n aggregates = this.parseCsv(this.parseSetItem.bind(this));\n }\n\n return this.expression(MergeTreeTtlExpr, {\n expressions,\n where,\n group,\n aggregates,\n });\n }\n\n parseStatement (): Expression | undefined {\n if (this.curr === undefined) {\n return undefined;\n }\n\n const statementTokens = Object.keys(this._constructor.STATEMENT_PARSERS) as TokenType[];\n if (this.matchSet(statementTokens)) {\n const comments = this.prevComments;\n const stmt = this._constructor.STATEMENT_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN]?.call(this);\n stmt?.addComments(comments, { prepend: true });\n return stmt;\n }\n\n if (this.matchSet(this._dialectConstructor.tokenizerClass.COMMANDS)) {\n return this.parseCommand();\n }\n\n let expression = this.parseExpression();\n expression = expression ? this.parseSetOperations(expression) : this.parseSelect();\n return this.parseQueryModifiers(expression);\n }\n\n parsePartitionedByBucketOrTruncate (): Expression | undefined {\n // Check for L_PAREN without advancing\n if (this.curr?.tokenType !== TokenType.L_PAREN) {\n // Partitioning by bucket or truncate follows the syntax:\n // PARTITION BY (BUCKET(..) | TRUNCATE(..))\n // If we don't have parenthesis after each keyword, we should instead parse this as an identifier\n this.retreat(this.index - 1);\n return undefined;\n }\n\n const ExprClass = (\n this.prev?.text.toUpperCase() === 'BUCKET'\n ? PartitionedByBucketExpr\n : PartitionByTruncateExpr\n );\n\n const args = this.parseWrappedCsv(() => this.parsePrimary() || this.parseColumn());\n let thisArg = seqGet(args, 0);\n let expression = seqGet(args, 1);\n\n if (thisArg instanceof LiteralExpr) {\n // Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order\n // - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`\n // - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`\n // Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`\n //\n // Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning\n // Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties\n [thisArg, expression] = [expression, thisArg];\n }\n\n return this.expression(ExprClass, {\n this: thisArg,\n expression,\n });\n }\n\n /**\n * Appends an error in the list of recorded errors or raises it, depending on the chosen\n * error level setting.\n */\n raiseError (message: string, token?: Token) {\n const errorToken = token || this.curr || this.prev || Token.string('');\n const {\n formattedSql, startContext, highlight, endContext,\n } = highlightSql({\n sql: this.sql,\n positions: [[errorToken.start ?? 0, errorToken.end ?? 0]],\n contextLength: this.errorMessageContext,\n });\n const formattedMessage = `${message}. Line ${errorToken.line}, Col: ${errorToken.col}.\\n ${formattedSql}`;\n\n const error = new ParseError(formattedMessage, [\n {\n description: message,\n line: errorToken.line,\n col: errorToken.col,\n startContext,\n highlight,\n endContext,\n },\n ]);\n\n if (this.errorLevel === ErrorLevel.IMMEDIATE) {\n throw error;\n }\n\n this.errors.push(error);\n }\n\n /**\n * Logs or raises any found errors, depending on the chosen error level setting.\n */\n checkErrors (): void {\n if (this.errorLevel === ErrorLevel.WARN) {\n for (const error of this.errors) {\n console.error(error.toString());\n }\n } else if (this.errorLevel === ErrorLevel.RAISE && 0 < this.errors.length) {\n throw new ParseError(\n concatMessages(this.errors, this.maxErrors),\n mergeErrors(this.errors),\n );\n }\n }\n\n /**\n * Creates a new, validated Expression.\n *\n * @param expClass - The expression class to instantiate.\n * @param options - Optional arguments including token, comments, and other expression arguments.\n * @returns The target expression.\n */\n expression<E extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n expClass: new (args: any) => E,\n options: {\n token?: Token;\n comments?: string[];\n [index: string]: unknown;\n } = {},\n ): E {\n const {\n token, comments, ...kwargs\n } = options;\n\n let instance: E;\n if (token) {\n instance = new expClass({\n this: token.text,\n ...kwargs,\n });\n instance.updatePositions(token);\n } else {\n instance = new expClass(kwargs);\n }\n\n if (comments?.length) {\n instance.addComments(comments);\n } else {\n this.addComments(instance);\n }\n\n return this.validateExpression(instance);\n }\n\n protected addComments (expression: Expression | undefined): void {\n if (expression && this.prevComments?.length) {\n expression.addComments(this.prevComments);\n this.prevComments = undefined;\n }\n }\n\n parseIdVar (options: {\n anyToken?: boolean;\n tokens?: Set<TokenType>;\n } = {}): Expression | undefined {\n const {\n anyToken = true,\n tokens,\n } = options;\n\n let expression = this.parseIdentifier();\n if (!expression && (\n (anyToken && this.advanceAny()) || this.matchSet(tokens || this._constructor.ID_VAR_TOKENS)\n )) {\n const quoted = this.prev?.tokenType === TokenType.STRING;\n expression = this.identifierExpression(undefined, { quoted });\n }\n\n return expression;\n }\n\n parseGrantPrincipal (): GrantPrincipalExpr | undefined {\n const kind = (this.matchTexts(['ROLE', 'GROUP']) || undefined) && this.prev?.text.toUpperCase();\n const principal = this.parseIdVar();\n\n if (!principal) {\n return undefined;\n }\n\n return this.expression(GrantPrincipalExpr, {\n this: principal,\n kind,\n });\n }\n\n parseGrantPrivilege (): GrantPrivilegeExpr | undefined {\n const privilegeParts: string[] = [];\n\n while (this.curr && !this.matchSet(this._constructor.PRIVILEGE_FOLLOW_TOKENS, { advance: false })) {\n privilegeParts.push(this.curr.text.toUpperCase());\n this.advance();\n }\n\n const thisVar = this.expression(VarExpr, { this: privilegeParts.join(' ') });\n const expressions = this.match(TokenType.L_PAREN, { advance: false })\n ? this.parseWrappedCsv(() => this.parseColumn())\n : undefined;\n\n return this.expression(GrantPrivilegeExpr, {\n this: thisVar,\n expressions,\n });\n }\n\n matchTexts (texts: string | string[] | Set<string>, options: { advance?: boolean } = {}): boolean {\n const { advance = true } = options;\n const textsArray = Array.from(texts instanceof Set ? texts : ensureList(texts));\n if (\n this.curr\n && this.curr.tokenType !== TokenType.STRING\n && textsArray.includes(this.curr.text.toUpperCase())\n ) {\n if (advance) {\n this.advance();\n }\n return true;\n }\n return false;\n }\n\n matchSet (types: Set<TokenType> | TokenType[], options: { advance?: boolean } = {}): boolean {\n const { advance = true } = options;\n if (!this.curr) {\n return false;\n }\n\n const hasType = Array.isArray(types)\n ? types.includes(this.curr.tokenType)\n : types.has(this.curr.tokenType);\n\n if (hasType) {\n if (advance) {\n this.advance();\n }\n return true;\n }\n\n return false;\n }\n\n matchPair (tokenTypeA: TokenType, tokenTypeB: TokenType, options: { advance?: boolean } = {}): boolean {\n const { advance = true } = options;\n if (!this.curr || !this.next) {\n return false;\n }\n\n if (this.curr.tokenType === tokenTypeA && this.next.tokenType === tokenTypeB) {\n if (advance) {\n this.advance(2);\n }\n return true;\n }\n\n return false;\n }\n\n matchLParen (expression?: Expression): void {\n if (!this.match(TokenType.L_PAREN, {\n advance: true,\n expression,\n })) {\n this.raiseError('Expecting (');\n }\n }\n\n matchRParen (expression?: Expression): void {\n if (!this.match(TokenType.R_PAREN, {\n advance: true,\n expression,\n })) {\n this.raiseError('Expecting )');\n }\n }\n\n matchTextSeq (texts: string | string[], options: { advance?: boolean } = {}): boolean {\n const { advance = true } = options;\n const textArray = ensureList(texts);\n\n const index = this.index;\n for (const text of textArray) {\n if (\n this.curr\n && this.curr.tokenType !== TokenType.STRING\n && this.curr.text.toUpperCase() === text\n ) {\n this.advance();\n } else {\n this.retreat(index);\n return false;\n }\n }\n\n if (!advance) {\n this.retreat(index);\n }\n\n return true;\n }\n\n replaceLambda (node: Expression | undefined, expressions: Expression[]): Expression | undefined {\n if (!node) {\n return node;\n }\n\n const lambdaTypes: Record<string, ExpressionValue | false> = {};\n for (const e of expressions) {\n lambdaTypes[e.name] = e.args.to || false;\n }\n\n for (const column of node.findAll(ColumnExpr)) {\n const typ = lambdaTypes[column.parts[0]?.name || ''];\n if (typ !== undefined) {\n const colThis = column.args.this;\n assertIsInstanceOf(colThis, IdentifierExpr);\n let dotOrId: DotExpr | IdentifierExpr | StarExpr | CastExpr = column.table ? column.toDot() : colThis;\n\n if (typ) {\n dotOrId = this.expression(\n CastExpr,\n {\n this: dotOrId,\n to: typ,\n },\n );\n }\n\n let parent = column.parent;\n\n while (parent instanceof DotExpr) {\n if (!(parent.parent instanceof DotExpr)) {\n parent.replace(dotOrId);\n break;\n }\n parent = parent.parent;\n }\n\n if (!parent || !(parent instanceof DotExpr)) {\n if (column === node) {\n node = dotOrId;\n } else {\n column.replace(dotOrId);\n }\n }\n }\n }\n\n return node;\n }\n\n parseTruncateTable (): TruncateTableExpr | Expression | undefined {\n const start = this.prev;\n\n if (this.match(TokenType.L_PAREN)) {\n this.retreat(this.index - 2);\n return this.parseFunction();\n }\n\n const isDatabase = this.match(TokenType.DATABASE) || undefined;\n\n this.match(TokenType.TABLE);\n\n const exists = this.parseExists({ not: false });\n\n const expressions = this.parseCsv(\n () => this.parseTable({\n schema: true,\n isDbReference: isDatabase,\n }),\n );\n\n const cluster = this.match(TokenType.ON) ? this.parseOnProperty() : undefined;\n\n let identity: string | undefined;\n if (this.matchTextSeq(['RESTART', 'IDENTITY'])) {\n identity = 'RESTART';\n } else if (this.matchTextSeq(['CONTINUE', 'IDENTITY'])) {\n identity = 'CONTINUE';\n } else {\n identity = undefined;\n }\n\n let option: string | undefined;\n if (this.matchTextSeq('CASCADE') || this.matchTextSeq('RESTRICT')) {\n option = this.prev?.text;\n } else {\n option = undefined;\n }\n\n const partition = this.parsePartition();\n\n if (this.curr) {\n return this.parseAsCommand(start);\n }\n\n return this.expression(\n TruncateTableExpr,\n {\n expressions,\n isDatabase,\n exists,\n cluster,\n identity,\n option,\n partition,\n },\n );\n }\n\n parseWithOperator (): Expression | undefined {\n const thisExpr = this.parseOrdered(() => this.parseOpClass());\n\n if (!this.match(TokenType.WITH)) {\n return thisExpr;\n }\n\n const op = this.parseVar({\n anyToken: true,\n tokens: this._constructor.RESERVED_TOKENS,\n });\n\n return this.expression(WithOperatorExpr, {\n this: thisExpr,\n op,\n });\n }\n\n parseWrappedOptions (): Expression[] {\n this.match(TokenType.EQ);\n this.match(TokenType.L_PAREN);\n\n const opts: Expression[] = [];\n let option: Expression | Expression[] | undefined;\n while (this.curr && !this.match(TokenType.R_PAREN)) {\n if (this.matchTextSeq(['FORMAT_NAME', '='])) {\n option = this.parseFormatName();\n } else {\n option = this.parseProperty();\n }\n\n if (option === undefined) {\n this.raiseError('Unable to parse option');\n break;\n }\n\n opts.push(...(Array.isArray(option) ? option : [option]));\n }\n\n return opts;\n }\n\n parseCopyParameters (): CopyParameterExpr[] {\n const sep = this._dialectConstructor.COPY_PARAMS_ARE_CSV ? TokenType.COMMA : undefined;\n\n const options: CopyParameterExpr[] = [];\n while (this.curr && !this.match(TokenType.R_PAREN, { advance: false })) {\n const option = this.parseVar({ anyToken: true });\n const prev = this.prev?.text.toUpperCase();\n\n this.match(TokenType.EQ);\n this.match(TokenType.ALIAS);\n\n const param = this.expression(CopyParameterExpr, { this: option });\n\n if (prev && this._constructor.COPY_INTO_VARLEN_OPTIONS.has(prev) && this.match(TokenType.L_PAREN, { advance: false })) {\n param.setArgKey('expressions', this.parseWrappedOptions());\n } else if (prev === 'FILE_FORMAT') {\n param.setArgKey('expression', this.parseField());\n } else if (\n prev === 'FORMAT'\n && this.prev?.tokenType === TokenType.ALIAS\n && this.matchTexts(['AVRO', 'JSON'])\n ) {\n param.setArgKey('this', this.expression(VarExpr, { this: `FORMAT AS ${this.prev?.text.toUpperCase()}` }));\n param.setArgKey('expression', this.parseField());\n } else {\n param.setArgKey('expression', this.parseUnquotedField() || this.parseBracket());\n }\n\n options.push(param);\n if (sep) {\n this.match(sep);\n }\n }\n\n return options;\n }\n\n parseCredentials (): CredentialsExpr | undefined {\n const expr = this.expression(CredentialsExpr, {});\n\n if (this.matchTextSeq(['STORAGE_INTEGRATION', '='])) {\n expr.setArgKey('storage', this.parseField());\n }\n if (this.matchTextSeq('CREDENTIALS')) {\n const creds = this.match(TokenType.EQ) ? this.parseWrappedOptions() : this.parseField();\n expr.setArgKey('credentials', creds);\n }\n if (this.matchTextSeq('ENCRYPTION')) {\n expr.setArgKey('encryption', this.parseWrappedOptions());\n }\n if (this.matchTextSeq('IAM_ROLE')) {\n expr.setArgKey(\n 'iamRole',\n this.match(TokenType.DEFAULT)\n ? this.expression(VarExpr, { this: this.prev?.text })\n : this.parseField(),\n );\n }\n if (this.matchTextSeq('REGION')) {\n expr.setArgKey('region', this.parseField());\n }\n\n return expr;\n }\n\n parseFileLocation (): Expression | undefined {\n return this.parseField();\n }\n\n parseCopy (): CopyExpr | CommandExpr {\n const start = this.prev;\n\n this.match(TokenType.INTO);\n\n const thisExpr = this.match(TokenType.L_PAREN, { advance: false })\n ? this.parseSelect({\n nested: true,\n parseSubqueryAlias: false,\n })\n : this.parseTable({ schema: true });\n\n const kind = this.match(TokenType.FROM) || !this.matchTextSeq('TO');\n\n let files = this.parseCsv(() => this.parseFileLocation());\n if (this.match(TokenType.EQ, { advance: false })) {\n this.advance(-1);\n files = [];\n }\n\n const credentials = this.parseCredentials();\n\n this.matchTextSeq('WITH');\n\n const params = this.parseWrapped(() => this.parseCopyParameters(), { optional: true });\n\n if (this.curr) {\n return this.parseAsCommand(start);\n }\n\n return this.expression(\n CopyExpr,\n {\n this: thisExpr,\n kind,\n credentials,\n files,\n params,\n },\n );\n }\n\n parseNormalize (): NormalizeExpr {\n return this.expression(\n NormalizeExpr,\n {\n this: this.parseBitwise(),\n form: this.match(TokenType.COMMA) && this.parseVar(),\n },\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseCeilFloor<T extends Expression> (exprType: new (args: any) => T): T {\n const args = this.parseCsv(() => this.parseLambda());\n\n const thisExpr = seqGet(args, 0);\n const decimals = seqGet(args, 1);\n\n return new exprType({\n this: thisExpr,\n decimals,\n to: this.matchTextSeq('TO') && this.parseVar(),\n });\n }\n\n parseStarOps (): Expression | undefined {\n const starToken = this.prev;\n\n if (this.matchTextSeq(['COLUMNS', '('], { advance: false })) {\n const thisExpr = this.parseFunction();\n if (thisExpr instanceof ColumnsExpr) {\n thisExpr.setArgKey('unpack', true);\n }\n return thisExpr;\n }\n\n return this.expression(\n StarExpr,\n {\n except: this.parseStarOp('EXCEPT') || this.parseStarOp('EXCLUDE'),\n replace: this.parseStarOp('REPLACE'),\n rename: this.parseStarOp('RENAME'),\n },\n ).updatePositions(starToken);\n }\n\n parseGrantRevokeCommon (): {\n privileges: GrantPrivilegeExpr[] | undefined;\n kind: string | undefined;\n securable: Expression | undefined;\n } {\n const privileges = this.parseCsv(() => this.parseGrantPrivilege());\n\n this.match(TokenType.ON);\n const kind = (this.matchSet(this._constructor.CREATABLES) || undefined) && this.prev?.text.toUpperCase();\n\n const securable = this.tryParse(() => this.parseTableParts());\n\n return {\n privileges,\n kind,\n securable,\n };\n }\n\n parseGrant (): GrantExpr | CommandExpr {\n const start = this.prev;\n\n const {\n privileges,\n kind,\n securable,\n } = this.parseGrantRevokeCommon();\n\n if (!securable || !this.matchTextSeq('TO')) {\n return this.parseAsCommand(start);\n }\n\n const principals = this.parseCsv(() => this.parseGrantPrincipal());\n\n const grantOption = this.matchTextSeq([\n 'WITH',\n 'GRANT',\n 'OPTION',\n ]) || undefined;\n\n if (this.curr) {\n return this.parseAsCommand(start);\n }\n\n return this.expression(\n GrantExpr,\n {\n privileges,\n kind,\n securable,\n principals,\n grantOption,\n },\n );\n }\n\n parseRevoke (): RevokeExpr | CommandExpr {\n const start = this.prev;\n\n const grantOption = this.matchTextSeq([\n 'GRANT',\n 'OPTION',\n 'FOR',\n ]) || undefined;\n\n const {\n privileges,\n kind,\n securable,\n } = this.parseGrantRevokeCommon();\n\n if (!securable || !this.matchTextSeq('FROM')) {\n return this.parseAsCommand(start);\n }\n\n const principals = this.parseCsv(() => this.parseGrantPrincipal());\n\n let cascade: string | undefined;\n if (this.matchTexts(['CASCADE', 'RESTRICT'])) {\n cascade = this.prev?.text.toUpperCase();\n }\n\n if (this.curr) {\n return this.parseAsCommand(start);\n }\n\n return this.expression(\n RevokeExpr,\n {\n privileges,\n kind,\n securable,\n principals,\n grantOption,\n cascade,\n },\n );\n }\n\n parseOverlay (): OverlayExpr {\n const parseOverlayArg = (text: string): Expression | undefined => {\n return (this.match(TokenType.COMMA) || this.matchTextSeq(text) || undefined) && this.parseBitwise();\n };\n\n return this.expression(\n OverlayExpr,\n {\n this: this.parseBitwise(),\n expression: parseOverlayArg('PLACING'),\n from: parseOverlayArg('FROM'),\n for: parseOverlayArg('FOR'),\n },\n );\n }\n\n parseFormatName (): PropertyExpr {\n return this.expression(\n PropertyExpr,\n {\n this: this.expression(VarExpr, { this: 'FORMAT_NAME' }),\n value: this.parseString() || this.parseTableParts(),\n },\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parseMaxMinBy (exprType: new (args: any) => AggFuncExpr): AggFuncExpr {\n const args: Expression[] = [];\n\n if (this.match(TokenType.DISTINCT)) {\n args.push(this.expression(DistinctExpr, { expressions: [this.parseLambda()] }));\n this.match(TokenType.COMMA);\n }\n\n args.push(...this.parseFunctionArgs());\n\n return this.expression(\n exprType,\n {\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n count: seqGet(args, 2),\n },\n );\n }\n\n identifierExpression (token?: Token, kwargs?: { [index: string]: unknown }): IdentifierExpr {\n return this.expression(IdentifierExpr, {\n token: token || this.prev,\n ...kwargs,\n });\n }\n\n parseTokens (parseMethod: () => Expression | undefined, expressions: Partial<Record<TokenType, typeof Expression>>): Expression | undefined {\n let thisExpr = parseMethod();\n\n const expressionTokens = new Set(Object.keys(expressions)) as Set<TokenType>;\n while (this.matchSet(expressionTokens)) {\n const exprType = expressions[this.prev?.tokenType ?? TokenType.UNKNOWN];\n if (!exprType) break;\n thisExpr = this.expression(\n exprType,\n {\n this: thisExpr,\n comments: this.prevComments,\n expression: parseMethod(),\n },\n );\n }\n\n return thisExpr;\n }\n\n parseWrappedIdVars (options: { optional?: boolean } = {}): Expression[] {\n const { optional = false } = options;\n return this.parseWrappedCsv(() => this.parseIdVar(), { optional });\n }\n\n parseWrappedCsv (parseMethod: () => Expression | undefined, options: {\n sep?: TokenType;\n optional?: boolean;\n } = {}): Expression[] {\n const {\n sep = TokenType.COMMA,\n optional = false,\n } = options;\n return this.parseWrapped(\n () => this.parseCsv(parseMethod, { sep }),\n { optional },\n );\n }\n\n parseWrapped<T> (parseMethod: () => T, options: { optional?: boolean } = {}): T {\n const { optional = false } = options;\n const wrapped = this.match(TokenType.L_PAREN) || undefined;\n if (!wrapped && !optional) {\n this.raiseError('Expecting (');\n }\n const parseResult = parseMethod();\n if (wrapped) {\n this.matchRParen();\n }\n return parseResult;\n }\n\n parseExpressions (): Expression[] {\n return this.parseCsv(() => this.parseExpression());\n }\n\n parseSelectOrExpression (options: { alias?: boolean } = {}): Expression | undefined {\n const { alias = false } = options;\n return (\n this.parseSetOperations(\n alias\n ? this.parseAlias(this.parseAssignment(), { explicit: true })\n : this.parseAssignment(),\n ) || this.parseSelect()\n );\n }\n\n parseDdlSelect (): Expression | undefined {\n return this.parseQueryModifiers(\n this.parseSetOperations(this.parseSelect({\n nested: true,\n parseSubqueryAlias: false,\n })),\n );\n }\n\n parseTransaction (): TransactionExpr | CommandExpr {\n let thisText: string | undefined;\n if (this.matchTexts(this._constructor.TRANSACTION_KIND)) {\n thisText = this.prev?.text;\n }\n\n this.matchTexts(['TRANSACTION', 'WORK']);\n\n const modes: string[] = [];\n while (true) {\n const mode: string[] = [];\n while (this.match(TokenType.VAR) || this.match(TokenType.NOT)) {\n mode.push(this.prev?.text ?? '');\n }\n\n if (0 < mode.length) {\n modes.push(mode.join(' '));\n }\n if (!this.match(TokenType.COMMA)) {\n break;\n }\n }\n\n return this.expression(TransactionExpr, {\n this: thisText,\n modes,\n });\n }\n\n parseStar (): Expression | undefined {\n if (this.match(TokenType.STAR)) {\n return this._constructor.PRIMARY_PARSERS[TokenType.STAR]?.call(this, this.prev as Token);\n }\n return this.parsePlaceholder();\n }\n\n parseParameter (): ParameterExpr {\n const thisExpr = this.parseIdentifier() || this.parsePrimaryOrVar();\n return this.expression(ParameterExpr, { this: thisExpr });\n }\n\n parsePlaceholder (): Expression | undefined {\n if (this.matchSet(new Set(Object.keys(this._constructor.PLACEHOLDER_PARSERS)) as Set<TokenType>)) {\n const placeholder = this._constructor.PLACEHOLDER_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN]?.call(this);\n if (placeholder) {\n return placeholder;\n }\n this.advance(-1);\n }\n return undefined;\n }\n\n parseStarOp (...keywords: string[]): Expression[] | undefined {\n if (!this.matchTexts(keywords)) {\n return undefined;\n }\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n return this.parseWrappedCsv(() => this.parseExpression());\n }\n\n const expression = this.parseAlias(this.parseDisjunction(), { explicit: true });\n return expression ? [expression] : undefined;\n }\n\n parseCsv<E extends Expression> (parseMethod: () => E | undefined, options: { sep?: TokenType } = {}): E[] {\n const { sep = TokenType.COMMA } = options;\n let parseResult = parseMethod();\n const items: E[] = parseResult !== undefined ? [parseResult] : [];\n\n while (this.match(sep)) {\n this.addComments(parseResult);\n parseResult = parseMethod();\n if (parseResult !== undefined) {\n items.push(parseResult);\n }\n }\n\n return items;\n }\n\n parseIdentifier (): Expression | undefined {\n if (this.match(TokenType.IDENTIFIER)) {\n return this.identifierExpression(undefined, { quoted: true });\n }\n return this.parsePlaceholder();\n }\n\n parseVar (options: {\n anyToken?: boolean;\n tokens?: Set<TokenType>;\n upper?: boolean;\n } = {}): Expression | undefined {\n const {\n anyToken = false,\n tokens,\n upper = false,\n } = options;\n\n if (\n (anyToken && this.advanceAny())\n || this.match(TokenType.VAR)\n || (tokens && this.matchSet(tokens))\n ) {\n const text = upper ? (this.prev?.text ?? '').toUpperCase() : this.prev?.text ?? '';\n return this.expression(VarExpr, { this: text });\n }\n return this.parsePlaceholder();\n }\n\n advanceAny (options: { ignoreReserved?: boolean } = {}): Token | undefined {\n const { ignoreReserved = false } = options;\n if (this.curr && (ignoreReserved || !this._constructor.RESERVED_TOKENS.has(this.curr.tokenType))) {\n this.advance();\n return this.prev;\n }\n return undefined;\n }\n\n parseVarOrString (options: { upper?: boolean } = {}): Expression | undefined {\n const { upper = false } = options;\n return this.parseString() || this.parseVar({\n anyToken: true,\n upper,\n });\n }\n\n parsePrimaryOrVar (): Expression | undefined {\n return this.parsePrimary() || this.parseVar({ anyToken: true });\n }\n\n parseNull (): Expression | undefined {\n if (this.matchSet(new Set([TokenType.NULL, TokenType.UNKNOWN]))) {\n return this._constructor.PRIMARY_PARSERS[TokenType.NULL]?.call(this, this.prev as Token);\n }\n return this.parsePlaceholder();\n }\n\n parseBoolean (): Expression | undefined {\n if (this.match(TokenType.TRUE)) {\n return this._constructor.PRIMARY_PARSERS[TokenType.TRUE]?.call(this, this.prev as Token);\n }\n if (this.match(TokenType.FALSE)) {\n return this._constructor.PRIMARY_PARSERS[TokenType.FALSE]?.call(this, this.prev as Token);\n }\n return this.parsePlaceholder();\n }\n\n parseString (): Expression | undefined {\n if (this.matchSet(new Set(Object.keys(this._constructor.STRING_PARSERS)) as Set<TokenType>)) {\n return this._constructor.STRING_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN]?.call(this, this.prev as Token);\n }\n return this.parsePlaceholder();\n }\n\n parseStringAsIdentifier (): IdentifierExpr | undefined {\n const id = (this.match(TokenType.STRING) || undefined) && this.prev?.text;\n const output = id === undefined ? undefined : toIdentifier(id, { quoted: true });\n if (output && this.prev) {\n output.updatePositions(this.prev);\n }\n return output;\n }\n\n parseNumber (): Expression | undefined {\n if (this.matchSet(new Set(Object.keys(this._constructor.NUMERIC_PARSERS)) as Set<TokenType>)) {\n return this._constructor.NUMERIC_PARSERS[this.prev?.tokenType ?? TokenType.UNKNOWN]?.call(this, this.prev as Token);\n }\n return this.parsePlaceholder();\n }\n\n parseHavingMax (thisExpr: Expression | undefined): Expression | undefined {\n if (this.match(TokenType.HAVING)) {\n this.matchTexts(['MAX', 'MIN']);\n const max = this.prev?.text.toUpperCase() !== 'MIN';\n return this.expression(\n HavingMaxExpr,\n {\n this: thisExpr,\n expression: this.parseColumn(),\n max,\n },\n );\n }\n\n return thisExpr;\n }\n\n parseWindow (thisExpr: Expression | undefined, options: { alias?: boolean } = {}): Expression | undefined {\n const { alias = false } = options;\n const func = thisExpr;\n const comments = func instanceof Expression ? func.comments : undefined;\n\n if (this.matchTextSeq(['WITHIN', 'GROUP'])) {\n const order = this.parseWrapped(() => this.parseOrder());\n thisExpr = this.expression(WithinGroupExpr, {\n this: thisExpr,\n expression: order,\n });\n }\n\n if (this.matchPair(TokenType.FILTER, TokenType.L_PAREN)) {\n this.match(TokenType.WHERE);\n thisExpr = this.expression(\n FilterExpr,\n {\n this: thisExpr,\n expression: this.parseWhere({ skipWhereToken: true }),\n },\n );\n this.matchRParen();\n }\n\n if (thisExpr instanceof AggFuncExpr) {\n const ignoreRespect = thisExpr.find([IgnoreNullsExpr, RespectNullsExpr]);\n\n if (ignoreRespect && ignoreRespect !== thisExpr) {\n ignoreRespect.replace(ignoreRespect.args.this);\n thisExpr = this.expression(ignoreRespect._constructor, { this: thisExpr });\n }\n }\n\n thisExpr = this.parseRespectOrIgnoreNulls(thisExpr);\n\n let over: string | undefined;\n if (alias) {\n over = undefined;\n this.match(TokenType.ALIAS);\n } else if (!this.matchSet(this._constructor.WINDOW_BEFORE_PAREN_TOKENS)) {\n return thisExpr;\n } else {\n over = this.prev?.text.toUpperCase();\n }\n\n if (comments && func instanceof Expression) {\n func.popComments();\n }\n\n if (!this.match(TokenType.L_PAREN)) {\n return this.expression(\n WindowExpr,\n {\n comments,\n this: thisExpr,\n alias: this.parseIdVar({ anyToken: false }),\n over,\n },\n );\n }\n\n const windowAlias = this.parseIdVar({\n anyToken: false,\n tokens: this._constructor.WINDOW_ALIAS_TOKENS,\n });\n\n let first: boolean | undefined = this.match(TokenType.FIRST) || undefined;\n if (this.matchTextSeq('LAST')) {\n first = false;\n }\n\n const [partition, order] = this.parsePartitionAndOrder();\n const kind = (this.matchSet(new Set([TokenType.ROWS, TokenType.RANGE])) || undefined) && this.prev?.text;\n\n let spec: WindowSpecExpr | undefined;\n if (kind) {\n this.match(TokenType.BETWEEN);\n const start = this.parseWindowSpec();\n\n const end = this.match(TokenType.AND) ? this.parseWindowSpec() : {};\n const exclude = this.matchTextSeq('EXCLUDE')\n ? this.parseVarFromOptions(this._constructor.WINDOW_EXCLUDE_OPTIONS)\n : undefined;\n\n spec = this.expression(\n WindowSpecExpr,\n {\n kind,\n start: start.value,\n startSide: start.side,\n end: end.value,\n endSide: end.side,\n exclude,\n },\n );\n } else {\n spec = undefined;\n }\n\n this.matchRParen();\n\n const window = this.expression(\n WindowExpr,\n {\n comments,\n this: thisExpr,\n partitionBy: partition,\n order,\n spec,\n alias: windowAlias,\n over,\n first,\n },\n );\n\n if (this.matchSet(this._constructor.WINDOW_BEFORE_PAREN_TOKENS, { advance: false })) {\n return this.parseWindow(window, { alias });\n }\n\n return window;\n }\n\n parsePartitionAndOrder (): [Expression[], Expression | undefined] {\n return [this.parsePartitionBy(), this.parseOrder()];\n }\n\n parseWindowSpec (): {\n value?: string | Expression;\n side?: string;\n } {\n this.match(TokenType.BETWEEN);\n\n return {\n value:\n (this.matchTextSeq('UNBOUNDED') && 'UNBOUNDED')\n || (this.matchTextSeq(['CURRENT', 'ROW']) && 'CURRENT ROW')\n || this.parseBitwise()\n || undefined,\n side: (this.matchTexts(this._constructor.WINDOW_SIDES) || undefined) && this.prev?.text,\n };\n }\n\n parseAlias (thisExpr: Expression | undefined, options: { explicit?: boolean } = {}): Expression | undefined {\n const { explicit = false } = options;\n\n if (this.canParseLimitOrOffset()) {\n return thisExpr;\n }\n\n const anyToken = this.match(TokenType.ALIAS);\n const comments = this.prevComments ?? [];\n\n if (explicit && !anyToken) {\n return thisExpr;\n }\n\n if (this.match(TokenType.L_PAREN)) {\n const aliases = this.expression(\n AliasesExpr,\n {\n comments,\n this: thisExpr,\n expressions: this.parseCsv(() => this.parseIdVar({ anyToken })),\n },\n );\n this.matchRParen(aliases);\n return aliases;\n }\n\n const alias =\n this.parseIdVar({\n anyToken,\n tokens: this._constructor.ALIAS_TOKENS,\n })\n || (this._constructor.STRING_ALIASES && this.parseStringAsIdentifier());\n\n if (alias) {\n comments.push(...alias.popComments());\n thisExpr = this.expression(AliasExpr, {\n comments,\n this: thisExpr,\n alias,\n });\n const column = (thisExpr as AliasExpr).args.this;\n\n if ((!thisExpr.comments || thisExpr.comments.length === 0) && column && column.comments && 0 < column.comments.length) {\n thisExpr.comments = column.popComments();\n }\n }\n\n return thisExpr;\n }\n\n parseOpenJson (): OpenJsonExpr {\n const thisExpr = this.parseBitwise();\n const path = (this.match(TokenType.COMMA) || undefined) && this.parseString();\n\n const parseOpenJsonColumnDef = (): OpenJsonColumnDefExpr => {\n const thisCol = this.parseField({ anyToken: true });\n const kind = this.parseTypes();\n const pathCol = this.parseString();\n const asJson = this.matchPair(TokenType.ALIAS, TokenType.JSON) || undefined;\n\n return this.expression(\n OpenJsonColumnDefExpr,\n {\n this: thisCol,\n kind,\n path: pathCol,\n asJson,\n },\n );\n };\n\n let expressions: OpenJsonColumnDefExpr[] | undefined;\n if (this.matchPair(TokenType.R_PAREN, TokenType.WITH)) {\n this.matchLParen();\n expressions = this.parseCsv(parseOpenJsonColumnDef);\n }\n\n return this.expression(OpenJsonExpr, {\n this: thisExpr,\n path,\n expressions,\n });\n }\n\n parsePosition (options: { haystackFirst?: boolean } = {}): StrPositionExpr {\n const { haystackFirst = false } = options;\n const args = this.parseCsv(() => this.parseBitwise());\n\n if (this.match(TokenType.IN)) {\n return this.expression(\n StrPositionExpr,\n {\n this: this.parseBitwise(),\n substr: seqGet(args, 0),\n },\n );\n }\n\n const haystack = haystackFirst ? seqGet(args, 0) : seqGet(args, 1);\n const needle = haystackFirst ? seqGet(args, 1) : seqGet(args, 0);\n\n return this.expression(\n StrPositionExpr,\n {\n this: haystack,\n substr: needle,\n position: seqGet(args, 2),\n },\n );\n }\n\n parseJoinHint (funcName: string): JoinHintExpr {\n const args = this.parseCsv(() => this.parseTable());\n return new JoinHintExpr({\n this: funcName.toUpperCase(),\n expressions: args,\n });\n }\n\n parseSubstring (): SubstringExpr {\n const args = this.parseCsv(() => this.parseBitwise()) as Expression[];\n\n let start: Expression | undefined;\n let length: Expression | undefined;\n\n while (this.curr) {\n if (this.match(TokenType.FROM)) {\n start = this.parseBitwise();\n } else if (this.match(TokenType.FOR)) {\n if (!start) {\n start = LiteralExpr.number(1);\n }\n length = this.parseBitwise();\n } else {\n break;\n }\n }\n\n if (start) {\n args.push(start);\n }\n if (length) {\n args.push(length);\n }\n\n return this.validateExpression(SubstringExpr.fromArgList(args), args);\n }\n\n parseTrim (): TrimExpr {\n let position: string | undefined;\n let collation: Expression | undefined;\n let expression: Expression | undefined;\n\n if (this.matchTexts(this._constructor.TRIM_TYPES)) {\n position = this.prev?.text.toUpperCase();\n }\n\n let thisExpr = this.parseBitwise();\n if (this.matchSet(new Set([TokenType.FROM, TokenType.COMMA]))) {\n const invertOrder = this.prev?.tokenType === TokenType.FROM || this._constructor.TRIM_PATTERN_FIRST;\n expression = this.parseBitwise();\n\n if (invertOrder) {\n [thisExpr, expression] = [expression, thisExpr];\n }\n }\n\n if (this.match(TokenType.COLLATE)) {\n collation = this.parseBitwise();\n }\n\n return this.expression(\n TrimExpr,\n {\n this: thisExpr,\n position,\n expression,\n collation,\n },\n );\n }\n\n parseWindowClause (): Expression[] | undefined {\n return (this.match(TokenType.WINDOW) || undefined) && this.parseCsv(() => this.parseNamedWindow());\n }\n\n parseNamedWindow (): Expression | undefined {\n return this.parseWindow(this.parseIdVar(), { alias: true });\n }\n\n parseRespectOrIgnoreNulls (thisExpr: Expression | undefined): Expression | undefined {\n if (this.matchTextSeq(['IGNORE', 'NULLS'])) {\n return this.expression(IgnoreNullsExpr, { this: thisExpr });\n }\n if (this.matchTextSeq(['RESPECT', 'NULLS'])) {\n return this.expression(RespectNullsExpr, { this: thisExpr });\n }\n return thisExpr;\n }\n\n parseJsonObject (options: { agg?: boolean } = {}): JsonObjectExpr | JsonObjectAggExpr {\n const { agg = false } = options;\n const star = this.parseStar();\n const expressions = star\n ? [star]\n : this.parseCsv(() => this.parseFormatJson(this.parseJsonKeyValue()));\n\n const nullHandling = this.parseOnHandling('NULL', ['NULL', 'ABSENT']);\n\n let uniqueKeys: boolean | undefined;\n if (this.matchTextSeq(['WITH', 'UNIQUE'])) {\n uniqueKeys = true;\n } else if (this.matchTextSeq(['WITHOUT', 'UNIQUE'])) {\n uniqueKeys = false;\n }\n\n this.matchTextSeq('KEYS');\n\n const returnType = (this.matchTextSeq('RETURNING') || undefined) && this.parseFormatJson(this.parseType());\n const encoding = (this.matchTextSeq('ENCODING') || undefined) && this.parseVar();\n\n return this.expression(\n agg ? JsonObjectAggExpr : JsonObjectExpr,\n {\n expressions,\n nullHandling,\n uniqueKeys,\n returnType,\n encoding,\n },\n );\n }\n\n parseJsonColumnDef (): JsonColumnDefExpr {\n let thisExpr: Expression | undefined;\n let ordinality: boolean | undefined;\n let kind: Expression | undefined;\n let nested: boolean | undefined;\n\n if (!this.matchTextSeq('NESTED')) {\n thisExpr = this.parseIdVar();\n ordinality = this.matchPair(TokenType.FOR, TokenType.ORDINALITY) || undefined;\n kind = this.parseTypes({ allowIdentifiers: false });\n nested = undefined;\n } else {\n thisExpr = undefined;\n ordinality = undefined;\n kind = undefined;\n nested = true;\n }\n\n const path = (this.matchTextSeq('PATH') || undefined) && this.parseString();\n const nestedSchema = nested && this.parseJsonSchema();\n\n return this.expression(\n JsonColumnDefExpr,\n {\n this: thisExpr,\n kind,\n path,\n nestedSchema,\n ordinality,\n },\n );\n }\n\n parseJsonSchema (): JsonSchemaExpr {\n this.matchTextSeq('COLUMNS');\n return this.expression(\n JsonSchemaExpr,\n {\n expressions: this.parseWrappedCsv(() => this.parseJsonColumnDef(), { optional: true }),\n },\n );\n }\n\n parseJsonTable (): JsonTableExpr {\n const thisExpr = this.parseFormatJson(this.parseBitwise());\n if (!thisExpr) {\n this.raiseError('Expected expression for JSON_TABLE');\n }\n const path = (this.match(TokenType.COMMA) || undefined) && this.parseString();\n const errorHandling = this.parseOnHandling('ERROR', ['ERROR', 'NULL']);\n const emptyHandling = this.parseOnHandling('EMPTY', ['ERROR', 'NULL']);\n const schema = this.parseJsonSchema();\n\n return new JsonTableExpr({\n this: thisExpr,\n schema,\n path,\n errorHandling,\n emptyHandling,\n });\n }\n\n parseMatchAgainst (): MatchAgainstExpr {\n let expressions: Expression[];\n\n if (this.matchTextSeq('TABLE')) {\n expressions = [];\n const table = this.parseTable();\n if (table) {\n expressions = [table];\n }\n } else {\n expressions = this.parseCsv(() => this.parseColumn());\n }\n\n this.matchTextSeq([\n ')',\n 'AGAINST',\n '(',\n ]);\n\n const thisExpr = this.parseString();\n\n let modifier: string | undefined;\n if (this.matchTextSeq([\n 'IN',\n 'NATURAL',\n 'LANGUAGE',\n 'MODE',\n ])) {\n modifier = 'IN NATURAL LANGUAGE MODE';\n if (this.matchTextSeq([\n 'WITH',\n 'QUERY',\n 'EXPANSION',\n ])) {\n modifier = `${modifier} WITH QUERY EXPANSION`;\n }\n } else if (this.matchTextSeq([\n 'IN',\n 'BOOLEAN',\n 'MODE',\n ])) {\n modifier = 'IN BOOLEAN MODE';\n } else if (this.matchTextSeq([\n 'WITH',\n 'QUERY',\n 'EXPANSION',\n ])) {\n modifier = 'WITH QUERY EXPANSION';\n } else {\n modifier = undefined;\n }\n\n return this.expression(\n MatchAgainstExpr,\n {\n this: thisExpr,\n expressions,\n modifier,\n },\n );\n }\n\n parseJsonKeyValue (): JsonKeyValueExpr | undefined {\n this.matchTextSeq('KEY');\n const key = this.parseColumn();\n this.matchSet(this._constructor.JSON_KEY_VALUE_SEPARATOR_TOKENS);\n this.matchTextSeq('VALUE');\n const value = this.parseBitwise();\n\n if (!key && !value) {\n return undefined;\n }\n return this.expression(JsonKeyValueExpr, {\n this: key,\n expression: value,\n });\n }\n\n parseFormatJson (thisExpr: Expression | undefined): Expression | undefined {\n if (!thisExpr || !this.matchTextSeq(['FORMAT', 'JSON'])) {\n return thisExpr;\n }\n\n return this.expression(FormatJsonExpr, { this: thisExpr });\n }\n\n parseOnCondition (): OnConditionExpr | undefined {\n let empty: string | Expression | undefined;\n let error: string | Expression | undefined;\n\n if (this._dialectConstructor.ON_CONDITION_EMPTY_BEFORE_ERROR) {\n empty = this.parseOnHandling('EMPTY', [...this._constructor.ON_CONDITION_TOKENS]);\n error = this.parseOnHandling('ERROR', [...this._constructor.ON_CONDITION_TOKENS]);\n } else {\n error = this.parseOnHandling('ERROR', [...this._constructor.ON_CONDITION_TOKENS]);\n empty = this.parseOnHandling('EMPTY', [...this._constructor.ON_CONDITION_TOKENS]);\n }\n\n const nullHandling = this.parseOnHandling('NULL', [...this._constructor.ON_CONDITION_TOKENS]);\n\n if (!empty && !error && !nullHandling) {\n return undefined;\n }\n\n return this.expression(\n OnConditionExpr,\n {\n empty,\n error,\n null: nullHandling,\n },\n );\n }\n\n parseOnHandling (on: string, values: string[]): string | Expression | undefined {\n for (const value of values) {\n if (this.matchTextSeq([\n value,\n 'ON',\n on,\n ])) {\n return `${value} ON ${on}`;\n }\n }\n\n const index = this.index;\n if (this.match(TokenType.DEFAULT)) {\n const defaultValue = this.parseBitwise();\n if (this.matchTextSeq(['ON', on])) {\n return defaultValue;\n }\n\n this.retreat(index);\n }\n\n return undefined;\n }\n\n parseConvert (options: {\n strict: boolean;\n safe?: boolean;\n }): Expression | undefined {\n const {\n safe, strict,\n } = options;\n const thisExpr = this.parseBitwise();\n\n let to: Expression | undefined;\n if (this.match(TokenType.USING)) {\n to = this.expression(\n CharacterSetExpr,\n { this: this.parseVar({ tokens: new Set([TokenType.BINARY]) }) },\n );\n } else if (this.match(TokenType.COMMA)) {\n to = this.parseTypes();\n } else {\n to = undefined;\n }\n\n return this.buildCast({\n strict,\n this: thisExpr,\n to,\n safe,\n });\n }\n\n parseXmlElement (): XmlElementExpr {\n let evalname: boolean | undefined;\n let thisExpr: Expression | undefined;\n\n if (this.matchTextSeq('EVALNAME')) {\n evalname = true;\n thisExpr = this.parseBitwise();\n } else {\n evalname = undefined;\n this.matchTextSeq('NAME');\n thisExpr = this.parseIdVar();\n }\n\n return this.expression(\n XmlElementExpr,\n {\n this: thisExpr,\n expressions: (this.match(TokenType.COMMA) || undefined) && this.parseCsv(() => this.parseBitwise()),\n evalname,\n },\n );\n }\n\n parseXmlTable (): XmlTableExpr {\n let namespaces: XmlNamespaceExpr[] | undefined;\n let passing: Expression[] | undefined;\n let columns: Expression[] | undefined;\n\n if (this.matchTextSeq(['XMLNAMESPACES', '('])) {\n namespaces = this.parseXmlNamespace();\n this.match(TokenType.R_PAREN);\n this.match(TokenType.COMMA);\n }\n\n const thisExpr = this.parseString();\n\n if (this.matchTextSeq('PASSING')) {\n this.matchTextSeq(['BY', 'VALUE']);\n passing = this.parseCsv(() => this.parseColumn());\n }\n\n const byRef = this.matchTextSeq([\n 'RETURNING',\n 'SEQUENCE',\n 'BY',\n 'REF',\n ]) || undefined;\n\n if (this.matchTextSeq('COLUMNS')) {\n columns = this.parseCsv(() => this.parseFieldDef());\n }\n\n return this.expression(\n XmlTableExpr,\n {\n this: thisExpr,\n namespaces,\n passing,\n columns,\n byRef,\n },\n );\n }\n\n parseXmlNamespace (): XmlNamespaceExpr[] {\n const namespaces: XmlNamespaceExpr[] = [];\n\n while (true) {\n let uri: Expression | undefined;\n if (this.match(TokenType.DEFAULT)) {\n uri = this.parseString();\n } else {\n uri = this.parseAlias(this.parseString());\n }\n namespaces.push(this.expression(XmlNamespaceExpr, { this: uri }));\n if (!this.match(TokenType.COMMA)) {\n break;\n }\n }\n\n return namespaces;\n }\n\n parseDecode (): DecodeExpr | DecodeCaseExpr | undefined {\n const args = this.parseCsv(() => this.parseDisjunction());\n\n if (args.length < 3) {\n return this.expression(DecodeExpr, {\n this: seqGet(args, 0),\n charset: seqGet(args, 1),\n });\n }\n\n return this.expression(DecodeCaseExpr, { expressions: args });\n }\n\n parseGapFill (): GapFillExpr {\n this.match(TokenType.TABLE);\n const thisExpr = this.parseTable();\n\n this.match(TokenType.COMMA);\n const args = [thisExpr, ...this.parseCsv(() => this.parseLambda())].filter((e): e is Expression => e instanceof Expression);\n\n const gapFill = GapFillExpr.fromArgList(args);\n return this.validateExpression(gapFill, args);\n }\n\n parseChar (): ChrExpr {\n return this.expression(\n ChrExpr,\n {\n expressions: this.parseCsv(() => this.parseAssignment()),\n charset: (this.match(TokenType.USING) || undefined) && this.parseVar(),\n },\n );\n }\n\n parseCast (options: {\n strict: boolean;\n safe?: boolean;\n }): Expression {\n const {\n safe, strict,\n } = options;\n let thisExpr = this.parseDisjunction();\n\n if (!this.match(TokenType.ALIAS)) {\n if (this.match(TokenType.COMMA)) {\n return this.expression(CastToStrTypeExpr, {\n this: thisExpr,\n to: this.parseString(),\n });\n }\n\n this.raiseError('Expected AS after CAST');\n }\n\n let fmt: Expression | undefined;\n let to = this.parseTypes() as DataTypeExpr | CharacterSetExpr | undefined;\n\n let defaultValue: Expression | undefined;\n if (this.match(TokenType.DEFAULT)) {\n defaultValue = this.parseBitwise();\n this.matchTextSeq([\n 'ON',\n 'CONVERSION',\n 'ERROR',\n ]);\n }\n\n if (this.matchSet(new Set([TokenType.FORMAT, TokenType.COMMA]))) {\n const fmtString = this.parseString() as StringExpr;\n fmt = this.parseAtTimeZone(fmtString);\n\n if (!to) {\n to = DataTypeExpr.build(DataTypeExprKind.UNKNOWN);\n }\n if (to && DataTypeExpr.TEMPORAL_TYPES.has(to.args.this as DataTypeExprKind)) {\n thisExpr = this.expression(\n to.args.this === DataTypeExprKind.DATE ? StrToDateExpr : StrToTimeExpr,\n {\n this: thisExpr,\n format: LiteralExpr.string(\n formatTime(\n (fmtString?.args.this ?? '') as string,\n (Object.keys(this._dialectConstructor.FORMAT_MAPPING).length ? this._dialectConstructor.FORMAT_MAPPING : this._dialectConstructor.TIME_MAPPING),\n (Object.keys(this._dialectConstructor.FORMAT_MAPPING).length ? this._dialectConstructor.FORMAT_TRIE : this._dialectConstructor.TIME_TRIE),\n ) ?? '',\n ),\n safe,\n },\n );\n\n if (fmt instanceof AtTimeZoneExpr && thisExpr instanceof StrToTimeExpr) {\n thisExpr.setArgKey('zone', fmt.args.zone);\n }\n return thisExpr;\n }\n } else if (!to) {\n this.raiseError('Expected TYPE after CAST');\n } else if (to instanceof IdentifierExpr) {\n to = DataTypeExpr.build(to.name, {\n dialect: this.dialect,\n udt: true,\n });\n } else if (to.args.this === DataTypeExprKind.CHAR) {\n if (this.match(TokenType.CHARACTER_SET)) {\n to = this.expression(CharacterSetExpr, { this: this.parseVarOrString() });\n }\n }\n\n return this.buildCast({\n strict,\n this: thisExpr,\n to,\n format: fmt,\n safe,\n action: this.parseVarFromOptions(this._constructor.CAST_ACTIONS, { raiseUnmatched: false }),\n default: defaultValue,\n });\n }\n\n parseStringAgg (): GroupConcatExpr {\n let args: (Expression | undefined)[];\n\n if (this.match(TokenType.DISTINCT)) {\n args = [this.expression(DistinctExpr, { expressions: [this.parseDisjunction()] })];\n if (this.match(TokenType.COMMA)) {\n args.push(...this.parseCsv(() => this.parseDisjunction()));\n }\n } else {\n args = this.parseCsv(() => this.parseDisjunction());\n }\n\n let onOverflow: Expression | undefined;\n if (this.matchTextSeq(['ON', 'OVERFLOW'])) {\n if (this.matchTextSeq('ERROR')) {\n onOverflow = var_('ERROR');\n } else {\n this.matchTextSeq('TRUNCATE');\n onOverflow = this.expression(\n OverflowTruncateBehaviorExpr,\n {\n this: this.parseString(),\n withCount:\n this.matchTextSeq(['WITH', 'COUNT'])\n || !this.matchTextSeq(['WITHOUT', 'COUNT']),\n },\n );\n }\n } else {\n onOverflow = undefined;\n }\n\n const index = this.index;\n if (!this.match(TokenType.R_PAREN) && 0 < args.length) {\n args[0] = this.parseLimit(this.parseOrder({ thisExpr: args[0] }));\n return this.expression(GroupConcatExpr, {\n this: args[0],\n separator: seqGet(args, 1),\n });\n }\n\n if (!this.matchTextSeq(['WITHIN', 'GROUP'])) {\n this.retreat(index);\n return this.validateExpression(GroupConcatExpr.fromArgList(args.filter((a): a is Expression => a instanceof Expression)), args);\n }\n\n this.matchLParen();\n\n return this.expression(\n GroupConcatExpr,\n {\n this: this.parseOrder({ thisExpr: seqGet(args, 0) }),\n separator: seqGet(args, 1),\n onOverflow,\n },\n );\n }\n\n parseBracket (thisExpr?: Expression): Expression | undefined {\n if (!this.matchSet(new Set([TokenType.L_BRACKET, TokenType.L_BRACE]))) {\n return thisExpr;\n }\n\n let parseMap: boolean;\n if (this._constructor.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS) {\n const mapToken = seqGet(this.tokens, this.index - 2);\n parseMap = mapToken !== undefined && mapToken.text.toUpperCase() === 'MAP';\n } else {\n parseMap = false;\n }\n\n const bracketKind = this.prev?.tokenType;\n if (\n bracketKind === TokenType.L_BRACE\n && this.curr\n && this.curr.tokenType === TokenType.VAR\n && this._constructor.ODBC_DATETIME_LITERALS[this.curr.text.toLowerCase()]\n ) {\n return this.parseOdbcDatetimeLiteral();\n }\n\n const expressions = this.parseCsv(() =>\n this.parseBracketKeyValue({ isMap: bracketKind === TokenType.L_BRACE }));\n\n if (bracketKind === TokenType.L_BRACKET && !this.match(TokenType.R_BRACKET)) {\n this.raiseError('Expected ]');\n } else if (bracketKind === TokenType.L_BRACE && !this.match(TokenType.R_BRACE)) {\n this.raiseError('Expected }');\n }\n\n if (bracketKind === TokenType.L_BRACE) {\n thisExpr = this.expression(\n StructExpr,\n {\n expressions: this.kvToPropEq(expressions, {\n parseMap,\n }),\n },\n );\n } else if (!thisExpr && bracketKind) {\n thisExpr = buildArrayConstructor(ArrayExpr, expressions, bracketKind, this.dialect);\n } else if (thisExpr && bracketKind) {\n const constructorType = this._constructor.ARRAY_CONSTRUCTORS[thisExpr?.name.toUpperCase() ?? ''];\n if (constructorType) {\n return buildArrayConstructor(constructorType, expressions, bracketKind, this.dialect);\n }\n\n const adjustedExpressions = applyIndexOffset(\n thisExpr,\n expressions,\n -this._dialectConstructor.INDEX_OFFSET,\n { dialect: this.dialect },\n );\n thisExpr = this.expression(\n BracketExpr,\n {\n this: thisExpr,\n expressions: adjustedExpressions,\n comments: thisExpr.popComments(),\n },\n );\n }\n\n this.addComments(thisExpr);\n return this.parseBracket(thisExpr);\n }\n\n parseSlice (thisExpr: Expression | undefined): Expression | undefined {\n if (!this.match(TokenType.COLON)) {\n return thisExpr;\n }\n\n let end: Expression | undefined;\n if (this.matchPair(TokenType.DASH, TokenType.COLON, { advance: false })) {\n this.advance();\n end = LiteralExpr.number('1').neg();\n } else {\n end = this.parseAssignment();\n }\n\n const step = this.match(TokenType.COLON) ? this.parseUnary() : undefined;\n return this.expression(SliceExpr, {\n this: thisExpr,\n expression: end,\n step,\n });\n }\n\n parseCase (): Expression | undefined {\n if (this.match(TokenType.DOT, { advance: false })) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n const ifs: IfExpr[] = [];\n let defaultCase: Expression | undefined;\n\n const comments = this.prevComments;\n const expression = this.parseDisjunction();\n\n while (this.match(TokenType.WHEN)) {\n const thisExpr = this.parseDisjunction();\n this.match(TokenType.THEN);\n const then = this.parseDisjunction();\n ifs.push(this.expression(IfExpr, {\n this: thisExpr,\n true: then,\n }));\n }\n\n if (this.match(TokenType.ELSE)) {\n defaultCase = this.parseDisjunction();\n }\n\n if (!this.match(TokenType.END)) {\n if (\n defaultCase instanceof IntervalExpr\n && (typeof defaultCase.args.this === 'string' ? defaultCase.args.this : defaultCase.args.this?.sql())?.toUpperCase() === 'END'\n ) {\n defaultCase = column({ col: 'interval' });\n } else {\n this.raiseError('Expected END after CASE', this.prev);\n }\n }\n\n return this.expression(\n CaseExpr,\n {\n comments,\n this: expression,\n ifs,\n default: defaultCase,\n },\n );\n }\n\n parseIf (): Expression | undefined {\n if (this.match(TokenType.L_PAREN)) {\n const args = this.parseCsv(() =>\n this.parseAlias(this.parseAssignment(), { explicit: true }));\n const thisExpr = this.validateExpression(IfExpr.fromArgList(args), args);\n this.matchRParen();\n return thisExpr;\n } else {\n const index = this.index - 1;\n\n if (this._constructor.NO_PAREN_IF_COMMANDS && index === 0) {\n return this.parseAsCommand(this.prev);\n }\n\n const condition = this.parseDisjunction();\n\n if (!condition) {\n this.retreat(index);\n return undefined;\n }\n\n this.match(TokenType.THEN);\n const trueExpr = this.parseDisjunction();\n const falseExpr = this.match(TokenType.ELSE) ? this.parseDisjunction() : undefined;\n this.match(TokenType.END);\n return this.expression(IfExpr, {\n this: condition,\n true: trueExpr,\n false: falseExpr,\n });\n }\n }\n\n parseNextValueFor (): Expression | undefined {\n if (!this.matchTextSeq(['VALUE', 'FOR'])) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n return this.expression(\n NextValueForExpr,\n {\n this: this.parseColumn(),\n order: (this.match(TokenType.OVER) || undefined) && this.parseWrapped(() => this.parseOrder()),\n },\n );\n }\n\n parseExtract (): ExtractExpr | AnonymousExpr {\n const thisExpr = this.parseFunction() || this.parseVarOrString({ upper: true });\n\n if (this.match(TokenType.FROM)) {\n return this.expression(ExtractExpr, {\n this: thisExpr,\n expression: this.parseBitwise(),\n });\n }\n\n if (!this.match(TokenType.COMMA)) {\n this.raiseError('Expected FROM or comma after EXTRACT', this.prev);\n }\n\n return this.expression(ExtractExpr, {\n this: thisExpr,\n expression: this.parseBitwise(),\n });\n }\n\n parsePrimaryKeyPart (): Expression | undefined {\n return this.parseField();\n }\n\n parsePeriodForSystemTime (): PeriodForSystemTimeConstraintExpr | undefined {\n if (!this.match(TokenType.TIMESTAMP_SNAPSHOT)) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n const idVars = this.parseWrappedIdVars();\n return this.expression(\n PeriodForSystemTimeConstraintExpr,\n {\n this: seqGet(idVars, 0),\n expression: seqGet(idVars, 1),\n },\n );\n }\n\n parsePrimaryKey (options: {\n wrappedOptional?: boolean;\n inProps?: boolean;\n namedPrimaryKey?: boolean;\n } = {}): PrimaryKeyColumnConstraintExpr | PrimaryKeyExpr {\n const {\n wrappedOptional = false,\n inProps = false,\n namedPrimaryKey = false,\n } = options;\n\n const desc =\n this.matchSet(new Set([TokenType.ASC, TokenType.DESC]))\n ? this.prev?.tokenType === TokenType.DESC\n : undefined;\n\n let thisExpr: Expression | undefined;\n if (\n namedPrimaryKey\n && !((this.curr?.text.toUpperCase() || '') in this._constructor.CONSTRAINT_PARSERS)\n && this.next\n && this.next.tokenType === TokenType.L_PAREN\n ) {\n thisExpr = this.parseIdVar();\n }\n\n if (!inProps && !this.match(TokenType.L_PAREN, { advance: false })) {\n return this.expression(\n PrimaryKeyColumnConstraintExpr,\n {\n desc,\n options: this.parseKeyConstraintOptions(),\n },\n );\n }\n\n const expressions = this.parseWrappedCsv(\n () => this.parsePrimaryKeyPart(),\n { optional: wrappedOptional },\n );\n\n return this.expression(\n PrimaryKeyExpr,\n {\n this: thisExpr,\n expressions,\n include: this.parseIndexParams(),\n options: this.parseKeyConstraintOptions(),\n },\n );\n }\n\n parseBracketKeyValue (_options: { isMap?: boolean } = {}): Expression | undefined {\n return this.parseSlice(this.parseAlias(this.parseDisjunction(), { explicit: true }));\n }\n\n parseOdbcDatetimeLiteral (): Expression {\n this.match(TokenType.VAR);\n const expClass = this._constructor.ODBC_DATETIME_LITERALS[this.prev?.text ?? ''.toLowerCase()];\n const expression = this.expression(expClass, { this: this.parseString() });\n if (!this.match(TokenType.R_BRACE)) {\n this.raiseError('Expected }');\n }\n return expression;\n }\n\n parseUniqueKey (): Expression | undefined {\n return this.parseIdVar({ anyToken: false });\n }\n\n parseUnique (): UniqueColumnConstraintExpr {\n this.matchTexts(['KEY', 'INDEX']);\n return this.expression(\n UniqueColumnConstraintExpr,\n {\n nulls: this.matchTextSeq([\n 'NULLS',\n 'NOT',\n 'DISTINCT',\n ]) || undefined,\n this: this.parseSchema({ this: this.parseUniqueKey() }),\n indexType: (this.match(TokenType.USING) || undefined) && this.advanceAny() && this.prev?.text,\n onConflict: this.parseOnConflict(),\n options: this.parseKeyConstraintOptions(),\n },\n );\n }\n\n parseKeyConstraintOptions (): string[] {\n const options: string[] = [];\n while (true) {\n if (!this.curr) {\n break;\n }\n\n if (this.match(TokenType.ON)) {\n let action: string | undefined;\n const on = this.advanceAny() && this.prev?.text;\n\n if (this.matchTextSeq(['NO', 'ACTION'])) {\n action = 'NO ACTION';\n } else if (this.matchTextSeq('CASCADE')) {\n action = 'CASCADE';\n } else if (this.matchTextSeq('RESTRICT')) {\n action = 'RESTRICT';\n } else if (this.matchPair(TokenType.SET, TokenType.NULL)) {\n action = 'SET NULL';\n } else if (this.matchPair(TokenType.SET, TokenType.DEFAULT)) {\n action = 'SET DEFAULT';\n } else {\n this.raiseError('Invalid key constraint');\n }\n\n options.push(`ON ${on} ${action}`);\n } else {\n const var_ = this.parseVarFromOptions(this._constructor.KEY_CONSTRAINT_OPTIONS, { raiseUnmatched: false });\n if (!var_) {\n break;\n }\n options.push(var_.name);\n }\n }\n\n return options;\n }\n\n parseReferences (options: { match?: boolean } = {}): ReferenceExpr | undefined {\n const { match = true } = options;\n if (match && !this.match(TokenType.REFERENCES)) {\n return undefined;\n }\n\n let expressions: Expression[] | undefined;\n const thisExpr = this.parseTable({ schema: true });\n const constraintOptions = this.parseKeyConstraintOptions();\n return this.expression(ReferenceExpr, {\n this: thisExpr,\n expressions,\n options: constraintOptions,\n });\n }\n\n parseForeignKey (): ForeignKeyExpr {\n const expressions = !this.match(TokenType.REFERENCES, { advance: false })\n ? this.parseWrappedIdVars()\n : undefined;\n\n const reference = this.parseReferences();\n const onOptions: Record<string, string> = {};\n\n while (this.match(TokenType.ON)) {\n if (!this.matchSet(new Set([TokenType.DELETE, TokenType.UPDATE]))) {\n this.raiseError('Expected DELETE or UPDATE');\n }\n\n const kind = (this.prev?.text ?? '').toLowerCase();\n\n let action: string;\n if (this.matchTextSeq(['NO', 'ACTION'])) {\n action = 'NO ACTION';\n } else if (this.match(TokenType.SET)) {\n this.matchSet(new Set([TokenType.NULL, TokenType.DEFAULT]));\n action = 'SET ' + (this.prev?.text ?? '').toUpperCase();\n } else {\n this.advance();\n action = (this.prev?.text ?? '').toUpperCase();\n }\n\n onOptions[kind] = action;\n }\n\n return this.expression(\n ForeignKeyExpr,\n {\n expressions,\n reference,\n options: this.parseKeyConstraintOptions(),\n ...onOptions,\n },\n );\n }\n\n parsePrimary (): Expression | undefined {\n if (this.matchSet(Object.keys(this._constructor.PRIMARY_PARSERS) as TokenType[])) {\n const tokenType = this.prev?.tokenType ?? TokenType.UNKNOWN;\n const primary = this._constructor.PRIMARY_PARSERS[tokenType]?.call(this, this.prev as Token);\n\n if (tokenType === TokenType.STRING) {\n const expressions = [primary];\n while (this.match(TokenType.STRING)) {\n expressions.push(LiteralExpr.string(this.prev?.text ?? ''));\n }\n\n if (1 < expressions.length) {\n return this.expression(\n ConcatExpr,\n {\n expressions,\n coalesce: this._dialectConstructor.CONCAT_COALESCE,\n },\n );\n }\n }\n\n return primary;\n }\n\n if (this.matchPair(TokenType.DOT, TokenType.NUMBER)) {\n return LiteralExpr.number(`0.${this.prev?.text ?? ''}`);\n }\n\n return this.parseParen();\n }\n\n parseField (options: {\n anyToken?: boolean;\n tokens?: Set<TokenType>;\n anonymousFunc?: boolean;\n } = {}): Expression | undefined {\n const {\n anyToken = false, tokens, anonymousFunc = false,\n } = options || {};\n\n let field: Expression | undefined;\n if (anonymousFunc) {\n field = (\n this.parseFunction({\n anonymous: anonymousFunc,\n anyToken,\n })\n || this.parsePrimary()\n );\n } else {\n field = this.parsePrimary() || this.parseFunction({\n anonymous: anonymousFunc,\n anyToken,\n });\n }\n return field || this.parseIdVar({\n anyToken,\n tokens,\n });\n }\n\n parseGeneratedAsIdentity (): GeneratedAsIdentityColumnConstraintExpr | ComputedColumnConstraintExpr | GeneratedAsRowColumnConstraintExpr {\n let thisExpr: GeneratedAsIdentityColumnConstraintExpr;\n\n if (this.matchTextSeq(['BY', 'DEFAULT'])) {\n const onNull = this.matchPair(TokenType.ON, TokenType.NULL) || undefined;\n thisExpr = this.expression(\n GeneratedAsIdentityColumnConstraintExpr,\n {\n this: false,\n onNull,\n },\n );\n } else {\n this.matchTextSeq('ALWAYS');\n thisExpr = this.expression(GeneratedAsIdentityColumnConstraintExpr, { this: true });\n }\n\n this.match(TokenType.ALIAS);\n\n if (this.matchTextSeq('ROW')) {\n const start = this.matchTextSeq('START') || undefined;\n if (!start) {\n this.match(TokenType.END);\n }\n const hidden = this.matchTextSeq('HIDDEN') || undefined;\n return this.expression(GeneratedAsRowColumnConstraintExpr, {\n start,\n hidden,\n });\n }\n\n const identity = this.matchTextSeq('IDENTITY') || undefined;\n\n if (this.match(TokenType.L_PAREN)) {\n if (this.match(TokenType.START_WITH)) {\n thisExpr.setArgKey('start', this.parseBitwise());\n }\n if (this.matchTextSeq(['INCREMENT', 'BY'])) {\n thisExpr.setArgKey('increment', this.parseBitwise());\n }\n if (this.matchTextSeq('MINVALUE')) {\n thisExpr.setArgKey('minvalue', this.parseBitwise());\n }\n if (this.matchTextSeq('MAXVALUE')) {\n thisExpr.setArgKey('maxvalue', this.parseBitwise());\n }\n\n if (this.matchTextSeq('CYCLE')) {\n thisExpr.setArgKey('cycle', true);\n } else if (this.matchTextSeq(['NO', 'CYCLE'])) {\n thisExpr.setArgKey('cycle', false);\n }\n\n if (!identity) {\n thisExpr.setArgKey('expression', this.parseRange());\n } else if (!thisExpr.args.start && this.match(TokenType.NUMBER, { advance: false })) {\n const args = this.parseCsv(this.parseBitwise.bind(this));\n thisExpr.setArgKey('start', seqGet(args, 0));\n thisExpr.setArgKey('increment', seqGet(args, 1));\n }\n\n this.matchRParen();\n }\n\n return thisExpr;\n }\n\n parseInline (): InlineLengthColumnConstraintExpr {\n this.matchTextSeq('LENGTH');\n return this.expression(InlineLengthColumnConstraintExpr, { this: this.parseBitwise() });\n }\n\n parseNotConstraint (): Expression | undefined {\n if (this.matchTextSeq('NULL')) {\n return this.expression(NotNullColumnConstraintExpr);\n }\n if (this.matchTextSeq('CASESPECIFIC')) {\n return this.expression(CaseSpecificColumnConstraintExpr, { not: true });\n }\n if (this.matchTextSeq(['FOR', 'REPLICATION'])) {\n return this.expression(NotForReplicationColumnConstraintExpr);\n }\n\n this.retreat(this.index - 1);\n return undefined;\n }\n\n parseColumnConstraint (): Expression | undefined {\n const thisExpr = (this.match(TokenType.CONSTRAINT) || undefined) && this.parseIdVar();\n\n const procedureOptionFollows =\n (this.match(TokenType.WITH, { advance: false }) || undefined)\n && this.next\n && this.next.text.toUpperCase() in this._constructor.PROCEDURE_OPTIONS;\n\n if (!procedureOptionFollows && this.matchTexts(Object.keys(this._constructor.CONSTRAINT_PARSERS))) {\n const constraint = this._constructor.CONSTRAINT_PARSERS[(this.prev?.text ?? '').toUpperCase()]?.call(this);\n if (!constraint) {\n this.retreat(this.index - 1);\n return undefined;\n }\n\n return this.expression(ColumnConstraintExpr, {\n this: thisExpr,\n kind: constraint,\n });\n }\n\n return thisExpr;\n }\n\n parseConstraint (): Expression | undefined {\n if (!this.match(TokenType.CONSTRAINT)) {\n return this.parseUnnamedConstraint({ constraints: this._constructor.SCHEMA_UNNAMED_CONSTRAINTS });\n }\n\n return this.expression(\n ConstraintExpr,\n {\n this: this.parseIdVar(),\n expressions: this.parseUnnamedConstraints(),\n },\n );\n }\n\n parseUnnamedConstraints (): Expression[] {\n const constraints: Expression[] = [];\n while (true) {\n const constraint = this.parseUnnamedConstraint() || this.parseFunction();\n if (!constraint) {\n break;\n }\n constraints.push(constraint);\n }\n\n return constraints;\n }\n\n parseUnnamedConstraint (options: { constraints?: Set<string> | string[] } = {}): Expression | undefined {\n const index = this.index;\n const { constraints } = options;\n\n if (\n this.match(TokenType.IDENTIFIER, { advance: false })\n || !this.matchTexts(constraints || Object.keys(this._constructor.CONSTRAINT_PARSERS))\n ) {\n return undefined;\n }\n\n const constraintName = (this.prev?.text ?? '').toUpperCase();\n if (!(constraintName in this._constructor.CONSTRAINT_PARSERS)) {\n this.raiseError(`No parser found for schema constraint ${constraintName}.`);\n }\n\n const constraint = this._constructor.CONSTRAINT_PARSERS[constraintName]?.call(this);\n if (!constraint || Array.isArray(constraint)) {\n this.retreat(index);\n return undefined;\n }\n\n return constraint;\n }\n\n parseFieldDef (): Expression | undefined {\n return this.parseColumnDef(this.parseField({ anyToken: true }));\n }\n\n parseColumnDef (\n thisExpr: Expression | undefined,\n options: { computedColumn?: boolean } = {},\n ): Expression | undefined {\n const {\n computedColumn = true,\n } = options;\n\n let thisResult: ExpressionValue | undefined = thisExpr;\n\n if (thisResult instanceof ColumnExpr) {\n thisResult = thisResult.args.this;\n }\n\n if (!computedColumn) {\n this.match(TokenType.ALIAS);\n }\n\n let kind = this.parseTypes({ schema: true });\n\n if (this.matchTextSeq(['FOR', 'ORDINALITY'])) {\n return this.expression(ColumnDefExpr, {\n this: thisResult,\n ordinality: true,\n });\n }\n\n const constraints: Expression[] = [];\n\n if ((!kind && this.match(TokenType.ALIAS)) || this.matchTexts(['ALIAS', 'MATERIALIZED'])) {\n const persisted = (this.prev?.text ?? '').toUpperCase() === 'MATERIALIZED';\n const constraintKind = new ComputedColumnConstraintExpr({\n this: this.parseDisjunction(),\n persisted: persisted || (this.matchTextSeq('PERSISTED') || undefined),\n dataType: (this.matchTextSeq('AUTO')\n ? new DataTypeExpr({ this: 'AUTO' })\n : this.parseTypes()) as DataTypeExpr,\n notNull: this.matchPair(TokenType.NOT, TokenType.NULL) || undefined,\n });\n constraints.push(this.expression(ColumnConstraintExpr, { kind: constraintKind }));\n } else if (!kind && this.matchSet([TokenType.IN, TokenType.OUT], { advance: false })) {\n const inOutConstraint = this.expression(\n InOutColumnConstraintExpr,\n {\n input: this.match(TokenType.IN) || undefined,\n output: this.match(TokenType.OUT) || undefined,\n },\n );\n constraints.push(inOutConstraint);\n kind = this.parseTypes();\n } else if (\n kind\n && this.match(TokenType.ALIAS, { advance: false })\n && (!this._constructor.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT\n || (this.next && this.next.tokenType === TokenType.L_PAREN))\n ) {\n this.advance();\n constraints.push(\n this.expression(\n ColumnConstraintExpr,\n {\n kind: new ComputedColumnConstraintExpr({\n this: this.parseDisjunction(),\n persisted:\n (this.matchTexts(['STORED', 'VIRTUAL']) || undefined)\n && (this.prev?.text ?? '').toUpperCase() === 'STORED',\n }),\n },\n ),\n );\n }\n\n while (true) {\n const constraint = this.parseColumnConstraint();\n if (!constraint) {\n break;\n }\n constraints.push(constraint);\n }\n\n if (!kind && constraints.length === 0) {\n return thisResult as Expression | undefined;\n }\n\n return this.expression(ColumnDefExpr, {\n this: thisResult,\n kind,\n constraints,\n });\n }\n\n parseAutoIncrement (): GeneratedAsIdentityColumnConstraintExpr | AutoIncrementColumnConstraintExpr {\n let start: Expression | undefined;\n let increment: Expression | undefined;\n let order: boolean | undefined;\n\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n const args = this.parseWrappedCsv(this.parseBitwise.bind(this));\n start = seqGet(args, 0);\n increment = seqGet(args, 1);\n } else if (this.matchTextSeq('START')) {\n start = this.parseBitwise();\n this.matchTextSeq('INCREMENT');\n increment = this.parseBitwise();\n if (this.matchTextSeq('ORDER')) {\n order = true;\n } else if (this.matchTextSeq('NOORDER')) {\n order = false;\n }\n }\n\n if (start && increment) {\n return new GeneratedAsIdentityColumnConstraintExpr({\n start,\n increment,\n this: false,\n order,\n });\n }\n\n return new AutoIncrementColumnConstraintExpr({});\n }\n\n parseCheckConstraint (): CheckColumnConstraintExpr | undefined {\n if (!this.match(TokenType.L_PAREN, { advance: false })) {\n return undefined;\n }\n\n return this.expression(\n CheckColumnConstraintExpr,\n {\n this: this.parseWrapped(this.parseAssignment.bind(this)),\n enforced: this.matchTextSeq('ENFORCED') || undefined,\n },\n );\n }\n\n parseAutoProperty (): AutoRefreshPropertyExpr | undefined {\n if (!this.matchTextSeq('REFRESH')) {\n this.retreat(this.index - 1);\n return undefined;\n }\n return this.expression(AutoRefreshPropertyExpr, { this: this.parseVar({ upper: true }) });\n }\n\n parseCompress (): CompressColumnConstraintExpr {\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n return this.expression(\n CompressColumnConstraintExpr,\n { this: this.parseWrappedCsv(this.parseBitwise.bind(this)) },\n );\n }\n\n return this.expression(CompressColumnConstraintExpr, { this: this.parseBitwise() });\n }\n\n parseFunction (options: {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n functions?: Record<string, Function>;\n anonymous?: boolean;\n optionalParens?: boolean;\n anyToken?: boolean;\n } = {}): Expression | undefined {\n const {\n functions,\n anonymous = false,\n optionalParens = true,\n anyToken = false,\n } = options;\n\n let fnSyntax = false;\n if (\n this.match(TokenType.L_BRACE, { advance: false })\n && this.next\n && this.next.text.toUpperCase() === 'FN'\n ) {\n this.advance(2);\n fnSyntax = true;\n }\n\n const func = this.parseFunctionCall({\n functions,\n anonymous,\n optionalParens,\n anyToken,\n });\n\n if (fnSyntax) {\n this.match(TokenType.R_BRACE);\n }\n\n return func;\n }\n\n parseFunctionArgs (options: { alias?: boolean } = {}): Expression[] {\n const {\n alias = false,\n } = options;\n return this.parseCsv(() => this.parseLambda({ alias }));\n }\n\n parseFunctionCall (options: {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n functions?: Record<string, Function>;\n anonymous?: boolean;\n optionalParens?: boolean;\n anyToken?: boolean;\n } = {}): Expression | undefined {\n const {\n functions,\n anonymous = false,\n optionalParens = true,\n anyToken = false,\n } = options;\n\n if (!this.curr) {\n return undefined;\n }\n\n const comments = this.curr.comments;\n const prev = this.prev;\n const token = this.curr;\n const tokenType = this.curr.tokenType;\n const thisText = this.curr.text;\n const upper = thisText.toUpperCase();\n\n const parser = this._constructor.NO_PAREN_FUNCTION_PARSERS[upper];\n if (parser && optionalParens && !this._constructor.INVALID_FUNC_NAME_TOKENS.has(tokenType)) {\n this.advance();\n return this.parseWindow(parser.call(this));\n }\n\n if (!this.next || this.next.tokenType !== TokenType.L_PAREN) {\n const noParen = this._constructor.NO_PAREN_FUNCTIONS[tokenType as keyof typeof this._constructor.NO_PAREN_FUNCTIONS];\n if (optionalParens && noParen) {\n this.advance();\n return this.expression(noParen);\n }\n\n return undefined;\n }\n\n if (anyToken) {\n if (this._constructor.RESERVED_TOKENS.has(tokenType)) {\n return undefined;\n }\n } else if (!this._constructor.FUNC_TOKENS.has(tokenType)) {\n return undefined;\n }\n\n this.advance(2);\n\n const funcParser = this._constructor.FUNCTION_PARSERS[upper];\n if (funcParser && !anonymous) {\n const thisExpr: Expression | undefined = funcParser.call(this);\n\n if (thisExpr instanceof Expression) {\n thisExpr.addComments(comments);\n }\n\n this.matchRParen(thisExpr);\n return this.parseWindow(thisExpr);\n } else {\n const subqueryPredicate = this._constructor.SUBQUERY_PREDICATES[tokenType];\n\n if (subqueryPredicate) {\n let expr: Expression | undefined;\n if (\n this.curr.tokenType === TokenType.SELECT\n || this.curr.tokenType === TokenType.WITH\n ) {\n expr = this.parseSelect();\n this.matchRParen();\n } else if (\n prev\n && (prev.tokenType === TokenType.LIKE || prev.tokenType === TokenType.ILIKE)\n ) {\n this.advance(-1);\n expr = this.parseBitwise();\n }\n\n if (expr) {\n return this.expression(subqueryPredicate, {\n comments,\n this: expr,\n });\n }\n }\n\n const functionsMap = functions ?? this._constructor.FUNCTIONS;\n\n const functionBuilder = functionsMap[upper];\n const knownFunction = functionBuilder && !anonymous;\n\n const alias =\n !knownFunction || this._constructor.FUNCTIONS_WITH_ALIASED_ARGS.has(upper);\n const args = this.parseFunctionArgs({ alias });\n\n const postFuncComments = this.curr?.comments;\n let isKnownFunction = knownFunction;\n if (isKnownFunction && postFuncComments) {\n if (\n postFuncComments.some((comment) =>\n comment.trimStart().startsWith(SQLGLOT_ANONYMOUS))\n ) {\n isKnownFunction = false;\n }\n }\n\n const argsWithPropEq = alias && isKnownFunction ? this.kvToPropEq(args) : args;\n\n let thisExpr: Expression;\n\n if (isKnownFunction) {\n let func: Expression;\n if (1 < functionBuilder.length) {\n func = functionBuilder(argsWithPropEq, { dialect: this.dialect });\n } else {\n func = functionBuilder(argsWithPropEq);\n }\n\n func = this.validateExpression(func, argsWithPropEq);\n if (this._dialectConstructor.PRESERVE_ORIGINAL_NAMES) {\n func.meta.name = thisText;\n }\n\n thisExpr = func;\n } else {\n let thisValue: Expression | string = thisText;\n if (tokenType === TokenType.IDENTIFIER) {\n thisValue = new IdentifierExpr({\n this: thisText,\n quoted: true,\n }).updatePositions(token);\n }\n\n thisExpr = this.expression(AnonymousExpr, {\n this: thisValue,\n expressions: args,\n });\n }\n\n thisExpr = thisExpr.updatePositions(token);\n\n if (thisExpr instanceof Expression) {\n thisExpr.addComments(comments);\n }\n\n this.matchRParen(thisExpr);\n return this.parseWindow(thisExpr);\n }\n }\n\n toPropEq (expression: Expression, _index: number): Expression {\n return expression;\n }\n\n kvToPropEq (\n expressions: Expression[],\n options: { parseMap?: boolean } = {},\n ): Expression[] {\n const {\n parseMap = false,\n } = options;\n const transformed: Expression[] = [];\n\n for (let index = 0; index < expressions.length; index++) {\n let e = expressions[index];\n\n if ([...this._constructor.KEY_VALUE_DEFINITIONS.keys()].some((def) => e instanceof def)) {\n if (e instanceof AliasExpr) {\n e = this.expression(PropertyEqExpr, {\n this: e.args.alias,\n expression: e.args.this,\n });\n }\n\n if (!(e instanceof PropertyEqExpr)) {\n const eThis = e.args.this;\n e = this.expression(PropertyEqExpr, {\n this: parseMap ? e.args.this : toIdentifier(eThis instanceof Expression ? eThis.name : eThis?.toString()),\n expression: e.args.expression,\n });\n }\n\n if (e.args.this instanceof ColumnExpr) {\n e.args.this.replace(e.args.this.args.this);\n }\n } else {\n e = this.toPropEq(e, index);\n }\n\n transformed.push(e);\n }\n\n return transformed;\n }\n\n parseUserDefinedFunctionExpression (): Expression | undefined {\n return this.parseStatement();\n }\n\n parseFunctionParameter (): Expression | undefined {\n return this.parseColumnDef(this.parseIdVar(), { computedColumn: false });\n }\n\n parseUserDefinedFunction (_options: { kind?: TokenType } = {}): Expression | undefined {\n const thisExpr = this.parseTableParts({ schema: true });\n\n if (!this.match(TokenType.L_PAREN)) {\n return thisExpr;\n }\n\n const expressions = this.parseCsv(this.parseFunctionParameter.bind(this));\n this.matchRParen();\n return this.expression(UserDefinedFunctionExpr, {\n this: thisExpr,\n expressions,\n wrapped: true,\n });\n }\n\n parseIntroducer (token: Token): IntroducerExpr | IdentifierExpr {\n const literal = this.parsePrimary();\n if (literal) {\n return this.expression(IntroducerExpr, {\n token,\n expression: literal,\n });\n }\n\n return this.identifierExpression(token);\n }\n\n parseSessionParameter (): SessionParameterExpr {\n let kind: string | undefined;\n let thisExpr = this.parseIdVar() || this.parsePrimary();\n\n if (thisExpr && this.match(TokenType.DOT)) {\n kind = thisExpr.name;\n thisExpr = this.parseVar() || this.parsePrimary();\n }\n\n return this.expression(SessionParameterExpr, {\n this: thisExpr,\n kind,\n });\n }\n\n parseLambdaArg (): Expression | undefined {\n return this.parseIdVar();\n }\n\n parseLambda (options: { alias?: boolean } = {}): Expression | undefined {\n const { alias = false } = options;\n const index = this.index;\n\n let expressions: (Expression | undefined)[];\n\n if (this.match(TokenType.L_PAREN)) {\n expressions = this.parseCsv(this.parseLambdaArg.bind(this));\n\n if (!this.match(TokenType.R_PAREN)) {\n this.retreat(index);\n }\n } else {\n expressions = [this.parseLambdaArg()];\n }\n\n if (this.matchSet(Object.keys(this._constructor.LAMBDAS) as TokenType[])) {\n const lambdaTokenType = this.prev?.tokenType;\n return lambdaTokenType && this._constructor.LAMBDAS[lambdaTokenType]?.call(this, expressions.filter((e): e is Expression => Boolean(e)));\n }\n\n this.retreat(index);\n\n let thisExpr: Expression | undefined;\n\n if (this.match(TokenType.DISTINCT)) {\n thisExpr = this.expression(DistinctExpr, {\n expressions: this.parseCsv(this.parseDisjunction.bind(this)),\n });\n } else {\n thisExpr = this.parseSelectOrExpression({ alias });\n }\n\n return this.parseLimit(\n this.parseOrder({ thisExpr: this.parseHavingMax(this.parseRespectOrIgnoreNulls(thisExpr)) }),\n );\n }\n\n parseSchema (options: { this?: Expression } = {}): Expression | undefined {\n const { this: thisExpr } = options;\n\n const index = this.index;\n if (!this.match(TokenType.L_PAREN)) {\n return thisExpr;\n }\n\n if (this.matchSet(this._constructor.SELECT_START_TOKENS)) {\n this.retreat(index);\n return thisExpr;\n }\n\n const args = this.parseCsv(() => this.parseConstraint() || this.parseFieldDef());\n this.matchRParen();\n return this.expression(SchemaExpr, {\n this: thisExpr,\n expressions: args,\n });\n }\n\n parseAlterTableSet (): AlterSetExpr {\n const alterSet = this.expression(AlterSetExpr);\n\n if (\n this.match(TokenType.L_PAREN, { advance: false })\n || this.matchTextSeq(['TABLE', 'PROPERTIES'])\n ) {\n alterSet.setArgKey('expressions', this.parseWrappedCsv(this.parseAssignment.bind(this)));\n } else if (this.matchTextSeq('FILESTREAM_ON', { advance: false })) {\n alterSet.setArgKey('expressions', [this.parseAssignment() as Expression]);\n } else if (this.matchTexts(['LOGGED', 'UNLOGGED'])) {\n alterSet.setArgKey('option', var_((this.prev?.text ?? '').toUpperCase()));\n } else if (this.matchTextSeq('WITHOUT') && this.matchTexts(['CLUSTER', 'OIDS'])) {\n alterSet.setArgKey('option', var_(`WITHOUT ${(this.prev?.text ?? '').toUpperCase()}`));\n } else if (this.matchTextSeq('LOCATION')) {\n alterSet.setArgKey('location', this.parseField());\n } else if (this.matchTextSeq(['ACCESS', 'METHOD'])) {\n alterSet.setArgKey('accessMethod', this.parseField());\n } else if (this.matchTextSeq('TABLESPACE')) {\n alterSet.setArgKey('tablespace', this.parseField());\n } else if (this.matchTextSeq(['FILE', 'FORMAT']) || this.matchTextSeq('FILEFORMAT')) {\n alterSet.setArgKey('fileFormat', [this.parseField()!]);\n } else if (this.matchTextSeq('STAGE_FILE_FORMAT')) {\n alterSet.setArgKey('fileFormat', this.parseWrappedOptions());\n } else if (this.matchTextSeq('STAGE_COPY_OPTIONS')) {\n alterSet.setArgKey('copyOptions', this.parseWrappedOptions());\n } else if (this.matchTextSeq('TAG') || this.matchTextSeq('TAGS')) {\n alterSet.setArgKey('tag', this.parseCsv(this.parseAssignment.bind(this)));\n } else {\n if (this.matchTextSeq('SERDE')) {\n alterSet.setArgKey('serde', this.parseField());\n }\n\n const properties = this.parseWrapped(this.parseProperties.bind(this), { optional: true });\n alterSet.setArgKey('expressions', properties ? [properties] : []);\n }\n\n return alterSet;\n }\n\n parseAlterSession (): AlterSessionExpr {\n if (this.match(TokenType.SET)) {\n const expressions = this.parseCsv(() => this.parseSetItemAssignment());\n return this.expression(AlterSessionExpr, {\n expressions,\n unset: false,\n });\n }\n\n this.matchTextSeq('UNSET');\n const expressions = this.parseCsv(() =>\n this.expression(SetItemExpr, { this: this.parseIdVar({ anyToken: true }) }));\n return this.expression(AlterSessionExpr, {\n expressions,\n unset: true,\n });\n }\n\n parseAlter (): AlterExpr | CommandExpr {\n const start = this.prev;\n\n const alterToken = (this.matchSet(this._constructor.ALTERABLES) || undefined) && this.prev;\n if (!alterToken) {\n return this.parseAsCommand(start);\n }\n\n const exists = this.parseExists();\n const only = this.matchTextSeq('ONLY') || undefined;\n\n let thisExpr: Expression | undefined;\n let check: boolean | undefined;\n let cluster: Expression | undefined;\n\n if (alterToken.tokenType === TokenType.SESSION) {\n thisExpr = undefined;\n check = undefined;\n cluster = undefined;\n } else {\n thisExpr = this.parseTable({\n schema: true,\n parsePartition: this._constructor.ALTER_TABLE_PARTITIONS,\n });\n check = this.matchTextSeq(['WITH', 'CHECK']) || undefined;\n cluster = this.match(TokenType.ON) ? this.parseOnProperty() : undefined;\n\n if (this.next) {\n this.advance();\n }\n }\n\n const parser = this.prev\n ? this._constructor.ALTER_PARSERS[this.prev.text.toUpperCase()]\n : undefined;\n if (parser) {\n const actions = ensureList(parser.call(this));\n const notValid = this.matchTextSeq(['NOT', 'VALID']) || undefined;\n const options = this.parseCsv(this.parseProperty.bind(this) as () => Expression | undefined);\n const cascade =\n this._dialectConstructor.ALTER_TABLE_SUPPORTS_CASCADE\n && this.matchTextSeq('CASCADE');\n\n if (!this.curr && actions) {\n return this.expression(AlterExpr, {\n this: thisExpr,\n kind: enumFromString(AlterExprKind, alterToken.text) ?? alterToken.text.toUpperCase(),\n exists,\n actions,\n only,\n options,\n cluster,\n notValid,\n check,\n cascade,\n });\n }\n }\n\n return this.parseAsCommand(start);\n }\n\n parseAnalyze (): AnalyzeExpr | CommandExpr {\n const start = this.prev;\n\n if (!this.curr) {\n return this.expression(AnalyzeExpr);\n }\n\n const options: string[] = [];\n while (this.matchTexts(this._constructor.ANALYZE_STYLES)) {\n if ((this.prev?.text ?? '').toUpperCase() === 'BUFFER_USAGE_LIMIT') {\n options.push(`BUFFER_USAGE_LIMIT ${this.parseNumber()}`);\n } else {\n options.push((this.prev?.text ?? '').toUpperCase());\n }\n }\n\n let thisExpr: Expression | undefined;\n let innerExpression: Expression | undefined;\n\n let kind: string | undefined = this.curr?.text.toUpperCase();\n\n if (this.match(TokenType.TABLE) || this.match(TokenType.INDEX)) {\n thisExpr = this.parseTableParts();\n } else if (this.matchTextSeq('TABLES')) {\n if (this.matchSet([TokenType.FROM, TokenType.IN])) {\n kind = `${kind} ${(this.prev?.text ?? '').toUpperCase()}`;\n thisExpr = this.parseTable({\n schema: true,\n isDbReference: true,\n });\n }\n } else if (this.matchTextSeq('DATABASE')) {\n thisExpr = this.parseTable({\n schema: true,\n isDbReference: true,\n });\n } else if (this.matchTextSeq('CLUSTER')) {\n thisExpr = this.parseTable();\n } else if (this.matchTexts(Object.keys(this._constructor.ANALYZE_EXPRESSION_PARSERS))) {\n kind = undefined;\n innerExpression =\n this._constructor.ANALYZE_EXPRESSION_PARSERS[(this.prev?.text ?? '').toUpperCase()].call(this);\n } else {\n kind = undefined;\n thisExpr = this.parseTableParts();\n }\n\n const partition = this.tryParse(this.parsePartition.bind(this));\n if (!partition && this.matchTexts(this._constructor.PARTITION_KEYWORDS)) {\n return this.parseAsCommand(start);\n }\n\n let mode: string | undefined;\n if (\n this.matchTextSeq([\n 'WITH',\n 'SYNC',\n 'MODE',\n ])\n || this.matchTextSeq([\n 'WITH',\n 'ASYNC',\n 'MODE',\n ])\n ) {\n mode = `WITH ${this.tokens[this.index - 2].text.toUpperCase()} MODE`;\n } else {\n mode = undefined;\n }\n\n if (this.matchTexts(Object.keys(this._constructor.ANALYZE_EXPRESSION_PARSERS))) {\n innerExpression =\n this._constructor.ANALYZE_EXPRESSION_PARSERS[(this.prev?.text ?? '').toUpperCase()].call(this);\n }\n\n const properties = this.parseProperties();\n return this.expression(AnalyzeExpr, {\n kind,\n this: thisExpr,\n mode,\n partition,\n properties,\n expression: innerExpression,\n options,\n });\n }\n\n parseAnalyzeStatistics (): AnalyzeStatisticsExpr {\n let thisValue: string | undefined;\n const kind = (this.prev?.text ?? '').toUpperCase();\n const option = this.matchTextSeq('DELTA') ? (this.prev?.text ?? '').toUpperCase() : undefined;\n let expressions: Expression[] = [];\n\n if (!this.matchTextSeq('STATISTICS')) {\n this.raiseError('Expecting token STATISTICS');\n }\n\n if (this.matchTextSeq('NOSCAN')) {\n thisValue = 'NOSCAN';\n } else if (this.match(TokenType.FOR)) {\n if (this.matchTextSeq(['ALL', 'COLUMNS'])) {\n thisValue = 'FOR ALL COLUMNS';\n }\n if (this.matchTexts('COLUMNS')) {\n thisValue = 'FOR COLUMNS';\n expressions = this.parseCsv(this.parseColumnReference.bind(this));\n }\n } else if (this.matchTextSeq('SAMPLE')) {\n const sample = this.parseNumber();\n expressions = [\n this.expression(AnalyzeSampleExpr, {\n sample,\n kind: this.match(TokenType.PERCENT) ? (this.prev?.text ?? '').toUpperCase() : undefined,\n }),\n ];\n }\n\n return this.expression(AnalyzeStatisticsExpr, {\n kind,\n option,\n this: thisValue,\n expressions,\n });\n }\n\n parseAnalyzeValidate (): AnalyzeValidateExpr {\n let kind: string | undefined;\n let thisValue: string | undefined;\n let expression: Expression | undefined;\n\n if (this.matchTextSeq(['REF', 'UPDATE'])) {\n kind = 'REF';\n thisValue = 'UPDATE';\n if (this.matchTextSeq([\n 'SET',\n 'DANGLING',\n 'TO',\n 'NULL',\n ])) {\n thisValue = 'UPDATE SET DANGLING TO NULL';\n }\n } else if (this.matchTextSeq('STRUCTURE')) {\n kind = 'STRUCTURE';\n if (this.matchTextSeq(['CASCADE', 'FAST'])) {\n thisValue = 'CASCADE FAST';\n } else if (\n this.matchTextSeq(['CASCADE', 'COMPLETE'])\n && this.matchTexts(['ONLINE', 'OFFLINE'])\n ) {\n thisValue = `CASCADE COMPLETE ${(this.prev?.text ?? '').toUpperCase()}`;\n expression = this.parseInto();\n }\n }\n\n return this.expression(AnalyzeValidateExpr, {\n kind,\n this: thisValue,\n expression,\n });\n }\n\n parseAnalyzeColumns (): AnalyzeColumnsExpr | undefined {\n const thisValue = (this.prev?.text ?? '').toUpperCase();\n if (this.matchTextSeq('COLUMNS')) {\n return this.expression(AnalyzeColumnsExpr, {\n this: `${thisValue} ${(this.prev?.text ?? '').toUpperCase()}`,\n });\n }\n return undefined;\n }\n\n parseAnalyzeDelete (): AnalyzeDeleteExpr | undefined {\n const kind = this.matchTextSeq('SYSTEM') ? (this.prev?.text ?? '').toUpperCase() : undefined;\n if (this.matchTextSeq('STATISTICS')) {\n return this.expression(AnalyzeDeleteExpr, { kind });\n }\n return undefined;\n }\n\n parseAnalyzeList (): AnalyzeListChainedRowsExpr | undefined {\n if (this.matchTextSeq(['CHAINED', 'ROWS'])) {\n return this.expression(AnalyzeListChainedRowsExpr, { expression: this.parseInto() });\n }\n return undefined;\n }\n\n parseAnalyzeHistogram (): AnalyzeHistogramExpr {\n const thisValue = (this.prev?.text ?? '').toUpperCase();\n let expression: Expression | undefined;\n let expressions: Expression[] = [];\n let updateOptions: string | undefined;\n\n if (this.matchTextSeq(['HISTOGRAM', 'ON'])) {\n expressions = this.parseCsv(this.parseColumnReference.bind(this));\n const withExpressions: string[] = [];\n while (this.match(TokenType.WITH)) {\n if (this.matchTexts(['SYNC', 'ASYNC'])) {\n if (this.matchTextSeq('MODE', { advance: false })) {\n withExpressions.push(`${(this.prev?.text ?? '').toUpperCase()} MODE`);\n this.advance();\n }\n } else {\n const buckets = this.parseNumber();\n if (this.matchTextSeq('BUCKETS')) {\n withExpressions.push(`${buckets} BUCKETS`);\n }\n }\n }\n if (0 < withExpressions.length) {\n expression = this.expression(AnalyzeWithExpr, { expressions: withExpressions });\n }\n\n if (this.matchTexts(['MANUAL', 'AUTO']) && this.match(TokenType.UPDATE, { advance: false })) {\n updateOptions = (this.prev?.text ?? '').toUpperCase();\n this.advance();\n } else if (this.matchTextSeq(['USING', 'DATA'])) {\n expression = this.expression(UsingDataExpr, { this: this.parseString() });\n }\n }\n\n return this.expression(AnalyzeHistogramExpr, {\n this: thisValue,\n expressions,\n expression,\n updateOptions,\n });\n }\n\n parseMerge (): MergeExpr {\n this.match(TokenType.INTO);\n const target = this.parseTable();\n\n if (target && this.match(TokenType.ALIAS, { advance: false })) {\n target.setArgKey('alias', this.parseTableAlias());\n }\n\n this.match(TokenType.USING);\n const using = this.parseTable();\n\n return this.expression(MergeExpr, {\n this: target,\n using,\n on: (this.match(TokenType.ON) || undefined) && this.parseDisjunction(),\n usingCond: (this.match(TokenType.USING) || undefined) && this.parseUsingIdentifiers(),\n whens: this.parseWhenMatched(),\n returning: this.parseReturning(),\n });\n }\n\n parseWhenMatched (): WhensExpr {\n const whens: WhenExpr[] = [];\n\n while (this.match(TokenType.WHEN)) {\n const matched = !this.match(TokenType.NOT);\n this.matchTextSeq('MATCHED');\n const source = this.matchTextSeq(['BY', 'TARGET'])\n ? false\n : this.matchTextSeq(['BY', 'SOURCE']);\n const condition = this.match(TokenType.AND) ? this.parseDisjunction() : undefined;\n\n this.match(TokenType.THEN);\n\n let then: Expression | undefined;\n\n if (this.match(TokenType.INSERT)) {\n const thisValue = this.parseStar();\n if (thisValue) {\n then = this.expression(InsertExpr, { this: thisValue });\n } else {\n then = this.expression(InsertExpr, {\n this: this.matchTextSeq('ROW')\n ? var_('ROW')\n : this.parseValue({ values: false }),\n expression: this.matchTextSeq('VALUES') && this.parseValue(),\n });\n }\n } else if (this.match(TokenType.UPDATE)) {\n const expressions = this.parseStar();\n if (expressions) {\n then = this.expression(UpdateExpr, { expressions });\n } else {\n then = this.expression(UpdateExpr, {\n expressions: this.match(TokenType.SET) && this.parseCsv(this.parseEquality.bind(this)),\n });\n }\n } else if (this.match(TokenType.DELETE)) {\n then = this.expression(VarExpr, { this: this.prev?.text ?? '' });\n } else {\n then = this.parseVarFromOptions(this._constructor.CONFLICT_ACTIONS);\n }\n\n whens.push(\n this.expression(WhenExpr, {\n matched,\n source,\n condition,\n then,\n }),\n );\n }\n return this.expression(WhensExpr, { expressions: whens });\n }\n\n parseShow (): Expression | undefined {\n const parser = this.findParser(\n this._constructor.SHOW_PARSERS,\n this._constructor.SHOW_TRIE,\n );\n if (parser) {\n return parser.call(this);\n }\n return this.parseAsCommand(this.prev);\n }\n\n parseSetItemAssignment (options: { kind?: string } = {}): Expression | undefined {\n const { kind } = options;\n const index = this.index;\n\n if (\n (kind === 'GLOBAL' || kind === 'SESSION')\n && this.matchTextSeq('TRANSACTION')\n ) {\n return this.parseSetTransaction({ global: kind === 'GLOBAL' });\n }\n\n const left = this.parsePrimary() || this.parseColumn();\n const assignmentDelimiter = this.matchTexts(\n this._constructor.SET_ASSIGNMENT_DELIMITERS,\n ) || undefined;\n\n if (\n !left\n || (this._constructor.SET_REQUIRES_ASSIGNMENT_DELIMITER && !assignmentDelimiter)\n ) {\n this.retreat(index);\n return undefined;\n }\n\n let right = this.parseStatement() || this.parseIdVar();\n if (right instanceof ColumnExpr || right instanceof IdentifierExpr) {\n right = var_(right.name);\n }\n\n const thisValue = this.expression(EqExpr, {\n this: left,\n expression: right,\n });\n return this.expression(SetItemExpr, {\n this: thisValue,\n kind,\n });\n }\n\n parseSetTransaction (options: { global?: boolean } = {}): Expression {\n const { global = false } = options;\n this.matchTextSeq('TRANSACTION');\n const characteristics = this.parseCsv(() =>\n this.parseVarFromOptions(this._constructor.TRANSACTION_CHARACTERISTICS));\n return this.expression(SetItemExpr, {\n expressions: characteristics,\n kind: 'TRANSACTION',\n global: global,\n });\n }\n\n parseSetItem (): Expression | undefined {\n const parser = this.findParser(\n this._constructor.SET_PARSERS,\n this._constructor.SET_TRIE,\n );\n return parser ? parser.call(this) : this.parseSetItemAssignment({ kind: undefined });\n }\n\n parseSet (options: {\n unset?: boolean;\n tag?: boolean;\n } = {}): SetExpr | CommandExpr {\n const {\n unset = false,\n tag = false,\n } = options;\n const index = this.index;\n const set = this.expression(SetExpr, {\n expressions: this.parseCsv(this.parseSetItem.bind(this)),\n unset,\n tag,\n });\n\n if (this.curr) {\n this.retreat(index);\n return this.parseAsCommand(this.prev);\n }\n\n return set;\n }\n\n parseVarFromOptions (\n options: Record<string, (string | string[])[] | undefined>,\n parseOptions: { raiseUnmatched?: boolean } = {},\n ): VarExpr | undefined {\n const { raiseUnmatched = true } = parseOptions;\n const start = this.curr;\n if (!start) {\n return undefined;\n }\n\n let option = start.text.toUpperCase();\n const continuations = options[option];\n\n const index = this.index;\n this.advance();\n let matched = false;\n for (const keywords of continuations || []) {\n const keywordArray = typeof keywords === 'string' ? [keywords] : keywords;\n\n if (this.matchTextSeq(keywordArray)) {\n option = `${option} ${keywordArray.join(' ')}`;\n matched = true;\n break;\n }\n }\n\n if (!matched && (continuations === undefined || 0 < continuations.length)) {\n if (raiseUnmatched) {\n this.raiseError(`Unknown option ${option}`);\n }\n\n this.retreat(index);\n return undefined;\n }\n\n return var_(option);\n }\n\n parseCache (): CacheExpr {\n const lazy = this.matchTextSeq('LAZY') || undefined;\n this.match(TokenType.TABLE);\n const table = this.parseTable({ schema: true });\n\n let options: Expression[] = [];\n if (this.matchTextSeq('OPTIONS')) {\n this.matchLParen();\n const k = this.parseString();\n if (!k) {\n this.raiseError('Expected option key');\n }\n this.match(TokenType.EQ);\n const v = this.parseString();\n if (!v) {\n this.raiseError('Expected option value');\n }\n options = [k, v].filter(Boolean) as Expression[];\n this.matchRParen();\n }\n\n this.match(TokenType.ALIAS);\n return this.expression(CacheExpr, {\n this: table,\n lazy,\n options,\n expression: this.parseSelect({ nested: true }),\n });\n }\n\n parsePartition (): PartitionExpr | undefined {\n if (!this.matchTexts(this._constructor.PARTITION_KEYWORDS)) {\n return undefined;\n }\n\n return this.expression(PartitionExpr, {\n subpartition: (this.prev?.text ?? '').toUpperCase() === 'SUBPARTITION',\n expressions: this.parseWrappedCsv(this.parseDisjunction.bind(this)),\n });\n }\n\n parseValue (_options?: { values?: boolean }): TupleExpr | undefined {\n const parseValueExpression = (): Expression | undefined => {\n if (\n this._dialectConstructor.SUPPORTS_VALUES_DEFAULT\n && this.match(TokenType.DEFAULT)\n ) {\n return var_((this.prev?.text ?? '').toUpperCase());\n }\n return this.parseExpression();\n };\n\n if (this.match(TokenType.L_PAREN)) {\n const expressions = this.parseCsv(parseValueExpression);\n this.matchRParen();\n return this.expression(TupleExpr, { expressions });\n }\n\n const expression = this.parseExpression();\n if (expression) {\n return this.expression(TupleExpr, { expressions: [expression] });\n }\n return undefined;\n }\n\n parseProjections (): Expression[] {\n return this.parseExpressions();\n }\n\n parseWrappedSelect (options: { table?: boolean } = {}): Expression | undefined {\n const { table = false } = options;\n let thisExpr: Expression | undefined;\n\n if (this.matchSet([TokenType.PIVOT, TokenType.UNPIVOT])) {\n thisExpr = this.parseSimplifiedPivot({\n isUnpivot: this.prev?.tokenType === TokenType.UNPIVOT,\n });\n } else if (this.match(TokenType.FROM)) {\n const from = this.parseFrom({\n skipFromToken: true,\n consumePipe: true,\n });\n const selectExpr = this.parseSelect({ from });\n if (selectExpr) {\n if (!selectExpr.args.from) {\n selectExpr.setArgKey('from', from);\n }\n thisExpr = selectExpr;\n } else {\n thisExpr = select('*').from(from as FromExpr);\n thisExpr = this.parseQueryModifiers(this.parseSetOperations(thisExpr));\n }\n } else {\n thisExpr = table\n ? this.parseTable({ consumePipe: true })\n : this.parseSelect({\n nested: true,\n parseSetOperation: false,\n });\n\n if (table && thisExpr instanceof ValuesExpr && thisExpr.alias) {\n const alias = thisExpr.args.alias?.pop();\n thisExpr = new TableExpr({\n this: thisExpr,\n alias,\n });\n }\n\n thisExpr = this.parseQueryModifiers(this.parseSetOperations(thisExpr));\n }\n\n return thisExpr;\n }\n\n parseCommitOrRollback (): CommitExpr | RollbackExpr {\n let chain: boolean | undefined;\n let savepoint: Expression | undefined;\n const isRollback = this.prev?.tokenType === TokenType.ROLLBACK;\n\n this.matchTexts(['TRANSACTION', 'WORK']);\n\n if (this.matchTextSeq('TO')) {\n this.matchTextSeq('SAVEPOINT');\n savepoint = this.parseIdVar();\n }\n\n if (this.match(TokenType.AND)) {\n chain = !this.matchTextSeq('NO');\n this.matchTextSeq('CHAIN');\n }\n\n if (isRollback) {\n return this.expression(RollbackExpr, { savepoint });\n }\n\n return this.expression(CommitExpr, { chain });\n }\n\n parseRefresh (): RefreshExpr | CommandExpr {\n let kind: string;\n\n if (this.match(TokenType.TABLE)) {\n kind = 'TABLE';\n } else if (this.matchTextSeq(['MATERIALIZED', 'VIEW'])) {\n kind = 'MATERIALIZED VIEW';\n } else {\n kind = '';\n }\n\n const thisValue = this.parseString() || this.parseTable();\n if (!kind && !(thisValue instanceof LiteralExpr)) {\n return this.parseAsCommand(this.prev);\n }\n\n return this.expression(RefreshExpr, {\n this: thisValue,\n kind,\n });\n }\n\n parseColumnDefWithExists (): ColumnDefExpr | undefined {\n const start = this.index;\n this.match(TokenType.COLUMN);\n\n const existsColumn = this.parseExists({ not: true });\n const expression = this.parseFieldDef();\n\n if (!(expression instanceof ColumnDefExpr)) {\n this.retreat(start);\n return undefined;\n }\n\n expression.setArgKey('exists', existsColumn);\n\n return expression;\n }\n\n parseAddColumn (): ColumnDefExpr | undefined {\n if ((this.prev?.text ?? '').toUpperCase() !== 'ADD') {\n return undefined;\n }\n\n const expression = this.parseColumnDefWithExists();\n if (!expression) {\n return undefined;\n }\n\n if (this.matchTexts(['FIRST', 'AFTER'])) {\n const position = this.prev?.text ?? '';\n const columnPosition = this.expression(ColumnPositionExpr, {\n this: this.parseColumn(),\n position,\n });\n expression.setArgKey('position', columnPosition);\n }\n\n return expression;\n }\n\n parseDropColumn (): DropExpr | CommandExpr | undefined {\n const drop = (this.match(TokenType.DROP) || undefined) && this.parseDrop();\n if (drop && !(drop instanceof CommandExpr)) {\n drop.setArgKey('kind', drop.args.kind || 'COLUMN');\n }\n return drop;\n }\n\n parseDropPartition (options: { exists?: boolean } = {}): DropPartitionExpr {\n const { exists } = options;\n return this.expression(DropPartitionExpr, {\n expressions: this.parseCsv(this.parsePartition.bind(this)),\n exists,\n });\n }\n\n parseAlterTableAdd (): Expression[] {\n const parseAddAlteration = (): Expression | undefined => {\n this.matchTextSeq('ADD');\n if (this.matchSet(this._constructor.ADD_CONSTRAINT_TOKENS, { advance: false })) {\n return this.expression(AddConstraintExpr, {\n expressions: this.parseCsv(this.parseConstraint.bind(this)),\n });\n }\n\n const columnDef = this.parseAddColumn();\n if (columnDef instanceof ColumnDefExpr) {\n return columnDef;\n }\n\n const exists = this.parseExists({ not: true });\n if (this.matchPair(TokenType.PARTITION, TokenType.L_PAREN, { advance: false })) {\n return this.expression(AddPartitionExpr, {\n exists,\n this: this.parseField({ anyToken: true }),\n location:\n this.matchTextSeq('LOCATION', { advance: false }) && this.parseProperty(),\n });\n }\n\n return undefined;\n };\n\n if (\n !this.matchSet(this._constructor.ADD_CONSTRAINT_TOKENS, { advance: false })\n && (!this._dialectConstructor.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN\n || this.matchTextSeq('COLUMNS'))\n ) {\n const schema = this.parseSchema();\n\n return schema\n ? Array.from(ensureList<Expression>(schema))\n : this.parseCsv(this.parseColumnDefWithExists.bind(this));\n }\n\n return this.parseCsv(parseAddAlteration);\n }\n\n parseAlterTableAlter (): Expression | undefined {\n if (this.matchTexts(Object.keys(this._constructor.ALTER_ALTER_PARSERS))) {\n return this._constructor.ALTER_ALTER_PARSERS[(this.prev?.text ?? '').toUpperCase()]?.call(this);\n }\n\n this.match(TokenType.COLUMN);\n const column = this.parseField({ anyToken: true });\n\n if (this.matchPair(TokenType.DROP, TokenType.DEFAULT)) {\n return this.expression(AlterColumnExpr, {\n this: column,\n drop: true,\n });\n }\n if (this.matchPair(TokenType.SET, TokenType.DEFAULT)) {\n return this.expression(AlterColumnExpr, {\n this: column,\n default: this.parseDisjunction(),\n });\n }\n if (this.match(TokenType.COMMENT)) {\n return this.expression(AlterColumnExpr, {\n this: column,\n comment: this.parseString(),\n });\n }\n if (this.matchTextSeq([\n 'DROP',\n 'NOT',\n 'NULL',\n ])) {\n return this.expression(AlterColumnExpr, {\n this: column,\n drop: true,\n allowNull: true,\n });\n }\n if (this.matchTextSeq([\n 'SET',\n 'NOT',\n 'NULL',\n ])) {\n return this.expression(AlterColumnExpr, {\n this: column,\n allowNull: false,\n });\n }\n\n if (this.matchTextSeq(['SET', 'VISIBLE'])) {\n return this.expression(AlterColumnExpr, {\n this: column,\n visible: 'VISIBLE',\n });\n }\n if (this.matchTextSeq(['SET', 'INVISIBLE'])) {\n return this.expression(AlterColumnExpr, {\n this: column,\n visible: 'INVISIBLE',\n });\n }\n\n this.matchTextSeq(['SET', 'DATA']);\n this.matchTextSeq('TYPE');\n return this.expression(AlterColumnExpr, {\n this: column,\n dtype: this.parseTypes(),\n collate: this.match(TokenType.COLLATE) && this.parseTerm(),\n using: this.match(TokenType.USING) && this.parseDisjunction(),\n });\n }\n\n parseAlterDiststyle (): AlterDistStyleExpr {\n if (this.matchTexts([\n 'ALL',\n 'EVEN',\n 'AUTO',\n ])) {\n return this.expression(AlterDistStyleExpr, { this: var_((this.prev?.text ?? '').toUpperCase()) });\n }\n\n this.matchTextSeq(['KEY', 'DISTKEY']);\n return this.expression(AlterDistStyleExpr, { this: this.parseColumn() });\n }\n\n parseAlterSortkey (options: { compound?: boolean } = {}): AlterSortKeyExpr {\n const { compound } = options;\n\n if (compound) {\n this.matchTextSeq('SORTKEY');\n }\n\n if (this.match(TokenType.L_PAREN, { advance: false })) {\n return this.expression(AlterSortKeyExpr, {\n expressions: this.parseWrappedIdVars(),\n compound,\n });\n }\n\n this.matchTexts(['AUTO', 'NONE']);\n return this.expression(AlterSortKeyExpr, {\n this: var_((this.prev?.text ?? '').toUpperCase()),\n compound,\n });\n }\n\n parseAlterTableDrop (): Expression[] {\n const index = this.index - 1;\n\n const partitionExists = this.parseExists();\n if (this.match(TokenType.PARTITION, { advance: false })) {\n return this.parseCsv(() => this.parseDropPartition({ exists: partitionExists }));\n }\n\n this.retreat(index);\n return this.parseCsv(this.parseDropColumn.bind(this));\n }\n\n parseAlterTableRename (): AlterRenameExpr | RenameColumnExpr | undefined {\n if (this.match(TokenType.COLUMN) || !this._constructor.ALTER_RENAME_REQUIRES_COLUMN) {\n const exists = this.parseExists();\n const oldColumn = this.parseColumn();\n const to = this.matchTextSeq('TO') || undefined;\n const newColumn = this.parseColumn();\n\n if (!oldColumn || !to || !newColumn) {\n return undefined;\n }\n\n return this.expression(RenameColumnExpr, {\n this: oldColumn,\n to: newColumn,\n exists,\n });\n }\n\n this.matchTextSeq('TO');\n return this.expression(AlterRenameExpr, { this: this.parseTable({ schema: true }) });\n }\n\n parseLoad (): LoadDataExpr | CommandExpr {\n if (this.matchTextSeq('DATA')) {\n const local = this.matchTextSeq('LOCAL') || undefined;\n this.matchTextSeq('INPATH');\n const inpath = this.parseString();\n const overwrite = this.match(TokenType.OVERWRITE) || undefined;\n this.matchPair(TokenType.INTO, TokenType.TABLE);\n\n return this.expression(LoadDataExpr, {\n this: this.parseTable({ schema: true }),\n local,\n overwrite,\n inpath,\n partition: this.parsePartition(),\n inputFormat: this.matchTextSeq('INPUTFORMAT') && this.parseString(),\n serde: this.matchTextSeq('SERDE') && this.parseString(),\n });\n }\n return this.parseAsCommand(this.prev);\n }\n\n parseDelete (): DeleteExpr {\n let tables: Expression[] | undefined;\n if (!this.match(TokenType.FROM, { advance: false })) {\n tables = this.parseCsv(this.parseTable.bind(this)) || undefined;\n }\n\n const returning = this.parseReturning();\n\n return this.expression(DeleteExpr, {\n tables,\n this: this.match(TokenType.FROM) && this.parseTable({ joins: true }),\n using:\n this.match(TokenType.USING)\n && this.parseCsv(() => this.parseTable({ joins: true })),\n cluster: this.match(TokenType.ON) && this.parseOnProperty(),\n where: this.parseWhere(),\n returning: returning || this.parseReturning(),\n order: this.parseOrder(),\n limit: this.parseLimit(),\n });\n }\n\n parseUpdate (): UpdateExpr {\n const kwargs: Record<string, unknown> = {\n this: this.parseTable({\n joins: true,\n aliasTokens: this._constructor.UPDATE_ALIAS_TOKENS,\n }),\n };\n\n while (this.curr) {\n if (this.match(TokenType.SET)) {\n kwargs.expressions = this.parseCsv(this.parseEquality.bind(this));\n } else if (this.match(TokenType.RETURNING, { advance: false })) {\n kwargs.returning = this.parseReturning();\n } else if (this.match(TokenType.FROM, { advance: false })) {\n const from = this.parseFrom({ joins: true });\n const table = from?.args.this;\n if (table instanceof SubqueryExpr && this.match(TokenType.JOIN, { advance: false })) {\n const joins = this.parseJoins();\n table.setArgKey('joins', 0 < joins.length ? joins : undefined);\n }\n\n kwargs.from = from;\n } else if (this.match(TokenType.WHERE, { advance: false })) {\n kwargs.where = this.parseWhere();\n } else if (this.match(TokenType.ORDER_BY, { advance: false })) {\n kwargs.order = this.parseOrder();\n } else if (this.match(TokenType.LIMIT, { advance: false })) {\n kwargs.limit = this.parseLimit();\n } else {\n break;\n }\n }\n\n return this.expression(UpdateExpr, kwargs);\n }\n\n parseUse (): UseExpr {\n return this.expression(UseExpr, {\n kind: this.parseVarFromOptions(this._constructor.USABLES, { raiseUnmatched: false }),\n this: this.parseTable({ schema: false }),\n });\n }\n\n parseUncache (): UncacheExpr {\n if (!this.match(TokenType.TABLE)) {\n this.raiseError('Expecting TABLE after UNCACHE');\n }\n\n return this.expression(UncacheExpr, {\n exists: this.parseExists(),\n this: this.parseTable({ schema: true }),\n });\n }\n\n parseAsCommand (start?: Token): CommandExpr {\n while (this.curr) {\n this.advance();\n }\n const text = this.findSql(start, this.prev);\n const size = start?.text.length || 0;\n this.warnUnsupported();\n return new CommandExpr({\n this: text.substring(0, size),\n expression: text.substring(size),\n });\n }\n\n parseDictProperty (options: { this: string }): DictPropertyExpr {\n const settings: DictSubPropertyExpr[] = [];\n\n this.matchLParen();\n const kind = this.parseIdVar();\n\n if (this.match(TokenType.L_PAREN)) {\n while (true) {\n const key = this.parseIdVar();\n const value = this.parsePrimary();\n if (!key && !value) {\n break;\n }\n settings.push(this.expression(DictSubPropertyExpr, {\n this: key,\n value,\n }));\n }\n this.match(TokenType.R_PAREN);\n }\n\n this.matchRParen();\n\n return this.expression(DictPropertyExpr, {\n this: options.this,\n kind: kind?.args.this,\n settings,\n });\n }\n\n parseDictRange (options: { this: string }): DictRangeExpr {\n this.matchLParen();\n const hasMin = this.matchTextSeq('MIN') || undefined;\n let min: Expression | undefined;\n let max: Expression | undefined;\n\n if (hasMin) {\n min = this.parseVar() || this.parsePrimary();\n if (!min) {\n this.raiseError('Expected MIN value');\n }\n this.matchTextSeq('MAX');\n max = this.parseVar() || this.parsePrimary();\n if (!max) {\n this.raiseError('Expected MAX value');\n }\n } else {\n max = this.parseVar() || this.parsePrimary();\n if (!max) {\n this.raiseError('Expected MAX value');\n }\n min = LiteralExpr.number(0);\n }\n this.matchRParen();\n return this.expression(DictRangeExpr, {\n this: options.this,\n min: min,\n max: max,\n });\n }\n\n parseComprehension (thisValue?: Expression): ComprehensionExpr | undefined {\n const index = this.index;\n const expression = this.parseColumn();\n const position = (this.match(TokenType.COMMA) || undefined) && this.parseColumn();\n\n if (!this.match(TokenType.IN)) {\n this.retreat(index - 1);\n return undefined;\n }\n const iterator = this.parseColumn();\n const condition = this.matchTextSeq('IF') ? this.parseDisjunction() : undefined;\n return this.expression(ComprehensionExpr, {\n this: thisValue,\n expression,\n position,\n iterator,\n condition,\n });\n }\n\n parseHeredoc (): HeredocExpr | undefined {\n if (this.match(TokenType.HEREDOC_STRING)) {\n return this.expression(HeredocExpr, { this: this.prev?.text ?? '' });\n }\n\n if (!this.matchTextSeq('$')) {\n return undefined;\n }\n\n const tags = ['$'];\n let tagText: string | undefined;\n\n if (this.isConnected()) {\n this.advance();\n tags.push((this.prev?.text ?? '').toUpperCase());\n } else {\n this.raiseError('No closing $ found');\n }\n\n if (tags[tags.length - 1] !== '$') {\n if (this.isConnected() && this.matchTextSeq('$')) {\n tagText = tags[tags.length - 1];\n tags.push('$');\n } else {\n this.raiseError('No closing $ found');\n }\n }\n\n const heredocStart = this.curr;\n\n while (this.curr) {\n if (this.matchTextSeq(tags, { advance: false })) {\n const thisValue = this.findSql(heredocStart, this.prev);\n this.advance(tags.length);\n return this.expression(HeredocExpr, {\n this: thisValue,\n tag: tagText,\n });\n }\n\n this.advance();\n }\n\n this.raiseError(`No closing ${tags.join('')} found`);\n return undefined;\n }\n\n findParser (\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n parsers: Record<string, Function>,\n trie: TrieNode,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ): Function | undefined {\n if (!this.curr) {\n return undefined;\n }\n\n const index = this.index;\n const thisPath: string[] = [];\n while (true) {\n const curr = this.curr.text.toUpperCase();\n const key = curr.split(' ');\n thisPath.push(curr);\n\n this.advance();\n const [result, newTrie] = inTrie(trie, key);\n trie = newTrie;\n if (result === TrieResult.FAILED) {\n break;\n }\n\n if (result === TrieResult.EXISTS) {\n const subparser = parsers[thisPath.join(' ')];\n return subparser;\n }\n }\n\n this.retreat(index);\n return undefined;\n }\n\n parseGroupConcat (): Expression | undefined {\n const concatExprs = (\n node: Expression | undefined,\n exprs: Expression[],\n ): Expression => {\n if (node instanceof DistinctExpr && 1 < (node.args.expressions?.length ?? 0)) {\n const concatExpressions = [\n this.expression(ConcatExpr, {\n expressions: node.args.expressions,\n safe: true,\n coalesce: this._dialectConstructor.CONCAT_COALESCE,\n }),\n ];\n node.setArgKey('expressions', concatExpressions);\n return node;\n }\n if (exprs.length === 1) {\n return exprs[0];\n }\n return this.expression(ConcatExpr, {\n expressions: exprs,\n safe: true,\n coalesce: this._dialectConstructor.CONCAT_COALESCE,\n });\n };\n\n const args = this.parseCsv(this.parseLambda.bind(this));\n\n let thisValue: Expression | undefined;\n if (args) {\n const lastArg = args[args.length - 1];\n const order: OrderExpr | undefined =\n lastArg instanceof OrderExpr ? lastArg : undefined;\n\n if (order?.args.this) {\n args[args.length - 1] = order.args.this;\n order.setArgKey('this', concatExprs(order.args.this, args));\n }\n\n thisValue = order || concatExprs(args[0], args);\n } else {\n thisValue = undefined;\n }\n\n const separator = this.match(TokenType.SEPARATOR) ? this.parseField() : undefined;\n\n return this.expression(GroupConcatExpr, {\n this: thisValue,\n separator,\n });\n }\n\n parseInitcap (): InitcapExpr {\n const expr = InitcapExpr.fromArgList(this.parseFunctionArgs());\n\n if (!expr.args.expression) {\n expr.setArgKey(\n 'expression',\n LiteralExpr.string(this._dialectConstructor.INITCAP_DEFAULT_DELIMITER_CHARS),\n );\n }\n\n return expr;\n }\n\n parseOperator (thisValue?: Expression): Expression | undefined {\n let result = thisValue;\n\n while (true) {\n if (!this.match(TokenType.L_PAREN)) {\n break;\n }\n\n let op = '';\n while (this.curr && !this.match(TokenType.R_PAREN)) {\n op += this.curr.text;\n this.advance();\n }\n\n result = this.expression(OperatorExpr, {\n comments: this.prevComments,\n this: result,\n operator: op,\n expression: this.parseBitwise(),\n });\n\n if (!this.match(TokenType.OPERATOR)) {\n break;\n }\n }\n\n return result;\n }\n\n buildPipeCte (options: {\n query: QueryExpr;\n expressions: Expression[];\n aliasCte?: TableAliasExpr;\n }): SelectExpr {\n const {\n query, expressions, aliasCte,\n } = options;\n let newCte: string | TableAliasExpr;\n if (aliasCte) {\n newCte = aliasCte;\n } else {\n this.pipeCteCounter += 1;\n newCte = `__tmp${this.pipeCteCounter}`;\n }\n\n const with_ = query.args.with;\n const ctes = with_?.pop();\n\n const newSelect = select(...expressions, { copy: false }).from(newCte, { copy: false });\n if (ctes) {\n newSelect.setArgKey('with', ctes);\n }\n\n return newSelect.with(newCte, query, {\n copy: false,\n });\n }\n\n parsePipeSyntaxSelect (query: SelectExpr): SelectExpr {\n const select = this.parseSelect({ consumePipe: false }) as SelectExpr;\n if (!select) {\n return query;\n }\n\n return this.buildPipeCte({\n query: query.select(select.args.expressions, { append: false }),\n expressions: [new StarExpr({})],\n });\n }\n\n parsePipeSyntaxLimit (query: SelectExpr): SelectExpr {\n const limit = this.parseLimit() as LimitExpr;\n const offset = this.parseOffset() as OffsetExpr;\n if (limit) {\n const currLimit = query.args.limit || limit;\n let currLimitValue: number;\n if (typeof currLimit === 'number') {\n currLimitValue = currLimit;\n } else {\n assertIsInstanceOf(currLimit, LimitExpr);\n const expr = currLimit.args.expression;\n assertIsInstanceOf(expr, Expression);\n currLimitValue = expr.toValue() as number;\n }\n const limitValue = (limit.args.expression?.toValue() ?? 0) as number;\n if (limitValue <= currLimitValue) {\n query.limit(limit, { copy: false });\n }\n }\n if (offset) {\n const currOffset = query.args.offset;\n let currOffsetVal: number;\n if (currOffset === undefined) {\n currOffsetVal = 0;\n } else if (typeof currOffset === 'number') {\n currOffsetVal = currOffset;\n } else {\n assertIsInstanceOf(currOffset, OffsetExpr);\n const expr = currOffset.args.expression;\n currOffsetVal = typeof expr === 'number' ? expr : (assertIsInstanceOf(expr, Expression), expr.toValue() as number);\n }\n const offsetExpr = offset.args.expression;\n const offsetVal: number = typeof offsetExpr === 'number' ? offsetExpr : (assertIsInstanceOf(offsetExpr, Expression), offsetExpr.toValue() as number);\n\n query.offset(LiteralExpr.number(currOffsetVal + offsetVal));\n }\n\n return query;\n }\n\n parsePipeSyntaxAggregateFields (): Expression | undefined {\n let thisValue = this.parseDisjunction();\n if (this.matchTextSeq(['GROUP', 'AND'], { advance: false })) {\n return thisValue;\n }\n\n thisValue = this.parseAlias(thisValue);\n\n if (this.matchSet([TokenType.ASC, TokenType.DESC], { advance: false })) {\n return this.parseOrdered(() => thisValue);\n }\n\n return thisValue;\n }\n\n parsePipeSyntaxAggregateGroupOrderBy (\n query: SelectExpr,\n options: { groupByExists?: boolean } = {},\n ): SelectExpr {\n const { groupByExists = true } = options;\n const expr = this.parseCsv(this.parsePipeSyntaxAggregateFields.bind(this));\n const aggregatesOrGroups: Expression[] = [];\n const orders: OrderedExpr[] = [];\n\n for (const element of expr) {\n let thisValue: Expression | undefined;\n if (element instanceof OrderedExpr) {\n thisValue = element.args.this;\n if (thisValue instanceof AliasExpr) {\n element.setArgKey('this', thisValue.args.alias);\n }\n orders.push(element);\n } else {\n thisValue = element;\n }\n if (thisValue) aggregatesOrGroups.push(thisValue);\n }\n\n if (groupByExists) {\n query\n .select(aggregatesOrGroups, { copy: false })\n .groupBy(\n ...aggregatesOrGroups.map((proj) => proj.args.alias || proj),\n { copy: false },\n );\n } else {\n query.select(aggregatesOrGroups, {\n append: false,\n copy: false,\n });\n }\n\n if (0 < orders.length) {\n return query.orderBy(...orders, {\n append: false,\n copy: false,\n });\n }\n\n return query;\n }\n\n parsePipeSyntaxAggregate (query: SelectExpr): SelectExpr {\n this.matchTextSeq('AGGREGATE');\n query = this.parsePipeSyntaxAggregateGroupOrderBy(query, { groupByExists: false });\n\n if (\n this.match(TokenType.GROUP_BY)\n || (this.matchTextSeq(['GROUP', 'AND']) && this.match(TokenType.ORDER_BY))\n ) {\n query = this.parsePipeSyntaxAggregateGroupOrderBy(query);\n }\n\n return this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n });\n }\n\n parsePipeSyntaxSetOperator (query?: QueryExpr): QueryExpr | undefined {\n if (!query) {\n return undefined;\n }\n const firstSetop = this.parseSetOperation(query) as SetOperationExpr;\n if (!firstSetop) {\n return undefined;\n }\n\n const parseAndUnwrapQuery = (): SelectExpr | undefined => {\n const expr = this.parseParen();\n return expr ? expr.assertIs(SubqueryExpr).unnest() as SelectExpr : undefined;\n };\n\n firstSetop.args.this?.pop();\n\n const setops = [\n firstSetop.args.expression?.pop().assertIs(SubqueryExpr)\n .unnest(),\n ...this.parseCsv(parseAndUnwrapQuery),\n ];\n\n query = this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n });\n const with_ = query.args.with;\n const ctes = with_?.pop();\n\n if (firstSetop instanceof UnionExpr) {\n query = query.union(...setops, {\n copy: false,\n ...firstSetop.args,\n });\n } else if (firstSetop instanceof ExceptExpr) {\n query = query.except(...setops, {\n copy: false,\n ...firstSetop.args,\n });\n } else {\n query = query.intersect(...setops, {\n copy: false,\n ...firstSetop.args,\n });\n }\n\n if (!query) {\n return undefined;\n }\n\n query.setArgKey('with', ctes);\n\n return this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n });\n }\n\n parsePipeSyntaxJoin (query?: QueryExpr): QueryExpr | undefined {\n if (!query) return undefined;\n const join = this.parseJoin();\n if (!join) {\n return undefined;\n }\n\n if (query instanceof SelectExpr) {\n return query.join(join, { copy: false });\n }\n\n return query;\n }\n\n parsePipeSyntaxPivot (query: SelectExpr): SelectExpr {\n const pivots = this.parsePivots();\n if (!pivots) {\n return query;\n }\n\n const from = query.args.from;\n if (from) {\n const fromThis = from.args.this;\n if (isInstanceOf(fromThis, Expression)) {\n fromThis.setArgKey('pivots', pivots);\n }\n }\n\n return this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n });\n }\n\n parsePipeSyntaxExtend (query: SelectExpr): SelectExpr {\n this.matchTextSeq('EXTEND');\n query.select([new StarExpr({}), ...this.parseExpressions()], {\n append: false,\n copy: false,\n });\n return this.buildPipeCte({\n query,\n expressions: [new StarExpr({})],\n });\n }\n\n parsePipeSyntaxTablesample (query: SelectExpr): SelectExpr {\n const sample = this.parseTableSample();\n\n const with_ = query.args.with;\n if (with_) {\n const expression = with_.args.expressions?.[with_.args.expressions.length - 1];\n if (expression instanceof Expression && expression.args.this instanceof Expression) {\n expression.args.this.setArgKey('sample', sample);\n }\n } else {\n query.setArgKey('sample', sample);\n }\n\n return query;\n }\n\n parsePipeSyntaxQuery (query: QueryExpr): QueryExpr | undefined {\n let result: QueryExpr | undefined = query;\n\n if (result instanceof SubqueryExpr) {\n result = select('*').from(result, { copy: false });\n }\n\n if (!result.args.from) {\n result = select('*').from(result.subquery(undefined, { copy: false }), { copy: false });\n }\n\n while (this.match(TokenType.PIPE_GT)) {\n const start = this.curr;\n const startIdx = start ? this.tokens.indexOf(start) : -1;\n const parser =\n this._constructor.PIPE_SYNTAX_TRANSFORM_PARSERS[this.curr?.text.toUpperCase() ?? ''];\n if (!parser) {\n let parsedQuery = this.parsePipeSyntaxSetOperator(result);\n parsedQuery = parsedQuery || this.parsePipeSyntaxJoin(result);\n if (!parsedQuery && 0 <= startIdx) {\n this.retreat(startIdx);\n this.raiseError(`Unsupported pipe syntax operator: '${start?.text.toUpperCase()}'.`);\n break;\n }\n result = parsedQuery;\n } else {\n result = parser.call(this, result as SelectExpr);\n }\n }\n\n return result;\n }\n\n parseDeclareitem (): DeclareItemExpr | undefined {\n const vars = this.parseCsv(this.parseIdVar.bind(this));\n if (!vars) {\n return undefined;\n }\n\n return this.expression(DeclareItemExpr, {\n this: vars,\n kind: this.parseTypes(),\n default: this.match(TokenType.DEFAULT) && this.parseBitwise(),\n });\n }\n\n parseDeclare (): DeclareExpr | CommandExpr {\n const start = this.prev;\n const expressions = this.tryParse(() => this.parseCsv(this.parseDeclareitem.bind(this)));\n\n if (!expressions || this.curr) {\n return this.parseAsCommand(start);\n }\n\n return this.expression(DeclareExpr, { expressions });\n }\n\n buildCast (options: {\n strict: boolean;\n [key: string]: unknown;\n }): CastExpr | TryCastExpr {\n const { strict } = options;\n const ExpClass = strict ? CastExpr : TryCastExpr;\n\n const kwargs: Record<string, unknown> = { ...options };\n delete kwargs.strict;\n\n if (ExpClass === TryCastExpr) {\n kwargs.requiresString = this._dialectConstructor.TRY_CAST_REQUIRES_STRING;\n }\n\n return this.expression(ExpClass, kwargs);\n }\n\n parseJsonValue (): JsonValueExpr {\n const thisValue = this.parseBitwise();\n this.match(TokenType.COMMA);\n const path = this.parseBitwise();\n\n const returning = (this.match(TokenType.RETURNING) || undefined) && this.parseType();\n\n return this.expression(JsonValueExpr, {\n this: thisValue,\n path: this.dialect.toJsonPath(path),\n returning,\n onCondition: this.parseOnCondition(),\n });\n }\n\n /**\n * Validates an Expression, making sure that all its mandatory arguments are set.\n *\n * @param expression - The expression to validate.\n * @param args - An optional list of items that was used to instantiate the expression, if it's a Func.\n * @returns The validated expression.\n */\n validateExpression<E extends Expression> (\n expression: E,\n args?: unknown[],\n ): E {\n if (this.errorLevel !== ErrorLevel.IGNORE) {\n for (const errorMessage of expression.errorMessages(args)) {\n this.raiseError(errorMessage);\n }\n }\n\n return expression;\n }\n\n get _constructor (): typeof Parser {\n return this.constructor as typeof Parser;\n }\n\n get _dialectConstructor (): typeof Dialect {\n return this.dialect._constructor;\n }\n}\n","// https://github.com/tobymao/sqlglot/blob/main/sqlglot/generator.py\n\nimport {\n cache,\n assertIsInstanceOf, isInstanceOf,\n} from './port_internals';\nimport type {\n AddConstraintExpr,\n AddPartitionExpr,\n AliasesExpr,\n AlterColumnExpr,\n AlterDistStyleExpr,\n AlterExpr,\n AlterIndexExpr,\n AlterSessionExpr,\n AlterSetExpr,\n AlterSortKeyExpr,\n AnalyzeDeleteExpr,\n AnalyzeExpr,\n AnalyzeHistogramExpr,\n AnalyzeListChainedRowsExpr,\n AnalyzeSampleExpr,\n AnalyzeStatisticsExpr,\n AnalyzeValidateExpr,\n AnonymousAggFuncExpr,\n AnonymousExpr,\n AnyValueExpr,\n ApplyExpr,\n ArrayAggExpr,\n ArrayAnyExpr,\n AtIndexExpr,\n AttachExpr,\n AttachOptionExpr,\n AutoIncrementColumnConstraintExpr,\n BetweenExpr,\n BitStringExpr,\n BitwiseAndExpr,\n BitwiseLeftShiftExpr,\n BitwiseNotExpr,\n BitwiseOrExpr,\n BitwiseRightShiftExpr,\n BitwiseXorExpr,\n BoolandExpr,\n BoolorExpr,\n BuildPropertyExpr,\n ByteStringExpr,\n CteExpr,\n CacheExpr,\n ChangesExpr,\n CharacterSetExpr,\n CheckColumnConstraintExpr,\n CheckExpr,\n CloneExpr,\n CollateExpr,\n ColumnConstraintExpr,\n ColumnPositionExpr,\n ColumnPrefixExpr,\n ColumnsExpr,\n CombinedAggFuncExpr,\n CombinedParameterizedAggExpr,\n CommentExpr,\n CommitExpr,\n ComprehensionExpr,\n CompressColumnConstraintExpr,\n ConcatExpr,\n ConditionalInsertExpr,\n ConnectExpr,\n ConstraintExpr,\n ConvertExpr,\n CopyExpr,\n CopyParameterExpr,\n CredentialsExpr,\n CubeExpr,\n DPipeExpr,\n DataTypeParamExpr,\n DateFromUnixDateExpr,\n DeclareExpr,\n DeclareItemExpr,\n DecodeCaseExpr,\n DetachExpr,\n DictSubPropertyExpr,\n DirectoryStageExpr,\n DistanceExpr,\n DistributeExpr,\n DropPartitionExpr,\n EscapeExpr,\n ExistsExpr,\n ExplodingGenerateSeriesExpr,\n ExportExpr,\n ExtractExpr,\n FeaturesAtTimeExpr,\n ForInExpr,\n ForeignKeyExpr,\n FormatJsonExpr,\n FormatPhraseExpr,\n FromTimeZoneExpr,\n GteExpr,\n GtExpr,\n GapFillExpr,\n GenerateEmbeddingExpr,\n GeneratedAsIdentityColumnConstraintExpr,\n GeneratedAsRowColumnConstraintExpr,\n GetExtractExpr,\n GlobExpr,\n GrantExpr,\n GrantPrincipalExpr,\n GrantPrivilegeExpr,\n GroupingSetsExpr,\n HeredocExpr,\n HexExpr,\n HexStringExpr,\n HintExpr,\n IgnoreNullsExpr,\n InExpr,\n InOutColumnConstraintExpr,\n IndexColumnConstraintExpr,\n IndexConstraintOptionExpr,\n IndexExpr,\n IndexParametersExpr,\n InitcapExpr,\n InputOutputFormatExpr,\n InstallExpr,\n IntDivExpr,\n IntroducerExpr,\n JsonArrayAggExpr,\n JsonArrayExpr,\n JsonCastExpr,\n JsonColumnDefExpr,\n JsonExistsExpr,\n JsonExpr,\n JsonExtractQuoteExpr,\n JsonKeyValueExpr,\n JsonObjectAggExpr,\n JsonPathExpr,\n JsonSchemaExpr,\n JsonTableExpr,\n JsonValueExpr,\n JoinHintExpr,\n KwargExpr,\n LteExpr,\n LtExpr,\n LastDayExpr,\n LimitOptionsExpr,\n LoadDataExpr,\n LocaltimeExpr,\n LocaltimestampExpr,\n LockExpr,\n LogExpr,\n LowerHexExpr,\n MlForecastExpr,\n MlTranslateExpr,\n MaskingPolicyColumnConstraintExpr,\n MatchAgainstExpr,\n MatchExpr,\n MatchRecognizeExpr,\n MatchRecognizeMeasureExpr,\n MedianExpr,\n MergeExpr,\n MergeTreeTtlActionExpr,\n ModExpr,\n ModelAttributeExpr,\n NeqExpr,\n NationalExpr,\n NextValueForExpr,\n NotNullColumnConstraintExpr,\n NullSafeEqExpr,\n NullSafeNeqExpr,\n Nvl2Expr,\n ObjectIdentifierExpr,\n OffsetExpr,\n OnClusterExpr,\n OnConditionExpr,\n OnConflictExpr,\n OpclassExpr,\n OpenJsonColumnDefExpr,\n OpenJsonExpr,\n OrderedExpr,\n OverflowTruncateBehaviorExpr,\n OverlapsExpr,\n OverlayExpr,\n PadExpr,\n ParameterExpr,\n ParameterizedAggExpr,\n ParseJsonExpr,\n PartitionByRangePropertyDynamicExpr,\n PartitionByRangePropertyExpr,\n PartitionExpr,\n PartitionRangeExpr,\n PeriodForSystemTimeConstraintExpr,\n PivotAliasExpr,\n PlaceholderExpr,\n PragmaExpr,\n PredictExpr,\n PrimaryKeyColumnConstraintExpr,\n PriorExpr,\n PseudoTypeExpr,\n PseudocolumnExpr,\n QualifyExpr,\n QueryBandExpr,\n QueryExpr,\n QueryOptionExpr,\n QueryTransformExpr,\n RawStringExpr,\n RecursiveWithSearchExpr,\n ReferenceExpr,\n RefreshExpr,\n RenameColumnExpr,\n RespectNullsExpr,\n ReturningExpr,\n RevokeExpr,\n RollbackExpr,\n RollupExpr,\n RollupIndexExpr,\n SafeDivideExpr,\n ScopeResolutionExpr,\n SemanticViewExpr,\n SemicolonExpr,\n SessionParameterExpr,\n SetItemExpr,\n ShowExpr,\n SimilarToExpr,\n SliceExpr,\n SortExpr,\n SpaceExpr,\n StringExpr,\n SummarizeExpr,\n TableFromRowsExpr,\n TagExpr,\n ToArrayExpr,\n ToCharExpr,\n ToDoubleExpr,\n ToNumberExpr,\n TransactionExpr,\n TranslateCharactersExpr,\n TrimExpr,\n TruncateTableExpr,\n TryExpr,\n UncacheExpr,\n UnicodeStringExpr,\n UniqueColumnConstraintExpr,\n UnixDateExpr,\n UnixSecondsExpr,\n UnpivotColumnsExpr,\n UseExpr,\n UserDefinedFunctionExpr,\n UuidExpr,\n ValuesExpr,\n VectorSearchExpr,\n VersionExpr,\n WatermarkColumnConstraintExpr,\n WeekStartExpr,\n WhenExpr,\n WhensExpr,\n WindowSpecExpr,\n WithFillExpr,\n WithinGroupExpr,\n XmlElementExpr,\n XmlKeyValueOptionExpr,\n XmlNamespaceExpr,\n XmlTableExpr,\n KillExpr,\n IndexTableHintExpr,\n HistoricalDataExpr,\n JsonPathKeyExpr,\n JsonPathSubscriptExpr,\n TableSampleExpr,\n UniqueKeyPropertyExpr,\n XorExpr,\n NotExpr,\n EqExpr,\n IsExpr,\n OrExpr,\n AndExpr,\n AddExpr,\n SubExpr,\n MulExpr,\n ExpressionValue,\n ExpressionOrString,\n JsonExtractScalarExpr, JsonbExtractExpr, JsonbExtractScalarExpr,\n} from './expressions';\nimport {\n DistinctExpr,\n FilterExpr,\n JsonExtractExpr,\n ConnectorExpr,\n AlterRenameExpr,\n BracketExpr,\n DateAddExpr,\n RandExpr,\n WindowExpr,\n WithTableHintExpr,\n Expression, LiteralExpr, TableExpr, IntoExpr, literal,\n FuncExpr, PropertyExpr,\n CaseExpr, IfExpr, NullExpr, BooleanExpr,\n CastExpr, DivExpr, DataTypeExpr, DataTypeExprKind,\n JoinExpr, SelectExpr, StarExpr, FromExpr, SubqueryExpr, TableAliasExpr,\n PropertiesExpr, PropertiesLocation,\n ArrayFilterExpr, ArraySizeExpr,\n ParenExpr, LikeExpr, ILikeExpr, AllExpr, AnyExpr, TupleExpr, ConditionExpr,\n BinaryExpr, UnionExpr,\n IdentifierExpr, ColumnExpr,\n alias, toIdentifier, cast, or, and,\n DirectoryExpr, PropertyEqExpr, AtTimeZoneExpr,\n StructExpr,\n TsOrDsToTimeExpr, TsOrDsToTimestampExpr, TsOrDsToDatetimeExpr, TsOrDsToDateExpr,\n StrToTimeExpr, TryCastExpr,\n TimestampDiffExpr,\n ConvertTimezoneExpr,\n GenerateSeriesExpr, UnnestExpr,\n PercentileContExpr,\n JsonPathPartExpr, JsonPathWildcardExpr,\n JsonPathFilterExpr, JsonPathRecursiveExpr, JsonPathRootExpr,\n JsonPathScriptExpr, JsonPathSelectorExpr, JsonPathSliceExpr, JsonPathUnionExpr,\n AliasExpr, VarExpr,\n var_,\n ComputedColumnConstraintExpr,\n NullifExpr,\n CoalesceExpr,\n ReturnExpr,\n LateralExpr,\n FetchExpr,\n LimitExpr,\n PivotExpr,\n ConcatWsExpr,\n DotExpr,\n PartitionBoundSpecExpr,\n IntervalExpr,\n ColumnDefExpr,\n SchemaExpr,\n JsonObjectExpr,\n IntervalSpanExpr,\n HavingMaxExpr,\n OrderExpr,\n PutExpr,\n InsertExpr,\n UpdateExpr,\n AdjacentExpr,\n AllowedValuesPropertyExpr,\n AnalyzeColumnsExpr,\n AnalyzeWithExpr,\n ArrayContainsAllExpr,\n ArrayOverlapsExpr,\n AutoRefreshPropertyExpr,\n BackupPropertyExpr,\n CaseSpecificColumnConstraintExpr,\n CeilExpr,\n CharacterSetColumnConstraintExpr,\n CharacterSetPropertyExpr,\n ClusteredColumnConstraintExpr,\n CollateColumnConstraintExpr,\n CommentColumnConstraintExpr,\n ConnectByRootExpr,\n ConvertToCharsetExpr,\n CopyGrantsPropertyExpr,\n CredentialsPropertyExpr,\n CurrentCatalogExpr,\n CurrentDateExpr,\n CurrentTimeExpr,\n CurrentTimestampExpr,\n DateFormatColumnConstraintExpr,\n DefaultColumnConstraintExpr,\n DynamicPropertyExpr,\n EmptyPropertyExpr,\n EncodeColumnConstraintExpr,\n EnviromentPropertyExpr,\n EphemeralColumnConstraintExpr,\n ExcludeColumnConstraintExpr,\n ExceptExpr,\n ExecuteAsPropertyExpr,\n ExternalPropertyExpr,\n ExtendsLeftExpr,\n ExtendsRightExpr,\n FloorExpr,\n ForcePropertyExpr,\n GetExpr,\n GlobalPropertyExpr,\n HeapPropertyExpr,\n IcebergPropertyExpr,\n InheritsPropertyExpr,\n InlineLengthColumnConstraintExpr,\n InputModelPropertyExpr,\n IntersectExpr,\n Int64Expr,\n JsonbContainsAllTopKeysExpr,\n JsonbContainsAnyTopKeysExpr,\n JsonbDeleteAtPathExpr,\n LanguagePropertyExpr,\n LocationPropertyExpr,\n LogPropertyExpr,\n MaterializedPropertyExpr,\n NetFuncExpr,\n NonClusteredColumnConstraintExpr,\n NoPrimaryIndexPropertyExpr,\n NotForReplicationColumnConstraintExpr,\n OnCommitPropertyExpr,\n OnPropertyExpr,\n OnUpdateColumnConstraintExpr,\n OperatorExpr,\n OutputModelPropertyExpr,\n PartitionByTruncateExpr,\n PartitionedByBucketExpr,\n PathColumnConstraintExpr,\n PivotAnyExpr,\n PositionalColumnExpr,\n ProjectionPolicyColumnConstraintExpr,\n RemoteWithConnectionModelPropertyExpr,\n ReturnsPropertyExpr,\n SafeFuncExpr,\n SamplePropertyExpr,\n SecurePropertyExpr,\n SecurityPropertyExpr,\n SessionUserExpr,\n SetConfigPropertyExpr,\n SetPropertyExpr,\n SettingsPropertyExpr,\n SharingPropertyExpr,\n SqlReadWritePropertyExpr,\n SqlSecurityPropertyExpr,\n StabilityPropertyExpr,\n StreamExpr,\n StreamingTablePropertyExpr,\n StrictPropertyExpr,\n SwapTableExpr,\n TableColumnExpr,\n TagsExpr,\n TemporaryPropertyExpr,\n TitleColumnConstraintExpr,\n ToMapExpr,\n ToTablePropertyExpr,\n TransformModelPropertyExpr,\n TransientPropertyExpr,\n UnloggedPropertyExpr,\n UppercaseColumnConstraintExpr,\n UsingDataExpr,\n UsingTemplatePropertyExpr,\n UtcDateExpr,\n UtcTimeExpr,\n UtcTimestampExpr,\n VariadicExpr,\n VarMapExpr,\n ViewAttributePropertyExpr,\n VolatilePropertyExpr,\n WithJournalTablePropertyExpr,\n WithOperatorExpr,\n WithProcedureOptionsExpr,\n WithSchemaBindingPropertyExpr,\n ZeroFillColumnConstraintExpr,\n AlgorithmPropertyExpr,\n AutoIncrementPropertyExpr,\n BlockCompressionPropertyExpr,\n ChecksumPropertyExpr,\n CollatePropertyExpr,\n ClusterExpr,\n ClusteredByPropertyExpr,\n DistributedByPropertyExpr,\n DuplicateKeyPropertyExpr,\n DataBlocksizePropertyExpr,\n DataDeletionPropertyExpr,\n DefinerPropertyExpr,\n DictRangeExpr,\n DictPropertyExpr,\n DistKeyPropertyExpr,\n DistStylePropertyExpr,\n EncodePropertyExpr,\n EnginePropertyExpr,\n FallbackPropertyExpr,\n FileFormatPropertyExpr,\n FreespacePropertyExpr,\n IncludePropertyExpr,\n IsolatedLoadingPropertyExpr,\n JournalPropertyExpr,\n LikePropertyExpr,\n LockPropertyExpr,\n LockingPropertyExpr,\n MergeBlockRatioPropertyExpr,\n PartitionedByPropertyExpr,\n PartitionedOfPropertyExpr,\n PrimaryKeyExpr,\n RefreshTriggerPropertyExpr,\n RollupPropertyExpr,\n RowFormatPropertyExpr,\n RowFormatDelimitedPropertyExpr,\n RowFormatSerdePropertyExpr,\n SchemaCommentPropertyExpr,\n SerdePropertiesExpr,\n SetExpr,\n SequencePropertiesExpr,\n SortKeyPropertyExpr,\n StorageHandlerPropertyExpr,\n WithSystemVersioningPropertyExpr,\n WithDataPropertyExpr,\n MergeTreeTtlExpr,\n CommandExpr,\n CreateExpr,\n DescribeExpr,\n DeleteExpr,\n DropExpr,\n GroupExpr,\n HavingExpr,\n WhereExpr,\n WithExpr,\n MultitableInsertsExpr,\n SetOperationExpr,\n NegExpr,\n UNWRAPPED_QUERIES,\n subquery,\n true_,\n union,\n JoinExprKind,\n AggFuncExpr,\n maybeCopy,\n null_,\n case_,\n wrap,\n not,\n paren,\n func,\n array,\n StrToDateExpr,\n TimeToStrExpr,\n} from './expressions';\nimport { formatTime } from './time';\nimport type { ParseOptions } from './parser';\nimport { ALL_FUNCTIONS } from './parser/function_registry';\nimport {\n Dialect, type DialectType, mapDatePart, unitToStr,\n concatToDPipeSql, NullOrdering, NullOrderingSupported, NormalizeFunctions,\n Dialects,\n} from './dialects/dialect';\nimport {\n ErrorLevel, UnsupportedError, concatMessages,\n} from './errors';\nimport {\n TokenType,\n} from './tokens';\nimport { simplify } from './optimizer/simplify';\nimport {\n applyIndexOffset, camelToScreamingSnakeCase, csv, mapOnExpression, nameSequence,\n seqGet,\n} from './helper';\nimport { ALL_JSON_PATH_PARTS } from './jsonpath/expressions';\nimport { annotateTypes } from './optimizer/annotate_types';\nimport type { TrieNode } from './trie';\nimport {\n ensureBools, moveCtesToTopLevel,\n} from './transforms';\n\nexport interface GeneratorOptions extends ParseOptions {\n pretty?: boolean;\n identify?: string | boolean;\n normalize?: boolean;\n pad?: number;\n indent?: number;\n normalizeFunctions?: NormalizeFunctions;\n unsupportedLevel?: ErrorLevel;\n maxUnsupported?: number;\n leadingComma?: boolean;\n maxTextWidth?: number;\n comments?: boolean;\n dialect?: DialectType;\n [key: string]: unknown;\n}\n\nexport interface TranspileOptions extends ParseOptions {\n write?: DialectType;\n identity?: boolean;\n errorLevel?: ErrorLevel;\n pretty?: boolean;\n [key: string]: unknown;\n}\n\n// Constants\nconst ESCAPED_UNICODE_RE = /\\\\(\\d+)/g;\n\nexport function unsupportedArgs<T extends Expression> (\n this: Generator,\n expression: T,\n ...args: (string | [string, string])[]\n): void {\n const expressionName = expression._constructor.name;\n const dialectName = this.dialect._constructor.name;\n for (const arg of args) {\n const [argName, diagnostic] = typeof arg === 'string' ? [arg, undefined] : arg;\n if (expression.getArgKey(argName)) {\n this.unsupported(diagnostic ?? `Argument '${argName}' is not supported for expression '${expressionName}' when targeting ${dialectName}.`);\n }\n }\n}\n\n/**\n * Generator converts a given syntax tree to the corresponding SQL string.\n *\n * Args:\n * pretty: Whether to format the produced SQL string. Default: False.\n * identify: Determines when an identifier should be quoted. Possible values are:\n * False (default): Never quote, except in cases where it's mandatory by the dialect.\n * True: Always quote except for specials cases.\n * 'safe': Only quote identifiers that are case insensitive.\n * normalize: Whether to normalize identifiers to lowercase. Default: False.\n * pad: The pad size in a formatted string. Default: 2.\n * indent: The indentation size in a formatted string. Default: 2.\n * normalizeFunctions: How to normalize function names. Possible values are:\n * \"upper\" or True (default): Convert names to uppercase.\n * \"lower\": Convert names to lowercase.\n * False: Disables function name normalization.\n * unsupportedLevel: Determines the generator's behavior when it encounters unsupported expressions.\n * Default ErrorLevel.WARN.\n * maxUnsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.\n * This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3\n * leadingComma: Whether the comma is leading or trailing in select expressions.\n * This is only relevant when generating in pretty mode. Default: False\n * maxTextWidth: The max number of characters in a segment before creating new lines in pretty mode.\n * Default: 80\n * comments: Whether to preserve comments in the output SQL code. Default: True\n */\nexport class Generator {\n // Static feature flags\n\n @cache\n @cache\n static get NULL_ORDERING_SUPPORTED (): NullOrderingSupported {\n return NullOrderingSupported.SUPPORTED;\n }\n\n // Whether ignore nulls is inside the agg or outside.\n // FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER\n static IGNORE_NULLS_IN_FUNC = false;\n static RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: (typeof Expression)[] = [];\n\n // Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported\n static LOCKING_READS_SUPPORTED = false;\n\n // Whether the EXCEPT and INTERSECT operations can return duplicates\n static EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = true;\n\n // Wrap derived values in parens, usually standard but spark doesn't support it\n static WRAP_DERIVED_VALUES = true;\n\n // Whether create function uses an AS before the RETURN\n static CREATE_FUNCTION_RETURN_AS = true;\n\n // Whether MERGE ... WHEN MATCHED BY SOURCE is allowed\n static MATCHED_BY_SOURCE = true;\n\n // Whether the INTERVAL expression works only with values like '1 day'\n static SINGLE_STRING_INTERVAL = false;\n\n // Whether the plural form of date parts like day (i.e. \"days\") is supported in INTERVALs\n static INTERVAL_ALLOWS_PLURAL_FORM = true;\n\n // Whether limit and fetch are supported (possible values: \"ALL\", \"LIMIT\", \"FETCH\")\n static LIMIT_FETCH = 'ALL';\n\n // Whether limit and fetch allows expresions or just limits\n static LIMIT_ONLY_LITERALS = false;\n\n // Whether a table is allowed to be renamed with a db\n static RENAME_TABLE_WITH_DB = true;\n\n // The separator for grouping sets and rollups\n static GROUPINGS_SEP = ',';\n\n // The string used for creating an index on a table\n static INDEX_ON = 'ON';\n\n // Separator for IN/OUT parameter mode (Oracle uses \" \" for \"IN OUT\", PostgreSQL uses \"\" for \"INOUT\")\n static INOUT_SEPARATOR = ' ';\n\n // Whether join hints should be generated\n static JOIN_HINTS = true;\n\n // Whether directed joins are supported\n static DIRECTED_JOINS = false;\n\n // Whether table hints should be generated\n static TABLE_HINTS = true;\n\n // Whether query hints should be generated\n static QUERY_HINTS = true;\n\n // What kind of separator to use for query hints\n static QUERY_HINT_SEP = ', ';\n\n // Whether comparing against booleans (e.g. x IS TRUE) is supported\n static IS_BOOL_ALLOWED = true;\n\n // Whether to include the \"SET\" keyword in the \"INSERT ... ON DUPLICATE KEY UPDATE\" statement\n static DUPLICATE_KEY_UPDATE_WITH_SET = true;\n\n // Whether to generate the limit as TOP <value> instead of LIMIT <value>\n static LIMIT_IS_TOP = false;\n\n // Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...\n static RETURNING_END = true;\n\n // Whether to generate an unquoted value for EXTRACT's date part argument\n static EXTRACT_ALLOWS_QUOTES = true;\n\n // Whether TIMETZ / TIMESTAMPTZ will be generated using the \"WITH TIME ZONE\" syntax\n static TZ_TO_WITH_TIME_ZONE = false;\n\n // Whether the NVL2 function is supported\n static NVL2_SUPPORTED = true;\n\n // https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax\n static SELECT_KINDS: string[] = ['STRUCT', 'VALUE'];\n\n // Whether VALUES statements can be used as derived tables.\n // MySQL 5 and Redshift do not allow this, so when False, it will convert\n // SELECT * VALUES into SELECT UNION\n static VALUES_AS_TABLE = true;\n\n // Whether the word COLUMN is included when adding a column with ALTER TABLE\n static ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = true;\n\n // UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)\n static UNNEST_WITH_ORDINALITY = true;\n\n // Whether FILTER (WHERE cond) can be used for conditional aggregation\n static AGGREGATE_FILTER_SUPPORTED = true;\n\n // Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds\n static SEMI_ANTI_JOIN_WITH_SIDE = true;\n\n // Whether to include the type of a computed column in the CREATE DDL\n static COMPUTED_COLUMN_WITH_TYPE = true;\n\n // Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY\n static SUPPORTS_TABLE_COPY = true;\n\n // Whether parentheses are required around the table sample's expression\n static TABLESAMPLE_REQUIRES_PARENS = true;\n\n // Whether a table sample clause's size needs to be followed by the ROWS keyword\n static TABLESAMPLE_SIZE_IS_ROWS = true;\n static TABLESAMPLE_SIZE_IS_PERCENT = false;\n\n // The keyword(s) to use when generating a sample clause\n static TABLESAMPLE_KEYWORDS = 'TABLESAMPLE';\n\n // Whether the TABLESAMPLE clause supports a method name, like BERNOULLI\n static TABLESAMPLE_WITH_METHOD = true;\n\n // The keyword to use when specifying the seed of a sample clause\n static TABLESAMPLE_SEED_KEYWORD = 'SEED';\n\n // Whether COLLATE is a function instead of a binary operator\n static COLLATE_IS_FUNC = false;\n\n // Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)\n static DATA_TYPE_SPECIFIERS_ALLOWED = false;\n\n // Whether conditions require booleans WHERE x = 0 vs WHERE x\n static ENSURE_BOOLS = false;\n\n // Whether the \"RECURSIVE\" keyword is required when defining recursive CTEs\n static CTE_RECURSIVE_KEYWORD_REQUIRED = true;\n\n // Whether CONCAT requires >1 arguments\n static SUPPORTS_SINGLE_ARG_CONCAT = true;\n\n // Whether LAST_DAY function supports a date part argument\n static LAST_DAY_SUPPORTS_DATE_PART = true;\n\n // Whether named columns are allowed in table aliases\n static SUPPORTS_TABLE_ALIAS_COLUMNS = true;\n\n // Whether UNPIVOT aliases are Identifiers (False means they're Literals)\n static UNPIVOT_ALIASES_ARE_IDENTIFIERS = true;\n\n // What delimiter to use for separating JSON key/value pairs\n static JSON_KEY_VALUE_PAIR_SEP = ':';\n\n // INSERT OVERWRITE TABLE x override\n static INSERT_OVERWRITE = ' OVERWRITE TABLE';\n\n // Whether the SELECT .. INTO syntax is used instead of CTAS\n static SUPPORTS_SELECT_INTO = false;\n\n // Whether UNLOGGED tables can be created\n static SUPPORTS_UNLOGGED_TABLES = false;\n\n // Whether the CREATE TABLE LIKE statement is supported\n static SUPPORTS_CREATE_TABLE_LIKE = true;\n\n // Whether the LikeProperty needs to be specified inside of the schema clause\n static LIKE_PROPERTY_INSIDE_SCHEMA = false;\n\n // Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be\n // transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args\n static MULTI_ARG_DISTINCT = true;\n\n // Whether the JSON extraction operators expect a value of type JSON\n static JSON_TYPE_REQUIRED_FOR_EXTRACTION = false;\n\n // Whether bracketed keys like [\"foo\"] are supported in JSON paths\n static JSON_PATH_BRACKETED_KEY_SUPPORTED = true;\n\n // Whether to escape keys using single quotes in JSON paths\n static JSON_PATH_SINGLE_QUOTE_ESCAPE = false;\n\n // The JsonPathPart expressions supported by this dialect\n @cache\n static get SUPPORTED_JSON_PATH_PARTS (): Set<typeof Expression> {\n return new Set([...ALL_JSON_PATH_PARTS]);\n }\n\n // Whether any(f(x) for x in array) can be implemented by this dialect\n static CAN_IMPLEMENT_ARRAY_ANY = false;\n\n // Whether the function TO_NUMBER is supported\n static SUPPORTS_TO_NUMBER = true;\n\n // Whether EXCLUDE in window specification is supported\n static SUPPORTS_WINDOW_EXCLUDE = false;\n\n // Whether or not set op modifiers apply to the outer set op or select.\n // SELECT * FROM x UNION SELECT * FROM y LIMIT 1\n // True means limit 1 happens after the set op, False means it it happens on y.\n static SET_OP_MODIFIERS = true;\n\n // Whether parameters from COPY statement are wrapped in parentheses\n static COPY_PARAMS_ARE_WRAPPED = true;\n\n // Whether values of params are set with \"=\" token or empty space\n static COPY_PARAMS_EQ_REQUIRED = false;\n\n // Whether COPY statement has INTO keyword\n static COPY_HAS_INTO_KEYWORD = true;\n\n // Whether the conditional TRY(expression) function is supported\n static TRY_SUPPORTED = true;\n\n // Whether the UESCAPE syntax in unicode strings is supported\n static SUPPORTS_UESCAPE = true;\n\n // Function used to replace escaped unicode codes in unicode strings\n static UNICODE_SUBSTITUTE?: string | ((substring: string, ...args: string[]) => string);\n\n // The keyword to use when generating a star projection with excluded columns\n static STAR_EXCEPT = 'EXCEPT';\n\n // The HEX function name\n static HEX_FUNC = 'HEX';\n\n // The keywords to use when prefixing & separating WITH based properties\n static WITH_PROPERTIES_PREFIX = 'WITH';\n\n // Whether to quote the generated expression of exp.JsonPath\n static QUOTE_JSON_PATH = true;\n\n // Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)\n static PAD_FILL_PATTERN_IS_REQUIRED = false;\n\n // Whether a projection can explode into multiple rows, e.g. by unnesting an array.\n static SUPPORTS_EXPLODING_PROJECTIONS = true;\n\n // Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version\n static ARRAY_CONCAT_IS_VAR_LEN = true;\n\n // Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone\n static SUPPORTS_CONVERT_TIMEZONE = false;\n\n // Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)\n static SUPPORTS_MEDIAN = true;\n\n // Whether UNIX_SECONDS(timestamp) is supported\n static SUPPORTS_UNIX_SECONDS = false;\n\n // Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)\n static ALTER_SET_WRAPPED = false;\n\n // Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation\n // For instance, to extract the day of week in ISO semantics, one can use IsoDOW, DAYOFWEEKISO etc depending on the dialect.\n static NORMALIZE_EXTRACT_DATE_PARTS = false;\n\n // The name to generate for the JsonPath expression. If `None`, only `this` will be generated\n static PARSE_JSON_NAME?: string | undefined = 'PARSE_JSON';\n\n // The function name of the exp.ArraySize expression\n static ARRAY_SIZE_NAME = 'ARRAY_LENGTH';\n\n // The syntax to use when altering the type of a column\n static ALTER_SET_TYPE = 'SET DATA TYPE';\n\n // Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)\n // None -> Doesn't support it at all\n // False (DuckDB) -> Has backwards-compatible support, but preferably generated without\n // True (Postgres) -> Explicitly requires it\n static ARRAY_SIZE_DIM_REQUIRED?: boolean;\n\n // Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated\n static SUPPORTS_DECODE_CASE = true;\n static ON_CONDITION_EMPTY_BEFORE_ERROR = false;\n\n // Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression\n static SUPPORTS_BETWEEN_FLAGS = false;\n\n // Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME\n static SUPPORTS_LIKE_QUANTIFIERS = true;\n\n // Prefix which is appended to exp.Table expressions in MATCH AGAINST\n static MATCH_AGAINST_TABLE_PREFIX?: string;\n\n // Whether to include the VARIABLE keyword for SET assignments\n static SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = false;\n\n // Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:\n // Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2\n // Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b\n static UPDATE_STATEMENT_SUPPORTS_FROM = true;\n\n // Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.\n static STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = true;\n\n static AFTER_HAVING_MODIFIER_TRANSFORMS: Map<string, (this: Generator, e: Expression) => string> = new Map([\n [\n 'cluster',\n function (this: Generator, e) {\n return this.sql(e, 'cluster');\n },\n ],\n [\n 'distribute',\n function (this: Generator, e) {\n return this.sql(e, 'distribute');\n },\n ],\n [\n 'sort',\n function (this: Generator, e) {\n return this.sql(e, 'sort');\n },\n ],\n [\n 'windows',\n function (this: Generator, e) {\n return e.getArgKey('windows')\n ? this.seg('WINDOW ') + this.expressions(e, {\n key: 'windows',\n flat: true,\n })\n : '';\n },\n ],\n [\n 'qualify',\n function (this: Generator, e) {\n return this.sql(e, 'qualify');\n },\n ],\n ]);\n\n static SAFE_JSON_PATH_KEY_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;\n\n static SENTINEL_LINE_BREAK = '__SQLGLOT__LB__';\n\n static TIME_PART_SINGULARS: Record<string, string> = {\n MICROSECONDS: 'MICROSECOND',\n SECONDS: 'SECOND',\n MINUTES: 'MINUTE',\n HOURS: 'HOUR',\n DAYS: 'DAY',\n WEEKS: 'WEEK',\n MONTHS: 'MONTH',\n QUARTERS: 'QUARTER',\n YEARS: 'YEAR',\n };\n\n @cache\n static get UNWRAPPED_INTERVAL_VALUES (): Set<typeof Expression> {\n return new Set<typeof Expression>([\n ColumnExpr,\n LiteralExpr,\n NegExpr,\n ParenExpr,\n ]);\n }\n\n @cache\n static get WITH_SEPARATED_COMMENTS (): Set<typeof Expression> {\n return new Set<typeof Expression>([\n CommandExpr,\n CreateExpr,\n DescribeExpr,\n DeleteExpr,\n DropExpr,\n FromExpr,\n InsertExpr,\n JoinExpr,\n MultitableInsertsExpr,\n OrderExpr,\n GroupExpr,\n HavingExpr,\n SelectExpr,\n SetOperationExpr,\n UpdateExpr,\n WhereExpr,\n WithExpr,\n ]);\n }\n\n @cache\n static get EXCLUDE_COMMENTS (): Set<typeof Expression> {\n return new Set<typeof Expression>([BinaryExpr, SetOperationExpr]);\n }\n\n @cache\n static get TYPE_MAPPING (): Map<DataTypeExprKind | string, string> {\n return new Map<DataTypeExprKind | string, string>([\n [DataTypeExprKind.DATETIME2, 'TIMESTAMP'],\n [DataTypeExprKind.NCHAR, 'CHAR'],\n [DataTypeExprKind.NVARCHAR, 'VARCHAR'],\n [DataTypeExprKind.MEDIUMTEXT, 'TEXT'],\n [DataTypeExprKind.LONGTEXT, 'TEXT'],\n [DataTypeExprKind.TINYTEXT, 'TEXT'],\n [DataTypeExprKind.BLOB, 'VARBINARY'],\n [DataTypeExprKind.MEDIUMBLOB, 'BLOB'],\n [DataTypeExprKind.LONGBLOB, 'BLOB'],\n [DataTypeExprKind.TINYBLOB, 'BLOB'],\n [DataTypeExprKind.INET, 'INET'],\n [DataTypeExprKind.ROWVERSION, 'VARBINARY'],\n [DataTypeExprKind.SMALLDATETIME, 'TIMESTAMP'],\n ]);\n }\n\n @cache\n static get UNSUPPORTED_TYPES (): Set<DataTypeExprKind | string> {\n return new Set<DataTypeExprKind>();\n }\n\n static STRUCT_DELIMITER = ['<', '>'];\n @cache\n static get PARAMETERIZABLE_TEXT_TYPES (): Set<DataTypeExprKind> {\n return new Set<DataTypeExprKind>([\n DataTypeExprKind.NVARCHAR,\n DataTypeExprKind.VARCHAR,\n DataTypeExprKind.CHAR,\n DataTypeExprKind.NCHAR,\n ]);\n }\n\n static PARAMETER_TOKEN = '@';\n static NAMED_PLACEHOLDER_TOKEN = ':';\n\n static RESERVED_KEYWORDS = new Set<string>();\n @cache\n static get TOKEN_MAPPING (): Partial<Record<TokenType, string>> {\n return {};\n }\n\n // Expressions that need to have all CTEs under them bubbled up to them\n static EXPRESSIONS_WITHOUT_NESTED_CTES: Set<typeof Expression> = new Set();\n\n // Creatables where the expression (e.g. AS SELECT ...) precedes the schema-level properties\n static EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: Set<string> = new Set();\n\n @cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static get ORIGINAL_TRANSFORMS (): Map<typeof Expression, (this: Generator, e: any) => string> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new Map<typeof Expression, (this: Generator, e: any) => string>(\n [\n [\n AdjacentExpr,\n function (this: Generator, e: BinaryExpr) {\n return this.binary(e, '-|-');\n },\n ],\n [\n AllowedValuesPropertyExpr,\n function (this: Generator, e) {\n return `ALLOWED_VALUES ${this.expressions(e, { flat: true })}`;\n },\n ],\n [\n AnalyzeColumnsExpr,\n function (this: Generator, e) {\n return this.sql(e, 'this');\n },\n ],\n [\n AnalyzeWithExpr,\n function (this: Generator, e) {\n return this.expressions(e, {\n prefix: 'WITH ',\n sep: ' ',\n });\n },\n ],\n [\n ArrayContainsAllExpr,\n function (this: Generator, e) {\n return this.binary(e, '@>');\n },\n ],\n [\n ArrayOverlapsExpr,\n function (this: Generator, e) {\n return this.binary(e, '&&');\n },\n ],\n [\n AutoRefreshPropertyExpr,\n function (this: Generator, e) {\n return `AUTO REFRESH ${this.sql(e, 'this')}`;\n },\n ],\n [\n BackupPropertyExpr,\n function (this: Generator, e) {\n return `BACKUP ${this.sql(e, 'this')}`;\n },\n ],\n [CaseSpecificColumnConstraintExpr, (e: Expression) => `${e.getArgKey('not') ? 'NOT ' : ''}CASESPECIFIC`],\n [\n CeilExpr,\n function (this: Generator, e) {\n return this.ceilFloor(e);\n },\n ],\n [\n CharacterSetColumnConstraintExpr,\n function (this: Generator, e) {\n return `CHARACTER SET ${this.sql(e, 'this')}`;\n },\n ],\n [\n CharacterSetPropertyExpr,\n function (this: Generator, e: Expression) {\n return `${e.getArgKey('default') ? 'DEFAULT ' : ''}CHARACTER SET=${this.sql(e, 'this')}`;\n },\n ],\n [\n ClusteredColumnConstraintExpr,\n function (this: Generator, e) {\n return `CLUSTERED (${this.expressions(e, {\n key: 'this',\n indent: false,\n })})`;\n },\n ],\n [\n CollateColumnConstraintExpr,\n function (this: Generator, e) {\n return `COLLATE ${this.sql(e, 'this')}`;\n },\n ],\n [\n CommentColumnConstraintExpr,\n function (this: Generator, e) {\n return `COMMENT ${this.sql(e, 'this')}`;\n },\n ],\n [\n ConnectByRootExpr,\n function (this: Generator, e) {\n return `CONNECT_BY_ROOT ${this.sql(e, 'this')}`;\n },\n ],\n [\n ConvertToCharsetExpr,\n function (this: Generator, e: ConvertToCharsetExpr) {\n return this.func('CONVERT', [\n e.args.this,\n e.args.dest,\n e.args.source,\n ]);\n },\n ],\n [CopyGrantsPropertyExpr, () => 'COPY GRANTS'],\n [\n CredentialsPropertyExpr,\n function (this: Generator, e) {\n return `CREDENTIALS=(${this.expressions(e, {\n key: 'expressions',\n sep: ' ',\n })})`;\n },\n ],\n [CurrentCatalogExpr, () => 'CURRENT_CATALOG'],\n [SessionUserExpr, () => 'SESSION_USER'],\n [\n DateFormatColumnConstraintExpr,\n function (this: Generator, e) {\n return `FORMAT ${this.sql(e, 'this')}`;\n },\n ],\n [\n DefaultColumnConstraintExpr,\n function (this: Generator, e) {\n return `DEFAULT ${this.sql(e, 'this')}`;\n },\n ],\n [DynamicPropertyExpr, () => 'DYNAMIC'],\n [EmptyPropertyExpr, () => 'EMPTY'],\n [\n EncodeColumnConstraintExpr,\n function (this: Generator, e) {\n return `ENCODE ${this.sql(e, 'this')}`;\n },\n ],\n [\n EnviromentPropertyExpr,\n function (this: Generator, e) {\n return `ENVIRONMENT (${this.expressions(e, { flat: true })})`;\n },\n ],\n [\n EphemeralColumnConstraintExpr,\n function (this: Generator, e: Expression) {\n return `EPHEMERAL${e.args.this ? ' ' + this.sql(e, 'this') : ''}`;\n },\n ],\n [\n ExcludeColumnConstraintExpr,\n function (this: Generator, e) {\n return `EXCLUDE ${this.sql(e, 'this').trimStart()}`;\n },\n ],\n [\n ExecuteAsPropertyExpr,\n function (this: Generator, e) {\n return this.nakedProperty(e);\n },\n ],\n [\n ExceptExpr,\n function (this: Generator, e) {\n return this.setOperations(e);\n },\n ],\n [ExternalPropertyExpr, () => 'EXTERNAL'],\n [\n FloorExpr,\n function (this: Generator, e) {\n return this.ceilFloor(e);\n },\n ],\n [\n GetExpr,\n function (this: Generator, e) {\n return this.getPutSql(e);\n },\n ],\n [GlobalPropertyExpr, () => 'GLOBAL'],\n [HeapPropertyExpr, () => 'HEAP'],\n [IcebergPropertyExpr, () => 'ICEBERG'],\n [\n InheritsPropertyExpr,\n function (this: Generator, e) {\n return `INHERITS (${this.expressions(e, { flat: true })})`;\n },\n ],\n [\n InlineLengthColumnConstraintExpr,\n function (this: Generator, e) {\n return `INLINE LENGTH ${this.sql(e, 'this')}`;\n },\n ],\n [\n InputModelPropertyExpr,\n function (this: Generator, e) {\n return `INPUT${this.sql(e, 'this')}`;\n },\n ],\n [\n IntersectExpr,\n function (this: Generator, e) {\n return this.setOperations(e);\n },\n ],\n [\n IntervalSpanExpr,\n function (this: Generator, e) {\n return `${this.sql(e, 'this')} TO ${this.sql(e, 'expression')}`;\n },\n ],\n [\n Int64Expr,\n function (this: Generator, e: Int64Expr) {\n return this.sql(cast(e.args.this || '', DataTypeExprKind.BIGINT));\n },\n ],\n [\n JsonbContainsAnyTopKeysExpr,\n function (this: Generator, e) {\n return this.binary(e, '?|');\n },\n ],\n [\n JsonbContainsAllTopKeysExpr,\n function (this: Generator, e) {\n return this.binary(e, '?&');\n },\n ],\n [\n JsonbDeleteAtPathExpr,\n function (this: Generator, e) {\n return this.binary(e, '#-');\n },\n ],\n [\n JsonPathFilterExpr,\n function (this: Generator, e: JsonPathFilterExpr) {\n return `?${e.args.this}`;\n },\n ],\n [\n JsonPathRecursiveExpr,\n function (this: Generator, e: JsonPathRecursiveExpr) {\n return `..${e.args.this || ''}`;\n },\n ],\n [JsonPathRootExpr, () => '$'],\n [\n JsonPathScriptExpr,\n function (this: Generator, e: JsonPathScriptExpr) {\n return `(${e.args.this}`;\n },\n ],\n [\n JsonPathSelectorExpr,\n function (this: Generator, e: JsonPathSelectorExpr) {\n return `[${this.jsonPathPart(e.args.this)}]`;\n },\n ],\n [\n JsonPathSliceExpr,\n function (this: Generator, e: JsonPathSliceExpr) {\n return [\n e.args.start,\n e.args.end,\n e.args.step,\n ]\n .filter((p) => p !== undefined)\n .map((p) => (p === false ? '' : this.jsonPathPart(p as ExpressionValue<JsonPathPartExpr>)))\n .join(':');\n },\n ],\n [\n JsonPathUnionExpr,\n function (this: Generator, e: JsonPathUnionExpr) {\n const parts = (e.args.expressions ?? []).map((p) => this.jsonPathPart(p as ExpressionValue<JsonPathPartExpr>));\n return `[${parts.join(',')}]`;\n },\n ],\n [JsonPathWildcardExpr, () => '*'],\n [\n LanguagePropertyExpr,\n function (this: Generator, e) {\n return this.nakedProperty(e);\n },\n ],\n [\n LocationPropertyExpr,\n function (this: Generator, e) {\n return this.nakedProperty(e);\n },\n ],\n [LogPropertyExpr, (e: Expression) => `${e.getArgKey('no') ? 'NO ' : ''}LOG`],\n [MaterializedPropertyExpr, () => 'MATERIALIZED'],\n [\n NetFuncExpr,\n function (this: Generator, e) {\n return `NET.${this.sql(e, 'this')}`;\n },\n ],\n [\n NonClusteredColumnConstraintExpr,\n function (this: Generator, e) {\n return `NONCLUSTERED (${this.expressions(e, {\n key: 'this',\n indent: false,\n })})`;\n },\n ],\n [NoPrimaryIndexPropertyExpr, () => 'NO PRIMARY INDEX'],\n [NotForReplicationColumnConstraintExpr, () => 'NOT FOR REPLICATION'],\n [OnCommitPropertyExpr, (e: Expression) => `ON COMMIT ${e.getArgKey('delete') ? 'DELETE' : 'PRESERVE'} ROWS`],\n [\n OnPropertyExpr,\n function (this: Generator, e) {\n return `ON ${this.sql(e, 'this')}`;\n },\n ],\n [\n OnUpdateColumnConstraintExpr,\n function (this: Generator, e) {\n return `ON UPDATE ${this.sql(e, 'this')}`;\n },\n ],\n [\n OperatorExpr,\n function (this: Generator, e) {\n return this.binary(e, '');\n },\n ],\n [\n OutputModelPropertyExpr,\n function (this: Generator, e) {\n return `OUTPUT${this.sql(e, 'this')}`;\n },\n ],\n [\n ExtendsLeftExpr,\n function (this: Generator, e) {\n return this.binary(e, '&<');\n },\n ],\n [\n ExtendsRightExpr,\n function (this: Generator, e) {\n return this.binary(e, '&>');\n },\n ],\n [\n PathColumnConstraintExpr,\n function (this: Generator, e) {\n return `PATH ${this.sql(e, 'this')}`;\n },\n ],\n [\n PartitionedByBucketExpr,\n function (this: Generator, e: PartitionedByBucketExpr) {\n return this.func('BUCKET', [e.args.this, e.args.expression]);\n },\n ],\n [\n PartitionByTruncateExpr,\n function (this: Generator, e: PartitionByTruncateExpr) {\n return this.func('TRUNCATE', [e.args.this, e.args.expression]);\n },\n ],\n [\n PivotAnyExpr,\n function (this: Generator, e) {\n return `ANY${this.sql(e, 'this')}`;\n },\n ],\n [\n PositionalColumnExpr,\n function (this: Generator, e) {\n return `#${this.sql(e, 'this')}`;\n },\n ],\n [\n ProjectionPolicyColumnConstraintExpr,\n function (this: Generator, e) {\n return `PROJECTION POLICY ${this.sql(e, 'this')}`;\n },\n ],\n [ZeroFillColumnConstraintExpr, () => 'ZEROFILL'],\n [\n PutExpr,\n function (this: Generator, e) {\n return this.getPutSql(e);\n },\n ],\n [\n RemoteWithConnectionModelPropertyExpr,\n function (this: Generator, e) {\n return `REMOTE WITH CONNECTION ${this.sql(e, 'this')}`;\n },\n ],\n [\n ReturnsPropertyExpr,\n function (this: Generator, e: ReturnsPropertyExpr) {\n return e.getArgKey('null') ? 'RETURNS NULL ON NULL INPUT' : this.nakedProperty(e);\n },\n ],\n [\n SafeFuncExpr,\n function (this: Generator, e) {\n return `SAFE.${this.sql(e, 'this')}`;\n },\n ],\n [\n SamplePropertyExpr,\n function (this: Generator, e) {\n return `SAMPLE BY ${this.sql(e, 'this')}`;\n },\n ],\n [SecurePropertyExpr, () => 'SECURE'],\n [\n SecurityPropertyExpr,\n function (this: Generator, e) {\n return `SECURITY ${this.sql(e, 'this')}`;\n },\n ],\n [\n SetConfigPropertyExpr,\n function (this: Generator, e) {\n return this.sql(e, 'this');\n },\n ],\n [SetPropertyExpr, (e: Expression) => `${e.getArgKey('multi') ? 'MULTI' : ''}SET`],\n [\n SettingsPropertyExpr,\n function (this: Generator, e) {\n return `SETTINGS${this.seg('')}${this.expressions(e)}`;\n },\n ],\n [\n SharingPropertyExpr,\n function (this: Generator, e) {\n return `SHARING=${this.sql(e, 'this')}`;\n },\n ],\n [SqlReadWritePropertyExpr, (e: Expression) => e.name],\n [\n SqlSecurityPropertyExpr,\n function (this: Generator, e) {\n return `SQL SECURITY ${this.sql(e, 'this')}`;\n },\n ],\n [StabilityPropertyExpr, (e: Expression) => e.name],\n [\n StreamExpr,\n function (this: Generator, e) {\n return `STREAM ${this.sql(e, 'this')}`;\n },\n ],\n [StreamingTablePropertyExpr, () => 'STREAMING'],\n [StrictPropertyExpr, () => 'STRICT'],\n [\n SwapTableExpr,\n function (this: Generator, e) {\n return `SWAP WITH ${this.sql(e, 'this')}`;\n },\n ],\n [\n TableColumnExpr,\n function (this: Generator, e: TableColumnExpr) {\n return this.sql(e.args.this);\n },\n ],\n [\n TagsExpr,\n function (this: Generator, e) {\n return `TAG (${this.expressions(e, { flat: true })})`;\n },\n ],\n [TemporaryPropertyExpr, () => 'TEMPORARY'],\n [\n TitleColumnConstraintExpr,\n function (this: Generator, e) {\n return `TITLE ${this.sql(e, 'this')}`;\n },\n ],\n [\n ToMapExpr,\n function (this: Generator, e) {\n return `MAP ${this.sql(e, 'this')}`;\n },\n ],\n [\n ToTablePropertyExpr,\n function (this: Generator, e: Expression) {\n return `TO ${this.sql(e.args.this as string | Expression)}`;\n },\n ],\n [\n TransformModelPropertyExpr,\n function (this: Generator, e: TransformModelPropertyExpr) {\n return this.func('TRANSFORM', e.args.expressions || []);\n },\n ],\n [TransientPropertyExpr, () => 'TRANSIENT'],\n [\n UnionExpr,\n function (this: Generator, e) {\n return this.setOperations(e);\n },\n ],\n [UnloggedPropertyExpr, () => 'UNLOGGED'],\n [\n UsingTemplatePropertyExpr,\n function (this: Generator, e) {\n return `USING TEMPLATE ${this.sql(e, 'this')}`;\n },\n ],\n [\n UsingDataExpr,\n function (this: Generator, e) {\n return `USING DATA ${this.sql(e, 'this')}`;\n },\n ],\n [UppercaseColumnConstraintExpr, () => 'UPPERCASE'],\n [\n UtcDateExpr,\n function (this: Generator, _e) {\n return this.sql(new CurrentDateExpr({ this: LiteralExpr.string('UTC') }));\n },\n ],\n [\n UtcTimeExpr,\n function (this: Generator, _e) {\n return this.sql(new CurrentTimeExpr({ this: LiteralExpr.string('UTC') }));\n },\n ],\n [\n UtcTimestampExpr,\n function (this: Generator, _e) {\n return this.sql(new CurrentTimestampExpr({ this: LiteralExpr.string('UTC') }));\n },\n ],\n [\n VariadicExpr,\n function (this: Generator, e) {\n return `VARIADIC ${this.sql(e, 'this')}`;\n },\n ],\n [\n VarMapExpr,\n function (this: Generator, e: VarMapExpr) {\n return this.func('MAP', [e.args.keys, e.args.values]);\n },\n ],\n [\n ViewAttributePropertyExpr,\n function (this: Generator, e) {\n return `WITH ${this.sql(e, 'this')}`;\n },\n ],\n [VolatilePropertyExpr, () => 'VOLATILE'],\n [\n WithJournalTablePropertyExpr,\n function (this: Generator, e) {\n return `WITH JOURNAL TABLE=${this.sql(e, 'this')}`;\n },\n ],\n [\n WithProcedureOptionsExpr,\n function (this: Generator, e) {\n return `WITH ${this.expressions(e, { flat: true })}`;\n },\n ],\n [\n WithSchemaBindingPropertyExpr,\n function (this: Generator, e) {\n return `WITH SCHEMA ${this.sql(e, 'this')}`;\n },\n ],\n [\n WithOperatorExpr,\n function (this: Generator, e) {\n return `${this.sql(e, 'this')} WITH ${this.sql(e, 'op')}`;\n },\n ],\n [ForcePropertyExpr, () => 'FORCE'],\n ],\n );\n }\n\n /**\n * @final Do not override this getter in subclasses; override `ORIGINAL_TRANSFORMS` instead.\n */\n @cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static get TRANSFORMS (): Map<typeof Expression, (this: Generator, e: any) => string> {\n const transforms = new Map(this.ORIGINAL_TRANSFORMS);\n for (const part of Array.from(ALL_JSON_PATH_PARTS).filter((cls) => !this.SUPPORTED_JSON_PATH_PARTS.has(cls))) {\n transforms.delete(part);\n }\n return transforms;\n }\n\n @cache\n static get PROPERTIES_LOCATION (): Map<typeof Expression, PropertiesLocation> {\n return new Map<typeof Expression, PropertiesLocation>([\n [AllowedValuesPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [AlgorithmPropertyExpr, PropertiesLocation.POST_CREATE],\n [AutoIncrementPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [AutoRefreshPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [BackupPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [BlockCompressionPropertyExpr, PropertiesLocation.POST_NAME],\n [CharacterSetPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ChecksumPropertyExpr, PropertiesLocation.POST_NAME],\n [CollatePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [CopyGrantsPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ClusterExpr, PropertiesLocation.POST_SCHEMA],\n [ClusteredByPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DistributedByPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DuplicateKeyPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DataBlocksizePropertyExpr, PropertiesLocation.POST_NAME],\n [DataDeletionPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DefinerPropertyExpr, PropertiesLocation.POST_CREATE],\n [DictRangeExpr, PropertiesLocation.POST_SCHEMA],\n [DictPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DynamicPropertyExpr, PropertiesLocation.POST_CREATE],\n [DistKeyPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [DistStylePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [EmptyPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [EncodePropertyExpr, PropertiesLocation.POST_EXPRESSION],\n [EnginePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [EnviromentPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ExecuteAsPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ExternalPropertyExpr, PropertiesLocation.POST_CREATE],\n [FallbackPropertyExpr, PropertiesLocation.POST_NAME],\n [FileFormatPropertyExpr, PropertiesLocation.POST_WITH],\n [FreespacePropertyExpr, PropertiesLocation.POST_NAME],\n [GlobalPropertyExpr, PropertiesLocation.POST_CREATE],\n [HeapPropertyExpr, PropertiesLocation.POST_WITH],\n [InheritsPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [IcebergPropertyExpr, PropertiesLocation.POST_CREATE],\n [IncludePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [InputModelPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [IsolatedLoadingPropertyExpr, PropertiesLocation.POST_NAME],\n [JournalPropertyExpr, PropertiesLocation.POST_NAME],\n [LanguagePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [LikePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [LocationPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [LockPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [LockingPropertyExpr, PropertiesLocation.POST_ALIAS],\n [LogPropertyExpr, PropertiesLocation.POST_NAME],\n [MaterializedPropertyExpr, PropertiesLocation.POST_CREATE],\n [MergeBlockRatioPropertyExpr, PropertiesLocation.POST_NAME],\n [NoPrimaryIndexPropertyExpr, PropertiesLocation.POST_EXPRESSION],\n [OnPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [OnCommitPropertyExpr, PropertiesLocation.POST_EXPRESSION],\n [OrderExpr, PropertiesLocation.POST_SCHEMA],\n [OutputModelPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [PartitionedByPropertyExpr, PropertiesLocation.POST_WITH],\n [PartitionedOfPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [PrimaryKeyExpr, PropertiesLocation.POST_SCHEMA],\n [PropertyExpr, PropertiesLocation.POST_WITH],\n [RefreshTriggerPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [RemoteWithConnectionModelPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ReturnsPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [RollupPropertyExpr, PropertiesLocation.UNSUPPORTED],\n [RowFormatPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [RowFormatDelimitedPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [RowFormatSerdePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SamplePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SchemaCommentPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SecurePropertyExpr, PropertiesLocation.POST_CREATE],\n [SecurityPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SerdePropertiesExpr, PropertiesLocation.POST_SCHEMA],\n [SetExpr, PropertiesLocation.POST_SCHEMA],\n [SettingsPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SetPropertyExpr, PropertiesLocation.POST_CREATE],\n [SetConfigPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SharingPropertyExpr, PropertiesLocation.POST_EXPRESSION],\n [SequencePropertiesExpr, PropertiesLocation.POST_EXPRESSION],\n [SortKeyPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SqlReadWritePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [SqlSecurityPropertyExpr, PropertiesLocation.POST_CREATE],\n [StabilityPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [StorageHandlerPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [StreamingTablePropertyExpr, PropertiesLocation.POST_CREATE],\n [StrictPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [TagsExpr, PropertiesLocation.POST_WITH],\n [TemporaryPropertyExpr, PropertiesLocation.POST_CREATE],\n [ToTablePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [TransientPropertyExpr, PropertiesLocation.POST_CREATE],\n [TransformModelPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [MergeTreeTtlExpr, PropertiesLocation.POST_SCHEMA],\n [UnloggedPropertyExpr, PropertiesLocation.POST_CREATE],\n [UsingTemplatePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ViewAttributePropertyExpr, PropertiesLocation.POST_SCHEMA],\n [VolatilePropertyExpr, PropertiesLocation.POST_CREATE],\n [WithDataPropertyExpr, PropertiesLocation.POST_EXPRESSION],\n [WithJournalTablePropertyExpr, PropertiesLocation.POST_NAME],\n [WithProcedureOptionsExpr, PropertiesLocation.POST_SCHEMA],\n [WithSchemaBindingPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [WithSystemVersioningPropertyExpr, PropertiesLocation.POST_SCHEMA],\n [ForcePropertyExpr, PropertiesLocation.POST_CREATE],\n ]);\n }\n\n // Instance properties\n protected pretty: boolean;\n protected identify: string | boolean;\n protected normalize: boolean;\n protected pad: number;\n protected indentAmount: number;\n protected normalizeFunctions: NormalizeFunctions;\n protected unsupportedLevel: ErrorLevel;\n protected maxUnsupported: number;\n protected leadingComma: boolean;\n protected maxTextWidth: number;\n protected comments: boolean;\n dialect: Dialect;\n protected unsupportedMessages: string[];\n protected escapedQuoteEnd: string;\n private nextName: () => string;\n protected escapedByteQuoteEnd: string;\n protected escapedIdentifierEnd: string;\n protected identifierStart: string;\n protected identifierEnd: string;\n quoteJsonPathKeyUsingBrackets: boolean;\n\n constructor (options: GeneratorOptions = {}) {\n this.pretty = options.pretty ?? false;\n this.identify = options.identify ?? false;\n this.normalize = options.normalize ?? false;\n this.pad = options.pad ?? 2;\n this.indentAmount = options.indent ?? 2;\n this.unsupportedLevel = options.unsupportedLevel ?? ErrorLevel.WARN;\n this.maxUnsupported = options.maxUnsupported ?? 3;\n this.leadingComma = options.leadingComma ?? false;\n this.maxTextWidth = options.maxTextWidth ?? 80;\n this.comments = options.comments ?? true;\n\n // Get dialect and prioritize option over dialect default\n this.dialect = Dialect.getOrRaise(options.dialect);\n const dialectClass = this.dialect._constructor;\n this.normalizeFunctions = options.normalizeFunctions ?? dialectClass.NORMALIZE_FUNCTIONS ?? NormalizeFunctions.UPPER;\n\n // Initialize escaped delimiters\n const stringEscapes = dialectClass.tokenizerClass.STRING_ESCAPES;\n const escapeChar = stringEscapes && 0 < stringEscapes.length ? stringEscapes[0] : '';\n this.escapedQuoteEnd = escapeChar + this.dialect._constructor.QUOTE_END;\n this.escapedByteQuoteEnd = this.dialect._constructor.BYTE_END\n ? escapeChar + this.dialect._constructor.BYTE_END\n : '';\n\n // Initialize identifier delimiters\n this.escapedIdentifierEnd = this.dialect._constructor.IDENTIFIER_END + this.dialect._constructor.IDENTIFIER_END;\n this.identifierStart = this.dialect._constructor.IDENTIFIER_START;\n this.identifierEnd = this.dialect._constructor.IDENTIFIER_END;\n\n this.unsupportedMessages = [];\n this.nextName = nameSequence('_t');\n this.quoteJsonPathKeyUsingBrackets = true;\n }\n\n get _constructor (): typeof Generator {\n return this.constructor as typeof Generator;\n }\n\n /**\n * Main generate method - converts an expression tree to SQL string.\n */\n generate (expression: Expression, options: { copy?: boolean } = {}): string {\n const { copy = true } = options;\n let expr = copy\n ? expression.copy()\n : expression;\n\n expr = this.preprocess(expr);\n\n this.unsupportedMessages = [];\n\n let sql = this.sql(expr).trim();\n\n if (this.pretty) {\n sql = sql.replaceAll(this._constructor.SENTINEL_LINE_BREAK, '\\n');\n }\n\n if (this.unsupportedLevel === ErrorLevel.IGNORE) {\n return sql;\n }\n\n if (this.unsupportedLevel === ErrorLevel.WARN) {\n for (const msg of this.unsupportedMessages) {\n console.warn(msg);\n }\n } else if (this.unsupportedLevel === ErrorLevel.RAISE && 0 < this.unsupportedMessages.length) {\n throw new UnsupportedError(concatMessages(this.unsupportedMessages, this.maxUnsupported));\n }\n\n return sql;\n }\n\n protected preprocess (expression: Expression): Expression {\n expression = this.moveCtesToTopLevel(expression);\n\n if (this._constructor.ENSURE_BOOLS) {\n expression = ensureBools(expression);\n }\n\n return expression;\n }\n\n protected moveCtesToTopLevel<E extends Expression> (expression: E): E {\n if (\n !expression.parent\n && (this.constructor as typeof Generator).EXPRESSIONS_WITHOUT_NESTED_CTES.has(expression._constructor)\n && [...expression.findAll(WithExpr)].some((node) => node.parent !== expression)\n ) {\n return moveCtesToTopLevel(expression) as E;\n }\n return expression;\n }\n\n /**\n * Record an unsupported expression/feature.\n */\n unsupported (message: string): void {\n if (this.unsupportedLevel === ErrorLevel.IMMEDIATE) {\n throw new UnsupportedError(message);\n }\n this.unsupportedMessages.push(message);\n }\n\n /**\n * Generate a separator (space or newline based on pretty mode).\n */\n sep (separator = ' '): string {\n if (this.pretty) {\n return `${separator.trim()}\\n`;\n }\n return separator;\n }\n\n /**\n * Generate a segment with separator.\n */\n seg (sql: string, separator = ' '): string {\n return `${this.sep(separator)}${sql}`;\n }\n\n sanitizeComment (comment: string): string {\n let result = comment;\n\n // Add space at start if first char is not whitespace\n if (result[0] && result[0].trim() !== '') {\n result = ' ' + result;\n }\n\n // Add space at end if last char is not whitespace\n if (result[result.length - 1] && result[result.length - 1].trim() !== '') {\n result = result + ' ';\n }\n\n // If dialect doesn't support nested comments, replace */ with * /\n if (!this.dialect._constructor.tokenizerClass.NESTED_COMMENTS) {\n result = result.replace(/\\*\\//g, '* /');\n }\n\n return result;\n }\n\n wrap (expression: ExpressionOrString): string {\n let thisSql: string;\n\n // Check if the expression is one that should not be accessed via the 'this' key\n if (typeof expression !== 'string' && UNWRAPPED_QUERIES.some((cls) => expression instanceof cls)) {\n thisSql = this.sql(expression);\n } else {\n thisSql = this.sql(expression, 'this');\n }\n\n if (!thisSql) {\n return '()';\n }\n\n thisSql = this.indent(thisSql, {\n level: 1,\n pad: 0,\n });\n\n return `(${this.sep('')}${thisSql}${this.seg(')', '')}`;\n }\n\n /**\n * Temporarily disable identifier quoting, execute func, then restore.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n noIdentify<T extends (...args: any[]) => string> (\n func: T,\n ...args: Parameters<T>\n ): string {\n const original = this.identify;\n this.identify = false;\n const result = func(...args);\n this.identify = original;\n return result;\n }\n\n /**\n * Normalize a function name based on settings.\n */\n normalizeFunc (name: string): string {\n if (this.normalizeFunctions === NormalizeFunctions.UPPER) {\n return name.toUpperCase();\n }\n if (this.normalizeFunctions === NormalizeFunctions.LOWER) {\n return name.toLowerCase();\n }\n return name;\n }\n\n protected indent (\n sql: string,\n options: {\n skipFirst?: boolean;\n skipLast?: boolean;\n level?: number;\n pad?: number;\n } = {},\n ) {\n const {\n skipFirst = false, skipLast = false, pad = this.pad, level = 0,\n } = options;\n if (!this.pretty || !sql) {\n return sql;\n }\n const lines = sql.split('\\n');\n\n return lines\n .map((line, i) => {\n if ((skipFirst && i === 0) || (skipLast && i === lines.length - 1)) {\n return line;\n }\n\n const indentation = ' '.repeat(level * this.indentAmount + pad);\n return `${indentation}${line}`;\n })\n .join('\\n');\n }\n\n /**\n * Core SQL generation method with auto-discovery.\n *\n * @param expression - Expression to generate SQL for (or string/undefined)\n * @param key - Optional key to extract from expression.args\n * @param comment - Whether to include comments (default: true)\n */\n sql (\n expression?: ExpressionValue,\n key?: string,\n options: { comment?: boolean } = {},\n ): string {\n const { comment = true } = options;\n\n // Handle undefined/null early\n if (expression === undefined) {\n return '';\n }\n\n // Handle string literals\n if (typeof expression === 'number' || typeof expression === 'string' || typeof expression === 'boolean') {\n return expression.toString();\n }\n\n if (Array.isArray(expression)) {\n return expression.map((e) => this.sql(e)).join(', ');\n }\n\n // Handle key extraction\n if (key) {\n const value = expression.getArgKey(key);\n if (value) {\n if (typeof value === 'string' || value instanceof Expression) {\n return this.sql(value);\n }\n }\n return '';\n }\n // Check TRANSFORMS\n const transform = this._constructor.TRANSFORMS.get(expression._constructor);\n\n let sql = '';\n if (transform instanceof Function) {\n sql = transform.call(this, expression);\n } else if (expression instanceof Expression) {\n const expHandlerName = `${expression._constructor.key}Sql`;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handler = (this as any)[expHandlerName];\n if (handler instanceof Function) {\n sql = handler.call(this, expression);\n } else if (expression instanceof FuncExpr || ALL_FUNCTIONS.has(expression._constructor as typeof FuncExpr)) {\n sql = this.functionFallbackSql(expression as FuncExpr);\n } else if (expression instanceof PropertyExpr) {\n sql = this.propertySql(expression);\n } else {\n throw new Error(`Unsupported expression type ${expression._constructor.name}`);\n }\n } else {\n throw new Error(`Expected an Expression. Received ${typeof expression}: ${expression}`);\n }\n\n return this.comments && comment ? this.maybeComment(sql, expression) : sql;\n }\n\n /**\n * Add comment to SQL if present.\n */\n protected maybeComment (\n sql: string,\n expression?: Expression,\n comments?: string[],\n separated = false,\n ): string {\n const commentsToUse = this.comments\n ? (comments !== undefined ? comments : expression?.comments)\n : undefined;\n\n if (!commentsToUse || commentsToUse.length === 0) {\n return sql;\n }\n\n if (expression && [...this._constructor.EXCLUDE_COMMENTS].some((cls) => expression instanceof cls)) {\n return sql;\n }\n\n const commentsSql = commentsToUse\n .filter((c) => c)\n .map((c) => `/*${this.sanitizeComment(c)}*/`)\n .join(' ');\n\n if (!commentsSql) {\n return sql;\n }\n\n const commentsSqlWithLineBreaks = this.replaceLineBreaks(commentsSql);\n\n if (separated || (expression && [...this._constructor.WITH_SEPARATED_COMMENTS].some((cls) => expression instanceof cls))) {\n return (!sql || sql[0] === ' ' || sql[0] === '\\n')\n ? `${this.sep()}${commentsSqlWithLineBreaks}${sql}`\n : `${commentsSqlWithLineBreaks}${this.sep()}${sql}`;\n }\n\n return `${sql} ${commentsSqlWithLineBreaks}`;\n }\n\n /**\n * Generate SQL for UNCACHE TABLE.\n */\n uncacheSql (expression: UncacheExpr): string {\n const table = this.sql(expression, 'this');\n const existsSql = expression.args.exists ? ' IF EXISTS' : '';\n return `UNCACHE TABLE${existsSql} ${table}`;\n }\n\n /**\n * Generate SQL for CACHE TABLE.\n */\n cacheSql (expression: CacheExpr): string {\n const lazy = expression.args.lazy ? ' LAZY' : '';\n const table = this.sql(expression, 'this');\n const options = expression.args.options as Expression[] | undefined;\n const optionsSql = options?.length\n ? ` OPTIONS(${this.sql(options[0])} = ${this.sql(options[1])})`\n : '';\n let sql = this.sql(expression, 'expression');\n sql = sql ? ` AS${this.sep()}${sql}` : '';\n sql = `CACHE${lazy} TABLE ${table}${optionsSql}${sql}`;\n return this.prependCtes(expression, sql);\n }\n\n /**\n * Generate SQL for CHARACTER SET.\n */\n characterSetSql (expression: CharacterSetExpr): string {\n if (expression.parent instanceof CastExpr) {\n return `CHAR CHARACTER SET ${this.sql(expression, 'this')}`;\n }\n const defaultStr = expression.args.default ? 'DEFAULT ' : '';\n return `${defaultStr}CHARACTER SET=${this.sql(expression, 'this')}`;\n }\n\n /**\n * Generate parts of a column reference (catalog.db.table.column).\n */\n protected columnParts (expression: ColumnExpr): string {\n const parts = [\n expression.args.catalog,\n expression.args.db,\n expression.args.table,\n expression.args.this,\n ].filter(Boolean);\n\n return parts.map((part) => this.sql(part as string | Expression)).join('.');\n }\n\n columnSql (expression: ColumnExpr): string {\n const joinMark = expression.args.joinMark ? ' (+)' : '';\n\n if (joinMark && !this.dialect._constructor.SUPPORTS_COLUMN_JOIN_MARKS) {\n this.unsupported('Outer join syntax using the (+) operator is not supported.');\n return this.columnParts(expression);\n }\n\n return `${this.columnParts(expression)}${joinMark}`;\n }\n\n pseudocolumnSql (expression: PseudocolumnExpr): string {\n return this.columnSql(expression);\n }\n\n columnPositionSql (expression: ColumnPositionExpr): string {\n const thisStr = this.sql(expression, 'this');\n const thisFormatted = thisStr ? ` ${thisStr}` : '';\n const position = this.sql(expression, 'position');\n return `${position}${thisFormatted}`;\n }\n\n columnDefSql (expression: ColumnDefExpr, options: { sep?: string } = {}): string {\n const { sep = ' ' } = options;\n const column = this.sql(expression, 'this');\n let kind = this.sql(expression, 'kind');\n const constraints = this.expressions(expression, {\n key: 'constraints',\n sep: ' ',\n flat: true,\n });\n const exists = expression.args.exists ? 'IF NOT EXISTS ' : '';\n kind = kind ? `${sep}${kind}` : '';\n const constraintsPart = constraints ? ` ${constraints}` : '';\n let position = this.sql(expression, 'position');\n position = position ? ` ${position}` : '';\n\n // Check for ComputedColumnConstraint\n const hasComputedColumn = 0 < Array.from(expression.findAll(ComputedColumnConstraintExpr)).length;\n if (hasComputedColumn && !this._constructor.COMPUTED_COLUMN_WITH_TYPE) {\n kind = '';\n }\n\n return `${exists}${column}${kind}${constraintsPart}${position}`;\n }\n\n columnConstraintSql (expression: ColumnConstraintExpr): string {\n const thisStr = this.sql(expression, 'this');\n const kindSql = this.sql(expression, 'kind').trim();\n return thisStr ? `CONSTRAINT ${thisStr} ${kindSql}` : kindSql;\n }\n\n computedColumnConstraintSql (expression: ComputedColumnConstraintExpr): string {\n const thisStr = this.sql(expression, 'this');\n let persisted: string;\n if (expression.args.notNull) {\n persisted = ' PERSISTED NOT NULL';\n } else if (expression.args.persisted) {\n persisted = ' PERSISTED';\n } else {\n persisted = '';\n }\n return `AS ${thisStr}${persisted}`;\n }\n\n autoIncrementColumnConstraintSql (_expression: AutoIncrementColumnConstraintExpr): string {\n return this.tokenSql(TokenType.AUTO_INCREMENT);\n }\n\n compressColumnConstraintSql (expression: CompressColumnConstraintExpr): string {\n let thisStr: string;\n if (Array.isArray(expression.args.this)) {\n thisStr = this.wrap(this.expressions(expression, {\n key: 'this',\n flat: true,\n }));\n } else {\n thisStr = this.sql(expression, 'this');\n }\n return `COMPRESS ${thisStr}`;\n }\n\n generatedAsIdentityColumnConstraintSql (expression: GeneratedAsIdentityColumnConstraintExpr): string {\n let thisStr = '';\n if (expression.args.this !== undefined) {\n const onNull = expression.args.onNull ? ' ON NULL' : '';\n thisStr = expression.args.this ? ' ALWAYS' : ` BY DEFAULT${onNull}`;\n }\n\n const start = expression.args.start;\n const startStr = start ? `START WITH ${start}` : '';\n const increment = expression.args.increment;\n const incrementStr = increment ? ` INCREMENT BY ${increment}` : '';\n const minvalue = expression.args.minvalue;\n const minvalueStr = minvalue ? ` MINVALUE ${minvalue}` : '';\n const maxvalue = expression.args.maxvalue;\n const maxvalueStr = maxvalue ? ` MAXVALUE ${maxvalue}` : '';\n const cycle = expression.args.cycle;\n let cycleSql = '';\n\n if (cycle !== undefined) {\n cycleSql = `${!cycle ? ' NO' : ''} CYCLE`;\n cycleSql = !startStr && !incrementStr ? cycleSql.trim() : cycleSql;\n }\n\n let sequenceOpts = '';\n if (startStr || incrementStr || cycleSql) {\n sequenceOpts = `${startStr}${incrementStr}${minvalueStr}${maxvalueStr}${cycleSql}`;\n sequenceOpts = ` (${sequenceOpts.trim()})`;\n }\n\n let expr = this.sql(expression, 'expression');\n expr = expr ? `(${expr})` : 'IDENTITY';\n\n return `GENERATED${thisStr} AS ${expr}${sequenceOpts}`;\n }\n\n generatedAsRowColumnConstraintSql (expression: GeneratedAsRowColumnConstraintExpr): string {\n const start = expression.args.start ? 'START' : 'END';\n const hidden = expression.args.hidden ? ' HIDDEN' : '';\n return `GENERATED ALWAYS AS ROW ${start}${hidden}`;\n }\n\n periodForSystemTimeConstraintSql (expression: PeriodForSystemTimeConstraintExpr): string {\n return `PERIOD FOR SYSTEM_TIME (${this.sql(expression, 'this')}, ${this.sql(expression, 'expression')})`;\n }\n\n notNullColumnConstraintSql (expression: NotNullColumnConstraintExpr): string {\n return expression.args.allowNull ? 'NULL' : 'NOT NULL';\n }\n\n primaryKeyColumnConstraintSql (expression: PrimaryKeyColumnConstraintExpr): string {\n const desc = expression.args.desc;\n if (desc !== undefined) {\n return `PRIMARY KEY${desc ? ' DESC' : ' ASC'}`;\n }\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `PRIMARY KEY${options}`;\n }\n\n uniqueColumnConstraintSql (expression: UniqueColumnConstraintExpr): string {\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n const indexType = expression.args.indexType;\n const indexTypeStr = indexType ? ` USING ${indexType}` : '';\n let onConflict = this.sql(expression, 'onConflict');\n onConflict = onConflict ? ` ${onConflict}` : '';\n const nullsSql = expression.args.nulls ? ' NULLS NOT DISTINCT' : '';\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `UNIQUE${nullsSql}${thisStr}${indexTypeStr}${onConflict}${options}`;\n }\n\n inOutColumnConstraintSql (expression: InOutColumnConstraintExpr): string {\n const input = expression.args.input;\n const output = expression.args.output;\n const variadic = expression.args.variadic;\n\n // VARIADIC is mutually exclusive with IN/OUT/INOUT\n if (variadic) {\n return 'VARIADIC';\n }\n\n if (input && output) {\n return `IN${this._constructor.INOUT_SEPARATOR}OUT`;\n }\n if (input) {\n return 'IN';\n }\n if (output) {\n return 'OUT';\n }\n\n return '';\n }\n\n createableSql (expression: Expression, _locations: unknown): string {\n return this.sql(expression, 'this');\n }\n\n createSql (expression: CreateExpr): string {\n let kind = this.sql(expression, 'kind').toUpperCase();\n kind = this.dialect._constructor.INVERSE_CREATABLE_KIND_MAPPING[kind] ?? kind;\n\n const properties = expression.args.properties;\n if (properties) {\n assertIsInstanceOf(properties, PropertiesExpr);\n }\n const propertiesLocs = properties ? this.locateProperties(properties) : new Map<string, Expression[]>();\n\n const thisSql = this.createableSql(expression, propertiesLocs);\n\n let propertiesSql = '';\n const postSchema = propertiesLocs.get(PropertiesLocation.POST_SCHEMA);\n const postWith = propertiesLocs.get(PropertiesLocation.POST_WITH);\n\n if (postSchema || postWith) {\n const propsAst = new PropertiesExpr({\n expressions: [...(postSchema ?? []), ...(postWith ?? [])],\n });\n propsAst.parent = expression;\n propertiesSql = this.sql(propsAst);\n\n if (postSchema) {\n propertiesSql = this.sep() + propertiesSql;\n } else if (!this.pretty) {\n propertiesSql = ` ${propertiesSql}`;\n }\n }\n\n const begin = expression.args.begin ? ' BEGIN' : '';\n const end = expression.args.end ? ' END' : '';\n\n let expressionSql = this.sql(expression, 'expression');\n if (expressionSql) {\n expressionSql = `${begin}${this.sep()}${expressionSql}${end}`;\n\n if (this._constructor.CREATE_FUNCTION_RETURN_AS || !(expression.args.expression instanceof ReturnExpr)) {\n let postaliasPropsSql = '';\n const postAlias = propertiesLocs.get(PropertiesLocation.POST_ALIAS);\n if (postAlias) {\n postaliasPropsSql = this.properties(\n new PropertiesExpr({ expressions: postAlias }),\n { wrapped: false },\n );\n }\n postaliasPropsSql = postaliasPropsSql ? ` ${postaliasPropsSql}` : '';\n expressionSql = ` AS${postaliasPropsSql}${expressionSql}`;\n }\n }\n\n let postindexPropsSql = '';\n const postIndex = propertiesLocs.get(PropertiesLocation.POST_INDEX);\n if (postIndex) {\n postindexPropsSql = this.properties(\n new PropertiesExpr({ expressions: postIndex }),\n {\n wrapped: false,\n prefix: ' ',\n },\n );\n }\n\n let indexes = this.expressions(expression, {\n key: 'indexes',\n indent: false,\n sep: ' ',\n });\n indexes = indexes ? ` ${indexes}` : '';\n const indexSql = indexes + postindexPropsSql;\n\n const replace = expression.args.replace ? ' OR REPLACE' : '';\n const refresh = expression.args.refresh ? ' OR REFRESH' : '';\n const unique = expression.args.unique ? ' UNIQUE' : '';\n\n const clustered = expression.args.clustered;\n let clusteredSql = '';\n if (clustered === undefined) {\n clusteredSql = '';\n } else if (clustered) {\n clusteredSql = ' CLUSTERED COLUMNSTORE';\n } else {\n clusteredSql = ' NONCLUSTERED COLUMNSTORE';\n }\n\n let postcreatePropsSql = '';\n const postCreate = propertiesLocs.get(PropertiesLocation.POST_CREATE);\n if (postCreate) {\n postcreatePropsSql = this.properties(\n new PropertiesExpr({ expressions: postCreate }),\n {\n sep: ' ',\n prefix: ' ',\n wrapped: false,\n },\n );\n }\n\n const modifiers = `${clusteredSql}${replace}${refresh}${unique}${postcreatePropsSql}`;\n\n let postexpressionPropsSql = '';\n const postExpression = propertiesLocs.get(PropertiesLocation.POST_EXPRESSION);\n if (postExpression) {\n postexpressionPropsSql = this.properties(\n new PropertiesExpr({ expressions: postExpression }),\n {\n sep: ' ',\n prefix: ' ',\n wrapped: false,\n },\n );\n }\n\n const concurrently = expression.args.concurrently ? ' CONCURRENTLY' : '';\n const existsSql = expression.args.exists ? ' IF NOT EXISTS' : '';\n const noSchemaBinding = expression.args.noSchemaBinding ? ' WITH NO SCHEMA BINDING' : '';\n\n let clone = this.sql(expression, 'clone');\n clone = clone ? ` ${clone}` : '';\n\n let propertiesExpression: string;\n if (this._constructor.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES.has(kind)) {\n propertiesExpression = `${expressionSql}${propertiesSql}`;\n } else {\n propertiesExpression = `${propertiesSql}${expressionSql}`;\n }\n\n expressionSql = `CREATE${modifiers} ${kind}${concurrently}${existsSql} ${thisSql}${propertiesExpression}${postexpressionPropsSql}${indexSql}${noSchemaBinding}${clone}`;\n\n return this.prependCtes(expression, expressionSql);\n }\n\n sequencePropertiesSql (expression: SequencePropertiesExpr): string {\n let start = this.sql(expression, 'start');\n start = start ? `START WITH ${start}` : '';\n let increment = this.sql(expression, 'increment');\n increment = increment ? ` INCREMENT BY ${increment}` : '';\n let minvalue = this.sql(expression, 'minvalue');\n minvalue = minvalue ? ` MINVALUE ${minvalue}` : '';\n let maxvalue = this.sql(expression, 'maxvalue');\n maxvalue = maxvalue ? ` MAXVALUE ${maxvalue}` : '';\n let owned = this.sql(expression, 'owned');\n owned = owned ? ` OWNED BY ${owned}` : '';\n\n const cache = expression.args.cache;\n let cacheStr = '';\n if (cache === undefined) {\n cacheStr = '';\n } else if (cache === true) {\n cacheStr = ' CACHE';\n } else {\n cacheStr = ` CACHE ${cache}`;\n }\n\n const options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n const optionsPart = options ? ` ${options}` : '';\n\n return `${start}${increment}${minvalue}${maxvalue}${cacheStr}${optionsPart}${owned}`.trimStart();\n }\n\n cloneSql (expression: CloneExpr): string {\n const thisStr = this.sql(expression, 'this');\n const shallow = expression.args.shallow ? 'ShaLLOW ' : '';\n const keyword = (expression.args.copy && this._constructor.SUPPORTS_TABLE_COPY) ? 'COPY' : 'CLONE';\n return `${shallow}${keyword} ${thisStr}`;\n }\n\n describeSql (expression: DescribeExpr): string {\n const style = expression.args.style;\n const stylePart = style ? ` ${style}` : '';\n let partition = this.sql(expression, 'partition');\n partition = partition ? ` ${partition}` : '';\n let format = this.sql(expression, 'format');\n format = format ? ` ${format}` : '';\n const asJson = expression.args.asJson ? ' AS JSON' : '';\n\n return `DESCRIBE${stylePart}${format} ${this.sql(expression, 'this')}${partition}${asJson}`;\n }\n\n heredocSql (expression: HeredocExpr): string {\n const tag = this.sql(expression, 'tag');\n return `$${tag}$${this.sql(expression, 'this')}$${tag}$`;\n }\n\n protected prependCtes (expression: Expression, sql: string): string {\n const withSql = this.sql(expression, 'with');\n if (withSql) {\n return `${withSql}${this.sep()}${sql}`;\n }\n return sql;\n }\n\n withSql (expression: WithExpr): string {\n const sql = this.expressions(expression, { flat: true });\n const recursive = (this._constructor.CTE_RECURSIVE_KEYWORD_REQUIRED && expression.args.recursive)\n ? 'RECURSIVE '\n : '';\n let search = this.sql(expression, 'search');\n search = search ? ` ${search}` : '';\n\n return `WITH ${recursive}${sql}${search}`;\n }\n\n cteSql (expression: CteExpr): string {\n const alias = expression.args.alias;\n if (alias) {\n alias.addComments?.(expression.popComments());\n }\n\n const aliasSql = this.sql(expression, 'alias');\n\n const materialized = expression.args.materialized;\n let materializedStr = '';\n if (materialized === false) {\n materializedStr = 'NOT MATERIALIZED ';\n } else if (materialized) {\n materializedStr = 'MATERIALIZED ';\n }\n\n const keyExpressions = this.expressions(expression, {\n key: 'keyExpressions',\n flat: true,\n });\n const keyPart = keyExpressions ? ` USING KEY (${keyExpressions})` : '';\n\n return `${aliasSql}${keyPart} AS ${materializedStr}${this.wrap(expression)}`;\n }\n\n tableAliasSql (expression: TableAliasExpr): string {\n let alias = this.sql(expression, 'this');\n let columns = this.expressions(expression, {\n key: 'columns',\n flat: true,\n });\n columns = columns ? `(${columns})` : '';\n\n if (columns && !this._constructor.SUPPORTS_TABLE_ALIAS_COLUMNS) {\n columns = '';\n this.unsupported('Named columns are not supported in table alias.');\n }\n\n if (!alias && !this.dialect._constructor.UNNEST_COLUMN_ONLY) {\n alias = this.nextName();\n }\n\n return `${alias}${columns}`;\n }\n\n bitStringSql (expression: BitStringExpr): string {\n const thisStr = this.sql(expression, 'this');\n if (this.dialect._constructor.BIT_START) {\n return `${this.dialect._constructor.BIT_START}${thisStr}${this.dialect._constructor.BIT_END}`;\n }\n return `${parseInt(thisStr, 2)}`;\n }\n\n hexStringSql (expression: HexStringExpr, options: { binaryFunctionRepr?: string } = {}): string {\n const { binaryFunctionRepr } = options;\n const thisStr = this.sql(expression, 'this');\n const isIntegerType = expression.args.isInteger;\n\n if ((isIntegerType && !this.dialect._constructor.HEX_STRING_IS_INTEGER_TYPE)\n || (!this.dialect._constructor.HEX_START && !binaryFunctionRepr)) {\n return `${parseInt(thisStr, 16)}`;\n }\n\n if (!isIntegerType) {\n if (binaryFunctionRepr) {\n return this.func(binaryFunctionRepr, [literal(thisStr)]);\n }\n if (this.dialect._constructor.HEX_STRING_IS_INTEGER_TYPE) {\n this.unsupported('Unsupported transpilation from BINARY/BLOB hex string');\n }\n }\n\n return `${this.dialect._constructor.HEX_START}${thisStr}${this.dialect._constructor.HEX_END}`;\n }\n\n byteStringSql (expression: ByteStringExpr): string {\n const thisStr = this.sql(expression, 'this');\n if (this.dialect._constructor.BYTE_START) {\n const escapedByteString = this.escapeStr(\n thisStr,\n {\n escapeBackslash: false,\n delimiter: this.dialect._constructor.BYTE_END,\n escapedDelimiter: this.escapedByteQuoteEnd,\n isByteString: true,\n },\n );\n const isBytes = expression.args.isBytes || false;\n const delimitedByteString = `${this.dialect._constructor.BYTE_START}${escapedByteString}${this.dialect._constructor.BYTE_END}`;\n\n if (isBytes && !this.dialect._constructor.BYTE_STRING_IS_BYTES_TYPE) {\n return this.sql(cast(delimitedByteString, 'BINARY', { dialect: this.dialect }));\n }\n if (!isBytes && this.dialect._constructor.BYTE_STRING_IS_BYTES_TYPE) {\n return this.sql(cast(delimitedByteString, 'VARCHAR', { dialect: this.dialect }));\n }\n\n return delimitedByteString;\n }\n return thisStr;\n }\n\n unicodeStringSql (expression: UnicodeStringExpr): string {\n const thisSql = this.sql(expression, 'this');\n const escape = expression.args.escape;\n\n let escapeSubstitute: string;\n let leftQuote: string;\n let rightQuote: string | undefined;\n\n if (this.dialect._constructor.UNICODE_START) {\n escapeSubstitute = '\\\\$1';\n leftQuote = this.dialect._constructor.UNICODE_START;\n rightQuote = this.dialect._constructor.UNICODE_END;\n } else {\n escapeSubstitute = '\\\\u$1';\n leftQuote = this.dialect._constructor.QUOTE_START;\n rightQuote = this.dialect._constructor.QUOTE_END;\n }\n\n let escapePattern: RegExp;\n let escapeSql: string;\n\n if (escape) {\n escapePattern = new RegExp(`${escape.name}(\\\\d+)`, 'g');\n escapeSql = this._constructor.SUPPORTS_UESCAPE ? ` UESCAPE ${this.sql(escape)}` : '';\n } else {\n escapePattern = ESCAPED_UNICODE_RE;\n escapeSql = '';\n }\n\n let resultThis = thisSql;\n if (!this.dialect._constructor.UNICODE_START || (escape && !this._constructor.SUPPORTS_UESCAPE)) {\n const replacement = this._constructor.UNICODE_SUBSTITUTE || escapeSubstitute;\n if (typeof replacement === 'function') {\n resultThis = resultThis.replace(escapePattern, replacement);\n } else {\n resultThis = resultThis.replace(escapePattern, replacement);\n }\n }\n\n return `${leftQuote}${resultThis}${rightQuote}${escapeSql}`;\n }\n\n rawStringSql (expression: RawStringExpr): string {\n let string = expression.args.this?.toString() || '';\n\n if (this.dialect._constructor.tokenizerClass.STRING_ESCAPES.includes('\\\\')) {\n string = string?.replace(/\\\\/g, '\\\\\\\\');\n }\n\n string = this.escapeStr(string, { escapeBackslash: false });\n return `${this.dialect._constructor.QUOTE_START}${string}${this.dialect._constructor.QUOTE_END}`;\n }\n\n dataTypeParamSql (expression: DataTypeParamExpr): string {\n const thisStr = this.sql(expression, 'this');\n const specifier = this.sql(expression, 'expression');\n const specifierStr = specifier && this._constructor.DATA_TYPE_SPECIFIERS_ALLOWED\n ? ` ${specifier}`\n : '';\n return `${thisStr}${specifierStr}`;\n }\n\n dataTypeSql (expression: DataTypeExpr): string {\n let nested = '';\n let values = '';\n\n const exprNested = expression.args.nested;\n const interior = exprNested && this.pretty\n ? this.expressions(expression, {\n dynamic: true,\n newLine: true,\n skipFirst: true,\n skipLast: true,\n })\n : this.expressions(expression, { flat: true });\n\n const typeValue = expression.args.this;\n if (typeof typeValue === 'string' && this._constructor.UNSUPPORTED_TYPES.has(typeValue)) {\n this.unsupported(`Data type ${typeValue} is not supported when targeting ${this.dialect._constructor.name}`);\n }\n\n let typeSql: string | undefined;\n if (typeValue instanceof Expression) {\n typeSql = this.sql(typeValue);\n } else if (typeValue === DataTypeExprKind.USERDEFINED && expression.args.kind) {\n typeSql = this.sql(expression, 'kind');\n } else {\n const typeMapping = typeValue && this._constructor.TYPE_MAPPING.get(typeValue);\n const isKnownKind = typeValue !== undefined && (Object.values(DataTypeExprKind) as string[]).includes(typeValue);\n typeSql = typeMapping !== undefined ? typeMapping : (typeValue && (isKnownKind ? camelToScreamingSnakeCase(typeValue) : typeValue));\n }\n\n if (interior) {\n if (exprNested) {\n const structDelim = this._constructor.STRUCT_DELIMITER;\n nested = `${structDelim[0]}${interior}${structDelim[1]}`;\n if (expression.args.values?.length) {\n const delimiters = typeValue === DataTypeExprKind.ARRAY ? ['[', ']'] : ['(', ')'];\n const valuesStr = this.expressions(expression, {\n key: 'values',\n flat: true,\n });\n values = `${delimiters[0]}${valuesStr}${delimiters[1]}`;\n }\n } else if (typeValue === DataTypeExprKind.INTERVAL) {\n nested = ` ${interior}`;\n } else {\n nested = `(${interior})`;\n }\n }\n\n typeSql = `${typeSql}${nested}${values}`;\n if (\n this._constructor.TZ_TO_WITH_TIME_ZONE\n && (typeValue === DataTypeExprKind.TIMETZ || typeValue === DataTypeExprKind.TIMESTAMPTZ)) {\n typeSql = `${typeSql} WITH TIME ZONE`;\n }\n\n return typeSql;\n }\n\n directorySql (expression: DirectoryExpr): string {\n const local = expression.args.local ? 'LOCAL ' : '';\n let rowFormat = this.sql(expression, 'rowFormat');\n rowFormat = rowFormat ? ` ${rowFormat}` : '';\n return `${local}DIRECTORY ${this.sql(expression, 'this')}${rowFormat}`;\n }\n\n deleteSql (expression: DeleteExpr): string {\n const thisStr = this.sql(expression, 'this');\n const thisClause = thisStr ? ` FROM ${thisStr}` : '';\n let using = this.expressions(expression, { key: 'using' });\n using = using ? ` USING ${using}` : '';\n let cluster = this.sql(expression, 'cluster');\n cluster = cluster ? ` ${cluster}` : '';\n const where = this.sql(expression, 'where');\n const returning = this.sql(expression, 'returning');\n const order = this.sql(expression, 'order');\n const limit = this.sql(expression, 'limit');\n let tables = this.expressions(expression, { key: 'tables' });\n tables = tables ? ` ${tables}` : '';\n let expressionSql: string;\n if (this._constructor.RETURNING_END) {\n expressionSql = `${thisClause}${using}${cluster}${where}${returning}${order}${limit}`;\n } else {\n expressionSql = `${returning}${thisClause}${using}${cluster}${where}${order}${limit}`;\n }\n return this.prependCtes(expression, `DELETE${tables}${expressionSql}`);\n }\n\n dropSql (expression: DropExpr): string {\n const thisStr = this.sql(expression, 'this');\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? ` (${expressions})` : '';\n const kind = expression.args.kind?.toString();\n const kindStr = kind ? (this.dialect._constructor.INVERSE_CREATABLE_KIND_MAPPING?.[kind] || kind).toUpperCase() : kind;\n const existsSql = expression.args.exists ? ' IF EXISTS ' : ' ';\n const concurrentlySql = expression.args.concurrently ? ' CONCURRENTLY' : '';\n let onCluster = this.sql(expression, 'cluster');\n onCluster = onCluster ? ` ${onCluster}` : '';\n const temporary = expression.args.temporary ? ' TEMPORARY' : '';\n const materialized = expression.args.materialized ? ' MATERIALIZED' : '';\n const cascade = expression.args.cascade ? ' CASCADE' : '';\n const constraints = expression.args.constraints ? ' CONSTRAINTS' : '';\n const purge = expression.args.purge ? ' PURGE' : '';\n return `DROP${temporary}${materialized} ${kindStr}${concurrentlySql}${existsSql}${thisStr}${onCluster}${expressions}${cascade}${constraints}${purge}`;\n }\n\n setOperation (expression: SetOperationExpr): string {\n const opType = expression._constructor;\n const opName = opType.key.toUpperCase();\n\n let distinct = expression.args.distinct;\n if (\n distinct === false\n && (expression instanceof ExceptExpr || expression instanceof IntersectExpr)\n && !this._constructor.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE\n ) {\n this.unsupported(`${opName} ALL is not supported`);\n }\n\n const defaultDistinct = this.dialect._constructor.SET_OP_DISTINCT_BY_DEFAULT?.[opType.key];\n\n if (distinct === undefined) {\n distinct = defaultDistinct;\n if (distinct === undefined) {\n this.unsupported(`${opName} requires DISTINCT or ALL to be specified`);\n }\n }\n\n let distinctOrAll: string;\n if (distinct === defaultDistinct) {\n distinctOrAll = '';\n } else {\n distinctOrAll = distinct ? ' DISTINCT' : ' ALL';\n }\n\n const sideKind = [expression.args.side, expression.args.kind].filter(Boolean).join(' ')\n .toUpperCase();\n const sideKindStr = sideKind ? `${sideKind} ` : '';\n\n const byName = expression.args.byName ? ' BY NAME' : '';\n let on = this.expressions(expression, {\n key: 'on',\n flat: true,\n });\n on = on ? ` ON (${on})` : '';\n\n return `${sideKindStr}${opName}${distinctOrAll}${byName}${on}`;\n }\n\n setOperations (expression: SetOperationExpr): string {\n if (!this._constructor.SET_OP_MODIFIERS) {\n const limit = expression.args.limit;\n const order = expression.args.order;\n\n if (limit || order) {\n const selectExpr = this.moveCtesToTopLevel(\n subquery(expression, '_l_0', { copy: false }).select('*', { copy: false }),\n );\n\n let select = selectExpr;\n\n if (limit) {\n select = select.limit(typeof limit === 'number' ? limit : limit.pop(), { copy: false });\n }\n\n if (order) {\n select = select.orderBy(order.pop(), { copy: false });\n }\n\n return this.sql(select);\n }\n }\n\n const sqls: string[] = [];\n const stack: (string | Expression)[] = [expression];\n\n while (0 < stack.length) {\n const node = stack.pop();\n\n if (node instanceof SetOperationExpr) {\n if (node.args.expression) stack.push(node.args.expression);\n stack.push(\n this.maybeComment(\n this.setOperation(node),\n undefined,\n node.comments,\n true,\n ),\n );\n if (node.args.this) stack.push(node.args.this);\n } else {\n const nodeStr = typeof node === 'string' ? node : this.sql(node);\n sqls.push(nodeStr);\n }\n }\n\n let self = sqls.join(this.sep());\n self = this.queryModifiers(expression, self);\n return this.prependCtes(expression, self);\n }\n\n fetchSql (expression: FetchExpr): string {\n const direction = expression.args.direction;\n const directionStr = direction ? ` ${direction}` : '';\n const count = this.sql(expression, 'count');\n const countStr = count ? ` ${count}` : '';\n const limitOptions = this.sql(expression, 'limitOptions');\n const limitOptionsStr = limitOptions ? `${limitOptions}` : ' ROWS ONLY';\n return `${this.seg('FETCH')}${directionStr}${countStr}${limitOptionsStr}`;\n }\n\n limitOptionsSql (expression: LimitOptionsExpr): string {\n const percent = expression.args.percent ? ' PERCENT' : '';\n const rows = expression.args.rows ? ' ROWS' : '';\n let withTies = expression.args.withTies ? ' WITH TIES' : '';\n\n if (!withTies && rows) {\n withTies = ' ONLY';\n }\n\n return `${percent}${rows}${withTies}`;\n }\n\n filterSql (expression: FilterExpr): string {\n const dialect = this._constructor;\n if (dialect.AGGREGATE_FILTER_SUPPORTED) {\n const thisStr = this.sql(expression, 'this');\n const where = this.sql(expression, 'expression').trim();\n return `${thisStr} FILTER(${where})`;\n }\n\n const agg = expression.args.this;\n const aggArg = agg ? agg.args.this : undefined;\n const cond = expression.args.expression?.args.this;\n\n if (aggArg instanceof Expression && cond instanceof Expression) {\n aggArg.replace(new IfExpr({\n this: cond.copy(),\n true: aggArg.copy(),\n }));\n }\n return this.sql(agg);\n }\n\n hintSql (expression: HintExpr): string {\n if (!this._constructor.QUERY_HINTS) {\n this.unsupported('Hints are not supported');\n return '';\n }\n return ` /*+ ${this.expressions(expression, { sep: this._constructor.QUERY_HINT_SEP }).trim()} */`;\n }\n\n indexParametersSql (expression: IndexParametersExpr): string {\n let using = this.sql(expression, 'using');\n using = using ? ` USING ${using}` : '';\n const columns = this.expressions(expression, {\n key: 'columns',\n flat: true,\n });\n const columnsStr = columns ? `(${columns})` : '';\n const partitionByRaw = this.expressions(expression, {\n key: 'partitionBy',\n flat: true,\n });\n const partitionBy = partitionByRaw ? ` PARTITION BY ${partitionByRaw}` : '';\n const where = this.sql(expression, 'where');\n let include = this.expressions(expression, {\n key: 'include',\n flat: true,\n });\n if (include) {\n include = ` INCLUDE (${include})`;\n }\n const withStorageRaw = this.expressions(expression, {\n key: 'withStorage',\n flat: true,\n });\n const withStorage = withStorageRaw ? ` WITH (${withStorageRaw})` : '';\n const tablespaceRaw = this.sql(expression, 'tablespace');\n const tablespace = tablespaceRaw ? ` USING INDEX TABLESPACE ${tablespaceRaw}` : '';\n const onRaw = this.sql(expression, 'on');\n const on = onRaw ? ` ON ${onRaw}` : '';\n\n return `${using}${columnsStr}${include}${withStorage}${tablespace}${partitionBy}${where}${on}`;\n }\n\n indexSql (expression: IndexExpr): string {\n const unique = expression.args.unique ? 'UNIQUE ' : '';\n const primary = expression.args.primary ? 'PRIMARY ' : '';\n const amp = expression.args.amp ? 'AMP ' : '';\n\n let name = this.sql(expression, 'this');\n name = name ? `${name} ` : '';\n\n let table = this.sql(expression, 'table');\n table = table ? `${this._constructor.INDEX_ON} ${table}` : '';\n\n const index = !table ? 'INDEX ' : '';\n\n const params = this.sql(expression, 'params');\n\n return `${unique}${primary}${amp}${index}${name}${table}${params}`;\n }\n\n identifierSql (expression: IdentifierExpr): string {\n let text = expression.name;\n const lower = text.toLowerCase();\n text = this.normalize && !expression.args.quoted ? lower : text;\n text = text.replaceAll(this.identifierEnd, this.escapedIdentifierEnd);\n\n if (\n expression.args.quoted\n || this.dialect.canQuote(expression, { identify: this.identify })\n || this._constructor.RESERVED_KEYWORDS.has(lower)\n || (!this.dialect._constructor.IDENTIFIERS_CAN_START_WITH_DIGIT && /^\\d/.test(text))\n ) {\n text = `${this.identifierStart}${text}${this.identifierEnd}`;\n }\n return text;\n }\n\n hexSql (expression: HexExpr): string {\n const hexFunc = this._constructor.HEX_FUNC;\n let text = this.func(hexFunc, [this.sql(expression, 'this')]);\n if (this.dialect._constructor.HEX_LOWERCASE) {\n text = this.func('LOWER', [text]);\n }\n return text;\n }\n\n lowerHexSql (expression: LowerHexExpr): string {\n const hexFunc = this._constructor.HEX_FUNC;\n let text = this.func(hexFunc, [this.sql(expression, 'this')]);\n if (!this.dialect._constructor.HEX_LOWERCASE) {\n text = this.func('LOWER', [text]);\n }\n return text;\n }\n\n inputOutputFormatSql (expression: InputOutputFormatExpr): string {\n const inputFormat = this.sql(expression, 'inputFormat');\n const inputFormatStr = inputFormat ? `INPUTFORMAT ${inputFormat}` : '';\n const outputFormat = this.sql(expression, 'outputFormat');\n const outputFormatStr = outputFormat ? `OUTPUTFORMAT ${outputFormat}` : '';\n return [inputFormatStr, outputFormatStr].join(this.sep());\n }\n\n nationalSql (expression: NationalExpr, options: { prefix?: string } = {}): string {\n const { prefix = 'N' } = options;\n const string = this.sql(literal(expression.name));\n return `${prefix}${string}`;\n }\n\n partitionSql (expression: PartitionExpr): string {\n const partitionKeyword = expression.args.subpartition ? 'SUBPARTITION' : 'PARTITION';\n return `${partitionKeyword}(${this.expressions(expression, { flat: true })})`;\n }\n\n propertiesSql (expression: PropertiesExpr): string {\n const rootProperties = [];\n const withProperties = [];\n\n for (const p of expression.args.expressions || []) {\n const pLoc = this._constructor.PROPERTIES_LOCATION.get(p._constructor);\n\n if (pLoc === PropertiesLocation.POST_WITH) {\n withProperties.push(p);\n } else if (pLoc === PropertiesLocation.POST_SCHEMA) {\n rootProperties.push(p);\n }\n }\n\n const rootPropsAst = new PropertiesExpr({ expressions: rootProperties });\n rootPropsAst.parent = expression.parent;\n\n const withPropsAst = new PropertiesExpr({ expressions: withProperties });\n withPropsAst.parent = expression.parent;\n\n const rootProps = this.rootProperties(rootPropsAst);\n let withProps = this.withProperties(withPropsAst);\n\n if (rootProps && withProps && !this.pretty) {\n withProps = ` ${withProps}`;\n }\n\n return rootProps + withProps;\n }\n\n rootProperties (properties: PropertiesExpr): string {\n if (0 < (properties.args.expressions || []).length) {\n return this.expressions(properties, {\n indent: false,\n sep: ' ',\n });\n }\n return '';\n }\n\n properties (\n properties: PropertiesExpr,\n options: {\n prefix?: string;\n sep?: string;\n suffix?: string;\n wrapped?: boolean;\n } = {},\n ): string {\n const {\n prefix = '',\n sep = ', ',\n suffix = '',\n wrapped = true,\n } = options;\n if (0 < (properties.args.expressions || []).length) {\n const expressions = this.expressions(properties, {\n sep,\n indent: false,\n });\n if (expressions) {\n const wrappedExpr = wrapped ? this.wrap(expressions) : expressions;\n return `${prefix}${prefix.trim() ? ' ' : ''}${wrappedExpr}${suffix}`;\n }\n }\n return '';\n }\n\n withProperties (properties: PropertiesExpr): string {\n return this.properties(properties, {\n prefix: this.seg(this._constructor.WITH_PROPERTIES_PREFIX, ''),\n });\n }\n\n locateProperties (properties: PropertiesExpr): Map<PropertiesLocation, Expression[]> {\n const propertiesLocs = new Map<PropertiesLocation, Expression[]>();\n const expressions = properties.args.expressions || [];\n for (const p of expressions) {\n const pLoc = this._constructor.PROPERTIES_LOCATION.get(p._constructor) || PropertiesLocation.UNSUPPORTED;\n if (pLoc !== PropertiesLocation.UNSUPPORTED) {\n if (!propertiesLocs.has(pLoc)) {\n propertiesLocs.set(pLoc, []);\n }\n propertiesLocs.get(pLoc)?.push(p);\n } else {\n this.unsupported(`Unsupported property ${p._constructor.key}`);\n }\n }\n return propertiesLocs;\n }\n\n propertyName (\n expression: PropertyExpr,\n options: {\n stringKey?: boolean;\n } = {},\n ): string {\n const { stringKey = false } = options;\n\n if (expression.args.this instanceof DotExpr) {\n return this.sql(expression, 'this');\n }\n return stringKey ? `'${expression.name}'` : expression.name;\n }\n\n propertySql (expression: PropertyExpr): string {\n const propertyCls = expression._constructor;\n if (propertyCls === PropertyExpr) {\n return `${this.propertyName(expression)}=${this.sql(expression, 'value')}`;\n }\n\n const propertyName = PropertiesExpr.PROPERTY_TO_NAME?.[propertyCls.key];\n if (!propertyName) {\n this.unsupported(`Unsupported property ${expression._constructor.key}`);\n }\n\n return `${propertyName}=${this.sql(expression, 'this')}`;\n }\n\n likePropertySql (expression: LikePropertyExpr): string {\n if (this._constructor.SUPPORTS_CREATE_TABLE_LIKE) {\n const options = (expression.args.expressions || [])\n .map((e) => `${e.name} ${this.sql(e, 'value')}`)\n .join(' ');\n const optionsStr = options ? ` ${options}` : '';\n return `LIKE ${this.sql(expression, 'this')}${optionsStr}`;\n }\n return this.propertySql(expression);\n }\n\n fallbackPropertySql (expression: FallbackPropertyExpr): string {\n const no = expression.args.no ? 'NO ' : '';\n const protection = expression.args.protection ? ' PROTECTION' : '';\n return `${no}FALLBACK${protection}`;\n }\n\n journalPropertySql (expression: JournalPropertyExpr): string {\n const no = expression.args.no ? 'NO ' : '';\n const local = expression.args.local;\n const localStr = local ? `${local} ` : '';\n const dual = expression.args.dual ? 'DUAL ' : '';\n const before = expression.args.before ? 'BEFORE ' : '';\n const after = expression.args.after ? 'AFTER ' : '';\n return `${no}${localStr}${dual}${before}${after}JOURNAL`;\n }\n\n freespacePropertySql (expression: FreespacePropertyExpr): string {\n const freespace = this.sql(expression, 'this');\n const percent = expression.args.percent ? ' PERCENT' : '';\n return `FREESPACE=${freespace}${percent}`;\n }\n\n checksumPropertySql (expression: ChecksumPropertyExpr): string {\n let property: string;\n if (expression.args.default) {\n property = 'DEFAULT';\n } else if (expression.args.on) {\n property = 'ON';\n } else {\n property = 'OFF';\n }\n return `CHECKSUM=${property}`;\n }\n\n mergeBlockRatioPropertySql (expression: MergeBlockRatioPropertyExpr): string {\n if (expression.args.no) {\n return 'NO MERGEBLOCKRATIO';\n }\n if (expression.args.default) {\n return 'DEFAULT MERGEBLOCKRATIO';\n }\n const percent = expression.args.percent ? ' PERCENT' : '';\n return `MERGEBLOCKRATIO=${this.sql(expression, 'this')}${percent}`;\n }\n\n dataBlocksizePropertySql (expression: DataBlocksizePropertyExpr): string {\n const defaultVal = expression.args.default;\n const minimum = expression.args.minimum;\n const maximum = expression.args.maximum;\n if (defaultVal || minimum || maximum) {\n let prop: string;\n if (defaultVal) {\n prop = 'DEFAULT';\n } else if (minimum) {\n prop = 'MINIMUM';\n } else {\n prop = 'MAXIMUM';\n }\n return `${prop} DATABLOCKSIZE`;\n }\n const units = expression.args.units;\n const unitsStr = units ? ` ${units}` : '';\n return `DATABLOCKSIZE=${this.sql(expression, 'size')}${unitsStr}`;\n }\n\n blockCompressionPropertySql (expression: BlockCompressionPropertyExpr): string {\n const autotemp = expression.args.autotemp;\n const always = expression.args.always;\n const defaultVal = expression.args.default;\n const manual = expression.args.manual;\n const never = expression.args.never;\n\n let prop: string;\n if (autotemp !== undefined) {\n prop = `AUTOTEMP${this.sql(autotemp)}`;\n } else if (always) {\n prop = 'ALWAYS';\n } else if (defaultVal) {\n prop = 'DEFAULT';\n } else if (manual) {\n prop = 'MANUAL';\n } else if (never) {\n prop = 'NEVER';\n } else {\n prop = '';\n }\n return `BLOCKCOMPRESSION=${prop}`;\n }\n\n isolatedLoadingPropertySql (expression: IsolatedLoadingPropertyExpr): string {\n const no = expression.args.no;\n const noStr = no ? ' NO' : '';\n const concurrent = expression.args.concurrent;\n const concurrentStr = concurrent ? ' CONCURRENT' : '';\n let target = this.sql(expression, 'target');\n target = target ? ` ${target}` : '';\n return `WITH${noStr}${concurrentStr} ISOLATED LOADING${target}`;\n }\n\n partitionBoundSpecSql (expression: PartitionBoundSpecExpr): string {\n if (Array.isArray(expression.args.this)) {\n return `IN (${this.expressions(expression, {\n key: 'this',\n flat: true,\n })})`;\n }\n if (expression.args.this) {\n const modulus = this.sql(expression, 'this');\n const remainder = this.sql(expression, 'expression');\n return `WITH (MODULUS ${modulus}, REMAINDER ${remainder})`;\n }\n\n const fromExpressions = this.expressions(expression, {\n key: 'fromExpressions',\n flat: true,\n });\n const toExpressions = this.expressions(expression, {\n key: 'toExpressions',\n flat: true,\n });\n return `FROM (${fromExpressions}) TO (${toExpressions})`;\n }\n\n partitionedOfPropertySql (expression: PartitionedOfPropertyExpr): string {\n const thisStr = this.sql(expression, 'this');\n\n let forValuesOrDefault: Expression | string = expression.args.expression ?? '';\n if (forValuesOrDefault instanceof PartitionBoundSpecExpr) {\n forValuesOrDefault = ` FOR VALUES ${this.sql(forValuesOrDefault)}`;\n } else {\n forValuesOrDefault = ' DEFAULT';\n }\n\n return `PARTITION OF ${thisStr}${forValuesOrDefault}`;\n }\n\n lockingPropertySql (expression: LockingPropertyExpr): string {\n const kind = expression.args.kind;\n const thisStr = expression.args.this ? ` ${this.sql(expression, 'this')}` : '';\n const forOrIn = expression.args.forOrIn;\n const forOrInStr = forOrIn ? ` ${forOrIn}` : '';\n const lockType = expression.args.lockType;\n const override = expression.args.override ? ' OVERRIDE' : '';\n return `LOCKING ${kind}${thisStr}${forOrInStr} ${lockType}${override}`;\n }\n\n withDataPropertySql (expression: WithDataPropertyExpr): string {\n const dataSql = `WITH ${expression.args.no ? 'NO ' : ''}DATA`;\n const statistics = expression.args.statistics;\n let statisticsSql = '';\n if (statistics !== undefined) {\n statisticsSql = ` AND ${!statistics ? 'NO ' : ''}STATISTICS`;\n }\n return `${dataSql}${statisticsSql}`;\n }\n\n withSystemVersioningPropertySql (expression: WithSystemVersioningPropertyExpr): string {\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? `HISTORY_TABLE=${thisStr}` : '';\n const dataConsistencyStr = this.sql(expression, 'dataConsistency');\n const dataConsistency = dataConsistencyStr ? `DATA_CONSISTENCY_CHECK=${dataConsistencyStr}` : undefined;\n const retentionPeriodStr = this.sql(expression, 'retentionPeriod');\n const retentionPeriod = retentionPeriodStr ? `HISTORY_RETENTION_PERIOD=${retentionPeriodStr}` : undefined;\n\n let onSql: string;\n if (thisStr) {\n onSql = this.func('ON', [\n thisStr,\n dataConsistency,\n retentionPeriod,\n ]);\n } else {\n onSql = expression.args.on ? 'ON' : 'OFF';\n }\n\n const sql = `SYSTEM_VERSIONING=${onSql}`;\n return expression.args.with ? `WITH(${sql})` : sql;\n }\n\n insertSql (expression: InsertExpr): string {\n const hint = this.sql(expression, 'hint');\n const overwrite = expression.args.overwrite;\n const isDirectory = expression.args.this instanceof DirectoryExpr;\n let keyword: string;\n if (isDirectory) {\n keyword = overwrite ? ' OVERWRITE' : ' INTO';\n } else {\n keyword = overwrite ? this._constructor.INSERT_OVERWRITE : ' INTO';\n }\n\n const stored = this.sql(expression, 'stored');\n const storedStr = stored ? ` ${stored}` : '';\n const alternative = expression.args.alternative as string | undefined;\n const alternativeStr = alternative ? ` OR ${alternative}` : '';\n const ignore = expression.args.ignore ? ' IGNORE' : '';\n const isFunction = expression.args.isFunction;\n if (isFunction) {\n keyword = `${keyword} FUNCTION`;\n }\n const thisStr = `${keyword} ${this.sql(expression, 'this')}`;\n const exists = expression.args.exists ? ' IF EXISTS' : '';\n const where = this.sql(expression, 'where');\n const whereStr = where ? `${this.sep()}REPLACE WHERE ${where}` : '';\n let expressionSql = `${this.sep()}${this.sql(expression, 'expression')}`;\n const onConflict = this.sql(expression, 'conflict');\n const onConflictStr = onConflict ? ` ${onConflict}` : '';\n const byName = expression.args.byName ? ' BY NAME' : '';\n const defaultValues = expression.args.default ? 'DEFAULT VALUES' : '';\n const returning = this.sql(expression, 'returning');\n\n if (this._constructor.RETURNING_END) {\n expressionSql = `${expressionSql}${onConflictStr}${defaultValues}${returning}`;\n } else {\n expressionSql = `${returning}${expressionSql}${onConflictStr}`;\n }\n\n const partition = this.sql(expression, 'partition');\n const partitionStr = partition ? ` ${partition}` : '';\n const settings = this.sql(expression, 'settings');\n const settingsStr = settings ? ` ${settings}` : '';\n const source = this.sql(expression, 'source');\n const sourceStr = source ? `TABLE ${source}` : '';\n\n const sql = `INSERT${hint}${alternativeStr}${ignore}${thisStr}${storedStr}${byName}${exists}${partitionStr}${settingsStr}${whereStr}${expressionSql}${sourceStr}`;\n return this.prependCtes(expression, sql);\n }\n\n introducerSql (expression: IntroducerExpr): string {\n const thisStr = this.sql(expression, 'this');\n return `_${thisStr} ${this.sql(expression, 'expression')}`;\n }\n\n killSql (expression: KillExpr): string {\n const kind = expression.args.kind ? `${expression.args.kind} ` : '';\n return `KILL ${kind}${this.sql(expression, 'this')}`;\n }\n\n pseudoTypeSql (expression: PseudoTypeExpr): string {\n return expression.name;\n }\n\n objectIdentifierSql (expression: ObjectIdentifierExpr): string {\n return expression.name;\n }\n\n onConflictSql (expression: OnConflictExpr): string {\n const conflict = expression.args.duplicate ? 'ON DUPLICATE KEY' : 'ON CONFLICT';\n\n let constraint = this.sql(expression, 'constraint');\n constraint = constraint ? ` ON CONSTRAINT ${constraint}` : '';\n\n let conflictKeys = this.expressions(expression, {\n key: 'conflictKeys',\n flat: true,\n });\n if (conflictKeys) {\n conflictKeys = `(${conflictKeys})`;\n }\n\n const indexPredicate = this.sql(expression, 'indexPredicate');\n conflictKeys = `${conflictKeys}${indexPredicate} `;\n\n const action = this.sql(expression, 'action');\n\n let expressions = this.expressions(expression, { flat: true });\n if (expressions) {\n const setKeyword = this._constructor.DUPLICATE_KEY_UPDATE_WITH_SET ? 'SET ' : '';\n expressions = ` ${setKeyword}${expressions}`;\n }\n\n const where = this.sql(expression, 'where');\n\n return `${conflict}${constraint}${conflictKeys}${action}${expressions}${where}`;\n }\n\n returningSql (expression: ReturningExpr): string {\n return this.opExpressions('RETURNING', expression);\n }\n\n rowFormatDelimitedPropertySql (expression: RowFormatDelimitedPropertyExpr): string {\n const fields = this.sql(expression, 'fields');\n const fieldsStr = fields ? ` FIELDS TERMINATED BY ${fields}` : '';\n const collectionItems = this.sql(expression, 'collectionItems');\n const collectionItemsStr = collectionItems ? ` COLLECTION ITEMS TERMINATED BY ${collectionItems}` : '';\n const mapKeys = this.sql(expression, 'mapKeys');\n const mapKeysStr = mapKeys ? ` MAP KEYS TERMINATED BY ${mapKeys}` : '';\n const lines = this.sql(expression, 'lines');\n const linesStr = lines ? ` LINES TERMINATED BY ${lines}` : '';\n const nullStr = this.sql(expression, 'null');\n const nullDef = nullStr ? ` NULL DEFINED AS ${nullStr}` : '';\n return `ROW FORMAT DELIMITED${fieldsStr}${collectionItemsStr}${mapKeysStr}${linesStr}${nullDef}`;\n }\n\n withTableHintSql (expression: WithTableHintExpr): string {\n return `WITH (${this.expressions(expression, { flat: true })})`;\n }\n\n indexTableHintSql (expression: IndexTableHintExpr): string {\n const thisSql = `${this.sql(expression, 'this')} INDEX`;\n let target = this.sql(expression, 'target');\n target = target ? ` FOR ${target}` : '';\n\n return `${thisSql}${target} (${this.expressions(expression, { flat: true })})`;\n }\n\n historicalDataSql (expression: HistoricalDataExpr): string {\n const thisSql = this.sql(expression, 'this');\n const kind = this.sql(expression, 'kind');\n const expr = this.sql(expression, 'expression');\n\n return `${thisSql} (${kind} => ${expr})`;\n }\n\n tableParts (expression: TableExpr): string {\n return [\n expression.args.catalog,\n expression.args.db,\n expression.args.this,\n ]\n .filter((part) => part !== undefined)\n .map((part) => this.sql(part))\n .join('.');\n }\n\n tableSql (\n expression: TableExpr,\n options: { sep?: string } = {},\n ): string {\n const { sep = ' AS ' } = options;\n\n const table = this.tableParts(expression);\n const only = expression.args.only ? 'ONLY ' : '';\n let partition = this.sql(expression, 'partition');\n partition = partition ? ` ${partition}` : '';\n let version = this.sql(expression, 'version');\n version = version ? ` ${version}` : '';\n let alias = this.sql(expression, 'alias');\n alias = alias ? `${sep}${alias}` : '';\n\n const sample = this.sql(expression, 'sample');\n let samplePreAlias: string;\n let samplePostAlias: string;\n if (this.dialect._constructor.ALIAS_POST_TABLESAMPLE) {\n samplePreAlias = sample;\n samplePostAlias = '';\n } else {\n samplePreAlias = '';\n samplePostAlias = sample;\n }\n\n let hints = this.expressions(expression, {\n key: 'hints',\n sep: ' ',\n });\n hints = hints && this._constructor.TABLE_HINTS ? ` ${hints}` : '';\n const pivots = this.expressions(expression, {\n key: 'pivots',\n sep: '',\n flat: true,\n });\n const joins = this.indent(\n this.expressions(expression, {\n key: 'joins',\n sep: '',\n flat: true,\n }),\n { skipFirst: true },\n );\n const laterals = this.expressions(expression, {\n key: 'laterals',\n sep: '',\n });\n\n let fileFormat = this.sql(expression, 'format');\n if (fileFormat) {\n const pattern = this.sql(expression, 'pattern');\n const patternStr = pattern ? `, PATTERN => ${pattern}` : '';\n fileFormat = ` (FILE_FORMAT => ${fileFormat}${patternStr})`;\n }\n\n let ordinality = expression.args.ordinality || '';\n if (ordinality) {\n ordinality = ` WITH ORDINALITY${alias}`;\n alias = '';\n }\n\n const when = this.sql(expression, 'when');\n let tableStr = table;\n if (when) {\n tableStr = `${table} ${when}`;\n }\n\n let changes = this.sql(expression, 'changes');\n changes = changes ? ` ${changes}` : '';\n\n const rowsFrom = this.expressions(expression, { key: 'rowsFrom' });\n if (rowsFrom) {\n tableStr = `ROWS FROM ${this.wrap(rowsFrom)}`;\n }\n\n const indexed = expression.args.indexed;\n let indexedStr: string;\n if (indexed !== undefined) {\n indexedStr = indexed ? ` INDEXED BY ${this.sql(indexed)}` : ' NOT INDEXED';\n } else {\n indexedStr = '';\n }\n\n return `${only}${tableStr}${changes}${partition}${version}${fileFormat}${samplePreAlias}${alias}${indexedStr}${hints}${pivots}${samplePostAlias}${joins}${laterals}${ordinality}`;\n }\n\n tableFromRowsSql (expression: TableFromRowsExpr): string {\n const table = this.func('TABLE', [expression.args.this]);\n let alias = this.sql(expression, 'alias');\n alias = alias ? ` AS ${alias}` : '';\n const sample = this.sql(expression, 'sample');\n const pivots = this.expressions(expression, {\n key: 'pivots',\n sep: '',\n flat: true,\n });\n const joins = this.indent(\n this.expressions(expression, {\n key: 'joins',\n sep: '',\n flat: true,\n }),\n { skipFirst: true },\n );\n return `${table}${alias}${pivots}${sample}${joins}`;\n }\n\n pivotSql (expression: PivotExpr): string {\n const expressions = this.expressions(expression, { flat: true });\n const direction = expression.args.unpivot ? 'UNPIVOT' : 'PIVOT';\n\n const group = this.sql(expression, 'group');\n\n if (expression.args.this) {\n const thisStr = this.sql(expression, 'this');\n let sql: string;\n if (!expressions) {\n sql = `UNPIVOT ${thisStr}`;\n } else {\n const on = `${this.seg('ON')} ${expressions}`;\n let into = this.sql(expression, 'into');\n into = into ? `${this.seg('INTO')} ${into}` : '';\n let using = this.expressions(expression, {\n key: 'using',\n flat: true,\n });\n using = using ? `${this.seg('USING')} ${using}` : '';\n sql = `${direction} ${thisStr}${on}${into}${using}${group}`;\n }\n return this.prependCtes(expression, sql);\n }\n\n let alias = this.sql(expression, 'alias');\n alias = alias ? ` AS ${alias}` : '';\n\n const fields = this.expressions(\n expression,\n {\n key: 'fields',\n sep: ' ',\n dynamic: true,\n newLine: true,\n skipFirst: true,\n skipLast: true,\n },\n );\n\n const includeNulls = expression.args.includeNulls;\n let nulls: string;\n if (includeNulls !== undefined) {\n nulls = includeNulls ? ' INCLUDE NULLS ' : ' EXCLUDE NULLS ';\n } else {\n nulls = '';\n }\n\n let defaultOnNull = this.sql(expression, 'defaultOnNull');\n defaultOnNull = defaultOnNull ? ` DEFAULT ON NULL (${defaultOnNull})` : '';\n const sql = `${this.seg(direction)}${nulls}(${expressions} FOR ${fields}${defaultOnNull}${group})${alias}`;\n return this.prependCtes(expression, sql);\n }\n\n versionSql (expression: VersionExpr): string {\n const thisStr = `FOR ${expression.name}`;\n const kind = expression.text?.('kind') || '';\n const expr = this.sql(expression, 'expression');\n return `${thisStr} ${kind} ${expr}`;\n }\n\n tupleSql (expression: TupleExpr): string {\n return `(${this.expressions(expression, {\n dynamic: true,\n newLine: true,\n skipFirst: true,\n skipLast: true,\n })})`;\n }\n\n updateFromJoinsSql (expression: UpdateExpr): [string, string] {\n const fromExpr = expression.args.from;\n if ((this.constructor as typeof Generator).UPDATE_STATEMENT_SUPPORTS_FROM || !fromExpr) {\n return ['', this.sql(expression, 'from')];\n }\n\n // Qualify unqualified columns in SET clause with the target table\n const targetTable = expression.args.this;\n if (targetTable instanceof TableExpr) {\n const targetName = toIdentifier(targetTable.aliasOrName);\n for (const eq of expression.args.expressions || []) {\n const col = eq.args.this;\n if (col instanceof ColumnExpr && !col.table) {\n col.setArgKey('table', targetName);\n }\n }\n }\n\n const table = fromExpr.args.this;\n const nestedJoins: Expression[] = (isInstanceOf(table, Expression) ? table.args.joins : undefined) || [];\n if (0 < nestedJoins.length && isInstanceOf(table, Expression)) {\n table.setArgKey('joins', undefined);\n }\n\n let joinSql = isInstanceOf(table, Expression)\n ? this.sql(new JoinExpr({\n this: table,\n on: true_(),\n }))\n : '';\n for (const nested of nestedJoins) {\n if (!nested.getArgKey('on') && !nested.getArgKey('using')) {\n nested.setArgKey('on', true_());\n }\n joinSql += this.sql(nested);\n }\n\n return [joinSql, ''];\n }\n\n updateSql (expression: UpdateExpr): string {\n const thisStr = this.sql(expression, 'this');\n const [joinSql, fromSql] = this.updateFromJoinsSql(expression);\n const setSql = this.expressions(expression, { flat: true });\n const whereSql = this.sql(expression, 'where');\n const returning = this.sql(expression, 'returning');\n const order = this.sql(expression, 'order');\n const limit = this.sql(expression, 'limit');\n let expressionSql: string;\n if (this._constructor.RETURNING_END) {\n expressionSql = `${fromSql}${whereSql}${returning}`;\n } else {\n expressionSql = `${returning}${fromSql}${whereSql}`;\n }\n let options = this.expressions(expression, { key: 'options' });\n options = options ? ` OPTION(${options})` : '';\n const sql = `UPDATE ${thisStr}${joinSql} SET ${setSql}${expressionSql}${order}${limit}${options}`;\n return this.prependCtes(expression, sql);\n }\n\n valuesSql (\n expression: ValuesExpr,\n options: { valuesAsTable?: boolean } = {},\n ): string {\n const { valuesAsTable = true } = options;\n const shouldUseValuesAsTable = valuesAsTable && this._constructor.VALUES_AS_TABLE;\n\n // The VALUES clause is still valid in an `INSERT INTO ..` statement, for example\n if (shouldUseValuesAsTable || !expression.findAncestor<FromExpr | JoinExpr>(FromExpr, JoinExpr)) {\n const args = this.expressions(expression);\n const alias = this.sql(expression, 'alias');\n let values = `VALUES${this.seg('')}${args}`;\n\n const shouldWrap = this._constructor.WRAP_DERIVED_VALUES\n && (alias || [FromExpr, TableExpr].some((cls) => expression.parent instanceof cls));\n\n if (shouldWrap) {\n values = `(${values})`;\n }\n\n values = this.queryModifiers(expression, values);\n return alias ? `${values} AS ${alias}` : values;\n }\n\n // Converts `VALUES...` expression into a series of select unions.\n const aliasNode = isInstanceOf(expression.args.alias, TableAliasExpr) ? expression.args.alias : undefined;\n const columnNames = aliasNode?.columns;\n\n const selects: QueryExpr[] = [];\n\n const expressions = expression.args.expressions;\n if (expressions) {\n for (let i = 0; i < expressions.length; i++) {\n const tup = expressions[i];\n let row = tup.args.expressions as Expression[] || [];\n\n if (i === 0 && columnNames && 0 < columnNames.length) {\n row = row.map((value: Expression, idx: number) => {\n const colName = columnNames[idx];\n return alias(value, colName instanceof IdentifierExpr ? colName : (colName as Expression).name, { copy: false });\n });\n }\n\n selects.push(new SelectExpr({ expressions: row }));\n }\n }\n\n if (selects.length === 0) {\n return '';\n }\n\n if (this.pretty) {\n const query = selects.reduce(\n (x: QueryExpr, y: QueryExpr): QueryExpr => union(\n [x, y],\n {\n distinct: false,\n copy: false,\n },\n ) ?? x,\n );\n const aliasThis = aliasNode?.args.this;\n const aliasExpr = aliasThis instanceof Expression ? aliasThis : undefined;\n return this.subquerySql(query.subquery(aliasExpr, { copy: false }));\n }\n\n const aliasStr = aliasNode ? ` AS ${this.sql(aliasNode, 'this')}` : '';\n const unions = selects.map((s) => this.sql(s)).join(' UNION ALL ');\n return `(${unions})${aliasStr}`;\n }\n\n varSql (expression: VarExpr): string {\n return this.sql(expression, 'this');\n }\n\n intoSql (expression: IntoExpr): string {\n unsupportedArgs.call(this, expression, 'expressions');\n const temporary = expression.args.temporary ? ' TEMPORARY' : '';\n const unlogged = expression.args.unlogged ? ' UNLOGGED' : '';\n\n const modifier = temporary || unlogged;\n\n return `${this.seg('INTO')}${modifier} ${this.sql(expression, 'this')}`;\n }\n\n fromSql (expression: FromExpr): string {\n return `${this.seg('FROM')} ${this.sql(expression, 'this')}`;\n }\n\n groupingSetsSql (expression: GroupingSetsExpr): string {\n const groupingSets = this.expressions(expression, { indent: false });\n return `GROUPING SETS ${this.wrap(groupingSets)}`;\n }\n\n /**\n * Generate SQL for ROLLUP.\n */\n rollupSql (expression: RollupExpr): string {\n const expressions = this.expressions(expression, { indent: false });\n return expressions ? `ROLLUP ${this.wrap(expressions)}` : 'WITH ROLLUP';\n }\n\n rollupIndexSql (expression: RollupIndexExpr): string {\n const thisStr = this.sql(expression, 'this');\n const columns = this.expressions(expression, { flat: true });\n const fromIndex = this.sql(expression, 'fromIndex');\n const fromStr = fromIndex ? ` FROM ${fromIndex}` : '';\n const properties = expression.args.properties;\n if (properties) {\n assertIsInstanceOf(properties, PropertiesExpr);\n }\n const propertiesStr = properties\n ? ` ${this.properties(properties, { prefix: 'PROPERTIES' })}`\n : '';\n return `${thisStr}(${columns})${fromStr}${propertiesStr}`;\n }\n\n rollupPropertySql (expression: RollupPropertyExpr): string {\n return `ROLLUP (${this.expressions(expression, { flat: true })})`;\n }\n\n cubeSql (expression: CubeExpr): string {\n const expressions = this.expressions(expression, { indent: false });\n return expressions ? `CUBE ${this.wrap(expressions)}` : 'WITH CUBE';\n }\n\n groupSql (expression: GroupExpr): string {\n const groupByAll = expression.args.all;\n let modifier = '';\n if (groupByAll === true) {\n modifier = ' ALL';\n } else if (groupByAll === false) {\n modifier = ' DISTINCT';\n }\n\n const groupBy = this.opExpressions(`GROUP BY${modifier}`, expression);\n\n const groupingSets = this.expressions(expression, { key: 'groupingSets' });\n const cube = this.expressions(expression, { key: 'cube' });\n const rollup = this.expressions(expression, { key: 'rollup' });\n\n const groupings = csv(\n [\n groupingSets ? this.seg(groupingSets) : '',\n cube ? this.seg(cube) : '',\n rollup ? this.seg(rollup) : '',\n expression.args.totals ? this.seg('WITH TOTALS') : '',\n ],\n { sep: this._constructor.GROUPINGS_SEP },\n );\n\n let result = groupBy;\n if (expression.args.expressions?.length\n && groupings\n && groupings.trim() !== 'WITH CUBE'\n && groupings.trim() !== 'WITH ROLLUP') {\n result = `${result}${this._constructor.GROUPINGS_SEP}`;\n }\n\n return `${result}${groupings}`;\n }\n\n havingSql (expression: HavingExpr): string {\n const thisStr = this.indent(this.sql(expression, 'this'));\n return `${this.seg('HAVING')}${this.sep()}${thisStr}`;\n }\n\n connectSql (expression: ConnectExpr): string {\n let start = this.sql(expression, 'start');\n start = start ? this.seg(`START WITH ${start}`) : '';\n const nocycle = expression.args.nocycle ? ' NOCYCLE' : '';\n const connect = this.sql(expression, 'connect');\n const connectPart = this.seg(`CONNECT BY${nocycle} ${connect}`);\n return start + connectPart;\n }\n\n priorSql (expression: PriorExpr): string {\n return `PRIOR ${this.sql(expression, 'this')}`;\n }\n\n joinSql (expression: JoinExpr): string {\n const genClass = this._constructor;\n let side: string | undefined;\n\n if (!genClass.SEMI_ANTI_JOIN_WITH_SIDE && (expression.args.kind === JoinExprKind.SEMI || expression.args.kind === JoinExprKind.ANTI)) {\n side = undefined;\n } else {\n side = expression.args.side?.toUpperCase();\n }\n\n const opSql = [\n expression.args.method,\n expression.args.global ? 'GLOBAL' : undefined,\n side,\n expression.args.kind === JoinExprKind.STRAIGHT_JOIN ? 'STRAIGHT_JOIN' : expression.args.kind?.toUpperCase(),\n (expression.args.hint && genClass.JOIN_HINTS) ? expression.args.hint : undefined,\n (expression.args.directed && genClass.DIRECTED_JOINS) ? 'DIRECTED' : undefined,\n ].filter((op) => op).join(' ');\n\n let matchCond = this.sql(expression, 'matchCondition');\n matchCond = matchCond ? ` MATCH_CONDITION (${matchCond})` : '';\n\n let onSql = this.sql(expression, 'on');\n const using = expression.args.using;\n\n if (!onSql && using) {\n onSql = using.map((col) => this.sql(col)).join(', ');\n }\n\n const thisExpr = expression.args.this;\n let thisSql = this.sql(thisExpr);\n\n const exprs = this.expressions(expression);\n if (exprs) {\n thisSql = `${thisSql},${this.seg(exprs)}`;\n }\n\n if (onSql) {\n onSql = this.indent(onSql, { skipFirst: true });\n const space = this.pretty ? this.seg(' '.repeat(this.pad)) : ' ';\n if (using) {\n onSql = `${space}USING (${onSql})`;\n } else {\n onSql = `${space}ON ${onSql}`;\n }\n } else if (!opSql) {\n // Check for Lateral with cross_apply\n if (thisExpr instanceof LateralExpr && thisExpr.args.crossApply !== undefined) {\n return ` ${thisSql}`;\n }\n return `, ${thisSql}`;\n }\n\n const finalOp = expression.args.kind !== JoinExprKind.STRAIGHT_JOIN ? (opSql ? `${opSql} JOIN` : 'JOIN') : opSql;\n\n const pivots = this.expressions(expression, {\n key: 'pivots',\n sep: '',\n flat: true,\n });\n return `${this.seg(finalOp)} ${thisSql}${matchCond}${onSql}${pivots}`;\n }\n\n lambdaSql (\n expression: Expression,\n options: {\n arrowSep?: string;\n wrap?: boolean;\n } = {},\n ): string {\n const {\n arrowSep = '->', wrap = true,\n } = options;\n\n let args = this.expressions(expression, { flat: true });\n args = (wrap && 1 < args.split(',').length) ? `(${args})` : args;\n return `${args} ${arrowSep} ${this.sql(expression, 'this')}`;\n }\n\n lateralOp (expression: LateralExpr): string {\n const crossApply = expression.args.crossApply;\n\n let op = '';\n if (crossApply === true) {\n op = 'INNER JOIN ';\n } else if (crossApply === false) {\n op = 'LEFT JOIN ';\n }\n\n return `${op}LATERAL`;\n }\n\n lateralSql (expression: LateralExpr): string {\n const thisStr = this.sql(expression, 'this');\n\n if (expression.args.view) {\n const alias = expression.args.alias;\n const columns = this.expressions(alias, {\n key: 'columns',\n flat: true,\n });\n const table = alias?.name ? ` ${alias.name}` : '';\n const columnsStr = columns ? ` AS ${columns}` : '';\n const outer = expression.args.outer ? ' OUTER' : '';\n const opSql = this.seg(`LATERAL VIEW${outer}`);\n return `${opSql}${this.sep()}${thisStr}${table}${columnsStr}`;\n }\n\n let alias = this.sql(expression, 'alias');\n alias = alias ? ` AS ${alias}` : '';\n\n let ordinality = expression.args.ordinality || '';\n if (ordinality) {\n ordinality = ` WITH ORDINALITY${alias}`;\n alias = '';\n }\n\n return `${this.lateralOp(expression)} ${thisStr}${alias}${ordinality}`;\n }\n\n limitSql (expression: LimitExpr, options: { top?: boolean } = {}): string {\n const { top = false } = options;\n\n const thisSql = this.sql(expression, 'this');\n\n const args = ['offset', 'expression']\n .map((k) => expression.getArgKey(k))\n .filter(Boolean)\n .map((e) => this._constructor.LIMIT_ONLY_LITERALS ? this.simplifyUnlessLiteral(e as Expression) : e as Expression);\n\n let argsSql = args.map((e) => this.sql(e)).join(', ');\n\n // Handle parentheses for TOP if non-numeric expressions are present\n if (top && args.some((e) => !e.isNumber)) {\n argsSql = `(${argsSql})`;\n }\n\n let expressions = this.expressions(expression, { flat: true });\n const limitOptions = this.sql(expression, 'limitOptions');\n expressions = expressions ? ` BY ${expressions}` : '';\n\n const keyword = top ? 'TOP' : 'LIMIT';\n\n return `${thisSql}${this.seg(keyword)} ${argsSql}${limitOptions}${expressions}`;\n }\n\n offsetSql (expression: OffsetExpr): string {\n const thisStr = this.sql(expression, 'this');\n let value = expression.args.expression as Expression;\n value = this._constructor.LIMIT_ONLY_LITERALS\n ? this.simplifyUnlessLiteral(value)\n : value;\n\n const expressions = this.expressions(expression, { flat: true });\n const exprPart = expressions ? ` BY ${expressions}` : '';\n\n return `${thisStr}${this.seg('OFFSET')} ${this.sql(value)}${exprPart}`;\n }\n\n setItemSql (expression: SetItemExpr): string {\n let kind = this.sql(expression, 'kind');\n if (!this._constructor.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD && kind === 'VARIABLE') {\n kind = '';\n } else {\n kind = kind ? `${kind} ` : '';\n }\n\n const thisStr = this.sql(expression, 'this');\n const expressions = this.expressions(expression);\n let collate = this.sql(expression, 'collate');\n collate = collate ? ` COLLATE ${collate}` : '';\n const global = expression.args.global ? 'GLOBAL ' : '';\n\n return `${global}${kind}${thisStr}${expressions}${collate}`;\n }\n\n setSql (expression: SetExpr): string {\n const expressions = ` ${this.expressions(expression, { flat: true })}`;\n const tag = expression.args.tag ? ' TAG' : '';\n return `${expression.args.unset ? 'UNSET' : 'SET'}${tag}${expressions}`;\n }\n\n queryBandSql (expression: QueryBandExpr): string {\n const thisStr = this.sql(expression, 'this');\n const update = expression.args.update ? ' UPDATE' : '';\n const scopeRaw = this.sql(expression, 'scope');\n const scope = scopeRaw ? ` FOR ${scopeRaw}` : '';\n\n return `QUERY_BAND = ${thisStr}${update}${scope}`;\n }\n\n pragmaSql (expression: PragmaExpr): string {\n return `PRAGMA ${this.sql(expression, 'this')}`;\n }\n\n lockSql (expression: LockExpr): string {\n if (!this._constructor.LOCKING_READS_SUPPORTED) {\n this.unsupported('Locking reads using \\'FOR UPDATE/SHARE\\' are not supported');\n return '';\n }\n\n const updateExpr = expression.args.update;\n const key = expression.args.key;\n let lockType: string;\n\n const update = updateExpr instanceof LiteralExpr ? updateExpr.args.this !== '0' : Boolean(updateExpr);\n if (update) {\n lockType = key ? 'FOR NO KEY UPDATE' : 'FOR UPDATE';\n } else {\n lockType = key ? 'FOR KEY SHARE' : 'FOR SHARE';\n }\n\n const expressions = this.expressions(expression, { flat: true });\n const exprPart = expressions ? ` OF ${expressions}` : '';\n const wait = expression.args.wait;\n\n let waitPart = '';\n if (wait !== undefined) {\n if (wait instanceof LiteralExpr) {\n waitPart = ` WAIT ${this.sql(wait)}`;\n } else {\n waitPart = wait ? ' NOWAIT' : ' SKIP LOCKED';\n }\n }\n\n return `${lockType}${exprPart}${waitPart || ''}`;\n }\n\n literalSql (expression: LiteralExpr): string {\n let text = expression.args.this || '';\n if (expression.isString) {\n text = `${this.dialect._constructor.QUOTE_START}${this.escapeStr(text)}${this.dialect._constructor.QUOTE_END}`;\n }\n return text;\n }\n\n escapeStr (\n text: string,\n options: {\n escapeBackslash?: boolean;\n delimiter?: string;\n escapedDelimiter?: string;\n isByteString?: boolean;\n } = {},\n ): string {\n const {\n escapeBackslash = true,\n delimiter,\n escapedDelimiter,\n isByteString = false,\n } = options;\n\n const supportsEscapeSequences = isByteString\n ? this.dialect._constructor.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES\n : this.dialect._constructor.STRINGS_SUPPORT_ESCAPED_SEQUENCES;\n\n if (supportsEscapeSequences) {\n text = Array.from(text)\n .map((ch) => {\n const escaped = this.dialect._constructor.ESCAPED_SEQUENCES[ch];\n if (escaped !== undefined) {\n return escapeBackslash || ch !== '\\\\' ? escaped : ch;\n }\n return ch;\n })\n .join('');\n }\n\n const delim = delimiter || this.dialect._constructor.QUOTE_END;\n\n return this.replaceLineBreaks(text).replaceAll(delim, escapedDelimiter || this.escapedQuoteEnd);\n }\n\n loadDataSql (expression: LoadDataExpr): string {\n const local = expression.args.local ? ' LOCAL' : '';\n const inpath = ` INPATH ${this.sql(expression, 'inpath')}`;\n const overwrite = expression.args.overwrite ? ' OVERWRITE' : '';\n const thisStr = ` INTO TABLE ${this.sql(expression, 'this')}`;\n const partition = this.sql(expression, 'partition');\n const partitionStr = partition ? ` ${partition}` : '';\n const inputFormat = this.sql(expression, 'inputFormat');\n const inputFormatStr = inputFormat ? ` INPUTFORMAT ${inputFormat}` : '';\n const serde = this.sql(expression, 'serde');\n const serdeStr = serde ? ` SERDE ${serde}` : '';\n return `LOAD DATA${local}${inpath}${overwrite}${thisStr}${partitionStr}${inputFormatStr}${serdeStr}`;\n }\n\n nullSql (..._args: unknown[]): string {\n return 'NULL';\n }\n\n booleanSql (expression: BooleanExpr): string {\n return expression.args.this ? 'TRUE' : 'FALSE';\n }\n\n boolandSql (expression: BoolandExpr): string {\n return `((${this.sql(expression, 'this')}) AND (${this.sql(expression, 'expression')}))`;\n }\n\n boolorSql (expression: BoolorExpr): string {\n return `((${this.sql(expression, 'this')}) OR (${this.sql(expression, 'expression')}))`;\n }\n\n orderSql (expression: OrderExpr, options: { flat?: boolean } = {}): string {\n const { flat = false } = options;\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? `${thisStr} ` : thisStr;\n const siblings = expression.args.siblings ? 'SIBLINGS ' : '';\n return this.opExpressions(`${thisStr}ORDER ${siblings}BY`, expression, { flat: flat || !!thisStr });\n }\n\n withFillSql (expression: WithFillExpr): string {\n let fromSql = this.sql(expression, 'from');\n fromSql = fromSql ? ` FROM ${fromSql}` : '';\n let toSql = this.sql(expression, 'to');\n toSql = toSql ? ` TO ${toSql}` : '';\n let stepSql = this.sql(expression, 'step');\n stepSql = stepSql ? ` STEP ${stepSql}` : '';\n\n const interpolate = expression.args.interpolate;\n const interpolatedValues = interpolate?.map((e) => {\n const isAlias = e instanceof AliasExpr;\n return isAlias\n ? `${this.sql(e, 'alias')} AS ${this.sql(e, 'this')}`\n : this.sql(e, 'this');\n }) || [];\n\n const interpolatePart = interpolatedValues.length\n ? ` INTERPOLATE (${interpolatedValues.join(', ')})`\n : '';\n\n return `WITH FILL${fromSql}${toSql}${stepSql}${interpolatePart}`;\n }\n\n clusterSql (expression: ClusterExpr): string {\n return this.opExpressions('CLUSTER BY', expression);\n }\n\n distributeSql (expression: DistributeExpr): string {\n return this.opExpressions('DISTRIBUTE BY', expression);\n }\n\n sortSql (expression: SortExpr): string {\n return this.opExpressions('SORT BY', expression);\n }\n\n orderedSql (expression: OrderedExpr): string {\n const desc = expression.args.desc;\n const asc = !desc;\n\n const nullsFirst = expression.args.nullsFirst;\n const nullsLast = !nullsFirst;\n const nullsAreLarge = this.dialect._constructor.NULL_ORDERING === NullOrdering.NULLS_ARE_LARGE;\n const nullsAreSmall = this.dialect._constructor.NULL_ORDERING === NullOrdering.NULLS_ARE_SMALL;\n const nullsAreLast = this.dialect._constructor.NULL_ORDERING === NullOrdering.NULLS_ARE_LAST;\n\n let thisStr = this.sql(expression, 'this');\n\n const sortOrder = desc ? ' DESC' : (desc === false ? ' ASC' : '');\n let nullsSortChange = '';\n\n if (nullsFirst && ((asc && nullsAreLarge) || (desc && nullsAreSmall) || nullsAreLast)) {\n nullsSortChange = ' NULLS FIRST';\n } else if (nullsLast && ((asc && nullsAreSmall) || (desc && nullsAreLarge)) && !nullsAreLast) {\n nullsSortChange = ' NULLS LAST';\n }\n\n // If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it\n if (nullsSortChange && this._constructor.NULL_ORDERING_SUPPORTED !== NullOrderingSupported.SUPPORTED) {\n const window = expression.findAncestor<WindowExpr | SelectExpr>(WindowExpr, SelectExpr);\n\n if (window instanceof WindowExpr && window.args.spec) {\n this.unsupported(\n `'${nullsSortChange.trim()}' translation not supported in window functions`,\n );\n nullsSortChange = '';\n } else if (\n this._constructor.NULL_ORDERING_SUPPORTED === NullOrderingSupported.PARTIAL\n && ((asc && nullsSortChange === ' NULLS LAST') || (desc && nullsSortChange === ' NULLS FIRST'))\n ) {\n // BigQuery does not allow these ordering/nulls combinations when used under\n // an aggregation func or under a window containing one\n let ancestor: Expression | undefined = expression.findAncestor<AggFuncExpr | WindowExpr | SelectExpr>(AggFuncExpr, WindowExpr, SelectExpr);\n\n if (ancestor instanceof WindowExpr) {\n ancestor = ancestor.args.this;\n }\n\n if (ancestor instanceof AggFuncExpr) {\n this.unsupported(\n `'${nullsSortChange.trim()}' translation not supported for aggregate functions with ${sortOrder} sort order`,\n );\n nullsSortChange = '';\n }\n } else if (this._constructor.NULL_ORDERING_SUPPORTED === NullOrderingSupported.UNSUPPORTED) {\n if (expression.args.this?.isInteger) {\n this.unsupported(\n `'${nullsSortChange.trim()}' translation not supported with positional ordering`,\n );\n } else if (!(expression.args.this instanceof RandExpr)) {\n const nullSortOrder = nullsSortChange === ' NULLS FIRST' ? ' DESC' : '';\n thisStr = `CASE WHEN ${thisStr} IS NULL THEN 1 ELSE 0 END${nullSortOrder}, ${thisStr}`;\n }\n nullsSortChange = '';\n }\n }\n\n let withFill = this.sql(expression, 'withFill');\n withFill = withFill ? ` ${withFill}` : '';\n\n return `${thisStr}${sortOrder}${nullsSortChange}${withFill}`;\n }\n\n matchRecognizeMeasureSql (expression: MatchRecognizeMeasureExpr): string {\n const windowFrameRaw = this.sql(expression, 'windowFrame');\n const windowFrame = windowFrameRaw ? `${windowFrameRaw} ` : '';\n\n const thisStr = this.sql(expression, 'this');\n\n return `${windowFrame}${thisStr}`;\n }\n\n matchRecognizeSql (expression: MatchRecognizeExpr): string {\n const partition = this.partitionBySql(expression);\n const order = this.sql(expression, 'order');\n const measuresRaw = this.expressions(expression, { key: 'measures' });\n const measures = measuresRaw ? this.seg(`MEASURES${this.seg(measuresRaw)}`) : '';\n const rowsRaw = this.sql(expression, 'rows');\n const rows = rowsRaw ? this.seg(rowsRaw) : '';\n const afterRaw = this.sql(expression, 'after');\n const after = afterRaw ? this.seg(afterRaw) : '';\n const patternRaw = this.sql(expression, 'pattern');\n const pattern = patternRaw ? this.seg(`PATTERN (${patternRaw})`) : '';\n const definitionSqls = (expression.args.define || []).map((definition: Expression) =>\n `${this.sql(definition, 'alias')} AS ${this.sql(definition, 'this')}`);\n const definitions = this.expressions(undefined, { sqls: definitionSqls });\n const define = definitions ? this.seg(`DEFINE${this.seg(definitions)}`) : '';\n const body = [\n partition,\n order,\n measures,\n rows,\n after,\n pattern,\n define,\n ].join('');\n const aliasRaw = this.sql(expression, 'alias');\n const aliasStr = aliasRaw ? ` ${aliasRaw}` : '';\n return `${this.seg('MATCH_RECOGNIZE')} ${this.wrap(body)}${aliasStr}`;\n }\n\n /**\n * Helper methods.\n */\n protected queryModifiers (\n expression: Expression,\n ...sqls: string[]\n ): string {\n let limit = expression.getArgKey('limit');\n\n if (limit !== undefined && !(limit instanceof FetchExpr || limit instanceof LimitExpr)) return '';\n\n // Convert between LIMIT and FETCH based on dialect preference\n const limitFetch = this._constructor.LIMIT_FETCH;\n if (limitFetch === 'LIMIT' && limit instanceof FetchExpr) {\n limit = new LimitExpr({\n expression: maybeCopy(limit.args.count),\n });\n } else if (limitFetch === 'FETCH' && limit instanceof LimitExpr) {\n limit = new FetchExpr({\n direction: 'FIRST',\n count: maybeCopy(limit.args.expression),\n });\n }\n\n return csv(\n [\n ...sqls,\n ...(expression.args.joins?.map((join) => this.sql(join)) ?? []),\n this.sql(expression, 'match'),\n ...(expression.args.laterals?.map((lateral) => this.sql(lateral)) ?? []),\n this.sql(expression, 'prewhere'),\n this.sql(expression, 'where'),\n this.sql(expression, 'connect'),\n this.sql(expression, 'group'),\n this.sql(expression, 'having'),\n ...Array.from(this._constructor.AFTER_HAVING_MODIFIER_TRANSFORMS.values()).map((gen) => gen.call(this, expression)),\n this.sql(expression, 'order'),\n ...this.offsetLimitModifiers(expression, { fetch: limit instanceof FetchExpr }, limit),\n ...this.afterLimitModifiers(expression),\n this.optionsModifier(expression),\n this.forModifiers(expression),\n ],\n { sep: '' },\n );\n }\n\n optionsModifier (expression: Expression): string {\n const options = this.expressions(expression, { key: 'options' });\n return options ? ` ${options}` : '';\n }\n\n forModifiers (expression: Expression): string {\n const forModifiers = this.expressions(expression, { key: 'for' });\n return forModifiers ? `${this.sep()}FOR XML${this.seg(forModifiers)}` : '';\n }\n\n queryOptionSql (_expression: QueryOptionExpr): string {\n this.unsupported('Unsupported query option.');\n return '';\n }\n\n afterLimitModifiers (expression: Expression): string[] {\n let locks = this.expressions(expression, {\n key: 'locks',\n sep: ' ',\n });\n locks = locks ? ` ${locks}` : '';\n return [locks, this.sql(expression, 'sample')];\n }\n\n /**\n * Generate SQL for SELECT.\n */\n selectSql (expression: SelectExpr): string {\n const into = expression.args.into;\n if (!this._constructor.SUPPORTS_SELECT_INTO && into) {\n into.pop();\n }\n\n const hint = this.sql(expression, 'hint');\n let distinct = this.sql(expression, 'distinct');\n distinct = distinct ? ` ${distinct}` : '';\n let kind = this.sql(expression, 'kind');\n\n const limit = expression.args.limit;\n let top = '';\n if (this._constructor.LIMIT_IS_TOP && limit instanceof LimitExpr) {\n top = this.limitSql(limit, { top: true });\n limit.pop();\n }\n\n let expressions = this.expressions(expression);\n\n const genClass = this._constructor;\n if (kind) {\n if (genClass.SELECT_KINDS.includes(kind)) {\n kind = ` AS ${kind}`;\n } else {\n if (kind === 'STRUCT') {\n // Rebuild expressions as Struct with PropertyEQ for aliases\n const exprs = expression.args.expressions as Expression[] || [];\n const structExprs = exprs.map((e) => {\n if (e instanceof AliasExpr) {\n return new PropertyEqExpr({\n this: e.args.alias instanceof IdentifierExpr ? e.args.alias : new IdentifierExpr({ this: e.args.alias?.toString() || '' }),\n expression: e.args.this,\n });\n }\n return e;\n });\n expressions = this.expressions(undefined, {\n sqls: [new StructExpr({ expressions: structExprs })],\n });\n }\n kind = '';\n }\n }\n\n const operationModifiers = this.expressions(expression, {\n key: 'operationModifiers',\n sep: ' ',\n });\n const operationModifiersPart = operationModifiers ? `${this.sep()}${operationModifiers}` : '';\n\n const topDistinct = genClass.LIMIT_IS_TOP ? `${distinct}${hint}${top}` : `${top}${hint}${distinct}`;\n expressions = expressions ? `${this.sep()}${expressions}` : expressions;\n\n let sql = this.queryModifiers(\n expression,\n `SELECT${topDistinct}${operationModifiersPart}${kind}${expressions}`,\n this.sql(expression, 'into', { comment: false }),\n this.sql(expression, 'from', { comment: false }),\n );\n\n sql = this.prependCtes(expression, sql);\n\n if (!genClass.SUPPORTS_SELECT_INTO && into) {\n assertIsInstanceOf(into, IntoExpr);\n let tableKind = '';\n if (into.args.temporary) {\n tableKind = ' TEMPORARY';\n } else if (genClass.SUPPORTS_UNLOGGED_TABLES && into.args.unlogged) {\n tableKind = ' UNLOGGED';\n }\n sql = `CREATE${tableKind} TABLE ${this.sql(into.args.this)} AS ${sql}`;\n }\n\n return sql;\n }\n\n /**\n * Generate SQL for schema.\n */\n schemaSql (expression: SchemaExpr): string {\n const thisStr = this.sql(expression, 'this');\n const sql = this.schemaColumnsSql(expression);\n return (thisStr && sql) ? `${thisStr} ${sql}` : (thisStr || sql);\n }\n\n /**\n * Generate SQL for schema columns.\n */\n schemaColumnsSql (expression: SchemaExpr): string {\n if (expression.args.expressions?.length) {\n return `(${this.sep('')}${this.expressions(expression)}${this.seg(')', '')}`;\n }\n return '';\n }\n\n starSql (expression: StarExpr): string {\n let except = this.expressions(expression, {\n key: 'except',\n flat: true,\n });\n except = except ? `${this.seg(this._constructor.STAR_EXCEPT)} (${except})` : '';\n let replace = this.expressions(expression, {\n key: 'replace',\n flat: true,\n });\n replace = replace ? `${this.seg('REPLACE')} (${replace})` : '';\n let rename = this.expressions(expression, {\n key: 'rename',\n flat: true,\n });\n rename = rename ? `${this.seg('RENAME')} (${rename})` : '';\n return `*${except}${replace}${rename}`;\n }\n\n parameterSql (expression: ParameterExpr): string {\n const thisStr = this.sql(expression, 'this');\n return `${this._constructor.PARAMETER_TOKEN}${thisStr}`;\n }\n\n sessionParameterSql (expression: SessionParameterExpr): string {\n const thisStr = this.sql(expression, 'this');\n let kind = expression.text('kind');\n if (kind) {\n kind = `${kind}.`;\n }\n return `@@${kind}${thisStr}`;\n }\n\n placeholderSql (expression: PlaceholderExpr): string {\n return expression.args.this !== undefined\n ? `${this._constructor.NAMED_PLACEHOLDER_TOKEN}${expression.name}`\n : '?';\n }\n\n subquerySql (expression: SubqueryExpr, options: { sep?: string } = {}): string {\n const { sep = ' AS ' } = options;\n let alias = this.sql(expression, 'alias');\n alias = alias ? `${sep}${alias}` : '';\n const sample = this.sql(expression, 'sample');\n\n if (this.dialect._constructor.ALIAS_POST_TABLESAMPLE && sample) {\n alias = `${sample}${alias}`;\n expression.setArgKey('sample', undefined);\n }\n\n const pivots = this.expressions(expression, {\n key: 'pivots',\n sep: '',\n flat: true,\n });\n const sql = this.queryModifiers(expression, this.wrap(expression), alias, pivots);\n return this.prependCtes(expression, sql);\n }\n\n qualifySql (expression: QualifyExpr): string {\n const thisStr = this.indent(this.sql(expression, 'this'));\n return `${this.seg('QUALIFY')}${this.sep()}${thisStr}`;\n }\n\n unnestSql (expression: UnnestExpr): string {\n const args = this.expressions(expression, { flat: true });\n\n const alias = expression.args.alias;\n const offset = expression.args.offset;\n\n if (this._constructor.UNNEST_WITH_ORDINALITY) {\n if (alias && offset instanceof IdentifierExpr) {\n assertIsInstanceOf(alias, TableAliasExpr);\n // Append offset to alias columns\n if (alias.args.columns) {\n alias.args.columns.push(offset);\n } else {\n alias.setArgKey('columns', [offset]);\n }\n }\n }\n\n let aliasStr = '';\n if (alias && this.dialect._constructor.UNNEST_COLUMN_ONLY) {\n assertIsInstanceOf(alias, TableAliasExpr);\n const columns = alias.columns;\n aliasStr = columns[0] ? this.sql(columns[0]) : '';\n } else {\n aliasStr = this.sql(alias);\n }\n\n aliasStr = aliasStr ? ` AS ${aliasStr}` : aliasStr;\n let suffix = '';\n if (this._constructor.UNNEST_WITH_ORDINALITY) {\n suffix = offset ? ` WITH ORDINALITY${aliasStr}` : aliasStr;\n } else {\n if (offset instanceof Expression) {\n suffix = `${aliasStr} WITH OFFSET AS ${this.sql(offset)}`;\n } else if (offset) {\n suffix = `${aliasStr} WITH OFFSET`;\n } else {\n suffix = aliasStr;\n }\n }\n\n return `UNNEST(${args})${suffix}`;\n }\n\n preWhereSql (_expression: Expression): string {\n return '';\n }\n\n whereSql (expression: WhereExpr): string {\n const thisStr = this.indent(this.sql(expression, 'this'));\n return `${this.seg('WHERE')}${this.sep()}${thisStr}`;\n }\n\n windowSql (expression: WindowExpr): string {\n let thisStr = this.sql(expression, 'this');\n const partition = this.partitionBySql(expression);\n const order = expression.args.order;\n if (order) {\n assertIsInstanceOf(order, OrderExpr);\n }\n const orderStr = order ? this.orderSql(order, { flat: true }) : '';\n const spec = this.sql(expression, 'spec');\n const alias = this.sql(expression, 'alias');\n const over = this.sql(expression, 'over') || 'OVER';\n\n thisStr = `${thisStr} ${expression.argKey === 'windows' ? 'AS' : over}`;\n\n const first = expression.args.first;\n let firstStr = '';\n if (first !== undefined) {\n firstStr = first ? 'FIRST' : 'LAST';\n }\n\n if (!partition && !orderStr && !spec && alias) {\n return `${thisStr} ${alias}`;\n }\n\n const args = this.formatArgs(\n [\n alias,\n firstStr,\n partition,\n orderStr,\n spec,\n ].filter((arg) => arg),\n { sep: ' ' },\n );\n\n return `${thisStr} (${args})`;\n }\n\n partitionBySql (expression: WindowExpr | MatchRecognizeExpr): string {\n const partition = this.expressions(expression, {\n key: 'partitionBy',\n flat: true,\n });\n return partition ? `PARTITION BY ${partition}` : '';\n }\n\n windowSpecSql (expression: WindowSpecExpr): string {\n const kind = this.sql(expression, 'kind');\n const start = csv(\n [this.sql(expression, 'start'), this.sql(expression, 'startSide')],\n { sep: ' ' },\n );\n const end = csv(\n [this.sql(expression, 'end'), this.sql(expression, 'endSide')],\n { sep: ' ' },\n ) || 'CURRENT ROW';\n\n let windowSpec = `${kind} BETWEEN ${start} AND ${end}`;\n\n const exclude = this.sql(expression, 'exclude');\n if (exclude) {\n if (this._constructor.SUPPORTS_WINDOW_EXCLUDE) {\n windowSpec += ` EXCLUDE ${exclude}`;\n } else {\n this.unsupported('EXCLUDE clause is not supported in the WINDOW clause');\n }\n }\n\n return windowSpec;\n }\n\n withinGroupSql (expression: WithinGroupExpr): string {\n const thisStr = this.sql(expression, 'this');\n let expressionSql = this.sql(expression, 'expression');\n expressionSql = expressionSql.substring(1); // order has a leading space\n return `${thisStr} WITHIN GROUP (${expressionSql})`;\n }\n\n betweenSql (expression: BetweenExpr): string {\n const thisStr = this.sql(expression, 'this');\n const low = this.sql(expression, 'low');\n const high = this.sql(expression, 'high');\n const symmetric = expression.args.symmetric;\n\n if (symmetric && !this._constructor.SUPPORTS_BETWEEN_FLAGS) {\n return `(${thisStr} BETWEEN ${low} AND ${high} OR ${thisStr} BETWEEN ${high} AND ${low})`;\n }\n\n const flag = symmetric\n ? ' SYMMETRIC'\n : (symmetric === false && this._constructor.SUPPORTS_BETWEEN_FLAGS)\n ? ' ASYMMETRIC'\n : '';\n\n return `${thisStr} BETWEEN${flag} ${low} AND ${high}`;\n }\n\n bracketOffsetExpressions (\n expression: BracketExpr,\n options: { indexOffset?: number } = {},\n ): Expression[] {\n const { indexOffset } = options;\n\n const offset = (indexOffset !== undefined ? indexOffset : this.dialect._constructor.INDEX_OFFSET) - (expression.args.offset || 0);\n // Call apply_index_offset helper (assumed to exist)\n const bracketThis = expression.args.this instanceof Expression ? expression.args.this : new Expression({});\n return applyIndexOffset(bracketThis, expression.args.expressions || [], offset, { dialect: this.dialect });\n }\n\n bracketSql (expression: BracketExpr): string {\n const expressions = this.bracketOffsetExpressions(expression);\n const expressionsSql = expressions.map((e) => this.sql(e)).join(', ');\n return `${this.sql(expression, 'this')}[${expressionsSql}]`;\n }\n\n allSql (expression: AllExpr): string {\n let thisStr = this.sql(expression, 'this');\n const thisExpr = expression.args.this;\n if (thisExpr && !(thisExpr instanceof TupleExpr || thisExpr instanceof ParenExpr)) {\n thisStr = this.wrap(thisStr);\n }\n return `ALL ${thisStr}`;\n }\n\n anySql (expression: AnyExpr): string {\n let thisStr = this.sql(expression, 'this');\n const thisExpr = expression.args.this;\n\n // UNWRAPPED_QUERIES are Select and SetOperation expressions\n if (UNWRAPPED_QUERIES.some((cls) => thisExpr instanceof cls) || thisExpr instanceof ParenExpr) {\n if (UNWRAPPED_QUERIES.some((cls) => thisExpr instanceof cls)) {\n thisStr = this.wrap(thisStr);\n }\n return `ANY${thisStr}`;\n }\n\n return `ANY ${thisStr}`;\n }\n\n existsSql (expression: ExistsExpr): string {\n return `EXISTS${this.wrap(expression)}`;\n }\n\n caseSql (expression: CaseExpr): string {\n const thisStr = this.sql(expression, 'this');\n const statements: string[] = [thisStr ? `CASE ${thisStr}` : 'CASE'];\n\n const ifs = expression.args.ifs as Expression[] | undefined;\n if (ifs) {\n for (const e of ifs) {\n statements.push(`WHEN ${this.sql(e, 'this')}`);\n statements.push(`THEN ${this.sql(e, 'true')}`);\n }\n }\n\n const defaultStr = this.sql(expression, 'default');\n if (defaultStr) {\n statements.push(`ELSE ${defaultStr}`);\n }\n\n statements.push('END');\n\n if (this.pretty && this.tooWide(statements)) {\n return this.indent(statements.join('\\n'), {\n skipFirst: true,\n skipLast: true,\n });\n }\n\n return statements.join(' ');\n }\n\n constraintSql (expression: ConstraintExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expressions = this.expressions(expression, { flat: true });\n return `CONSTRAINT ${thisStr} ${expressions}`;\n }\n\n nextValueForSql (expression: NextValueForExpr): string {\n const order = expression.args.order;\n if (order) {\n assertIsInstanceOf(order, OrderExpr);\n }\n const orderStr = order ? ` OVER (${this.orderSql(order, { flat: true })})` : '';\n return `NEXT VALUE FOR ${this.sql(expression, 'this')}${orderStr}`;\n }\n\n extractSql (expression: ExtractExpr): string {\n let thisExpr = expression.args.this as Expression;\n\n if (this._constructor.NORMALIZE_EXTRACT_DATE_PARTS) {\n thisExpr = mapDatePart(thisExpr, { dialect: this.dialect }) || thisExpr;\n }\n\n const thisSql = this._constructor.EXTRACT_ALLOWS_QUOTES ? this.sql(thisExpr) : thisExpr.name;\n const expressionSql = this.sql(expression, 'expression');\n\n return `EXTRACT(${thisSql} FROM ${expressionSql})`;\n }\n\n trimSql (expression: TrimExpr): string {\n const trimType = this.sql(expression, 'position');\n\n let funcName: string;\n if (trimType === 'LEADING') {\n funcName = 'LtRIM';\n } else if (trimType === 'TRAILING') {\n funcName = 'RTRIM';\n } else {\n funcName = 'TRIM';\n }\n\n return this.func(funcName, [expression.args.this, expression.args.expression]);\n }\n\n convertConcatArgs (expression: ConcatExpr | ConcatWsExpr): Expression[] {\n let args = expression.args.expressions || [];\n if (expression instanceof ConcatWsExpr) {\n args = args.slice(1); // Skip the delimiter\n }\n\n if (this.dialect._constructor.STRICT_STRING_CONCAT && expression.args.safe) {\n args = args.map((e) => cast(e, DataTypeExprKind.TEXT, { dialect: this.dialect }));\n }\n\n if (!this.dialect._constructor.CONCAT_COALESCE && expression.args.coalesce) {\n const wrapWithCoalesce = (e: Expression): Expression => {\n if (!e.type) {\n e = annotateTypes(e, { dialect: this.dialect });\n }\n\n if (e.isString || e.isType(DataTypeExprKind.ARRAY)) {\n return e;\n }\n\n return new CoalesceExpr({\n this: e,\n expressions: [\n new LiteralExpr({\n this: '',\n isString: true,\n }),\n ],\n });\n };\n\n args = args.map(wrapWithCoalesce);\n }\n\n return args;\n }\n\n concatSql (expression: ConcatExpr): string {\n if (this.dialect._constructor.CONCAT_COALESCE && !expression.args.coalesce) {\n // Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.\n // Transpile to double pipe operators, which typically returns NULL if any args are NULL\n // instead of coalescing them to empty string.\n return concatToDPipeSql.call(this, expression);\n }\n\n const expressions = this.convertConcatArgs(expression);\n\n // Some dialects don't allow a single-argument CONCAT call\n if (!this._constructor.SUPPORTS_SINGLE_ARG_CONCAT && expressions.length === 1) {\n return this.sql(expressions[0]);\n }\n\n return this.func('CONCAT', expressions);\n }\n\n concatWsSql (expression: ConcatWsExpr): string {\n const expressions = expression.args.expressions || [];\n const delimiter = expressions[0];\n const args = this.convertConcatArgs(expression);\n return this.func('CONCAT_WS', [delimiter, ...args]);\n }\n\n checkSql (expression: CheckExpr): string {\n const thisStr = this.sql(expression, 'this');\n return `CHECK (${thisStr})`;\n }\n\n foreignKeySql (expression: ForeignKeyExpr): string {\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? ` (${expressions})` : '';\n let reference = this.sql(expression, 'reference');\n reference = reference ? ` ${reference}` : '';\n let deleteSql = this.sql(expression, 'delete');\n deleteSql = deleteSql ? ` ON DELETE ${deleteSql}` : '';\n let update = this.sql(expression, 'update');\n update = update ? ` ON UPDATE ${update}` : '';\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `FOREIGN KEY${expressions}${reference}${deleteSql}${update}${options}`;\n }\n\n primaryKeySql (expression: PrimaryKeyExpr): string {\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n const expressions = this.expressions(expression, { flat: true });\n const include = this.sql(expression, 'include');\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `PRIMARY KEY${thisStr} (${expressions})${include}${options}`;\n }\n\n ifSql (expression: IfExpr): string {\n if (expression.parent instanceof CaseExpr) {\n return `WHEN ${this.sql(expression, 'this')} THEN ${this.sql(expression, 'true')}`;\n }\n return this.caseSql(new CaseExpr({\n ifs: [expression],\n default: expression.args.false,\n }));\n }\n\n matchAgainstSql (expression: MatchAgainstExpr): string {\n let exprs: (string | Expression)[] = expression.args.expressions || [];\n\n if (this._constructor.MATCH_AGAINST_TABLE_PREFIX) {\n exprs = exprs.map((expr) => {\n if (expr instanceof TableExpr) {\n return `TABLE ${this.sql(expr)}`;\n }\n return expr;\n });\n } else {\n exprs = expression.args.expressions || [];\n }\n\n const modifier = expression.args.modifier;\n const modifierPart = modifier ? ` ${modifier}` : '';\n\n return `${this.func('MATCH', exprs)} AGAINST(${this.sql(expression, 'this')}${modifierPart})`;\n }\n\n jsonKeyValueSql (expression: JsonKeyValueExpr): string {\n return `${this.sql(expression, 'this')}${this._constructor.JSON_KEY_VALUE_PAIR_SEP} ${this.sql(expression, 'expression')}`;\n }\n\n jsonPathSql (expression: JsonPathExpr): string {\n let path = this.expressions(expression, {\n sep: '',\n flat: true,\n }).replace(/^\\.+/, '');\n\n if (expression.args.escape) {\n path = this.escapeStr(path);\n }\n\n if (this._constructor.QUOTE_JSON_PATH) {\n path = `${this.dialect._constructor.QUOTE_START}${path}${this.dialect._constructor.QUOTE_END}`;\n }\n\n return path;\n }\n\n jsonPathPart (expression?: ExpressionValue<JsonPathPartExpr>): string {\n if (expression === undefined) {\n return '';\n }\n\n if (expression instanceof JsonPathPartExpr) {\n const transform = this._constructor.TRANSFORMS.get(expression._constructor);\n if (typeof transform !== 'function') {\n this.unsupported(`Unsupported JsonPathPart type ${expression._constructor.name}`);\n return '';\n }\n return transform.call(this, expression);\n }\n if (typeof expression === 'number') {\n return String(expression);\n }\n if (this.quoteJsonPathKeyUsingBrackets && this._constructor.JSON_PATH_SINGLE_QUOTE_ESCAPE) {\n const escaped = expression.toString().replaceAll('\\'', '\\\\\\'');\n return `\\\\'${escaped}\\\\'`;\n }\n const escaped = expression.toString().replace(/\"/g, '\\\\\"');\n return `\"${escaped}\"`;\n }\n\n formatJsonSql (expression: FormatJsonExpr): string {\n return `${this.sql(expression, 'this')} FORMAT JSON`;\n }\n\n formatPhraseSql (expression: FormatPhraseExpr): string {\n const thisStr = this.sql(expression, 'this');\n const fmt = this.sql(expression, 'format');\n return `${thisStr} (FORMAT ${fmt})`;\n }\n\n jsonObjectSql (expression: JsonObjectExpr | JsonObjectAggExpr): string {\n const nullHandling = expression.args.nullHandling;\n const nullHandlingStr = nullHandling ? ` ${nullHandling}` : '';\n\n const uniqueKeys = expression.args.uniqueKeys;\n let uniqueKeysStr = '';\n if (uniqueKeys !== undefined && uniqueKeys !== null) {\n uniqueKeysStr = ` ${uniqueKeys ? 'WITH' : 'WITHOUT'} UNIQUE KEYS`;\n }\n\n const returnType = this.sql(expression, 'returnType');\n const returnTypeStr = returnType ? ` RETURNING ${returnType}` : '';\n const encoding = this.sql(expression, 'encoding');\n const encodingStr = encoding ? ` ENCODING ${encoding}` : '';\n\n const funcName = expression instanceof JsonObjectExpr ? 'JSON_OBJECT' : 'JSON_OBJECTAGG';\n return this.func(\n funcName,\n expression.args.expressions || [],\n { suffix: `${nullHandlingStr}${uniqueKeysStr}${returnTypeStr}${encodingStr})` },\n );\n }\n\n jsonObjectAggSql (expression: JsonObjectAggExpr): string {\n return this.jsonObjectSql(expression);\n }\n\n jsonArraySql (expression: JsonArrayExpr): string {\n const nullHandling = expression.args.nullHandling;\n const nullHandlingStr = nullHandling ? ` ${nullHandling}` : '';\n const returnType = this.sql(expression, 'returnType');\n const returnTypeStr = returnType ? ` RETURNING ${returnType}` : '';\n const strict = expression.args.strict ? ' STRICT' : '';\n return this.func(\n 'JSON_ARRAY',\n expression.args.expressions || [],\n { suffix: `${nullHandlingStr}${returnTypeStr}${strict})` },\n );\n }\n\n jsonArrayAggSql (expression: JsonArrayAggExpr): string {\n const thisStr = this.sql(expression, 'this');\n const order = this.sql(expression, 'order');\n const nullHandling = expression.args.nullHandling;\n const nullHandlingStr = nullHandling ? ` ${nullHandling}` : '';\n const returnType = this.sql(expression, 'returnType');\n const returnTypeStr = returnType ? ` RETURNING ${returnType}` : '';\n const strict = expression.args.strict ? ' STRICT' : '';\n return this.func(\n 'JSON_ARRAYAGG',\n [thisStr],\n { suffix: `${order}${nullHandlingStr}${returnTypeStr}${strict})` },\n );\n }\n\n jsonColumnDefSql (expression: JsonColumnDefExpr): string {\n const path = this.sql(expression, 'path');\n const pathStr = path ? ` PATH ${path}` : '';\n const nestedSchema = this.sql(expression, 'nestedSchema');\n\n if (nestedSchema) {\n return `NESTED${pathStr} ${nestedSchema}`;\n }\n\n const thisStr = this.sql(expression, 'this');\n const kind = this.sql(expression, 'kind');\n const kindStr = kind ? ` ${kind}` : '';\n\n const ordinality = expression.args.ordinality ? ' FOR ORDINALITY' : '';\n return `${thisStr}${kindStr}${pathStr}${ordinality}`;\n }\n\n jsonSchemaSql (expression: JsonSchemaExpr): string {\n return this.func('COLUMNS', expression.args.expressions || []);\n }\n\n jsonTableSql (expression: JsonTableExpr): string {\n const thisStr = this.sql(expression, 'this');\n const path = this.sql(expression, 'path');\n const pathStr = path ? `, ${path}` : '';\n const errorHandling = expression.args.errorHandling;\n const errorHandlingStr = errorHandling ? ` ${errorHandling}` : '';\n const emptyHandling = expression.args.emptyHandling;\n const emptyHandlingStr = emptyHandling ? ` ${emptyHandling}` : '';\n const schema = this.sql(expression, 'schema');\n return this.func(\n 'JSON_TABLE',\n [thisStr],\n { suffix: `${pathStr}${errorHandlingStr}${emptyHandlingStr} ${schema})` },\n );\n }\n\n openJsonColumnDefSql (expression: OpenJsonColumnDefExpr): string {\n const thisStr = this.sql(expression, 'this');\n const kind = this.sql(expression, 'kind');\n const path = this.sql(expression, 'path');\n const pathStr = path ? ` ${path}` : '';\n const asJson = expression.args.asJson ? ' AS JSON' : '';\n return `${thisStr} ${kind}${pathStr}${asJson}`;\n }\n\n openJsonSql (expression: OpenJsonExpr): string {\n const thisStr = this.sql(expression, 'this');\n const path = this.sql(expression, 'path');\n const pathStr = path ? `, ${path}` : '';\n const expressions = this.expressions(expression);\n const withStr = expressions\n ? ` WITH (${this.seg(this.indent(expressions), '')}${this.seg(')', '')}`\n : '';\n return `OPENJSON(${thisStr}${pathStr})${withStr}`;\n }\n\n inSql (expression: InExpr): string {\n const query = expression.args.query;\n const unnest = expression.args.unnest;\n const field = expression.args.field;\n const isGlobal = expression.args.isGlobal ? ' GLOBAL' : '';\n\n let inSql: string;\n\n if (query) {\n inSql = this.sql(query);\n } else if (unnest) {\n inSql = this.inUnnestOp(unnest);\n } else if (field) {\n inSql = this.sql(field);\n } else {\n const options = {\n dynamic: true,\n newLine: true,\n skipFirst: true,\n skipLast: true,\n };\n inSql = `(${this.expressions(expression, options)})`;\n }\n\n return `${this.sql(expression, 'this')}${isGlobal} IN ${inSql}`;\n }\n\n inUnnestOp (unnest: Expression): string {\n return `(SELECT ${this.sql(unnest)})`;\n }\n\n intervalSql (expression: IntervalExpr): string {\n const unitExpression = expression.args.unit;\n let unit = unitExpression ? this.sql(unitExpression) : '';\n if (!this._constructor.INTERVAL_ALLOWS_PLURAL_FORM) {\n unit = this._constructor.TIME_PART_SINGULARS[unit] || unit;\n }\n const unitStr = unit ? ` ${unit}` : '';\n\n if (this._constructor.SINGLE_STRING_INTERVAL) {\n const thisName = expression.args.this instanceof Expression ? expression.args.this?.name || '' : expression.args.this;\n if (thisName) {\n if (unitExpression instanceof IntervalSpanExpr) {\n return `INTERVAL '${thisName}'${unitStr}`;\n }\n return `INTERVAL '${thisName}${unitStr}'`;\n }\n return `INTERVAL${unitStr}`;\n }\n\n let thisStr = this.sql(expression, 'this');\n if (thisStr) {\n const unwrapped = expression.args.this instanceof Expression && this._constructor.UNWRAPPED_INTERVAL_VALUES.has(expression.args.this._constructor);\n thisStr = unwrapped ? ` ${thisStr}` : ` (${thisStr})`;\n }\n\n return `INTERVAL${thisStr}${unitStr}`;\n }\n\n returnSql (expression: ReturnExpr): string {\n return `RETURN ${this.sql(expression, 'this')}`;\n }\n\n referenceSql (expression: ReferenceExpr): string {\n const thisStr = this.sql(expression, 'this');\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? `(${expressions})` : '';\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `REFERENCES ${thisStr}${expressions}${options}`;\n }\n\n anonymousSql (expression: AnonymousExpr): string {\n const parent = expression.parent;\n const isQualified = parent instanceof DotExpr && parent.args.expression === expression;\n return this.func(\n this.sql(expression, 'this'),\n expression.args.expressions || [],\n { normalize: !isQualified },\n );\n }\n\n parenSql (expression: ParenExpr): string {\n const sql = this.seg(this.indent(this.sql(expression, 'this')), '');\n return `(${sql}${this.seg(')', '')}`;\n }\n\n negSql (expression: NegExpr): string {\n const thisSql = this.sql(expression, 'this');\n // Avoid converting \"- -5\" to \"--5\" which is a comment\n const sep = thisSql[0] === '-'\n ? ' '\n : '';\n return `-${sep}${thisSql}`;\n }\n\n notSql (expression: NotExpr): string {\n return `NOT ${this.sql(expression, 'this')}`;\n }\n\n aliasSql (expression: AliasExpr): string {\n let alias = this.sql(expression, 'alias');\n alias = alias ? ` AS ${alias}` : '';\n return `${this.sql(expression, 'this')}${alias}`;\n }\n\n pivotAliasSql (expression: PivotAliasExpr): string {\n const alias = expression.args.alias;\n\n const parent = expression.parent;\n const pivot = parent && parent.parent;\n\n if (pivot instanceof PivotExpr && pivot.unpivot) {\n const identifierAlias = alias instanceof IdentifierExpr;\n const literalAlias = alias instanceof LiteralExpr;\n\n if (identifierAlias && !this._constructor.UNPIVOT_ALIASES_ARE_IDENTIFIERS) {\n alias.replace(new LiteralExpr({\n this: (alias as Expression).name,\n isString: true,\n }));\n } else if (!identifierAlias && literalAlias && this._constructor.UNPIVOT_ALIASES_ARE_IDENTIFIERS) {\n alias.replace(new IdentifierExpr({\n this: (alias as Expression).name,\n }));\n }\n }\n\n return this.aliasSql(expression);\n }\n\n aliasesSql (expression: AliasesExpr): string {\n return `${this.sql(expression, 'this')} AS (${this.expressions(expression, { flat: true })})`;\n }\n\n atIndexSql (expression: AtIndexExpr): string {\n const thisStr = this.sql(expression, 'this');\n const index = this.sql(expression, 'expression');\n return `${thisStr} AT ${index}`;\n }\n\n atTimeZoneSql (expression: AtTimeZoneExpr): string {\n const thisStr = this.sql(expression, 'this');\n const zone = this.sql(expression, 'zone');\n return `${thisStr} AT TIME ZONE ${zone}`;\n }\n\n fromTimeZoneSql (expression: FromTimeZoneExpr): string {\n const thisStr = this.sql(expression, 'this');\n const zone = this.sql(expression, 'zone');\n return `${thisStr} AT TIME ZONE ${zone} AT TIME ZONE 'UTC'`;\n }\n\n addSql (expression: AddExpr): string {\n return this.binary(expression, '+');\n }\n\n andSql (expression: AndExpr, stack?: (string | Expression)[]): string {\n return this.connectorSql(expression, 'AND', stack);\n }\n\n orSql (expression: OrExpr, stack?: (string | Expression)[]): string {\n return this.connectorSql(expression, 'OR', stack);\n }\n\n xorSql (expression: XorExpr, stack?: (string | Expression)[]): string {\n return this.connectorSql(expression, 'XOR', stack);\n }\n\n connectorSql (\n expression: ConnectorExpr,\n op: string,\n stack?: (string | Expression)[],\n ): string {\n if (stack !== undefined) {\n if (expression.args.expressions && 0 < expression.args.expressions.length) {\n stack.push(this.expressions(expression, { sep: ` ${op} ` }));\n } else {\n stack.push(expression.right || '');\n if (expression.comments && this.comments) {\n for (const comment of expression.comments) {\n if (comment) {\n op += ` /*${this.sanitizeComment(comment)}*/`;\n }\n }\n }\n stack.push(op, expression.left || '');\n }\n return op;\n }\n\n const localStack: (string | Expression)[] = [expression];\n const sqls: string[] = [];\n const ops = new Set<string>();\n\n while (0 < localStack.length) {\n const node = localStack.pop()!;\n\n if (node instanceof ConnectorExpr) {\n const methodName = `${node._constructor.key}Sql` as keyof this;\n const method = this[methodName];\n\n if (typeof method === 'function') {\n ops.add(method.call(this, node, localStack));\n }\n } else {\n const sql = this.sql(node);\n const lastSql = sqls[sqls.length - 1];\n\n if (0 < sqls.length && ops.has(lastSql)) {\n sqls[sqls.length - 1] += ` ${sql}`;\n } else {\n sqls.push(sql);\n }\n }\n }\n\n const sep = this.pretty && this.tooWide(sqls) ? '\\n' : ' ';\n return sqls.join(sep);\n }\n\n bitwiseAndSql (expression: BitwiseAndExpr): string {\n return this.binary(expression, '&');\n }\n\n bitwiseLeftShiftSql (expression: BitwiseLeftShiftExpr): string {\n return this.binary(expression, '<<');\n }\n\n bitwiseNotSql (expression: BitwiseNotExpr): string {\n return `~${this.sql(expression, 'this')}`;\n }\n\n bitwiseOrSql (expression: BitwiseOrExpr): string {\n return this.binary(expression, '|');\n }\n\n bitwiseRightShiftSql (expression: BitwiseRightShiftExpr): string {\n return this.binary(expression, '>>');\n }\n\n bitwiseXorSql (expression: BitwiseXorExpr): string {\n return this.binary(expression, '^');\n }\n\n castSql (expression: Expression, options: { safePrefix?: string } = {}): string {\n const { safePrefix } = options;\n let formatSql = this.sql(expression, 'format');\n formatSql = formatSql ? ` FORMAT ${formatSql}` : '';\n let toSql = this.sql(expression, 'to');\n toSql = toSql ? ` ${toSql}` : '';\n let action = this.sql(expression, 'action');\n action = action ? ` ${action}` : '';\n let defaultSql = this.sql(expression, 'default');\n defaultSql = defaultSql ? ` DEFAULT ${defaultSql} ON CONVERSION ERROR` : '';\n return `${safePrefix || ''}CAST(${this.sql(expression, 'this')} AS${toSql}${defaultSql}${formatSql}${action})`;\n }\n\n strToTimeSql (expression: StrToTimeExpr): string {\n return this.func('STR_TO_TIME', [expression.args.this ?? '', expression.args.format]);\n }\n\n currentDateSql (expression: CurrentDateExpr): string {\n const zone = this.sql(expression, 'this');\n return zone ? `CURRENT_DATE(${zone})` : 'CURRENT_DATE';\n }\n\n collateSql (expression: CollateExpr): string {\n if (this._constructor.COLLATE_IS_FUNC) {\n return this.functionFallbackSql(expression);\n }\n return this.binary(expression, 'COLLATE');\n }\n\n commandSql (expression: CommandExpr): string {\n const thisStr = this.sql(expression, 'this');\n const exprText = expression.text('expression').trim();\n return `${thisStr} ${exprText}`;\n }\n\n commentSql (expression: CommentExpr): string {\n const thisStr = this.sql(expression, 'this');\n const kind = expression.args.kind;\n const materialized = expression.args.materialized ? ' MATERIALIZED' : '';\n const existsSql = expression.args.exists ? ' IF EXISTS ' : ' ';\n const expressionSql = this.sql(expression, 'expression');\n return `COMMENT${existsSql}ON${materialized} ${kind} ${thisStr} IS ${expressionSql}`;\n }\n\n mergeTreeTtlActionSql (expression: MergeTreeTtlActionExpr): string {\n const thisStr = this.sql(expression, 'this');\n const del = expression.args.delete ? ' DELETE' : '';\n const recompress = this.sql(expression, 'recompress');\n const recompressStr = recompress ? ` RECOMPRESS ${recompress}` : '';\n const toDisk = this.sql(expression, 'toDisk');\n const toDiskStr = toDisk ? ` TO DISK ${toDisk}` : '';\n const toVolume = this.sql(expression, 'toVolume');\n const toVolumeStr = toVolume ? ` TO VOLUME ${toVolume}` : '';\n return `${thisStr}${del}${recompressStr}${toDiskStr}${toVolumeStr}`;\n }\n\n mergeTreeTtlSql (expression: MergeTreeTtlExpr): string {\n const where = this.sql(expression, 'where');\n const group = this.sql(expression, 'group');\n const aggregates = this.expressions(expression, { key: 'aggregates' });\n const aggregatesStr = aggregates ? `${this.seg('SET')}${this.seg(aggregates)}` : '';\n if (!where && !group && !aggregatesStr && expression.args.expressions?.length === 1) {\n return `TTL ${this.expressions(expression, { flat: true })}`;\n }\n return `TTL${this.seg(this.expressions(expression))}${where}${group}${aggregatesStr}`;\n }\n\n transactionSql (expression: TransactionExpr): string {\n const modes = this.expressions(expression, { key: 'modes' });\n const modesStr = modes ? ` ${modes}` : '';\n return `BEGIN${modesStr}`;\n }\n\n commitSql (expression: CommitExpr): string {\n const chain = expression.args.chain;\n let chainStr = '';\n if (chain !== undefined) {\n chainStr = chain ? ' AND CHAIN' : ' AND NO CHAIN';\n }\n return `COMMIT${chainStr}`;\n }\n\n rollbackSql (expression: RollbackExpr): string {\n const savepoint = expression.args.savepoint;\n const savepointStr = savepoint ? ` TO ${savepoint}` : '';\n return `ROLLBACK${savepointStr}`;\n }\n\n alterColumnSql (expression: AlterColumnExpr): string {\n const thisStr = this.sql(expression, 'this');\n\n const dtype = this.sql(expression, 'dtype');\n if (dtype) {\n let collate = this.sql(expression, 'collate');\n collate = collate ? ` COLLATE ${collate}` : '';\n let using = this.sql(expression, 'using');\n using = using ? ` USING ${using}` : '';\n const alterSetType = this._constructor.ALTER_SET_TYPE;\n const alterSetTypeStr = alterSetType ? `${alterSetType} ` : '';\n return `ALTER COLUMN ${thisStr} ${alterSetTypeStr}${dtype}${collate}${using}`;\n }\n\n const defaultVal = this.sql(expression, 'default');\n if (defaultVal) {\n return `ALTER COLUMN ${thisStr} SET DEFAULT ${defaultVal}`;\n }\n\n const comment = this.sql(expression, 'comment');\n if (comment) {\n return `ALTER COLUMN ${thisStr} COMMENT ${comment}`;\n }\n\n const visible = expression.args.visible;\n if (visible) {\n return `ALTER COLUMN ${thisStr} SET ${visible}`;\n }\n\n const allowNull = expression.args.allowNull;\n const drop = expression.args.drop;\n\n if (!drop && !allowNull) {\n this.unsupported('Unsupported ALTER COLUMN syntax');\n }\n\n if (allowNull !== undefined) {\n const keyword = drop ? 'DROP' : 'SET';\n return `ALTER COLUMN ${thisStr} ${keyword} NOT NULL`;\n }\n\n return `ALTER COLUMN ${thisStr} DROP DEFAULT`;\n }\n\n alterIndexSql (expression: AlterIndexExpr): string {\n const thisStr = this.sql(expression, 'this');\n const visible = expression.args.visible;\n const visibleSql = visible ? 'VISIBLE' : 'INVISIBLE';\n return `ALTER INDEX ${thisStr} ${visibleSql}`;\n }\n\n alterDistStyleSql (expression: AlterDistStyleExpr): string {\n let thisStr = this.sql(expression, 'this');\n if (!(expression.args.this instanceof VarExpr)) {\n thisStr = `KEY DISTKEY ${thisStr}`;\n }\n return `ALTER DISTSTYLE ${thisStr}`;\n }\n\n alterSortKeySql (expression: AlterSortKeyExpr): string {\n const compound = expression.args.compound ? ' COMPOUND' : '';\n const thisStr = this.sql(expression, 'this');\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? `(${expressions})` : '';\n return `ALTER${compound} SORTKEY ${thisStr || expressions}`;\n }\n\n alterRenameSql (expression: Expression, options: { includeTo?: boolean } = {}): string {\n const { includeTo = true } = options;\n let expr = expression;\n if (!this._constructor.RENAME_TABLE_WITH_DB) {\n // Remove db from tables\n expr = expression.transform((n) => {\n if (n instanceof TableExpr) {\n return new TableExpr({ this: n.args.this });\n }\n return n;\n }).assertIs(AlterRenameExpr);\n }\n const thisStr = this.sql(expr, 'this');\n const toKw = includeTo ? ' TO' : '';\n return `RENAME${toKw} ${thisStr}`;\n }\n\n renameColumnSql (expression: RenameColumnExpr): string {\n const exists = expression.args.exists ? ' IF EXISTS' : '';\n const oldColumn = this.sql(expression, 'this');\n const newColumn = this.sql(expression, 'to');\n return `RENAME COLUMN${exists} ${oldColumn} TO ${newColumn}`;\n }\n\n alterSetSql (expression: AlterSetExpr): string {\n let exprs = this.expressions(expression, { flat: true });\n if (this._constructor.ALTER_SET_WRAPPED) {\n exprs = `(${exprs})`;\n }\n return `SET ${exprs}`;\n }\n\n alterSql (expression: AlterExpr): string {\n const actions = expression.args.actions || [];\n\n let actionsSql: string;\n if (!this.dialect._constructor.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN\n && actions[0] instanceof ColumnDefExpr) {\n actionsSql = this.expressions(expression, {\n key: 'actions',\n flat: true,\n });\n actionsSql = `ADD ${actionsSql}`;\n } else {\n const actionsList: string[] = [];\n for (const action of actions) {\n const actionExpr = action as Expression;\n let actionSql: string;\n if (actionExpr instanceof ColumnDefExpr || actionExpr instanceof SchemaExpr) {\n actionSql = this.addColumnSql(actionExpr);\n } else {\n actionSql = this.sql(actionExpr);\n if (actionExpr instanceof SelectExpr || actionExpr instanceof UnionExpr) {\n actionSql = `AS ${actionSql}`;\n }\n }\n actionsList.push(actionSql);\n }\n actionsSql = this.formatArgs(actionsList).replace(/^\\n+/, '');\n }\n\n const exists = expression.args.exists ? ' IF EXISTS' : '';\n let onCluster = this.sql(expression, 'cluster');\n onCluster = onCluster ? ` ${onCluster}` : '';\n const only = expression.args.only ? ' ONLY' : '';\n let options = this.expressions(expression, { key: 'options' });\n options = options ? `, ${options}` : '';\n const kind = this.sql(expression, 'kind').toUpperCase();\n const notValid = expression.args.notValid ? ' NOT VALID' : '';\n const check = expression.args.check ? ' WITH CHECK' : '';\n const cascade = expression.args.cascade && this.dialect._constructor.ALTER_TABLE_SUPPORTS_CASCADE\n ? ' CASCADE'\n : '';\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n\n return `ALTER ${kind}${exists}${only}${thisStr}${onCluster}${check}${this.sep()}${actionsSql}${notValid}${options}${cascade}`;\n }\n\n alterSessionSql (expression: AlterSessionExpr): string {\n const itemsSql = this.expressions(expression, { flat: true });\n const keyword = expression.args.unset ? 'UNSET' : 'SET';\n return `${keyword} ${itemsSql}`;\n }\n\n addColumnSql (expression: Expression): string {\n const sql = this.sql(expression);\n let columnText: string;\n if (expression instanceof SchemaExpr) {\n columnText = ' COLUMNS';\n } else if (expression instanceof ColumnDefExpr\n && this._constructor.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD) {\n columnText = ' COLUMN';\n } else {\n columnText = '';\n }\n return `ADD${columnText} ${sql}`;\n }\n\n dropPartitionSql (expression: DropPartitionExpr): string {\n const expressions = this.expressions(expression);\n const exists = expression.args.exists ? ' IF EXISTS ' : ' ';\n return `DROP${exists}${expressions}`;\n }\n\n addConstraintSql (expression: AddConstraintExpr): string {\n return `ADD ${this.expressions(expression, { indent: false })}`;\n }\n\n addPartitionSql (expression: AddPartitionExpr): string {\n const exists = expression.args.exists ? 'IF NOT EXISTS ' : '';\n let location = this.sql(expression, 'location');\n location = location ? ` ${location}` : '';\n return `ADD ${exists}${this.sql(expression, 'this')}${location}`;\n }\n\n distinctSql (expression: DistinctExpr): string {\n let thisSql = this.expressions(expression, { flat: true });\n\n // Handle dialects that don't support multiple arguments in DISTINCT\n // by wrapping them in a CASE statement logic\n if (!this._constructor.MULTI_ARG_DISTINCT && 1 < (expression.args.expressions?.length ?? 0)) {\n let caseAst = case_();\n\n for (const arg of expression.args.expressions || []) {\n caseAst = caseAst.when(arg.is(null_()), null_());\n }\n\n thisSql = this.sql(caseAst.else(`(${thisSql})`));\n }\n\n thisSql = thisSql ? ` ${thisSql}` : '';\n\n let on = this.sql(expression, 'on');\n on = on ? ` ON ${on}` : '';\n\n return `DISTINCT${thisSql}${on}`;\n }\n\n ignoreNullsSql (expression: IgnoreNullsExpr): string {\n return this.embedIgnoreNulls(expression, 'IGNORE NULLS');\n }\n\n respectNullsSql (expression: RespectNullsExpr): string {\n return this.embedIgnoreNulls(expression, 'RESPECT NULLS');\n }\n\n havingMaxSql (expression: HavingMaxExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expressionSql = this.sql(expression, 'expression');\n const kind = expression.args.max ? 'MAX' : 'MIN';\n return `${thisStr} HAVING ${kind} ${expressionSql}`;\n }\n\n intDivSql (expression: IntDivExpr): string {\n return this.sql(new CastExpr({\n this: new DivExpr({\n this: expression.args.this,\n expression: expression.args.expression,\n }),\n to: new DataTypeExpr({ this: DataTypeExprKind.INT }),\n }));\n }\n\n dPipeSql (expression: DPipeExpr): string {\n if (this.dialect._constructor.STRICT_STRING_CONCAT && expression.args.safe) {\n const flatParts = Array.from(expression.flatten())\n .map((e) => cast(e, DataTypeExprKind.TEXT, { copy: false }));\n return this.func('CONCAT', flatParts);\n }\n return this.binary(expression, '||');\n }\n\n divSql (expression: DivExpr): string {\n const l = expression.left;\n const r = expression.right;\n\n // Handle SAFE_DIVISION by wrapping the divisor in NULLIF(r, 0)\n if (!this.dialect._constructor.SAFE_DIVISION && expression.args.safe) {\n r?.replace(\n new NullifExpr({\n this: r.copy(),\n expression: LiteralExpr.number(0),\n }),\n );\n }\n\n // Handle TYPED_DIVISION: Cast to DOUBLE if neither side is a real type\n if (this.dialect._constructor.TYPED_DIVISION && !expression.args.typed) {\n if (!l?.isType(Array.from(DataTypeExpr.REAL_TYPES)) && !r?.isType(Array.from(DataTypeExpr.REAL_TYPES))) {\n l?.replace(cast(l.copy(), DataTypeExprKind.DOUBLE));\n }\n } else if (!this.dialect._constructor.TYPED_DIVISION && expression.args.typed) {\n // Handle non-TYPED_DIVISION: Cast result to BIGINT if both sides are integers\n if (l?.isType(Array.from(DataTypeExpr.INTEGER_TYPES)) && r?.isType(Array.from(DataTypeExpr.INTEGER_TYPES))) {\n return this.sql(\n cast(\n l.div(r),\n DataTypeExprKind.BIGINT,\n ),\n );\n }\n }\n\n return this.binary(expression, '/');\n }\n\n safeDivideSql (expression: SafeDivideExpr): string {\n const n = wrap(expression.args.this, BinaryExpr);\n const d = wrap(expression.args.expression, BinaryExpr);\n if (!n || !d) return '';\n return this.sql(\n new IfExpr({\n this: d.neq(0),\n true: n.div(d),\n false: null_(),\n }),\n );\n }\n\n overlapsSql (expression: OverlapsExpr): string {\n return this.binary(expression, 'OVERLAPS');\n }\n\n distanceSql (expression: DistanceExpr): string {\n return this.binary(expression, '<->');\n }\n\n dotSql (expression: DotExpr): string {\n return `${this.sql(expression, 'this')}.${this.sql(expression, 'expression')}`;\n }\n\n eqSql (expression: EqExpr): string {\n return this.binary(expression, '=');\n }\n\n propertyEqSql (expression: PropertyEqExpr): string {\n return this.binary(expression, ':=');\n }\n\n escapeSql (expression: EscapeExpr): string {\n return this.binary(expression, 'ESCAPE');\n }\n\n globSql (expression: GlobExpr): string {\n return this.binary(expression, 'GLOB');\n }\n\n gtSql (expression: GtExpr): string {\n return this.binary(expression, '>');\n }\n\n gteSql (expression: GteExpr): string {\n return this.binary(expression, '>=');\n }\n\n isSql (expression: IsExpr): string {\n if (!this._constructor.IS_BOOL_ALLOWED && expression.args.expression instanceof BooleanExpr) {\n const thisArg = expression.args.this;\n return this.sql(\n expression.args.expression.args.this\n ? thisArg\n : not(thisArg instanceof Expression ? thisArg : typeof thisArg === 'string' ? thisArg : undefined),\n );\n }\n\n return this.binary(expression, 'IS');\n }\n\n iLikeSql (expression: ILikeExpr): string {\n return this.likeSql(expression);\n }\n\n likeSql (expression: LikeExpr | ILikeExpr): string {\n const thisExpr = expression.args.this;\n const rhs = expression.args.expression;\n\n let expClass: typeof Expression;\n let op: string;\n\n if (expression instanceof LikeExpr) {\n expClass = LikeExpr;\n op = 'LIKE';\n } else {\n expClass = ILikeExpr;\n op = 'ILIKE';\n }\n\n // Check if we are dealing with 'LIKE ANY' or 'LIKE ALL' when the dialect doesn't support it\n if ((rhs instanceof AllExpr || rhs instanceof AnyExpr) && !this._constructor.SUPPORTS_LIKE_QUANTIFIERS) {\n let exprs: ExpressionValue[] | undefined;\n const unnested = rhs.args.this instanceof Expression ? rhs.args.this.unnest() : undefined;\n if (unnested instanceof TupleExpr) {\n exprs = unnested.args.expressions;\n } else if (unnested) {\n exprs = [unnested];\n } else {\n exprs = [];\n }\n\n const connective = rhs instanceof AnyExpr ? or : and;\n\n // Build the expanded expression: (this LIKE expr1 OR this LIKE expr2...)\n let likeExpr: Expression = new expClass({\n this: thisExpr,\n expression: exprs?.[0],\n });\n\n for (let i = 1; i < (exprs?.length || 0); i++) {\n likeExpr = connective([\n likeExpr,\n new expClass({\n this: thisExpr,\n expression: exprs?.[i] || 0,\n }),\n ]);\n }\n\n const parent = expression.parent;\n\n // Wrap in parentheses if the expansion happens within another condition to maintain precedence\n if (parent instanceof ConditionExpr && !(parent instanceof likeExpr._constructor)) {\n likeExpr = paren(likeExpr, { copy: false });\n }\n\n return this.sql(likeExpr);\n }\n\n return this.binary(expression, op);\n }\n\n matchSql (expression: MatchExpr): string {\n return this.binary(expression, 'MATCH');\n }\n\n similarToSql (expression: SimilarToExpr): string {\n return this.binary(expression, 'SIMILAR TO');\n }\n\n ltSql (expression: LtExpr): string {\n return this.binary(expression, '<');\n }\n\n lteSql (expression: LteExpr): string {\n return this.binary(expression, '<=');\n }\n\n modSql (expression: ModExpr): string {\n return this.binary(expression, '%');\n }\n\n mulSql (expression: MulExpr): string {\n return this.binary(expression, '*');\n }\n\n neqSql (expression: NeqExpr): string {\n return this.binary(expression, '<>');\n }\n\n nullSafeEqSql (expression: NullSafeEqExpr): string {\n return this.binary(expression, 'IS NOT DISTINCT FROM');\n }\n\n nullSafeNeqSql (expression: NullSafeNeqExpr): string {\n return this.binary(expression, 'IS DISTINCT FROM');\n }\n\n subSql (expression: SubExpr): string {\n return this.binary(expression, '-');\n }\n\n tryCastSql (expression: TryCastExpr): string {\n return this.castSql(expression, { safePrefix: 'TRY_' });\n }\n\n jsonCastSql (expression: JsonCastExpr): string {\n return this.castSql(expression);\n }\n\n trySql (expression: TryExpr): string {\n if (!this._constructor.TRY_SUPPORTED) {\n this.unsupported('Unsupported TRY function');\n return this.sql(expression, 'this');\n }\n return this.func('TRY', [expression.args.this]);\n }\n\n logSql (expression: LogExpr): string {\n let thisExpr: Expression | undefined = expression.args.this;\n let exprArg: Expression | undefined = expression.args.expression;\n if (this.dialect._constructor.LOG_BASE_FIRST === false) {\n [thisExpr, exprArg] = [exprArg, thisExpr];\n } else if (this.dialect._constructor.LOG_BASE_FIRST === undefined && exprArg) {\n if (!thisExpr) return '';\n const baseName = thisExpr.name;\n if (baseName === '2' || baseName === '10') {\n return this.func(`LOG${baseName}`, [exprArg]);\n }\n this.unsupported(`Unsupported logarithm with base ${this.sql(thisExpr)}`);\n }\n return this.func('LOG', [thisExpr, exprArg]);\n }\n\n useSql (expression: UseExpr): string {\n const kind = this.sql(expression, 'kind');\n const kindStr = kind ? ` ${kind}` : '';\n const thisStr = this.sql(expression, 'this') || this.expressions(expression, { flat: true });\n const thisResult = thisStr ? ` ${thisStr}` : '';\n return `USE${kindStr}${thisResult}`;\n }\n\n binary (expression: BinaryExpr, op: string): string {\n const sqls: string[] = [];\n const stack: (string | Expression)[] = [expression];\n\n const binaryType = expression._constructor;\n\n while (0 < stack.length) {\n const node = stack.pop();\n\n if (node instanceof BinaryExpr && node._constructor === binaryType) {\n const opFunc = node.args.operator;\n if (opFunc) {\n op = `OPERATOR(${this.sql(opFunc)})`;\n }\n\n stack.push(node.right || '');\n stack.push(` ${this.maybeComment(op, undefined, node.comments)} `);\n stack.push(node.left || '');\n } else {\n sqls.push(this.sql(node));\n }\n }\n\n return sqls.join('');\n }\n\n ceilFloor (expression: CeilExpr | FloorExpr): string {\n const toClause = this.sql(expression, 'to');\n\n if (toClause) {\n return `${(expression._constructor as typeof FuncExpr).sqlName}(${this.sql(expression, 'this')} TO ${toClause})`;\n }\n\n return this.functionFallbackSql(expression);\n }\n\n functionFallbackSql (expression: FuncExpr | JsonExtractExpr): string {\n const args: (ExpressionValue | undefined)[] = [];\n\n for (const key of expression._constructor.availableArgs) {\n const argValue = expression.getArgKey(key);\n\n if (Array.isArray(argValue)) {\n args.push(...argValue);\n } else {\n args.push(argValue);\n }\n }\n\n let name: string | undefined;\n if (this.dialect._constructor.PRESERVE_ORIGINAL_NAMES) {\n name = (expression.meta?.name as string | undefined) || (expression._constructor as typeof FuncExpr).sqlName();\n } else {\n name = (expression._constructor as typeof FuncExpr).sqlName();\n }\n\n return this.func(name, args);\n }\n\n func (\n name: string,\n args: (ExpressionValue | undefined)[],\n options: {\n prefix?: string;\n suffix?: string;\n normalize?: boolean;\n } = {},\n ): string {\n const {\n prefix = '(', suffix = ')', normalize = true,\n } = options;\n const funcName = normalize ? this.normalizeFunc(name) : name;\n return `${funcName}${prefix}${this.formatArgs(args)}${suffix}`;\n }\n\n formatArgs (\n args: (ExpressionValue | undefined)[],\n options: { sep?: string } = {},\n ): string {\n const { sep = ', ' } = options;\n const argSqls = args\n .filter((arg) => arg !== undefined && typeof arg !== 'boolean')\n .map((arg) => this.sql(arg))\n .filter((argSql) => argSql !== '');\n\n if (this.pretty && this.tooWide(argSqls)) {\n const joined = argSqls.join(`${sep.trim()}\\n`);\n return this.indent(`\\n${joined}\\n`, {\n skipFirst: true,\n skipLast: true,\n });\n }\n\n return argSqls.join(sep);\n }\n\n tooWide (args: string[]): boolean {\n const totalLength = args.reduce((acc, arg) => acc + arg.length, 0);\n return this.maxTextWidth < totalLength;\n }\n\n expressions (\n expression?: Expression | undefined,\n options: {\n sqls?: (string | Expression)[];\n key?: string;\n flat?: boolean;\n indent?: boolean;\n skipFirst?: boolean;\n skipLast?: boolean;\n sep?: string;\n prefix?: string;\n dynamic?: boolean;\n newLine?: boolean;\n } = {},\n ): string {\n const {\n flat = false,\n indent = true,\n skipFirst = false,\n skipLast = false,\n sep = ', ',\n prefix = '',\n dynamic = false,\n newLine = false,\n key,\n sqls,\n } = options;\n\n const exprs = expression ? expression.getArgKey(key || 'expressions') : sqls;\n\n if (!exprs || (Array.isArray(exprs) && exprs.length === 0)) {\n return '';\n }\n\n const exprArray = Array.isArray(exprs) ? exprs : [exprs];\n\n if (flat) {\n return exprArray\n .map((e) => this.sql(e))\n .filter((sql) => sql)\n .join(sep);\n }\n\n const numSqls = exprArray.length;\n const resultSqls: string[] = [];\n\n for (let i = 0; i < numSqls; i++) {\n const e = exprArray[i];\n const sql = this.sql(e, undefined, { comment: false });\n if (!sql) continue;\n\n const comments = e instanceof Expression ? this.maybeComment('', e) : '';\n\n if (this.pretty) {\n if (this.leadingComma) {\n resultSqls.push(`${0 < i ? sep : ''}${prefix}${sql}${comments}`);\n } else {\n const separator = i + 1 < numSqls ? (comments ? sep.trimEnd() : sep) : '';\n resultSqls.push(`${prefix}${sql}${separator}${comments}`);\n }\n } else {\n resultSqls.push(`${prefix}${sql}${comments}${i + 1 < numSqls ? sep : ''}`);\n }\n }\n\n let resultSql: string;\n\n if (this.pretty && (!dynamic || this.tooWide(resultSqls))) {\n if (newLine) {\n resultSqls.unshift('');\n resultSqls.push('');\n }\n resultSql = resultSqls.map((s) => s.trimEnd()).join('\\n');\n } else {\n resultSql = resultSqls.join('');\n }\n\n return indent\n ? this.indent(resultSql, {\n skipFirst,\n skipLast,\n })\n : resultSql;\n }\n\n opExpressions (op: string, expression: Expression, options: { flat?: boolean } = {}): string {\n const flat = (options.flat ?? false) || expression.parent instanceof PropertiesExpr;\n const expressionsSql = this.expressions(expression, { flat });\n if (flat) {\n return `${op} ${expressionsSql}`;\n }\n return `${this.seg(op)}${expressionsSql\n ? this.sep()\n : ''}${expressionsSql}`;\n }\n\n nakedProperty (expression: PropertyExpr): string {\n const propertyName = PropertiesExpr.PROPERTY_TO_NAME[expression._constructor.key];\n if (!propertyName) {\n this.unsupported(`Unsupported property ${expression._constructor.key}`);\n }\n return `${propertyName || ''} ${this.sql(expression, 'this')}`;\n }\n\n tagSql (expression: TagExpr): string {\n const prefix = expression.args.prefix || '';\n const postfix = expression.args.postfix || '';\n return `${prefix}${this.sql(expression.args.this)}${postfix}`;\n }\n\n protected tokenSql (tokenType: TokenType): string {\n return this._constructor.TOKEN_MAPPING[tokenType]\n ?? tokenType.replace(/([A-Z])/g, '_$1').toUpperCase();\n }\n\n userDefinedFunctionSql (expression: UserDefinedFunctionExpr): string {\n const thisStr = this.sql(expression, 'this');\n let expressions = this.noIdentify(this.expressions.bind(this), expression);\n if (expression.args.wrapped) {\n expressions = this.wrap(expressions);\n } else {\n expressions = expressions ? ` ${expressions}` : '';\n }\n return expressions.trim() !== '' ? `${thisStr}${expressions}` : thisStr;\n }\n\n joinHintSql (expression: JoinHintExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expressions = this.expressions(expression, { flat: true });\n return `${thisStr}(${expressions})`;\n }\n\n kwargSql (expression: KwargExpr): string {\n return this.binary(expression, '=>');\n }\n\n whenSql (expression: WhenExpr): string {\n const matched = expression.args.matched ? 'MATCHED' : 'NOT MATCHED';\n const source = this._constructor.MATCHED_BY_SOURCE && expression.args.source ? ' BY SOURCE' : '';\n const condition = this.sql(expression, 'condition');\n const conditionStr = condition ? ` AND ${condition}` : '';\n\n const thenExpression = expression.args.then;\n let then = '';\n if (thenExpression instanceof InsertExpr) {\n let thisStr = this.sql(thenExpression, 'this');\n thisStr = thisStr ? `INSERT ${thisStr}` : 'INSERT';\n const thenExpr = this.sql(thenExpression, 'expression');\n then = thenExpr ? `${thisStr} VALUES ${thenExpr}` : thisStr;\n } else if (thenExpression instanceof UpdateExpr) {\n if (thenExpression.getArgKey('expressions') instanceof StarExpr) {\n then = `UPDATE ${this.sql(thenExpression, 'expressions')}`;\n } else {\n const expressionsSql = this.expressions(thenExpression);\n then = expressionsSql ? `UPDATE SET${this.sep()}${expressionsSql}` : 'UPDATE';\n }\n } else {\n then = this.sql(thenExpression);\n }\n return `WHEN ${matched}${source}${conditionStr} THEN ${then}`;\n }\n\n whensSql (expression: WhensExpr): string {\n return this.expressions(expression, {\n sep: ' ',\n indent: false,\n });\n }\n\n mergeSql (expression: MergeExpr): string {\n const table = expression.args.this;\n let tableAlias = '';\n\n if (isInstanceOf(table, TableExpr)) {\n const hints: Expression[] = table.args.hints || [];\n if (0 < hints.length && table.alias && hints[0] instanceof WithTableHintExpr) {\n // T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]\n const aliasRaw = table.args.alias;\n const aliasExpr = isInstanceOf(aliasRaw, Expression) ? aliasRaw.pop() : undefined;\n tableAlias = aliasExpr ? ` AS ${this.sql(aliasExpr)}` : '';\n }\n }\n\n const thisStr = this.sql(table);\n const using = `USING ${this.sql(expression, 'using')}`;\n let whens = this.sql(expression, 'whens');\n\n let on = this.sql(expression, 'on');\n on = on ? `ON ${on}` : '';\n\n if (!on) {\n const usingCond = this.expressions(expression, { key: 'usingCond' });\n on = usingCond ? `USING (${usingCond})` : '';\n }\n\n const returning = this.sql(expression, 'returning');\n if (returning) {\n whens = `${whens}${returning}`;\n }\n\n const sep = this.sep();\n\n return this.prependCtes(\n expression,\n `MERGE INTO ${thisStr}${tableAlias}${sep}${using}${sep}${on}${sep}${whens}`,\n );\n }\n\n toCharSql (expression: ToCharExpr): string {\n unsupportedArgs.call(this, expression, 'format');\n return this.sql(cast(expression.args.this, DataTypeExprKind.TEXT));\n }\n\n toNumberSql (expression: ToNumberExpr): string {\n if (!this._constructor.SUPPORTS_TO_NUMBER) {\n this.unsupported('Unsupported TO_NUMBER function');\n return this.sql(cast(expression.args.this as Expression, DataTypeExprKind.DOUBLE));\n }\n const fmt = expression.args.format;\n if (!fmt) {\n this.unsupported('Conversion format is required for TO_NUMBER');\n return this.sql(cast(expression.args.this as Expression, DataTypeExprKind.DOUBLE));\n }\n return this.func('TO_NUMBER', [expression.args.this, fmt]);\n }\n\n dictPropertySql (expression: DictPropertyExpr): string {\n const thisStr = this.sql(expression, 'this');\n const kind = this.sql(expression, 'kind');\n const settingsSql = this.expressions(expression, {\n key: 'settings',\n sep: ' ',\n });\n const args = settingsSql\n ? `(${this.sep('')}${settingsSql}${this.seg(')', '')}`\n : '()';\n return `${thisStr}(${kind}${args})`;\n }\n\n dictRangeSql (expression: DictRangeExpr): string {\n const thisStr = this.sql(expression, 'this');\n const max = this.sql(expression, 'max');\n const min = this.sql(expression, 'min');\n return `${thisStr}(MIN ${min} MAX ${max})`;\n }\n\n dictSubPropertySql (expression: DictSubPropertyExpr): string {\n return `${this.sql(expression, 'this')} ${this.sql(expression, 'value')}`;\n }\n\n duplicateKeyPropertySql (expression: DuplicateKeyPropertyExpr): string {\n return `DUPLICATE KEY (${this.expressions(expression, { flat: true })})`;\n }\n\n distributedByPropertySql (expression: DistributedByPropertyExpr): string {\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? ` ${this.wrap(expressions)}` : '';\n const buckets = this.sql(expression, 'buckets');\n const kind = this.sql(expression, 'kind');\n const bucketsStr = buckets ? ` BUCKETS ${buckets}` : '';\n const order = this.sql(expression, 'order');\n return `DISTRIBUTED BY ${kind}${expressions}${bucketsStr}${order}`;\n }\n\n onClusterSql (_expression: OnClusterExpr): string {\n return '';\n }\n\n clusteredByPropertySql (expression: ClusteredByPropertyExpr): string {\n const expressions = this.expressions(expression, {\n key: 'expressions',\n flat: true,\n });\n let sortedBy = this.expressions(expression, {\n key: 'sortedBy',\n flat: true,\n });\n sortedBy = sortedBy ? ` SORTED BY (${sortedBy})` : '';\n const buckets = this.sql(expression, 'buckets');\n return `CLUSTERED BY (${expressions})${sortedBy} INTO ${buckets} BUCKETS`;\n }\n\n anyValueSql (expression: AnyValueExpr): string {\n const thisStr = this.sql(expression, 'this');\n const having = this.sql(expression, 'having');\n if (having) {\n const max = expression.args.max;\n return this.func('ANY_VALUE', [`${thisStr} HAVING ${max ? 'MAX' : 'MIN'} ${having}`]);\n }\n return this.func('ANY_VALUE', [expression.args.this]);\n }\n\n queryTransformSql (expression: QueryTransformExpr): string {\n const transform = this.func('TRANSFORM', expression.args.expressions || []);\n let rowFormatBefore = this.sql(expression, 'rowFormatBefore');\n rowFormatBefore = rowFormatBefore ? ` ${rowFormatBefore}` : '';\n let recordWriter = this.sql(expression, 'recordWriter');\n recordWriter = recordWriter ? ` RECORDWRITER ${recordWriter}` : '';\n const using = ` USING ${this.sql(expression, 'commandScript')}`;\n let schema = this.sql(expression, 'schema');\n schema = schema ? ` AS ${schema}` : '';\n let rowFormatAfter = this.sql(expression, 'rowFormatAfter');\n rowFormatAfter = rowFormatAfter ? ` ${rowFormatAfter}` : '';\n let recordReader = this.sql(expression, 'recordReader');\n recordReader = recordReader ? ` RECORDREADER ${recordReader}` : '';\n return `${transform}${rowFormatBefore}${recordWriter}${using}${schema}${rowFormatAfter}${recordReader}`;\n }\n\n indexConstraintOptionSql (expression: IndexConstraintOptionExpr): string {\n const keyBlockSize = this.sql(expression, 'keyBlockSize');\n if (keyBlockSize) {\n return `KEY_BLOCK_SIZE = ${keyBlockSize}`;\n }\n\n const using = this.sql(expression, 'using');\n if (using) {\n return `USING ${using}`;\n }\n\n const parser = this.sql(expression, 'parser');\n if (parser) {\n return `WITH PARSER ${parser}`;\n }\n\n const comment = this.sql(expression, 'comment');\n if (comment) {\n return `COMMENT ${comment}`;\n }\n\n const visible = expression.args.visible;\n if (visible !== undefined) {\n return visible ? 'VISIBLE' : 'INVISIBLE';\n }\n\n const engineAttr = this.sql(expression, 'engineAttr');\n if (engineAttr) {\n return `ENGINE_ATTRIBUTE = ${engineAttr}`;\n }\n\n const secondaryEngineAttr = this.sql(expression, 'secondaryEngineAttr');\n if (secondaryEngineAttr) {\n return `SECONDARY_ENGINE_ATTRIBUTE = ${secondaryEngineAttr}`;\n }\n\n this.unsupported('Unsupported index constraint option.');\n return '';\n }\n\n checkColumnConstraintSql (expression: CheckColumnConstraintExpr): string {\n const enforced = expression.args.enforced ? ' ENFORCED' : '';\n return `CHECK (${this.sql(expression, 'this')})${enforced}`;\n }\n\n indexColumnConstraintSql (expression: IndexColumnConstraintExpr): string {\n const kind = this.sql(expression, 'kind');\n const kindStr = kind ? `${kind} INDEX` : 'INDEX';\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n let indexType = this.sql(expression, 'indexType');\n indexType = indexType ? ` USING ${indexType}` : '';\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? ` (${expressions})` : '';\n let options = this.expressions(expression, {\n key: 'options',\n flat: true,\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n return `${kindStr}${thisStr}${indexType}${expressions}${options}`;\n }\n\n nvl2Sql (expression: Nvl2Expr): string {\n if (this._constructor.NVL2_SUPPORTED) {\n return this.functionFallbackSql(expression);\n }\n\n const nvl2This = expression.args.this;\n if (!nvl2This) return '';\n const caseExpr = new CaseExpr({})\n .when(\n nvl2This.is(null_()).not({ copy: false }),\n expression.args.true ?? '',\n { copy: false },\n );\n\n const elseCond = expression.args.false;\n if (elseCond) {\n caseExpr.else(elseCond, { copy: false });\n }\n\n return this.sql(caseExpr);\n }\n\n comprehensionSql (expression: ComprehensionExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expr = this.sql(expression, 'expression');\n const position = this.sql(expression, 'position');\n const positionStr = position ? `, ${position}` : '';\n const iterator = this.sql(expression, 'iterator');\n const condition = this.sql(expression, 'condition');\n const conditionStr = condition ? ` IF ${condition}` : '';\n return `${thisStr} FOR ${expr}${positionStr} IN ${iterator}${conditionStr}`;\n }\n\n columnPrefixSql (expression: ColumnPrefixExpr): string {\n return `${this.sql(expression, 'this')}(${this.sql(expression, 'expression')})`;\n }\n\n opclassSql (expression: OpclassExpr): string {\n return `${this.sql(expression, 'this')} ${this.sql(expression, 'expression')}`;\n }\n\n mlSql (expression: FuncExpr, name: string): string {\n const model = `MODEL ${this.sql(expression, 'this')}`;\n const exprNode = expression.args.expression;\n let exprSql: string | undefined;\n if (exprNode) {\n const raw = this.sql(expression, 'expression');\n exprSql = !(exprNode instanceof SubqueryExpr) ? `TABLE ${raw}` : raw;\n }\n const parameters = this.sql(expression, 'paramsStruct') || undefined;\n return this.func(name, [\n model,\n exprSql,\n parameters,\n ]);\n }\n\n predictSql (expression: PredictExpr): string {\n return this.mlSql(expression, 'PREDICT');\n }\n\n generateEmbeddingSql (expression: GenerateEmbeddingExpr): string {\n const name = expression.args.isText ? 'GENERATE_TEXT_EMBEDDING' : 'GENERATE_EMBEDDING';\n return this.mlSql(expression, name);\n }\n\n mlTranslateSql (expression: MlTranslateExpr): string {\n return this.mlSql(expression, 'TRANSLATE');\n }\n\n mlForecastSql (expression: MlForecastExpr): string {\n return this.mlSql(expression, 'FORECAST');\n }\n\n featuresAtTimeSql (expression: FeaturesAtTimeExpr): string {\n const thisNode = expression.args.this;\n let thisStr = this.sql(expression, 'this');\n if (thisNode instanceof TableExpr) {\n thisStr = `TABLE ${thisStr}`;\n }\n return this.func(\n 'FEATURES_AT_TIME',\n [\n thisStr,\n expression.args.time,\n ...(expression.args.numRows || [undefined]),\n ...(expression.args.ignoreFeatureNulls || [undefined]),\n ],\n );\n }\n\n vectorSearchSql (expression: VectorSearchExpr): string {\n let thisStr = this.sql(expression, 'this');\n if (expression.args.this instanceof TableExpr) {\n thisStr = `TABLE ${thisStr}`;\n }\n const queryTable = expression.args.queryTable;\n let queryTableStr = queryTable ? this.sql(queryTable) : undefined;\n if (queryTable instanceof TableExpr) {\n queryTableStr = `TABLE ${queryTableStr}`;\n }\n return this.func(\n 'VECTOR_SEARCH',\n [\n thisStr,\n expression.args.columnToSearch,\n queryTableStr,\n expression.args.queryColumnToSearch,\n expression.args.topK,\n expression.args.distanceType,\n ...(expression.args.options || [undefined]),\n ],\n );\n }\n\n forInSql (expression: ForInExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expressionSql = this.sql(expression, 'expression');\n return `FOR ${thisStr} DO ${expressionSql}`;\n }\n\n refreshSql (expression: RefreshExpr): string {\n const thisStr = this.sql(expression, 'this');\n const isLiteral = expression.args.this instanceof LiteralExpr;\n const kind = isLiteral ? '' : `${expression.args.kind || ''} `;\n return `REFRESH ${kind}${thisStr}`;\n }\n\n toArraySql (expression: ToArrayExpr): string {\n let arg = expression.args.this;\n if (!arg) return '';\n if (!arg.type) {\n arg = annotateTypes(arg, { dialect: this.dialect });\n }\n\n if (arg.isType(DataTypeExprKind.ARRAY)) {\n return this.sql(arg);\n }\n\n const condForNull = arg.is(null_());\n return this.sql(func('IF', condForNull, null_(), array(arg, { copy: false })));\n }\n\n tsOrDsToTimeSql (expression: TsOrDsToTimeExpr): string {\n const thisArg = expression.args.this;\n if (!thisArg) return '';\n const timeFormat = this.formatTime(expression);\n if (timeFormat) {\n return this.sql(\n cast(\n new StrToTimeExpr({\n this: thisArg,\n format: expression.args.format || timeFormat,\n }),\n DataTypeExprKind.TIME,\n ),\n );\n }\n if (expression.args.this instanceof TsOrDsToTimeExpr\n || thisArg.isType(DataTypeExprKind.TIME)) {\n return this.sql(thisArg);\n }\n return this.sql(cast(thisArg, DataTypeExprKind.TIME));\n }\n\n tsOrDsToTimestampSql (expression: TsOrDsToTimestampExpr): string {\n const thisArg = expression.args.this;\n if (!thisArg) return '';\n if (thisArg instanceof TsOrDsToTimestampExpr\n || thisArg.isType(DataTypeExprKind.TIMESTAMP)) {\n return this.sql(thisArg);\n }\n return this.sql(cast(thisArg, DataTypeExprKind.TIMESTAMP));\n }\n\n tsOrDsToDatetimeSql (expression: TsOrDsToDatetimeExpr): string {\n const thisArg = expression.args.this;\n if (!thisArg) return '';\n if (expression.args.this instanceof TsOrDsToDatetimeExpr\n || thisArg.isType(DataTypeExprKind.DATETIME)) {\n return this.sql(thisArg);\n }\n return this.sql(cast(thisArg, DataTypeExprKind.DATETIME));\n }\n\n tsOrDsToDateSql (expression: TsOrDsToDateExpr): string {\n const thisArg = expression.args.this;\n if (!thisArg) return '';\n const timeFormat = this.formatTime(expression);\n const safe = expression.args.safe;\n if (timeFormat && ![this.dialect._constructor.TIME_FORMAT, this.dialect._constructor.DATE_FORMAT].includes(timeFormat)) {\n return this.sql(\n cast(\n new StrToTimeExpr({\n this: thisArg,\n format: timeFormat,\n safe,\n }),\n DataTypeExprKind.DATE,\n ),\n );\n }\n if (expression.args.this instanceof TsOrDsToDateExpr\n || thisArg.isType(DataTypeExprKind.DATE)) {\n return this.sql(thisArg);\n }\n if (safe) {\n return this.sql(\n new TryCastExpr({\n this: thisArg,\n to: new DataTypeExpr({ this: DataTypeExprKind.DATE }),\n }),\n );\n }\n return this.sql(cast(thisArg, DataTypeExprKind.DATE));\n }\n\n unixDateSql (expression: UnixDateExpr): string {\n const startDate = cast(LiteralExpr.string('1970-01-01'), DataTypeExprKind.DATE);\n return this.func(\n 'DATEDIFF',\n [\n expression.args.this,\n startDate,\n 'day',\n ],\n );\n }\n\n lastDaySql (expression: LastDayExpr): string {\n if (this._constructor.LAST_DAY_SUPPORTS_DATE_PART) {\n return this.functionFallbackSql(expression);\n }\n const unit = expression.text('unit');\n const unitStr = unit ? this.sql(unit) : '';\n if (unitStr && unitStr !== 'MONTH') {\n this.unsupported('Date parts are not supported in LAST_DAY.');\n }\n return this.func('LAST_DAY', [expression.args.this]);\n }\n\n dateAddSql (expression: DateAddExpr): string {\n return this.func(\n 'DATE_ADD',\n [\n expression.args.this,\n expression.args.expression,\n unitToStr(expression),\n ],\n );\n }\n\n arrayAnySql (expression: ArrayAnyExpr): string {\n if (this._constructor.CAN_IMPLEMENT_ARRAY_ANY) {\n const filtered = new ArrayFilterExpr({\n this: expression.args.this,\n expression: expression.args.expression,\n });\n const filteredNotEmpty = new ArraySizeExpr({ this: filtered }).neq(0);\n const originalIsEmpty = new ArraySizeExpr({ this: expression.args.this }).eq(0);\n return this.sql(paren(originalIsEmpty.or(filteredNotEmpty)));\n }\n\n // SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect\n if (!(this.dialect._constructor !== Dialect)) {\n this.unsupported('ARRAY_ANY is unsupported');\n }\n\n return this.functionFallbackSql(expression);\n }\n\n structSql (expression: StructExpr): string {\n expression.setArgKey(\n 'expressions',\n (expression.args.expressions ?? []).map((e) => {\n if (e instanceof PropertyEqExpr) {\n const thisArg = e.args.this;\n const aliasName = mapOnExpression(thisArg, (expr) => {\n if (expr instanceof IdentifierExpr) {\n return expr;\n }\n return expr.name;\n });\n const exprArg = e.args.expression;\n return alias(\n (exprArg instanceof Expression || typeof exprArg === 'string') ? exprArg : '',\n aliasName,\n );\n }\n return e;\n }),\n );\n\n return this.functionFallbackSql(expression);\n }\n\n partitionRangeSql (expression: PartitionRangeExpr): string {\n const low = this.sql(expression, 'this');\n const high = this.sql(expression, 'expression');\n return `${low} TO ${high}`;\n }\n\n truncateTableSql (expression: TruncateTableExpr): string {\n const target = expression.args.isDatabase ? 'DATABASE' : 'TABLE';\n const tables = ` ${this.expressions(expression)}`;\n const exists = expression.args.exists ? ' IF EXISTS' : '';\n let onCluster = this.sql(expression, 'cluster');\n onCluster = onCluster ? ` ${onCluster}` : '';\n let identity = this.sql(expression, 'identity');\n identity = identity ? ` ${identity} IDENTITY` : '';\n let option = this.sql(expression, 'option');\n option = option ? ` ${option}` : '';\n let partition = this.sql(expression, 'partition');\n partition = partition ? ` ${partition}` : '';\n return `TRUNCATE ${target}${exists}${tables}${onCluster}${identity}${option}${partition}`;\n }\n\n convertSql (expression: ConvertExpr): string {\n const to = expression.args.this;\n const value = expression.args.expression;\n const style = expression.args.style;\n const safe = expression.args.safe;\n const strict = expression.getArgKey('strict');\n\n if (!to || !value) {\n return '';\n }\n\n assertIsInstanceOf(to, DataTypeExpr);\n let finalTo: DataTypeExpr = to;\n\n // Retrieve length of datatype and override to default if not specified\n if (!seqGet(to.args.expressions ?? [], 0) && this._constructor.PARAMETERIZABLE_TEXT_TYPES.has(to.args.this as DataTypeExprKind)) {\n finalTo = DataTypeExpr.build(to.args.this, {\n expressions: [LiteralExpr.number(30)],\n nested: false,\n }) ?? finalTo;\n }\n\n let transformed: Expression | undefined = undefined;\n const castClass = strict ? CastExpr : TryCastExpr;\n\n // Check whether a conversion with format (T-SQL calls this 'style') is applicable\n if (style instanceof LiteralExpr && style.isInteger) {\n const styleValue = style.name;\n const tsqlDialect = Dialect.get(Dialects.TSQL) as (typeof Dialect & { CONVERT_FORMAT_MAPPING: Record<string | number, string> }) | undefined;\n const convertedStyle = tsqlDialect?.CONVERT_FORMAT_MAPPING?.[styleValue] ?? '';\n\n if (!convertedStyle) {\n this.unsupported(`Unsupported T-SQL 'style' value: ${styleValue}`);\n }\n\n const fmt = LiteralExpr.string(convertedStyle);\n\n if (finalTo.args.this === DataTypeExprKind.DATE) {\n transformed = new StrToDateExpr({\n this: value,\n format: fmt,\n });\n } else if (\n finalTo.args.this === DataTypeExprKind.DATETIME\n || finalTo.args.this === DataTypeExprKind.DATETIME2\n ) {\n transformed = new StrToTimeExpr({\n this: value,\n format: fmt,\n });\n } else if (this._constructor.PARAMETERIZABLE_TEXT_TYPES.has(finalTo.args.this as DataTypeExprKind)) {\n transformed = new castClass({\n this: new TimeToStrExpr({\n this: value,\n format: fmt,\n }),\n to: finalTo,\n safe: safe,\n });\n } else if (finalTo.args.this === DataTypeExprKind.TEXT) {\n transformed = new TimeToStrExpr({\n this: value,\n format: fmt,\n });\n }\n }\n\n if (!transformed) {\n transformed = new castClass({\n this: value,\n to: finalTo,\n safe: safe,\n });\n }\n\n return this.sql(transformed);\n }\n\n jsonPathKeySql (expression: JsonPathKeyExpr): string {\n const thisVal = expression.args.this;\n if (thisVal instanceof JsonPathWildcardExpr) {\n const part = this.jsonPathPart(thisVal);\n return part ? `.${part}` : '';\n }\n if (typeof thisVal === 'string' && this._constructor.SAFE_JSON_PATH_KEY_RE.test(thisVal)) {\n return `.${thisVal}`;\n }\n const part = this.jsonPathPart(typeof thisVal === 'string' ? thisVal : (thisVal instanceof Expression ? thisVal.name ?? '' : ''));\n return (this.quoteJsonPathKeyUsingBrackets && this._constructor.JSON_PATH_BRACKETED_KEY_SUPPORTED)\n ? `[${part}]`\n : `.${part}`;\n }\n\n jsonPathSubscriptSql (expression: JsonPathSubscriptExpr): string {\n const thisVal = expression.args.this;\n const part = this.jsonPathPart(thisVal as ExpressionValue<JsonPathPartExpr>);\n return part ? `[${part}]` : '';\n }\n\n simplifyUnlessLiteral<E extends Expression> (expression: E): E {\n if (!(expression instanceof LiteralExpr)) {\n expression = simplify(expression, { dialect: this.dialect });\n }\n\n return expression;\n }\n\n embedIgnoreNulls (expression: IgnoreNullsExpr | RespectNullsExpr, text: string): string {\n const thisExpr = expression.args.this;\n\n // Check if the current expression is in the unsupported list for the dialect\n if (this._constructor.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS.some((cls) => thisExpr instanceof cls)) {\n this.unsupported(\n `RESPECT/IGNORE NULLS is not supported for ${thisExpr?._constructor.key} in ${this.dialect.constructor.name}`,\n );\n return this.sql(thisExpr);\n }\n\n if (this._constructor.IGNORE_NULLS_IN_FUNC && !expression.meta?.inline) {\n // Sort modifiers: HavingMax -> Order -> Limit\n const mods = [\n ...expression.findAll<HavingMaxExpr | OrderExpr | LimitExpr>([\n HavingMaxExpr,\n OrderExpr,\n LimitExpr,\n ]),\n ].sort((a, b) => {\n const getPriority = (x: Expression) => {\n if (x instanceof HavingMaxExpr) return 0;\n if (x instanceof OrderExpr) return 1;\n return 2;\n };\n return getPriority(a) - getPriority(b);\n });\n\n if (0 < mods.length) {\n const mod = mods[0];\n const newThis = new expression._constructor({ this: mod.args.this?.copy() });\n\n newThis.meta = {\n ...newThis.meta,\n inline: true,\n };\n mod.args.this?.replace(newThis);\n\n return this.sql(expression.args.this);\n }\n\n const aggFunc = expression.find(AggFuncExpr);\n if (aggFunc) {\n // Inject the modifier text inside the function parentheses\n const aggFuncSql = this.sql(aggFunc, undefined, { comment: false }).slice(0, -1) + ` ${text})`;\n return this.maybeComment(aggFuncSql, undefined, aggFunc.comments);\n }\n }\n\n return `${this.sql(expression, 'this')} ${text}`;\n }\n\n replaceLineBreaks (string: string): string {\n if (this.pretty) {\n return string.replace(/\\n/g, this._constructor.SENTINEL_LINE_BREAK);\n }\n return string;\n }\n\n copyParameterSql (expression: CopyParameterExpr): string {\n const option = this.sql(expression, 'this');\n\n if (expression.args.expressions && 0 < expression.args.expressions.length) {\n const upper = option.toUpperCase();\n\n const sep = upper === 'FILE_FORMAT' ? ' ' : ', ';\n\n const op = (upper === 'COPY_OPTIONS' || upper === 'FORMAT_OPTIONS') ? ' ' : ' = ';\n\n const values = this.expressions(expression, {\n flat: true,\n sep,\n });\n return `${option}${op}(${values})`;\n }\n\n const value = this.sql(expression, 'expression');\n\n if (!value) return option;\n\n const op = this._constructor.COPY_PARAMS_EQ_REQUIRED ? ' = ' : ' ';\n\n return `${option}${op}${value}`;\n }\n\n credentialsSql (expression: CredentialsExpr): string {\n const credExpr = expression.args.credentials;\n let credentials = '';\n if (credExpr instanceof LiteralExpr) {\n const credStr = this.sql(expression, 'credentials');\n credentials = credStr ? `CREDENTIALS ${credStr}` : '';\n } else {\n const credStr = this.expressions(expression, {\n key: 'credentials',\n flat: true,\n sep: ' ',\n });\n credentials = credExpr !== undefined && credExpr !== null ? `CREDENTIALS = (${credStr})` : '';\n }\n\n const storage = this.sql(expression, 'storage');\n const storageStr = storage ? `STORAGE_INTEGRATION = ${storage}` : '';\n\n const encryption = this.expressions(expression, {\n key: 'encryption',\n flat: true,\n sep: ' ',\n });\n const encryptionStr = encryption ? ` ENCRYPTION = (${encryption})` : '';\n\n const iamRole = this.sql(expression, 'iamRole');\n const iamRoleStr = iamRole ? `IAM_ROLE ${iamRole}` : '';\n\n const region = this.sql(expression, 'region');\n const regionStr = region ? ` REGION ${region}` : '';\n\n return `${credentials}${storageStr}${encryptionStr}${iamRoleStr}${regionStr}`;\n }\n\n copySql (expression: CopyExpr): string {\n const dialect = this._constructor;\n let thisStr = this.sql(expression, 'this');\n thisStr = dialect.COPY_HAS_INTO_KEYWORD ? ` INTO ${thisStr}` : ` ${thisStr}`;\n\n let credentials = this.sql(expression, 'credentials');\n credentials = credentials ? this.seg(credentials) : '';\n const files = this.expressions(expression, {\n key: 'files',\n flat: true,\n });\n const kind = (files && expression.args.kind) ? this.seg('FROM') : (files ? this.seg('TO') : '');\n\n const copyParamsAreCsv = this.dialect._constructor.COPY_PARAMS_ARE_CSV || false;\n const sep = copyParamsAreCsv ? ', ' : ' ';\n let params = this.expressions(\n expression,\n {\n key: 'params',\n sep,\n newLine: true,\n skipLast: true,\n skipFirst: true,\n indent: dialect.COPY_PARAMS_ARE_WRAPPED,\n },\n );\n\n if (params) {\n if (dialect.COPY_PARAMS_ARE_WRAPPED) {\n params = ` WITH (${params})`;\n } else if (!this.pretty && (files || credentials)) {\n params = ` ${params}`;\n }\n }\n\n return `COPY${thisStr}${kind} ${files}${credentials}${params}`;\n }\n\n semicolonSql (_expression: SemicolonExpr): string {\n return '';\n }\n\n dataDeletionPropertySql (expression: DataDeletionPropertyExpr): string {\n const onStr = expression.args.on ? 'ON' : 'OFF';\n const filterCol = this.sql(expression, 'filterColumn');\n const filterColStr = filterCol ? `FILTER_COLUMN=${filterCol}` : undefined;\n const retentionPeriod = this.sql(expression, 'retentionPeriod');\n const retentionPeriodStr = retentionPeriod ? `RETENTION_PERIOD=${retentionPeriod}` : undefined;\n if (filterColStr || retentionPeriodStr) {\n return `DATA_DELETION=${this.func('ON', [filterColStr, retentionPeriodStr])}`;\n }\n return `DATA_DELETION=${onStr}`;\n }\n\n gapFillSql (expression: GapFillExpr): string {\n let thisSql = this.sql(expression, 'this');\n thisSql = `TABLE ${thisSql}`;\n\n const otherArgs = Object.entries(expression.args)\n .filter(([k]) => k !== 'this')\n .map(([_, v]) => v as string | Expression);\n\n return this.func('GAP_FILL', [thisSql, ...otherArgs]);\n }\n\n scopeResolution (rhs: string, scopeName: string): string {\n return this.func('SCOPE_RESOLUTION', [scopeName || undefined, rhs]);\n }\n\n scopeResolutionSql (expression: ScopeResolutionExpr): string {\n const thisStr = this.sql(expression, 'this');\n const exprNode = expression.args.expression;\n let expr: string;\n if (exprNode instanceof FuncExpr) {\n const exprThis = this.sql(exprNode, 'this');\n const exprArgs = this.formatArgs(exprNode.args.expressions || []);\n expr = `${exprThis}(${exprArgs})`;\n } else {\n expr = this.sql(expression, 'expression');\n }\n return this.scopeResolution(expr, thisStr);\n }\n\n parseJsonSql (expression: ParseJsonExpr): string {\n const parseName = this._constructor.PARSE_JSON_NAME;\n if (parseName === undefined || parseName === null) {\n return this.sql(expression.args.this);\n }\n return this.func(\n parseName,\n [expression.args.this, expression.args.expression],\n );\n }\n\n randSql (expression: RandExpr): string {\n const lower = this.sql(expression, 'lower');\n const upper = this.sql(expression, 'upper');\n if (lower && upper) {\n return `(${upper} - ${lower}) * ${this.func('RAND', [expression.args.this])} + ${lower}`;\n }\n return this.func('RAND', [expression.args.this]);\n }\n\n changesSql (expression: ChangesExpr): string {\n const information = this.sql(expression, 'information');\n const informationStr = `INFORMATION => ${information}`;\n const atBefore = this.sql(expression, 'atBefore');\n const atBeforeStr = atBefore ? `${this.seg('')}${atBefore}` : '';\n const end = this.sql(expression, 'end');\n const endStr = end ? `${this.seg('')}${end}` : '';\n return `CHANGES (${informationStr})${atBeforeStr}${endStr}`;\n }\n\n padSql (expression: PadExpr): string {\n const prefix = expression.args.isLeft ? 'L' : 'R';\n\n let fillPattern = this.sql(expression, 'fillPattern');\n if (!fillPattern && this._constructor.PAD_FILL_PATTERN_IS_REQUIRED) {\n fillPattern = '\\' \\'';\n }\n\n return this.func(`${prefix}PAD`, [\n expression.args.this,\n expression.args.expression,\n fillPattern,\n ]);\n }\n\n summarizeSql (expression: SummarizeExpr): string {\n const table = expression.args.table ? ' TABLE' : '';\n return `SUMMARIZE${table} ${this.sql(expression.args.this as Expression)}`;\n }\n\n explodingGenerateSeriesSql (expression: ExplodingGenerateSeriesExpr): string {\n const generateSeries = new GenerateSeriesExpr({ ...expression.args });\n\n let parent = expression.parent;\n if (parent instanceof AliasExpr || parent instanceof TableAliasExpr) {\n parent = parent.parent;\n }\n\n if (this._constructor.SUPPORTS_EXPLODING_PROJECTIONS && !(parent instanceof TableExpr) && !(parent instanceof UnnestExpr)) {\n return this.sql(new UnnestExpr({ expressions: [generateSeries] }));\n }\n\n if (parent instanceof SelectExpr) {\n this.unsupported('GenerateSeries projection unnesting is not supported.');\n }\n\n return this.sql(generateSeries);\n }\n\n convertTimezoneSql (expression: ConvertTimezoneExpr): string {\n if (this._constructor.SUPPORTS_CONVERT_TIMEZONE) {\n return this.functionFallbackSql(expression);\n }\n\n const sourceTz = expression.args.sourceTz;\n const targetTz = expression.args.targetTz;\n let timestamp = expression.args.timestamp;\n\n if (sourceTz && timestamp) {\n timestamp = new AtTimeZoneExpr({\n this: cast(timestamp, DataTypeExprKind.TIMESTAMPNTZ),\n zone: sourceTz,\n });\n }\n\n const expr = new AtTimeZoneExpr({\n this: timestamp,\n zone: targetTz,\n });\n\n return this.sql(expr);\n }\n\n jsonSql (expression: JsonExpr): string {\n const thisStr = this.sql(expression, 'this');\n const thisResult = thisStr ? ` ${thisStr}` : '';\n\n const _with = expression.args.with;\n\n let withSql = '';\n if (_with === undefined || _with === null) {\n withSql = '';\n } else if (!_with) {\n withSql = ' WITHOUT';\n } else {\n withSql = ' WITH';\n }\n\n const uniqueSql = expression.args.unique ? ' UNIQUE KEYS' : '';\n\n return `JSON${thisResult}${withSql}${uniqueSql}`;\n }\n\n jsonValueSql (expression: JsonValueExpr): string {\n const path = this.sql(expression, 'path');\n let returning = this.sql(expression, 'returning');\n returning = returning ? ` RETURNING ${returning}` : '';\n\n let onCondition = this.sql(expression, 'onCondition');\n onCondition = onCondition ? ` ${onCondition}` : '';\n\n return this.func('JSON_VALUE', [expression.args.this, `${path}${returning}${onCondition}`]);\n }\n\n conditionalInsertSql (expression: ConditionalInsertExpr): string {\n const else_ = expression.args.else ? 'ELSE ' : '';\n const condition = this.sql(expression, 'expression');\n const conditionStr = condition ? `WHEN ${condition} THEN ` : else_;\n const insert = this.sql(expression, 'this').substring('INSERT'.length)\n .trim();\n return `${conditionStr}${insert}`;\n }\n\n multitableInsertsSql (expression: MultitableInsertsExpr): string {\n const kind = this.sql(expression, 'kind');\n const expressions = this.seg(this.expressions(expression, { sep: ' ' }));\n const res = `INSERT ${kind}${expressions}${this.seg(this.sql(expression, 'source'))}`;\n return res;\n }\n\n onConditionSql (expression: OnConditionExpr): string {\n const empty = expression.args.empty;\n let emptyStr = '';\n if (empty instanceof Expression) {\n emptyStr = `DEFAULT ${this.sql(empty)} ON EMPTY`;\n } else {\n emptyStr = this.sql(expression, 'empty');\n }\n\n const error = expression.args.error;\n let errorStr = '';\n if (error instanceof Expression) {\n errorStr = `DEFAULT ${this.sql(error)} ON ERROR`;\n } else {\n errorStr = this.sql(expression, 'error');\n }\n\n if (errorStr && emptyStr) {\n if (this._constructor.ON_CONDITION_EMPTY_BEFORE_ERROR) {\n errorStr = `${emptyStr} ${errorStr}`;\n } else {\n errorStr = `${errorStr} ${emptyStr}`;\n }\n emptyStr = '';\n }\n\n const nullStr = this.sql(expression, 'null');\n\n return `${emptyStr}${errorStr}${nullStr}`;\n }\n\n jsonExtractSql (expression: JsonExtractExpr): string {\n return this.functionFallbackSql(expression);\n }\n\n jsonExtractScalarSql (expression: JsonExtractScalarExpr): string {\n return this.functionFallbackSql(expression);\n }\n\n jsonbExtractSql (expression: JsonbExtractExpr): string {\n return this.functionFallbackSql(expression);\n }\n\n jsonbExtractScalarSql (expression: JsonbExtractScalarExpr): string {\n return this.functionFallbackSql(expression);\n }\n\n jsonExtractQuoteSql (expression: JsonExtractQuoteExpr): string {\n const scalar = expression.args.scalar ? ' ON SCALAR STRING' : '';\n return `${this.sql(expression, 'option')} QUOTES${scalar}`;\n }\n\n jsonExistsSql (expression: JsonExistsExpr): string {\n const thisStr = this.sql(expression, 'this');\n let path = this.sql(expression, 'path');\n\n const passing = this.expressions(expression, { key: 'passing' });\n const passingStr = passing ? ` PASSING ${passing}` : '';\n\n let onCondition = this.sql(expression, 'onCondition');\n onCondition = onCondition ? ` ${onCondition}` : '';\n\n path = `${path}${passingStr}${onCondition}`;\n\n return this.func('JSON_EXISTS', [thisStr, path]);\n }\n\n addArrayAggNullFilter (\n arrayAggSql: string,\n arrayAggExpr: ArrayAggExpr,\n columnExpr: Expression,\n ): string {\n // Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls\n // on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)\n if (\n !(this.dialect._constructor.ARRAY_AGG_INCLUDES_NULLS && arrayAggExpr.args.nullsExcluded)\n ) {\n return arrayAggSql;\n }\n\n const parent = arrayAggExpr.parent;\n if (parent instanceof FilterExpr) {\n const parentCond = (parent.args.expression as WhereExpr | undefined)?.args.this;\n if (!parentCond) return arrayAggSql;\n parentCond.replace(\n parentCond.and(columnExpr.is(null_()).not()),\n );\n } else if (columnExpr.find(ColumnExpr)) {\n // Do not add the filter if the input is not a column (e.g. literal, struct etc)\n // DISTINCT is already present in the agg function, do not propagate it to FILTER as well\n const thisSql = columnExpr instanceof DistinctExpr\n ? this.expressions(columnExpr)\n : this.sql(columnExpr);\n\n arrayAggSql = `${arrayAggSql} FILTER(WHERE ${thisSql} IS NOT NULL)`;\n }\n\n return arrayAggSql;\n }\n\n arrayAggSql (expression: ArrayAggExpr): string {\n const arrayAgg = this.functionFallbackSql(expression);\n const arrayAggThis = expression.args.this instanceof Expression ? expression.args.this : undefined;\n if (!arrayAggThis) return arrayAgg;\n return this.addArrayAggNullFilter(arrayAgg, expression, arrayAggThis);\n }\n\n sliceSql (expression: SliceExpr): string {\n const step = this.sql(expression, 'step');\n const end = this.sql(typeof expression.args.expression === 'number' ? expression.args.expression.toString() : expression.args.expression);\n const begin = this.sql(expression.args.this);\n\n const sql = step ? `${end}:${step}` : end;\n return sql ? `${begin}:${sql}` : `${begin}:`;\n }\n\n applySql (expression: ApplyExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expr = this.sql(expression, 'expression');\n\n return `${thisStr} APPLY(${expr})`;\n }\n\n grantOrRevokeSql (\n expression: GrantExpr | RevokeExpr,\n options: {\n keyword?: string;\n preposition?: string;\n grantOptionPrefix?: string;\n grantOptionSuffix?: string;\n } = {},\n ): string {\n const {\n keyword,\n preposition,\n grantOptionPrefix = '',\n grantOptionSuffix = '',\n } = options;\n\n const privilegesSql = this.expressions(expression, {\n key: 'privileges',\n flat: true,\n });\n\n const kind = this.sql(expression, 'kind');\n const kindStr = kind ? ` ${kind}` : '';\n\n let securable = this.sql(expression, 'securable');\n securable = securable ? ` ${securable}` : '';\n\n const principals = this.expressions(expression, {\n key: 'principals',\n flat: true,\n });\n\n let grantOptionPrefixStr = grantOptionPrefix;\n let grantOptionSuffixStr = grantOptionSuffix;\n if (!expression.args.grantOption) {\n grantOptionPrefixStr = grantOptionSuffixStr = '';\n }\n\n const cascade = this.sql(expression, 'cascade');\n const cascadeStr = cascade ? ` ${cascade}` : '';\n\n return `${keyword} ${grantOptionPrefixStr}${privilegesSql} ON${kindStr}${securable} ${preposition} ${principals}${grantOptionSuffixStr}${cascadeStr}`;\n }\n\n grantSql (expression: GrantExpr): string {\n return this.grantOrRevokeSql(\n expression,\n {\n keyword: 'GRANT',\n preposition: 'TO',\n grantOptionSuffix: ' WITH GRANT OPTION',\n },\n );\n }\n\n revokeSql (expression: RevokeExpr): string {\n return this.grantOrRevokeSql(\n expression,\n {\n keyword: 'REVOKE',\n preposition: 'FROM',\n grantOptionPrefix: 'GRANT OPTION FOR ',\n },\n );\n }\n\n grantPrivilegeSql (expression: GrantPrivilegeExpr): string {\n const thisStr = this.sql(expression, 'this');\n const columns = this.expressions(expression, { flat: true });\n const columnsStr = columns ? `(${columns})` : '';\n\n return `${thisStr}${columnsStr}`;\n }\n\n grantPrincipalSql (expression: GrantPrincipalExpr): string {\n const thisStr = this.sql(expression, 'this');\n\n const kind = this.sql(expression, 'kind');\n const kindStr = kind ? `${kind} ` : '';\n\n return `${kindStr}${thisStr}`;\n }\n\n columnsSql (expression: ColumnsExpr): string {\n const func = this.functionFallbackSql(expression);\n const unpack = expression.args.unpack;\n return unpack ? `*${func}` : func;\n }\n\n overlaySql (expression: OverlayExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expr = this.sql(expression, 'expression');\n const fromSql = this.sql(expression, 'from');\n const forSql = this.sql(expression, 'for');\n const forStr = forSql ? ` FOR ${forSql}` : '';\n return `OVERLAY(${thisStr} PLACING ${expr} FROM ${fromSql}${forStr})`;\n }\n\n toDoubleSql (expression: ToDoubleExpr): string {\n unsupportedArgs.call(this, expression, 'format');\n const castCls = expression.args.safe ? TryCastExpr : CastExpr;\n return this.sql(new castCls({\n this: expression.args.this,\n to: new DataTypeExpr({ this: DataTypeExprKind.DOUBLE }),\n }));\n }\n\n stringSql (expression: StringExpr): string {\n let thisExpr: Expression = expression.args.this as Expression;\n const zone = expression.args.zone;\n\n if (zone) {\n // This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)\n // BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC\n // set for source_tz to transpile the time conversion before the STRING cast\n thisExpr = new ConvertTimezoneExpr({\n sourceTz: new LiteralExpr({\n this: 'UTC',\n isString: true,\n }),\n targetTz: zone,\n timestamp: thisExpr,\n });\n }\n\n return this.sql(cast(thisExpr, 'VARCHAR', { dialect: this.dialect }));\n }\n\n medianSql (expression: MedianExpr): string {\n if (!this._constructor.SUPPORTS_MEDIAN) {\n return this.sql(\n new PercentileContExpr({\n this: expression.args.this as Expression,\n expression: LiteralExpr.number(0.5),\n }),\n );\n }\n return this.functionFallbackSql(expression);\n }\n\n overflowTruncateBehaviorSql (expression: OverflowTruncateBehaviorExpr): string {\n const filler = this.sql(expression, 'this');\n const fillerStr = filler ? ` ${filler}` : '';\n const withCount = expression.args.withCount ? 'WITH COUNT' : 'WITHOUT COUNT';\n return `TRUNCATE${fillerStr} ${withCount}`;\n }\n\n unixSecondsSql (expression: UnixSecondsExpr): string {\n if (this._constructor.SUPPORTS_UNIX_SECONDS) {\n return this.functionFallbackSql(expression);\n }\n const startTs = cast(LiteralExpr.string('1970-01-01 00:00:00+00'), DataTypeExprKind.TIMESTAMPTZ);\n return this.sql(\n new TimestampDiffExpr({\n this: expression.args.this,\n expression: startTs,\n unit: new VarExpr({ this: 'SECONDS' }),\n }),\n );\n }\n\n arraySizeSql (expression: ArraySizeExpr): string {\n let dim = expression.args.expression;\n\n // For dialects that don't support the dimension arg, we can safely transpile its default value (1st dimension)\n if (dim && this._constructor.ARRAY_SIZE_DIM_REQUIRED === undefined) {\n if (!(dim.isInteger && dim.name === '1')) {\n this.unsupported('Cannot transpile dimension argument for ARRAY_LENGTH');\n }\n dim = undefined;\n }\n\n // If dimension is required but not specified, default initialize it\n if (this._constructor.ARRAY_SIZE_DIM_REQUIRED && !dim) {\n dim = new LiteralExpr({\n this: '1',\n isString: false,\n });\n }\n\n return this.func(this._constructor.ARRAY_SIZE_NAME, [expression.args.this, dim]);\n }\n\n attachSql (expression: AttachExpr): string {\n const thisStr = this.sql(expression, 'this');\n const existsSql = expression.args.exists ? ' IF NOT EXISTS' : '';\n let expressions = this.expressions(expression);\n expressions = expressions ? ` (${expressions})` : '';\n return `ATTACH${existsSql} ${thisStr}${expressions}`;\n }\n\n detachSql (expression: DetachExpr): string {\n const thisStr = this.sql(expression, 'this');\n const existsSql = expression.args.exists ? ' DATABASE IF EXISTS' : '';\n return `DETACH${existsSql} ${thisStr}`;\n }\n\n attachOptionSql (expression: AttachOptionExpr): string {\n const thisStr = this.sql(expression, 'this');\n let value = this.sql(expression, 'expression');\n value = value ? ` ${value}` : '';\n return `${thisStr}${value}`;\n }\n\n watermarkColumnConstraintSql (expression: WatermarkColumnConstraintExpr): string {\n return `WATERMARK FOR ${this.sql(expression, 'this')} AS ${this.sql(expression, 'expression')}`;\n }\n\n encodePropertySql (expression: EncodePropertyExpr): string {\n const encode = expression.args.key ? 'KEY ENCODE' : 'ENCODE';\n let encodeSql = `${encode} ${this.sql(expression, 'this')}`;\n\n const properties = expression.args.properties;\n if (properties) {\n assertIsInstanceOf(properties, PropertiesExpr);\n encodeSql = `${encodeSql} ${this.properties(properties)}`;\n }\n\n return encodeSql;\n }\n\n includePropertySql (expression: IncludePropertyExpr): string {\n const thisStr = this.sql(expression, 'this');\n let include = `INCLUDE ${thisStr}`;\n\n const columnDef = this.sql(expression, 'columnDef');\n if (columnDef) {\n include = `${include} ${columnDef}`;\n }\n\n const alias = this.sql(expression, 'alias');\n if (alias) {\n include = `${include} AS ${alias}`;\n }\n\n return include;\n }\n\n xmlElementSql (expression: XmlElementExpr): string {\n const prefix = expression.args.evalname ? 'EVALNAME' : 'NAME';\n const name = `${prefix} ${this.sql(expression, 'this')}`;\n return this.func('XmlELEMENT', [name, ...(expression.args.expressions || [])]);\n }\n\n xmlKeyValueOptionSql (expression: XmlKeyValueOptionExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expr = this.sql(expression, 'expression');\n const exprStr = expr ? `(${expr})` : '';\n return `${thisStr}${exprStr}`;\n }\n\n partitionByRangePropertySql (expression: PartitionByRangePropertyExpr): string {\n const partitions = this.expressions(expression, { key: 'partitionExpressions' });\n const create = this.expressions(expression, { key: 'createExpressions' });\n return `PARTITION BY RANGE ${this.wrap(partitions)} ${this.wrap(create)}`;\n }\n\n partitionByRangePropertyDynamicSql (expression: PartitionByRangePropertyDynamicExpr): string {\n const start = this.sql(expression, 'start');\n const end = this.sql(expression, 'end');\n\n const every = expression.args.every;\n if (every instanceof IntervalExpr && every.args.this?.isString) {\n every.args.this.replace(LiteralExpr.number(every.name));\n }\n\n const everySql = this.sql(every as Expression);\n return `START(${start}) END(${end}) EVERY(${everySql})`;\n }\n\n unpivotColumnsSql (expression: UnpivotColumnsExpr): string {\n const name = this.sql(expression, 'this');\n const values = this.expressions(expression, { flat: true });\n\n return `NAME ${name} VALUE ${values}`;\n }\n\n analyzeSampleSql (expression: AnalyzeSampleExpr): string {\n const kind = this.sql(expression, 'kind');\n const sample = this.sql(expression, 'sample');\n return `SAMPLE ${sample} ${kind}`;\n }\n\n analyzeStatisticsSql (expression: AnalyzeStatisticsExpr): string {\n const kind = this.sql(expression, 'kind');\n let option = this.sql(expression, 'option');\n option = option ? ` ${option}` : '';\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n let columns = this.expressions(expression);\n columns = columns ? ` ${columns}` : '';\n return `${kind}${option} STATISTICS${thisStr}${columns}`;\n }\n\n analyzeHistogramSql (expression: AnalyzeHistogramExpr): string {\n const thisStr = this.sql(expression, 'this');\n const columns = this.expressions(expression);\n let innerExpression = this.sql(expression, 'expression');\n innerExpression = innerExpression ? ` ${innerExpression}` : '';\n let updateOptions = this.sql(expression, 'updateOptions');\n updateOptions = updateOptions ? ` ${updateOptions} UPDATE` : '';\n return `${thisStr} HISTOGRAM ON ${columns}${innerExpression}${updateOptions}`;\n }\n\n analyzeDeleteSql (expression: AnalyzeDeleteExpr): string {\n let kind = this.sql(expression, 'kind');\n kind = kind ? ` ${kind}` : '';\n return `DELETE${kind} STATISTICS`;\n }\n\n analyzeListChainedRowsSql (expression: AnalyzeListChainedRowsExpr): string {\n const innerExpression = this.sql(expression, 'expression');\n return `LIST CHAINED ROWS${innerExpression}`;\n }\n\n analyzeValidateSql (expression: AnalyzeValidateExpr): string {\n const kind = this.sql(expression, 'kind');\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n const innerExpression = this.sql(expression, 'expression');\n return `VALIDATE ${kind}${thisStr}${innerExpression}`;\n }\n\n analyzeSql (expression: AnalyzeExpr): string {\n let options = this.expressions(expression, {\n key: 'options',\n sep: ' ',\n });\n options = options ? ` ${options}` : '';\n let kind = this.sql(expression, 'kind');\n kind = kind ? ` ${kind}` : '';\n let thisStr = this.sql(expression, 'this');\n thisStr = thisStr ? ` ${thisStr}` : '';\n let mode = this.sql(expression, 'mode');\n mode = mode ? ` ${mode}` : '';\n let properties = this.sql(expression, 'properties');\n properties = properties ? ` ${properties}` : '';\n let partition = this.sql(expression, 'partition');\n partition = partition ? ` ${partition}` : '';\n let innerExpression = this.sql(expression, 'expression');\n innerExpression = innerExpression ? ` ${innerExpression}` : '';\n return `ANALYZE${options}${kind}${thisStr}${mode}${properties}${partition}${innerExpression}`;\n }\n\n xmlTableSql (expression: XmlTableExpr): string {\n const thisStr = this.sql(expression, 'this');\n const namespaces = this.expressions(expression, { key: 'namespaces' });\n const namespacesStr = namespaces ? `XMLNAMESPACES(${namespaces}), ` : '';\n const passing = this.expressions(expression, { key: 'passing' });\n const passingStr = passing ? `${this.sep()}PASSING${this.seg(passing)}` : '';\n const columns = this.expressions(expression, { key: 'columns' });\n const columnsStr = columns ? `${this.sep()}COLUMNS${this.seg(columns)}` : '';\n const byRef = expression.args.byRef ? `${this.sep()}RETURNING SEQUENCE BY REF` : '';\n return `XMLTABLE(${this.sep('')}${this.indent(namespacesStr + thisStr + passingStr + byRef + columnsStr)}${this.seg(')', '')}`;\n }\n\n xmlNamespaceSql (expression: XmlNamespaceExpr): string {\n const thisStr = this.sql(expression, 'this');\n return expression.args.this instanceof AliasExpr ? thisStr : `DEFAULT ${thisStr}`;\n }\n\n exportSql (expression: ExportExpr): string {\n const thisStr = this.sql(expression, 'this');\n const connection = this.sql(expression, 'connection');\n const connectionStr = connection ? `WITH CONNECTION ${connection} ` : '';\n const options = this.sql(expression, 'options');\n return `EXPORT DATA ${connectionStr}${options} AS ${thisStr}`;\n }\n\n declareSql (expression: DeclareExpr): string {\n return `DECLARE ${this.expressions(expression, { flat: true })}`;\n }\n\n declareItemSql (expression: DeclareItemExpr): string {\n const variable = this.sql(expression, 'this');\n const defaultVal = this.sql(expression, 'default');\n const defaultStr = defaultVal ? ` = ${defaultVal}` : '';\n\n let kind = this.sql(expression, 'kind');\n if (expression.args.kind instanceof SchemaExpr) {\n kind = `TABLE ${kind}`;\n }\n\n return `${variable} AS ${kind}${defaultStr}`;\n }\n\n recursiveWithSearchSql (expression: RecursiveWithSearchExpr): string {\n const kind = this.sql(expression, 'kind');\n const thisStr = this.sql(expression, 'this');\n const set = this.sql(expression, 'expression');\n const using = this.sql(expression, 'using');\n const usingStr = using ? ` USING ${using}` : '';\n\n const kindSql = kind === 'CYCLE' ? kind : `SEARCH ${kind} FIRST BY`;\n\n return `${kindSql} ${thisStr} SET ${set}${usingStr}`;\n }\n\n parameterizedAggSql (expression: ParameterizedAggExpr): string {\n const params = this.expressions(expression, {\n key: 'params',\n flat: true,\n });\n const name = expression.name || '';\n return this.func(name, expression.args.expressions || []) + `(${params})`;\n }\n\n anonymousAggFuncSql (expression: AnonymousAggFuncExpr): string {\n const name = expression.name || '';\n return this.func(name, expression.args.expressions || []);\n }\n\n combinedAggFuncSql (expression: CombinedAggFuncExpr): string {\n return this.anonymousAggFuncSql(expression);\n }\n\n combinedParameterizedAggSql (expression: CombinedParameterizedAggExpr): string {\n return this.parameterizedAggSql(expression);\n }\n\n showSql (_expression: ShowExpr): string {\n this.unsupported('Unsupported SHOW statement');\n return '';\n }\n\n installSql (_expression: InstallExpr): string {\n this.unsupported('Unsupported INSTALL statement');\n return '';\n }\n\n getPutSql (expression: PutExpr | GetExpr): string {\n const props = expression.args.properties;\n if (props) {\n assertIsInstanceOf(props, PropertiesExpr);\n }\n const propsSql = props\n ? this.properties(props, {\n prefix: ' ',\n sep: ' ',\n wrapped: false,\n })\n : '';\n const thisStr = this.sql(expression, 'this');\n const target = this.sql(expression, 'target');\n\n if (expression instanceof PutExpr) {\n return `PUT ${thisStr} ${target}${propsSql}`;\n } else {\n return `GET ${target} ${thisStr}${propsSql}`;\n }\n }\n\n translateCharactersSql (expression: TranslateCharactersExpr): string {\n const thisStr = this.sql(expression, 'this');\n const expr = this.sql(expression, 'expression');\n const withError = expression.args.withError ? ' WITH ERROR' : '';\n return `TRANSLATE(${thisStr} USING ${expr}${withError})`;\n }\n\n decodeCaseSql (expression: DecodeCaseExpr): string {\n if (this._constructor.SUPPORTS_DECODE_CASE) {\n return this.func('DECODE', expression.args.expressions ?? []);\n }\n\n const [baseExpression, ...restExpressions] = expression.args.expressions ?? [];\n\n const ifs: IfExpr[] = [];\n for (let i = 0; i < restExpressions.length - 1; i += 2) {\n const search = restExpressions[i];\n const result = restExpressions[i + 1];\n\n if (search instanceof LiteralExpr) {\n ifs.push(new IfExpr({\n this: baseExpression.eq(search),\n true: result,\n }));\n } else if (search instanceof NullExpr) {\n ifs.push(new IfExpr({\n this: baseExpression.is(null_()),\n true: result,\n }));\n } else {\n let finalSearch = search;\n if (finalSearch instanceof BinaryExpr) {\n finalSearch = paren(finalSearch);\n }\n\n const cond = or(\n [baseExpression.eq(finalSearch), and([baseExpression.is(null_()), finalSearch.is(null_())], { copy: false })],\n { copy: false },\n );\n ifs.push(new IfExpr({\n this: cond,\n true: result,\n }));\n }\n }\n\n const defaultExpr = restExpressions.length % 2 === 1 ? restExpressions[restExpressions.length - 1] : undefined;\n const caseExpr = new CaseExpr({\n ifs,\n default: defaultExpr,\n });\n\n return this.sql(caseExpr);\n }\n\n semanticViewSql (expression: SemanticViewExpr): string {\n const thisSql = this.sql(expression, 'this');\n const thisSeg = this.seg(thisSql, '');\n\n const dimensionsExpr = this.expressions(expression, {\n key: 'dimensions',\n dynamic: true,\n skipFirst: true,\n skipLast: true,\n });\n const dimensions = dimensionsExpr ? this.seg(`DIMENSIONS ${dimensionsExpr}`) : '';\n\n const metricsExpr = this.expressions(expression, {\n key: 'metrics',\n dynamic: true,\n skipFirst: true,\n skipLast: true,\n });\n const metrics = metricsExpr ? this.seg(`METRICS ${metricsExpr}`) : '';\n\n const factsExpr = this.expressions(expression, {\n key: 'facts',\n dynamic: true,\n skipFirst: true,\n skipLast: true,\n });\n const facts = factsExpr ? this.seg(`FACTS ${factsExpr}`) : '';\n\n const whereExpr = this.sql(expression, 'where');\n const where = whereExpr ? this.seg(`WHERE ${whereExpr}`) : '';\n\n const body = this.indent(thisSeg + metrics + dimensions + facts + where, {\n skipFirst: true,\n });\n\n return `SEMANTIC_VIEW(${body}${this.seg(')', '')}`;\n }\n\n getExtractSql (expression: GetExtractExpr): string {\n let thisExpr = expression.args.this;\n const expr = expression.args.expression;\n\n if (!thisExpr) return '';\n if (!thisExpr.type || !expression.type) {\n thisExpr = annotateTypes(thisExpr, { dialect: this.dialect });\n }\n\n if (thisExpr.isType([DataTypeExprKind.ARRAY, DataTypeExprKind.MAP])) {\n return this.sql(new BracketExpr({\n this: thisExpr,\n expressions: expr ? [expr] : [],\n }));\n }\n\n return this.sql(\n new JsonExtractExpr({\n this: thisExpr,\n expression: this.dialect.toJsonPath(expr),\n }),\n );\n }\n\n dateFromUnixDateSql (expression: DateFromUnixDateExpr): string {\n return this.sql(\n new DateAddExpr({\n this: cast(LiteralExpr.string('1970-01-01'), DataTypeExprKind.DATE),\n expression: expression.args.this as Expression,\n unit: var_('DAY'),\n }),\n );\n }\n\n spaceSql (expression: SpaceExpr): string {\n return this.func('REPEAT', [literal(' '), expression.args.this]);\n }\n\n buildPropertySql (expression: BuildPropertyExpr): string {\n return `BUILD ${this.sql(expression, 'this')}`;\n }\n\n refreshTriggerPropertySql (expression: RefreshTriggerPropertyExpr): string {\n const method = this.sql(expression, 'method');\n const kind = expression.args.kind;\n if (!kind) {\n return `REFRESH ${method}`;\n }\n\n const every = this.sql(expression, 'every');\n const unit = this.sql(expression, 'unit');\n const everyStr = every ? ` EVERY ${every} ${unit}` : '';\n let starts = this.sql(expression, 'starts');\n starts = starts ? ` STARTS ${starts}` : '';\n\n return `REFRESH ${method} ON ${kind}${everyStr}${starts}`;\n }\n\n modelAttributeSql (_expression: ModelAttributeExpr): string {\n this.unsupported('The model!attribute syntax is not supported');\n return '';\n }\n\n directoryStageSql (expression: DirectoryStageExpr): string {\n return this.func('DIRECTORY', [expression.args.this]);\n }\n\n uuidSql (expression: UuidExpr): string {\n const uuidFuncSql = this.func('UUID', []);\n const isString = expression.args.isString;\n if (isString && !this.dialect._constructor.UUID_IS_STRING_TYPE) {\n return this.sql(cast(var_(uuidFuncSql) as Expression, DataTypeExprKind.VARCHAR));\n }\n return uuidFuncSql;\n }\n\n initcapSql (expression: InitcapExpr): string {\n let delimiters = expression.args.expression;\n\n if (delimiters) {\n if (!this.dialect._constructor.INITCAP_SUPPORTS_CUSTOM_DELIMITERS) {\n // Dialect doesn't support custom delimiters - always omit them\n delimiters = undefined;\n } else if (\n // Do not generate delimiters arg if we are round-tripping from default delimiters\n delimiters.isString\n && delimiters.args.this === this.dialect._constructor.INITCAP_DEFAULT_DELIMITER_CHARS\n ) {\n delimiters = undefined;\n }\n }\n\n return this.func('INITCAP', [expression.args.this, delimiters]);\n }\n\n localtimeSql (expression: LocaltimeExpr): string {\n const thisArg = expression.args.this;\n return thisArg ? this.func('LOCALTIME', [thisArg]) : 'LOCALTIME';\n }\n\n localtimestampSql (expression: LocaltimestampExpr): string {\n const thisArg = expression.args.this;\n return thisArg ? this.func('LOCALTIMESTAMP', [thisArg]) : 'LOCALTIMESTAMP';\n }\n\n weekStartSql (expression: WeekStartExpr): string {\n const thisExpr = expression.args.this;\n const thisName = thisExpr?.name.toUpperCase();\n if (this.dialect._constructor.WEEK_OFFSET === -1 && thisName === 'SUNDAY') {\n return 'WEEK';\n }\n return this.func('WEEK', [thisExpr]);\n }\n\n chrSql (expression: Expression, options: { name?: string } = {}): string {\n const { name = 'CHR' } = options;\n const thisStr = this.expressions(expression);\n const charset = this.sql(expression, 'charset');\n const using = charset ? ` USING ${charset}` : '';\n return this.func(name, [thisStr + using]);\n }\n\n blockSql (expression: Expression): string {\n const expressions = this.expressions(expression, {\n sep: '; ',\n flat: true,\n });\n return expressions ? `${expressions}` : '';\n }\n\n tableSampleSql (expression: TableSampleExpr, options: { tablesampleKeyword?: string } = {}): string {\n const { tablesampleKeyword } = options;\n let method = this.sql(expression, 'method');\n method = method && this._constructor.TABLESAMPLE_WITH_METHOD ? `${method} ` : '';\n const numerator = this.sql(expression, 'bucketNumerator');\n const denominator = this.sql(expression, 'bucketDenominator');\n let field = this.sql(expression, 'bucketField');\n field = field ? ` ON ${field}` : '';\n const bucket = numerator ? `BUCKET ${numerator} OUT OF ${denominator}${field}` : '';\n let seed = this.sql(expression, 'seed');\n seed = seed ? ` ${this._constructor.TABLESAMPLE_SEED_KEYWORD} (${seed})` : '';\n\n let size = this.sql(expression, 'size');\n if (size && this._constructor.TABLESAMPLE_SIZE_IS_ROWS) {\n size = `${size} ROWS`;\n }\n\n let percent = this.sql(expression, 'percent');\n if (percent && !this.dialect._constructor.TABLESAMPLE_SIZE_IS_PERCENT) {\n percent = `${percent} PERCENT`;\n }\n\n let expr = `${bucket}${percent}${size}`;\n if (this._constructor.TABLESAMPLE_REQUIRES_PARENS) {\n expr = `(${expr})`;\n }\n\n const keyword = tablesampleKeyword || this._constructor.TABLESAMPLE_KEYWORDS;\n return ` ${keyword} ${method}${expr}${seed}`;\n }\n\n maskingPolicyColumnConstraintSql (expression: MaskingPolicyColumnConstraintExpr): string {\n const thisStr = this.sql(expression, 'this');\n let expressions = this.expressions(expression, { flat: true });\n expressions = expressions ? ` USING (${expressions})` : '';\n return `MASKING POLICY ${thisStr}${expressions}`;\n }\n\n uniqueKeyPropertySql (expression: UniqueKeyPropertyExpr, options: { prefix?: string } = {}): string {\n const { prefix = 'UNIQUE KEY' } = options;\n return `${prefix} (${this.expressions(expression, { flat: true })})`;\n }\n\n /**\n * Format the time expression using the dialect's inverse time mapping.\n */\n formatTime (\n expression: Expression,\n inverseTimeMapping?: Record<string, string>,\n inverseTimeTrie?: TrieNode,\n ): string | undefined {\n const dialectCls = this.dialect._constructor;\n const mapping = inverseTimeMapping || dialectCls.INVERSE_TIME_MAPPING;\n const trie = inverseTimeTrie || dialectCls.INVERSE_TIME_TRIE;\n return formatTime(\n this.sql(expression, 'format'),\n mapping,\n trie,\n );\n }\n\n ceilSql (expression: CeilExpr): string {\n return this.ceilFloor(expression);\n }\n\n floorSql (expression: FloorExpr): string {\n return this.ceilFloor(expression);\n }\n\n offsetLimitModifiers (expression: Expression, options: { fetch: boolean }, limit: Expression | undefined): string[] {\n const { fetch } = options;\n return [fetch ? this.sql(expression, 'offset') : this.sql(limit), fetch ? this.sql(limit) : this.sql(expression, 'offset')];\n }\n}\n\nexport function generate (expression: Expression, opts?: GeneratorOptions): string {\n const generator = new Generator(opts);\n return generator.generate(expression);\n}\n","// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/dialects/dialect.py\n\nimport type {\n JsonbExtractScalarExpr,\n JsonExtractScalarExpr,\n TimeSubExpr,\n TimestampSubExpr,\n TsOrDsDiffExpr,\n ApproxDistinctExpr,\n ILikeExpr,\n CurrentDateExpr,\n WithExpr,\n TableSampleExpr,\n PivotExpr,\n CommentColumnConstraintExpr,\n MapFromEntriesExpr,\n PropertyExpr,\n StrPositionExpr,\n StructExtractExpr,\n ArrayAppendExpr,\n ArrayPrependExpr,\n ArrayConcatExpr,\n MapExpr,\n VarMapExpr,\n MonthsBetweenExpr,\n UnixToStrExpr,\n StrToUnixExpr,\n RegexpReplaceExpr,\n RegexpExtractAllExpr,\n FuncExpr,\n ConcatWsExpr,\n ConcatExpr,\n StrToTimeExpr,\n TrimExpr,\n CountIfExpr,\n MaxExpr,\n MinExpr,\n EncodeExpr,\n DecodeExpr,\n DateStrToDateExpr,\n TimeStrToTimeExpr,\n RightExpr,\n LeftExpr,\n DatetimeExpr,\n TimeExpr,\n TimestampExpr,\n Sha2Expr,\n DatetimeDiffExpr,\n TimestampDiffExpr,\n MakeIntervalExpr,\n GroupConcatExpr,\n ArrayCompactExpr,\n GetbitExpr,\n TimeUnitExpr,\n GeneratedAsIdentityColumnConstraintExpr,\n ArgMaxExpr,\n ArgMinExpr,\n MergeExpr,\n AnyValueExpr,\n XorExpr,\n JsonExtractExprArgs,\n RegexpExtractExprArgs,\n GenerateDateArrayExpr,\n ToNumberExpr,\n Sha2DigestExpr,\n ExpressionValue,\n DecodeCaseExpr,\n} from '../expressions';\nimport {\n TruncExpr,\n SelectExpr,\n JsonbExtractExpr,\n DatetimeSubExpr,\n RegexpExtractExpr,\n PosexplodeExpr,\n TableAliasExpr,\n ExplodeExpr,\n KwargExpr,\n ArrayRemoveExpr,\n JsonPathPartExpr,\n LimitExpr,\n PlaceholderExpr,\n JoinExprKind,\n UpdateExpr,\n InsertExpr,\n TupleExpr,\n WhenExpr,\n GenerateSeriesExpr,\n DateDiffExpr,\n DayExpr,\n LastDayExpr,\n DivExpr,\n ParenExpr,\n SAFE_IDENTIFIER_RE,\n Expression,\n AddExpr,\n alias,\n AliasExpr,\n AndExpr,\n AnonymousExpr,\n ArrayExpr,\n ArrayFilterExpr,\n AtTimeZoneExpr,\n BitwiseAndExpr,\n BitwiseRightShiftExpr,\n CastExpr,\n CaseExpr,\n ColumnExpr,\n DataTypeExpr,\n DataTypeExprKind,\n DateAddExpr,\n DateSubExpr,\n DateTruncExpr,\n DatetimeAddExpr,\n DPipeExpr,\n EqExpr,\n EscapeExpr,\n ExpressionKey,\n GtExpr,\n GteExpr,\n IdentifierExpr,\n IfExpr,\n IntervalExpr,\n IsExpr,\n JoinExpr,\n JsonExtractExpr,\n JsonPathExpr,\n JsonPathKeyExpr,\n JsonPathRootExpr,\n JsonPathSubscriptExpr,\n JsonPathWildcardExpr,\n LambdaExpr,\n LateralExpr,\n LengthExpr,\n LikeExpr,\n LiteralExpr,\n LowerExpr,\n LtExpr,\n LteExpr,\n NeqExpr,\n NotExpr,\n NullExpr,\n OrderExpr,\n OrExpr,\n ParseJsonExpr,\n QueryExpr,\n ReplaceExpr,\n select,\n SubExpr,\n SubstringExpr,\n TimeAddExpr,\n TimestampAddExpr,\n TimestampFromPartsExpr,\n TimestampTruncExpr,\n TimeToStrExpr,\n ToCharExpr,\n toIdentifier,\n TsOrDsAddExpr,\n UnnestExpr,\n VarExpr,\n WithinGroupExpr,\n cast,\n var_,\n InExpr,\n CoalesceExpr,\n DistinctExpr,\n DataTypeParamExpr,\n StarExpr,\n isType,\n null_,\n and,\n or,\n} from '../expressions';\nimport {\n cache,\n assertIsInstanceOf, isInstanceOf,\n enumFromString,\n} from '../port_internals';\nimport { annotateTypes } from '../optimizer/annotate_types';\nimport type { TokenizerOptions } from '../tokens';\nimport {\n TokenType, Tokenizer,\n} from '../tokens';\nimport type { ParseOptions } from '../parser';\nimport { Parser } from '../parser';\nimport {\n newTrie, type TrieNode,\n} from '../trie';\nimport {\n JsonPathTokenizer, parse as parseJsonPath,\n} from '../jsonpath';\nimport type {\n GeneratorOptions, TranspileOptions,\n} from '../generator';\nimport {\n Generator, unsupportedArgs,\n} from '../generator';\nimport {\n ensureList,\n isInt,\n seqGet,\n suggestClosestMatchAndFail, toBool,\n} from '../helper';\nimport {\n formatTime, subsecondPrecision, TIMEZONES,\n} from '../time';\nimport {\n DialectTyping, type ExpressionMetadata,\n} from '../typing';\n\n// Type aliases for common expression type unions\nexport type DateAddOrDiff =\n | DateAddExpr\n | DateDiffExpr\n | DateSubExpr\n | TsOrDsAddExpr\n | TsOrDsDiffExpr;\n\nexport type DateAddOrSub =\n | DateAddExpr\n | TsOrDsAddExpr\n | DateSubExpr;\n\nexport type JsonExtractType =\n | JsonExtractExpr\n | JsonExtractScalarExpr\n | JsonbExtractExpr\n | JsonbExtractScalarExpr;\n\nexport type DatetimeDelta =\n | DateAddExpr\n | DatetimeAddExpr\n | DatetimeSubExpr\n | TimeAddExpr\n | TimeSubExpr\n | TimestampAddExpr\n | TimestampSubExpr\n | TsOrDsAddExpr;\n\nexport const DATETIME_ADD = [\n DateAddExpr,\n TimeAddExpr,\n DatetimeAddExpr,\n TsOrDsAddExpr,\n TimestampAddExpr,\n] as const;\n\n// Type aliases for dialect configuration properties\nexport enum NormalizeFunctions {\n NONE = '',\n UPPER = 'upper',\n LOWER = 'lower',\n}\n\nexport enum NullOrdering {\n NULLS_ARE_SMALL = 'nulls_are_small',\n NULLS_ARE_LARGE = 'nulls_are_large',\n NULLS_ARE_LAST = 'nulls_are_last',\n}\n\nexport enum NullOrderingSupported {\n SUPPORTED = 'supported',\n PARTIAL = 'partial',\n UNSUPPORTED = 'unsupported',\n}\n\nexport interface DialectOptions {\n version?: number | string;\n normalizationStrategy?: NormalizationStrategy;\n [index: string]: boolean | string | number | undefined;\n}\n\n/**\n * Base unescaped sequences that are common across dialects.\n */\nconst BASE_UNESCAPED_SEQUENCES: Record<string, string> = {\n '\\\\a': '\\x07',\n '\\\\b': '\\b',\n '\\\\f': '\\f',\n '\\\\n': '\\n',\n '\\\\r': '\\r',\n '\\\\t': '\\t',\n '\\\\v': '\\v',\n '\\\\\\\\': '\\\\',\n};\n\nexport const PLUGIN_GROUP_NAME = 'sqlglot.dialects';\n\n/**\n * Dialects supported by SQLGlot.\n * @enum\n */\nexport enum Dialects {\n DIALECT = '',\n\n ATHENA = 'athena',\n BIGQUERY = 'bigquery',\n CLICKHOUSE = 'clickhouse',\n DATABRICKS = 'databricks',\n DORIS = 'doris',\n DREMIO = 'dremio',\n DRILL = 'drill',\n DRUID = 'druid',\n DUCKDB = 'duckdb',\n DUNE = 'dune',\n FABRIC = 'fabric',\n HIVE = 'hive',\n MATERIALIZE = 'materialize',\n MYSQL = 'mysql',\n ORACLE = 'oracle',\n POSTGRES = 'postgres',\n PRESTO = 'presto',\n PRQL = 'prql',\n REDSHIFT = 'redshift',\n RISINGWAVE = 'risingwave',\n SINGLESTORE = 'singlestore',\n SNOWFLAKE = 'snowflake',\n SOLR = 'solr',\n SPARK = 'spark',\n SPARK2 = 'spark2',\n SQLITE = 'sqlite',\n STARROCKS = 'starrocks',\n TABLEAU = 'tableau',\n TERADATA = 'teradata',\n TRINO = 'trino',\n TSQL = 'tsql',\n EXASOL = 'exasol',\n}\n\n/**\n * Specifies the strategy according to which identifiers should be normalized.\n */\nexport enum NormalizationStrategy {\n /** Unquoted identifiers are lowercased. */\n LOWERCASE = 'lowercase',\n /** Unquoted identifiers are uppercased. */\n UPPERCASE = 'uppercase',\n /** Always case-sensitive, regardless of quotes. */\n CASE_SENSITIVE = 'caseSensitive',\n /** Always case-insensitive (lowercase), regardless of quotes. */\n CASE_INSENSITIVE = 'caseInsensitive',\n /** Always case-insensitive (uppercase), regardless of quotes. */\n CASE_INSENSITIVE_UPPERCASE = 'caseInsensitiveUppercase',\n}\n\nexport type DialectType = string | Dialect | typeof Dialect;\n\n/**\n * Base dialect class for SQL parsing and generation.\n *\n * Dialect = sqlglot's _Dialect (metaclass) + Dialect\n */\nexport class Dialect {\n static DIALECT_NAME = Dialects.DIALECT;\n\n /** The base index offset for arrays. */\n static INDEX_OFFSET = 0;\n\n /** First day of the week in DATE_TRUNC(week). Defaults to 0 (Monday). -1 would be Sunday. */\n static WEEK_OFFSET = 0;\n\n /** Whether `UNNEST` table aliases are treated as column aliases. */\n static UNNEST_COLUMN_ONLY = false;\n\n /** Whether the table alias comes after tablesample. */\n static ALIAS_POST_TABLESAMPLE = false;\n\n /** Whether a size in the table sample clause represents percentage. */\n static TABLESAMPLE_SIZE_IS_PERCENT = false;\n\n static NORMALIZATION_STRATEGY = NormalizationStrategy.LOWERCASE;\n\n /** Whether an unquoted identifier can start with a digit. */\n static IDENTIFIERS_CAN_START_WITH_DIGIT = false;\n\n /** Whether the DPIPE token (`||`) is a string concatenation operator. */\n static DPIPE_IS_STRING_CONCAT = true;\n\n /** Whether `CONCAT`'s arguments must be strings. */\n static STRICT_STRING_CONCAT = false;\n\n /** Whether user-defined data types are supported. */\n static SUPPORTS_USER_DEFINED_TYPES = true;\n\n /** Whether `SEMI` or `ANTI` joins are supported. */\n static SUPPORTS_SEMI_ANTI_JOIN = true;\n\n /** Whether the old-style outer join (+) syntax is supported. */\n static get SUPPORTS_COLUMN_JOIN_MARKS (): boolean {\n return !!this.tokenizerClass.KEYWORDS['(+)'];\n }\n\n /** Separator of COPY statement parameters. */\n static COPY_PARAMS_ARE_CSV = true;\n\n /**\n * Determines how function names are going to be normalized.\n * Possible values:\n * NormalizeFunctions.UPPER or true: Convert names to uppercase.\n * NormalizeFunctions.LOWER: Convert names to lowercase.\n * false: Disables function name normalization.\n */\n static NORMALIZE_FUNCTIONS: NormalizeFunctions = NormalizeFunctions.UPPER;\n\n /**\n * Whether the name of the function should be preserved inside the node's metadata.\n */\n static PRESERVE_ORIGINAL_NAMES = false;\n\n /**\n * Whether the base comes first in the `LOG` function.\n * Possible values: true, false, undefined (two arguments are not supported by `LOG`)\n */\n static LOG_BASE_FIRST: boolean | undefined = true;\n\n /**\n * Default `NULL` ordering method to use if not explicitly set.\n * Possible values: NullOrdering.NULLS_ARE_SMALL, NullOrdering.NULLS_ARE_LARGE, NullOrdering.NULLS_ARE_LAST\n */\n @cache\n static get NULL_ORDERING (): NullOrdering {\n return NullOrdering.NULLS_ARE_SMALL;\n }\n\n /**\n * Whether the behavior of `a / b` depends on the types of `a` and `b`.\n * false means `a / b` is always float division.\n * true means `a / b` is integer division if both `a` and `b` are integers.\n */\n static TYPED_DIVISION = false;\n\n /** Whether division by zero throws an error (false) or returns NULL (true). */\n static SAFE_DIVISION = false;\n\n /** A `NULL` arg in `CONCAT` yields `NULL` by default, but in some dialects it yields an empty string. */\n static CONCAT_COALESCE = false;\n\n /** Whether the `HEX` function returns a lowercase hexadecimal string. */\n static HEX_LOWERCASE = false;\n\n static DATE_FORMAT = '\\'%Y-%m-%d\\'';\n static DATEINT_FORMAT = '\\'%Y%m%d\\'';\n static TIME_FORMAT = '\\'%Y-%m-%d %H:%M:%S\\'';\n\n /** Associates this dialect's time formats with their equivalent Python `strftime` formats. */\n @cache\n static get TIME_MAPPING (): Record<string, string> {\n return {};\n }\n\n /**\n * Helper which is used for parsing the special syntax `CAST(x AS DATE FORMAT 'yyyy')`.\n * If empty, the corresponding trie will be constructed off of `TIME_MAPPING`.\n */\n @cache\n static get FORMAT_MAPPING (): Record<string, string> {\n return {};\n }\n\n static ORIGINAL_UNESCAPED_SEQUENCES: Record<string, string> = {};\n\n @cache\n static get UNESCAPED_SEQUENCES (): Record<string, string> {\n let res = {};\n if (this.STRINGS_SUPPORT_ESCAPED_SEQUENCES || this.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES) {\n res = {\n ...BASE_UNESCAPED_SEQUENCES,\n ...this.ORIGINAL_UNESCAPED_SEQUENCES,\n };\n } else {\n res = this.ORIGINAL_UNESCAPED_SEQUENCES;\n }\n return res;\n }\n\n /**\n * Columns that are auto-generated by the engine corresponding to this dialect.\n * For example, such columns may be excluded from `SELECT *` queries.\n */\n @cache\n static get PSEUDOCOLUMNS (): Set<string> {\n return new Set();\n }\n\n /**\n * Some dialects allow you to reference a CTE column alias in the HAVING clause.\n */\n static PREFER_CTE_ALIAS_COLUMN = false;\n\n /**\n * Whether alias reference expansion should run before column qualification.\n */\n static FORCE_EARLY_ALIAS_REF_EXPANSION = false;\n\n /** Whether alias reference expansion before qualification should only happen for the GROUP BY clause. */\n static EXPAND_ONLY_GROUP_ALIAS_REF = false;\n\n /** Whether to annotate all scopes during optimization. */\n static ANNOTATE_ALL_SCOPES = false;\n\n /** Whether alias reference expansion is disabled for this dialect. */\n static DISABLES_ALIAS_REF_EXPANSION = false;\n\n /** Whether alias references are allowed in JOIN ... ON clauses. */\n static SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS = false;\n\n /** Whether ORDER BY ALL is supported. */\n static SUPPORTS_ORDER_BY_ALL = false;\n\n /** Whether projection alias names can shadow table/source names in GROUP BY and HAVING clauses. */\n static PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = false;\n\n /** Whether table names can be referenced as columns (treated as structs). */\n static TABLES_REFERENCEABLE_AS_COLUMNS = false;\n\n /** Whether the dialect supports expanding struct fields using star notation. */\n static SUPPORTS_STRUCT_STAR_EXPANSION = false;\n\n /** Whether pseudocolumns should be excluded from star expansion (SELECT *). */\n static EXCLUDES_PSEUDOCOLUMNS_FROM_STAR = false;\n\n /** Whether query results are typed as structs in metadata for type inference. */\n static QUERY_RESULTS_ARE_STRUCTS = false;\n\n /** Whether struct field access requires parentheses around the expression. */\n static REQUIRES_PARENTHESIZED_STRUCT_ACCESS = false;\n\n /** Whether NULL/VOID is supported as a valid data type. */\n static SUPPORTS_NULL_TYPE = false;\n\n /** Whether COALESCE in comparisons has non-standard NULL semantics. */\n static COALESCE_COMPARISON_NON_STANDARD = false;\n\n /** Whether the ARRAY constructor is context-sensitive. */\n static HAS_DISTINCT_ARRAY_CONSTRUCTORS = false;\n\n /** Whether expressions like x::INT[5] should be parsed as fixed-size array defs/casts. */\n static SUPPORTS_FIXED_SIZE_ARRAYS = false;\n\n /** Whether failing to parse a JSON path expression will log a warning. */\n static STRICT_JSON_PATH_SYNTAX = true;\n\n /** Whether empty ON condition should error before attempting to parse. */\n static ON_CONDITION_EMPTY_BEFORE_ERROR = true;\n\n /** Whether ArrayAgg needs to filter NULL values. */\n static ARRAY_AGG_INCLUDES_NULLS: boolean | undefined = true;\n\n /** Whether Array update functions return NULL when the input array is NULL. */\n static ARRAY_FUNCS_PROPAGATES_NULLS = false;\n\n /**\n * This flag is used in the optimizer's canonicalize rule and determines whether x will be promoted\n * to the literal's type in x::DATE < '2020-01-01 12:05:03' (i.e., DATETIME). When false, the literal\n * is cast to x's type to match it instead.\n */\n static PROMOTE_TO_INFERRED_DATETIME_TYPE = false;\n\n /** Whether the DEFAULT keyword is supported in the VALUES clause. */\n static SUPPORTS_VALUES_DEFAULT = true;\n\n /** Whether number literals can include underscores for better readability. */\n static NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = false;\n\n /** Whether hex strings such as x'CC' evaluate to integer or binary/blob type. */\n static HEX_STRING_IS_INTEGER_TYPE = false;\n\n /** The default value for the capturing group. */\n static REGEXP_EXTRACT_DEFAULT_GROUP = 0;\n\n /** Whether REGEXP_EXTRACT returns NULL when the position arg exceeds the string length. */\n static REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL = true;\n\n @cache\n static get SET_OP_DISTINCT_BY_DEFAULT (): Partial<Record<ExpressionKey, boolean>> {\n return {\n [ExpressionKey.EXCEPT]: true,\n [ExpressionKey.INTERSECT]: true,\n [ExpressionKey.UNION]: true,\n };\n }\n\n /**\n * Helper for dialects that use a different name for the same creatable kind.\n * For example, the Clickhouse equivalent of CREATE SCHEMA is CREATE DATABASE.\n */\n @cache\n static get CREATABLE_KIND_MAPPING (): Record<string, string> {\n return {};\n }\n\n /**\n * Hive by default does not update the schema of existing partitions when a column is changed.\n * The CASCADE clause is used to indicate that the change should be propagated to all existing partitions.\n * The Spark dialect, while derived from Hive, does not support the CASCADE clause.\n */\n static ALTER_TABLE_SUPPORTS_CASCADE = false;\n\n /** Whether ADD is present for each column added by ALTER TABLE. */\n static ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = true;\n\n /**\n * Whether the value/LHS of the TRY_CAST(<value> AS <type>) should strictly be a\n * STRING type (Snowflake's case) or can be of any type.\n */\n static TRY_CAST_REQUIRES_STRING: boolean | undefined = undefined;\n\n /**\n * Whether the double negation can be applied.\n * Not safe with MySQL and SQLite due to type coercion (may not return boolean).\n */\n static SAFE_TO_ELIMINATE_DOUBLE_NEGATION = true;\n\n /**\n * Whether the INITCAP function supports custom delimiter characters as the second argument.\n */\n static get INITCAP_SUPPORTS_CUSTOM_DELIMITERS (): boolean {\n return [\n Dialects.DIALECT,\n Dialects.BIGQUERY,\n Dialects.SNOWFLAKE,\n ].includes(this.DIALECT_NAME);\n }\n\n /** Default delimiter characters for INITCAP function: whitespace and non-alphanumeric characters. */\n static INITCAP_DEFAULT_DELIMITER_CHARS = ' \\t\\n\\r\\f\\v!\"#$%&\\'()*+,\\\\-./:;<=>?@\\\\[\\\\]^_`{|}~';\n\n /** Whether byte string literals (ex: BigQuery's b'...') are typed as BYTES/BINARY. */\n static BYTE_STRING_IS_BYTES_TYPE = false;\n\n /** Whether a UUID is considered a string or a UUID type. */\n static UUID_IS_STRING_TYPE = false;\n\n /** Whether JSON_EXTRACT_SCALAR returns undefined if a non-scalar value is selected. */\n static JSON_EXTRACT_SCALAR_SCALAR_ONLY = false;\n\n /**\n * Maps function expressions to their default output column name(s).\n * For example, in Postgres, generate_series function outputs a column named \"generate_series\" by default.\n */\n @cache\n static get DEFAULT_FUNCTIONS_COLUMN_NAMES (): Map<string, string | string[]> {\n return new Map();\n }\n\n @cache\n static get DEFAULT_NULL_TYPE (): DataTypeExprKind {\n return DataTypeExprKind.UNKNOWN;\n }\n\n @cache\n static get UNMERGABLE_ARGS (): Set<string> {\n return new Set(\n Array.from(SelectExpr.availableArgs).filter(\n (arg) => ![\n 'expressions',\n 'from',\n 'joins',\n 'where',\n 'order',\n 'hint',\n ].includes(arg),\n ),\n );\n }\n\n /**\n * Whether LEAST/GREATEST functions ignore NULL values, e.g:\n * - BigQuery, Snowflake, MySQL, Presto/Trino: LEAST(1, NULL, 2) -> NULL\n * - Spark, Postgres, DuckDB, TSQL: LEAST(1, NULL, 2) -> 1\n */\n static LEAST_GREATEST_IGNORES_NULLS = true;\n\n /** Whether to prioritize non-literal types over literals during type annotation. */\n static PRIORITIZE_NON_LITERAL_TYPES = false;\n\n static get TRY_SUPPORTED (): boolean {\n const TRY_SUPPORTED_DIALECTS = [\n Dialects.DIALECT,\n Dialects.ATHENA,\n Dialects.PRESTO,\n Dialects.TRINO,\n Dialects.DUCKDB,\n ];\n\n return TRY_SUPPORTED_DIALECTS.includes(this.DIALECT_NAME);\n }\n\n /**\n * Whether UESCAPE clause is supported in string literals.\n * Returns false for non-Athena/Presto/Trino/DuckDB dialects.\n */\n static get SUPPORTS_UESCAPE (): boolean {\n return [\n Dialects.DIALECT,\n Dialects.ATHENA,\n Dialects.PRESTO,\n Dialects.TRINO,\n Dialects.DUCKDB,\n ].includes(this.DIALECT_NAME);\n }\n\n // --- Autofilled by metaclass in Python, set as instance properties in TypeScript ---\n static _tokenizerClassCache = new WeakMap<typeof Dialect, typeof Tokenizer>();\n static get tokenizerClass (): typeof Tokenizer {\n const cached = this._tokenizerClassCache.get(this);\n if (cached) return cached;\n if (Object.prototype.hasOwnProperty.call(this, 'Tokenizer')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (this as any).Tokenizer;\n }\n const base = Object.getPrototypeOf(this);\n const baseTokenizer = base?.tokenizerClass as typeof Tokenizer;\n if (!baseTokenizer) {\n this._tokenizerClassCache.set(this, Tokenizer);\n return Tokenizer;\n }\n class DerivedTokenizer extends baseTokenizer {}\n this._tokenizerClassCache.set(this, DerivedTokenizer);\n return DerivedTokenizer;\n }\n\n static _jsonpathTokenizerClassCache = new WeakMap<typeof Dialect, typeof JsonPathTokenizer>();\n static get jsonpathTokenizerClass (): typeof JsonPathTokenizer {\n const cached = this._jsonpathTokenizerClassCache.get(this);\n if (cached) return cached;\n if (Object.prototype.hasOwnProperty.call(this, 'JsonPathTokenizer')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (this as any).JsonPathTokenizer;\n }\n const base = Object.getPrototypeOf(this);\n const baseJsonpathTokenizer = base?.JsonPathTokenizer as typeof JsonPathTokenizer;\n if (!baseJsonpathTokenizer) {\n this._jsonpathTokenizerClassCache.set(this, JsonPathTokenizer);\n return JsonPathTokenizer;\n }\n class DerivedJsonPathTokenizer extends baseJsonpathTokenizer {}\n this._jsonpathTokenizerClassCache.set(this, DerivedJsonPathTokenizer);\n return DerivedJsonPathTokenizer;\n }\n\n static _parserClassCache = new WeakMap<typeof Dialect, typeof Parser>();\n\n // NOTE: These logic should be handled in the respective dialect files:\n // - https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/dialects/dialect.py#L317-L380\n static get parserClass (): typeof Parser {\n const cached = this._parserClassCache.get(this);\n if (cached) return cached;\n\n if (Object.prototype.hasOwnProperty.call(this, 'Parser')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (this as any).Parser;\n }\n\n const base = Object.getPrototypeOf(this);\n const baseParser = base?.parserClass as typeof Parser;\n\n if (!baseParser) {\n this._parserClassCache.set(this, Parser);\n return Parser;\n }\n\n class DerivedParser extends baseParser {}\n this._parserClassCache.set(this, DerivedParser);\n return DerivedParser;\n }\n\n static _generatorClassCache = new WeakMap<typeof Dialect, typeof Generator>();\n\n // NOTE: These logic should be handled in the respective dialect files:\n // - https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/dialects/dialect.py#L300-L326\n static get generatorClass (): typeof Generator {\n const cached = this._generatorClassCache.get(this);\n if (cached) return cached;\n\n if (Object.prototype.hasOwnProperty.call(this, 'Generator')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (this as any).Generator;\n }\n\n const base = Object.getPrototypeOf(this);\n const baseGenerator = base?.generatorClass as typeof Generator;\n\n if (!baseGenerator) {\n this._generatorClassCache.set(this, Generator);\n return Generator;\n }\n\n class DerivedGenerator extends baseGenerator {}\n this._generatorClassCache.set(this, DerivedGenerator);\n return DerivedGenerator;\n }\n\n @cache\n static get TIME_TRIE (): TrieNode {\n return newTrie(Object.keys(this.TIME_MAPPING).map((k) => Array.from(k)));\n }\n\n @cache\n static get FORMAT_TRIE (): TrieNode {\n const mapping = 0 < Object.keys(this.FORMAT_MAPPING).length\n ? this.FORMAT_MAPPING\n : this.TIME_MAPPING;\n return newTrie(Object.keys(mapping).map((k) => Array.from(k)));\n }\n\n @cache\n static get INVERSE_TIME_MAPPING (): Record<string, string> {\n return Object.fromEntries(Object.entries(this.TIME_MAPPING).map(([k, v]) => [v, k]));\n }\n\n @cache\n static get INVERSE_TIME_TRIE (): TrieNode {\n return newTrie(Object.keys(this.INVERSE_TIME_MAPPING).map((k) => Array.from(k)));\n }\n\n @cache\n static get INVERSE_FORMAT_MAPPING (): Record<string, string> {\n return Object.fromEntries(Object.entries(this.FORMAT_MAPPING).map(([k, v]) => [v, k]));\n }\n\n @cache\n static get INVERSE_CREATABLE_KIND_MAPPING (): Record<string, string> {\n return Object.fromEntries(Object.entries(this.CREATABLE_KIND_MAPPING).map(([k, v]) => [v, k]));\n }\n\n @cache\n static get ESCAPED_SEQUENCES (): Record<string, string> {\n return Object.fromEntries(\n Object.entries(this.UNESCAPED_SEQUENCES).map(([k, v]) => [v, k]),\n );\n }\n\n /** Delimiters for string literals. */\n static get QUOTE_START (): string {\n return Object.entries(this.tokenizerClass._QUOTES)[0]?.[0];\n }\n\n static get QUOTE_END (): string {\n return Object.entries(this.tokenizerClass._QUOTES)[0]?.[1];\n }\n\n /** Delimiters for identifiers. */\n static get IDENTIFIER_START (): string {\n return Object.entries(this.tokenizerClass._IDENTIFIERS)[0]?.[0];\n }\n\n static get IDENTIFIER_END (): string {\n return Object.entries(this.tokenizerClass._IDENTIFIERS)[0]?.[1];\n }\n\n private static getStartEnd (tokenType: TokenType): [string | undefined, string | undefined] {\n const result = Object.entries(this.tokenizerClass._FORMAT_STRINGS).find(\n ([_, [__, type]]) => type === tokenType,\n );\n\n if (result) {\n const [start, [end]] = result;\n return [start, end];\n }\n\n return [undefined, undefined];\n }\n\n /** Delimiters for bit literals. */\n static get BIT_START (): string | undefined {\n return this.getStartEnd(TokenType.BIT_STRING)[0];\n }\n\n static get BIT_END (): string | undefined {\n return this.getStartEnd(TokenType.BIT_STRING)[1];\n }\n\n /** Delimiters for hex literals. */\n static get HEX_START (): string | undefined {\n return this.getStartEnd(TokenType.HEX_STRING)[0];\n }\n\n static get HEX_END (): string | undefined {\n return this.getStartEnd(TokenType.HEX_STRING)[1];\n }\n\n /** Delimiters for byte literals. */\n static get BYTE_START (): string | undefined {\n return this.getStartEnd(TokenType.BYTE_STRING)[0];\n }\n\n static get BYTE_END (): string | undefined {\n return this.getStartEnd(TokenType.BYTE_STRING)[1];\n }\n\n /** Delimiters for unicode literals. */\n static get UNICODE_START (): string | undefined {\n return this.getStartEnd(TokenType.UNICODE_STRING)[0];\n }\n\n static get UNICODE_END (): string | undefined {\n return this.getStartEnd(TokenType.UNICODE_STRING)[1];\n }\n\n /** Date part mapping for normalization. */\n @cache\n static get DATE_PART_MAPPING (): Record<string, string> {\n return {\n 'Y': 'YEAR',\n 'YY': 'YEAR',\n 'YYY': 'YEAR',\n 'YYYY': 'YEAR',\n 'YR': 'YEAR',\n 'YEARS': 'YEAR',\n 'YRS': 'YEAR',\n 'MM': 'MONTH',\n 'MON': 'MONTH',\n 'MONS': 'MONTH',\n 'MONTHS': 'MONTH',\n 'D': 'DAY',\n 'DD': 'DAY',\n 'DAYS': 'DAY',\n 'DAYOFMONTH': 'DAY',\n 'DAY OF WEEK': 'DAYOFWEEK',\n 'WEEKDAY': 'DAYOFWEEK',\n 'DOW': 'DAYOFWEEK',\n 'DW': 'DAYOFWEEK',\n 'WEEKDAY_ISO': 'DAYOFWEEKISO',\n 'DOW_ISO': 'DAYOFWEEKISO',\n 'DW_ISO': 'DAYOFWEEKISO',\n 'DAYOFWEEK_ISO': 'DAYOFWEEKISO',\n 'DAY OF YEAR': 'DAYOFYEAR',\n 'DOY': 'DAYOFYEAR',\n 'DY': 'DAYOFYEAR',\n 'W': 'WEEK',\n 'WK': 'WEEK',\n 'WEEKOFYEAR': 'WEEK',\n 'WOY': 'WEEK',\n 'WY': 'WEEK',\n 'WEEK_ISO': 'WEEKISO',\n 'WEEKOFYEARISO': 'WEEKISO',\n 'WEEKOFYEAR_ISO': 'WEEKISO',\n 'Q': 'QUARTER',\n 'QTR': 'QUARTER',\n 'QTRS': 'QUARTER',\n 'QUARTERS': 'QUARTER',\n 'H': 'HOUR',\n 'HH': 'HOUR',\n 'HR': 'HOUR',\n 'HOURS': 'HOUR',\n 'HRS': 'HOUR',\n 'M': 'MINUTE',\n 'MI': 'MINUTE',\n 'MIN': 'MINUTE',\n 'MINUTES': 'MINUTE',\n 'MINS': 'MINUTE',\n 'S': 'SECOND',\n 'SEC': 'SECOND',\n 'SECONDS': 'SECOND',\n 'SECS': 'SECOND',\n 'MS': 'MILLISECOND',\n 'MSEC': 'MILLISECOND',\n 'MSECS': 'MILLISECOND',\n 'MSECOND': 'MILLISECOND',\n 'MSECONDS': 'MILLISECOND',\n 'MILLISEC': 'MILLISECOND',\n 'MILLISECS': 'MILLISECOND',\n 'MILLISECON': 'MILLISECOND',\n 'MILLISECONDS': 'MILLISECOND',\n 'US': 'MICROSECOND',\n 'USEC': 'MICROSECOND',\n 'USECS': 'MICROSECOND',\n 'MICROSEC': 'MICROSECOND',\n 'MICROSECS': 'MICROSECOND',\n 'USECOND': 'MICROSECOND',\n 'USECONDS': 'MICROSECOND',\n 'MICROSECONDS': 'MICROSECOND',\n 'NS': 'NANOSECOND',\n 'NSEC': 'NANOSECOND',\n 'NANOSEC': 'NANOSECOND',\n 'NSECOND': 'NANOSECOND',\n 'NSECONDS': 'NANOSECOND',\n 'NANOSECS': 'NANOSECOND',\n 'EPOCH_SECOND': 'EPOCH',\n 'EPOCH_SECONDS': 'EPOCH',\n 'EPOCH_MILLISECONDS': 'EPOCH_MILLISECOND',\n 'EPOCH_MICROSECONDS': 'EPOCH_MICROSECOND',\n 'EPOCH_NANOSECONDS': 'EPOCH_NANOSECOND',\n 'TZH': 'TIMEZONE_HOUR',\n 'TZM': 'TIMEZONE_MINUTE',\n 'DEC': 'DECADE',\n 'DECS': 'DECADE',\n 'DECADES': 'DECADE',\n 'MIL': 'MILLENNIUM',\n 'MILS': 'MILLENNIUM',\n 'MILLENIA': 'MILLENNIUM',\n 'C': 'CENTURY',\n 'CENT': 'CENTURY',\n 'CENTS': 'CENTURY',\n 'CENTURIES': 'CENTURY',\n };\n }\n\n /** Specifies what types a given type can be coerced into. */\n @cache\n static get COERCES_TO (): Map<string, Set<string>> {\n return new Map();\n }\n\n /** Specifies type inference & validation rules for expressions. */\n @cache\n static get EXPRESSION_METADATA (): ExpressionMetadata {\n return DialectTyping.EXPRESSION_METADATA;\n }\n\n /** Determines the supported Dialect instance settings. */\n @cache\n static get SUPPORTED_SETTINGS (): Set<string> {\n return new Set(['normalizationStrategy', 'version']);\n }\n\n /**\n * Extracts quote delimiters from the tokenizer class.\n * Returns [start, end] delimiter pair for string literals.\n */\n @cache\n static get QUOTE_DELIMITERS (): [string, string] {\n return Object.entries(this.tokenizerClass.QUOTES)[0] as [string, string];\n }\n\n /**\n * Extracts identifier delimiters from the tokenizer class.\n * Returns [start, end] delimiter pair for identifiers.\n */\n @cache\n static get IDENTIFIER_DELIMITERS (): [string, string] {\n return Object.entries(this.tokenizerClass.IDENTIFIERS)[0] as [string, string];\n }\n\n /** Valid interval units. */\n @cache\n static get BASE_VALID_INTERVAL_UNITS (): Set<string> {\n return new Set<string>();\n } // NOTE: Corresponds to VALID_INTERVAL_UNITS in sqlglot python version\n\n @cache\n static get VALID_INTERVAL_UNITS (): Set<string> {\n const mapping = this.DATE_PART_MAPPING;\n return new Set([\n ...this.BASE_VALID_INTERVAL_UNITS,\n ...Object.keys(mapping),\n ...Object.values(mapping),\n ]);\n }\n\n /** Whether strings support escaped sequences. */\n static get STRINGS_SUPPORT_ESCAPED_SEQUENCES (): boolean {\n return this.tokenizerClass.STRING_ESCAPES.includes('\\\\');\n }\n\n /** Whether byte strings support escaped sequences. */\n static get BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES (): boolean {\n return this.tokenizerClass.BYTE_STRING_ESCAPES.includes('\\\\');\n }\n\n /**\n * Compare this dialect class with another value for equality.\n *\n * @param other - Value to compare with\n * @returns true if equal, false otherwise\n */\n static equals (other: unknown): boolean {\n if (this === other) {\n return true;\n }\n if (typeof other === 'string') {\n return this === this.get(other);\n }\n if (other instanceof Dialect) {\n return this === other.constructor;\n }\n return false;\n }\n\n private static registry: Map<string, typeof Dialect> = new Map();\n // If a subclass wants `Dialect.get` or `Dialect.getOrRaise` to recognize it, it should call this method\n static register (name: string, dialect: typeof Dialect) {\n this.registry.set(name, dialect);\n }\n\n /**\n * Get a dialect class by name.\n */\n static get (name: string, fallback?: typeof Dialect): typeof Dialect | undefined {\n return this.registry.get(name) || fallback;\n }\n\n /**\n * @param dialect - The target dialect. If this is a string, it can be optionally followed by\n * additional key-value pairs that are separated by commas and are used to specify dialect settings.\n *\n * @example\n * ```ts\n * const dialect = Dialect.getOrRaise(\"duckdb\");\n * const dialect2 = Dialect.getOrRaise(\"mysql, normalization_strategy = case_sensitive\");\n * ```\n */\n static getOrRaise (dialect?: DialectType): Dialect {\n if (!dialect) {\n return new this();\n }\n\n // Is a Dialect class (including the base Dialect class itself)\n if (typeof dialect === 'function' && (dialect === Dialect || dialect.prototype instanceof Dialect)) {\n return new (dialect as new () => Dialect)();\n }\n\n // Is already a Dialect instance\n if (dialect instanceof Dialect) {\n return dialect;\n }\n\n // Handle String parsing (\"mysql, normalizationStrategy = case_sensitive\")\n if (typeof dialect === 'string') {\n let dialectName: string;\n const kwargs: Record<string, string | number | boolean> = {};\n\n try {\n const [name, ...kvStrings] = dialect.split(',');\n dialectName = name.trim();\n\n for (const kvString of kvStrings) {\n const parts = kvString.split('=');\n const key = parts[0].trim();\n let value: string | boolean = true;\n\n if (parts.length === 2) {\n value = parts[1].trim();\n }\n\n kwargs[key] = toBool(value) ?? value;\n }\n } catch {\n throw new Error(\n `Invalid dialect format: '${dialect}'. `\n + 'Please use the correct format: \\'dialect [, k1 = v2 [, ...]]\\'.',\n );\n }\n\n const ResultClass = this.get(dialectName);\n\n if (!ResultClass) {\n const allDialects = new Set(Object.keys(Dialects));\n suggestClosestMatchAndFail('dialect', dialectName, Array.from(allDialects));\n }\n\n return new ResultClass(kwargs);\n }\n\n throw new Error(`Invalid dialect type for '${dialect}': '${typeof dialect}'.`);\n }\n\n static formatTime (expression?: string | Expression): Expression | undefined {\n if (typeof expression === 'string') {\n return LiteralExpr.string(\n formatTime(\n expression.slice(1, -1),\n this.TIME_MAPPING,\n this.TIME_TRIE,\n ) ?? '',\n );\n }\n\n if (expression instanceof Expression && expression.isString) {\n return LiteralExpr.string(\n formatTime(expression.args.this as string, this.TIME_MAPPING, this.TIME_TRIE) ?? '',\n );\n }\n\n return expression;\n }\n\n version: {\n major: number;\n minor: number;\n patch: number;\n };\n\n normalizationStrategy: NormalizationStrategy;\n settings: Record<string, boolean | number | string | undefined>;\n\n constructor (options: DialectOptions = {}) {\n const {\n version = Number.MAX_SAFE_INTEGER,\n normalizationStrategy,\n } = options;\n\n const parts = version.toString().split('.');\n while (parts.length < 3) {\n parts.push('0');\n }\n this.version = {\n major: parseInt(parts[0]),\n minor: parseInt(parts[1]),\n patch: parseInt(parts[2]),\n };\n\n this.normalizationStrategy = enumFromString(NormalizationStrategy, normalizationStrategy) ?? this._constructor.NORMALIZATION_STRATEGY;\n\n this.settings = options;\n\n for (const unsupportedSetting of Object.keys(options)) {\n if (!this._constructor.SUPPORTED_SETTINGS.has(unsupportedSetting)) {\n suggestClosestMatchAndFail(\n 'setting',\n unsupportedSetting,\n [...this._constructor.SUPPORTED_SETTINGS],\n );\n }\n }\n }\n\n equals (other: unknown): boolean {\n if (this === other) {\n return true;\n }\n if (other instanceof Dialect) {\n return this.constructor === other.constructor;\n }\n return false;\n }\n\n hash (): string {\n return this.constructor.name.toLowerCase();\n }\n\n /**\n * Transforms an identifier in a way that resembles how it'd be resolved by this dialect.\n *\n * For example, an identifier like `FoO` would be resolved as `foo` in Postgres, because it\n * lowercases all unquoted identifiers. On the other hand, Snowflake uppercases them, so\n * it would resolve it as `FOO`. If it was quoted, it'd need to be treated as case-sensitive,\n * and so any normalization would be prohibited in order to avoid \"breaking\" the identifier.\n *\n * There are also dialects like Spark, which are case-insensitive even when quotes are\n * present, and dialects like MySQL, whose resolution rules match those employed by the\n * underlying operating system, for example they may always be case-sensitive in Linux.\n *\n * Finally, the normalization behavior of some engines can even be controlled through flags,\n * like in Redshift's case, where users can explicitly set enable_case_sensitive_identifier.\n *\n * SQLGlot aims to understand and handle all of these different behaviors gracefully, so\n * that it can analyze queries in the optimizer and successfully capture their semantics.\n */\n normalizeIdentifier<E extends Expression> (expression: E): E {\n if (\n expression instanceof IdentifierExpr\n && this.normalizationStrategy !== NormalizationStrategy.CASE_SENSITIVE\n && (\n !expression.args.quoted\n || this.normalizationStrategy === NormalizationStrategy.CASE_INSENSITIVE\n || this.normalizationStrategy === NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE\n )\n ) {\n const shouldUppercase = (\n this.normalizationStrategy === NormalizationStrategy.UPPERCASE\n || this.normalizationStrategy === NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE\n );\n const thisValue = expression.args.this;\n if (typeof thisValue === 'string') {\n const normalized = shouldUppercase\n ? thisValue.toUpperCase()\n : thisValue.toLowerCase();\n expression.setArgKey('this', normalized);\n }\n }\n\n return expression;\n }\n\n /**\n * Checks if text contains any case sensitive characters, based on the dialect's rules.\n */\n caseSensitive (text: string): boolean {\n if (this.normalizationStrategy === NormalizationStrategy.CASE_INSENSITIVE) {\n return false;\n }\n\n const unsafe = this.normalizationStrategy === NormalizationStrategy.UPPERCASE\n ? (char: string) => char === char.toLowerCase() && char !== char.toUpperCase()\n : (char: string) => char === char.toUpperCase() && char !== char.toLowerCase();\n\n return Array.from(text).some(unsafe);\n }\n\n /**\n * Checks if an identifier can be quoted.\n *\n * @param identifier - The identifier to check\n * @param identify - `true`: always quote; `\"safe\"`: only if case-insensitive; `\"unsafe\"`: only if case-sensitive\n */\n canQuote (\n identifier: IdentifierExpr,\n options: {\n identify?: string | boolean;\n } = {},\n ): boolean {\n const { identify = 'safe' } = options;\n\n if (identifier.args.quoted) return true;\n if (!identify) return false;\n if (identify === true) return true;\n\n const name = identifier.name ?? '';\n const isSafe = !this.caseSensitive(name) && SAFE_IDENTIFIER_RE.test(name);\n\n if (identify === 'safe') return isSafe;\n if (identify === 'unsafe') return !isSafe;\n\n throw new Error(`Unexpected argument for identify: '${identify}'`);\n }\n\n /**\n * Adds quotes to a given expression if it is an identifier.\n *\n * @param expression - The expression of interest\n * @param identify - If `false`, only quote if the identifier is deemed \"unsafe\"\n */\n quoteIdentifier<E extends Expression> (\n expression: E,\n options: {\n identify?: boolean;\n } = {},\n ): E {\n const { identify = true } = options;\n\n if (expression instanceof IdentifierExpr) {\n expression.setArgKey('quoted', this.canQuote(expression, { identify: identify || 'unsafe' }));\n }\n return expression;\n }\n\n toJsonPath (path?: Expression): Expression | undefined {\n if (path instanceof LiteralExpr) {\n let pathText = path.name;\n\n if (path.isNumber) {\n pathText = `[${pathText}]`;\n }\n\n try {\n return parseJsonPath(pathText, { dialect: this });\n } catch (e) {\n const isStrict = this._constructor.STRICT_JSON_PATH_SYNTAX;\n const trimmedPath = pathText.trimStart();\n const hasModePrefix = trimmedPath.startsWith('lax') || trimmedPath.startsWith('strict');\n\n if (isStrict && !hasModePrefix) {\n console.warn(`Invalid JSON path syntax. ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n }\n\n return path;\n }\n\n /**\n * Parse SQL string into expression tree.\n */\n parse (sql: string, opts?: ParseOptions): (Expression | undefined)[] {\n return this.parser(opts).parse(this.tokenize(sql), sql);\n }\n\n parser (opts?: ParseOptions): Parser {\n return new this._constructor.parserClass({\n dialect: this,\n ...opts,\n });\n }\n\n /**\n * Parse SQL string into specific expression type.\n */\n parseIntoTypes<T extends typeof Expression> (\n expressionType: string | T | (string | T)[],\n sql: string,\n opts?: ParseOptions,\n ): (Expression | undefined)[] {\n return this.parser(opts).parseIntoTypes(expressionType, this.tokenize(sql), sql);\n }\n\n /**\n * Generate SQL from an expression tree.\n */\n generate (expression: Expression, options: GeneratorOptions & { copy?: boolean } = {}): string {\n const {\n copy = true, ...restOptions\n } = options;\n return this.generator(restOptions).generate(expression, { copy });\n }\n\n /**\n * Get or create a generator instance for this dialect.\n */\n generator (options: GeneratorOptions = {}): Generator {\n return new this._constructor.generatorClass({\n dialect: this,\n ...options,\n });\n }\n\n /**\n * Tokenize SQL string into tokens.\n */\n tokenize (sql: string, options: TokenizerOptions = {}): ReturnType<Tokenizer['tokenize']> {\n return this.tokenizer(options).tokenize(sql);\n }\n\n /**\n * Get a tokenizer instance for this dialect.\n */\n tokenizer (options: TokenizerOptions = {}): Tokenizer {\n return new this._constructor.tokenizerClass({\n dialect: this,\n ...options,\n });\n }\n\n /**\n * Get a jsonpath tokenizer instance for this dialect.\n */\n jsonpathTokenizer (options: TokenizerOptions = {}): Tokenizer {\n return new this._constructor.jsonpathTokenizerClass({\n dialect: this,\n ...options,\n });\n }\n\n /**\n * Parse SQL and generate it back in this dialect.\n */\n transpile (sql: string, options: TranspileOptions = {}): string[] {\n return this.parse(sql, options).map((expression) =>\n expression\n ? this.generate(expression, {\n copy: false,\n ...options,\n })\n : '');\n }\n\n /**\n * Generate column aliases for a VALUES expression.\n * By default, generates _col_0, _col_1, etc.\n */\n generateValuesAliases (expression: Expression): IdentifierExpr[] {\n const firstRow = expression.args.expressions?.[0];\n if (!(firstRow instanceof Expression)) {\n return [];\n }\n\n const firstRowExpr = firstRow as Expression;\n return (firstRowExpr.args.expressions ?? []).map((_: unknown, i: number) => toIdentifier(`_col_${i}`));\n }\n\n get _constructor (): typeof Dialect {\n return this.constructor as typeof Dialect;\n }\n}\n\n// Register the base Dialect\nDialect.register(Dialects.DIALECT, Dialect);\n\n/**\n * Creates a function that renames a function call.\n */\nexport function renameFunc (name: string): (this: Generator, expression: Expression) => string {\n return function (this: Generator, expression: Expression): string {\n const constructor = expression._constructor as typeof FuncExpr;\n const argOrder = constructor.argOrder;\n if (argOrder && 0 < argOrder.length) {\n const args = argOrder\n .map((arg) => expression.getArgKey(arg))\n .filter((arg) => arg !== undefined);\n // Flatten arrays (for var len args)\n const flattenedArgs = args.reduce((acc: (ExpressionValue | undefined)[], val) =>\n (Array.isArray(val) ? acc.concat(val) : acc.concat([val])), []);\n return this.func(name, flattenedArgs);\n }\n\n const values = Object.values(expression.args).filter((v) => v !== undefined);\n return this.func(name, values);\n };\n}\n\n/**\n * Generate APPROX_COUNT_DISTINCT SQL (with unsupported accuracy parameter).\n */\nexport function approxCountDistinctSql (this: Generator, expression: ApproxDistinctExpr): string {\n unsupportedArgs.call(this, expression, 'accuracy');\n return this.func('APPROX_COUNT_DISTINCT', [expression.args.this]);\n}\n\n/**\n * Creates an IF function generator with custom name and optional false value.\n */\nexport function ifSql (\n name: string = 'IF',\n falseValue?: Expression | string,\n): (this: Generator, expression: IfExpr) => string {\n return function (this: Generator, expression: IfExpr): string {\n if (expression.parent instanceof CaseExpr) {\n return `WHEN ${this.sql(expression, 'this')} THEN ${this.sql(expression, 'true')}`;\n }\n return this.func(\n name,\n [\n expression.args.this,\n expression.args.true,\n expression.args.false || falseValue,\n ],\n );\n };\n}\n\n/**\n * Generate arrow-based JSON extract (-> or ->>).\n */\nexport function arrowJsonExtractSql (this: Generator, expression: JsonExtractType): string {\n const thisArg = expression.args.this;\n\n if (\n this._constructor.JSON_TYPE_REQUIRED_FOR_EXTRACTION\n && thisArg instanceof LiteralExpr\n && thisArg.isString\n ) {\n const jsonType = new DataTypeExpr({ this: DataTypeExprKind.JSON });\n thisArg.replace(cast(thisArg.copy(), jsonType));\n }\n\n const operator = expression instanceof JsonExtractExpr ? '->' : '->>';\n return this.binary(expression, operator);\n}\n\n/**\n * Generate inline array syntax: [elem1, elem2, ...]\n */\nexport function inlineArraySql (this: Generator, expression: Expression): string {\n return `[${this.expressions(expression, {\n dynamic: true,\n newLine: true,\n skipFirst: true,\n skipLast: true,\n })}]`;\n}\n\n/**\n * Generate inline array unless it contains a query.\n */\nexport function inlineArrayUnlessQuery (this: Generator, expression: Expression): string {\n const elem = seqGet(expression.args.expressions ?? [], 0);\n if (elem instanceof Expression && elem?.find?.(QueryExpr)) {\n return this.func('ARRAY', [elem]);\n }\n return inlineArraySql.call(this, expression);\n}\n\n/**\n * Transpile ILIKE to LIKE with LOWER().\n */\nexport function noIlikeSql (this: Generator, expression: ILikeExpr): string {\n const likeExpr = new LikeExpr({\n this: new LowerExpr({ this: expression.args.this }),\n expression: new LowerExpr({ this: expression.args.expression }),\n });\n return this.likeSql(likeExpr);\n}\n\n/**\n * Generate CURRENT_DATE without parentheses.\n */\nexport function noParenCurrentDateSql (this: Generator, expression: CurrentDateExpr): string {\n const zone = this.sql(expression, 'this');\n return zone ? `CURRENT_DATE AT TIME ZONE ${zone}` : 'CURRENT_DATE';\n}\n\n/**\n * Emit unsupported warning for recursive CTEs.\n */\nexport function noRecursiveCteSql (this: Generator, expression: WithExpr): string {\n if (expression.args.recursive) {\n this.unsupported('Recursive CTEs are unsupported');\n expression.setArgKey('recursive', false);\n }\n return this.withSql(expression);\n}\n\n/**\n * Emit unsupported warning for TABLESAMPLE.\n */\nexport function noTablesampleSql (this: Generator, expression: TableSampleExpr): string {\n this.unsupported('TABLESAMPLE unsupported');\n return this.sql(expression.args.this);\n}\n\n/**\n * Emit unsupported warning for PIVOT.\n */\nexport function noPivotSql (this: Generator, _expression: PivotExpr): string {\n this.unsupported('PIVOT unsupported');\n return '';\n}\n\n/**\n * Transpile TRY_CAST to CAST.\n */\nexport function noTrycastSql (this: Generator, expression: Expression): string {\n return this.castSql(expression);\n}\n\n/**\n * Emit unsupported warning for comment column constraints.\n */\nexport function noCommentColumnConstraintSql (this: Generator, _expression: CommentColumnConstraintExpr): string {\n this.unsupported('CommentColumnConstraint unsupported');\n return '';\n}\n\n/**\n * Emit unsupported warning for MAP_FROM_ENTRIES.\n */\nexport function noMapFromEntriesSql (this: Generator, _expression: MapFromEntriesExpr): string {\n this.unsupported('MAP_FROM_ENTRIES unsupported');\n return '';\n}\n\n/**\n * Generate property SQL: key=value.\n */\nexport function propertySql (this: Generator, expression: PropertyExpr): string {\n return `${this.propertyName(expression, { stringKey: true })}=${this.sql(expression, 'value')}`;\n}\n\n/**\n * Generate STRPOS/POSITION SQL with optional parameters.\n */\nexport function strPositionSql (\n this: Generator,\n expression: StrPositionExpr,\n options: {\n funcName?: string;\n supportsPosition?: boolean;\n supportsOccurrence?: boolean;\n useAnsiPosition?: boolean;\n } = {},\n): string {\n const {\n funcName = 'STRPOS',\n supportsPosition = false,\n supportsOccurrence = false,\n useAnsiPosition = true,\n } = options;\n\n let string = expression.args.this;\n const substr = expression.args.substr;\n let position = expression.args.position;\n const occurrence = expression.args.occurrence;\n const zero = LiteralExpr.number(0);\n const one = LiteralExpr.number(1);\n\n if (supportsOccurrence && occurrence && supportsPosition && !position) {\n position = one;\n }\n\n const transpilePosition = position && !supportsPosition;\n if (transpilePosition) {\n string = new SubstringExpr({\n this: string,\n start: position,\n });\n }\n\n let func: Expression;\n if (funcName === 'POSITION' && useAnsiPosition) {\n func = new AnonymousExpr({\n this: funcName,\n expressions: [\n new InExpr({\n this: substr,\n field: string,\n }),\n ],\n });\n } else {\n const args = (funcName === 'LOCATE' || funcName === 'CHARINDEX')\n ? [substr, string]\n : [string, substr];\n\n if (supportsPosition && position !== undefined) {\n args.push(position);\n }\n\n if (occurrence) {\n if (supportsOccurrence) {\n args.push(occurrence);\n } else {\n this.unsupported(`${funcName} does not support the occurrence parameter.`);\n }\n }\n func = new AnonymousExpr({\n this: funcName,\n expressions: args.filter((x): x is Expression => x !== undefined),\n });\n }\n\n if (transpilePosition) {\n const funcWithOffset = new SubExpr({\n this: new AddExpr({\n this: func,\n expression: position,\n }),\n expression: one,\n });\n\n const funcWrapped = new IfExpr({\n this: func.eq(zero),\n true: zero,\n false: funcWithOffset,\n });\n\n return this.sql(funcWrapped);\n }\n\n return this.sql(func);\n}\n\n/**\n * Generate struct extract: struct.field.\n */\nexport function structExtractSql (this: Generator, expression: StructExtractExpr): string {\n return `${this.sql(expression, 'this')}.${this.sql(toIdentifier(expression.args.expression?.name ?? ''))}`;\n}\n\n/**\n * Creates array append/prepend function with NULL handling.\n */\nexport function arrayAppendSql (\n name: string,\n options: {\n swapParams?: boolean;\n } = {},\n): (this: Generator, expression: ArrayAppendExpr | ArrayPrependExpr) => string {\n const { swapParams = false } = options;\n return function (this: Generator, expression: ArrayAppendExpr | ArrayPrependExpr): string {\n let thisArg = expression.args.this;\n const element = expression.args.expression;\n\n let args = swapParams ? [element, thisArg] : [thisArg, element];\n const funcSql = this.func(name, args);\n\n const sourceNullPropagation = Boolean(expression.args.nullPropagation);\n const targetNullPropagation = this.dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS;\n\n // No transpilation needed when source and target have matching NULL semantics\n if (sourceNullPropagation === targetNullPropagation) {\n return funcSql;\n }\n\n // Source propagates NULLs, target doesn't: wrap in conditional to return NULL explicitly\n if (sourceNullPropagation) {\n return this.sql(\n new IfExpr({\n this: new IsExpr({\n this: thisArg,\n expression: null_(),\n }),\n true: null_(),\n false: funcSql,\n }),\n );\n }\n\n // Source doesn't propagate NULLs, target does: use COALESCE to convert NULL to empty array\n const coalesceThis = thisArg ?? new ArrayExpr({ expressions: [] });\n thisArg = new CoalesceExpr({\n expressions: [coalesceThis, new ArrayExpr({ expressions: [] })],\n });\n\n args = swapParams ? [element, thisArg] : [thisArg, element];\n return this.func(name, args);\n };\n}\n\nexport function arrayConcatSql (\n name: string,\n): (this: Generator, expression: ArrayConcatExpr) => string {\n function buildFuncCall (this: Generator, funcName: string, args: Expression[]): string {\n if (this._constructor.ARRAY_CONCAT_IS_VAR_LEN) {\n return this.func(funcName, args);\n }\n\n if (args.length === 1) {\n return this.func(funcName, [args[0], new ArrayExpr({ expressions: [] })]);\n }\n\n // Binary nesting: ARRAY_CAT(a, ARRAY_CAT(b, c))\n let result = this.func(funcName, [args[args.length - 2], args[args.length - 1]]);\n for (let i = args.length - 3; 0 <= i; i--) {\n result = `${funcName}(${this.sql(args[i])}, ${result})`;\n }\n return result;\n }\n\n return function (this: Generator, expression: ArrayConcatExpr): string {\n const thisArg = expression.args.this;\n const exprs = expression.args.expressions || [];\n const allArgs = [...(thisArg ? [thisArg] : []), ...exprs] as Expression[];\n\n const sourceNullPropagation = Boolean(expression.args.nullPropagation);\n const targetNullPropagation = this.dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS;\n\n if (\n sourceNullPropagation === targetNullPropagation\n || thisArg instanceof ArrayExpr\n || exprs.length === 0\n ) {\n return buildFuncCall.call(this, name, allArgs);\n }\n\n if (sourceNullPropagation) {\n // Build OR-chain: a IS NULL OR b IS NULL\n const nullChecks = allArgs.map(\n (arg) => new IsExpr({\n this: arg.copy(),\n expression: null_(),\n }),\n );\n\n const combinedCheck = nullChecks.reduce(\n (acc, curr) => new OrExpr({\n this: acc,\n expression: curr,\n }),\n );\n\n return this.sql(\n new IfExpr({\n this: combinedCheck,\n true: null_(),\n false: buildFuncCall.call(this, name, allArgs),\n }),\n );\n }\n\n // Convert NULL -> empty array\n const wrappedArgs = allArgs.map(\n (arg) => new CoalesceExpr({\n expressions: [arg.copy(), new ArrayExpr({ expressions: [] })],\n }),\n );\n\n return buildFuncCall.call(this, name, wrappedArgs);\n };\n}\n\nexport function varMapSql (\n this: Generator,\n expression: MapExpr | VarMapExpr,\n mapFuncName: string = 'MAP',\n): string {\n const keys = expression.args.keys;\n const values = expression.args.values;\n\n if (!(keys instanceof ArrayExpr) || !(values instanceof ArrayExpr)) {\n this.unsupported('Cannot convert array columns into map.');\n return this.func(mapFuncName, [...ensureList(keys), ...ensureList(values)] as (Expression | string | undefined)[]);\n }\n\n const args: string[] = [];\n const keyExprs = keys.args.expressions;\n const valueExprs = values.args.expressions;\n\n for (let i = 0; i < Math.min(keyExprs?.length || 0, valueExprs?.length || 0); i++) {\n args.push(this.sql(keyExprs?.[i]));\n args.push(this.sql(valueExprs?.[i]));\n }\n\n return this.func(mapFuncName, args);\n}\n\nexport function monthsBetweenSql (this: Generator, expression: MonthsBetweenExpr): string {\n const date1 = expression.args.this;\n const date2 = expression.args.expression;\n\n const date1Cast = cast(date1, DataTypeExprKind.DATE);\n const date2Cast = cast(date2, DataTypeExprKind.DATE);\n\n const wholeMonths = new DateDiffExpr({\n this: date1Cast,\n expression: date2Cast,\n unit: var_('month'),\n });\n\n const day1 = new DayExpr({ this: date1Cast.copy() });\n const day2 = new DayExpr({ this: date2Cast.copy() });\n\n const lastDay1 = new LastDayExpr({ this: date1Cast.copy() });\n const lastDay2 = new LastDayExpr({ this: date2Cast.copy() });\n\n const dayOfLastDay1 = new DayExpr({ this: lastDay1 });\n const dayOfLastDay2 = new DayExpr({ this: lastDay2 });\n\n const isLastDay1 = new EqExpr({\n this: day1.copy(),\n expression: dayOfLastDay1,\n });\n const isLastDay2 = new EqExpr({\n this: day2.copy(),\n expression: dayOfLastDay2,\n });\n const bothLastDay = new AndExpr({\n this: isLastDay1,\n expression: isLastDay2,\n });\n\n const fractional = new DivExpr({\n this: new ParenExpr({\n this: new SubExpr({\n this: day1.copy(),\n expression: day2.copy(),\n }),\n }),\n expression: LiteralExpr.number('31.0'),\n });\n\n const fractionalWithCheck = new IfExpr({\n this: bothLastDay,\n true: LiteralExpr.number('0'),\n false: fractional,\n });\n\n return this.sql(new AddExpr({\n this: wholeMonths,\n expression: fractionalWithCheck,\n }));\n}\n\nexport function buildFormattedTime<T extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExpClass: new (args: any) => T,\n options: {\n dialect: string;\n defaultValue?: boolean | string;\n },\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any) => T {\n const {\n dialect, defaultValue,\n } = options;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any) => {\n return new ExpClass({\n this: seqGet(args, 0),\n format: Dialect.get(dialect)?.formatTime(\n seqGet(args, 1) || (defaultValue === true ? Dialect.get(dialect)?.TIME_FORMAT : undefined),\n ),\n });\n };\n}\n\nexport function timeFormat (\n dialect?: string,\n): (this: Generator, expression: UnixToStrExpr | StrToUnixExpr) => string | undefined {\n return function (this: Generator, expression: UnixToStrExpr | StrToUnixExpr) {\n const format = this.formatTime(expression);\n return format !== Dialect.getOrRaise(dialect)?._constructor.TIME_FORMAT ? format : undefined;\n };\n}\n\nexport function buildDateDelta<T extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExpClass: new (args: any) => T,\n unitMapping?: Record<string, string>,\n options: {\n defaultUnit?: string;\n supportsTimezone?: boolean;\n } = {},\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any) => T {\n const {\n defaultUnit = 'DAY',\n supportsTimezone = false,\n } = options;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any) => {\n const unitBased = 3 <= args.length;\n const hasTimezone = args.length === 4;\n const thisArg = unitBased ? args[2] : seqGet(args, 0);\n let unit = undefined;\n\n if (unitBased || defaultUnit) {\n unit = unitBased ? args[0] : LiteralExpr.string(defaultUnit);\n const unitName = unit.name?.toLowerCase();\n if (unitMapping && unitName && unitMapping[unitName]) {\n unit = var_(unitMapping[unitName]);\n }\n if ((unit instanceof VarExpr || unit instanceof ColumnExpr || unit instanceof LiteralExpr) && !(unit instanceof ColumnExpr && unit.parts.length !== 1)) {\n const UNABBREVIATED: Record<string, string> = {\n D: 'DAY',\n H: 'HOUR',\n M: 'MINUTE',\n MS: 'MILLISECOND',\n NS: 'NANOSECOND',\n Q: 'QUARTER',\n S: 'SECOND',\n US: 'MICROSECOND',\n W: 'WEEK',\n Y: 'YEAR',\n };\n unit = new VarExpr({ this: (UNABBREVIATED[unit.name] || unit.name).toUpperCase() });\n }\n }\n\n const expression = new ExpClass({\n this: thisArg,\n expression: seqGet(args, 1),\n unit,\n });\n if (supportsTimezone && hasTimezone) {\n expression.setArgKey('zone', args[args.length - 1]);\n }\n return expression;\n };\n}\n\nexport function buildDateDeltaWithInterval<T extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExpClass: new (args: any) => T,\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any) => T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any) => {\n if (args.length < 2) throw new Error(`Expected at least 2 arguments but got ${args.length}`);\n const interval = args[1];\n\n if (!(interval instanceof IntervalExpr)) {\n throw new Error(`INTERVAL expression expected but got '${interval}'`);\n }\n\n return new ExpClass({\n this: args[0],\n expression: interval.args.this,\n unit: unitToStr(interval),\n });\n };\n}\n\nexport function dateTruncToTime (args: (Expression | undefined)[]): DateTruncExpr | TimestampTruncExpr {\n const unit = seqGet(args, 0);\n const thisExpr = seqGet(args, 1);\n if (thisExpr instanceof CastExpr && thisExpr.isType('date')) {\n return new DateTruncExpr({\n unit: unit,\n this: thisExpr,\n });\n }\n return new TimestampTruncExpr({\n this: thisExpr,\n unit: unit,\n });\n}\n\nexport function dateAddIntervalSql (\n dataType: string,\n kind: string,\n): (this: Generator, expression: TimeUnitExpr) => string {\n return function (this: Generator, expression: TimeUnitExpr) {\n const thisArg = this.sql(expression, 'this');\n const interval = new IntervalExpr({\n this: expression.args.expression,\n unit: unitToVar(expression),\n });\n return `${dataType}_${kind}(${thisArg}, ${this.sql(interval)})`;\n };\n}\n\nexport function timestampTruncSql (\n options: {\n func?: string;\n zone?: boolean;\n } = {},\n): (this: Generator, expression: TimestampTruncExpr) => string {\n const {\n func = 'DATE_TRUNC', zone = false,\n } = options;\n return function (this: Generator, expression: TimestampTruncExpr): string {\n const args = [unitToStr(expression), expression.args.this];\n if (zone) {\n args.push(expression.args.zone);\n }\n return this.func(func, args);\n };\n}\n\nexport function noTimestampSql (this: Generator, expression: TimestampExpr): string {\n const zone = expression.args.zone;\n if (!zone) {\n const annotated = annotateTypes(expression, { dialect: this.dialect }).type;\n const targetType: DataTypeExpr | DataTypeExprKind = isInstanceOf(annotated, DataTypeExpr) ? annotated : DataTypeExprKind.TIMESTAMP;\n return this.sql(cast(expression.args.this || '', targetType));\n }\n\n if (TIMEZONES.has(zone.name?.toLowerCase())) {\n return this.sql(\n new AtTimeZoneExpr({\n this: cast(expression.args.this || '', DataTypeExprKind.TIMESTAMP),\n zone: zone,\n }),\n );\n }\n return this.func('TIMESTAMP', [expression.args.this, zone]);\n}\n\nexport function noTimeSql (this: Generator, expression: TimeExpr): string {\n const thisArg = cast(expression.args.this || '', DataTypeExprKind.TIMESTAMPTZ);\n const expr = cast(\n new AtTimeZoneExpr({\n this: thisArg,\n zone: expression.args.zone,\n }),\n DataTypeExprKind.TIME,\n );\n return this.sql(expr);\n}\n\nexport function noDatetimeSql (this: Generator, expression: DatetimeExpr): string {\n const thisArg = expression.args.this;\n const expr = expression.args.expression;\n\n if (expr && TIMEZONES.has(expr.name?.toLowerCase() || '')) {\n const tsTz = cast(thisArg, DataTypeExprKind.TIMESTAMPTZ);\n const ts = cast(new AtTimeZoneExpr({\n this: tsTz,\n zone: expr,\n }), DataTypeExprKind.TIMESTAMP);\n return this.sql(ts);\n }\n\n const date = cast(thisArg, DataTypeExprKind.DATE);\n const time = cast(expr || '', DataTypeExprKind.TIME);\n\n return this.sql(cast(new AddExpr({\n this: date,\n expression: time,\n }), DataTypeExprKind.TIMESTAMP));\n}\n\nexport function leftToSubstringSql (this: Generator, expression: LeftExpr): string {\n return this.sql(\n new SubstringExpr({\n this: expression.args.this,\n start: LiteralExpr.number(1),\n length: expression.args.expression,\n }),\n );\n}\n\nexport function rightToSubstringSql (this: Generator, expression: RightExpr): string {\n return this.sql(\n new SubstringExpr({\n this: expression.args.this,\n start: new SubExpr({\n this: new LengthExpr({ this: expression.args.this }),\n expression: new ParenExpr({\n this: new SubExpr({\n this: expression.args.expression,\n expression: LiteralExpr.number(1),\n }),\n }),\n }),\n }),\n );\n}\n\nexport function timeStrToTimeSql (\n this: Generator,\n expression: TimeStrToTimeExpr,\n options: {\n includePrecision?: boolean;\n } = {},\n): string {\n const { includePrecision = false } = options;\n\n let datatype = DataTypeExpr.build(\n expression.args.zone ? DataTypeExprKind.TIMESTAMPTZ : DataTypeExprKind.TIMESTAMP,\n );\n\n if (expression.args.this instanceof LiteralExpr && includePrecision) {\n const precision = subsecondPrecision(expression.args.this.name);\n if (0 < precision && datatype) {\n datatype = DataTypeExpr.build(\n datatype.args.this,\n {\n expressions: new DataTypeParamExpr({\n this: LiteralExpr.number(precision),\n }),\n },\n );\n }\n }\n\n return this.sql(cast(expression.args.this, datatype, { dialect: this.dialect }));\n}\n\nexport function dateStrToDateSql (this: Generator, expression: DateStrToDateExpr): string {\n const thisArg = expression.args.this;\n const castArg: string | Expression = (thisArg instanceof Expression || typeof thisArg === 'string') ? thisArg : '';\n return this.sql(cast(castArg, DataTypeExprKind.DATE));\n}\n\nexport function encodeDecodeSql (\n this: Generator,\n expression: EncodeExpr | DecodeExpr,\n name: string,\n options: { replace?: boolean } = {},\n): string {\n const {\n replace = true,\n } = options;\n const charset = expression.args.charset;\n if (charset && !['utf-8', 'utf8'].includes(charset.name?.toLowerCase())) {\n this.unsupported(`Expected utf-8 character set, got ${charset}.`);\n }\n\n return this.func(name, [expression.args.this, replace ? expression.getArgKey('replace') as Expression | undefined : undefined]);\n}\n\nexport function decodeToCaseSql (this: Generator, expression: DecodeCaseExpr): string {\n const expressions = expression.args.expressions || [];\n const condition = expressions[0];\n const rest = expressions.slice(1);\n\n const caseExpr = new CaseExpr({});\n for (let i = 0; i < rest.length - 1; i += 2) {\n const left = condition;\n const right = rest[i];\n const whenCond = new OrExpr({\n this: new EqExpr({\n this: left,\n expression: right,\n }),\n expression: new ParenExpr({\n this: new AndExpr({\n this: new IsExpr({\n this: left,\n expression: new NullExpr(),\n }),\n expression: new IsExpr({\n this: right,\n expression: new NullExpr(),\n }),\n }),\n }),\n });\n caseExpr.append('ifs', new IfExpr({\n this: whenCond,\n true: rest[i + 1],\n }));\n }\n\n if (rest.length % 2 === 1) {\n caseExpr.setArgKey('default', rest[rest.length - 1]);\n }\n\n return this.sql(caseExpr);\n}\n\nexport function minOrLeast (this: Generator, expression: MinExpr): string {\n const name = 0 < (expression.args.expressions?.length ?? 0) ? 'LEAST' : 'MIN';\n return renameFunc(name).call(this, expression);\n}\n\nexport function maxOrGreatest (this: Generator, expression: MaxExpr): string {\n const name = 0 < (expression.args.expressions?.length ?? 0) ? 'GREATEST' : 'MAX';\n return renameFunc(name).call(this, expression);\n}\n\nexport function countIfToSum (this: Generator, expression: CountIfExpr): string {\n let cond: ExpressionValue | undefined = expression.args.this;\n\n if (cond instanceof DistinctExpr) {\n cond = cond.args.expressions?.[0];\n this.unsupported('DISTINCT is not supported when converting COUNT_IF to SUM');\n }\n\n return this.func('sum', cond\n ? [\n new IfExpr({\n this: cond,\n true: LiteralExpr.number(1),\n false: LiteralExpr.number(0),\n }),\n ]\n : []);\n}\n\nexport function trimSql (this: Generator, expression: TrimExpr, options: { defaultTrimType?: string } = {}): string {\n const { defaultTrimType = '' } = options;\n\n const target = this.sql(expression, 'this');\n const trimType = this.sql(expression, 'position') || defaultTrimType;\n const removeChars = this.sql(expression, 'expression');\n const collation = this.sql(expression, 'collation');\n\n if (!removeChars) {\n return this.trimSql(expression);\n }\n\n const typePart = trimType ? `${trimType} ` : '';\n const charPart = removeChars ? `${removeChars} ` : '';\n const fromPart = typePart || charPart ? 'FROM ' : '';\n const collPart = collation ? ` COLLATE ${collation}` : '';\n\n return `TRIM(${typePart}${charPart}${fromPart}${target}${collPart})`;\n}\n\nexport function strToTimeSql (this: Generator, expression: StrToTimeExpr): string {\n return this.func('STRPTIME', [expression.args.this, this.formatTime(expression)]);\n}\n\nexport function concatToDPipeSql (this: Generator, expression: ConcatExpr): string {\n return this.sql(\n (expression.args.expressions ?? []).reduce((acc, curr) => new DPipeExpr({\n this: acc,\n expression: curr,\n })),\n );\n}\n\nexport function concatWsToDPipeSql (this: Generator, expression: ConcatWsExpr): string {\n const [delim, ...rest] = expression.args.expressions ?? [];\n return this.sql(\n rest.reduce((acc, curr) =>\n new DPipeExpr({\n this: acc,\n expression: new DPipeExpr({\n this: delim,\n expression: curr,\n }),\n })),\n );\n}\n\nexport function regexpExtractSql (\n this: Generator,\n expression: RegexpExtractExpr | RegexpExtractAllExpr,\n): string {\n let group = expression.args.group;\n\n if (group && group.name === String(this.dialect._constructor.REGEXP_EXTRACT_DEFAULT_GROUP)) {\n group = undefined;\n }\n\n return this.func((expression._constructor as typeof FuncExpr).sqlName(), [\n expression.args.this,\n expression.args.expression,\n group,\n ]);\n}\n\nexport function regexpReplaceSql (this: Generator, expression: RegexpReplaceExpr): string {\n return this.func('REGEXP_REPLACE', [\n expression.args.this,\n expression.args.expression,\n expression.args.replacement,\n ]);\n}\nexport function pivotColumnNames (aggregations: Expression[], options: { dialect: DialectType }): string[] {\n const { dialect } = options;\n const names: string[] = [];\n for (const agg of aggregations) {\n if (agg instanceof AliasExpr) {\n names.push(agg.alias);\n } else {\n const aggAllUnquoted = agg.transform((node) => {\n if (node instanceof IdentifierExpr) {\n return new IdentifierExpr({\n this: node.name,\n quoted: false,\n });\n }\n return node;\n });\n names.push(aggAllUnquoted.sql({\n dialect,\n normalizeFunctions: NormalizeFunctions.LOWER,\n }));\n }\n }\n return names;\n}\n\nexport function binaryFromFunction<T extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExprType: new (args: any) => T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any[]) => T {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any[]) => new ExprType({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function buildTimestampTrunc (args: any[]): TimestampTruncExpr {\n return new TimestampTruncExpr({\n this: seqGet(args, 1),\n unit: seqGet(args, 0),\n });\n}\n\nexport function buildTrunc (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n args: any[],\n options: {\n dialect: DialectType;\n dateTruncUnabbreviate?: boolean;\n defaultDateTruncUnit?: string;\n dateTruncRequiresPart?: boolean;\n },\n): DateTruncExpr | TruncExpr | AnonymousExpr {\n const {\n dialect, dateTruncUnabbreviate = true, defaultDateTruncUnit, dateTruncRequiresPart = true,\n } = options;\n let thisArg = seqGet(args, 0);\n let second = seqGet(args, 1);\n\n if (thisArg && !thisArg.type) {\n thisArg = annotateTypes(thisArg, { dialect });\n }\n if (second && !second.type) {\n second = annotateTypes(second, { dialect });\n }\n\n const isThisTemporal = thisArg?.isType(DataTypeExpr.TEMPORAL_TYPES);\n const isSecondText = second?.isType(DataTypeExpr.TEXT_TYPES);\n const isThisNumeric = thisArg?.isType(DataTypeExpr.NUMERIC_TYPES);\n const isSecondNumeric = second?.isType(DataTypeExpr.NUMERIC_TYPES);\n\n if ((thisArg && isThisTemporal && (second || defaultDateTruncUnit)) || (second && isSecondText)) {\n const unit = second || LiteralExpr.string(defaultDateTruncUnit ?? '');\n return new DateTruncExpr({\n this: thisArg,\n unit,\n unabbreviate: dateTruncUnabbreviate,\n });\n }\n\n if (\n (thisArg && isThisNumeric)\n || (second && isSecondNumeric)\n || (!dateTruncRequiresPart && !second)\n ) {\n return new TruncExpr({\n this: thisArg,\n decimals: second,\n });\n }\n\n return new AnonymousExpr({\n this: 'TRUNC',\n expressions: args,\n });\n}\n\nexport function anyValueToMaxSql (this: Generator, expression: AnyValueExpr): string {\n return this.func('MAX', [expression.args.this]);\n}\n\nexport function boolXorSql (this: Generator, expression: XorExpr): string {\n const a = this.sql(expression.left);\n const b = this.sql(expression.right);\n return `(${a} AND (NOT ${b})) OR ((NOT ${a}) AND {b})`;\n}\n\nexport function isParseJson (expression: unknown): boolean {\n return expression instanceof ParseJsonExpr || (expression instanceof CastExpr && expression.isType('json'));\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isnullToIsNull (args: any[]): Expression {\n return new ParenExpr({\n this: new IsExpr({\n this: seqGet(args, 0),\n expression: null_(),\n }),\n });\n}\n\nexport function generatedAsIdentityColumnConstraintSql (\n this: Generator,\n expression: GeneratedAsIdentityColumnConstraintExpr,\n): string {\n const start = this.sql(expression, 'start') || '1';\n const increment = this.sql(expression, 'increment') || '1';\n return `IDENTITY(${start}, ${increment})`;\n}\n\nexport function argMaxOrMinNoCount (name: string): (this: Generator, expression: ArgMaxExpr | ArgMinExpr) => string {\n return function (this: Generator, expression: ArgMaxExpr | ArgMinExpr): string {\n return this.func(name, [expression.args.this, expression.args.expression]);\n };\n}\n\nexport function tsOrDsAddCast (expression: TsOrDsAddExpr): TsOrDsAddExpr {\n if (!expression.args.this) return expression;\n let thisArg = expression.args.this.copy();\n const returnType = expression.returnType;\n assertIsInstanceOf(returnType, DataTypeExpr);\n\n if (returnType.isType(DataTypeExprKind.DATE)) {\n thisArg = cast(thisArg, DataTypeExprKind.TIMESTAMP);\n }\n\n expression.args.this?.replace(cast(thisArg, returnType));\n return expression;\n}\n\nexport function dateDeltaSql (\n name: string,\n options: {\n cast?: boolean;\n } = {},\n): (this: Generator, expression: DateAddExpr | DateDiffExpr | DateSubExpr | TsOrDsAddExpr | TsOrDsDiffExpr) => string {\n const { cast = false } = options;\n return function (this: Generator, expression): string {\n let expr = expression;\n if (cast && expression instanceof TsOrDsAddExpr) {\n expr = tsOrDsAddCast(expression);\n }\n\n return this.func(name, [\n unitToVar(expr),\n expr.args.expression,\n expr.args.this,\n ]);\n };\n}\n\nexport function dateDeltaToBinaryIntervalOp (\n options: {\n cast?: boolean;\n } = {},\n): (this: Generator, expression: DatetimeAddExpr | DatetimeSubExpr) => string {\n const { cast: shouldCast = true } = options;\n return function (this: Generator, expression: DatetimeAddExpr | DatetimeSubExpr): string {\n let thisArg = expression.args.this;\n const unit = unitToVar(expression);\n const op = expression instanceof DateAddExpr || expression instanceof TimeAddExpr || expression instanceof TimestampAddExpr || expression instanceof DatetimeAddExpr || expression instanceof TsOrDsAddExpr ? '+' : '-';\n\n let toType: DataTypeExpr | DataTypeExprKind | undefined = undefined;\n if (shouldCast) {\n if (expression instanceof TsOrDsAddExpr) {\n const rt = expression.returnType;\n assertIsInstanceOf(rt, DataTypeExpr);\n toType = rt;\n } else if (thisArg?.isString) {\n toType = (expression instanceof DatetimeAddExpr || expression instanceof DatetimeSubExpr)\n ? DataTypeExprKind.DATETIME\n : DataTypeExprKind.DATE;\n }\n }\n\n thisArg = toType ? cast(thisArg, toType) : thisArg;\n const expr = expression.args.expression;\n const interval = expr instanceof IntervalExpr\n ? expr\n : new IntervalExpr({\n this: expr,\n unit,\n });\n\n return `${this.sql(thisArg)} ${op} ${this.sql(interval)}`;\n };\n}\n\nexport function unitToStr (expression: TsOrDsDiffExpr | DateAddOrSub | DateTruncExpr | TimeUnitExpr, options: {\n defaultValue?: string;\n} = {}): Expression | undefined {\n const { defaultValue = 'DAY' } = options;\n const unit = expression.args.unit;\n if (unit instanceof LiteralExpr) {\n }\n if (!unit) {\n return defaultValue ? LiteralExpr.string(defaultValue) : undefined;\n }\n\n if (unit instanceof PlaceholderExpr || !(unit instanceof VarExpr || unit instanceof LiteralExpr || unit instanceof IdentifierExpr || unit instanceof ColumnExpr)) {\n return unit;\n }\n\n return LiteralExpr.string(unit.name.toUpperCase());\n}\n\nexport function unitToVar (expression: TimeUnitExpr, options: {\n defaultValue?: string;\n} = {}): Expression | undefined {\n const { defaultValue = 'DAY' } = options;\n const unit = expression.args.unit;\n\n if (unit instanceof VarExpr || unit instanceof PlaceholderExpr || unit instanceof ColumnExpr) {\n return unit;\n }\n\n const value = unit?.name.toUpperCase() || defaultValue;\n return value ? var_(value) : undefined;\n}\n\nexport function mapDatePart (part: string | Expression | undefined, options: { dialect?: DialectType } = {}): Expression | undefined {\n const { dialect = Dialect } = options;\n if (part === undefined) return part;\n\n const partName = part instanceof Expression ? part.name : part;\n\n const mapped =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n part instanceof Expression && !(part instanceof ColumnExpr) && (part as any).parts?.length !== 1 ? Dialect.getOrRaise(dialect)._constructor.DATE_PART_MAPPING[partName.toUpperCase()] : undefined;\n if (mapped) {\n if (part instanceof Expression && part.isString) return LiteralExpr.string(mapped);\n if (typeof part === 'string') return LiteralExpr.string(part);\n return var_(mapped);\n }\n\n return part instanceof Expression ? part : LiteralExpr.string(part);\n}\n\nexport function noLastDaySql (this: Generator, expression: LastDayExpr): string {\n const truncCurrDate = new DateTruncExpr({\n this: expression.args.this,\n unit: var_('MONTH'),\n });\n const plusOneMonth = new DateAddExpr({\n this: truncCurrDate,\n expression: LiteralExpr.number(1),\n unit: var_('MONTH'),\n });\n const minusOneDay = new DateSubExpr({\n this: plusOneMonth,\n expression: LiteralExpr.number(1),\n unit: var_('DAY'),\n });\n\n return this.sql(cast(minusOneDay, DataTypeExprKind.DATE));\n}\n\nexport function mergeWithoutTargetSql (this: Generator, expression: MergeExpr): string {\n const alias = expression.args.this?.args.alias;\n const normalize = (id: Expression | undefined) => id ? this.dialect.normalizeIdentifier(id).name : undefined;\n\n const thisThis = expression.args.this?.args.this;\n const targets = new Set([normalize(isInstanceOf(thisThis, Expression) ? thisThis : undefined)]);\n if (isInstanceOf(alias, Expression)) targets.add(typeof alias.args.this === 'string' ? alias.args.this : normalize(isInstanceOf(alias.args.this, Expression) ? alias.args.this : undefined));\n\n for (const when of expression.args.whens?.args.expressions || []) {\n if (!isInstanceOf(when, WhenExpr)) continue;\n const then = when.args.then;\n if (then instanceof UpdateExpr) {\n for (const equals of then.findAll(EqExpr)) {\n const lhs = equals.args.this;\n if (lhs instanceof ColumnExpr && targets.has(normalize(typeof lhs.args.table === 'string' ? toIdentifier(lhs.args.table) : lhs.args.table))) {\n lhs.replace(new ColumnExpr({ this: lhs.args.this }));\n }\n }\n } else if (then instanceof InsertExpr) {\n const columnList = then.args.this;\n if (columnList instanceof TupleExpr) {\n for (const colVal of columnList.args.expressions || []) {\n if (!(colVal instanceof Expression)) continue;\n const col = colVal;\n const tableArg = col.getArgKey('table');\n if (\n (col.args.this instanceof IdentifierExpr || col.args.this instanceof StarExpr)\n && tableArg\n && targets.has(tableArg instanceof Expression ? normalize(tableArg) : tableArg.toString())) {\n col.replace(new ColumnExpr({ this: col.args.this }));\n }\n }\n }\n }\n }\n\n return this.mergeSql(expression);\n}\n\nexport function buildJsonExtractPath<T extends JsonExtractExpr | JsonExtractScalarExpr | JsonbExtractExpr | JsonbExtractScalarExpr> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExprType: (typeof JsonExtractExpr | typeof JsonExtractScalarExpr | typeof JsonbExtractExpr | typeof JsonbExtractScalarExpr) & (new (arg: any) => T),\n options: {\n zeroBasedIndexing?: boolean;\n arrowReqJsonType?: boolean;\n jsonType?: string;\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any) => T {\n const {\n zeroBasedIndexing = true, arrowReqJsonType = false, jsonType,\n } = options;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any) => {\n const segments: JsonPathPartExpr[] = [new JsonPathRootExpr({})];\n for (const arg of args.slice(1)) {\n if (!(arg instanceof LiteralExpr)) {\n return ExprType.fromArgList(args);\n }\n\n const text = arg.name;\n if (isInt(text) && (!arrowReqJsonType || !arg.isString)) {\n const index = parseInt(text);\n segments.push(new JsonPathSubscriptExpr({ this: zeroBasedIndexing ? index : index - 1 }));\n } else {\n segments.push(new JsonPathKeyExpr({ this: text }));\n }\n }\n\n args.splice(2);\n const kwargs: JsonExtractExprArgs & Record<string, unknown> = {\n this: seqGet(args, 0),\n expression: new JsonPathExpr({ expressions: segments }),\n };\n\n if (!(ExprType.prototype instanceof JsonbExtractExpr)) {\n kwargs.onlyJsonTypes = arrowReqJsonType;\n }\n if (jsonType !== undefined) kwargs.jsonType = jsonType;\n\n return new ExprType(kwargs) as T;\n };\n}\n\nexport function jsonExtractSegments (\n name: string,\n options: {\n quotedIndex?: boolean;\n op?: string;\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (this: Generator, expression: any) => string {\n const {\n quotedIndex = true, op,\n } = options;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (this: Generator, expression: any): string {\n const path = expression.args.expression;\n if (!(path instanceof JsonPathExpr)) {\n return renameFunc(name).call(this, expression);\n }\n\n const segments: string[] = [];\n for (const segment of path.args.expressions ?? []) {\n let segmentSql = this.sql(segment);\n if (segmentSql) {\n if (segment instanceof JsonPathPartExpr && (quotedIndex || !(segment instanceof JsonPathSubscriptExpr))) {\n if (path.args.escape) segmentSql = this.escapeStr(segmentSql);\n segmentSql = `${this.dialect._constructor.QUOTE_START}${segmentSql}${this.dialect._constructor.QUOTE_END}`;\n }\n segments.push(segmentSql);\n }\n }\n\n if (op) return [this.sql(expression.args.this), ...segments].join(` ${op} `);\n return this.func(name, [expression.args.this, ...segments]);\n };\n}\n\nexport function jsonPathKeyOnlyName (this: Generator, expression: JsonPathKeyExpr): string {\n if (expression.args.this instanceof JsonPathWildcardExpr) {\n this.unsupported('Unsupported wildcard in JsonPathKey expression');\n }\n return expression.name;\n}\n\nexport function filterArrayUsingUnnest (this: Generator, expression: ArrayFilterExpr | ArrayRemoveExpr): string {\n let cond = expression.args.expression;\n let aliasExpr: Expression = LiteralExpr.string('_u');\n\n if (cond instanceof LambdaExpr && (cond.args.expressions?.length ?? 0) === 1) {\n const firstExpr = cond.args.expressions?.[0];\n if (firstExpr) aliasExpr = firstExpr;\n cond = cond.args.this;\n } else if (expression instanceof ArrayRemoveExpr) {\n cond = new NeqExpr({\n this: aliasExpr,\n expression: expression.args.expression,\n });\n }\n\n const unnest = new UnnestExpr({ expressions: [...(expression.args.this ? [expression.args.this] : [])] });\n const filtered = select(aliasExpr)\n .from(alias(unnest, undefined, { table: [aliasExpr as string | IdentifierExpr] }))\n .where(cond);\n return this.sql(new ArrayExpr({ expressions: [filtered] }));\n}\n\nexport function arrayCompactSql (this: Generator, expression: ArrayCompactExpr): string {\n const lambdaId = new IdentifierExpr({\n this: '_u',\n quoted: false,\n });\n const cond = new IsExpr({\n this: lambdaId,\n expression: null_(),\n }).not();\n return this.sql(new ArrayFilterExpr({\n this: expression.args.this,\n expression: new LambdaExpr({\n this: cond,\n expressions: [lambdaId],\n }),\n }));\n}\n\nexport function removeFromArrayUsingFilter (this: Generator, expression: ArrayRemoveExpr): string {\n const lambdaId = new IdentifierExpr({\n this: '_u',\n quoted: false,\n });\n const cond = new NeqExpr({\n this: lambdaId,\n expression: expression.args.expression,\n });\n const filterSql = this.sql(new ArrayFilterExpr({\n this: expression.args.this,\n expression: new LambdaExpr({\n this: cond,\n expressions: [lambdaId],\n }),\n }));\n\n if (expression.args.nullPropagation && !this.dialect._constructor.ARRAY_FUNCS_PROPAGATES_NULLS) {\n const val = expression.args.expression;\n if ((val instanceof LiteralExpr && !(val instanceof NullExpr)) || val instanceof ArrayExpr) {\n return filterSql;\n }\n return this.sql(new IfExpr({\n this: new IsExpr({\n this: val,\n expression: null_(),\n }),\n true: null_(),\n false: filterSql,\n }));\n }\n return filterSql;\n}\n\nexport function toNumberWithNlsParam (this: Generator, expression: ToNumberExpr): string {\n return this.func(\n 'TO_NUMBER',\n [\n expression.args.this,\n expression.args.format,\n expression.args.nlsparam,\n ],\n );\n}\n\nexport function buildDefaultDecimalType (precision?: number, scale?: number): (dtype: DataTypeExpr) => DataTypeExpr {\n return (dtype: DataTypeExpr) => {\n if (0 < (dtype.args.expressions?.length ?? 0) || precision === undefined) return dtype;\n const params = scale !== undefined ? `${precision}, ${scale}` : `${precision}`;\n return DataTypeExpr.build(`DECIMAL(${params})`) ?? dtype;\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function buildTimestampFromParts (args: any[]): Expression {\n if (args.length === 2) {\n return new AnonymousExpr({\n this: 'TIMESTAMP_FROM_PARTS',\n expressions: args,\n });\n }\n return TimestampFromPartsExpr.fromArgList(args);\n}\n\nexport function sha256Sql (this: Generator, expression: Sha2Expr): string {\n return this.func(`SHA${expression.text('length') || '256'}`, [expression.args.this]);\n}\n\nexport function sha2DigestSql (this: Generator, expression: Sha2DigestExpr): string {\n return this.func(`SHA${expression.text('length') || '256'}`, [expression.args.this]);\n}\n\nexport function sequenceSql (this: Generator, e: Expression): string {\n const expression = e as GenerateSeriesExpr | GenerateDateArrayExpr;\n let start = expression.args.start;\n let end = expression.args.end;\n const step = expression.args.step;\n const targetType = (start instanceof CastExpr ? start.args.to : (end instanceof CastExpr ? end.args.to : undefined));\n\n if (start !== undefined && end !== undefined) {\n if (isType(targetType, ['date', 'timestamp'])) {\n assertIsInstanceOf(targetType, DataTypeExpr);\n if (start instanceof CastExpr && targetType === start.to) end = cast(end, targetType);\n else start = cast(start, targetType);\n }\n\n if (isInstanceOf(expression, GenerateSeriesExpr) && expression.args.isEndExclusive) {\n const stepVal = step || LiteralExpr.number(1);\n const endSub = new SubExpr({\n this: end,\n expression: stepVal,\n });\n end = new ParenExpr({ this: endSub });\n const sequenceCall = new AnonymousExpr({\n this: 'SEQUENCE',\n expressions: [\n start,\n end,\n ...(step ? [step] : []),\n ],\n });\n const zero = LiteralExpr.number(0);\n const shouldReturnEmpty = or([\n new EqExpr({\n this: stepVal.copy(),\n expression: zero.copy(),\n }),\n and([\n new GtExpr({\n this: stepVal.copy(),\n expression: zero.copy(),\n }),\n new GteExpr({\n this: start.copy(),\n expression: end.copy(),\n }),\n ]),\n and([\n new LtExpr({\n this: stepVal.copy(),\n expression: zero.copy(),\n }),\n new LteExpr({\n this: start.copy(),\n expression: end.copy(),\n }),\n ]),\n ]);\n const emptyArrayOrSequence = new IfExpr({\n this: shouldReturnEmpty,\n true: new ArrayExpr({ expressions: [] }),\n false: sequenceCall,\n });\n return this.sql(this.simplifyUnlessLiteral(emptyArrayOrSequence));\n }\n }\n return this.func('SEQUENCE', [\n start,\n end,\n step,\n ]);\n}\n\nexport function buildLike<T extends Expression> (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ExprType: new (args: any) => T,\n options: {\n notLike?: boolean;\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): (args: any[]) => Expression {\n const { notLike = false } = options;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any[]) => {\n let likeExpr: Expression = new ExprType({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n });\n const escape = seqGet(args, 2);\n if (escape) likeExpr = new EscapeExpr({\n this: likeExpr,\n expression: escape,\n });\n return notLike ? new NotExpr({ this: likeExpr }) : likeExpr;\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function buildRegexpExtract<T extends Expression> (ExprType: (typeof RegexpExtractExpr) & (new (args: any) => T)): (args: any[], options: { dialect: Dialect }) => Expression {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (args: any[], { dialect }: { dialect: Dialect }) => {\n const kwargs: RegexpExtractExprArgs = {\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n group: seqGet(args, 2) || LiteralExpr.number(dialect._constructor.REGEXP_EXTRACT_DEFAULT_GROUP),\n parameters: seqGet(args, 3),\n };\n if (ExprType === RegexpExtractExpr) {\n kwargs.nullIfPosOverflow = dialect._constructor.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL;\n }\n return new ExprType(kwargs);\n };\n}\n\nexport function explodeToUnnestSql (this: Generator, expression: LateralExpr): string {\n const thisArg = expression.args.this;\n const aliasExpr = expression.args.alias;\n let crossJoinExpr;\n\n // Handle POSEXPLODE transpilation, regardless of whether it's from Hive LATERAL VIEW or regular LATERAL\n if (thisArg instanceof PosexplodeExpr && aliasExpr) {\n assertIsInstanceOf(aliasExpr, TableAliasExpr);\n const [pos, ...cols] = aliasExpr.args.columns || [];\n assertIsInstanceOf(pos, IdentifierExpr);\n const validCols = cols.filter((c) => c instanceof Expression) as Expression[];\n const lateralSubquery = select([alias(pos.sub(1), pos), ...validCols])\n .from(new UnnestExpr({\n expressions: [...(thisArg.args.this ? [thisArg.args.this] : [])],\n offset: true,\n alias: new TableAliasExpr({\n this: aliasExpr.args.this as IdentifierExpr | undefined,\n columns: [...validCols, pos] as IdentifierExpr[],\n }),\n }));\n crossJoinExpr = new LateralExpr({ this: lateralSubquery.subquery() });\n } else if (thisArg instanceof ExplodeExpr) {\n crossJoinExpr = new UnnestExpr({\n expressions: [...(thisArg.args.this ? [thisArg.args.this] : [])],\n alias: aliasExpr,\n });\n }\n\n if (crossJoinExpr) {\n return this.sql(new JoinExpr({\n this: crossJoinExpr,\n kind: JoinExprKind.CROSS,\n }));\n }\n\n return this.lateralSql(expression);\n}\n\nexport function timestampDiffSql (this: Generator, expression: DatetimeDiffExpr | TimestampDiffExpr): string {\n return this.func('TIMESTAMPDIFF', [\n expression.unit,\n expression.args.expression,\n expression.args.this,\n ]);\n}\n\nexport function noMakeIntervalSql (this: Generator, expression: MakeIntervalExpr, options: {\n sep?: string;\n} = {}): string {\n const { sep = ', ' } = options;\n const args = Object.entries(expression.args).map(([unit, val]) => `${val instanceof KwargExpr ? val.args.expression : val} ${unit}`);\n return `INTERVAL '${args.join(sep)}'`;\n}\n\nexport function lengthOrCharLengthSql (this: Generator, expression: LengthExpr): string {\n return this.func(expression.args.binary ? 'LENGTH' : 'CHAR_LENGTH', [expression.args.this]);\n}\n\nexport function groupConcatSql (this: Generator, expression: GroupConcatExpr, options: {\n funcName?: string;\n sep?: string;\n withinGroup?: boolean;\n onOverflow?: boolean;\n} = {}): string {\n const {\n funcName = 'LISTAGG', sep = ',', withinGroup = true, onOverflow = false,\n } = options;\n let thisArg: Expression | undefined = expression.args.this;\n const separator = this.sql(expression.args.separator || (sep ? LiteralExpr.string(sep) : undefined));\n const overflow = onOverflow && this.sql(expression, 'on_overflow') ? ` ON OVERFLOW ${this.sql(expression, 'on_overflow')}` : '';\n\n let limit = undefined;\n if (thisArg instanceof LimitExpr) {\n limit = thisArg;\n thisArg = limit.args.this?.pop();\n }\n\n const order = thisArg?.find(OrderExpr);\n if (order?.args.this) thisArg = order.args.this.pop();\n\n const formattedArgs = [thisArg, separator ? `${separator}${overflow}` : (overflow || undefined)].filter(Boolean).join(', ');\n let listagg: Expression = new AnonymousExpr({\n this: funcName,\n expressions: [formattedArgs],\n });\n let modifiers = this.sql(limit);\n\n if (order) {\n if (withinGroup) listagg = new WithinGroupExpr({\n this: listagg,\n expression: order,\n });\n else modifiers = `${this.sql(order)}${modifiers}`;\n }\n\n if (modifiers) listagg.setArgKey('expressions', [`${formattedArgs}${modifiers}`]);\n return this.sql(listagg);\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function buildTimeToStrOrToChar (args: any[], { dialect }: { dialect: DialectType }): TimeToStrExpr | ToCharExpr {\n if (args.length === 2) {\n const thisArg = args[0];\n if (!thisArg.type) annotateTypes(thisArg, { dialect: dialect });\n if (thisArg.isType(DataTypeExpr.TEMPORAL_TYPES)) {\n let dialectName: string;\n if (typeof dialect === 'string') {\n dialectName = dialect;\n } else if (typeof dialect === 'function') {\n dialectName = dialect.name.toLowerCase();\n } else {\n dialectName = dialect.constructor.name.toLowerCase();\n }\n return buildFormattedTime(TimeToStrExpr, {\n dialect: dialectName,\n defaultValue: true,\n })(args);\n }\n }\n return ToCharExpr.fromArgList(args);\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function buildReplaceWithOptionalReplacement (args: any[]): ReplaceExpr {\n return new ReplaceExpr({\n this: seqGet(args, 0),\n expression: seqGet(args, 1),\n replacement: seqGet(args, 2) || LiteralExpr.string(''),\n });\n}\n\nexport function regexpReplaceGlobalModifier (expression: RegexpReplaceExpr): Expression | undefined {\n const modifiers = expression.args.modifiers;\n if (!expression.args.singleReplace && (!expression.args.occurrence || (expression.args.occurrence.isInteger && expression.args.occurrence.toValue() === 0))) {\n if (!modifiers || modifiers.isString) {\n return LiteralExpr.string((modifiers?.name || '') + 'g');\n }\n }\n return modifiers;\n}\n\nexport function getBitSql (this: Generator, expression: GetbitExpr): string {\n const value = expression.args.this;\n const pos = expression.args.expression;\n\n if (!expression.args.zeroIsMsb && expression.isType([...DataTypeExpr.SIGNED_INTEGER_TYPES, ...DataTypeExpr.UNSIGNED_INTEGER_TYPES])) {\n const shifted = new BitwiseRightShiftExpr({\n this: value,\n expression: pos,\n });\n const masked = new BitwiseAndExpr({\n this: shifted,\n expression: LiteralExpr.number(1),\n });\n return this.sql(masked);\n }\n return this.func('GET_BIT', [value, pos]);\n}\n","// https://github.com/tobymao/sqlglot/blob/264e95f04d95f2cd7bcf255ee7ae160db36882a7/sqlglot/tokens.py\n\nimport { cache } from './port_internals';\nimport { Dialect } from './dialects/dialect';\nimport { TokenError } from './errors';\nimport {\n inTrie, newTrie, TrieResult, type TrieNode,\n} from './trie';\n\n/**\n * Represents a syntax token's tag.\n */\nexport enum TokenType {\n L_PAREN = 'lParen',\n R_PAREN = 'rParen',\n L_BRACKET = 'lBracket',\n R_BRACKET = 'rBracket',\n L_BRACE = 'lBrace',\n R_BRACE = 'rBrace',\n COMMA = 'comma',\n DOT = 'dot',\n DASH = 'dash',\n PLUS = 'plus',\n COLON = 'colon',\n DOTCOLON = 'dotcolon',\n DCOLON = 'dcolon',\n DCOLONDOLLAR = 'dcolondollar',\n DCOLONPERCENT = 'dcolonpercent',\n DCOLONQMARK = 'dcolonqmark',\n DQMARK = 'dqmark',\n SEMICOLON = 'semicolon',\n STAR = 'star',\n BACKSLASH = 'backslash',\n SLASH = 'slash',\n LT = 'lt',\n LTE = 'lte',\n GT = 'gt',\n GTE = 'gte',\n NOT = 'not',\n EQ = 'eq',\n NEQ = 'neq',\n NULLSAFE_EQ = 'nullsafeEq',\n COLON_EQ = 'colonEq',\n COLON_GT = 'colonGt',\n NCOLON_GT = 'ncolonGt',\n AND = 'and',\n OR = 'or',\n AMP = 'amp',\n DPIPE = 'dpipe',\n PIPE_GT = 'pipeGt',\n PIPE = 'pipe',\n PIPE_SLASH = 'pipeSlash',\n DPIPE_SLASH = 'dpipeSlash',\n CARET = 'caret',\n CARET_AT = 'caretAt',\n TILDE = 'tilde',\n ARROW = 'arrow',\n DARROW = 'darrow',\n FARROW = 'farrow',\n HASH = 'hash',\n HASH_ARROW = 'hashArrow',\n DHASH_ARROW = 'dhashArrow',\n LR_ARROW = 'lrArrow',\n DAT = 'dat',\n LT_AT = 'ltAt',\n AT_GT = 'atGt',\n DOLLAR = 'dollar',\n PARAMETER = 'parameter',\n SESSION = 'session',\n SESSION_PARAMETER = 'sessionParameter',\n SESSION_USER = 'sessionUser',\n DAMP = 'damp',\n AMP_LT = 'ampLt',\n AMP_GT = 'ampGt',\n ADJACENT = 'adjacent',\n XOR = 'xor',\n DSTAR = 'dstar',\n QMARK_AMP = 'qmarkAmp',\n QMARK_PIPE = 'qmarkPipe',\n HASH_DASH = 'hashDash',\n EXCLAMATION = 'exclamation',\n\n URI_START = 'uriStart',\n\n BLOCK_START = 'blockStart',\n BLOCK_END = 'blockEnd',\n\n SPACE = 'space',\n BREAK = 'break',\n\n STRING = 'string',\n NUMBER = 'number',\n IDENTIFIER = 'identifier',\n DATABASE = 'database',\n COLUMN = 'column',\n COLUMN_DEF = 'columnDef',\n SCHEMA = 'schema',\n TABLE = 'table',\n WAREHOUSE = 'warehouse',\n STAGE = 'stage',\n STREAMLIT = 'streamlit',\n VAR = 'var',\n BIT_STRING = 'bitString',\n HEX_STRING = 'hexString',\n BYTE_STRING = 'byteString',\n NATIONAL_STRING = 'nationalString',\n RAW_STRING = 'rawString',\n HEREDOC_STRING = 'heredocString',\n UNICODE_STRING = 'unicodeString',\n\n // types\n BIT = 'bit',\n BOOLEAN = 'boolean',\n TINYINT = 'tinyint',\n UTINYINT = 'utinyint',\n SMALLINT = 'smallint',\n USMALLINT = 'usmallint',\n MEDIUMINT = 'mediumint',\n UMEDIUMINT = 'umediumint',\n INT = 'int',\n UINT = 'uint',\n BIGINT = 'bigint',\n UBIGINT = 'ubigint',\n BIGNUM = 'bignum',\n INT128 = 'int128',\n UINT128 = 'uint128',\n INT256 = 'int256',\n UINT256 = 'uint256',\n FLOAT = 'float',\n DOUBLE = 'double',\n UDOUBLE = 'udouble',\n DECIMAL = 'decimal',\n DECIMAL32 = 'decimal32',\n DECIMAL64 = 'decimal64',\n DECIMAL128 = 'decimal128',\n DECIMAL256 = 'decimal256',\n DECFLOAT = 'decfloat',\n UDECIMAL = 'udecimal',\n BIGDECIMAL = 'bigdecimal',\n CHAR = 'char',\n NCHAR = 'nchar',\n VARCHAR = 'varchar',\n NVARCHAR = 'nvarchar',\n BPCHAR = 'bpchar',\n TEXT = 'text',\n MEDIUMTEXT = 'mediumtext',\n LONGTEXT = 'longtext',\n BLOB = 'blob',\n MEDIUMBLOB = 'mediumblob',\n LONGBLOB = 'longblob',\n TINYBLOB = 'tinyblob',\n TINYTEXT = 'tinytext',\n NAME = 'name',\n BINARY = 'binary',\n VARBINARY = 'varbinary',\n JSON = 'json',\n JSONB = 'jsonb',\n TIME = 'time',\n TIMETZ = 'timetz',\n TIME_NS = 'timeNs',\n TIMESTAMP = 'timestamp',\n TIMESTAMPTZ = 'timestamptz',\n TIMESTAMPLTZ = 'timestampltz',\n TIMESTAMPNTZ = 'timestampntz',\n TIMESTAMP_S = 'timestampS',\n TIMESTAMP_MS = 'timestampMs',\n TIMESTAMP_NS = 'timestampNs',\n DATETIME = 'datetime',\n DATETIME2 = 'datetime2',\n DATETIME64 = 'datetime64',\n SMALLDATETIME = 'smalldatetime',\n DATE = 'date',\n DATE32 = 'date32',\n INT4RANGE = 'int4range',\n INT4MULTIRANGE = 'int4multirange',\n INT8RANGE = 'int8range',\n INT8MULTIRANGE = 'int8multirange',\n NUMRANGE = 'numrange',\n NUMMULTIRANGE = 'nummultirange',\n TSRANGE = 'tsrange',\n TSMULTIRANGE = 'tsmultirange',\n TSTZRANGE = 'tstzrange',\n TSTZMULTIRANGE = 'tstzmultirange',\n DATERANGE = 'daterange',\n DATEMULTIRANGE = 'datemultirange',\n UUID = 'uuid',\n GEOGRAPHY = 'geography',\n GEOGRAPHYPOINT = 'geographypoint',\n NULLABLE = 'nullable',\n GEOMETRY = 'geometry',\n POINT = 'point',\n RING = 'ring',\n LINESTRING = 'linestring',\n LOCALTIME = 'localtime',\n LOCALTIMESTAMP = 'localtimestamp',\n SYSTIMESTAMP = 'systimestamp',\n MULTILINESTRING = 'multilinestring',\n POLYGON = 'polygon',\n MULTIPOLYGON = 'multipolygon',\n HLLSKETCH = 'hllsketch',\n HSTORE = 'hstore',\n SUPER = 'super',\n SERIAL = 'serial',\n SMALLSERIAL = 'smallserial',\n BIGSERIAL = 'bigserial',\n XML = 'xml',\n YEAR = 'year',\n USERDEFINED = 'userdefined',\n MONEY = 'money',\n SMALLMONEY = 'smallmoney',\n ROWVERSION = 'rowversion',\n IMAGE = 'image',\n VARIANT = 'variant',\n OBJECT = 'object',\n INET = 'inet',\n IPADDRESS = 'ipaddress',\n IPPREFIX = 'ipprefix',\n IPV4 = 'ipv4',\n IPV6 = 'ipv6',\n ENUM = 'enum',\n ENUM8 = 'enum8',\n ENUM16 = 'enum16',\n FIXEDSTRING = 'fixedstring',\n LOWCARDINALITY = 'lowcardinality',\n NESTED = 'nested',\n AGGREGATEFUNCTION = 'aggregatefunction',\n SIMPLEAGGREGATEFUNCTION = 'simpleaggregatefunction',\n TDIGEST = 'tdigest',\n UNKNOWN = 'unknown',\n VECTOR = 'vector',\n DYNAMIC = 'dynamic',\n VOID = 'void',\n\n // keywords\n ALIAS = 'alias',\n ALTER = 'alter',\n ALL = 'all',\n ANTI = 'anti',\n ANY = 'any',\n APPLY = 'apply',\n ARRAY = 'array',\n ASC = 'asc',\n ASOF = 'asof',\n ATTACH = 'attach',\n AUTO_INCREMENT = 'autoIncrement',\n BEGIN = 'begin',\n BETWEEN = 'between',\n BULK_COLLECT_INTO = 'bulkCollectInto',\n CACHE = 'cache',\n CASE = 'case',\n CHARACTER_SET = 'characterSet',\n CLUSTER_BY = 'clusterBy',\n COLLATE = 'collate',\n COMMAND = 'command',\n COMMENT = 'comment',\n COMMIT = 'commit',\n CONNECT_BY = 'connectBy',\n CONSTRAINT = 'constraint',\n COPY = 'copy',\n CREATE = 'create',\n CROSS = 'cross',\n CUBE = 'cube',\n CURRENT_DATE = 'currentDate',\n CURRENT_DATETIME = 'currentDatetime',\n CURRENT_SCHEMA = 'currentSchema',\n CURRENT_TIME = 'currentTime',\n CURRENT_TIMESTAMP = 'currentTimestamp',\n CURRENT_USER = 'currentUser',\n CURRENT_ROLE = 'currentRole',\n CURRENT_CATALOG = 'currentCatalog',\n DECLARE = 'declare',\n DEFAULT = 'default',\n DELETE = 'delete',\n DESC = 'desc',\n DESCRIBE = 'describe',\n DETACH = 'detach',\n DICTIONARY = 'dictionary',\n DISTINCT = 'distinct',\n DISTRIBUTE_BY = 'distributeBy',\n DIV = 'div',\n DROP = 'drop',\n ELSE = 'else',\n END = 'end',\n ESCAPE = 'escape',\n EXCEPT = 'except',\n EXECUTE = 'execute',\n EXISTS = 'exists',\n FALSE = 'false',\n FETCH = 'fetch',\n FILE = 'file',\n FILE_FORMAT = 'fileFormat',\n FILTER = 'filter',\n FINAL = 'final',\n FIRST = 'first',\n FOR = 'for',\n FORCE = 'force',\n FOREIGN_KEY = 'foreignKey',\n FORMAT = 'format',\n FROM = 'from',\n FULL = 'full',\n FUNCTION = 'function',\n GET = 'get',\n GLOB = 'glob',\n GLOBAL = 'global',\n GRANT = 'grant',\n GROUP_BY = 'groupBy',\n GROUPING_SETS = 'groupingSets',\n HAVING = 'having',\n HINT = 'hint',\n IGNORE = 'ignore',\n ILIKE = 'ilike',\n IN = 'in',\n INDEX = 'index',\n INDEXED_BY = 'indexedBy',\n INNER = 'inner',\n INSERT = 'insert',\n INSTALL = 'install',\n INTERSECT = 'intersect',\n INTERVAL = 'interval',\n INTO = 'into',\n INTRODUCER = 'introducer',\n IRLIKE = 'irlike',\n IS = 'is',\n ISNULL = 'isnull',\n JOIN = 'join',\n JOIN_MARKER = 'joinMarker',\n KEEP = 'keep',\n KEY = 'key',\n KILL = 'kill',\n LANGUAGE = 'language',\n LATERAL = 'lateral',\n LEFT = 'left',\n LIKE = 'like',\n LIMIT = 'limit',\n LIST = 'list',\n LOAD = 'load',\n LOCK = 'lock',\n MAP = 'map',\n MATCH = 'match',\n MATCH_CONDITION = 'matchCondition',\n MATCH_RECOGNIZE = 'matchRecognize',\n MEMBER_OF = 'memberOf',\n MERGE = 'merge',\n MOD = 'mod',\n MODEL = 'model',\n NATURAL = 'natural',\n NEXT = 'next',\n NOTHING = 'nothing',\n NOTNULL = 'notnull',\n NULL = 'null',\n OBJECT_IDENTIFIER = 'objectIdentifier',\n OFFSET = 'offset',\n ON = 'on',\n ONLY = 'only',\n OPERATOR = 'operator',\n ORDER_BY = 'orderBy',\n ORDER_SIBLINGS_BY = 'orderSiblingsBy',\n ORDERED = 'ordered',\n ORDINALITY = 'ordinality',\n OUT = 'out',\n INOUT = 'inout',\n OUTER = 'outer',\n OVER = 'over',\n OVERLAPS = 'overlaps',\n OVERWRITE = 'overwrite',\n PARTITION = 'partition',\n PARTITION_BY = 'partitionBy',\n PERCENT = 'percent',\n PIVOT = 'pivot',\n PLACEHOLDER = 'placeholder',\n POSITIONAL = 'positional',\n PRAGMA = 'pragma',\n PREWHERE = 'prewhere',\n PRIMARY_KEY = 'primaryKey',\n PROCEDURE = 'procedure',\n PROPERTIES = 'properties',\n PSEUDO_TYPE = 'pseudoType',\n PUT = 'put',\n QUALIFY = 'qualify',\n QUOTE = 'quote',\n QDCOLON = 'qdcolon',\n RANGE = 'range',\n RECURSIVE = 'recursive',\n REFRESH = 'refresh',\n RENAME = 'rename',\n REPLACE = 'replace',\n RETURNING = 'returning',\n REVOKE = 'revoke',\n REFERENCES = 'references',\n RIGHT = 'right',\n RLIKE = 'rlike',\n ROLLBACK = 'rollback',\n ROLLUP = 'rollup',\n ROW = 'row',\n ROWS = 'rows',\n SELECT = 'select',\n SEMI = 'semi',\n SEPARATOR = 'separator',\n SEQUENCE = 'sequence',\n SERDE_PROPERTIES = 'serdeProperties',\n SET = 'set',\n SETTINGS = 'settings',\n SHOW = 'show',\n SIMILAR_TO = 'similarTo',\n SOME = 'some',\n SORT_BY = 'sortBy',\n SOUNDS_LIKE = 'soundsLike',\n START_WITH = 'startWith',\n STORAGE_INTEGRATION = 'storageIntegration',\n STRAIGHT_JOIN = 'straightJoin',\n STRUCT = 'struct',\n SUMMARIZE = 'summarize',\n TABLE_SAMPLE = 'tableSample',\n TAG = 'tag',\n TEMPORARY = 'temporary',\n TOP = 'top',\n THEN = 'then',\n TRUE = 'true',\n TRUNCATE = 'truncate',\n UNCACHE = 'uncache',\n UNION = 'union',\n UNNEST = 'unnest',\n UNPIVOT = 'unpivot',\n UPDATE = 'update',\n USE = 'use',\n USING = 'using',\n VALUES = 'values',\n VARIADIC = 'variadic',\n VIEW = 'view',\n SEMANTIC_VIEW = 'semanticView',\n VOLATILE = 'volatile',\n WHEN = 'when',\n WHERE = 'where',\n WINDOW = 'window',\n WITH = 'with',\n UNIQUE = 'unique',\n UTC_DATE = 'utcDate',\n UTC_TIME = 'utcTime',\n UTC_TIMESTAMP = 'utcTimestamp',\n VERSION_SNAPSHOT = 'versionSnapshot',\n TIMESTAMP_SNAPSHOT = 'timestampSnapshot',\n OPTION = 'option',\n SINK = 'sink',\n SOURCE = 'source',\n ANALYZE = 'analyze',\n NAMESPACE = 'namespace',\n EXPORT = 'export',\n\n // sentinel\n HIVE_TOKEN_STREAM = 'hiveTokenStream',\n}\n\n/**\n * Represents a single token in the SQL lexical analysis.\n */\nexport class Token {\n tokenType: TokenType;\n text: string;\n line: number;\n col: number;\n start: number;\n end: number;\n comments: string[];\n\n /**\n * Creates a token representing a numeric literal.\n *\n * @example Token.number(42)\n */\n static number (number: number): Token {\n return new Token(TokenType.NUMBER, String(number));\n }\n\n /**\n * Creates a token representing a string literal.\n *\n * @example Token.string(\"hello\")\n */\n static string (string: string): Token {\n return new Token(TokenType.STRING, string);\n }\n\n /**\n * Creates a token representing an identifier.\n *\n * @example Token.identifier(\"users\")\n */\n static identifier (identifier: string): Token {\n return new Token(TokenType.IDENTIFIER, identifier);\n }\n\n constructor (\n tokenType: TokenType,\n text: string,\n line: number = 1,\n col: number = 1,\n start: number = 0,\n end: number = 0,\n comments: string[] = [],\n ) {\n this.tokenType = tokenType;\n this.text = text;\n this.line = line;\n this.col = col;\n this.start = start;\n this.end = end;\n this.comments = comments;\n }\n}\n\nexport interface TokenizerOptions {\n dialect?: Dialect | string;\n [index: string]: unknown;\n}\n\n/**\n * Token pair type for denoting opening/closing delimiters.\n */\nexport type TokenPair = string | [string, string];\n\n/**\n * Base tokenizer class for SQL lexical analysis.\n *\n * Tokenizer = sqlglot's _Tokenizer + Tokenizer\n *\n */\nexport class Tokenizer {\n /**\n * Singular token texts to token types\n *\n */\n static SINGLE_TOKENS: Record<string, TokenType> = {\n '(': TokenType.L_PAREN,\n ')': TokenType.R_PAREN,\n '[': TokenType.L_BRACKET,\n ']': TokenType.R_BRACKET,\n '{': TokenType.L_BRACE,\n '}': TokenType.R_BRACE,\n '&': TokenType.AMP,\n '^': TokenType.CARET,\n ':': TokenType.COLON,\n ',': TokenType.COMMA,\n '.': TokenType.DOT,\n '-': TokenType.DASH,\n '=': TokenType.EQ,\n '>': TokenType.GT,\n '<': TokenType.LT,\n '%': TokenType.MOD,\n '!': TokenType.NOT,\n '|': TokenType.PIPE,\n '+': TokenType.PLUS,\n ';': TokenType.SEMICOLON,\n '/': TokenType.SLASH,\n '\\\\': TokenType.BACKSLASH,\n '*': TokenType.STAR,\n '~': TokenType.TILDE,\n '?': TokenType.PLACEHOLDER,\n '@': TokenType.PARAMETER,\n '#': TokenType.HASH,\n // Used for breaking a var like x'y' but nothing else the token type doesn't matter\n '\\'': TokenType.UNKNOWN,\n '`': TokenType.UNKNOWN,\n '\"': TokenType.UNKNOWN,\n };\n\n /**\n * Bit string prefixes.\n *\n * @example [[\"b'\", \"'\"], [\"B'\", \"'\"]]\n */\n static BIT_STRINGS: TokenPair[] = [];\n\n /**\n * Byte string prefixes.\n *\n * @example [[\"b'\", \"'\"], [\"B'\", \"'\"]]\n */\n static BYTE_STRINGS: TokenPair[] = [];\n\n /**\n * Hex string prefixes.\n *\n * @example [[\"x'\", \"'\"], [\"X'\", \"'\"], [\"0x\", \"\"]]\n */\n static HEX_STRINGS: TokenPair[] = [];\n\n /**\n * Raw string prefixes.\n *\n * @example [[\"r'\", \"'\"], [\"R'\", \"'\"]]\n */\n static RAW_STRINGS: TokenPair[] = [];\n\n /**\n * Heredoc string prefixes.\n *\n * @example [[\"$$\", \"$$\"]]\n */\n static HEREDOC_STRINGS: TokenPair[] = [];\n\n /**\n * Identifier quote delimiters.\n *\n * @example ['\"', '`', ['[', ']']]\n */\n static IDENTIFIERS: TokenPair[] = ['\"'];\n\n /**\n * String quote delimiters.\n *\n * @example [\"'\"]\n */\n static QUOTES: TokenPair[] = ['\\''];\n\n /**\n * Unicode string prefixes.\n *\n * @example [[\"u'\", \"'\"], [\"U'\", \"'\"]]\n */\n static UNICODE_STRINGS: TokenPair[] = [];\n\n /**\n * Characters that can be escaped in strings.\n *\n * @example [\"'\", \"\\\\\"]\n */\n static STRING_ESCAPES: string[] = ['\\''];\n\n /**\n * Characters that can be escaped in byte strings.\n *\n * @example [\"\\\\\"]\n */\n static get BYTE_STRING_ESCAPES (): string[] {\n return this.STRING_ESCAPES;\n }\n\n /**\n * Set of single-character tokens that can appear in variable names.\n *\n * Used to break variable-like tokens when these characters are encountered.\n *\n * @example new Set(['@', '$'])\n */\n static VAR_SINGLE_TOKENS: Set<string> = new Set();\n\n /**\n * Characters that can follow an escape character.\n *\n * @example [\"n\", \"t\", \"r\", \"\\\\\"]\n */\n static ESCAPE_FOLLOW_CHARS: string[] = [];\n\n /**\n * Characters that can be escaped in identifiers.\n *\n * @example ['\"']\n */\n static IDENTIFIER_ESCAPES: string[] = [];\n\n /**\n * Whether the heredoc tags follow the same lexical rules as unquoted identifiers.\n *\n */\n static HEREDOC_TAG_IS_IDENTIFIER = false;\n\n /**\n * Token that we'll generate as a fallback if the heredoc prefix doesn't correspond to a heredoc.\n *\n */\n static HEREDOC_STRING_ALTERNATIVE = TokenType.VAR;\n\n /**\n * Whether string escape characters function as such when placed within raw strings.\n *\n */\n static STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS = true;\n\n /**\n * Whether nested comments are supported.\n *\n */\n static NESTED_COMMENTS = true;\n\n /**\n * The string that marks the start of a hint comment.\n *\n */\n static HINT_START = '/*+';\n\n /**\n * Set of token types that can precede a hint.\n *\n */\n static TOKENS_PRECEDING_HINT = new Set([\n TokenType.SELECT,\n TokenType.INSERT,\n TokenType.UPDATE,\n TokenType.DELETE,\n ]);\n\n /**\n * Dictionary mapping comment start delimiters to end delimiters (or null for single-line comments).\n *\n * Includes Jinja comments ({# #}) by default.\n *\n * @example { '--': undefined, '{#': '#}' }\n */\n @cache\n static get _COMMENTS (): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n for (const comment of this.COMMENTS) {\n if (typeof comment === 'string') {\n result[comment] = undefined;\n } else {\n result[comment[0]] = comment[1];\n }\n }\n result['{#'] = '#}';\n if (this.HINT_START in this.KEYWORDS) {\n result[this.HINT_START] = '*/';\n }\n return result;\n }\n\n /**\n * Dictionary mapping format string prefixes to [closing delimiter, token type] tuples.\n */\n @cache\n static get _FORMAT_STRINGS (): Record<string, [string, TokenType]> {\n const nationalStrings: Record<string, [string, TokenType]> = {};\n for (const [start, end] of Object.entries(this._QUOTES)) {\n for (const prefix of ['n', 'N']) {\n nationalStrings[prefix + start] = [end, TokenType.NATIONAL_STRING];\n }\n }\n return {\n ...nationalStrings,\n ...this.quotesToFormat(TokenType.BIT_STRING, this.BIT_STRINGS),\n ...this.quotesToFormat(TokenType.BYTE_STRING, this.BYTE_STRINGS),\n ...this.quotesToFormat(TokenType.HEX_STRING, this.HEX_STRINGS),\n ...this.quotesToFormat(TokenType.STRING, this.RAW_STRINGS),\n ...this.quotesToFormat(TokenType.HEREDOC_STRING, this.HEREDOC_STRINGS),\n ...this.quotesToFormat(TokenType.UNICODE_STRING, this.UNICODE_STRINGS),\n };\n }\n\n /**\n * Dictionary mapping opening identifier delimiters to closing delimiters.\n *\n * @example { '\"': '\"', '`': '`' }\n */\n @cache\n static get _IDENTIFIERS (): Record<string, string> {\n return this.convertQuotes(this.IDENTIFIERS);\n }\n\n /**\n * Set of characters that can be escaped in identifiers.\n *\n * @example new Set(['\"'])\n */\n @cache\n static get _IDENTIFIER_ESCAPES (): Set<string> {\n return new Set(this.IDENTIFIER_ESCAPES);\n }\n\n /**\n * Dictionary mapping opening quote delimiters to closing delimiters.\n *\n * @example { \"'\": \"'\", '\"': '\"' }\n */\n @cache\n static get _QUOTES (): Record<string, string> {\n return this.convertQuotes(this.QUOTES);\n }\n\n /**\n * Set of characters that can be escaped in strings.\n *\n * @example new Set([\"'\", \"\\\\\"])\n */\n @cache\n static get _STRING_ESCAPES (): Set<string> {\n return new Set(this.STRING_ESCAPES);\n }\n\n /**\n * Set of characters that can be escaped in byte strings.\n *\n * @example new Set([\"\\\\\"])\n */\n @cache\n static get _BYTE_STRING_ESCAPES (): Set<string> {\n return new Set(this.BYTE_STRING_ESCAPES);\n }\n\n /**\n * Set of characters that can follow an escape character.\n *\n * @example new Set([\"n\", \"t\", \"r\"])\n */\n @cache\n static get _ESCAPE_FOLLOW_CHARS (): Set<string> {\n return new Set(this.ESCAPE_FOLLOW_CHARS);\n }\n\n /**\n * Trie structure for efficient keyword matching.\n *\n * Contains keywords that have spaces or contain single-token characters,\n * allowing for efficient prefix-based scanning of multi-character keywords.\n */\n @cache\n static get _KEYWORD_TRIE (): TrieNode {\n const singleTokenChars = Object.keys(this.SINGLE_TOKENS);\n const keys = [\n ...Object.keys(this.KEYWORDS),\n ...Object.keys(this._COMMENTS),\n ...Object.keys(this._QUOTES),\n ...Object.keys(this._FORMAT_STRINGS),\n ];\n return newTrie(\n keys\n .filter((key) => key.includes(' ') || singleTokenChars.some((c) => key.includes(c)))\n .map((key) => Array.from(key.toUpperCase())),\n );\n }\n\n /**\n * Override this in subclasses to add or change keywords for a dialect.\n * The `KEYWORDS` getter will return this value (with caching).\n */\n static ORIGINAL_KEYWORDS: Record<string, TokenType> = {\n '{%': TokenType.BLOCK_START,\n '{%+': TokenType.BLOCK_START,\n '{%-': TokenType.BLOCK_START,\n '%}': TokenType.BLOCK_END,\n '+%}': TokenType.BLOCK_END,\n '-%}': TokenType.BLOCK_END,\n '{{+': TokenType.BLOCK_START,\n '{{-': TokenType.BLOCK_START,\n '+}}': TokenType.BLOCK_END,\n '-}}': TokenType.BLOCK_END,\n [this.HINT_START]: TokenType.HINT,\n '&<': TokenType.AMP_LT,\n '&>': TokenType.AMP_GT,\n '==': TokenType.EQ,\n '::': TokenType.DCOLON,\n '?::': TokenType.QDCOLON,\n '||': TokenType.DPIPE,\n '|>': TokenType.PIPE_GT,\n '>=': TokenType.GTE,\n '<=': TokenType.LTE,\n '<>': TokenType.NEQ,\n '!=': TokenType.NEQ,\n ':=': TokenType.COLON_EQ,\n '<=>': TokenType.NULLSAFE_EQ,\n '->': TokenType.ARROW,\n '->>': TokenType.DARROW,\n '=>': TokenType.FARROW,\n '#>': TokenType.HASH_ARROW,\n '#>>': TokenType.DHASH_ARROW,\n '<->': TokenType.LR_ARROW,\n '&&': TokenType.DAMP,\n '??': TokenType.DQMARK,\n '~~~': TokenType.GLOB,\n '~~': TokenType.LIKE,\n '~~*': TokenType.ILIKE,\n '~*': TokenType.IRLIKE,\n '-|-': TokenType.ADJACENT,\n 'ALL': TokenType.ALL,\n 'AND': TokenType.AND,\n 'ANTI': TokenType.ANTI,\n 'ANY': TokenType.ANY,\n 'ASC': TokenType.ASC,\n 'AS': TokenType.ALIAS,\n 'ASOF': TokenType.ASOF,\n 'AUTOINCREMENT': TokenType.AUTO_INCREMENT,\n 'AUTO_INCREMENT': TokenType.AUTO_INCREMENT,\n 'BEGIN': TokenType.BEGIN,\n 'BETWEEN': TokenType.BETWEEN,\n 'CACHE': TokenType.CACHE,\n 'UNCACHE': TokenType.UNCACHE,\n 'CASE': TokenType.CASE,\n 'CHARACTER SET': TokenType.CHARACTER_SET,\n 'CLUSTER BY': TokenType.CLUSTER_BY,\n 'COLLATE': TokenType.COLLATE,\n 'COLUMN': TokenType.COLUMN,\n 'COMMIT': TokenType.COMMIT,\n 'CONNECT BY': TokenType.CONNECT_BY,\n 'CONSTRAINT': TokenType.CONSTRAINT,\n 'COPY': TokenType.COPY,\n 'CREATE': TokenType.CREATE,\n 'CROSS': TokenType.CROSS,\n 'CUBE': TokenType.CUBE,\n 'CURRENT_DATE': TokenType.CURRENT_DATE,\n 'CURRENT_SCHEMA': TokenType.CURRENT_SCHEMA,\n 'CURRENT_TIME': TokenType.CURRENT_TIME,\n 'CURRENT_TIMESTAMP': TokenType.CURRENT_TIMESTAMP,\n 'CURRENT_USER': TokenType.CURRENT_USER,\n 'CURRENT_CATALOG': TokenType.CURRENT_CATALOG,\n 'DATABASE': TokenType.DATABASE,\n 'DEFAULT': TokenType.DEFAULT,\n 'DELETE': TokenType.DELETE,\n 'DESC': TokenType.DESC,\n 'DESCRIBE': TokenType.DESCRIBE,\n 'DISTINCT': TokenType.DISTINCT,\n 'DISTRIBUTE BY': TokenType.DISTRIBUTE_BY,\n 'DIV': TokenType.DIV,\n 'DROP': TokenType.DROP,\n 'ELSE': TokenType.ELSE,\n 'END': TokenType.END,\n 'ENUM': TokenType.ENUM,\n 'ESCAPE': TokenType.ESCAPE,\n 'EXCEPT': TokenType.EXCEPT,\n 'EXECUTE': TokenType.EXECUTE,\n 'EXISTS': TokenType.EXISTS,\n 'FALSE': TokenType.FALSE,\n 'FETCH': TokenType.FETCH,\n 'FILTER': TokenType.FILTER,\n 'FILE': TokenType.FILE,\n 'FIRST': TokenType.FIRST,\n 'FULL': TokenType.FULL,\n 'FUNCTION': TokenType.FUNCTION,\n 'FOR': TokenType.FOR,\n 'FOREIGN KEY': TokenType.FOREIGN_KEY,\n 'FORMAT': TokenType.FORMAT,\n 'FROM': TokenType.FROM,\n 'GEOGRAPHY': TokenType.GEOGRAPHY,\n 'GEOMETRY': TokenType.GEOMETRY,\n 'GLOB': TokenType.GLOB,\n 'GROUP BY': TokenType.GROUP_BY,\n 'GROUPING SETS': TokenType.GROUPING_SETS,\n 'HAVING': TokenType.HAVING,\n 'ILIKE': TokenType.ILIKE,\n 'IN': TokenType.IN,\n 'INDEX': TokenType.INDEX,\n 'INET': TokenType.INET,\n 'INNER': TokenType.INNER,\n 'INSERT': TokenType.INSERT,\n 'INTERVAL': TokenType.INTERVAL,\n 'INTERSECT': TokenType.INTERSECT,\n 'INTO': TokenType.INTO,\n 'IS': TokenType.IS,\n 'ISNULL': TokenType.ISNULL,\n 'JOIN': TokenType.JOIN,\n 'KEEP': TokenType.KEEP,\n 'KILL': TokenType.KILL,\n 'LATERAL': TokenType.LATERAL,\n 'LEFT': TokenType.LEFT,\n 'LIKE': TokenType.LIKE,\n 'LIMIT': TokenType.LIMIT,\n 'LOAD': TokenType.LOAD,\n 'LOCALTIME': TokenType.LOCALTIME,\n 'LOCALTIMESTAMP': TokenType.LOCALTIMESTAMP,\n 'LOCK': TokenType.LOCK,\n 'MERGE': TokenType.MERGE,\n 'NAMESPACE': TokenType.NAMESPACE,\n 'NATURAL': TokenType.NATURAL,\n 'NEXT': TokenType.NEXT,\n 'NOT': TokenType.NOT,\n 'NOTNULL': TokenType.NOTNULL,\n 'NULL': TokenType.NULL,\n 'OBJECT': TokenType.OBJECT,\n 'OFFSET': TokenType.OFFSET,\n 'ON': TokenType.ON,\n 'OR': TokenType.OR,\n 'XOR': TokenType.XOR,\n 'ORDER BY': TokenType.ORDER_BY,\n 'ORDINALITY': TokenType.ORDINALITY,\n 'OUT': TokenType.OUT,\n 'OUTER': TokenType.OUTER,\n 'OVER': TokenType.OVER,\n 'OVERLAPS': TokenType.OVERLAPS,\n 'OVERWRITE': TokenType.OVERWRITE,\n 'PARTITION': TokenType.PARTITION,\n 'PARTITION BY': TokenType.PARTITION_BY,\n 'PARTITIONED BY': TokenType.PARTITION_BY,\n 'PARTITIONED_BY': TokenType.PARTITION_BY,\n 'PERCENT': TokenType.PERCENT,\n 'PIVOT': TokenType.PIVOT,\n 'PRAGMA': TokenType.PRAGMA,\n 'PRIMARY KEY': TokenType.PRIMARY_KEY,\n 'PROCEDURE': TokenType.PROCEDURE,\n 'OPERATOR': TokenType.OPERATOR,\n 'QUALIFY': TokenType.QUALIFY,\n 'RANGE': TokenType.RANGE,\n 'RECURSIVE': TokenType.RECURSIVE,\n 'REGEXP': TokenType.RLIKE,\n 'RENAME': TokenType.RENAME,\n 'REPLACE': TokenType.REPLACE,\n 'RETURNING': TokenType.RETURNING,\n 'REFERENCES': TokenType.REFERENCES,\n 'RIGHT': TokenType.RIGHT,\n 'RLIKE': TokenType.RLIKE,\n 'ROLLBACK': TokenType.ROLLBACK,\n 'ROLLUP': TokenType.ROLLUP,\n 'ROW': TokenType.ROW,\n 'ROWS': TokenType.ROWS,\n 'SCHEMA': TokenType.SCHEMA,\n 'SELECT': TokenType.SELECT,\n 'SEMI': TokenType.SEMI,\n 'SESSION': TokenType.SESSION,\n 'SESSION_USER': TokenType.SESSION_USER,\n 'SET': TokenType.SET,\n 'SETTINGS': TokenType.SETTINGS,\n 'SHOW': TokenType.SHOW,\n 'SIMILAR TO': TokenType.SIMILAR_TO,\n 'SOME': TokenType.SOME,\n 'SORT BY': TokenType.SORT_BY,\n 'START WITH': TokenType.START_WITH,\n 'STRAIGHT_JOIN': TokenType.STRAIGHT_JOIN,\n 'TABLE': TokenType.TABLE,\n 'TABLESAMPLE': TokenType.TABLE_SAMPLE,\n 'TEMP': TokenType.TEMPORARY,\n 'TEMPORARY': TokenType.TEMPORARY,\n 'THEN': TokenType.THEN,\n 'TRUE': TokenType.TRUE,\n 'TRUNCATE': TokenType.TRUNCATE,\n 'UNION': TokenType.UNION,\n 'UNKNOWN': TokenType.UNKNOWN,\n 'UNNEST': TokenType.UNNEST,\n 'UNPIVOT': TokenType.UNPIVOT,\n 'UPDATE': TokenType.UPDATE,\n 'USE': TokenType.USE,\n 'USING': TokenType.USING,\n 'UUID': TokenType.UUID,\n 'VALUES': TokenType.VALUES,\n 'VIEW': TokenType.VIEW,\n 'VOLATILE': TokenType.VOLATILE,\n 'WHEN': TokenType.WHEN,\n 'WHERE': TokenType.WHERE,\n 'WINDOW': TokenType.WINDOW,\n 'WITH': TokenType.WITH,\n 'APPLY': TokenType.APPLY,\n 'ARRAY': TokenType.ARRAY,\n 'BIT': TokenType.BIT,\n 'BOOL': TokenType.BOOLEAN,\n 'BOOLEAN': TokenType.BOOLEAN,\n 'BYTE': TokenType.TINYINT,\n 'MEDIUMINT': TokenType.MEDIUMINT,\n 'INT1': TokenType.TINYINT,\n 'TINYINT': TokenType.TINYINT,\n 'INT16': TokenType.SMALLINT,\n 'SHORT': TokenType.SMALLINT,\n 'SMALLINT': TokenType.SMALLINT,\n 'HUGEINT': TokenType.INT128,\n 'UHUGEINT': TokenType.UINT128,\n 'INT2': TokenType.SMALLINT,\n 'INTEGER': TokenType.INT,\n 'INT': TokenType.INT,\n 'INT4': TokenType.INT,\n 'INT32': TokenType.INT,\n 'INT64': TokenType.BIGINT,\n 'INT128': TokenType.INT128,\n 'INT256': TokenType.INT256,\n 'LONG': TokenType.BIGINT,\n 'BIGINT': TokenType.BIGINT,\n 'INT8': TokenType.TINYINT,\n 'UINT': TokenType.UINT,\n 'UINT128': TokenType.UINT128,\n 'UINT256': TokenType.UINT256,\n 'DEC': TokenType.DECIMAL,\n 'DECIMAL': TokenType.DECIMAL,\n 'DECIMAL32': TokenType.DECIMAL32,\n 'DECIMAL64': TokenType.DECIMAL64,\n 'DECIMAL128': TokenType.DECIMAL128,\n 'DECIMAL256': TokenType.DECIMAL256,\n 'DECFLOAT': TokenType.DECFLOAT,\n 'BIGDECIMAL': TokenType.BIGDECIMAL,\n 'BIGNUMERIC': TokenType.BIGDECIMAL,\n 'BIGNUM': TokenType.BIGNUM,\n 'LIST': TokenType.LIST,\n 'MAP': TokenType.MAP,\n 'NULLABLE': TokenType.NULLABLE,\n 'NUMBER': TokenType.DECIMAL,\n 'NUMERIC': TokenType.DECIMAL,\n 'FIXED': TokenType.DECIMAL,\n 'REAL': TokenType.FLOAT,\n 'FLOAT': TokenType.FLOAT,\n 'FLOAT4': TokenType.FLOAT,\n 'FLOAT8': TokenType.DOUBLE,\n 'DOUBLE': TokenType.DOUBLE,\n 'DOUBLE PRECISION': TokenType.DOUBLE,\n 'JSON': TokenType.JSON,\n 'JSONB': TokenType.JSONB,\n 'CHAR': TokenType.CHAR,\n 'CHARACTER': TokenType.CHAR,\n 'CHAR VARYING': TokenType.VARCHAR,\n 'CHARACTER VARYING': TokenType.VARCHAR,\n 'NCHAR': TokenType.NCHAR,\n 'VARCHAR': TokenType.VARCHAR,\n 'VARCHAR2': TokenType.VARCHAR,\n 'NVARCHAR': TokenType.NVARCHAR,\n 'NVARCHAR2': TokenType.NVARCHAR,\n 'BPCHAR': TokenType.BPCHAR,\n 'STR': TokenType.TEXT,\n 'STRING': TokenType.TEXT,\n 'TEXT': TokenType.TEXT,\n 'LONGTEXT': TokenType.LONGTEXT,\n 'MEDIUMTEXT': TokenType.MEDIUMTEXT,\n 'TINYTEXT': TokenType.TINYTEXT,\n 'CLOB': TokenType.TEXT,\n 'LONGVARCHAR': TokenType.TEXT,\n 'BINARY': TokenType.BINARY,\n 'BLOB': TokenType.VARBINARY,\n 'LONGBLOB': TokenType.LONGBLOB,\n 'MEDIUMBLOB': TokenType.MEDIUMBLOB,\n 'TINYBLOB': TokenType.TINYBLOB,\n 'BYTEA': TokenType.VARBINARY,\n 'VARBINARY': TokenType.VARBINARY,\n 'TIME': TokenType.TIME,\n 'TIMETZ': TokenType.TIMETZ,\n 'TIME_NS': TokenType.TIME_NS,\n 'TIMESTAMP': TokenType.TIMESTAMP,\n 'TIMESTAMPTZ': TokenType.TIMESTAMPTZ,\n 'TIMESTAMPLTZ': TokenType.TIMESTAMPLTZ,\n 'TIMESTAMP_LTZ': TokenType.TIMESTAMPLTZ,\n 'TIMESTAMPNTZ': TokenType.TIMESTAMPNTZ,\n 'TIMESTAMP_NTZ': TokenType.TIMESTAMPNTZ,\n 'DATE': TokenType.DATE,\n 'DATETIME': TokenType.DATETIME,\n 'INT4RANGE': TokenType.INT4RANGE,\n 'INT4MULTIRANGE': TokenType.INT4MULTIRANGE,\n 'INT8RANGE': TokenType.INT8RANGE,\n 'INT8MULTIRANGE': TokenType.INT8MULTIRANGE,\n 'NUMRANGE': TokenType.NUMRANGE,\n 'NUMMULTIRANGE': TokenType.NUMMULTIRANGE,\n 'TSRANGE': TokenType.TSRANGE,\n 'TSMULTIRANGE': TokenType.TSMULTIRANGE,\n 'TSTZRANGE': TokenType.TSTZRANGE,\n 'TSTZMULTIRANGE': TokenType.TSTZMULTIRANGE,\n 'DATERANGE': TokenType.DATERANGE,\n 'DATEMULTIRANGE': TokenType.DATEMULTIRANGE,\n 'UNIQUE': TokenType.UNIQUE,\n 'VECTOR': TokenType.VECTOR,\n 'STRUCT': TokenType.STRUCT,\n 'SEQUENCE': TokenType.SEQUENCE,\n 'VARIANT': TokenType.VARIANT,\n 'ALTER': TokenType.ALTER,\n 'ANALYZE': TokenType.ANALYZE,\n 'CALL': TokenType.COMMAND,\n 'COMMENT': TokenType.COMMENT,\n 'EXPLAIN': TokenType.COMMAND,\n 'GRANT': TokenType.GRANT,\n 'REVOKE': TokenType.REVOKE,\n 'OPTIMIZE': TokenType.COMMAND,\n 'PREPARE': TokenType.COMMAND,\n 'VACUUM': TokenType.COMMAND,\n 'USER-DEFINED': TokenType.USERDEFINED,\n 'FOR VERSION': TokenType.VERSION_SNAPSHOT,\n 'FOR TIMESTAMP': TokenType.TIMESTAMP_SNAPSHOT,\n };\n\n /**\n * Map of SQL keywords and operators to their corresponding token types.\n *\n * Lazily computed and cached per class. Delegates to `ORIGINAL_KEYWORDS`.\n *\n * @final Do not override this getter in subclasses; override `ORIGINAL_KEYWORDS` instead.\n * @example { 'SELECT': TokenType.SELECT, 'FROM': TokenType.FROM }\n */\n @cache\n static get KEYWORDS (): Record<string, TokenType> {\n return this.ORIGINAL_KEYWORDS;\n }\n\n /**\n * Map of whitespace characters to their token types.\n *\n */\n static WHITE_SPACE: Record<string, TokenType> = {\n ' ': TokenType.SPACE,\n '\\t': TokenType.SPACE,\n '\\n': TokenType.BREAK,\n '\\r': TokenType.BREAK,\n };\n\n /**\n * Set of command token types.\n *\n */\n static COMMANDS = new Set([\n TokenType.COMMAND,\n TokenType.EXECUTE,\n TokenType.FETCH,\n TokenType.SHOW,\n TokenType.RENAME,\n ]);\n\n /**\n * Set of token types that can precede a command.\n *\n */\n static COMMAND_PREFIX_TOKENS = new Set([TokenType.SEMICOLON, TokenType.BEGIN]);\n\n /**\n * Handle numeric literals like in hive (3L = BIGINT).\n *\n */\n static NUMERIC_LITERALS: Record<string, string> = {};\n\n /**\n * Array of comment delimiters.\n */\n static COMMENTS: (string | [string, string])[] = ['--', ['/*', '*/']];\n\n /** The SQL string being tokenized. */\n sql = '';\n /** The length of the SQL string. */\n size = 0;\n /** Array of tokens produced by tokenization. */\n tokens: Token[] = [];\n dialect: Dialect;\n /** Starting position of the current token. */\n private _start = 0;\n /** Current position in the SQL string. */\n private _current = 0;\n /** Current line number (1-indexed). */\n private line = 1;\n /** Current column number. */\n private _col = 0;\n /** Accumulated comments for the next token. */\n private comments: string[] = [];\n /** Current character being processed. */\n private char = '';\n /** Whether we've reached the end of the SQL string. */\n private _end = false;\n /** The next character to be processed. */\n private peek = '';\n /** Line number of the previously added token. */\n private prevTokenLine = -1;\n\n constructor (options: TokenizerOptions = {}) {\n const { dialect } = options;\n this.dialect = Dialect.getOrRaise(dialect);\n this.reset();\n }\n\n /**\n * Reset the tokenizer state.\n */\n reset (): void {\n this.sql = '';\n this.size = 0;\n this.tokens = [];\n this._start = 0;\n this._current = 0;\n this.line = 1;\n this._col = 0;\n this.comments = [];\n this.char = '';\n this._end = false;\n this.peek = '';\n this.prevTokenLine = -1;\n }\n\n /**\n * Returns a list of tokens corresponding to the SQL string.\n *\n * @param sql - The SQL string to tokenize\n * @returns Array of tokens\n * @throws {Error} If tokenization fails, with context around the error position\n */\n tokenize (sql: string): Token[] {\n this.reset();\n this.sql = sql;\n this.size = sql.length;\n\n try {\n this.scan();\n } catch {\n const start = Math.max(this._current - 50, 0);\n const end = Math.min(this._current + 50, this.size - 1);\n const context = this.sql.slice(start, end);\n throw new TokenError(`Error tokenizing '${context}'`);\n }\n\n return this.tokens;\n }\n\n /**\n * Main scanning loop that processes the SQL string character by character.\n *\n * @param until - Optional callback to stop scanning early\n */\n private scan (until?: () => boolean): void {\n while (this.size && !this._end) {\n let current = this._current;\n\n // Skip spaces here rather than iteratively calling advance() for performance reasons\n while (current < this.size) {\n const char = this.sql[current];\n\n if (this.isWhitespace(char) && (char === ' ' || char === '\\t')) {\n current++;\n } else {\n break;\n }\n }\n\n const offset = this._current < current\n ? current - this._current\n : 1;\n\n this._start = current;\n this.advance({ i: offset });\n\n if (!this.isWhitespace(this.char)) {\n if (this.isDigit(this.char)) {\n this.scanNumber();\n } else if (this.char in (this._constructor)._IDENTIFIERS) {\n this.scanIdentifier((this._constructor)._IDENTIFIERS[this.char]);\n } else {\n this.scanKeywords();\n }\n }\n\n if (until?.()) {\n break;\n }\n }\n\n if (this.tokens.length && this.comments.length) {\n this.tokens[this.tokens.length - 1].comments.push(...this.comments);\n }\n }\n\n private chars (size: number): string {\n if (size === 1) {\n return this.char;\n }\n\n const start = this._current - 1;\n const end = start + size;\n\n return end <= this.size\n ? this.sql.slice(start, end)\n : '';\n }\n\n /**\n * Advances the current position in the SQL string.\n *\n * @param opts.i - Number of characters to advance\n * @param opts.alnum - If true, fast-forward through alphanumeric characters\n */\n private advance (opts: {\n i?: number;\n alnum?: boolean;\n } = {}): void {\n const {\n i = 1, alnum = false,\n } = opts;\n const constructor = this._constructor;\n if (constructor.WHITE_SPACE[this.char] === TokenType.BREAK) {\n // Ensures we don't count an extra line if we get a \\r\\n line break sequence\n if (!(this.char === '\\r' && this.peek === '\\n')) {\n this._col = i;\n this.line += 1;\n }\n } else {\n this._col += i;\n }\n\n this._current += i;\n this._end = this.size <= this._current;\n this.char = this.sql[this._current - 1];\n if (this.char === undefined) {\n throw new Error('Index out of bound');\n }\n this.peek = this._end\n ? ''\n : this.sql[this._current];\n\n if (alnum && this.isAlnum(this.char)) {\n // Here we use local variables instead of attributes for better performance\n let {\n _col, _current, _end, peek: _peek,\n } = this;\n\n while (this.isAlnum(_peek)) {\n _col += 1;\n _current += 1;\n _end = this.size <= _current;\n _peek = _end\n ? ''\n : this.sql[_current];\n }\n\n this._col = _col;\n this._current = _current;\n this._end = _end;\n this.peek = _peek;\n this.char = this.sql[_current - 1];\n }\n }\n\n private get text (): string {\n return this.sql.slice(this._start, this._current);\n }\n\n /**\n * Adds a token to the tokens array.\n *\n * Handles special cases like attaching comments and parsing command strings.\n *\n * @param tokenType - The type of token to add\n * @param text - Optional text override (defaults to current token text)\n */\n private add (tokenType: TokenType, text?: string): void {\n this.prevTokenLine = this.line;\n\n if (this.comments.length && tokenType === TokenType.SEMICOLON && this.tokens.length) {\n this.tokens[this.tokens.length - 1].comments.push(...this.comments);\n this.comments = [];\n }\n\n this.tokens.push(\n new Token(\n tokenType,\n text ?? this.text,\n this.line,\n this._col,\n this._start,\n this._current - 1,\n this.comments,\n ),\n );\n this.comments = [];\n\n const constructor = this._constructor;\n // If we have either a semicolon or a begin token before the command's token, we'll parse\n // whatever follows the command's token as a string\n if (\n constructor.COMMANDS.has(tokenType)\n && this.peek !== ';'\n && (this.tokens.length === 1 || constructor.COMMAND_PREFIX_TOKENS.has(this.tokens[this.tokens.length - 2].tokenType))\n ) {\n const start = this._current;\n const tokensLength = this.tokens.length;\n this.scan(() => this.peek === ';');\n this.tokens = this.tokens.slice(0, tokensLength);\n const commandText = this.sql.slice(start, this._current).trim();\n if (commandText) {\n this.add(TokenType.STRING, commandText);\n }\n }\n }\n\n private scanKeywords (): void {\n let size = 0;\n let word: string | undefined = undefined;\n let chars = this.text;\n let char = chars;\n let prevSpace = false;\n let skip = false;\n const constructor = this._constructor;\n let trie = constructor._KEYWORD_TRIE;\n let singleToken = char in this._constructor.SINGLE_TOKENS;\n\n while (chars) {\n let result: TrieResult;\n if (skip) {\n result = TrieResult.PREFIX;\n } else {\n [result, trie] = inTrie(trie, Array.from(char.toUpperCase()));\n }\n\n if (result === TrieResult.FAILED) {\n break;\n }\n if (result === TrieResult.EXISTS) {\n word = chars;\n }\n\n const end = this._current + size;\n size += 1;\n\n if (end < this.size) {\n char = this.sql[end];\n singleToken = singleToken || char in this._constructor.SINGLE_TOKENS;\n const isSpace = this.isWhitespace(char);\n\n if (!isSpace || !prevSpace) {\n if (isSpace) {\n char = ' ';\n }\n chars += char;\n prevSpace = isSpace;\n skip = false;\n } else {\n skip = true;\n }\n } else {\n char = '';\n break;\n }\n }\n\n if (word) {\n if (this.scanString(word)) {\n return;\n }\n if (this.scanComment(word)) {\n return;\n }\n if (prevSpace || singleToken || !char) {\n this.advance({ i: size - 1 });\n const upper = word.toUpperCase();\n this.add(constructor.KEYWORDS[upper], upper);\n return;\n }\n }\n\n const type = this._constructor.SINGLE_TOKENS[this.char];\n if (type !== undefined) {\n this.add(type, this.char);\n return;\n }\n\n this.scanVar();\n }\n\n /**\n * Scans a comment (single-line or multi-line with nesting support).\n *\n * @param commentStart - The comment start delimiter\n * @returns True if a comment was scanned\n */\n private scanComment (commentStart: string): boolean {\n const constructor = this._constructor;\n if (!(commentStart in constructor._COMMENTS)) {\n return false;\n }\n\n const commentStartLine = this.line;\n const commentStartSize = commentStart.length;\n const commentEnd = constructor._COMMENTS[commentStart];\n\n if (commentEnd) {\n // Skip the comment's start delimiter\n this.advance({ i: commentStartSize });\n\n let commentCount = 1;\n const commentEndSize = commentEnd.length;\n\n while (!this._end) {\n if (this.chars(commentEndSize) === commentEnd) {\n commentCount -= 1;\n if (!commentCount) {\n break;\n }\n }\n\n this.advance({ alnum: true });\n\n // Nested comments are allowed by some dialects\n if (\n constructor.NESTED_COMMENTS\n && !this._end\n && this.chars(commentStartSize) === commentStart\n ) {\n this.advance({ i: commentStartSize });\n commentCount += 1;\n }\n }\n\n this.comments.push(this.text.slice(commentStartSize, -commentEndSize + 1));\n this.advance({ i: commentEndSize - 1 });\n } else {\n while (!this._end && constructor.WHITE_SPACE[this.peek] !== TokenType.BREAK) {\n this.advance({\n i: 1,\n alnum: true,\n });\n }\n this.comments.push(this.text.slice(commentStartSize));\n }\n\n if (\n commentStart === constructor.HINT_START\n && this.tokens.length\n && constructor.TOKENS_PRECEDING_HINT.has(this.tokens[this.tokens.length - 1].tokenType)\n ) {\n this.add(TokenType.HINT);\n }\n\n // Leading comment is attached to the succeeding token, whilst trailing comment to the preceding.\n if (commentStartLine === this.prevTokenLine) {\n this.tokens[this.tokens.length - 1].comments.push(...this.comments);\n this.comments = [];\n this.prevTokenLine = this.line;\n }\n\n return true;\n }\n\n /**\n * Scans a numeric literal, including decimals, scientific notation, and type suffixes.\n */\n private scanNumber (): void {\n const constructor = this._constructor;\n if (this.char === '0') {\n const peek = this.peek.toUpperCase();\n if (peek === 'B') {\n return constructor.BIT_STRINGS.length\n ? this.scanBits()\n : this.add(TokenType.NUMBER);\n } else if (peek === 'X') {\n return constructor.HEX_STRINGS.length\n ? this.scanHex()\n : this.add(TokenType.NUMBER);\n }\n }\n\n let decimal = false;\n let scientific = 0;\n\n while (true) {\n if (this.isDigit(this.peek)) {\n this.advance();\n } else if (this.peek === '.' && !decimal) {\n if (this.tokens.length && this.tokens[this.tokens.length - 1].tokenType === TokenType.PARAMETER) {\n return this.add(TokenType.NUMBER);\n }\n decimal = true;\n this.advance();\n } else if (['-', '+'].includes(this.peek) && scientific === 1) {\n // Only consume +/- if followed by a digit\n if (this._current + 1 < this.size && this.isDigit(this.sql[this._current + 1])) {\n scientific += 1;\n this.advance();\n } else {\n return this.add(TokenType.NUMBER);\n }\n } else if (this.peek.toUpperCase() === 'E' && !scientific) {\n scientific += 1;\n this.advance();\n } else if (this.peek === '_' && this.dialect._constructor.NUMBERS_CAN_BE_UNDERSCORE_SEPARATED) {\n this.advance();\n } else if (this.isIdentifierChar(this.peek)) {\n const numberText = this.text;\n let literal = '';\n\n while (this.peek.trim() && !this._constructor.SINGLE_TOKENS[this.peek]) {\n literal += this.peek;\n this.advance();\n }\n\n const tokenType = constructor.KEYWORDS[constructor.NUMERIC_LITERALS[literal.toUpperCase()] || ''];\n\n if (tokenType) {\n this.add(TokenType.NUMBER, numberText);\n this.add(TokenType.DCOLON, '::');\n return this.add(tokenType, literal);\n }\n\n if (this.dialect._constructor.IDENTIFIERS_CAN_START_WITH_DIGIT) {\n return this.add(TokenType.VAR);\n }\n\n this.advance({ i: -literal.length });\n return this.add(TokenType.NUMBER, numberText);\n } else {\n return this.add(TokenType.NUMBER);\n }\n }\n }\n\n private scanBits (): void {\n this.advance();\n const value = this.extractValue();\n // If `value` can't be converted to a binary, fallback to tokenizing it as an identifier\n if (!Number.isNaN(parseInt(value, 2))) {\n this.add(TokenType.BIT_STRING, value.slice(2)); // Drop the 0b\n } else {\n this.add(TokenType.IDENTIFIER);\n }\n }\n\n private scanHex (): void {\n this.advance();\n const value = this.extractValue();\n // If `value` can't be converted to a hex, fallback to tokenizing it as an identifier\n if (!Number.isNaN(parseInt(value, 16))) {\n this.add(TokenType.HEX_STRING, value.slice(2)); // Drop the 0x\n } else {\n this.add(TokenType.IDENTIFIER);\n }\n }\n\n private extractValue (): string {\n while (true) {\n const char = this.peek.trim();\n if (char && !this._constructor.SINGLE_TOKENS[char]) {\n this.advance({\n i: 1,\n alnum: true,\n });\n } else {\n break;\n }\n }\n\n return this.text;\n }\n\n /**\n * Scans various string types including quoted strings, format strings, and heredocs.\n *\n * @param start - The string start delimiter\n * @returns True if a string was scanned\n */\n private scanString (start: string): boolean {\n const constructor = this._constructor;\n let base: number | undefined = undefined;\n let tokenType = TokenType.STRING;\n\n let end: string;\n if (start in constructor._QUOTES) {\n end = constructor._QUOTES[start];\n } else if (start in constructor._FORMAT_STRINGS) {\n [end, tokenType] = constructor._FORMAT_STRINGS[start];\n\n if (tokenType === TokenType.HEX_STRING) {\n base = 16;\n } else if (tokenType === TokenType.BIT_STRING) {\n base = 2;\n } else if (tokenType === TokenType.HEREDOC_STRING) {\n this.advance();\n\n let tag: string;\n if (this.char === end) {\n tag = '';\n } else {\n tag = this.extractString(end, undefined, {\n rawString: true,\n raiseUnmatched: !constructor.HEREDOC_TAG_IS_IDENTIFIER,\n });\n }\n\n if (\n tag\n && constructor.HEREDOC_TAG_IS_IDENTIFIER\n && (this._end || this.isDigit(tag) || this.isWhitespace(tag))\n ) {\n if (!this._end) {\n this.advance({ i: -1 });\n }\n\n this.advance({ i: -tag.length });\n this.add(constructor.HEREDOC_STRING_ALTERNATIVE);\n return true;\n }\n\n end = `${start}${tag}${end}`;\n }\n } else {\n return false;\n }\n\n this.advance({ i: start.length });\n const text = this.extractString(\n end,\n tokenType === TokenType.BYTE_STRING\n ? constructor._BYTE_STRING_ESCAPES\n : constructor._STRING_ESCAPES,\n { rawString: tokenType === TokenType.RAW_STRING },\n );\n\n if (base && text && Number.isNaN(parseInt(text, base))) {\n throw new Error(`Numeric string contains invalid characters from ${this.line}:${this._start}`);\n }\n\n this.add(tokenType, text);\n return true;\n }\n\n private scanIdentifier (identifierEnd: string): void {\n this.advance();\n const constructor = this._constructor;\n const escapes = new Set([...Array.from(constructor._IDENTIFIER_ESCAPES), identifierEnd]);\n const text = this.extractString(identifierEnd, escapes);\n this.add(TokenType.IDENTIFIER, text);\n }\n\n private scanVar (): void {\n const constructor = this._constructor;\n while (true) {\n const char = this.peek.trim();\n if (char && (constructor.VAR_SINGLE_TOKENS.has(char) || !this._constructor.SINGLE_TOKENS[char])) {\n this.advance({\n i: 1,\n alnum: true,\n });\n } else {\n break;\n }\n }\n\n this.add(\n this.tokens.length && this.tokens[this.tokens.length - 1].tokenType === TokenType.PARAMETER\n ? TokenType.VAR\n : constructor.KEYWORDS[this.text.toUpperCase()] || TokenType.VAR,\n );\n }\n\n /**\n * Extracts string content between delimiters, handling escape sequences.\n *\n * @param delimiter - The closing delimiter to look for\n * @param escapes - Set of escapable characters (null uses default string escapes)\n * @param rawString - If true, treat as raw string with minimal escape processing\n * @param raiseUnmatched - If true, throw error on unmatched delimiter\n * @returns The extracted string content\n * @throws {Error} If delimiter is unmatched and raiseUnmatched is true\n */\n private extractString (\n delimiter: string,\n escapes: Set<string> | undefined = undefined,\n options: {\n rawString?: boolean;\n raiseUnmatched?: boolean;\n } = {},\n ): string {\n const {\n rawString = false, raiseUnmatched = true,\n } = options;\n const constructor = this._constructor;\n let text = '';\n const delimSize = delimiter.length;\n escapes = escapes === undefined\n ? constructor._STRING_ESCAPES\n : escapes;\n\n while (true) {\n if (\n !rawString\n && this.dialect._constructor.UNESCAPED_SEQUENCES\n && this.peek\n && escapes.has(this.char)\n ) {\n const unescapedSequence = this.dialect._constructor.UNESCAPED_SEQUENCES[this.char + this.peek];\n if (unescapedSequence) {\n this.advance({ i: 2 });\n text += unescapedSequence;\n continue;\n }\n }\n\n const isValidCustomEscape =\n constructor.ESCAPE_FOLLOW_CHARS.length\n && this.char === '\\\\'\n && !constructor.ESCAPE_FOLLOW_CHARS.includes(this.peek);\n\n if (\n (constructor.STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS || !rawString)\n && escapes.has(this.char)\n && (this.peek === delimiter || escapes.has(this.peek) || isValidCustomEscape)\n && (!(this.char in constructor._QUOTES) || this.char === this.peek)\n ) {\n if (this.peek === delimiter) {\n text += this.peek;\n } else if (isValidCustomEscape && this.char !== this.peek) {\n text += this.peek;\n } else {\n text += this.char + this.peek;\n }\n\n if (this._current + 1 < this.size) {\n this.advance({ i: 2 });\n } else {\n throw new TokenError(`Missing ${delimiter} from ${this.line}:${this._current}`);\n }\n } else {\n if (this.chars(delimSize) === delimiter) {\n if (1 < delimSize) {\n this.advance({ i: delimSize - 1 });\n }\n break;\n }\n\n if (this._end) {\n if (!raiseUnmatched) {\n return text + this.char;\n }\n\n throw new TokenError(`Missing ${delimiter} from ${this.line}:${this._start}`);\n }\n\n const current = this._current - 1;\n this.advance({\n i: 1,\n alnum: true,\n });\n text += this.sql.slice(current, this._current - 1);\n }\n }\n\n return text;\n }\n\n private static convertQuotes (arr: Iterable<TokenPair>): Record<string, string> {\n const res: Record<string, string> = {};\n for (const item of arr) {\n const key = typeof item === 'string'\n ? item\n : item[0];\n const value = typeof item === 'string'\n ? item\n : item[1];\n res[key] = value;\n }\n return res;\n }\n\n private static quotesToFormat (\n tokenType: TokenType,\n arr: TokenPair[],\n ): Record<string, [string, TokenType]> {\n const quotes = this.convertQuotes(arr);\n const result: Record<string, [string, TokenType]> = {};\n for (const [k, v] of Object.entries(quotes)) {\n result[k] = [v, tokenType];\n }\n return result;\n }\n\n // Helper methods\n\n private isWhitespace (char: string): boolean {\n return /\\s/.test(char);\n }\n\n private isDigit (char: string): boolean {\n return /\\d/.test(char);\n }\n\n private isAlnum (char: string): boolean {\n return /[a-zA-Z0-9]/.test(char);\n }\n\n private isIdentifierChar (char: string): boolean {\n return /[a-zA-Z_]/.test(char);\n }\n\n private get _constructor (): typeof Tokenizer {\n return this.constructor as typeof Tokenizer;\n }\n}\n","// https://github.com/tobymao/sqlglot/blob/main/sqlglot/optimizer/qualify_columns.py\n\nimport type {\n CteExpr,\n SetOperationExpr,\n} from '../expressions';\nimport {\n IdentifierExpr,\n AggFuncExpr,\n ColumnDefExpr,\n DataTypeExpr,\n Expression,\n AliasesExpr,\n AliasExpr,\n alias as aliasExpr,\n and as andExpr,\n column as columnExpr,\n ColumnExpr,\n CONSTANTS,\n CoalesceExpr,\n DistinctExpr,\n DotExpr,\n ExplodeExpr,\n GroupExpr,\n HavingExpr,\n InExpr,\n JoinExpr,\n LiteralExpr,\n paren as parenExpr,\n PivotExpr,\n PropertyEqExpr,\n PseudocolumnExpr,\n QueryTransformExpr,\n SelectExpr,\n StarExpr,\n StructExpr,\n SubqueryExpr,\n TableAliasExpr,\n TableColumnExpr,\n toIdentifier,\n UnnestExpr,\n WindowExpr,\n WithExpr,\n ParenExpr,\n DataTypeExprKind,\n alias,\n} from '../expressions';\nimport {\n assertIsInstanceOf, filterInstanceOf, isInstanceOf,\n} from '../port_internals';\nimport {\n Dialect, type DialectType,\n} from '../dialects/dialect';\nimport {\n ensureSchema, type Schema,\n} from '../schema';\nimport {\n highlightSql, OptimizeError,\n} from '../errors';\nimport { seqGet } from '../helper';\nimport { TypeAnnotator } from './annotate_types';\nimport { Resolver } from './resolver';\nimport {\n buildScope, Scope, traverseScope, walkInScope,\n} from './scope';\nimport { simplifyParens } from './simplify';\n\n/**\n * Rewrite sqlglot AST to have fully qualified columns.\n *\n * Example:\n * ```ts\n * import { parseOne } from 'sqlglot';\n * import { qualifyColumns } from 'sqlglot/optimizer';\n *\n * const schema = { tbl: { col: \"INT\" } };\n * const expression = parseOne(\"SELECT col FROM tbl\");\n * qualifyColumns(expression, schema).sql();\n * // 'SELECT tbl.col AS col FROM tbl'\n * ```\n *\n * @param expression - Expression to qualify\n * @param options - Qualification options\n * @param options.schema - Database schema\n * @param options.expandAliasRefs - Whether to expand references to aliases (default: true)\n * @param options.expandStars - Whether to expand star queries (default: true)\n * @param options.inferSchema - Whether to infer the schema if missing\n * @param options.allowPartialQualification - Whether to allow partial qualification (default: false)\n * @param options.dialect - SQL dialect\n * @returns The qualified expression\n *\n * Notes:\n * - Currently only handles a single PIVOT or UNPIVOT operator\n */\nexport function qualifyColumns<E extends Expression> (\n expression: E,\n options: {\n schema?: Record<string, unknown> | Schema;\n expandAliasRefs?: boolean;\n expandStars?: boolean;\n inferSchema?: boolean;\n allowPartialQualification?: boolean;\n dialect?: DialectType;\n } = {},\n): E {\n const {\n schema: schemaArg,\n expandAliasRefs = true,\n expandStars = true,\n inferSchema: inferSchemaArg,\n allowPartialQualification = false,\n dialect: dialectArg,\n } = options;\n\n const schema = ensureSchema(schemaArg, { dialect: dialectArg });\n const annotator = new TypeAnnotator({ schema });\n const inferSchema = inferSchemaArg ?? Boolean(schema.empty);\n const dialect = schema.dialect || new Dialect();\n const dialectClass = dialect._constructor;\n const pseudocolumns = dialectClass.PSEUDOCOLUMNS;\n\n for (const scope of traverseScope(expression)) {\n if (dialectClass.PREFER_CTE_ALIAS_COLUMN) {\n pushdownCteAliasColumns(scope);\n }\n\n const scopeExpression = scope.expression;\n const isSelect = scopeExpression instanceof SelectExpr;\n\n separatePseudocolumns(scope, pseudocolumns);\n\n const resolver = new Resolver(scope, schema, { inferSchema });\n popTableColumnAliases(scope.ctes);\n popTableColumnAliases(scope.derivedTables);\n const usingColumnTables = expandUsing(scope, resolver);\n\n if ((schema.empty || dialectClass.FORCE_EARLY_ALIAS_REF_EXPANSION) && expandAliasRefs) {\n expandAliasRefs_(\n scope,\n resolver,\n dialect,\n { expandOnlyGroupby: dialectClass.EXPAND_ONLY_GROUP_ALIAS_REF },\n );\n }\n\n convertColumnsToDots(scope, resolver);\n qualifyColumnsInScope(\n scope,\n resolver,\n { allowPartialQualification },\n );\n\n if (!schema.empty && expandAliasRefs) {\n expandAliasRefs_(scope, resolver, dialect, { expandOnlyGroupby: false });\n }\n\n if (isSelect) {\n if (expandStars) {\n expandStars_(\n scope,\n resolver,\n usingColumnTables,\n pseudocolumns,\n annotator,\n );\n }\n qualifyOutputs(scope);\n }\n\n expandGroupBy(scope, dialect);\n expandOrderByAndDistinctOn(scope, resolver);\n\n if (dialectClass.ANNOTATE_ALL_SCOPES) {\n annotator.annotateScope(scope);\n }\n }\n\n return expression;\n}\n\n/**\n * Raise an error if any columns aren't qualified\n *\n * @param expression - Expression to validate\n * @param sql - Optional SQL string for error highlighting\n * @returns The validated expression\n * @throws OptimizeError if unqualified columns are found\n */\nexport function validateQualifyColumns<E extends Expression> (\n expression: E,\n sql?: string,\n): E {\n const allUnqualifiedColumns: ColumnExpr[] = [];\n\n for (const scope of traverseScope(expression)) {\n if (!(scope.expression instanceof SelectExpr)) {\n continue;\n }\n\n let unqualifiedColumns = scope.unqualifiedColumns;\n\n if (0 < scope.externalColumns.length && !scope.isCorrelatedSubquery && scope.pivots.length === 0) {\n const column = scope.externalColumns[0];\n const forTable = column.table ? ` for table: '${column.table}'` : '';\n const line = (column.args.this as Expression)?.meta?.['line'] as number | undefined;\n const col = (column.args.this as Expression)?.meta?.['col'] as number | undefined;\n\n const start = (column.args.this as Expression)?.meta?.['start'] as number | undefined;\n const end = (column.args.this as Expression)?.meta?.['end'] as number | undefined;\n\n let errorMsg = `Column '${column.name}' could not be resolved${forTable}.`;\n if (line && col) {\n errorMsg += ` Line: ${line}, Col: ${col}`;\n }\n if (sql && start !== undefined && end !== undefined) {\n const { formattedSql } = highlightSql({\n sql,\n positions: [[start, end]],\n });\n errorMsg += `\\n ${formattedSql}`;\n }\n\n throw new OptimizeError(errorMsg);\n }\n\n if (0 < unqualifiedColumns.length && 0 < scope.pivots.length && scope.pivots[0].unpivot) {\n const unpivotColumnSet = new Set(unpivotColumns(scope.pivots[0]));\n unqualifiedColumns = unqualifiedColumns.filter((c) => !unpivotColumnSet.has(c));\n }\n\n allUnqualifiedColumns.push(...unqualifiedColumns);\n }\n\n if (0 < allUnqualifiedColumns.length) {\n const firstColumn = allUnqualifiedColumns[0];\n const firstColumnThis = firstColumn.args.this;\n const line = isInstanceOf(firstColumnThis, Expression) ? firstColumnThis.meta['line'] : undefined;\n const col = isInstanceOf(firstColumnThis, Expression) ? firstColumnThis.meta['col'] : undefined;\n\n const start = isInstanceOf(firstColumnThis, Expression) ? firstColumnThis.meta['start'] as number | undefined : undefined;\n const end = isInstanceOf(firstColumnThis, Expression) ? firstColumnThis.meta['end'] as number | undefined : undefined;\n\n let errorMsg = `Ambiguous column '${firstColumn.name}'`;\n if (line && col) {\n errorMsg += ` (Line: ${line}, Col: ${col})`;\n }\n if (sql && start !== undefined && end !== undefined) {\n const { formattedSql } = highlightSql({\n sql,\n positions: [[start, end]],\n });\n errorMsg += `\\n ${formattedSql}`;\n }\n\n throw new OptimizeError(errorMsg);\n }\n\n return expression;\n}\n\nfunction separatePseudocolumns (scope: Scope, pseudocolumns: Set<string>): void {\n if (pseudocolumns.size === 0) {\n return;\n }\n\n let hasPseudocolumns = false;\n const scopeExpression = scope.expression;\n\n for (const column of scope.columns) {\n const name = column.name.toUpperCase();\n if (!pseudocolumns.has(name)) {\n continue;\n }\n\n if (name !== 'LEVEL' || (\n scopeExpression instanceof SelectExpr\n && (scopeExpression.args as Record<string, unknown>).connect\n )) {\n column.replace(new PseudocolumnExpr({ ...column.args }));\n hasPseudocolumns = true;\n }\n }\n\n if (hasPseudocolumns) {\n scope.clearCache();\n }\n}\n\nfunction unpivotColumns (unpivot: PivotExpr): ColumnExpr[] {\n const fields = unpivot.args.fields || [];\n const nameColumns = fields\n .filter((field): field is InExpr => field instanceof InExpr && field.args.this instanceof ColumnExpr)\n .map((field) => field.args.this as ColumnExpr);\n\n const valueColumns: ColumnExpr[] = [];\n for (const e of unpivot.args.expressions as Expression[]) {\n for (const col of e.findAll(ColumnExpr)) {\n valueColumns.push(col);\n }\n }\n\n return [...nameColumns, ...valueColumns];\n}\n\nfunction popTableColumnAliases (derivedTables: Iterable<Expression>): void {\n for (const table of derivedTables) {\n if (table.parent instanceof WithExpr && table.parent.args.recursive) {\n continue;\n }\n const tableAlias = table.getArgKey('alias');\n if (tableAlias instanceof TableAliasExpr) {\n tableAlias.setArgKey('columns', undefined);\n }\n }\n}\n\nfunction expandUsing (scope: Scope, resolver: Resolver): Map<string, string[]> {\n const joins = (scope.expression as SelectExpr).args.joins || [];\n if (joins.length === 0) {\n return new Map();\n }\n\n const names = new Set(joins.map((j) => j.aliasOrName));\n const ordered: string[] = Object.keys(scope.selectedSources).filter((k) => !names.has(k));\n\n if (0 < names.size && ordered.length === 0) {\n throw new OptimizeError(`Joins ${[...names].join(',')} missing source table ${scope.expression}`);\n }\n\n // column name -> first source name that has it\n const columns: Record<string, string> = {};\n\n const updateSourceColumns = (sourceName: string): void => {\n for (const colName of resolver.getSourceColumns(sourceName)) {\n if (!(colName in columns)) {\n columns[colName] = sourceName;\n }\n }\n };\n\n for (const sourceName of ordered) {\n updateSourceColumns(sourceName);\n }\n\n // column name -> ordered map of table names\n const columnTables = new Map<string, string[]>();\n\n for (let i = 0; i < joins.length; i++) {\n const join = joins[i];\n assertIsInstanceOf(join, JoinExpr);\n const sourceTable = ordered[ordered.length - 1];\n if (sourceTable) {\n updateSourceColumns(sourceTable);\n }\n\n const joinTable = join.aliasOrName;\n ordered.push(joinTable);\n\n const using = join.args.using;\n if (!using) continue;\n\n const joinColumns = resolver.getSourceColumns(joinTable);\n\n const conditions: Expression[] = [];\n const usingIdentifierCount = using.length;\n const isSemiOrAntiJoin = join.isSemiOrAntiJoin;\n\n for (const identifierNode of using) {\n const identifier = (identifierNode as Expression).name;\n let table = columns[identifier];\n\n if (!table || !joinColumns.includes(identifier)) {\n if (0 < Object.keys(columns).length && !('*' in columns) && 0 < joinColumns.length) {\n throw new OptimizeError(`Cannot automatically join: ${identifier}`);\n }\n }\n\n table = table || sourceTable;\n\n let lhs: Expression;\n if (i === 0 || usingIdentifierCount === 1) {\n lhs = columnExpr({\n col: identifier,\n table,\n });\n } else {\n const coalesceColumns = ordered.slice(0, -1)\n .filter((t) => resolver.getSourceColumns(t).includes(identifier))\n .map((t) => columnExpr({\n col: identifier,\n table: t,\n }));\n if (1 < coalesceColumns.length) {\n lhs = new CoalesceExpr({\n this: coalesceColumns[0],\n expressions: coalesceColumns.slice(1),\n });\n } else {\n lhs = columnExpr({\n col: identifier,\n table,\n });\n }\n }\n\n conditions.push(lhs.eq(columnExpr({\n col: identifier,\n table: joinTable,\n })));\n\n if (!isSemiOrAntiJoin) {\n let tables_ = columnTables.get(identifier);\n if (!tables_) {\n tables_ = [];\n columnTables.set(identifier, tables_);\n }\n if (!tables_.includes(table)) tables_.push(table);\n if (!tables_.includes(joinTable)) tables_.push(joinTable);\n }\n }\n\n join.setArgKey('using', undefined);\n join.setArgKey('on', andExpr(conditions, { copy: false }));\n }\n\n if (0 < columnTables.size) {\n for (const column of scope.columns) {\n const tables_ = !column.table ? columnTables.get(column.name) : undefined;\n if (tables_ !== undefined) {\n const coalesceArgs = tables_.map((t) => columnExpr({\n col: column.name,\n table: t,\n }));\n let replacement: Expression = new CoalesceExpr({\n this: coalesceArgs[0],\n expressions: coalesceArgs.slice(1),\n });\n\n if (column.parent instanceof SelectExpr) {\n replacement = aliasExpr(replacement, column.name, { copy: false }) as Expression;\n } else if (column.parent instanceof StructExpr) {\n replacement = new PropertyEqExpr({\n this: toIdentifier(column.name),\n expression: replacement,\n });\n }\n\n scope.replace(column, replacement);\n }\n }\n }\n\n return columnTables;\n}\n\nfunction expandAliasRefs_ (\n scope: Scope,\n resolver: Resolver,\n dialect: Dialect,\n options: { expandOnlyGroupby: boolean },\n): void {\n const { expandOnlyGroupby } = options;\n const expression = scope.expression;\n const dialectClass = dialect._constructor;\n\n if (!(expression instanceof SelectExpr) || dialectClass.DISABLES_ALIAS_REF_EXPANSION) {\n return;\n }\n\n const aliasToExpression = new Map<string, [Expression, number]>();\n const projections = new Set(expression.selects.map((s) => (s as Expression).aliasOrName));\n let replaced = false;\n\n const replaceColumns = (\n node: Expression | undefined,\n options: { resolveTable?: boolean;\n literalIndex?: boolean; } = {},\n ): void => {\n const {\n resolveTable = false, literalIndex = false,\n } = options;\n const isGroupBy = node instanceof GroupExpr;\n const isHaving = node instanceof HavingExpr;\n if (!node || (expandOnlyGroupby && !isGroupBy)) {\n return;\n }\n\n for (const column of walkInScope(node, { prune: (n) => n.isStar })) {\n if (!(column instanceof ColumnExpr)) continue;\n\n if (expandOnlyGroupby && isGroupBy && column.parent !== node) {\n continue;\n }\n\n let skipReplace = false;\n const table = (resolveTable && !column.table) ? resolver.getTable(column.name) : undefined;\n const aliasEntry = aliasToExpression.get(column.name);\n const aliasExpr_ = aliasEntry ? aliasEntry[0] : undefined;\n const aliasIdx = aliasEntry ? aliasEntry[1] : 1;\n\n if (aliasExpr_) {\n skipReplace = Boolean(\n aliasExpr_.find(AggFuncExpr)\n && column.findAncestor(AggFuncExpr)\n && !(column.findAncestor<WindowExpr | SelectExpr>(WindowExpr, SelectExpr) instanceof WindowExpr),\n );\n\n if (isHaving && dialectClass.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES) {\n skipReplace = skipReplace || Array.from(aliasExpr_.findAll(ColumnExpr)).some(\n (n) => projections.has(n.parts[0]?.name || ''),\n );\n }\n } else if (dialectClass.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES && (isGroupBy || isHaving)) {\n const columnTable = table ? table.name : column.table;\n if (columnTable && projections.has(columnTable)) {\n column.replace(toIdentifier(column.name));\n replaced = true;\n continue;\n }\n }\n\n if (table && (!aliasExpr_ || skipReplace)) {\n column.setArgKey('table', table);\n } else if (!column.table && aliasExpr_ && !skipReplace) {\n if ((aliasExpr_ instanceof LiteralExpr || aliasExpr_.isNumber) && (literalIndex || resolveTable)) {\n if (literalIndex) {\n column.replace(LiteralExpr.number(aliasIdx));\n replaced = true;\n }\n } else {\n replaced = true;\n const parenNode = column.replace(parenExpr(aliasExpr_)) as Expression;\n const simplified = simplifyParens(parenNode, dialect);\n if (simplified !== parenNode) {\n parenNode.replace(simplified);\n }\n }\n }\n }\n };\n\n for (let i = 0; i < expression.selects.length; i++) {\n const projection = expression.selects[i] as Expression;\n replaceColumns(projection);\n if (projection instanceof AliasExpr) {\n aliasToExpression.set(projection.alias, [projection.args.this as Expression, i + 1]);\n }\n }\n\n // Handle recursive CTE alias columns\n let parentScope: Scope | undefined = scope;\n let onRightSubTree = false;\n while (parentScope && !parentScope.isCte) {\n if (parentScope.isUnion && parentScope.parent) {\n onRightSubTree = (parentScope.parent.expression as SetOperationExpr).args.expression === parentScope.expression;\n }\n parentScope = parentScope.parent;\n }\n\n if (parentScope && onRightSubTree) {\n const cteExpr = parentScope.expression.parent;\n if (cteExpr) {\n const withNode = cteExpr.findAncestor(WithExpr);\n if (withNode?.args.recursive) {\n const aliasArg = (cteExpr as CteExpr).args.alias;\n const aliasColumns = aliasArg instanceof TableAliasExpr ? aliasArg.columns : [];\n const columnsSource: Expression[] = 0 < aliasColumns.length\n ? aliasColumns as Expression[]\n : ((cteExpr as CteExpr).args.this as SelectExpr)?.selects || [];\n for (const col of columnsSource) {\n if (col instanceof Expression) {\n aliasToExpression.delete(col.outputName);\n }\n }\n }\n }\n }\n\n replaceColumns(expression.args.where as Expression | undefined);\n replaceColumns(expression.args.group as Expression | undefined, { literalIndex: true });\n replaceColumns(expression.args.having as Expression | undefined, { resolveTable: true });\n replaceColumns(expression.args.qualify as Expression | undefined, { resolveTable: true });\n\n if (dialectClass.SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS) {\n for (const join of expression.args.joins || []) {\n replaceColumns(join);\n }\n }\n\n if (replaced) {\n scope.clearCache();\n }\n}\n\nfunction convertColumnsToDots (scope: Scope, resolver: Resolver): void {\n let converted = false;\n const allCols: (ColumnExpr | DotExpr)[] = [...scope.columns, ...scope.stars];\n\n for (const column of allCols) {\n if (column instanceof DotExpr) continue;\n\n const columnTable = column.table;\n const dotParts = (column.meta['dotParts'] as unknown[] | undefined) || [];\n delete column.meta['dotParts'];\n\n if (\n columnTable\n && !scope.sources.has(columnTable)\n && (\n !scope.parent\n || !scope.parent.sources.has(columnTable)\n || !scope.isCorrelatedSubquery\n )\n ) {\n const parts = column.parts;\n if (parts.length < 2) continue;\n\n const firstPart = parts[0];\n const remainingParts = parts.slice(1);\n if (!firstPart) continue;\n\n let newRoot: IdentifierExpr;\n let fieldParts: Expression[];\n let newTable: Expression | undefined;\n let wasQualified: boolean;\n\n if (scope.sources.has(firstPart.name)) {\n // Column is already table-qualified: firstPart is the table, remainingParts[0] is column\n if (remainingParts.length === 0) continue;\n newTable = firstPart;\n newRoot = remainingParts[0] as IdentifierExpr;\n fieldParts = remainingParts.slice(1);\n wasQualified = true;\n } else {\n // firstPart is the column name, resolver finds the table\n newTable = resolver.getTable(firstPart.name);\n newRoot = firstPart as IdentifierExpr;\n fieldParts = remainingParts;\n wasQualified = false;\n }\n\n if (newTable) {\n converted = true;\n const newColumn = columnExpr({\n col: newRoot,\n table: newTable as IdentifierExpr,\n });\n if (0 < dotParts.length) {\n newColumn.meta['dotParts'] = dotParts.slice(wasQualified ? 2 : 1);\n }\n if (0 < fieldParts.length) {\n column.replace(DotExpr.build([newColumn, ...fieldParts]));\n } else {\n column.replace(newColumn);\n }\n }\n }\n }\n\n if (converted) {\n scope.clearCache();\n }\n}\n\nfunction qualifyColumnsInScope (\n scope: Scope,\n resolver: Resolver,\n options: { allowPartialQualification: boolean },\n): void {\n const { allowPartialQualification } = options;\n const dialectClass = resolver.dialect._constructor;\n\n for (const column of scope.columns) {\n const columnTable = column.table;\n const columnName = column.name;\n\n if (columnTable && scope.sources.has(columnTable)) {\n const sourceColumns = resolver.getSourceColumns(columnTable);\n if (\n !allowPartialQualification\n && 0 < sourceColumns.length\n && !sourceColumns.includes(columnName)\n && !sourceColumns.includes('*')\n ) {\n throw new OptimizeError(`Unknown column: ${columnName}`);\n }\n }\n\n if (!columnTable) {\n if (0 < scope.pivots.length && !column.findAncestor(PivotExpr)) {\n column.setArgKey('table', toIdentifier((scope.pivots[0] as PivotExpr).alias));\n continue;\n }\n\n const table = resolver.getTable(column);\n\n if (table) {\n const source = scope.sources.get(table.name);\n if (source instanceof Scope\n && source.columnIndex.has(column)\n ) {\n continue;\n }\n }\n\n if (table) {\n column.setArgKey('table', table);\n } else if (\n dialectClass.TABLES_REFERENCEABLE_AS_COLUMNS\n && column.parts.length === 1\n && columnName in scope.selectedSources\n ) {\n const colThis = column.args.this;\n scope.replace(column, new TableColumnExpr({ this: isInstanceOf(colThis, Expression) ? colThis : undefined }));\n }\n }\n }\n\n for (const pivot of scope.pivots) {\n if (pivot instanceof PivotExpr) {\n for (const column of pivot.findAll(ColumnExpr)) {\n if (!column.table && resolver.allColumns.has(column.name)) {\n const table = resolver.getTable(column.name);\n if (table) {\n column.setArgKey('table', table);\n }\n }\n }\n }\n }\n}\n\nfunction addExceptColumns (\n expression: Expression,\n tables: Iterable<string>,\n exceptColumns: Map<string, Set<string>>,\n): void {\n const except_ = expression.getArgKey('except') ?? expression.getArgKey('except');\n if (!except_) return;\n const exceptList = Array.isArray(except_) ? except_ : [except_];\n const columns = new Set(\n (exceptList as Expression[])\n .filter((e): e is Expression => e instanceof Expression)\n .map((e) => e.name),\n );\n for (const table of tables) {\n exceptColumns.set(table, columns);\n }\n}\n\nfunction addRenameColumns (\n expression: Expression,\n tables: Iterable<string>,\n renameColumns: Map<string, Record<string, string>>,\n): void {\n const rename = expression.getArgKey('rename') as Expression[] | undefined;\n if (!rename || rename.length === 0) return;\n const columns: Record<string, string> = {};\n for (const e of rename) {\n if (e instanceof Expression) {\n const thisExpr = e.args.this as Expression | undefined;\n if (thisExpr instanceof Expression) {\n columns[thisExpr.name] = (e as Expression).alias;\n }\n }\n }\n for (const table of tables) {\n renameColumns.set(table, columns);\n }\n}\n\nfunction addReplaceColumns (\n expression: Expression,\n tables: Iterable<string>,\n replaceColumns: Map<string, Record<string, AliasExpr>>,\n): void {\n const replace = expression.getArgKey('replace') as Expression[] | undefined;\n if (!replace || replace.length === 0) return;\n const columns: Record<string, AliasExpr> = {};\n for (const e of replace) {\n if (e instanceof AliasExpr) {\n columns[e.alias] = e;\n }\n }\n for (const table of tables) {\n replaceColumns.set(table, columns);\n }\n}\n\nfunction expandStructStarsNoParens (expression: DotExpr): AliasExpr[] {\n const dotColumn = expression.find(ColumnExpr);\n if (!(dotColumn instanceof ColumnExpr) || !dotColumn.isType(DataTypeExprKind.STRUCT)) {\n return [];\n }\n\n // All nested struct values are ColumnDefs, so normalize the first Column in one\n const dotColumnCopy = dotColumn.copy();\n const dotColumnType = dotColumnCopy.type;\n let startingStruct: IdentifierExpr | DotExpr | DataTypeExpr | ColumnDefExpr | undefined = isInstanceOf(dotColumnType, DataTypeExpr) ? dotColumnType : undefined;\n\n // First part is the table name and last part is the star so they can be dropped\n const dotParts = expression.parts.slice(1, -1);\n\n // If we're expanding a nested struct eg. t.c.f1.f2.* find the last struct (f2 in this case)\n outer: for (const part of dotParts.slice(1)) {\n const fieldExprs = startingStruct?.args.expressions || [];\n for (const field of fieldExprs) {\n // Unable to expand star unless all fields are named\n if (!(field.args.this instanceof IdentifierExpr)) {\n return [];\n }\n\n if (!isInstanceOf(field, ColumnDefExpr)) {\n return [];\n }\n\n const fieldKindRaw: unknown = field.args.kind;\n const fieldKind = isInstanceOf(fieldKindRaw, DataTypeExpr) ? fieldKindRaw : undefined;\n\n if (field.name === part.name && fieldKind?.isType(DataTypeExprKind.STRUCT)) {\n startingStruct = fieldKind;\n break outer;\n }\n }\n // There is no matching field in the struct\n return [];\n }\n\n const takenNames = new Set<string>();\n const newSelections: AliasExpr[] = [];\n\n for (const field of (startingStruct?.args.expressions || [])) {\n const name = field.name;\n const fieldThis = field.args.this;\n\n // Ambiguous or anonymous fields can't be expanded\n if (takenNames.has(name) || !(fieldThis instanceof IdentifierExpr)) {\n return [];\n }\n\n takenNames.add(name);\n\n const thisIdent = fieldThis.copy() as IdentifierExpr;\n const allParts = [...dotParts.map((p) => p.copy()), thisIdent];\n const [root, ...parts] = allParts;\n\n const newColumn = columnExpr(\n {\n col: root as IdentifierExpr,\n table: dotColumnCopy.args.table as IdentifierExpr | undefined,\n },\n { fields: parts as IdentifierExpr[] },\n );\n\n newSelections.push(aliasExpr(newColumn, thisIdent, { copy: false }) as AliasExpr);\n }\n\n return newSelections;\n}\n\nfunction expandStructStarsWithParens (expression: DotExpr): AliasExpr[] {\n if (!(expression.args.this instanceof ParenExpr)) {\n return [];\n }\n\n const dotColumn = expression.find(ColumnExpr);\n if (!(dotColumn instanceof ColumnExpr) || !dotColumn.isType(DataTypeExprKind.STRUCT)) {\n return [];\n }\n\n let parent = dotColumn.parent;\n const dotColumnType2 = dotColumn.type;\n let startingStruct: string | ColumnDefExpr | DotExpr | IdentifierExpr | DataTypeExpr | undefined = isInstanceOf(dotColumnType2, DataTypeExpr) ? dotColumnType2 : undefined;\n\n while (parent !== undefined) {\n if (parent instanceof ParenExpr) {\n parent = parent.parent;\n continue;\n }\n\n if (!(parent instanceof DotExpr)) {\n return [];\n }\n\n const rhs = parent.right;\n if (rhs instanceof StarExpr) {\n break;\n }\n\n if (!(rhs instanceof IdentifierExpr)) {\n return [];\n }\n\n let matched = false;\n const expressions: (DataTypeExpr | ColumnDefExpr)[] = filterInstanceOf((startingStruct as DataTypeExpr).args.expressions || [], ColumnDefExpr);\n for (const structFieldDef of expressions) {\n if (structFieldDef.name === rhs.name) {\n matched = true;\n startingStruct = structFieldDef.args.kind as DataTypeExpr | undefined;\n break;\n }\n }\n\n if (!matched) return [];\n\n parent = parent.parent;\n }\n\n const newSelections = [];\n\n const outerParen = expression.args.this;\n\n const expressions: (DataTypeExpr | ColumnDefExpr)[] = filterInstanceOf((startingStruct as DataTypeExpr).args.expressions || [], ColumnDefExpr);\n for (const structFieldDef of expressions) {\n const newIdentifier = structFieldDef.args.this instanceof IdentifierExpr ? structFieldDef.args.this.copy() : new IdentifierExpr({ this: structFieldDef.args.this?.toString() });\n const newDot = DotExpr.build([outerParen.copy(), newIdentifier]);\n const newAlias = alias(newDot, newIdentifier, { copy: false }) as AliasExpr;\n newSelections.push(newAlias);\n }\n\n return newSelections;\n}\n\nfunction expandStars_ (\n scope: Scope,\n resolver: Resolver,\n usingColumnTables: Map<string, string[]>,\n pseudocolumns: Set<string>,\n annotator: TypeAnnotator,\n): void {\n const newSelections: Expression[] = [];\n const exceptColumns = new Map<string, Set<string>>();\n const replaceColumnsMap = new Map<string, Record<string, AliasExpr>>();\n const renameColumnsMap = new Map<string, Record<string, string>>();\n const coalesedColumns = new Set<string>();\n const dialectClass = resolver.dialect._constructor;\n\n let pivotOutputColumns: string[] | undefined = undefined;\n const pivotExcludeColumns = new Set<string>();\n\n const pivot = seqGet(scope.pivots, 0);\n if (pivot instanceof PivotExpr && !pivot.aliasColumnNames.length) {\n if (pivot.unpivot) {\n pivotOutputColumns = unpivotColumns(pivot).map((c) => c.outputName);\n\n for (const field of pivot.args.fields || []) {\n if (field instanceof InExpr) {\n for (const e of field.args.expressions as Expression[]) {\n for (const c of (e as Expression).findAll(ColumnExpr)) {\n pivotExcludeColumns.add(c.outputName);\n }\n }\n }\n }\n } else {\n for (const c of pivot.findAll(ColumnExpr)) {\n pivotExcludeColumns.add(c.outputName);\n }\n\n const pivotColumns = pivot.getArgKey('columns') as Expression[] | undefined;\n pivotOutputColumns = (pivotColumns || []).map((c) => (c as Expression).outputName);\n if (!pivotOutputColumns.length) {\n pivotOutputColumns = (pivot.args.expressions as Expression[]).map((c) => c.aliasOrName);\n }\n }\n }\n\n if (dialectClass.SUPPORTS_STRUCT_STAR_EXPANSION && scope.stars.some((col) => col instanceof DotExpr)) {\n annotator.annotateScope(scope);\n }\n\n for (const expression of scope.expression.selects) {\n const tables: string[] = [];\n\n if (expression instanceof StarExpr) {\n tables.push(...Object.keys(scope.selectedSources));\n addExceptColumns(expression, tables, exceptColumns);\n addReplaceColumns(expression, tables, replaceColumnsMap);\n addRenameColumns(expression, tables, renameColumnsMap);\n } else if ((expression as Expression).isStar) {\n if (!(expression instanceof DotExpr)) {\n const tableName = expression instanceof ColumnExpr ? expression.table : '';\n if (tableName) tables.push(tableName);\n const exprThis = (expression as Expression).args.this;\n if (exprThis instanceof Expression) {\n addExceptColumns(exprThis, tables, exceptColumns);\n addReplaceColumns(exprThis, tables, replaceColumnsMap);\n addRenameColumns(exprThis, tables, renameColumnsMap);\n }\n } else if (dialectClass.SUPPORTS_STRUCT_STAR_EXPANSION && !dialectClass.REQUIRES_PARENTHESIZED_STRUCT_ACCESS) {\n const structFields = expandStructStarsNoParens(expression as DotExpr);\n if (0 < structFields.length) {\n newSelections.push(...structFields);\n continue;\n }\n } else if (dialectClass.REQUIRES_PARENTHESIZED_STRUCT_ACCESS) {\n const structFields = expandStructStarsWithParens(expression as DotExpr);\n if (0 < structFields.length) {\n newSelections.push(...structFields);\n continue;\n }\n }\n }\n\n if (tables.length === 0) {\n newSelections.push(expression as Expression);\n continue;\n }\n\n for (const table of tables) {\n if (!scope.sources.has(table)) {\n throw new OptimizeError(`Unknown table: ${table}`);\n }\n\n let columns = resolver.getSourceColumns(table, { onlyVisible: true });\n if (!columns.length) columns = scope.outerColumns;\n\n if (0 < pseudocolumns.size && dialectClass.EXCLUDES_PSEUDOCOLUMNS_FROM_STAR) {\n columns = columns.filter((name) => !pseudocolumns.has(name.toUpperCase()));\n }\n\n if (!columns.length || columns.includes('*')) {\n return;\n }\n\n const columnsToExclude = exceptColumns.get(table) || new Set<string>();\n const renamedColumns = renameColumnsMap.get(table) || {};\n const replacedColumns = replaceColumnsMap.get(table) || {};\n\n if (pivot instanceof PivotExpr) {\n let pivotColumns: string[] | undefined;\n if (pivotOutputColumns && 0 < pivotExcludeColumns.size) {\n pivotColumns = columns.filter((c) => !pivotExcludeColumns.has(c));\n pivotColumns.push(...pivotOutputColumns);\n } else {\n pivotColumns = pivot.aliasColumnNames;\n }\n\n if (0 < pivotColumns.length) {\n for (const name of pivotColumns) {\n if (!columnsToExclude.has(name)) {\n newSelections.push(\n aliasExpr(columnExpr({\n col: name,\n table: pivot.alias,\n }), name, { copy: false }) as Expression,\n );\n }\n }\n continue;\n }\n }\n\n for (const name of columns) {\n if (columnsToExclude.has(name) || coalesedColumns.has(name)) continue;\n\n const tablesForCol = usingColumnTables.get(name);\n if (tablesForCol?.includes(table)) {\n coalesedColumns.add(name);\n const coalesceArgs = tablesForCol.map((t) => columnExpr({\n col: name,\n table: t,\n }));\n newSelections.push(\n aliasExpr(\n new CoalesceExpr({\n this: coalesceArgs[0],\n expressions: coalesceArgs.slice(1),\n }),\n name,\n { copy: false },\n ) as Expression,\n );\n } else {\n const alias_ = renamedColumns[name] ?? name;\n const selectionExpr = replacedColumns[name] || columnExpr({\n col: name,\n table,\n });\n newSelections.push(\n alias_ !== name\n ? aliasExpr(selectionExpr, alias_, { copy: false }) as Expression\n : selectionExpr,\n );\n }\n }\n }\n }\n\n if (0 < newSelections.length && scope.expression instanceof SelectExpr) {\n scope.expression.setArgKey('expressions', newSelections);\n }\n}\n\nexport function qualifyOutputs (scopeOrExpression: Scope | Expression): void {\n let scopeInstance: Scope;\n\n if (scopeOrExpression instanceof Scope) {\n scopeInstance = scopeOrExpression;\n } else {\n const built = buildScope(scopeOrExpression);\n if (!(built instanceof Scope)) return;\n scopeInstance = built;\n }\n\n if (!(scopeInstance.expression instanceof SelectExpr)) {\n return;\n }\n\n const selects = scopeInstance.expression.selects;\n const outerColumns = scopeInstance.outerColumns;\n const newSelections: Expression[] = [];\n const maxLen = Math.max(selects.length, outerColumns.length);\n\n for (let i = 0; i < maxLen; i++) {\n let selection = selects[i] as Expression | undefined;\n const aliasedColumn = outerColumns[i];\n\n if (!selection || selection instanceof QueryTransformExpr) {\n break;\n }\n\n if (selection instanceof SubqueryExpr) {\n if (!selection.outputName) {\n selection.setArgKey('alias', new TableAliasExpr({ this: toIdentifier(`_col_${i}`) }));\n }\n } else if (!(selection instanceof AliasExpr) && !(selection instanceof AliasesExpr) && !selection.isStar) {\n selection = aliasExpr(\n selection,\n selection.outputName || `_col_${i}`,\n { copy: false },\n ) as Expression;\n }\n\n if (aliasedColumn) {\n selection.setArgKey('alias', toIdentifier(aliasedColumn));\n }\n\n newSelections.push(selection);\n }\n\n if (0 < newSelections.length && scopeInstance.expression instanceof SelectExpr) {\n scopeInstance.expression.setArgKey('expressions', newSelections);\n }\n}\n\nfunction selectByPos (scope: Scope, node: LiteralExpr): AliasExpr {\n const index = Number(node.args.this) - 1;\n const select = scope.expression.selects[index] as Expression | undefined;\n if (!(select instanceof AliasExpr)) {\n throw new OptimizeError(`Unknown output column: ${node.name}`);\n }\n return select;\n}\n\nfunction expandPositionalReferences (\n scope: Scope,\n expressions: Iterable<Expression>,\n dialect: Dialect,\n options: { alias?: boolean } = {},\n): Expression[] {\n const { alias = false } = options;\n const dialectClass = dialect._constructor;\n const newNodes: Expression[] = [];\n let ambiguousProjections: Set<string> | undefined;\n\n for (const node of expressions) {\n if (node.isInteger) {\n const select = selectByPos(scope, node as LiteralExpr);\n if (alias) {\n const selectAlias = select.alias;\n newNodes.push(selectAlias ? columnExpr({ col: selectAlias }) : node);\n } else {\n const selectThis = select.args.this as Expression;\n let ambiguous = false;\n\n if (dialectClass.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES) {\n if (ambiguousProjections === undefined) {\n ambiguousProjections = new Set(\n scope.expression.selects\n .map((s) => s.aliasOrName)\n .filter((name) => name in scope.selectedSources),\n );\n }\n ambiguous = Array.from(selectThis.findAll(ColumnExpr)).some(\n (col) => ambiguousProjections?.has(col.parts[0]?.name || ''),\n );\n }\n\n if (\n CONSTANTS.some((C) => selectThis instanceof C)\n || selectThis.isNumber\n || selectThis.find(ExplodeExpr)\n || selectThis.find(UnnestExpr)\n || ambiguous\n ) {\n newNodes.push(node);\n } else {\n newNodes.push(selectThis.copy());\n }\n }\n } else {\n newNodes.push(node);\n }\n }\n\n return newNodes;\n}\n\nfunction expandGroupBy (scope: Scope, dialect: Dialect): void {\n const group = (scope.expression as SelectExpr).args.group;\n if (!group) return;\n\n const groupExpressions = filterInstanceOf(group.args.expressions || [], Expression);\n group.setArgKey('expressions', expandPositionalReferences(scope, groupExpressions, dialect));\n (scope.expression as SelectExpr).setArgKey('group', group);\n}\n\nfunction expandOrderByAndDistinctOn (scope: Scope, resolver: Resolver): void {\n for (const modifierKey of ['order', 'distinct']) {\n let modifier = scope.expression.getArgKey(modifierKey) as Expression | undefined;\n\n if (modifier instanceof DistinctExpr) {\n modifier = modifier.args.on;\n }\n\n if (!(modifier instanceof Expression)) {\n continue;\n }\n\n let modifierExpressions = modifier.args.expressions as Expression[];\n\n if (modifierKey === 'order') {\n modifierExpressions = modifierExpressions.map(\n (ordered) => ordered.args.this as Expression,\n );\n }\n\n const expanded = expandPositionalReferences(scope, modifierExpressions, resolver.dialect, { alias: true });\n\n for (let j = 0; j < modifierExpressions.length; j++) {\n const original = modifierExpressions[j];\n const expandedNode = expanded[j];\n\n for (const agg of original.findAll(AggFuncExpr)) {\n for (const col of agg.findAll(ColumnExpr)) {\n if (!col.table) {\n const table = resolver.getTable(col.name);\n if (table) {\n col.setArgKey('table', table);\n }\n }\n }\n }\n\n original.replace(expandedNode);\n }\n\n if (scope.expression.getArgKey('group')) {\n const selectsMap = new Map<string, Expression>(\n scope.expression.selects\n .filter((s): s is AliasExpr => s instanceof AliasExpr)\n .map((s) => [(s.args.this as Expression).sql(), columnExpr({ col: s.aliasOrName })]),\n );\n\n for (const expr of modifierExpressions) {\n if (expr.isInteger) {\n expr.replace(toIdentifier(selectByPos(scope, expr as LiteralExpr).alias));\n } else {\n const match = selectsMap.get(expr.sql());\n if (match) expr.replace(match);\n }\n }\n }\n }\n}\n\nexport function quoteIdentifiers (\n expression: Expression,\n options: {\n dialect?: DialectType;\n identify?: boolean;\n } = {},\n): Expression {\n const {\n dialect: dialectArg, identify = true,\n } = options;\n const dialect = Dialect.getOrRaise(dialectArg);\n return expression.transform(\n dialect.quoteIdentifier.bind(dialect),\n {\n identify,\n copy: false,\n },\n );\n}\n\nexport function pushdownCteAliasColumns (scope: Scope): void {\n for (const cte of scope.ctes) {\n const aliasColumnNames = cte.aliasColumnNames;\n if (0 < aliasColumnNames.length && cte.args.this instanceof SelectExpr) {\n const selectExpr = cte.args.this;\n const expressions = selectExpr.args.expressions || [];\n const newExpressions: Expression[] = [];\n\n for (let i = 0; i < aliasColumnNames.length; i++) {\n const alias_ = aliasColumnNames[i];\n const projection = expressions[i];\n if (!projection) break;\n\n let newProjection: Expression;\n if (projection instanceof AliasExpr) {\n projection.setArgKey('alias', toIdentifier(alias_));\n newProjection = projection;\n } else {\n newProjection = aliasExpr(projection, alias_) as Expression;\n }\n newExpressions.push(newProjection);\n }\n\n selectExpr.setArgKey('expressions', newExpressions);\n }\n }\n}\n","import type {\n Expression, QueryExpr,\n} from './expressions';\nimport {\n TableExpr, TagExpr,\n SelectExpr,\n StarExpr,\n SubqueryExpr,\n SetOperationExpr,\n ColumnExpr,\n UdtfExpr,\n PlaceholderExpr,\n UNWRAPPED_QUERIES,\n maybeParse,\n expand,\n} from './expressions';\nimport { SqlglotError } from './errors';\nimport { normalizeIdentifiers } from './optimizer/normalize_identifiers';\nimport { qualify } from './optimizer/qualify';\nimport {\n buildScope,\n Scope, ScopeType, findAllInScope,\n} from './optimizer/scope';\nimport type { DialectType } from './dialects/dialect';\nimport type { Schema } from './schema';\nimport { id } from './port_internals';\n\nexport interface NodeOptions {\n name: string;\n expression: Expression;\n source: Expression;\n downstream?: Node[];\n sourceName?: string;\n referenceNodeName?: string;\n}\n\nexport class Node {\n public name: string;\n public expression: Expression;\n public source: Expression;\n public downstream: Node[];\n public sourceName: string;\n public referenceNodeName: string;\n\n constructor (options: NodeOptions) {\n this.name = options.name;\n this.expression = options.expression;\n this.source = options.source;\n this.downstream = options.downstream || [];\n this.sourceName = options.sourceName || '';\n this.referenceNodeName = options.referenceNodeName || '';\n }\n\n * walk (): Generator<Node> {\n yield this;\n for (const d of this.downstream) {\n yield* d.walk();\n }\n }\n\n toHtml (dialect?: DialectType, opts: Record<string, unknown> = {}): GraphHTML {\n const nodes: Record<string, unknown> = {};\n const edges: unknown[] = [];\n\n for (const node of this.walk()) {\n let label: string;\n let title: string;\n let group: number;\n\n if (node.expression instanceof TableExpr) {\n label = `FROM ${node.expression.args.this}`;\n title = `<pre>SELECT ${node.name} FROM ${node.expression.args.this}</pre>`;\n group = 1;\n } else {\n label = node.expression.sql({\n pretty: true,\n dialect,\n });\n\n const sourceSql = node.source.transform((n: Expression) => {\n return n === node.expression\n ? new TagExpr({\n this: n,\n prefix: '<b>',\n postfix: '</b>',\n })\n : n;\n }, { copy: false }).sql({\n pretty: true,\n dialect,\n });\n\n title = `<pre>${sourceSql}</pre>`;\n group = 0;\n }\n\n const nodeId = id(node);\n\n nodes[nodeId] = {\n id: nodeId,\n label,\n title,\n group,\n };\n\n for (const d of node.downstream) {\n edges.push({\n from: nodeId,\n to: id(d),\n });\n }\n }\n\n return new GraphHTML(nodes, edges, opts);\n }\n}\n\nexport interface LineageOptions {\n schema?: Record<string, unknown> | Schema;\n sources?: Record<string, string | QueryExpr>;\n dialect?: DialectType;\n scope?: Scope;\n trimSelects?: boolean;\n copy?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * Build the lineage graph for a column of a SQL query.\n *\n * @param column The column to build the lineage for.\n * @param sql The SQL string or expression.\n * @param options Additional options like schema, dialect, scope, etc.\n * @returns A lineage node.\n */\nexport function lineage (\n column: string | ColumnExpr,\n sql: string | Expression,\n options: LineageOptions = {},\n): Node {\n const {\n schema,\n sources,\n dialect,\n scope: providedScope,\n trimSelects = true,\n copy = true,\n ...kwargs\n } = options;\n\n let expression = maybeParse(sql, {\n copy,\n dialect,\n });\n const normalizedColumn = normalizeIdentifiers(column, { dialect }).name;\n\n if (sources) {\n const parsedSources: Record<string, QueryExpr> = {};\n for (const [k, v] of Object.entries(sources)) {\n parsedSources[k] = maybeParse(v, {\n copy,\n dialect,\n }) as QueryExpr;\n }\n\n expression = expand(expression, parsedSources, {\n dialect,\n copy,\n });\n }\n\n let scope = providedScope;\n\n if (!scope) {\n expression = qualify(expression, {\n dialect,\n schema,\n validateQualifyColumns: false,\n identify: false,\n ...kwargs,\n });\n\n scope = buildScope(expression);\n }\n\n if (!scope) {\n throw new SqlglotError('Cannot build lineage, sql must be SELECT');\n }\n\n // Assuming scope.expression is a Select expression containing `selects`\n const hasColumn = scope.expression.selects.some(\n (select) => select.aliasOrName === normalizedColumn,\n );\n\n if (!hasColumn) {\n throw new SqlglotError(`Cannot find column '${normalizedColumn}' in query.`);\n }\n\n return toNode(normalizedColumn, scope, dialect, { trimSelects });\n}\n\nexport interface ToNodeOptions {\n scopeName?: string;\n upstream?: Node;\n sourceName?: string;\n referenceNodeName?: string;\n trimSelects?: boolean;\n}\n\nexport function toNode (\n column: string | number,\n scope: Scope,\n dialect: DialectType | undefined,\n options: ToNodeOptions = {},\n): Node {\n let {\n // eslint-disable-next-line prefer-const\n scopeName = undefined,\n upstream = undefined,\n // eslint-disable-next-line prefer-const\n sourceName = undefined,\n // eslint-disable-next-line prefer-const\n referenceNodeName = undefined,\n // eslint-disable-next-line prefer-const\n trimSelects = true,\n } = options;\n\n // Find the specific select clause that is the source of the column we want.\n // This can either be a specific, named select or a generic `*` clause.\n let select: Expression;\n if (typeof column === 'number') {\n select = scope.expression.selects[column];\n } else {\n const foundSelect = scope.expression.selects.find(\n (s) => s.aliasOrName === column,\n );\n select = foundSelect || (scope.expression.isStar ? new StarExpr({}) : scope.expression);\n }\n\n if (scope.expression instanceof SubqueryExpr) {\n for (const source of scope.subqueryScopes) {\n return toNode(column, source, dialect, {\n upstream,\n sourceName,\n referenceNodeName,\n trimSelects,\n });\n }\n }\n\n if (scope.expression instanceof SetOperationExpr) {\n const name = scope.expression.constructor.name.toUpperCase().replace('EXPR', '');\n upstream = upstream || new Node({\n name,\n source: scope.expression,\n expression: select,\n });\n\n let index: number;\n if (typeof column === 'number') {\n index = column;\n } else {\n index = scope.expression.selects.findIndex(\n (s) => s.aliasOrName === column || s.isStar,\n );\n }\n\n if (index === -1) {\n throw new Error(`Could not find ${column} in ${scope.expression}`);\n }\n\n for (const s of scope.unionScopes) {\n toNode(index, s, dialect, {\n upstream,\n sourceName,\n referenceNodeName,\n trimSelects,\n });\n }\n\n return upstream;\n }\n\n let source: Expression;\n if (trimSelects && scope.expression instanceof SelectExpr) {\n // For better ergonomics in our node labels, replace the full select with\n // a version that has only the column we care about.\n source = scope.expression.select(select, { append: false }) as Expression;\n } else {\n source = scope.expression;\n }\n\n // Create the node for this step in the lineage chain, and attach it to the previous one.\n const node = new Node({\n name: scopeName ? `${scopeName}.${column}` : String(column),\n source,\n expression: select,\n sourceName: sourceName || '',\n referenceNodeName: referenceNodeName || '',\n });\n\n if (upstream) {\n upstream.downstream.push(node);\n }\n\n const subqueryScopes = new WeakMap<Expression, Scope>();\n for (const subqueryScope of scope.subqueryScopes) {\n subqueryScopes.set(subqueryScope.expression, subqueryScope);\n }\n\n for (const subquery of findAllInScope<QueryExpr>(select, UNWRAPPED_QUERIES)) {\n const subqueryScope = subqueryScopes.get(subquery);\n if (!subqueryScope) {\n console.warn(`Unknown subquery scope: ${subquery.sql({ dialect })}`);\n continue;\n }\n\n for (const name of subquery.namedSelects || []) {\n toNode(name, subqueryScope, dialect, {\n upstream: node,\n trimSelects,\n });\n }\n }\n\n // if the select is a star add all scope sources as downstreams\n if (select instanceof StarExpr) {\n for (const sourceVal of Array.from(scope.sources.values())) {\n let sourceExpr;\n if (sourceVal instanceof Scope) {\n sourceExpr = sourceVal.expression;\n } else {\n sourceExpr = sourceVal;\n }\n node.downstream.push(\n new Node({\n name: select.sql({ comments: false }),\n source: sourceExpr,\n expression: sourceExpr,\n }),\n );\n }\n }\n\n // Find all columns that went into creating this one to list their lineage nodes.\n const sourceColumnsSet = new Set<ColumnExpr>(\n findAllInScope(select, [ColumnExpr]),\n );\n\n let derivedTables: Expression[];\n // If the source is a Udtf find columns used in the Udtf to generate the table\n if (source instanceof UdtfExpr) {\n const udtfCols = source.findAll(ColumnExpr);\n for (const c of udtfCols) sourceColumnsSet.add(c);\n\n derivedTables = Array.from(scope.sources.values())\n .filter((s): s is Scope => s instanceof Scope && s.isDerivedTable)\n .map((s) => s.expression.parent)\n .filter((p): p is Expression => p !== undefined);\n } else {\n derivedTables = scope.derivedTables;\n }\n\n const sourceNames = new Map<string, string>();\n for (const dt of derivedTables) {\n if (dt.comments && dt.comments[0]?.startsWith('source: ')) {\n sourceNames.set(dt.alias, dt.comments[0].split(' ')[1]);\n }\n }\n\n const pivots = scope.pivots;\n const pivot = pivots.length === 1 && !pivots[0].unpivot ? pivots[0] : undefined;\n const pivotColumnMapping = new Map<string, ColumnExpr[]>();\n\n if (pivot) {\n const pivotColumns = pivot.args.columns ?? [];\n const pivotAggsCount = pivot.args.expressions?.length ?? 0;\n\n pivot.args.expressions?.forEach((agg, i: number) => {\n const aggCols = Array.from(agg.findAll(ColumnExpr)) as ColumnExpr[];\n for (let colIndex = i; colIndex < (pivotColumns?.length || 0); colIndex += pivotAggsCount) {\n pivotColumnMapping.set(pivotColumns[colIndex].name, aggCols);\n }\n });\n }\n\n for (const c of Array.from(sourceColumnsSet)) {\n const table = c.table;\n let sourceScopeOrExpr: Expression | Scope | undefined = scope.sources.get(table);\n\n if (sourceScopeOrExpr instanceof Scope) {\n let refNodeName: string | undefined = undefined;\n if (sourceScopeOrExpr.scopeType === ScopeType.DERIVED_TABLE && !sourceNames.has(table)) {\n refNodeName = table;\n } else if (sourceScopeOrExpr.scopeType === ScopeType.CTE) {\n const selectedNode = scope.selectedSources[table]?.[0];\n refNodeName = selectedNode ? selectedNode.name : undefined;\n }\n\n toNode(c.name, sourceScopeOrExpr, dialect, {\n scopeName: table,\n upstream: node,\n sourceName: sourceNames.get(table) || sourceName,\n referenceNodeName: refNodeName,\n trimSelects,\n });\n } else if (pivot && pivot.aliasOrName === c.table) {\n const downstreamColumns: ColumnExpr[] = [];\n const columnName = c.name;\n\n if (pivot.args.columns?.some((pc) => pc.name === columnName)) {\n downstreamColumns.push(...(pivotColumnMapping.get(columnName) || []));\n } else {\n // Adapt column to be from the implicit pivoted source\n downstreamColumns.push(new ColumnExpr({\n this: c.args.this,\n table: pivot.parent?.aliasOrName,\n }));\n }\n\n for (const downstreamColumn of downstreamColumns) {\n const dsTable = downstreamColumn.table;\n let dsSource: Expression | Scope | undefined = scope.sources.get(dsTable);\n\n if (dsSource instanceof Scope) {\n toNode(downstreamColumn.name, dsSource, dialect, {\n scopeName: dsTable,\n upstream: node,\n sourceName: sourceNames.get(dsTable) || sourceName,\n referenceNodeName,\n trimSelects,\n });\n } else {\n dsSource = dsSource || new PlaceholderExpr({});\n node.downstream.push(\n new Node({\n name: downstreamColumn.sql({ comments: false }),\n source: dsSource,\n expression: dsSource,\n }),\n );\n }\n }\n } else {\n sourceScopeOrExpr = sourceScopeOrExpr || new PlaceholderExpr({});\n node.downstream.push(\n new Node({\n name: c.sql({ comments: false }),\n source: sourceScopeOrExpr,\n expression: sourceScopeOrExpr,\n }),\n );\n }\n }\n\n return node;\n}\n\n/**\n * Node to HTML generator using vis.js.\n *\n * https://visjs.github.io/vis-network/docs/network/\n */\nexport class GraphHTML {\n public imports: boolean;\n public options: Record<string, unknown>;\n public nodes: Record<string, unknown>;\n public edges: unknown[];\n\n constructor (\n nodes: Record<string, unknown>,\n edges: unknown[],\n options: {\n imports?: boolean;\n [index: string]: unknown;\n } = {},\n ) {\n const { imports = true } = options;\n\n this.imports = imports;\n this.nodes = nodes;\n this.edges = edges;\n\n this.options = {\n height: '500px',\n width: '100%',\n layout: {\n hierarchical: {\n enabled: true,\n nodeSpacing: 200,\n sortMethod: 'directed',\n },\n },\n interaction: {\n dragNodes: false,\n selectable: false,\n },\n physics: {\n enabled: false,\n },\n edges: {\n arrows: 'to',\n },\n nodes: {\n font: '20px monaco',\n shape: 'box',\n widthConstraint: {\n maximum: 300,\n },\n },\n ...(options || {}),\n };\n }\n\n toString (): string {\n const nodesJson = JSON.stringify(Object.values(this.nodes));\n const edgesJson = JSON.stringify(this.edges);\n const optionsJson = JSON.stringify(this.options);\n\n const importsHtml = this.imports\n ? `<script type=\"text/javascript\" src=\"https://unpkg.com/vis-data@latest/peer/umd/vis-data.min.js\"></script>\n <script type=\"text/javascript\" src=\"https://unpkg.com/vis-network@latest/peer/umd/vis-network.min.js\"></script>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/vis-network/styles/vis-network.min.css\" />`\n : '';\n\n return `<div>\n <div id=\"sqlglot-lineage\"></div>\n ${importsHtml}\n <script type=\"text/javascript\">\n var nodes = new vis.DataSet(${nodesJson});\n nodes.forEach(row => {\n row[\"title\"] = new DOMParser().parseFromString(row[\"title\"], \"text/html\").body.childNodes[0];\n });\n\n new vis.Network(\n document.getElementById(\"sqlglot-lineage\"),\n {\n nodes: nodes,\n edges: new vis.DataSet(${edgesJson})\n },\n ${optionsJson}\n );\n </script>\n</div>`;\n }\n\n toHtml (): string {\n return this.toString();\n }\n}\n","// https://github.com/tobymao/sqlglot/blob/main/sqlglot/optimizer/merge_subqueries.py\n\nimport { isInstanceOf } from '../port_internals';\nimport {\n AggFuncExpr,\n alias as aliasExpr,\n BinaryExpr,\n ColumnExpr,\n columnTableNames,\n EqExpr,\n ExplodeExpr,\n Expression,\n FromExpr,\n FuncExpr,\n GroupExpr,\n HavingExpr,\n JoinExpr,\n JoinExprKind,\n NeqExpr,\n OrderExpr,\n ParenExpr,\n paren as parenExpr,\n QueryTransformExpr,\n SelectExpr,\n SubqueryExpr,\n TableAliasExpr,\n TableExpr,\n toIdentifier,\n UnaryExpr,\n WhereExpr,\n WindowExpr,\n} from '../expressions';\nimport {\n findNewName, seqGet,\n} from '../helper';\nimport { Dialect } from '../dialects/dialect';\nimport {\n Scope, traverseScope,\n} from './scope';\n\n/**\n * Rewrite sqlglot AST to merge derived tables into the outer query.\n *\n * This also merges CTEs if they are selected from only once.\n *\n * Example:\n * ```ts\n * import { parseOne } from 'sqlglot';\n * import { mergeSubqueries } from 'sqlglot/optimizer';\n *\n * const expression = parseOne(\"SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y\");\n * mergeSubqueries(expression).sql();\n * // 'SELECT x.a FROM x CROSS JOIN y'\n * ```\n *\n * If `leaveTablesIsolated` is True, this will not merge inner queries into outer\n * queries if it would result in multiple table selects in a single query.\n *\n * Inspired by https://dev.mysql.com/doc/refman/8.0/en/derived-table-optimization.html\n *\n * @param expression - Expression to optimize\n * @param options - Optimization options\n * @param options.leaveTablesIsolated - Don't merge if it creates multiple table selects (default: false)\n * @returns The optimized expression\n */\nexport function mergeSubqueries<E extends Expression> (\n expression: E,\n options: {\n leaveTablesIsolated?: boolean;\n } = {},\n): E {\n const { leaveTablesIsolated = false } = options;\n\n expression = mergeCtes(expression, { leaveTablesIsolated }) as E;\n expression = mergeDerivedTables(expression, { leaveTablesIsolated }) as E;\n\n return expression;\n}\n\nconst SAFE_TO_REPLACE_UNWRAPPED = [\n ColumnExpr,\n EqExpr,\n FuncExpr,\n NeqExpr,\n ParenExpr,\n] as const;\n\ntype FromOrJoin = FromExpr | JoinExpr;\n\nfunction mergeCtes<E extends Expression> (\n expression: E,\n options: { leaveTablesIsolated: boolean },\n): E {\n const { leaveTablesIsolated } = options;\n const scopes = Array.from(traverseScope(expression));\n\n // All places where we select from CTEs\n // Key on CTE scope ID to detect CTEs selected from multiple times\n const cteSelections = new Map<Scope, [Scope, Scope, Expression][]>();\n\n for (const outerScope of scopes) {\n for (const [, sourceEntry] of Object.entries(outerScope.selectedSources)) {\n const [table, innerSource] = sourceEntry;\n if (innerSource instanceof Scope && innerSource.isCte) {\n let scopeList = cteSelections.get(innerSource);\n if (!scopeList) {\n scopeList = [];\n cteSelections.set(innerSource, scopeList);\n }\n scopeList.push([\n outerScope,\n innerSource,\n table,\n ]);\n }\n }\n }\n\n // Only merge CTEs that are selected from exactly once\n const singularCteSelections: [Scope, Scope, Expression][] = [];\n for (const [, selections] of cteSelections) {\n if (selections.length === 1) {\n singularCteSelections.push(selections[0]);\n }\n }\n\n for (const [\n outerScope,\n innerScope,\n table,\n ] of singularCteSelections) {\n // Find FromExpr or JoinExpr ancestor\n const fromOrJoin = table.findAncestor<FromExpr | JoinExpr>(FromExpr, JoinExpr);\n if (fromOrJoin && mergeable(outerScope, innerScope, { leaveTablesIsolated }, fromOrJoin)) {\n const alias = table.aliasOrName;\n renameInnerSources(outerScope, innerScope, alias);\n mergeFrom(outerScope, innerScope, table as SubqueryExpr | TableExpr, alias);\n mergeExpressions(outerScope, innerScope, alias);\n mergeOrder(outerScope, innerScope);\n mergeJoins(outerScope, innerScope, fromOrJoin);\n mergeWhere(outerScope, innerScope, fromOrJoin);\n mergeHints(outerScope, innerScope);\n popCte(innerScope);\n outerScope.clearCache();\n }\n }\n\n return expression;\n}\n\nfunction mergeDerivedTables<E extends Expression> (\n expression: E,\n options: { leaveTablesIsolated: boolean },\n): E {\n const { leaveTablesIsolated } = options;\n for (const outerScope of traverseScope(expression)) {\n for (const subquery of outerScope.derivedTables) {\n // Find FromExpr or JoinExpr ancestor\n const fromOrJoin = subquery.findAncestor<FromExpr | JoinExpr>(FromExpr, JoinExpr);\n\n const alias = subquery.aliasOrName;\n const innerScope = outerScope.sources.get(alias);\n\n if (\n innerScope instanceof Scope\n && fromOrJoin\n && mergeable(outerScope, innerScope, { leaveTablesIsolated }, fromOrJoin)\n ) {\n renameInnerSources(outerScope, innerScope, alias);\n mergeFrom(outerScope, innerScope, subquery, alias);\n mergeExpressions(outerScope, innerScope, alias);\n mergeOrder(outerScope, innerScope);\n mergeJoins(outerScope, innerScope, fromOrJoin);\n mergeWhere(outerScope, innerScope, fromOrJoin);\n mergeHints(outerScope, innerScope);\n outerScope.clearCache();\n }\n }\n }\n\n return expression;\n}\n\nfunction mergeable (\n outerScope: Scope,\n innerScope: Scope,\n options: { leaveTablesIsolated: boolean },\n fromOrJoin: FromOrJoin,\n): boolean {\n const { leaveTablesIsolated } = options;\n const innerSelect = innerScope.expression.unnest();\n\n // Check if window expressions are in unmergable operations\n function isWindowExpressionInUnmergableOperation (): boolean {\n const windowAliases = new Set<string>();\n if (!(innerSelect instanceof SelectExpr)) {\n return false;\n }\n const innerSelectExpr = innerSelect;\n\n for (const s of innerSelectExpr.selects) {\n if (!(s instanceof Expression)) {\n continue;\n }\n if (s.find(WindowExpr)) {\n windowAliases.add(s.aliasOrName);\n }\n }\n\n const innerSelectName = fromOrJoin.aliasOrName;\n const unmergableWindowColumns: ColumnExpr[] = [];\n\n for (const column of outerScope.columns) {\n // Check if column has unmergable ancestor\n const hasUnmergableAncestor = column.findAncestor(\n WhereExpr,\n GroupExpr,\n OrderExpr,\n JoinExpr,\n HavingExpr,\n AggFuncExpr,\n ) !== undefined;\n\n if (hasUnmergableAncestor) {\n unmergableWindowColumns.push(column);\n }\n }\n\n const windowExpressionsInUnmergable = unmergableWindowColumns.filter(\n (column) => column.table === innerSelectName && windowAliases.has(column.name),\n );\n\n return 0 < windowExpressionsInUnmergable.length;\n }\n\n // Check if outer select joins on inner select's join\n function outerSelectJoinsOnInnerSelectJoin (): boolean {\n if (!(fromOrJoin instanceof JoinExpr)) {\n return false;\n }\n\n const alias = fromOrJoin.aliasOrName;\n const on = fromOrJoin.args.on;\n\n if (!on) {\n return false;\n }\n\n const selections = Array.from(on.findAll(ColumnExpr))\n .filter((c) => c.table === alias)\n .map((c) => c.name);\n\n const innerFrom = innerScope.expression.getArgKey('from') as FromExpr | undefined;\n\n if (!innerFrom) {\n return false;\n }\n\n const innerFromTable = innerFrom.aliasOrName;\n\n if (!(innerScope.expression instanceof SelectExpr)) {\n return false;\n }\n\n const innerSelectExpr = innerScope.expression;\n const innerProjections = new Map<string, Expression>();\n\n for (const s of innerSelectExpr.selects) {\n if (!(s instanceof Expression)) {\n continue;\n }\n innerProjections.set(s.aliasOrName, s);\n }\n\n return selections.some((selection) => {\n const projection = innerProjections.get(selection);\n if (!projection) {\n return false;\n }\n const columns = Array.from(projection.findAll(ColumnExpr));\n return columns.some((col) => col.table !== innerFromTable);\n });\n }\n\n // Check if this is a recursive CTE\n function isRecursive (): boolean {\n const cte = innerScope.expression.parent;\n let node: Expression | undefined = outerScope.expression.parent;\n\n while (node) {\n if (node === cte) {\n return true;\n }\n node = node.parent;\n }\n return false;\n }\n\n // Main mergeability checks\n if (!(outerScope.expression instanceof SelectExpr)) {\n return false;\n }\n\n const outerSelectExpr = outerScope.expression;\n\n if (outerSelectExpr.isStar) {\n return false;\n }\n\n if (!(innerSelect instanceof SelectExpr)) {\n return false;\n }\n\n const innerSelectExpr = innerSelect;\n\n // Check for unmergable args\n for (const arg of Dialect.UNMERGABLE_ARGS) {\n if (innerSelectExpr.getArgKey(arg)) {\n return false;\n }\n }\n\n if (!innerSelectExpr.args.from) {\n return false;\n }\n\n if (0 < outerScope.pivots.length) {\n return false;\n }\n\n // Check for AggFunc, Select, or Explode in inner expressions\n for (const e of innerSelectExpr.args.expressions ?? []) {\n if (!(e instanceof Expression)) {\n continue;\n }\n if (e.find(AggFuncExpr) || e.find(SelectExpr) || e.find(ExplodeExpr)) {\n return false;\n }\n }\n\n if (leaveTablesIsolated && 1 < Object.keys(outerScope.selectedSources).length) {\n return false;\n }\n\n if (fromOrJoin instanceof JoinExpr && innerSelectExpr.args.joins && 0 < innerSelectExpr.args.joins.length) {\n return false;\n }\n\n const joinSide = fromOrJoin instanceof JoinExpr ? fromOrJoin.args.side : undefined;\n\n if (\n fromOrJoin instanceof JoinExpr\n && innerSelectExpr.args.where\n && (joinSide === JoinExprKind.FULL || joinSide === JoinExprKind.LEFT || joinSide === JoinExprKind.RIGHT)\n ) {\n return false;\n }\n\n const outerJoins = outerSelectExpr.args.joins;\n\n if (\n fromOrJoin instanceof FromExpr\n && innerSelectExpr.args.where\n && outerJoins\n && outerJoins.some((j) => isInstanceOf(j, JoinExpr) && (j.args.side === JoinExprKind.FULL || j.args.side === JoinExprKind.RIGHT))\n ) {\n return false;\n }\n\n if (outerSelectJoinsOnInnerSelectJoin()) {\n return false;\n }\n\n if (isWindowExpressionInUnmergableOperation()) {\n return false;\n }\n\n if (isRecursive()) {\n return false;\n }\n\n if (innerSelectExpr.args.order && outerScope.isUnion) {\n return false;\n }\n\n const firstExpr = seqGet(innerSelectExpr.args.expressions ?? [], 0);\n if (firstExpr instanceof QueryTransformExpr) {\n return false;\n }\n\n return true;\n}\n\nfunction renameInnerSources (outerScope: Scope, innerScope: Scope, alias: string): void {\n const innerTaken = new Set(Object.keys(innerScope.selectedSources));\n const outerTaken = new Set(Object.keys(outerScope.selectedSources));\n const conflicts = new Set(Array.from(innerTaken).filter((x) => outerTaken.has(x)));\n conflicts.delete(alias);\n\n const taken = new Set([...outerTaken, ...innerTaken]);\n\n for (const conflict of conflicts) {\n const newName = findNewName(Array.from(taken), conflict);\n\n const sourceEntry = innerScope.selectedSources[conflict];\n if (!sourceEntry) {\n continue;\n }\n\n const [source] = sourceEntry;\n const newAlias = toIdentifier(newName);\n\n if (source instanceof TableExpr) {\n if (source.alias) {\n source.setArgKey('alias', newAlias);\n } else {\n source.replace(aliasExpr(source, newAlias, { copy: false }));\n }\n } else if (source?.parent instanceof SubqueryExpr) {\n source.parent.setArgKey('alias', new TableAliasExpr({ this: newAlias }));\n }\n\n for (const column of innerScope.sourceColumns(conflict)) {\n column.setArgKey('table', toIdentifier(newName));\n }\n\n innerScope.renameSource(conflict, newName);\n taken.add(newName);\n }\n}\n\nfunction mergeFrom (\n outerScope: Scope,\n innerScope: Scope,\n nodeToReplace: SubqueryExpr | TableExpr,\n alias: string,\n): void {\n const from = innerScope.expression.getArgKey('from') as FromExpr;\n const newSubquery = from.args.this as Expression;\n\n newSubquery.setArgKey('joins', nodeToReplace.args.joins);\n nodeToReplace.replace(newSubquery);\n\n // Update join hints\n for (const joinHint of outerScope.joinHints) {\n const tables = Array.from(joinHint.findAll(TableExpr));\n for (const table of tables) {\n if (table.aliasOrName === nodeToReplace.aliasOrName) {\n table.setArgKey('this', toIdentifier(newSubquery.aliasOrName));\n }\n }\n }\n\n outerScope.removeSource(alias);\n const newSubquerySource = innerScope.sources.get(newSubquery.aliasOrName);\n if (newSubquerySource !== undefined) {\n outerScope.addSource(newSubquery.aliasOrName, newSubquerySource);\n }\n}\n\nfunction mergeJoins (outerScope: Scope, innerScope: Scope, fromOrJoin: FromOrJoin): void {\n const newJoins: JoinExpr[] = [];\n\n const joins = innerScope.expression.getArgKey('joins') as JoinExpr[] | undefined;\n\n if (joins) {\n for (const join of joins) {\n newJoins.push(join);\n const joinSource = innerScope.sources.get(join.aliasOrName);\n if (joinSource) {\n outerScope.addSource(join.aliasOrName, joinSource);\n }\n }\n }\n\n if (0 < newJoins.length) {\n const outerJoins = (outerScope.expression.getArgKey('joins') as JoinExpr[] | undefined) || [];\n\n // Maintain join order\n let position: number;\n if (fromOrJoin instanceof FromExpr) {\n position = 0;\n } else {\n position = outerJoins.indexOf(fromOrJoin) + 1;\n }\n\n outerJoins.splice(position, 0, ...newJoins);\n (outerScope.expression as SelectExpr).setArgKey('joins', outerJoins);\n }\n}\n\nfunction mergeExpressions (outerScope: Scope, innerScope: Scope, alias: string): void {\n // Collect all columns that reference the alias of the inner query\n const outerColumns = new Map<string, ColumnExpr[]>();\n\n for (const column of outerScope.columns) {\n if (column.table === alias) {\n const name = column.name;\n let columnList = outerColumns.get(name);\n if (!columnList) {\n columnList = [];\n outerColumns.set(name, columnList);\n }\n columnList.push(column);\n }\n }\n\n // Replace columns with the projection expression in the inner query\n if (!(innerScope.expression instanceof SelectExpr)) {\n return;\n }\n\n const innerSelectExpr = innerScope.expression;\n\n for (const expr of innerSelectExpr.args.expressions ?? []) {\n if (!(expr instanceof Expression)) {\n continue;\n }\n\n const projectionName = expr.aliasOrName;\n\n if (!projectionName) {\n continue;\n }\n\n const columnsToReplace = outerColumns.get(projectionName) || [];\n\n const unaliasedExpr = expr.unalias();\n const mustWrapExpression = !SAFE_TO_REPLACE_UNWRAPPED.some((cls) => unaliasedExpr instanceof cls);\n const isNumber = unaliasedExpr.isNumber;\n\n for (const column of columnsToReplace) {\n const parent = column.parent;\n\n // Don't merge literal numbers in GROUP BY (positional context)\n if (isNumber && parent instanceof GroupExpr) {\n column.replace(toIdentifier(column.name));\n continue;\n }\n\n let replacementExpr = unaliasedExpr;\n\n // Wrap in parens if needed to preserve precedence\n if (\n parent\n && (parent instanceof UnaryExpr || parent instanceof BinaryExpr)\n && mustWrapExpression\n ) {\n replacementExpr = parenExpr(replacementExpr, { copy: false });\n }\n\n // Make sure we don't change the column name\n if (parent instanceof SelectExpr && column.name !== replacementExpr.name) {\n replacementExpr = aliasExpr(replacementExpr, column.name, { copy: false });\n }\n\n column.replace(replacementExpr.copy());\n }\n }\n}\n\nfunction mergeWhere (outerScope: Scope, innerScope: Scope, fromOrJoin: FromOrJoin): void {\n const where = innerScope.expression.getArgKey('where') as WhereExpr | undefined;\n\n if (!where) {\n return;\n }\n\n const whereThis = where.args.this;\n if (!(whereThis instanceof Expression)) {\n return;\n }\n\n const outerExpression = outerScope.expression as SelectExpr;\n\n if (fromOrJoin instanceof JoinExpr) {\n // Merge predicates from outer join to ON clause if columns are already joined\n const from = outerExpression.args.from;\n const sources = new Set<string>();\n\n if (from) {\n sources.add(from.aliasOrName);\n }\n\n const joins = outerExpression.args.joins;\n if (joins) {\n for (const join of joins) {\n const source = join.aliasOrName;\n sources.add(source);\n if (source === fromOrJoin.aliasOrName) {\n break;\n }\n }\n }\n\n const whereTables = columnTableNames(whereThis);\n const allTablesInSources = Array.from(whereTables).every((t) => sources.has(t));\n\n if (allTablesInSources) {\n fromOrJoin.on(whereThis, { copy: false });\n return;\n }\n }\n\n outerExpression.where(whereThis, { copy: false });\n}\n\nfunction mergeOrder (outerScope: Scope, innerScope: Scope): void {\n const outerSelectExpr = outerScope.expression as SelectExpr;\n\n if (\n outerSelectExpr.args.group\n || outerSelectExpr.args.distinct\n || outerSelectExpr.args.having\n || outerSelectExpr.args.order\n || Object.keys(outerScope.selectedSources).length !== 1\n ) {\n return;\n }\n\n const hasAgg = outerSelectExpr.args.expressions?.some((expr) => {\n if (!(expr instanceof Expression)) {\n return false;\n }\n return expr.find(AggFuncExpr) !== undefined;\n });\n\n if (hasAgg) {\n return;\n }\n\n outerSelectExpr.setArgKey('order', innerScope.expression.getArgKey('order'));\n}\n\nfunction mergeHints (outerScope: Scope, innerScope: Scope): void {\n const innerScopeHint = innerScope.expression.getArgKey('hint') as Expression | undefined;\n\n if (!innerScopeHint) {\n return;\n }\n\n const outerSelectExpr = outerScope.expression as SelectExpr;\n const outerScopeHint = outerSelectExpr.args.hint;\n\n if (outerScopeHint) {\n const innerHintExpressions = innerScopeHint.args.expressions;\n for (const hintExpression of innerHintExpressions ?? []) {\n if (hintExpression instanceof Expression) {\n outerScopeHint.append('expressions', hintExpression);\n }\n }\n } else {\n outerSelectExpr.setArgKey('hint', innerScopeHint);\n }\n}\n\nfunction popCte (innerScope: Scope): void {\n const cte = innerScope.expression.parent;\n if (!cte) {\n return;\n }\n\n const with_ = cte.parent;\n if (!with_) {\n return;\n }\n\n const withExpressions = with_.args.expressions;\n if (withExpressions?.length === 1) {\n with_.pop();\n } else {\n cte.pop();\n }\n}\n","import { cache } from '../port_internals';\nimport {\n Generator, unsupportedArgs,\n} from '../generator';\nimport type {\n GeneratedAsRowColumnConstraintExpr, PrimaryKeyColumnConstraintExpr, PrimaryKeyExpr,\n ArrayExpr,\n ArrayContainsAllExpr,\n DPipeExpr,\n ExtractExpr,\n TimestampTruncExpr,\n AtTimeZoneExpr,\n IsAsciiExpr,\n IgnoreNullsExpr,\n AlterRenameExpr,\n AlterColumnExpr,\n FuncExpr,\n} from '../expressions';\nimport {\n AlterIndexExpr, ColumnExpr, PartitionExpr, PartitionListExpr, PartitionRangeExpr,\n BitwiseAndAggExpr,\n BitwiseCountExpr,\n BitwiseOrAggExpr,\n BitwiseXorAggExpr,\n ConvertTimezoneExpr,\n CurrentDateExpr,\n DateTruncExpr,\n EqExpr,\n JsonArrayContainsExpr,\n ShowExpr,\n SoundexExpr,\n TimeToStrExpr,\n TsOrDsToDateExpr,\n Expression,\n StrToDateExpr,\n StrToTimeExpr,\n UnixToTimeExpr,\n CastExpr,\n MaxExpr,\n MinExpr,\n StrPositionExpr,\n DateAddExpr,\n DateSubExpr,\n DataTypeExpr,\n DataTypeExprKind,\n LiteralExpr,\n TryCastExpr,\n TableSampleExpr,\n PivotExpr,\n ILikeExpr,\n DivExpr,\n func,\n AndExpr,\n XorExpr,\n OrExpr,\n DayExpr,\n DayOfMonthExpr,\n DayOfWeekExpr,\n DayOfYearExpr,\n NumberToStrExpr,\n LengthExpr,\n TimeFromPartsExpr,\n MonthExpr,\n CurrentSchemaExpr,\n TimestampDiffExpr,\n DateDiffExpr,\n VarExpr,\n CurrentVersionExpr,\n WeekExpr,\n WeekOfYearExpr,\n YearExpr,\n AnonymousExpr,\n SetItemExpr,\n LockPropertyExpr,\n PartitionByRangePropertyExpr,\n PartitionByListPropertyExpr,\n SetItemExprKind,\n ZeroFillColumnConstraintExpr,\n GeneratedAsIdentityColumnConstraintExpr,\n ComputedColumnConstraintExpr,\n ColumnPrefixExpr,\n IndexColumnConstraintExpr,\n IndexConstraintOptionExpr,\n ArrayAggExpr,\n ChrExpr,\n IntervalExpr,\n PropertiesLocation,\n TransientPropertyExpr,\n VolatilePropertyExpr,\n PartitionedByPropertyExpr,\n SelectExpr,\n GroupConcatExpr,\n LogicalOrExpr,\n LogicalAndExpr,\n NullSafeEqExpr,\n NullSafeNeqExpr,\n JsonExtractScalarExpr,\n StuffExpr,\n SessionUserExpr,\n TimestampAddExpr,\n TimestampSubExpr,\n TimeStrToUnixExpr,\n TimeStrToTimeExpr,\n TrimExpr,\n TruncExpr,\n TsOrDsAddExpr,\n TsOrDsDiffExpr,\n UnicodeExpr,\n UtcTimestampExpr,\n UtcTimeExpr,\n DateStrToDateExpr,\n} from '../expressions';\nimport { type ExpressionMetadata } from '../typing';\nimport { MySQLTyping } from '../typing/mysql';\nimport type { TokenPair } from '../tokens';\nimport {\n Tokenizer, TokenType,\n} from '../tokens';\nimport { seqGet } from '../helper';\nimport { Parser } from '../parser';\nimport {\n eliminateDistinctOn, eliminateFullOuterJoin, eliminateQualify, eliminateSemiAndAntiJoins, preprocess, unnestGenerateDateArrayUsingRecursiveCte,\n} from '../transforms';\nimport {\n isnullToIsNull,\n lengthOrCharLengthSql,\n maxOrGreatest,\n minOrLeast,\n noIlikeSql,\n noParenCurrentDateSql,\n noPivotSql,\n noTablesampleSql,\n noTrycastSql,\n strPositionSql,\n trimSql,\n arrowJsonExtractSql,\n dateAddIntervalSql,\n dateStrToDateSql,\n timeStrToTimeSql,\n unitToVar,\n renameFunc,\n NormalizationStrategy,\n Dialect,\n buildDateDeltaWithInterval,\n Dialects,\n buildFormattedTime,\n buildDateDelta,\n NullOrderingSupported,\n} from './dialect';\n\n/**\n * A higher-order function that returns a parser for MySQL SHOW statements.\n * @param args - Arguments to pass to the internal MySQL SHOW parser.\n * @param kwargs - Keyword arguments to pass to the internal MySQL SHOW parser.\n * @returns A function that takes a MySQL Parser and returns a ShowExpr.\n */\nexport function showParser (\n thisArg: string,\n options: {\n target?: boolean | string;\n full?: boolean;\n global?: boolean;\n } = {},\n): (this: Parser) => ShowExpr {\n return function (this: Parser): ShowExpr {\n return (this as MySQLParser).parseShowMysql(thisArg, options);\n };\n}\n\n/**\n * Transpiles the DATE_TRUNC expression into MySQL-compatible SQL.\n * Since MySQL lacks a native DATE_TRUNC, this uses string concatenation and conversion.\n * @param self - The MySQL Generator instance.\n * @param expression - The DateTruncExpr node to transpile.\n * @returns The generated SQL string.\n */\nexport function dateTruncSql (this: Generator, expression: DateTruncExpr): string {\n const expr = this.sql(expression, 'this');\n const unit = expression.text('unit').toUpperCase();\n\n let concat: string;\n let dateFormat: string;\n\n if (unit === 'WEEK') {\n concat = `CONCAT(YEAR(${expr}), ' ', WEEK(${expr}, 1), ' 1')`;\n dateFormat = '%Y %u %w';\n } else if (unit === 'MONTH') {\n concat = `CONCAT(YEAR(${expr}), ' ', MONTH(${expr}), ' 1')`;\n dateFormat = '%Y %c %e';\n } else if (unit === 'QUARTER') {\n concat = `CONCAT(YEAR(${expr}), ' ', QUARTER(${expr}) * 3 - 2, ' 1')`;\n dateFormat = '%Y %c %e';\n } else if (unit === 'YEAR') {\n concat = `CONCAT(YEAR(${expr}), ' 1 1')`;\n dateFormat = '%Y %c %e';\n } else {\n if (unit !== 'DAY') {\n this.unsupported(`Unexpected interval unit: ${unit}`);\n }\n return this.func('DATE', [expr]);\n }\n\n return this.func('STR_TO_DATE', [concat, `'${dateFormat}'`]);\n}\n\n/**\n * All specifiers for time parts (as opposed to date parts)\n * Reference: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format\n */\nexport const TIME_SPECIFIERS = new Set([\n 'f',\n 'H',\n 'h',\n 'I',\n 'i',\n 'k',\n 'l',\n 'p',\n 'r',\n 'S',\n 's',\n 'T',\n]);\n\n/**\n * Checks if a MySQL date format string contains any time-specific specifiers.\n */\nfunction hasTimeSpecifier (dateFormat: string): boolean {\n let i = 0;\n const length = dateFormat.length;\n\n while (i < length) {\n if (dateFormat[i] === '%') {\n i += 1;\n if (i < length && TIME_SPECIFIERS.has(dateFormat[i])) {\n return true;\n }\n }\n i += 1;\n }\n return false;\n}\n\n/**\n * Parser builder for STR_TO_DATE. Decides whether to return a StrToDateExpr or StrToTimeExpr\n * based on the presence of time specifiers in the format string.\n */\nexport function buildStrToDate (args: [Expression, ...(string | Expression | undefined)[]]): StrToDateExpr | StrToTimeExpr {\n const mysqlDateFormat = seqGet(args, 1);\n const dateFormat = MySQL.formatTime(mysqlDateFormat);\n const thisArg = seqGet(args, 0) as Expression;\n\n if (mysqlDateFormat && hasTimeSpecifier(mysqlDateFormat instanceof Expression ? mysqlDateFormat.name : mysqlDateFormat)) {\n return new StrToTimeExpr({\n this: thisArg,\n format: dateFormat || '',\n });\n }\n\n return new StrToDateExpr({\n this: thisArg,\n format: dateFormat,\n });\n}\n\n/**\n * Generator for STR_TO_DATE.\n */\nexport function strToDateSql (\n this: Generator,\n expression: StrToDateExpr | StrToTimeExpr | TsOrDsToDateExpr,\n): string {\n return this.func('STR_TO_DATE', [expression.args.this, this.formatTime(expression)]);\n}\n\n/**\n * Generator for UNIX_TO_TIME. Handles optional scale for sub-second precision.\n */\nexport function unixToTimeSql (this: Generator, expression: UnixToTimeExpr): string {\n const scale = expression.args.scale;\n const timestamp = expression.args.this;\n\n if (scale === undefined || scale.toValue() === UnixToTimeExpr.SECONDS.toValue()) {\n return this.func('FROM_UNIXTIME', [timestamp, this.formatTime(expression)]);\n }\n\n return this.func(\n 'FROM_UNIXTIME',\n [\n new DivExpr({\n this: timestamp,\n expression: func('POW', '10', scale.toString()),\n }),\n this.formatTime(expression),\n ],\n );\n}\n\n/**\n * Higher-order function to generate MySQL DATE_ADD or DATE_SUB calls.\n * @param kind - Either 'ADD' or 'SUB'.\n * @returns A function that generates the MySQL-specific DATE logic.\n */\nexport function dateAddSql (kind: string): (this: Generator, expression: DateAddExpr) => string {\n return function (this: Generator, expression: DateAddExpr): string {\n return this.func(\n `DATE_${kind}`,\n [\n expression.args.this,\n new IntervalExpr({\n this: expression.args.expression,\n unit: unitToVar(expression),\n }),\n ],\n );\n };\n}\n\n/**\n * Handles converting a Timestamp or DateString to a Date in MySQL.\n * @param self - The MySQL Generator.\n * @param expression - The TsOrDsToDateExpr node.\n */\nexport function tsOrDsToDateSql (this: Generator, expression: TsOrDsToDateExpr): string {\n const timeFormat = expression.args.format;\n return timeFormat\n ? strToDateSql.call(this, expression)\n : this.func('DATE', [expression.args.this]);\n}\n\n/**\n * Optimization that removes redundant TsOrDsToDate wrappers when the parent\n * function can handle raw types.\n */\nexport function removeTsOrDsToDate<T extends FuncExpr> (\n toSql?: (this: Generator, expression: T) => string,\n args: string[] = ['this'],\n): (this: Generator, expression: T) => string {\n return function (this: Generator, expression: T): string {\n for (const argKey of args) {\n const arg = expression.getArgKey(argKey);\n if (arg instanceof TsOrDsToDateExpr && !arg.args.format) {\n expression.setArgKey(argKey, arg.args.this);\n }\n }\n\n return toSql ? toSql.call(this, expression) : this.functionFallbackSql(expression);\n };\n}\n\nclass MySQLTokenizer extends Tokenizer {\n static QUOTES: TokenPair[] = ['\\'', '\"'];\n static COMMENTS: TokenPair[] = [\n '--',\n '#',\n ['/*', '*/'],\n ];\n\n static IDENTIFIERS: TokenPair[] = ['`'];\n static STRING_ESCAPES: string[] = [\n '\\'',\n '\"',\n '\\\\',\n ];\n\n static BIT_STRINGS: TokenPair[] = [\n ['b\\'', '\\''],\n ['B\\'', '\\''],\n ['0b', ''],\n ];\n\n static HEX_STRINGS: TokenPair[] = [\n ['x\\'', '\\''],\n ['X\\'', '\\''],\n ['0x', ''],\n ];\n\n /**\n * Special characters that are recognized after an escape character (\\) in MySQL.\n * Reference: https://dev.mysql.com/doc/refman/8.4/en/string-literals.html\n */\n public ESCAPE_FOLLOW_CHARS = [\n '0',\n 'b',\n 'n',\n 'r',\n 't',\n 'Z',\n '%',\n '_',\n ];\n\n public NESTED_COMMENTS = false;\n\n @cache\n static get ORIGINAL_KEYWORDS (): Record<string, TokenType> {\n return {\n ...Tokenizer.KEYWORDS,\n 'BLOB': TokenType.BLOB,\n 'CHARSET': TokenType.CHARACTER_SET,\n 'DISTINCTROW': TokenType.DISTINCT,\n 'EXPLAIN': TokenType.DESCRIBE,\n 'FORCE': TokenType.FORCE,\n 'IGNORE': TokenType.IGNORE,\n 'KEY': TokenType.KEY,\n 'LOCK TABLES': TokenType.COMMAND,\n 'LONGBLOB': TokenType.LONGBLOB,\n 'LONGTEXT': TokenType.LONGTEXT,\n 'MEDIUMBLOB': TokenType.MEDIUMBLOB,\n 'MEDIUMINT': TokenType.MEDIUMINT,\n 'MEDIUMTEXT': TokenType.MEDIUMTEXT,\n 'MEMBER OF': TokenType.MEMBER_OF,\n 'MOD': TokenType.MOD,\n 'SEPARATOR': TokenType.SEPARATOR,\n 'SERIAL': TokenType.SERIAL,\n 'SIGNED': TokenType.BIGINT,\n 'SIGNED INTEGER': TokenType.BIGINT,\n 'SOUNDS LIKE': TokenType.SOUNDS_LIKE,\n 'START': TokenType.BEGIN,\n 'TIMESTAMP': TokenType.TIMESTAMPTZ,\n 'TINYBLOB': TokenType.TINYBLOB,\n 'TINYTEXT': TokenType.TINYTEXT,\n 'UNLOCK TABLES': TokenType.COMMAND,\n 'UNSIGNED': TokenType.UBIGINT,\n 'UNSIGNED INTEGER': TokenType.UBIGINT,\n 'YEAR': TokenType.YEAR,\n '_ARMSCII8': TokenType.INTRODUCER,\n '_ASCII': TokenType.INTRODUCER,\n '_BIG5': TokenType.INTRODUCER,\n '_BINARY': TokenType.INTRODUCER,\n '_CP1250': TokenType.INTRODUCER,\n '_CP1251': TokenType.INTRODUCER,\n '_CP1256': TokenType.INTRODUCER,\n '_CP1257': TokenType.INTRODUCER,\n '_CP850': TokenType.INTRODUCER,\n '_CP852': TokenType.INTRODUCER,\n '_CP866': TokenType.INTRODUCER,\n '_CP932': TokenType.INTRODUCER,\n '_DEC8': TokenType.INTRODUCER,\n '_EUCJPMS': TokenType.INTRODUCER,\n '_EUCKR': TokenType.INTRODUCER,\n '_GB18030': TokenType.INTRODUCER,\n '_GB2312': TokenType.INTRODUCER,\n '_GBK': TokenType.INTRODUCER,\n '_GEOSTD8': TokenType.INTRODUCER,\n '_GREEK': TokenType.INTRODUCER,\n '_HEBREW': TokenType.INTRODUCER,\n '_HP8': TokenType.INTRODUCER,\n '_KEYBCS2': TokenType.INTRODUCER,\n '_KOI8R': TokenType.INTRODUCER,\n '_KOI8U': TokenType.INTRODUCER,\n '_LATIN1': TokenType.INTRODUCER,\n '_LATIN2': TokenType.INTRODUCER,\n '_LATIN5': TokenType.INTRODUCER,\n '_LATIN7': TokenType.INTRODUCER,\n '_MACCE': TokenType.INTRODUCER,\n '_MACROMAN': TokenType.INTRODUCER,\n '_SJIS': TokenType.INTRODUCER,\n '_SWE7': TokenType.INTRODUCER,\n '_TIS620': TokenType.INTRODUCER,\n '_UCS2': TokenType.INTRODUCER,\n '_UJIS': TokenType.INTRODUCER,\n '_UTF8': TokenType.INTRODUCER,\n '_UTF16': TokenType.INTRODUCER,\n '_UTF16LE': TokenType.INTRODUCER,\n '_UTF32': TokenType.INTRODUCER,\n '_UTF8MB3': TokenType.INTRODUCER,\n '_UTF8MB4': TokenType.INTRODUCER,\n '@@': TokenType.SESSION_PARAMETER,\n };\n }\n\n @cache\n static get COMMANDS () {\n const commands = new Set([...Tokenizer.COMMANDS, TokenType.REPLACE]);\n commands.delete(TokenType.SHOW);\n return commands;\n };\n};\n\nclass MySQLParser extends Parser {\n // port from _Dialect metaclass logic\n @cache\n static get ID_VAR_TOKENS (): Set<TokenType> {\n return new Set([...Parser.ID_VAR_TOKENS, TokenType.CURRENT_CATALOG]);\n }\n\n @cache\n static get FUNC_TOKENS (): Set<TokenType> {\n return new Set([\n ...Parser.FUNC_TOKENS,\n TokenType.DATABASE,\n TokenType.MOD,\n TokenType.SCHEMA,\n TokenType.VALUES,\n TokenType.CHARACTER_SET,\n ]);\n }\n\n @cache\n static get CONJUNCTION (): Partial<Record<TokenType, typeof Expression>> {\n return {\n ...Parser.CONJUNCTION,\n [TokenType.DAMP]: AndExpr,\n [TokenType.XOR]: XorExpr,\n };\n }\n\n @cache\n static get DISJUNCTION (): Partial<Record<TokenType, typeof Expression>> {\n return {\n ...Parser.DISJUNCTION,\n [TokenType.DPIPE]: OrExpr,\n };\n }\n\n @cache\n static get TABLE_ALIAS_TOKENS (): Set<TokenType> {\n return (() => {\n const tokens = new Set(Parser.TABLE_ALIAS_TOKENS);\n for (const hint of Parser.TABLE_INDEX_HINT_TOKENS) {\n tokens.delete(hint);\n }\n return tokens;\n })();\n }\n\n @cache\n static get RANGE_PARSERS (): Partial<Record<TokenType, (this: Parser, this_: Expression) => Expression | undefined>> {\n return {\n ...Parser.RANGE_PARSERS,\n [TokenType.SOUNDS_LIKE]: function (this: Parser, thisArg: Expression): EqExpr {\n return this.expression(EqExpr, {\n this: this.expression(SoundexExpr, { this: thisArg }),\n expression: this.expression(SoundexExpr, { this: this.parseTerm() }),\n });\n },\n [TokenType.MEMBER_OF]: function (this: Parser, thisArg: Expression): JsonArrayContainsExpr {\n return this.expression(JsonArrayContainsExpr, {\n this: thisArg,\n expression: this.parseWrapped(this.parseExpression.bind(this)),\n });\n },\n };\n }\n\n @cache\n static get FUNCTIONS (): typeof Parser.FUNCTIONS {\n return {\n ...Parser.FUNCTIONS,\n BIT_AND: (args: unknown[]) => BitwiseAndAggExpr.fromArgList(args),\n BIT_OR: (args: unknown[]) => BitwiseOrAggExpr.fromArgList(args),\n BIT_XOR: (args: unknown[]) => BitwiseXorAggExpr.fromArgList(args),\n BIT_COUNT: (args: unknown[]) => BitwiseCountExpr.fromArgList(args),\n CONVERT_TZ: (args: Expression[]): ConvertTimezoneExpr =>\n new ConvertTimezoneExpr({\n sourceTz: seqGet(args, 1),\n targetTz: seqGet(args, 2),\n timestamp: seqGet(args, 0),\n }),\n CURDATE: (args: unknown[]) => CurrentDateExpr.fromArgList(args),\n DATE: (args: Expression[]): TsOrDsToDateExpr =>\n new TsOrDsToDateExpr({ this: seqGet(args, 0) }),\n DATE_ADD: (args: Expression[]) => buildDateDeltaWithInterval(DateAddExpr)(args),\n DATE_FORMAT: buildFormattedTime(TimeToStrExpr, { dialect: Dialects.MYSQL }),\n DATE_SUB: (args: Expression[]) => buildDateDeltaWithInterval(DateSubExpr)(args),\n DAY: (args: Expression[]): DayExpr =>\n new DayExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n DAYOFMONTH: (args: Expression[]): DayOfMonthExpr =>\n new DayOfMonthExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n DAYOFWEEK: (args: Expression[]): DayOfWeekExpr =>\n new DayOfWeekExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n DAYOFYEAR: (args: Expression[]): DayOfYearExpr =>\n new DayOfYearExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n FORMAT: (args: unknown[]) => NumberToStrExpr.fromArgList(args),\n FROM_UNIXTIME: buildFormattedTime(UnixToTimeExpr, { dialect: Dialects.MYSQL }),\n ISNULL: isnullToIsNull,\n LENGTH: (args: Expression[]): LengthExpr =>\n new LengthExpr({\n this: seqGet(args, 0),\n binary: true,\n }),\n MAKETIME: (args: unknown[]) => TimeFromPartsExpr.fromArgList(args),\n MONTH: (args: Expression[]): MonthExpr =>\n new MonthExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n MONTHNAME: (args: Expression[]): TimeToStrExpr =>\n new TimeToStrExpr({\n this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }),\n format: new LiteralExpr({\n this: '%B',\n isString: true,\n }),\n }),\n SCHEMA: (args: unknown[]) => CurrentSchemaExpr.fromArgList(args),\n DATABASE: (args: unknown[]) => CurrentSchemaExpr.fromArgList(args),\n STR_TO_DATE: buildStrToDate as (args: Expression[]) => Expression,\n TIMESTAMPDIFF: buildDateDelta(TimestampDiffExpr),\n TO_DAYS: (args: Expression[]) => {\n const diff = new DateDiffExpr({\n this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }),\n expression: new TsOrDsToDateExpr({\n this: new LiteralExpr({\n this: '0000-01-01',\n isString: true,\n }),\n }),\n unit: new VarExpr({ this: 'DAY' }),\n });\n return diff.add(1); // Standardized parenthesized expression with offset\n },\n VERSION: (args: unknown[]) => CurrentVersionExpr.fromArgList(args),\n WEEK: (args: Expression[]): WeekExpr =>\n new WeekExpr({\n this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }),\n mode: seqGet(args, 1),\n }),\n WEEKOFYEAR: (args: Expression[]): WeekOfYearExpr =>\n new WeekOfYearExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n YEAR: (args: Expression[]): YearExpr =>\n new YearExpr({ this: new TsOrDsToDateExpr({ this: seqGet(args, 0) }) }),\n };\n }\n\n @cache\n static get FUNCTION_PARSERS (): Partial<Record<string, (this: Parser) => Expression | undefined>> {\n return {\n ...Parser.FUNCTION_PARSERS,\n GROUP_CONCAT: function (this: Parser) {\n return this.parseGroupConcat();\n },\n VALUES: function (this: Parser): AnonymousExpr {\n return this.expression(AnonymousExpr, {\n this: 'VALUES',\n expressions: [this.parseIdVar()],\n });\n },\n JSON_VALUE: function (this: Parser): Expression {\n return this.parseJsonValue();\n },\n SUBSTR: function (this: Parser): Expression {\n return this.parseSubstring();\n },\n };\n }\n\n @cache\n static get STATEMENT_PARSERS (): Partial<Record<TokenType, (this: Parser) => Expression | undefined>> {\n return {\n ...Parser.STATEMENT_PARSERS,\n [TokenType.SHOW]: function (this: Parser) {\n return this.parseShow();\n },\n };\n }\n\n static SHOW_PARSERS: typeof Parser.SHOW_PARSERS = ({\n 'BINARY LOGS': showParser('BINARY LOGS'),\n 'MASTER LOGS': showParser('BINARY LOGS'),\n 'BINLOG EVENTS': showParser('BINLOG EVENTS'),\n 'CHARACTER SET': showParser('CHARACTER SET'),\n 'CHARSET': showParser('CHARACTER SET'),\n 'COLLATION': showParser('COLLATION'),\n 'FULL COLUMNS': showParser('COLUMNS', {\n target: 'FROM',\n full: true,\n }),\n 'COLUMNS': showParser('COLUMNS', { target: 'FROM' }),\n 'CREATE DATABASE': showParser('CREATE DATABASE', { target: true }),\n 'CREATE EVENT': showParser('CREATE EVENT', { target: true }),\n 'CREATE FUNCTION': showParser('CREATE FUNCTION', { target: true }),\n 'CREATE PROCEDURE': showParser('CREATE PROCEDURE', { target: true }),\n 'CREATE TABLE': showParser('CREATE TABLE', { target: true }),\n 'CREATE TRIGGER': showParser('CREATE TRIGGER', { target: true }),\n 'CREATE VIEW': showParser('CREATE VIEW', { target: true }),\n 'DATABASES': showParser('DATABASES'),\n 'SCHEMAS': showParser('DATABASES'),\n 'ENGINE': showParser('ENGINE', { target: true }),\n 'STORAGE ENGINES': showParser('ENGINES'),\n 'ENGINES': showParser('ENGINES'),\n 'ERRORS': showParser('ERRORS'),\n 'EVENTS': showParser('EVENTS'),\n 'FUNCTION CODE': showParser('FUNCTION CODE', { target: true }),\n 'FUNCTION STATUS': showParser('FUNCTION STATUS'),\n 'GRANTS': showParser('GRANTS', { target: 'FOR' }),\n 'INDEX': showParser('INDEX', { target: 'FROM' }),\n 'MASTER STATUS': showParser('MASTER STATUS'),\n 'OPEN TABLES': showParser('OPEN TABLES'),\n 'PLUGINS': showParser('PLUGINS'),\n 'PROCEDURE CODE': showParser('PROCEDURE CODE', { target: true }),\n 'PROCEDURE STATUS': showParser('PROCEDURE STATUS'),\n 'PRIVILEGES': showParser('PRIVILEGES'),\n 'FULL PROCESSLIST': showParser('PROCESSLIST', { full: true }),\n 'PROCESSLIST': showParser('PROCESSLIST'),\n 'PROFILE': showParser('PROFILE'),\n 'PROFILES': showParser('PROFILES'),\n 'RELAYLOG EVENTS': showParser('RELAYLOG EVENTS'),\n 'REPLICAS': showParser('REPLICAS'),\n 'SLAVE HOSTS': showParser('REPLICAS'),\n 'REPLICA STATUS': showParser('REPLICA STATUS'),\n 'SLAVE STATUS': showParser('REPLICA STATUS'),\n 'GLOBAL STATUS': showParser('STATUS', { global: true }),\n 'SESSION STATUS': showParser('STATUS'),\n 'STATUS': showParser('STATUS'),\n 'TABLE STATUS': showParser('TABLE STATUS'),\n 'FULL TABLES': showParser('TABLES', { full: true }),\n 'TABLES': showParser('TABLES'),\n 'TRIGGERS': showParser('TRIGGERS'),\n 'GLOBAL VARIABLES': showParser('VARIABLES', { global: true }),\n 'SESSION VARIABLES': showParser('VARIABLES'),\n 'VARIABLES': showParser('VARIABLES'),\n 'WARNINGS': showParser('WARNINGS'),\n });\n\n @cache\n static get PROPERTY_PARSERS (): Record<string, (this: Parser, ...args: unknown[]) => Expression | Expression[] | undefined> {\n return {\n ...Parser.PROPERTY_PARSERS,\n 'LOCK': function (this: Parser) {\n return this.parsePropertyAssignment(LockPropertyExpr);\n },\n 'PARTITION BY': function (this: Parser) {\n return (this as MySQLParser).parsePartitionProperty();\n },\n };\n }\n\n @cache\n static get SET_PARSERS (): Record<string, (this: Parser) => Expression | undefined> {\n return {\n ...Parser.SET_PARSERS,\n 'PERSIST': function (this: Parser) {\n return (this as MySQLParser).parseSetItemAssignment({ kind: SetItemExprKind.PERSIST });\n },\n 'PERSIST_ONLY': function (this: Parser) {\n return this.parseSetItemAssignment({ kind: 'PERSIST_ONLY' });\n },\n 'CHARACTER SET': function (this: Parser): Expression {\n return (this as MySQLParser).parseSetItemCharset('CHARACTER SET');\n },\n 'CHARSET': function (this: Parser): Expression {\n return (this as MySQLParser).parseSetItemCharset('CHARACTER SET');\n },\n 'NAMES': function (this: Parser) {\n return (this as MySQLParser).parseSetItemNames();\n },\n };\n }\n\n @cache\n static get CONSTRAINT_PARSERS (): Partial<Record<string, (this: Parser, ...args: unknown[]) => Expression | Expression[] | undefined>> {\n return {\n ...Parser.CONSTRAINT_PARSERS,\n FULLTEXT: function (this: Parser) {\n return (this as MySQLParser).parseIndexConstraint('FULLTEXT');\n },\n INDEX: function (this: Parser) {\n return (this as MySQLParser).parseIndexConstraint();\n },\n KEY: function (this: Parser) {\n return (this as MySQLParser).parseIndexConstraint();\n },\n SPATIAL: function (this: Parser) {\n return (this as MySQLParser).parseIndexConstraint('SPATIAL');\n },\n ZEROFILL: function (this: Parser) {\n return this.expression(ZeroFillColumnConstraintExpr);\n },\n };\n }\n\n @cache\n static get ALTER_PARSERS (): Partial<Record<string, (this: Parser) => Expression | Expression[] | undefined>> {\n return {\n ...Parser.ALTER_PARSERS,\n MODIFY: function (this: Parser) {\n return this.parseAlterTableAlter();\n },\n };\n }\n\n @cache\n static get ALTER_ALTER_PARSERS (): Partial<Record<string, (this: Parser) => Expression>> {\n return {\n ...Parser.ALTER_ALTER_PARSERS,\n INDEX: function (this: Parser) {\n return (this as MySQLParser).parseAlterTableAlterIndex();\n },\n };\n }\n\n @cache\n static get SCHEMA_UNNAMED_CONSTRAINTS (): Set<string> {\n return new Set([\n ...Parser.SCHEMA_UNNAMED_CONSTRAINTS,\n 'FULLTEXT',\n 'INDEX',\n 'KEY',\n 'SPATIAL',\n ]);\n }\n\n @cache\n static get PROFILE_TYPES () {\n return {\n ALL: [],\n CPU: [],\n IPC: [],\n MEMORY: [],\n SOURCE: [],\n SWAPS: [],\n BLOCK: ['IO'],\n CONTEXT: ['SWITCHES'],\n PAGE: ['FAULTS'],\n };\n }\n\n @cache\n static get TYPE_TOKENS (): Set<TokenType> {\n return new Set([...Parser.TYPE_TOKENS, TokenType.SET]);\n }\n\n @cache\n static get ENUM_TYPE_TOKENS (): Set<TokenType> {\n return new Set([...Parser.ENUM_TYPE_TOKENS, TokenType.SET]);\n }\n\n /**\n * Modifiers that can appear in a MySQL SELECT statement.\n */\n @cache\n static get OPERATION_MODIFIERS (): Set<string> {\n return new Set([\n 'HIGH_PRIORITY',\n 'STRAIGHT_JOIN',\n 'SQL_SMALL_RESULT',\n 'SQL_BIG_RESULT',\n 'SQL_BUFFER_RESULT',\n 'SQL_NO_CACHE',\n 'SQL_CALC_FOUND_ROWS',\n ]);\n }\n\n static LOG_DEFAULTS_TO_LN = true;\n static STRING_ALIASES = true;\n static VALUES_FOLLOWED_BY_PAREN = false;\n static SUPPORTS_PARTITION_SELECTION = true;\n\n /**\n * Handles MySQL's GENERATED ALWAYS AS logic, including VIRTUAL vs STORED persistence.\n */\n public parseGeneratedAsIdentity ():\n | GeneratedAsIdentityColumnConstraintExpr\n | ComputedColumnConstraintExpr\n | GeneratedAsRowColumnConstraintExpr {\n let thisExpr = super.parseGeneratedAsIdentity();\n\n if (this.matchTexts(['STORED', 'VIRTUAL'])) {\n const persisted = this.prev?.text.toUpperCase() === 'STORED';\n\n if (thisExpr instanceof ComputedColumnConstraintExpr) {\n thisExpr.setArgKey('persisted', persisted);\n } else if (thisExpr instanceof GeneratedAsIdentityColumnConstraintExpr) {\n // Pivot to a ComputedColumnConstraint if persistence is explicitly specified\n thisExpr = this.expression(ComputedColumnConstraintExpr, {\n this: thisExpr.args.expression,\n persisted: persisted,\n });\n }\n }\n\n return thisExpr;\n }\n\n /**\n * Parses MySQL-specific primary key parts which allow column prefixes (e.g. KEY(col(10))).\n */\n public parsePrimaryKeyPart (): Expression | undefined {\n const thisExpr = this.parseIdVar();\n if (!this.match(TokenType.L_PAREN)) {\n return thisExpr;\n }\n\n const expression = this.parseNumber();\n this.matchRParen();\n\n return this.expression(ColumnPrefixExpr, {\n this: thisExpr,\n expression: expression,\n });\n }\n\n /**\n * Parses MySQL index constraints, including support for KEY_BLOCK_SIZE,\n * custom parsers, and visibility toggles.\n */\n protected parseIndexConstraint (kind?: string): IndexColumnConstraintExpr {\n if (kind) {\n this.matchTexts(['INDEX', 'KEY']);\n }\n\n const thisExpr = this.parseIdVar({ anyToken: false });\n const indexType = this.match(TokenType.USING) && this.advanceAny() && this.prev?.text;\n const expressions = this.parseWrappedCsv(this.parseOrdered.bind(this));\n\n const options: IndexConstraintOptionExpr[] = [];\n while (true) {\n let opt: IndexConstraintOptionExpr | undefined = undefined;\n\n if (this.matchTextSeq(['KEY_BLOCK_SIZE'])) {\n this.match(TokenType.EQ);\n opt = new IndexConstraintOptionExpr({ keyBlockSize: this.parseNumber() });\n } else if (this.match(TokenType.USING)) {\n opt = new IndexConstraintOptionExpr({ using: this.advanceAny() && this.prev?.text });\n } else if (this.matchTextSeq(['WITH', 'PARSER'])) {\n opt = new IndexConstraintOptionExpr({ parser: this.parseVar({ anyToken: true }) });\n } else if (this.match(TokenType.COMMENT)) {\n opt = new IndexConstraintOptionExpr({ comment: this.parseString() });\n } else if (this.matchTextSeq(['VISIBLE'])) {\n opt = new IndexConstraintOptionExpr({ visible: true });\n } else if (this.matchTextSeq(['INVISIBLE'])) {\n opt = new IndexConstraintOptionExpr({ visible: false });\n } else if (this.matchTextSeq(['ENGINE_ATTRIBUTE'])) {\n this.match(TokenType.EQ);\n opt = new IndexConstraintOptionExpr({ engineAttr: this.parseString() });\n } else if (this.matchTextSeq(['SECONDARY_ENGINE_ATTRIBUTE'])) {\n this.match(TokenType.EQ);\n opt = new IndexConstraintOptionExpr({ secondaryEngineAttr: this.parseString() });\n }\n\n if (!opt) break;\n options.push(opt);\n }\n\n return this.expression(IndexColumnConstraintExpr, {\n this: thisExpr,\n expressions: expressions,\n kind: kind,\n indexType: indexType,\n options: options,\n });\n }\n\n /**\n * Core internal parser for the varied MySQL SHOW statement variants.\n */\n public parseShowMysql (\n thisArg: string,\n options: {\n target?: boolean | string;\n full?: boolean;\n global?: boolean;\n } = {},\n ): ShowExpr {\n const {\n target = false,\n full,\n global,\n } = options;\n const json = this.matchTextSeq(['JSON']);\n let targetId: Expression | undefined = undefined;\n\n if (target) {\n if (typeof target === 'string') {\n this.matchTextSeq(target.split(' '));\n }\n targetId = this.parseIdVar();\n }\n\n const log = this.matchTextSeq(['IN']) ? this.parseString() : undefined;\n let position: Expression | undefined = undefined;\n let db: Expression | undefined = undefined;\n\n if (thisArg === 'BINLOG EVENTS' || thisArg === 'RELAYLOG EVENTS') {\n position = this.matchTextSeq(['FROM']) ? this.parseNumber() : undefined;\n } else {\n if (this.match(TokenType.FROM)) {\n db = this.parseIdVar();\n } else if (this.match(TokenType.DOT)) {\n db = targetId;\n targetId = this.parseIdVar();\n }\n }\n\n const channel = this.matchTextSeq(['FOR', 'CHANNEL']) ? this.parseIdVar() : undefined;\n const like = this.matchTextSeq(['LIKE']) ? this.parseString() : undefined;\n const where = this.parseWhere();\n\n let types: Expression[] | undefined = undefined;\n let query: Expression | undefined = undefined;\n let offset: Expression | undefined = undefined;\n let limit: Expression | undefined = undefined;\n\n if (thisArg === 'PROFILE') {\n types = this.parseCsv(() => this.parseVarFromOptions(MySQLParser.PROFILE_TYPES));\n query = this.matchTextSeq(['FOR', 'QUERY']) ? this.parseNumber() : undefined;\n offset = this.matchTextSeq(['OFFSET']) ? this.parseNumber() : undefined;\n limit = this.matchTextSeq(['LIMIT']) ? this.parseNumber() : undefined;\n } else {\n [offset, limit] = this.parseOldstyleLimit();\n }\n\n let mutex: boolean | undefined = undefined;\n if (this.matchTextSeq(['MUTEX'])) mutex = true;\n if (this.matchTextSeq(['STATUS'])) mutex = false;\n\n const forTable = this.matchTextSeq(['FOR', 'TABLE']) ? this.parseIdVar() : undefined;\n const forGroup = this.matchTextSeq(['FOR', 'GROUP']) ? this.parseString() : undefined;\n const forUser = this.matchTextSeq(['FOR', 'USER']) ? this.parseString() : undefined;\n const forRole = this.matchTextSeq(['FOR', 'ROLE']) ? this.parseString() : undefined;\n const intoOutfile = this.matchTextSeq(['INTO', 'OUTFILE']) ? this.parseString() : undefined;\n\n return this.expression(ShowExpr, {\n this: thisArg,\n target: targetId,\n full: full,\n log: log,\n position: position,\n db: db,\n channel: channel,\n like: like,\n where: where,\n types: types,\n query: query,\n offset: offset,\n limit: limit,\n mutex: mutex,\n forTable: forTable,\n forGroup: forGroup,\n forUser: forUser,\n forRole: forRole,\n intoOutfile: intoOutfile,\n json: json,\n global: global,\n });\n }\n\n protected parseOldstyleLimit (): [Expression | undefined, Expression | undefined] {\n let limit: Expression | undefined = undefined;\n let offset: Expression | undefined = undefined;\n\n if (this.matchTextSeq(['LIMIT'])) {\n const parts = this.parseCsv(this.parseNumber.bind(this));\n if (parts.length === 1) {\n limit = parts[0];\n } else if (parts.length === 2) {\n limit = parts[1];\n offset = parts[0];\n }\n }\n\n return [offset, limit];\n }\n\n protected parseSetItemCharset (kind: string): Expression {\n const thisExpr = this.parseString() || this.parseUnquotedField();\n return this.expression(SetItemExpr, {\n this: thisExpr,\n kind: kind,\n });\n }\n\n protected parseSetItemNames (): Expression {\n const charset = this.parseString() || this.parseUnquotedField();\n let collate: Expression | undefined = undefined;\n\n if (this.matchTextSeq(['COLLATE'])) {\n collate = this.parseString() || this.parseUnquotedField();\n }\n\n return this.expression(SetItemExpr, {\n this: charset,\n collate: collate,\n kind: 'NAMES',\n });\n }\n\n /**\n * Overrides core type parsing to handle MySQL's unique 'BINARY' modifier\n * which can act as a cast without parentheses.\n */\n public parseType (options: {\n parseInterval?: boolean;\n fallbackToIdentifier?: boolean;\n } = {}): Expression | undefined {\n const {\n parseInterval = true,\n fallbackToIdentifier = false,\n } = options;\n if (this.match(TokenType.BINARY, { advance: false })) {\n const dataType = this.parseTypes({\n checkFunc: true,\n allowIdentifiers: false,\n });\n\n if (dataType instanceof DataTypeExpr) {\n return this.expression(CastExpr, {\n this: this.parseColumn(),\n to: dataType,\n });\n }\n }\n\n return super.parseType({\n parseInterval,\n fallbackToIdentifier,\n });\n }\n\n protected parseAlterTableAlterIndex (): AlterIndexExpr {\n const index = this.parseField({ anyToken: true });\n let visible: boolean | undefined = undefined;\n\n if (this.matchTextSeq(['VISIBLE'])) {\n visible = true;\n } else if (this.matchTextSeq(['INVISIBLE'])) {\n visible = false;\n }\n\n return this.expression(AlterIndexExpr, {\n this: index,\n visible: visible,\n });\n }\n\n /**\n * Parses MySQL partitioning properties for RANGE and LIST schemes.\n */\n protected parsePartitionProperty (): Expression | Expression[] | undefined {\n let partitionCls: typeof Expression | undefined = undefined;\n let valueParser: (() => Expression | undefined) | undefined = undefined;\n\n if (this.matchTextSeq(['RANGE'])) {\n partitionCls = PartitionByRangePropertyExpr;\n valueParser = this.parsePartitionRangeValue.bind(this);\n } else if (this.matchTextSeq(['LIST'])) {\n partitionCls = PartitionByListPropertyExpr;\n valueParser = this.parsePartitionListValue.bind(this);\n }\n\n if (!partitionCls || !valueParser) {\n return undefined;\n }\n\n const partitionExpressions = this.parseWrappedCsv(this.parseAssignment.bind(this));\n\n // For Doris and Starrocks compatibility check\n if (!this.matchTextSeq(['(', 'PARTITION'], { advance: false })) {\n return partitionExpressions;\n }\n\n const createExpressions = this.parseWrappedCsv(valueParser);\n\n return this.expression(partitionCls, {\n partitionExpressions: partitionExpressions,\n createExpressions: createExpressions,\n });\n }\n\n protected parsePartitionRangeValue (): Expression | undefined {\n this.matchTextSeq(['PARTITION']);\n const name = this.parseIdVar();\n\n if (!this.matchTextSeq([\n 'VALUES',\n 'LESS',\n 'THAN',\n ])) {\n return name;\n }\n\n let values = this.parseWrappedCsv(this.parseExpression.bind(this));\n\n if (\n values.length === 1\n && values[0] instanceof ColumnExpr\n && values[0].name.toUpperCase() === 'MAXVALUE'\n ) {\n values = [new VarExpr({ this: 'MAXVALUE' })];\n }\n\n const partRange = this.expression(PartitionRangeExpr, {\n this: name,\n expressions: values,\n });\n return this.expression(PartitionExpr, { expressions: [partRange] });\n }\n\n protected parsePartitionListValue (): PartitionExpr {\n this.matchTextSeq(['PARTITION']);\n const name = this.parseIdVar();\n this.matchTextSeq(['VALUES', 'IN']);\n const values = this.parseWrappedCsv(this.parseExpression.bind(this));\n\n const partList = this.expression(PartitionListExpr, {\n this: name,\n expressions: values,\n });\n return this.expression(PartitionExpr, { expressions: [partList] });\n }\n\n public parsePrimaryKey (\n options: {\n wrappedOptional?: boolean;\n inProps?: boolean;\n namedPrimaryKey?: boolean;\n } = {},\n ): PrimaryKeyColumnConstraintExpr | PrimaryKeyExpr {\n const {\n wrappedOptional = false,\n inProps = false,\n namedPrimaryKey: _namedPrimaryKey = false,\n } = options;\n // MySQL always supports named primary keys in this context\n return super.parsePrimaryKey({\n wrappedOptional,\n inProps,\n namedPrimaryKey: true,\n });\n }\n}\n\nclass MySQLGenerator extends Generator {\n // port from _Dialect metaclass logic\n @cache\n static get AFTER_HAVING_MODIFIER_TRANSFORMS () {\n const modifiers = new Map(super.AFTER_HAVING_MODIFIER_TRANSFORMS);\n [\n 'cluster',\n 'distribute',\n 'sort',\n ].forEach((m) => modifiers.delete(m));\n return modifiers;\n }\n\n // port from _Dialect metaclass logic\n static SUPPORTS_DECODE_CASE = false;\n // port from _Dialect metaclass logic\n static readonly SELECT_KINDS: string[] = [];\n // port from _Dialect metaclass logic\n static TRY_SUPPORTED = false;\n // port from _Dialect metaclass logic\n static SUPPORTS_UESCAPE = false;\n static INTERVAL_ALLOWS_PLURAL_FORM: boolean = false;\n static LOCKING_READS_SUPPORTED: boolean = true;\n\n @cache\n static get NULL_ORDERING_SUPPORTED () {\n return NullOrderingSupported.UNSUPPORTED;\n }\n\n static JOIN_HINTS: boolean = false;\n static TABLE_HINTS: boolean = true;\n static DUPLICATE_KEY_UPDATE_WITH_SET: boolean = false;\n static QUERY_HINT_SEP: string = ' ';\n static VALUES_AS_TABLE: boolean = false;\n static NVL2_SUPPORTED: boolean = false;\n static LAST_DAY_SUPPORTS_DATE_PART: boolean = false;\n static JSON_TYPE_REQUIRED_FOR_EXTRACTION: boolean = true;\n static JSON_PATH_BRACKETED_KEY_SUPPORTED: boolean = false;\n static JSON_KEY_VALUE_PAIR_SEP: string = ',';\n static SUPPORTS_TO_NUMBER: boolean = false;\n static PARSE_JSON_NAME: string | undefined = undefined;\n static PAD_FILL_PATTERN_IS_REQUIRED: boolean = true;\n static WRAP_DERIVED_VALUES: boolean = false;\n static VARCHAR_REQUIRES_SIZE: boolean = true;\n static SUPPORTS_MEDIAN: boolean = false;\n static UPDATE_STATEMENT_SUPPORTS_FROM: boolean = false;\n static SUPPORTS_CONVERT_TIMEZONE = true;\n\n @cache\n static get ORIGINAL_TRANSFORMS () {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new Map<typeof Expression, (this: Generator, e: any) => string>([\n ...Generator.TRANSFORMS,\n [ArrayAggExpr, renameFunc('GROUP_CONCAT')],\n [BitwiseAndAggExpr, renameFunc('BIT_AND')],\n [BitwiseOrAggExpr, renameFunc('BIT_OR')],\n [BitwiseXorAggExpr, renameFunc('BIT_XOR')],\n [BitwiseCountExpr, renameFunc('BIT_COUNT')],\n [\n ChrExpr,\n function (this: Generator, e: ChrExpr): string {\n return this.chrSql(e, { name: 'CHAR' });\n },\n ],\n [CurrentDateExpr, noParenCurrentDateSql],\n [CurrentVersionExpr, renameFunc('VERSION')],\n [\n DateDiffExpr,\n removeTsOrDsToDate(\n function (this: Generator, e: DateDiffExpr): string {\n return this.func('DATEDIFF', [e.args.this, e.args.expression]);\n },\n ['this', 'expression'],\n ),\n ],\n [DateAddExpr, removeTsOrDsToDate(dateAddSql('ADD'))],\n [DateStrToDateExpr, dateStrToDateSql],\n [DateSubExpr, removeTsOrDsToDate(dateAddSql('SUB'))],\n [DateTruncExpr, dateTruncSql],\n [DayExpr, removeTsOrDsToDate()],\n [DayOfMonthExpr, removeTsOrDsToDate(renameFunc('DAYOFMONTH'))],\n [DayOfWeekExpr, removeTsOrDsToDate(renameFunc('DAYOFWEEK'))],\n [DayOfYearExpr, removeTsOrDsToDate(renameFunc('DAYOFYEAR'))],\n [\n GroupConcatExpr,\n function (this: Generator, e: GroupConcatExpr): string {\n return `GROUP_CONCAT(${this.sql(e, 'this')} SEPARATOR ${this.sql(e, 'separator') || '\\',\\''})`;\n },\n ],\n [ILikeExpr, noIlikeSql],\n [JsonExtractScalarExpr, arrowJsonExtractSql],\n [LengthExpr, lengthOrCharLengthSql],\n [LogicalOrExpr, renameFunc('MAX')],\n [LogicalAndExpr, renameFunc('MIN')],\n [MaxExpr, maxOrGreatest],\n [MinExpr, minOrLeast],\n [MonthExpr, removeTsOrDsToDate()],\n [\n NullSafeEqExpr,\n function (this: Generator, e: NullSafeEqExpr): string {\n return this.binary(e, '<=>');\n },\n ],\n [\n NullSafeNeqExpr,\n function (this: Generator, e: NullSafeNeqExpr): string {\n return `NOT ${this.binary(e, '<=>')}`;\n },\n ],\n [NumberToStrExpr, renameFunc('FORMAT')],\n [PivotExpr, noPivotSql],\n [\n SelectExpr,\n preprocess([\n eliminateDistinctOn,\n eliminateSemiAndAntiJoins,\n eliminateQualify,\n eliminateFullOuterJoin,\n unnestGenerateDateArrayUsingRecursiveCte,\n ]),\n ],\n [\n StrPositionExpr,\n function (this: Generator, e: StrPositionExpr): string {\n return strPositionSql.call(this, e, {\n funcName: 'LOCATE',\n supportsPosition: true,\n });\n },\n ],\n [StrToDateExpr, strToDateSql],\n [StrToTimeExpr, strToDateSql],\n [StuffExpr, renameFunc('INSERT')],\n [SessionUserExpr, () => 'SESSION_USER()'],\n [TableSampleExpr, noTablesampleSql],\n [TimeFromPartsExpr, renameFunc('MAKETIME')],\n [TimestampAddExpr, dateAddIntervalSql('DATE', 'ADD')],\n [\n TimestampDiffExpr,\n function (this: Generator, e: TimestampDiffExpr): string {\n return this.func('TIMESTAMPDIFF', [\n unitToVar(e),\n e.args.expression,\n e.args.this,\n ]);\n },\n ],\n [TimestampSubExpr, dateAddIntervalSql('DATE', 'SUB')],\n [TimeStrToUnixExpr, renameFunc('UNIX_TIMESTAMP')],\n [\n TimeStrToTimeExpr,\n function (this: Generator, e: TimeStrToTimeExpr): string {\n return timeStrToTimeSql.call(this, e, { includePrecision: !e.args.zone });\n },\n ],\n [\n TimeToStrExpr,\n removeTsOrDsToDate(function (this: Generator, e: TimeToStrExpr) {\n return this.func('DATE_FORMAT', [e.args.this, this.formatTime(e)]);\n }),\n ],\n [TrimExpr, trimSql],\n [TruncExpr, renameFunc('TRUNCATE')],\n [TryCastExpr, noTrycastSql],\n [TsOrDsAddExpr, dateAddSql('ADD')],\n [\n TsOrDsDiffExpr,\n function (this: Generator, e: TsOrDsDiffExpr): string {\n return this.func('DATEDIFF', [e.args.this, e.args.expression]);\n },\n ],\n [TsOrDsToDateExpr, tsOrDsToDateSql],\n [\n UnicodeExpr,\n function (this: Generator, e: UnicodeExpr): string {\n return `ORD(CONVERT(${this.sql(e.args.this)} USING utf32))`;\n },\n ],\n [UnixToTimeExpr, unixToTimeSql],\n [WeekExpr, removeTsOrDsToDate()],\n [WeekOfYearExpr, removeTsOrDsToDate(renameFunc('WEEKOFYEAR'))],\n [YearExpr, removeTsOrDsToDate()],\n [UtcTimestampExpr, renameFunc('UTC_TIMESTAMP')],\n [UtcTimeExpr, renameFunc('UTC_TIME')],\n ]);\n }\n\n /**\n * Maps unsigned types to their standard MySQL counterparts.\n * MySQL adds the 'UNSIGNED' attribute during generation based on the DataTypeExpr.\n */\n @cache\n static get UNSIGNED_TYPE_MAPPING (): Map<DataTypeExprKind, string> {\n return new Map<DataTypeExprKind, string>([\n [DataTypeExprKind.UBIGINT, 'BIGINT'],\n [DataTypeExprKind.UINT, 'INT'],\n [DataTypeExprKind.UMEDIUMINT, 'MEDIUMINT'],\n [DataTypeExprKind.USMALLINT, 'SMALLINT'],\n [DataTypeExprKind.UTINYINT, 'TINYINT'],\n [DataTypeExprKind.UDECIMAL, 'DECIMAL'],\n [DataTypeExprKind.UDOUBLE, 'DOUBLE'],\n ]);\n }\n\n /**\n * Standardizes various timestamp types to MySQL's DATETIME or TIMESTAMP.\n */\n @cache\n static get TIMESTAMP_TYPE_MAPPING (): Map<DataTypeExprKind, string> {\n return new Map<DataTypeExprKind, string>([\n [DataTypeExprKind.DATETIME2, 'DATETIME'],\n [DataTypeExprKind.SMALLDATETIME, 'DATETIME'],\n [DataTypeExprKind.TIMESTAMP, 'DATETIME'],\n [DataTypeExprKind.TIMESTAMPNTZ, 'DATETIME'],\n [DataTypeExprKind.TIMESTAMPTZ, 'TIMESTAMP'],\n [DataTypeExprKind.TIMESTAMPLTZ, 'TIMESTAMP'],\n ]);\n }\n\n @cache\n static get TYPE_MAPPING (): Map<DataTypeExprKind | string, string> {\n const mapping = new Map(Generator.TYPE_MAPPING);\n\n for (const [k, v] of MySQLGenerator.UNSIGNED_TYPE_MAPPING) {\n mapping.set(k, v);\n }\n for (const [k, v] of MySQLGenerator.TIMESTAMP_TYPE_MAPPING) {\n mapping.set(k, v);\n }\n\n // Remove types that MySQL handles natively or via special attributes\n mapping.delete(DataTypeExprKind.MEDIUMTEXT);\n mapping.delete(DataTypeExprKind.LONGTEXT);\n mapping.delete(DataTypeExprKind.TINYTEXT);\n mapping.delete(DataTypeExprKind.BLOB);\n mapping.delete(DataTypeExprKind.MEDIUMBLOB);\n mapping.delete(DataTypeExprKind.LONGBLOB);\n mapping.delete(DataTypeExprKind.TINYBLOB);\n\n return mapping;\n }\n\n @cache\n static get PROPERTIES_LOCATION (): Map<typeof Expression, PropertiesLocation> {\n const map = new Map(Generator.PROPERTIES_LOCATION);\n map.set(TransientPropertyExpr, PropertiesLocation.UNSUPPORTED);\n map.set(VolatilePropertyExpr, PropertiesLocation.UNSUPPORTED);\n map.set(PartitionedByPropertyExpr, PropertiesLocation.UNSUPPORTED);\n map.set(PartitionByRangePropertyExpr, PropertiesLocation.POST_SCHEMA);\n map.set(PartitionByListPropertyExpr, PropertiesLocation.POST_SCHEMA);\n return map;\n }\n\n static LIMIT_FETCH: string = 'LIMIT';\n static LIMIT_ONLY_LITERALS: boolean = true;\n\n /**\n * MySQL CAST targets for character-based types.\n */\n @cache\n static get CHAR_CAST_MAPPING () {\n return [\n DataTypeExprKind.LONGTEXT,\n DataTypeExprKind.LONGBLOB,\n DataTypeExprKind.MEDIUMBLOB,\n DataTypeExprKind.MEDIUMTEXT,\n DataTypeExprKind.TEXT,\n DataTypeExprKind.TINYBLOB,\n DataTypeExprKind.TINYTEXT,\n DataTypeExprKind.VARCHAR,\n ].reduce((acc, type) => ({\n ...acc,\n [type]: 'CHAR',\n }), {});\n }\n\n /**\n * MySQL CAST targets for integer-based types.\n */\n @cache\n static get SIGNED_CAST_MAPPING () {\n return [\n DataTypeExprKind.BIGINT,\n DataTypeExprKind.BOOLEAN,\n DataTypeExprKind.INT,\n DataTypeExprKind.SMALLINT,\n DataTypeExprKind.TINYINT,\n DataTypeExprKind.MEDIUMINT,\n ].reduce((acc, type) => ({\n ...acc,\n [type]: 'SIGNED',\n }), {});\n }\n\n /**\n * MySQL is restricted in which types it can use as a CAST target.\n * Reference: https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast\n */\n @cache\n static get CAST_MAPPING (): Record<string, string> {\n return {\n ...MySQLGenerator.CHAR_CAST_MAPPING,\n ...MySQLGenerator.SIGNED_CAST_MAPPING,\n [DataTypeExprKind.UBIGINT]: 'UNSIGNED',\n };\n }\n\n /**\n * Types that require specific function-like syntax for timestamp manipulation.\n */\n @cache\n static get TIMESTAMP_FUNC_TYPES (): Set<string> {\n return new Set([DataTypeExprKind.TIMESTAMPTZ, DataTypeExprKind.TIMESTAMPLTZ]);\n }\n\n /**\n * Comprehensive list of MySQL reserved keywords for identifier quoting.\n * Reference: https://dev.mysql.com/doc/refman/8.0/en/keywords.html\n */\n @cache\n static get RESERVED_KEYWORDS (): Set<string> {\n return new Set([\n 'accessible',\n 'add',\n 'all',\n 'alter',\n 'analyze',\n 'and',\n 'as',\n 'asc',\n 'asensitive',\n 'before',\n 'between',\n 'bigint',\n 'binary',\n 'blob',\n 'both',\n 'by',\n 'call',\n 'cascade',\n 'case',\n 'change',\n 'char',\n 'character',\n 'check',\n 'collate',\n 'column',\n 'condition',\n 'constraint',\n 'continue',\n 'convert',\n 'create',\n 'cross',\n 'cube',\n 'cume_dist',\n 'current_date',\n 'current_time',\n 'current_timestamp',\n 'current_user',\n 'cursor',\n 'database',\n 'databases',\n 'day_hour',\n 'day_microsecond',\n 'day_minute',\n 'day_second',\n 'dec',\n 'decimal',\n 'declare',\n 'default',\n 'delayed',\n 'delete',\n 'dense_rank',\n 'desc',\n 'describe',\n 'deterministic',\n 'distinct',\n 'distinctrow',\n 'div',\n 'double',\n 'drop',\n 'dual',\n 'each',\n 'else',\n 'elseif',\n 'empty',\n 'enclosed',\n 'escaped',\n 'except',\n 'exists',\n 'exit',\n 'explain',\n 'false',\n 'fetch',\n 'first_value',\n 'float',\n 'float4',\n 'float8',\n 'for',\n 'force',\n 'foreign',\n 'from',\n 'fulltext',\n 'function',\n 'generated',\n 'get',\n 'grant',\n 'group',\n 'grouping',\n 'groups',\n 'having',\n 'high_priority',\n 'hour_microsecond',\n 'hour_minute',\n 'hour_second',\n 'if',\n 'ignore',\n 'in',\n 'index',\n 'infile',\n 'inner',\n 'inout',\n 'insensitive',\n 'insert',\n 'int',\n 'int1',\n 'int2',\n 'int3',\n 'int4',\n 'int8',\n 'integer',\n 'intersect',\n 'interval',\n 'into',\n 'io_after_gtids',\n 'io_before_gtids',\n 'is',\n 'iterate',\n 'join',\n 'json_table',\n 'key',\n 'keys',\n 'kill',\n 'lag',\n 'last_value',\n 'lateral',\n 'lead',\n 'leading',\n 'leave',\n 'left',\n 'like',\n 'limit',\n 'linear',\n 'lines',\n 'load',\n 'localtime',\n 'localtimestamp',\n 'lock',\n 'long',\n 'longblob',\n 'longtext',\n 'loop',\n 'low_priority',\n 'master_bind',\n 'master_ssl_verify_server_cert',\n 'match',\n 'maxvalue',\n 'mediumblob',\n 'mediumint',\n 'mediumtext',\n 'middleint',\n 'minute_microsecond',\n 'minute_second',\n 'mod',\n 'modifies',\n 'natural',\n 'not',\n 'no_write_to_binlog',\n 'nth_value',\n 'ntile',\n 'undefined',\n 'numeric',\n 'of',\n 'on',\n 'optimize',\n 'optimizer_costs',\n 'option',\n 'optionally',\n 'or',\n 'order',\n 'out',\n 'outer',\n 'outfile',\n 'over',\n 'partition',\n 'percent_rank',\n 'precision',\n 'primary',\n 'procedure',\n 'purge',\n 'range',\n 'rank',\n 'read',\n 'reads',\n 'read_write',\n 'real',\n 'recursive',\n 'references',\n 'regexp',\n 'release',\n 'rename',\n 'repeat',\n 'replace',\n 'require',\n 'resignal',\n 'restrict',\n 'return',\n 'revoke',\n 'right',\n 'rlike',\n 'row',\n 'rows',\n 'row_number',\n 'schema',\n 'schemas',\n 'second_microsecond',\n 'select',\n 'sensitive',\n 'separator',\n 'set',\n 'show',\n 'signal',\n 'smallint',\n 'spatial',\n 'specific',\n 'sql',\n 'sqlexception',\n 'sqlstate',\n 'sqlwarning',\n 'sql_big_result',\n 'sql_calc_found_rows',\n 'sql_small_result',\n 'ssl',\n 'starting',\n 'stored',\n 'straight_join',\n 'system',\n 'table',\n 'terminated',\n 'then',\n 'tinyblob',\n 'tinyint',\n 'tinytext',\n 'to',\n 'trailing',\n 'trigger',\n 'true',\n 'undo',\n 'union',\n 'unique',\n 'unlock',\n 'unsigned',\n 'update',\n 'usage',\n 'use',\n 'using',\n 'utc_date',\n 'utc_time',\n 'utc_timestamp',\n 'values',\n 'varbinary',\n 'varchar',\n 'varcharacter',\n 'varying',\n 'virtual',\n 'when',\n 'where',\n 'while',\n 'window',\n 'with',\n 'write',\n 'xor',\n 'year_month',\n 'zerofill',\n ]);\n }\n\n public computedColumnConstraintSql (expression: ComputedColumnConstraintExpr): string {\n const persisted = expression.args.persisted ? 'STORED' : 'VIRTUAL';\n return `GENERATED ALWAYS AS (${this.sql(expression.args.this?.unnest())}) ${persisted}`;\n }\n\n public arraySql (expression: ArrayExpr): string {\n this.unsupported('Arrays are not supported by MySQL');\n return this.functionFallbackSql(expression);\n }\n\n public arrayContainsAllSql (expression: ArrayContainsAllExpr): string {\n this.unsupported('Array operations are not supported by MySQL');\n return this.functionFallbackSql(expression);\n }\n\n public dpipeSql (expression: DPipeExpr): string {\n return this.func('CONCAT', [...expression.flatten()]);\n }\n\n public extractSql (expression: ExtractExpr): string {\n const unit = expression.name;\n if (unit && unit.toLowerCase() === 'epoch') {\n return this.func('UNIX_TIMESTAMP', [expression.args.expression as Expression]);\n }\n\n return super.extractSql(expression);\n }\n\n public dataTypeSql (expression: DataTypeExpr): string {\n if (\n (this.constructor as typeof MySQLGenerator).VARCHAR_REQUIRES_SIZE\n && expression.isType(DataTypeExprKind.VARCHAR)\n && !expression.args.expressions?.length\n ) {\n // `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT`\n return 'TEXT';\n }\n\n let result = super.dataTypeSql(expression);\n if (MySQLGenerator.UNSIGNED_TYPE_MAPPING.has(expression.args.this as DataTypeExprKind)) {\n result = `${result} UNSIGNED`;\n }\n\n return result;\n }\n\n public jsonArrayContainsSql (expression: JsonArrayContainsExpr): string {\n return `${this.sql(expression, 'this')} MEMBER OF(${this.sql(expression, 'expression')})`;\n }\n\n public castSql (expression: CastExpr, _options: { safePrefix?: string } = {}): string {\n const toExpr = expression.args.to;\n if (toExpr instanceof DataTypeExpr) {\n const toThis = toExpr.args.this as string;\n if ((this._constructor as typeof MySQLGenerator).TIMESTAMP_FUNC_TYPES.has(toThis)) {\n return this.func('TIMESTAMP', [expression.args.this as Expression]);\n }\n const to = (this._constructor as typeof MySQLGenerator).CAST_MAPPING[toThis];\n if (to) {\n toExpr.setArgKey('this', to);\n }\n }\n return super.castSql(expression);\n }\n\n public showSql (expression: ShowExpr): string {\n const thisName = ` ${expression.name}`;\n const full = expression.args.full ? ' FULL' : '';\n const global_ = expression.args.global ? ' GLOBAL' : '';\n\n let target = this.sql(expression, 'target');\n target = target ? ` ${target}` : '';\n\n if (['COLUMNS', 'INDEX'].includes(expression.name)) {\n target = ` FROM${target}`;\n } else if (expression.name === 'GRANTS') {\n target = ` FOR${target}`;\n } else if (['LINKS', 'PARTITIONS'].includes(expression.name)) {\n target = target ? ` ON${target}` : '';\n } else if (expression.name === 'PROJECTIONS') {\n target = target ? ` ON TABLE${target}` : '';\n }\n\n const db = this.prefixedSql('FROM', expression, 'db');\n const like = this.prefixedSql('LIKE', expression, 'like');\n const where = this.sql(expression, 'where');\n\n let types = this.expressions(expression, { key: 'types' });\n types = types ? ` ${types}` : types;\n const query = this.prefixedSql('FOR QUERY', expression, 'query');\n\n let offset = '';\n let limit = '';\n if (expression.name === 'PROFILE') {\n offset = this.prefixedSql('OFFSET', expression, 'offset');\n limit = this.prefixedSql('LIMIT', expression, 'limit');\n } else {\n limit = this.oldStyleLimitSql(expression);\n }\n\n const log = this.prefixedSql('IN', expression, 'log');\n const position = this.prefixedSql('FROM', expression, 'position');\n const channel = this.prefixedSql('FOR CHANNEL', expression, 'channel');\n\n let mutexOrStatus = '';\n if (expression.name === 'ENGINE') {\n mutexOrStatus = expression.args.mutex ? ' MUTEX' : ' STATUS';\n }\n\n const forTable = this.prefixedSql('FOR TABLE', expression, 'forTable');\n const forGroup = this.prefixedSql('FOR GROUP', expression, 'forGroup');\n const forUser = this.prefixedSql('FOR USER', expression, 'forUser');\n const forRole = this.prefixedSql('FOR ROLE', expression, 'forRole');\n const intoOutfile = this.prefixedSql('INTO OUTFILE', expression, 'intoOutfile');\n const json = expression.args.json ? ' JSON' : '';\n\n return `SHOW${full}${global_}${thisName}${json}${target}${forTable}${types}${db}${query}${log}${position}${channel}${mutexOrStatus}${like}${where}${offset}${limit}${forGroup}${forUser}${forRole}${intoOutfile}`;\n }\n\n /**\n * MySQL doesn't use the TO keyword in ALTER ... RENAME.\n */\n public alterRenameSql (expression: AlterRenameExpr, _options: { includeTo?: boolean } = {}): string {\n return super.alterRenameSql(expression, { includeTo: false });\n }\n\n /**\n * MySQL uses MODIFY COLUMN for changing column data types.\n */\n public alterColumnSql (expression: AlterColumnExpr): string {\n const dtype = this.sql(expression, 'dtype');\n if (!dtype) {\n return super.alterColumnSql(expression);\n }\n\n const thisExpr = this.sql(expression, 'this');\n return `MODIFY COLUMN ${thisExpr} ${dtype}`;\n }\n\n protected prefixedSql (prefix: string, expression: Expression, arg: string): string {\n const sql = this.sql(expression, arg);\n return sql ? ` ${prefix} ${sql}` : '';\n }\n\n protected oldStyleLimitSql (expression: ShowExpr): string {\n const limit = this.sql(expression, 'limit');\n const offset = this.sql(expression, 'offset');\n if (limit) {\n const limitOffset = offset ? `${offset}, ${limit}` : limit;\n return ` LIMIT ${limitOffset}`;\n }\n return '';\n }\n\n /**\n * Simulates TIMESTAMP_TRUNC using TIMESTAMPDIFF and DATE_ADD math.\n */\n public timestampTruncSql (expression: TimestampTruncExpr): string {\n const unit = expression.args.unit;\n const startTs = '\\'0000-01-01 00:00:00\\'';\n\n // Calculate diff between 0000-01-01 and target, then add that interval back to the base\n const timestampDiff = buildDateDelta(TimestampDiffExpr)([\n unit as Expression,\n new LiteralExpr({\n this: startTs,\n isString: true,\n }),\n expression.args.this as Expression,\n ]);\n const interval = new IntervalExpr({\n this: timestampDiff,\n unit: unit,\n });\n const dateAdd = buildDateDeltaWithInterval(DateAddExpr)([\n new LiteralExpr({\n this: startTs,\n isString: true,\n }),\n interval,\n ]);\n\n return this.sql(dateAdd);\n }\n\n public convertTimezoneSql (expression: ConvertTimezoneExpr): string {\n const fromTz = expression.args.sourceTz;\n const toTz = expression.args.targetTz;\n const dt = expression.args.timestamp;\n\n return this.func('CONVERT_TZ', [\n dt,\n fromTz,\n toTz,\n ] as Expression[]);\n }\n\n public attimezoneSql (expression: AtTimeZoneExpr): string {\n this.unsupported('AT TIME ZONE is not supported by MySQL');\n return this.sql(expression.args.this);\n }\n\n public isasciiSql (expression: IsAsciiExpr): string {\n return `REGEXP_LIKE(${this.sql(expression.args.this)}, '^[[:ascii:]]*$')`;\n }\n\n public ignoreNullsSql (expression: IgnoreNullsExpr): string {\n this.unsupported('MySQL does not support IGNORE NULLS.');\n return this.sql(expression.args.this);\n }\n\n public currentSchemaSql (_expression: CurrentSchemaExpr): string {\n unsupportedArgs.call(this, _expression, 'this');\n return this.func('SCHEMA', []);\n }\n\n public partitionSql (expression: PartitionExpr): string {\n const parent = expression.parent;\n if (parent instanceof PartitionByRangePropertyExpr || parent instanceof PartitionByListPropertyExpr) {\n return this.expressions(expression, { flat: true });\n }\n return super.partitionSql(expression);\n }\n\n public partitionByRangeOrListSql (\n expression: PartitionByRangePropertyExpr | PartitionByListPropertyExpr,\n kind: string,\n ): string {\n const partitions = this.expressions(expression, {\n key: 'partitionExpressions',\n flat: true,\n });\n const create = this.expressions(expression, {\n key: 'createExpressions',\n flat: true,\n });\n return `PARTITION BY ${kind} (${partitions}) (${create})`;\n }\n\n public partitionByRangePropertySql (expression: PartitionByRangePropertyExpr): string {\n return this.partitionByRangeOrListSql(expression, 'RANGE');\n }\n\n public partitionByListPropertySql (expression: PartitionByListPropertyExpr): string {\n return this.partitionByRangeOrListSql(expression, 'LIST');\n }\n\n public partitionListSql (expression: PartitionListExpr): string {\n const name = this.sql(expression, 'this');\n const values = this.expressions(expression, { flat: true });\n return `PARTITION ${name} VALUES IN (${values})`;\n }\n\n public partitionRangeSql (expression: PartitionRangeExpr): string {\n const name = this.sql(expression, 'this');\n const values = this.expressions(expression, { flat: true });\n return `PARTITION ${name} VALUES LESS THAN (${values})`;\n }\n}\n\nexport class MySQL extends Dialect {\n static DIALECT_NAME = Dialects.MYSQL;\n static PROMOTE_TO_INFERRED_DATETIME_TYPE: boolean = true;\n\n // MySQL allows identifiers to start with digits if they are quoted or in specific contexts\n // Reference: https://dev.mysql.com/doc/refman/8.0/en/identifiers.html\n static IDENTIFIERS_CAN_START_WITH_DIGIT: boolean = true;\n\n /**\n * We default to treating all identifiers as case-sensitive, since it matches MySQL's\n * behavior on Linux systems. For MacOS and Windows systems, one can override this\n * setting by specifying `dialect=\"mysql, normalization_strategy = lowercase\"`.\n * * Reference: https://dev.mysql.com/doc/refman/8.2/en/identifier-case-sensitivity.html\n */\n @cache\n static get NORMALIZATION_STRATEGY (): NormalizationStrategy {\n return NormalizationStrategy.CASE_SENSITIVE;\n }\n\n static TIME_FORMAT: string = '\\'%Y-%m-%d %T\\'';\n static DPIPE_IS_STRING_CONCAT: boolean = false;\n static SUPPORTS_USER_DEFINED_TYPES: boolean = false;\n static SUPPORTS_SEMI_ANTI_JOIN: boolean = false;\n static SAFE_DIVISION: boolean = true;\n static SAFE_TO_ELIMINATE_DOUBLE_NEGATION: boolean = false;\n static LEAST_GREATEST_IGNORES_NULLS: boolean = false;\n\n @cache\n static get EXPRESSION_METADATA (): ExpressionMetadata {\n return new Map(MySQLTyping.EXPRESSION_METADATA);\n }\n\n /**\n * MySQL-specific time format mapping.\n * Reference: https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions\n */\n @cache\n static get TIME_MAPPING (): Record<string, string> {\n return {\n '%M': '%B',\n '%c': '%-m',\n '%e': '%-d',\n '%h': '%I',\n '%i': '%M',\n '%s': '%S',\n '%u': '%W',\n '%k': '%-H',\n '%l': '%-I',\n '%T': '%H:%M:%S',\n '%W': '%A',\n };\n }\n\n /**\n * Valid interval units supported by MySQL.\n * Includes standard units plus MySQL-specific compound units.\n */\n @cache\n static get VALID_INTERVAL_UNITS () {\n return new Set([\n ...Dialect.VALID_INTERVAL_UNITS,\n 'SECOND_MICROSECOND',\n 'MINUTE_MICROSECOND',\n 'MINUTE_SECOND',\n 'HOUR_MICROSECOND',\n 'HOUR_SECOND',\n 'HOUR_MINUTE',\n 'DAY_MICROSECOND',\n 'DAY_SECOND',\n 'DAY_MINUTE',\n 'DAY_HOUR',\n 'YEAR_MONTH',\n ]);\n }\n\n static Tokenizer = MySQLTokenizer;\n static Parser = MySQLParser;\n static Generator = MySQLGenerator;\n}\nDialect.register(Dialects.MYSQL, MySQL);\n","import type { ExpressionOrString } from './expressions';\nimport {\n Expression,\n WithExpr,\n SelectExpr, SetOperationExpr, AggFuncExpr,\n ColumnExpr, IdentifierExpr,\n SubqueryExpr,\n column,\n alias,\n JoinExpr,\n} from './expressions';\nimport {\n nameSequence,\n} from './helper';\nimport {\n assertIsInstanceOf, filterInstanceOf, id, isInstanceOf,\n} from './port_internals';\nimport { joinCondition } from './optimizer';\n\n/**\n * Represents a query execution plan as a Directed Acyclic Graph (DAG) of Steps.\n */\nexport class Plan {\n public expression: Expression;\n public root: Step | undefined;\n private _dag: Map<Step, Set<Step>> | undefined = undefined;\n\n constructor (expression: Expression) {\n // Ensuring we work on a copy to prevent mutation of the original AST\n this.expression = expression.copy();\n this.root = Step.fromExpression(this.expression);\n }\n\n /**\n * Generates or retrieves the DAG of steps.\n */\n get dag (): Map<Step, Set<Step>> {\n if (this._dag) {\n return this._dag;\n }\n\n const dag = new Map<Step, Set<Step>>();\n const seen = new Set<Step>();\n\n const stack = [this.root];\n\n while (0 < stack.length) {\n const node = stack.pop()!;\n if (seen.has(node)) continue;\n\n seen.add(node);\n const dependencies = new Set<Step>();\n\n for (const dep of node.dependencies) {\n dependencies.add(dep);\n stack.push(dep);\n }\n\n dag.set(node, dependencies);\n }\n\n this._dag = dag;\n return this._dag;\n }\n\n /**\n * Returns an iterator over the leaf nodes (steps with no dependencies).\n */\n get leaves (): IterableIterator<Step> {\n const leafNodes: Step[] = [];\n for (const [node, deps] of this.dag.entries()) {\n if (deps.size === 0) {\n leafNodes.push(node);\n }\n }\n return leafNodes[Symbol.iterator]();\n }\n\n toString (): string {\n return `Plan\\n----\\n${this.root?.toString() ?? 'unnamed'}`;\n }\n}\n\n/**\n * Base class for an execution step in the query plan DAG.\n */\nexport class Step {\n public name: string = '';\n public dependencies: Set<Step> = new Set();\n public dependents: Set<Step> = new Set();\n public projections: Expression[] = [];\n public limit: number = Infinity;\n public condition: Expression | undefined = undefined;\n\n // Intermediate state for builders\n public operands: Expression[] = [];\n public aggregations: Expression[] = [];\n\n static fromExpression (\n expression?: Expression,\n ctes: Map<string, Step> = new Map(),\n ): Step | undefined {\n if (!expression) return undefined;\n const unnested = expression.unnest();\n const with_ = unnested.getArgKey('with');\n\n // Handle CTEs\n if (isInstanceOf(with_, WithExpr)) {\n ctes = new Map(ctes);\n for (const cte of with_.args.expressions || []) {\n assertIsInstanceOf(cte.args.this, Expression);\n const step = Step.fromExpression(cte.args.this, ctes);\n if (step) {\n step.name = cte.alias;\n ctes.set(step.name, step);\n }\n }\n }\n\n const from = unnested.args.from;\n let step: Step | undefined;\n\n if (unnested instanceof SelectExpr && from instanceof Expression) {\n assertIsInstanceOf(from.args.this, Expression);\n step = Scan.fromExpression(from.args.this, ctes);\n } else if (unnested instanceof SetOperationExpr) {\n step = SetOperation.fromExpression(unnested, ctes);\n } else {\n step = new Scan();\n }\n\n const joins = filterInstanceOf(unnested.args.joins || [], JoinExpr);\n if (joins && 0 < joins.length) {\n const joinStep = Join.fromJoins(joins, ctes);\n if (step) {\n joinStep.name = step.name;\n joinStep.sourceName = step.name;\n joinStep.addDependency(step);\n }\n step = joinStep;\n }\n\n const projections: Expression[] = [];\n const operands = new Map<Expression, string>();\n const aggregations = new Map<Expression, undefined>();\n const nextOperandName = nameSequence('_a_');\n\n const extractAggOperands = (expr: Expression): boolean => {\n const aggFuncs = Array.from(expr.findAll(AggFuncExpr));\n if (0 < aggFuncs.length) {\n aggregations.set(expr, undefined);\n }\n\n for (const agg of aggFuncs) {\n for (const operand of agg.unnestOperands()) {\n if (operand instanceof ColumnExpr) continue;\n if (!operands.has(operand)) {\n operands.set(operand, nextOperandName());\n }\n operand.replace(new ColumnExpr({\n this: new IdentifierExpr({\n this: operands.get(operand),\n quoted: true,\n }),\n }));\n }\n }\n return 0 < aggFuncs.length;\n };\n\n const setOpsAndAggs = (target: Step) => {\n target.operands = Array.from(operands.entries()).map(\n ([expr, aliasExpr]) => alias(\n expr,\n aliasExpr,\n ),\n );\n target.aggregations = Array.from(aggregations.keys());\n };\n\n for (const e of unnested.args.expressions || []) {\n assertIsInstanceOf(e, Expression);\n if (e.find(AggFuncExpr)) {\n projections.push(column(\n {\n col: e.aliasOrName,\n table: step?.name,\n },\n { quoted: true },\n ));\n extractAggOperands(e);\n } else {\n projections.push(e);\n }\n }\n\n const where = unnested.getArgKey('where');\n if (step && where instanceof Expression && where.args.this instanceof Expression) {\n step.condition = where.args.this;\n }\n\n const group = unnested.getArgKey('group');\n if (group || 0 < aggregations.size) {\n const aggregate = new Aggregate();\n if (step) {\n aggregate.source = step.name;\n aggregate.name = step.name;\n }\n\n const having = unnested.getArgKey('having');\n if (having instanceof Expression && isInstanceOf(having.args.this, 'string', Expression)) {\n if (extractAggOperands(alias(\n having.args.this,\n '_h',\n { quoted: true },\n ))) {\n aggregate.condition = column(\n {\n col: '_h',\n table: step?.name,\n },\n { quoted: true },\n );\n } else if (isInstanceOf(having.args.this, Expression)) {\n aggregate.condition = having.args.this;\n }\n }\n\n setOpsAndAggs(aggregate);\n\n const groupExprs = isInstanceOf(group, Expression) ? filterInstanceOf(group.args.expressions ?? [], Expression) : [];\n aggregate.group = Object.fromEntries(\n groupExprs.map((e, i) => [`_g${i}`, e]),\n );\n\n const intermediate = new Map<string, string>();\n Object.entries(aggregate.group).forEach(([k, v]) => {\n intermediate.set(v.sql(), k);\n if (v instanceof ColumnExpr) {\n intermediate.set(v.name, k);\n }\n });\n\n const lookupNode = (node: Expression): string | undefined =>\n intermediate.get(node.sql()) ?? (node instanceof ColumnExpr ? intermediate.get(node.name) : undefined);\n\n // Replace projections with group references\n for (const projection of projections) {\n for (const node of projection.walk()) {\n const name = lookupNode(node);\n if (name) {\n node.replace(column({\n col: name,\n table: step?.name,\n }, { quoted: true }));\n }\n }\n }\n\n // Replace group references in the HAVING condition (non-aggregate)\n if (aggregate.condition) {\n for (const node of aggregate.condition.walk()) {\n const name = lookupNode(node);\n if (name) {\n node.replace(column({\n col: name,\n table: step?.name,\n }, { quoted: true }));\n }\n }\n }\n\n aggregate.addDependency(step);\n step = aggregate;\n }\n\n const order = unnested.getArgKey('order');\n if (isInstanceOf(order, Expression)) {\n // If we have an aggregate step, extract any agg funcs in ORDER BY into operands\n if (step instanceof Aggregate) {\n const orderExprs = filterInstanceOf(order.args.expressions || [], Expression);\n for (let i = 0; i < orderExprs.length; i++) {\n const ordered = orderExprs[i];\n const orderThis = (ordered.args as Record<string, Expression | undefined>).this;\n if (orderThis && extractAggOperands(alias(orderThis, `_o_${i}`, { quoted: true }))) {\n orderThis.replace(column({\n col: `_o_${i}`,\n table: step.name,\n }, { quoted: true }));\n }\n }\n setOpsAndAggs(step);\n }\n\n const sort = new Sort();\n sort.name = step?.name ?? '';\n sort.key = filterInstanceOf(order.args.expressions || [], Expression);\n sort.addDependency(step);\n step = sort;\n }\n\n if (step) step.projections = projections;\n\n if (unnested instanceof SelectExpr && unnested.args.distinct) {\n const distinct = new Aggregate();\n distinct.source = step?.name;\n distinct.name = step?.name ?? '';\n distinct.group = Object.fromEntries(\n (projections.length ? projections : filterInstanceOf(unnested.args.expressions || [], Expression))\n .map((e) => [\n e.aliasOrName,\n column({\n col: e.aliasOrName,\n table: step?.name,\n }, { quoted: true }),\n ]),\n );\n distinct.addDependency(step);\n step = distinct;\n }\n\n const limit = unnested.getArgKey('limit');\n if (step && isInstanceOf(limit, Expression)) {\n step.limit = parseInt(limit.text('expression'));\n }\n\n return step;\n }\n\n addDependency (dependency?: Step): void {\n if (!dependency) return;\n this.dependencies.add(dependency);\n dependency.dependents.add(this);\n }\n\n /**\n * Returns a string representation of the execution step.\n */\n toString (): string {\n return this.toS();\n }\n\n /**\n * Recursive helper to build the formatted string tree.\n * @param level Indentation depth.\n */\n toS (level: number = 0): string {\n const indent = ' '.repeat(level);\n const nested = `${indent} `;\n\n // Each subclass (Scan, Join, Aggregate) can provide specific context lines\n let context = this._toS(`${nested} `);\n\n if (0 < context.length) {\n context = [`${nested}Context:`, ...context];\n }\n\n const lines: string[] = [\n `${indent}- ${this.id}`,\n ...context,\n `${nested}Projections:`,\n ];\n\n for (const expression of this.projections) {\n lines.push(`${nested} - ${expression.sql()}`);\n }\n\n if (this.condition) {\n lines.push(`${nested}Condition: ${this.condition.sql()}`);\n }\n\n if (this.limit !== Infinity) {\n lines.push(`${nested}Limit: ${this.limit}`);\n }\n\n if (0 < this.dependencies.size) {\n lines.push(`${nested}Dependencies:`);\n for (const dependency of this.dependencies) {\n // Recursively call toS for each dependency with increased level\n lines.push(` ${dependency.toS(level + 1)}`);\n }\n }\n\n return lines.join('\\n');\n }\n\n /**\n * Property-like getter for the class name.\n */\n get typeName (): string {\n return this.constructor.name;\n }\n\n /**\n * Unique identifier for the step in the output.\n */\n get id (): string {\n const nameStr = this.name ? ` ${this.name}` : '';\n return `${this.typeName}:${nameStr} (${id(this)})`;\n }\n\n /**\n * Hook for subclasses to add extra descriptive lines (like Join conditions or Source tables).\n */\n _toS (_indent: string): string[] {\n return [];\n }\n}\n\n/**\n * Represents a leaf-level operation that reads from a table, subquery, or CTE.\n */\nexport class Scan extends Step {\n public source: ExpressionOrString | undefined = undefined;\n\n static fromExpression (\n expression: Expression,\n ctes: Map<string, Step> = new Map(),\n ): Step | undefined {\n const table = expression;\n const alias = expression.aliasOrName;\n\n if (expression instanceof SubqueryExpr) {\n const innerTable = expression.args.this;\n const step = innerTable !== undefined ? Step.fromExpression(innerTable, ctes) : undefined;\n if (step) step.name = alias;\n return step;\n }\n\n const step = new Scan();\n step.name = alias;\n step.source = expression;\n\n // If the table name matches a CTE, we add that CTE's step as a dependency\n const tableName = table.name;\n if (ctes.has(tableName)) {\n step.addDependency(ctes.get(tableName));\n }\n\n return step;\n }\n\n override _toS (indent: string): string[] {\n const source = this.source instanceof Expression ? this.source.sql() : this.source ?? '-static-';\n return [`${indent}Source: ${source}`];\n }\n}\n\n/**\n * Represents a join operation between a source step and one or more join targets.\n */\nexport class Join extends Step {\n public sourceName: string | undefined = undefined;\n public joins: Record<string, {\n side: string | undefined;\n joinKey: unknown[];\n sourceKey: unknown[];\n condition: Expression | undefined;\n }> = {};\n\n static fromJoins (\n joins: Iterable<JoinExpr>,\n ctes: Map<string, Step> = new Map(),\n ): Join {\n const step = new Join();\n\n for (const join of joins) {\n const {\n sourceKeys, joinKeys, on,\n } = joinCondition(join);\n\n step.joins[join.aliasOrName] = {\n side: join.side,\n joinKey: joinKeys || [],\n sourceKey: sourceKeys || [],\n condition: on || undefined,\n };\n\n // Each join target becomes a new dependency branch via a Scan\n if (join.args.this !== undefined) step.addDependency(Scan.fromExpression(join.args.this, ctes));\n }\n\n return step;\n }\n\n override _toS (indent: string): string[] {\n const lines = [`${indent}Source: ${this.sourceName || this.name}`];\n\n for (const [name, join] of Object.entries(this.joins)) {\n lines.push(`${indent}${name}: ${join.side || 'INNER'}`);\n\n const joinKeyStr = join.joinKey.map((key) => String(key)).join(', ');\n if (joinKeyStr) {\n lines.push(`${indent}Key: ${joinKeyStr}`);\n }\n\n if (join.condition) {\n lines.push(`${indent}On: ${join.condition.sql()}`);\n }\n }\n\n return lines;\n }\n}\n\n/**\n * Represents an aggregation step (GROUP BY / HAVING).\n */\nexport class Aggregate extends Step {\n public aggregations: Expression[] = [];\n public operands: Expression[] = [];\n public group: Record<string, Expression> = {};\n public source: ExpressionOrString | undefined = undefined;\n\n constructor () {\n super();\n }\n\n override _toS (indent: string): string[] {\n const lines: string[] = [`${indent}Aggregations:`];\n\n for (const expression of this.aggregations) {\n lines.push(`${indent} - ${expression.sql()}`);\n }\n\n if (0 < Object.keys(this.group).length) {\n lines.push(`${indent}Group:`);\n for (const expression of Object.values(this.group)) {\n lines.push(`${indent} - ${expression.sql()}`);\n }\n }\n\n if (this.condition) {\n lines.push(`${indent}Having:`);\n lines.push(`${indent} - ${this.condition.sql()}`);\n }\n\n if (0 < this.operands.length) {\n lines.push(`${indent}Operands:`);\n for (const expression of this.operands) {\n lines.push(`${indent} - ${expression.sql()}`);\n }\n }\n\n return lines;\n }\n}\n\n/**\n * Represents an ordering step (ORDER BY).\n */\nexport class Sort extends Step {\n public key: Expression[] = [];\n\n constructor () {\n super();\n }\n\n override _toS (indent: string): string[] {\n const lines: string[] = [`${indent}Key:`];\n\n for (const expression of this.key) {\n lines.push(`${indent} - ${expression.sql()}`);\n }\n\n return lines;\n }\n}\n\n/**\n * Represents set operations like UNION, INTERSECT, and EXCEPT.\n */\nexport class SetOperation extends Step {\n public op: typeof SetOperationExpr; // Constructor for the specific SetOperation type\n public left: string | undefined;\n public right: string | undefined;\n public distinct: boolean;\n\n constructor (\n op: typeof SetOperationExpr,\n left: string | undefined,\n right: string | undefined,\n options: { distinct?: boolean } = {},\n ) {\n const { distinct = false } = options;\n super();\n this.op = op;\n this.left = left;\n this.right = right;\n this.distinct = distinct;\n }\n\n static fromExpression (\n expression?: SetOperationExpr,\n ctes: Map<string, Step> = new Map(),\n ): SetOperation | undefined {\n if (!(expression instanceof SetOperationExpr)) {\n throw new Error('Expected SetOperation expression');\n }\n\n const left = expression.args.this !== undefined ? Step.fromExpression(expression.args.this, ctes) : undefined;\n if (left) left.name = left.name || 'left';\n\n const right = expression.args.expression !== undefined ? Step.fromExpression(expression.args.expression, ctes) : undefined;\n if (right) right.name = right.name || 'right';\n\n const step = new SetOperation(\n expression._constructor as typeof SetOperationExpr,\n left?.name,\n right?.name,\n { distinct: Boolean(expression.args.distinct) },\n );\n\n if (left) step.addDependency(left);\n if (right) step.addDependency(right);\n\n const limit = expression.args.limit;\n if (isInstanceOf(limit, Expression)) {\n step.limit = parseInt(limit.text('expression'));\n }\n\n return step;\n }\n\n override _toS (indent: string): string[] {\n const lines: string[] = [];\n if (this.distinct) {\n lines.push(`${indent}Distinct: ${this.distinct}`);\n }\n return lines;\n }\n\n get typeName (): string {\n return this.op.name;\n }\n}\n","import {\n DateTime, Duration,\n} from 'luxon';\nimport {\n DataTypeExpr, DataTypeExprKind,\n} from '../expressions';\nimport { Generator } from '../generator';\nimport {\n isInt, seqGet,\n} from '../helper';\nimport {\n add, and, eq, floorDiv, gt, gte, lt, lte, lshift, mod, mul, neq, or, pow, rshift, sub, truediv, xor,\n} from '../port_internals/ops_utils';\n\n/**\n * Makes a method return `undefined` if any argument is `undefined`.\n *\n * Can be used directly:\n * @undefinedIfAny\n * static foo(a, b) { ... }\n *\n * Or as a factory to restrict the check to specific argument indices:\n * @undefinedIfAny(0, 1)\n * static foo(a, b, c) { ... } // only checks a and b\n */\nfunction undefinedIfAny<This, Args extends unknown[], Return> (\n value: (this: This, ...args: Args) => Return,\n context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,\n): (this: This, ...args: Args) => Return;\nfunction undefinedIfAny (...indices: number[]): <This, Args extends unknown[], Return>(\n value: (this: This, ...args: Args) => Return,\n context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,\n) => (this: This, ...args: Args) => Return;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction undefinedIfAny (...args: any[]): unknown {\n const isDirectDecorator =\n args.length === 2 // decorator receives exactly (value, context)\n && typeof args[0] === 'function' // value is the decorated method\n && args[1] !== undefined // context is present\n && typeof args[1] === 'object' // context is an object\n && args[1].kind === 'method'; // context.kind identifies a method decorator\n\n const wrap = (func: (...a: unknown[]) => unknown, indices: number[]) => {\n const predicate =\n 0 < indices.length\n ? (...fnArgs: unknown[]) => indices.some((i) => fnArgs[i] === undefined)\n : (...fnArgs: unknown[]) => fnArgs.some((a) => a === undefined);\n\n return (...fnArgs: unknown[]) => (predicate(...fnArgs) ? undefined : func(...fnArgs));\n };\n\n if (isDirectDecorator) {\n return wrap(args[0] as (...a: unknown[]) => unknown, []);\n }\n const indices = args as number[];\n return (value: (...a: unknown[]) => unknown) => wrap(value, indices);\n}\n\nclass ReverseKey<T> {\n constructor (public obj: T) {}\n\n eq (other: ReverseKey<T>): boolean {\n return other.obj === this.obj;\n }\n\n lt (other: ReverseKey<T>): boolean {\n return lt(other.obj, this.obj);\n }\n}\n\nfunction filterUndefineds<T, R> (func: (values: T[]) => R, emptyUndefined = true) {\n return (values: unknown): R | undefined => {\n // Convert generators/iterables to arrays\n const arr = Array.isArray(values)\n ? values\n : (values !== null && values !== undefined && typeof (values as Record<symbol, unknown>)[Symbol.iterator] === 'function')\n ? [...(values as Iterable<unknown>)]\n : [values];\n const filtered = arr.filter((v): v is T => v !== undefined);\n if (filtered.length === 0 && emptyUndefined) return undefined;\n return func(filtered);\n };\n}\n\nfunction fmean<T> (values: T[]): number {\n return truediv(values.reduce((a: unknown, b: T) => add(a, b), 0), values.length) as number;\n}\n\nexport class ENV {\n // aggs\n static ARRAYAGG = <T>(values: T[]): T[] => [...values];\n static ARRAYUNIQUEAGG = filterUndefineds(<T>(acc: T[]): T[] => [...new Set(acc)]);\n static AVG = filterUndefineds((acc: number[]) => fmean(acc));\n static COUNT = filterUndefineds((acc: unknown[]) => acc.length, false);\n static MAX = filterUndefineds(<T>(acc: T[]): T => acc.reduce((a, b) => (lt(a, b) ? b : a)));\n static MIN = filterUndefineds(<T>(acc: T[]): T => acc.reduce((a, b) => (lt(b, a) ? b : a)));\n static SUM = filterUndefineds(<T>(acc: T[]) => acc.reduce((a: unknown, b: T) => add(a, b), 0));\n\n // scalar functions\n @undefinedIfAny\n static ABS<T extends number> (this_: T): number {\n return Math.abs(this_);\n }\n\n @undefinedIfAny\n static ADD<E, T> (e: E, this_: T) {\n return add(e, this_);\n }\n\n @undefinedIfAny\n static ARRAYANY<T> (arr: T[], func: (e: T) => boolean): boolean {\n return arr.some((e) => func(e));\n }\n\n @undefinedIfAny(0, 1)\n @undefinedIfAny(0, 1)\n static ARRAYTOSTRING<T> (this_: T[], expression: string, fallback?: T): string {\n return this_\n .map((x) => (x !== undefined ? x : fallback))\n .filter((x): x is T => x !== undefined)\n .join(expression);\n }\n\n @undefinedIfAny\n static BETWEEN<T> (this_: T, low: T, high: T): boolean {\n return lte(low, this_) && lte(this_, high);\n }\n\n @undefinedIfAny\n static BITWISEAND<T, E> (this_: T, e: E) {\n return and(this_, e);\n }\n\n @undefinedIfAny\n static BITWISELEFTSHIFT<T, E> (this_: T, e: E) {\n return lshift(this_, e);\n }\n\n @undefinedIfAny\n static BITWISEOR<T, E> (this_: T, e: E) {\n return or(this_, e);\n }\n\n @undefinedIfAny\n static BITWISERIGHTSHIFT<T, E> (this_: T, e: E) {\n return rshift(this_, e);\n }\n\n @undefinedIfAny\n static BITWISEXOR<T, E> (this_: T, e: E) {\n return xor(this_, e);\n }\n\n static CAST<T> (this_: T, to: DataTypeExprKind): unknown {\n if (this_ === undefined) return undefined;\n if (to === DataTypeExprKind.DATE) {\n if (this_ instanceof DateTime) return this_.startOf('day');\n if (typeof this_ === 'string') return DateTime.fromISO(this_).startOf('day');\n }\n if (to === DataTypeExprKind.TIME) {\n if (this_ instanceof DateTime) return this_;\n if (typeof this_ === 'string') return DateTime.fromISO(`1970-01-01T${this_}`);\n }\n if (to === DataTypeExprKind.DATETIME || to === DataTypeExprKind.TIMESTAMP) {\n if (this_ instanceof DateTime) return this_;\n if (typeof this_ === 'string') return DateTime.fromISO(this_);\n }\n if (to === DataTypeExprKind.BOOLEAN) return Boolean(this_);\n if (DataTypeExpr.TEXT_TYPES.has(to)) return String(this_);\n if (to === DataTypeExprKind.FLOAT || to === DataTypeExprKind.DOUBLE) return Number(this_);\n if (DataTypeExpr.NUMERIC_TYPES.has(to)) return Math.trunc(Number(this_));\n throw new Error(`Casting ${this_} to '${to}' not implemented.`);\n }\n\n static COALESCE = <T>(...args: (T | undefined)[]): T | undefined => args.find((a) => a !== undefined);\n\n @undefinedIfAny\n static CONCAT (...args: string[]): string {\n return args.join('');\n }\n\n @undefinedIfAny\n static SAFECONCAT (...args: unknown[]): string {\n return args.map(String).join('');\n }\n\n @undefinedIfAny\n static CONCATWS (this_: string, ...args: string[]): string {\n return args.join(this_);\n }\n\n @undefinedIfAny\n static DATEDIFF (this_: DateTime, expression: DateTime): number {\n return Math.floor(this_.diff(expression, 'days').days);\n }\n\n @undefinedIfAny\n static DATESTRTODATE (arg: string): DateTime {\n return DateTime.fromISO(arg).startOf('day');\n }\n\n @undefinedIfAny\n static DIV<E, T> (e: E, this_: T) {\n if (this_ === 0) throw new Error('division by zero');\n return truediv(e, this_);\n }\n\n static DOT<E extends Record<PropertyKey, unknown>, K extends PropertyKey> (e: E, this_: K): E[K] | undefined {\n if (e === undefined || this_ === undefined) return undefined;\n return e[this_];\n }\n\n @undefinedIfAny\n static EQ<T, E> (this_: T, e: E): boolean {\n return eq(this_, e);\n }\n\n @undefinedIfAny\n static EXTRACT (this_: string, e: DateTime): unknown {\n return (e as unknown as Record<string, unknown>)[this_];\n }\n\n @undefinedIfAny\n static GT<T, E> (this_: T, e: E): boolean {\n return gt(this_, e);\n }\n\n @undefinedIfAny\n static GTE<T, E> (this_: T, e: E): boolean {\n return gte(this_, e);\n }\n\n static IF = <T, F>(predicate: unknown, true_: T, false_: F): T | F => (predicate ? true_ : false_);\n\n @undefinedIfAny\n static INTDIV<E, T> (e: E, this_: T) {\n return floorDiv(e, this_);\n }\n\n @undefinedIfAny\n static INTERVAL<T> (this_: T, unit: string): number {\n const plural = unit + 'S';\n if (plural in Generator.TIME_PART_SINGULARS) unit = plural;\n return Duration.fromObject({ [unit.toLowerCase()]: parseFloat(this_ as string) }).toMillis();\n }\n\n @undefinedIfAny(0, 1)\n static JSONEXTRACT<T> (this_: T, expression: unknown[]): unknown {\n let current: unknown = this_;\n for (const pathSegment of expression) {\n if (current === undefined) break;\n if (typeof current === 'object' && !Array.isArray(current)) {\n current = (current as Record<PropertyKey, unknown>)[pathSegment as PropertyKey];\n } else if (Array.isArray(current) && isInt(String(pathSegment))) {\n current = seqGet(current, parseInt(pathSegment as string));\n } else {\n throw new Error(`Unable to extract value for ${current} at ${pathSegment}.`);\n }\n }\n return current;\n }\n\n @undefinedIfAny\n static LEFT (this_: string, e: number): string {\n return this_.slice(0, e);\n }\n\n @undefinedIfAny\n static LIKE (this_: string, e: string): boolean {\n return new RegExp('^' + e.replace(/_/g, '.').replace(/%/g, '.*') + '$').test(this_);\n }\n\n @undefinedIfAny\n static LOWER (arg: string): string {\n return arg.toLowerCase();\n }\n\n @undefinedIfAny\n static LT<T, E> (this_: T, e: E): boolean {\n return lt(this_, e);\n }\n\n @undefinedIfAny\n static LTE<T, E> (this_: T, e: E): boolean {\n return lte(this_, e);\n }\n\n static MAP<K extends PropertyKey, V> (keys: K[], values: V[]): Record<K, V> | undefined {\n if (keys === undefined || values === undefined) return undefined;\n return Object.fromEntries(keys.map((k, i) => [k, values[i]])) as Record<K, V>;\n }\n\n @undefinedIfAny\n static MOD<E, T> (e: E, this_: T) {\n return mod(e, this_);\n }\n\n @undefinedIfAny\n static MUL<E, T> (e: E, this_: T) {\n return mul(e, this_);\n }\n\n @undefinedIfAny\n static NEQ<T, E> (this_: T, e: E): boolean {\n return neq(this_, e);\n }\n\n @undefinedIfAny\n static ORD (c: string): number {\n return c.codePointAt(0)!;\n }\n\n static ORDERED<T> (this_: T, options: {\n desc: boolean;\n undefinedFirst: boolean;\n }): T | ReverseKey<T> {\n const { desc } = options;\n if (desc) return new ReverseKey(this_);\n return this_;\n }\n\n static POW = <A, B>(a: A, b: B) => pow(a, b);\n\n @undefinedIfAny\n static RIGHT (this_: string, e: number): string {\n return this_.slice(-e);\n }\n\n @undefinedIfAny\n static ROUND (this_: number, decimals?: number, _truncate?: unknown): number {\n return decimals === undefined ? Math.round(this_) : parseFloat(this_.toFixed(decimals));\n }\n\n static STRPOSITION (this_?: string, substr?: string, position?: number): number | undefined {\n if (this_ === undefined || substr === undefined) return undefined;\n const pos = position !== undefined ? position - 1 : 0;\n return this_.indexOf(substr, pos) + 1;\n }\n\n @undefinedIfAny\n static SUB<E, T> (e: E, this_: T) {\n return sub(e, this_);\n }\n\n static SUBSTRING (this_?: string, start?: number, length?: number): string | undefined {\n if (this_ === undefined) return undefined;\n if (start === undefined) return this_;\n if (start === 0) return '';\n const s = start < 0 ? this_.length + start : start - 1;\n const end = length === undefined ? undefined : s + length;\n return this_.slice(s, end);\n }\n\n @undefinedIfAny\n static TIMESTRTOTIME (arg: string): DateTime {\n return DateTime.fromISO(arg);\n }\n\n @undefinedIfAny\n static UPPER (arg: string): string {\n return arg.toUpperCase();\n }\n\n @undefinedIfAny\n static YEAR (arg: DateTime): number {\n return arg.year;\n }\n\n @undefinedIfAny\n static MONTH (arg: DateTime): number {\n return arg.month;\n }\n\n @undefinedIfAny\n static DAY (arg: DateTime): number {\n return arg.day;\n }\n\n static CURRENTDATETIME = (): DateTime => DateTime.now();\n static CURRENTTIMESTAMP = (): DateTime => DateTime.now();\n static CURRENTTIME = (): DateTime => DateTime.now();\n static CURRENTDATE = (): DateTime => DateTime.now().startOf('day');\n\n @undefinedIfAny\n static STRFTIME (fmt: string, arg: string): string {\n const d = DateTime.fromISO(arg);\n return fmt\n .replace('%Y', String(d.year))\n .replace('%m', String(d.month).padStart(2, '0'))\n .replace('%d', String(d.day).padStart(2, '0'))\n .replace('%H', String(d.hour).padStart(2, '0'))\n .replace('%M', String(d.minute).padStart(2, '0'))\n .replace('%S', String(d.second).padStart(2, '0'));\n }\n\n @undefinedIfAny\n static STRTOTIME (arg: string, format: string): DateTime {\n void format;\n return DateTime.fromISO(arg);\n }\n\n @undefinedIfAny\n static TRIM (this_: string, e?: string): string {\n return e === undefined ? this_.trim() : this_.replace(new RegExp(`^[${e}]+|[${e}]+$`, 'g'), '');\n }\n\n static STRUCT = <K extends string, V>(...args: (K | V)[]): Record<K, V> => {\n const result = {} as Record<K, V>;\n for (let x = 0; x < args.length; x += 2) {\n if (args[x] !== undefined && args[x + 1] !== undefined) {\n result[args[x] as K] = args[x + 1] as V;\n }\n }\n return result;\n };\n\n @undefinedIfAny\n static UNIXTOTIME (arg: number): DateTime {\n return DateTime.fromSeconds(arg);\n }\n\n // Make the static methods enumerable\n // Skip non-configurable properties\n // so that `...` spreading would work\n static {\n for (const key of Object.getOwnPropertyNames(this)) {\n const desc = Object.getOwnPropertyDescriptor(this, key);\n if (!desc?.configurable) {\n continue;\n }\n if (!desc.enumerable) {\n Object.defineProperty(this, key, {\n ...desc,\n enumerable: true,\n });\n }\n }\n }\n}\n\nexport type Env = Record<string, unknown>;\n","import { Dialect } from '../dialects/dialect';\nimport { ExecuteError } from '../errors';\nimport {\n AddExpr,\n AliasExpr,\n AnonymousExpr,\n AndExpr,\n ArrayExpr,\n BetweenExpr,\n BooleanExpr,\n CaseExpr,\n CastExpr,\n ColumnExpr,\n ConcatExpr,\n DistinctExpr,\n DivExpr,\n DotExpr,\n EqExpr,\n Expression,\n ExceptExpr,\n ExtractExpr,\n FuncExpr,\n InExpr,\n IntersectExpr,\n IntervalExpr,\n IsExpr,\n JsonExtractExpr,\n JsonPathExpr,\n JsonPathKeyExpr,\n JsonPathSubscriptExpr,\n LambdaExpr,\n LiteralExpr,\n ModExpr,\n MulExpr,\n NeqExpr,\n NotExpr,\n NullExpr,\n OrderedExpr,\n OrExpr,\n StarExpr,\n SubExpr,\n UnionExpr,\n JoinExprKind,\n type TableExpr,\n} from '../expressions';\nimport { ALL_FUNCTIONS } from '../parser/function_registry';\nimport { Generator } from '../generator';\nimport type {\n Plan, Step,\n} from '../planner';\nimport {\n Scan, Aggregate, Join, Sort, SetOperation,\n} from '../planner';\nimport { Context } from './context';\nimport { ENV } from './env';\nimport {\n Table, RowReader,\n} from './table';\n\n/** Generates ORDERED(this, desc, nullsFirst) for use in sort key evaluation. */\nfunction orderedJs (this: Generator, expression: OrderedExpr): string {\n const thisSql = this.sql(expression, 'this');\n const desc = expression.args.desc ? 'true' : 'false';\n const nullsFirst = expression.args.nullsFirst ? 'true' : 'false';\n return `ORDERED(${thisSql}, { desc: ${desc}, undefinedFirst: ${nullsFirst} })`;\n}\n\n/** Generates a function call using the expression's key as the function name. */\nfunction rename (this: Generator, e: Expression): string {\n try {\n const values = Object.values(e.args);\n\n if (values.length === 1) {\n const val = values[0];\n if (!Array.isArray(val)) {\n return this.func(e._constructor.key, [val]);\n }\n return this.func(e._constructor.key, val);\n }\n\n if (e instanceof FuncExpr && (e._constructor as typeof FuncExpr).isVarLenArgs) {\n const args = [];\n for (const v of values) {\n if (Array.isArray(v)) {\n args.push(...v);\n } else if (v !== undefined) {\n args.push(v);\n }\n }\n return this.func(e._constructor.key, args);\n }\n\n return this.func(\n e._constructor.key,\n values.filter((v) => v !== undefined),\n );\n } catch (ex) {\n throw new Error(`Could not rename ${e}: ${ex}`);\n }\n}\n\n/** Generates a JS ternary chain from a CASE expression, building from the last branch inward. */\nfunction caseJs (this: Generator, expression: CaseExpr): string {\n const thisStr = this.sql(expression, 'this');\n let chain = this.sql(expression, 'default') || 'null';\n\n const ifs = expression.args.ifs ?? [];\n for (const e of [...ifs].reverse()) {\n const trueStr = this.sql(e, 'true');\n let condition = this.sql(e, 'this');\n if (thisStr) {\n condition = `${thisStr} === (${condition})`;\n }\n chain = `(${condition} ? ${trueStr} : (${chain}))`;\n }\n\n return chain;\n}\n\n/** Generates a JS arrow function from a Lambda expression. */\nfunction lambdaJs (this: Generator, e: LambdaExpr): string {\n return `(${this.expressions(e, { flat: true })}) => ${this.sql(e, 'this')}`;\n}\n\n/** Generates a DIV call, appending `|| undefined` for safe division and wrapping in Math.trunc for integer division. */\nfunction divJs (this: Generator, e: DivExpr): string {\n let denominator = this.sql(e, 'expression');\n\n if (e.args.safe) {\n denominator += ' || undefined';\n }\n\n let sql = `DIV(${this.sql(e, 'this')}, ${denominator})`;\n\n if (e.args.typed) {\n sql = `Math.trunc(${sql})`;\n }\n\n return sql;\n}\n\nexport class JavascriptGenerator extends Generator {\n static override get TRANSFORMS () {\n const parent = super.TRANSFORMS;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const overrides = new Map<typeof Expression, (this: Generator, e: any) => string>([\n ...parent.entries(),\n [\n AddExpr,\n function (this: Generator, e: AddExpr) {\n return `ADD(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n AnonymousExpr,\n function (this: Generator, e: AnonymousExpr) {\n return this.func(e.name, e.args.expressions ?? []);\n },\n ],\n [\n AliasExpr,\n function (this: Generator, e: AliasExpr) {\n return this.sql(e.args.this as Expression);\n },\n ],\n [\n AndExpr,\n function (this: Generator, e: AndExpr) {\n return this.binary(e, '&&');\n },\n ],\n [\n ArrayExpr,\n function (this: Generator, e: ArrayExpr) {\n return `[${this.expressions(e, { flat: true })}]`;\n },\n ],\n [BetweenExpr, rename],\n [\n BooleanExpr,\n function (this: Generator, e: BooleanExpr) {\n return e.args.this ? 'true' : 'false';\n },\n ],\n [CaseExpr, caseJs],\n [\n CastExpr,\n function (this: Generator, e: CastExpr) {\n // Get the DataTypeExprKind as a lowercase string\n // DataTypeExprKind enum values are lowercase strings like 'text', 'bigint', etc.\n const toExpr = e.args.to;\n const toKind = typeof toExpr === 'string'\n ? toExpr.toLowerCase()\n : this.sql(toExpr as Expression).toLowerCase();\n return `CAST(${this.sql(e.args.this as Expression)}, '${toKind}')`;\n },\n ],\n [\n ColumnExpr,\n function (this: Generator, e: ColumnExpr) {\n const table = this.sql(e, 'table') || undefined;\n const col = this.sql(e.args.this as Expression);\n return `scope[${table}][${col}]`;\n },\n ],\n [\n ConcatExpr,\n function (this: Generator, e: ConcatExpr) {\n return this.func(\n e.args.safe ? 'SAFECONCAT' : 'CONCAT',\n e.args.expressions ?? [],\n );\n },\n ],\n [\n DistinctExpr,\n function (this: Generator, e: DistinctExpr) {\n return `new Set([${this.sql(e, 'this')}])`;\n },\n ],\n [\n DotExpr,\n function (this: Generator, e: DotExpr) {\n // DotExpr represents property access like .flavor\n // this is the left side (e.g., scope[\"i\"][\"attributes\"])\n // expression is the right side (the property name)\n const left = this.sql(e.args.this as Expression);\n const right = this.sql(e.args.expression as Expression);\n // Convert .property to [\"property\"] notation\n return `${left}[${right}]`;\n },\n ],\n [DivExpr, divJs],\n [\n EqExpr,\n function (this: Generator, e: EqExpr) {\n return `EQ(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n NeqExpr,\n function (this: Generator, e: NeqExpr) {\n return `NEQ(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n ModExpr,\n function (this: Generator, e: ModExpr) {\n return `MOD(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n MulExpr,\n function (this: Generator, e: MulExpr) {\n return `MUL(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n SubExpr,\n function (this: Generator, e: SubExpr) {\n return `SUB(${this.sql(e, 'this')}, ${this.sql(e, 'expression')})`;\n },\n ],\n [\n ExtractExpr,\n function (this: Generator, e: ExtractExpr) {\n return `EXTRACT('${e.name.toLowerCase()}', ${this.sql(e, 'expression')})`;\n },\n ],\n [\n InExpr,\n function (this: Generator, e: InExpr) {\n return `[${this.expressions(e, { flat: true })}].includes(${this.sql(e, 'this')})`;\n },\n ],\n [\n IntervalExpr,\n function (this: Generator, e: IntervalExpr) {\n return `INTERVAL(${this.sql(e.args.this as Expression)}, '${this.sql(e.args.unit as Expression)}')`;\n },\n ],\n [\n IsExpr,\n function (this: Generator, e: IsExpr) {\n return e.args.this instanceof LiteralExpr ? this.binary(e, '===') : this.binary(e, '===');\n },\n ],\n [\n JsonExtractExpr,\n function (this: Generator, e: JsonExtractExpr) {\n return this.func(\n JsonExtractExpr.key,\n [\n e.args.this as Expression,\n e.args.expression as Expression,\n ...(e.args.expressions ?? []) as Expression[],\n ],\n );\n },\n ],\n [\n JsonPathExpr,\n function (this: Generator, e: JsonPathExpr) {\n const parts = e.args.expressions ?? [];\n return `[${parts.slice(1).map((p) => this.sql(p as Expression))\n .join(',')}]`;\n },\n ],\n [\n JsonPathKeyExpr,\n function (this: Generator, e: JsonPathKeyExpr) {\n return `'${this.sql(e.args.this as Expression)}'`;\n },\n ],\n [\n JsonPathSubscriptExpr,\n function (this: Generator, e: JsonPathSubscriptExpr) {\n return `'${e.args.this}'`;\n },\n ],\n [LambdaExpr, lambdaJs],\n [\n NotExpr,\n function (this: Generator, e: NotExpr) {\n return `!(${this.sql(e.args.this as Expression)})`;\n },\n ],\n [NullExpr, () => 'undefined'],\n [\n OrExpr,\n function (this: Generator, e: OrExpr) {\n return this.binary(e, '||');\n },\n ],\n [OrderedExpr, orderedJs],\n [StarExpr, () => '1'],\n ]);\n\n // Map all registered functions to rename (like Python's ALL_FUNCTIONS -> _rename)\n for (const cls of ALL_FUNCTIONS) {\n if (!overrides.has(cls as typeof Expression)) {\n overrides.set(cls as typeof Expression, rename);\n }\n }\n\n return overrides;\n }\n}\n\nexport class JavascriptDialect extends Dialect {\n static Generator = JavascriptGenerator;\n}\n\nexport class JavascriptExecutor {\n public generator: Generator;\n public env: Record<string, unknown>;\n public tables: Record<string, Table>;\n\n constructor (env?: Record<string, unknown>, tables?: Record<string, Table>) {\n this.generator = new JavascriptDialect().generator({\n identify: true,\n comments: false,\n });\n this.env = {\n ...ENV,\n ...(env || {}),\n };\n this.tables = tables || {};\n }\n\n /**\n * Traverses the logical execution plan DAG from leaves to root,\n * evaluating each step and passing the context upwards.\n */\n execute (plan: Plan): Table {\n const finished = new Set<Scan | Aggregate | Join | Sort | SetOperation>();\n const queue = new Set<Scan | Aggregate | Join | Sort | SetOperation>(plan.leaves as Iterable<Scan>);\n const contexts = new Map<Scan | Aggregate | Join | Sort | SetOperation, Context>();\n\n while (0 < queue.size) {\n const node = Array.from(queue).pop();\n if (!node) break;\n queue.delete(node);\n\n try {\n const contextTables = new Map<string, Table>();\n for (const dep of node.dependencies) {\n const depCtx = contexts.get(dep as Scan);\n if (depCtx) {\n for (const [name, table] of depCtx.tables) {\n contextTables.set(name, table);\n }\n }\n }\n\n const context = this.context(contextTables);\n\n if (node instanceof Scan) {\n contexts.set(node, this.scan(node, context));\n } else if (node instanceof Aggregate) {\n contexts.set(node, this.aggregate(node, context));\n } else if (node instanceof Join) {\n contexts.set(node, this.join(node, context));\n } else if (node instanceof Sort) {\n contexts.set(node, this.sort(node, context));\n } else if (node instanceof SetOperation) {\n contexts.set(node, this.setOperation(node, context));\n } else {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n throw new Error(`NotImplementedError: ${(node as any).constructor.name}`);\n }\n\n finished.add(node);\n\n for (const dep of node.dependents) {\n if (Array.from(dep.dependencies).every((d) => contexts.has(d as Scan))) {\n queue.add(dep as Scan);\n }\n }\n\n for (const dep of node.dependencies) {\n if (Array.from(dep.dependents).every((d) => finished.has(d as Scan))) {\n contexts.delete(dep as Scan);\n }\n }\n } catch (e: unknown) {\n throw new ExecuteError(`Step '${node.name}' failed: ${(e as Error).message}`);\n }\n }\n\n const root = plan.root;\n if (!root) return new Table([]);\n return contexts.get(root as Scan)?.tables.get(root.name) ?? new Table([]);\n }\n\n /** Convert a SQL expression into literal JS code string. */\n generate (expression: Expression | undefined): string | undefined {\n if (!expression) return undefined;\n return this.generator.generate(expression);\n }\n\n /** Convert an array of SQL expressions into an array of JS code strings. */\n generateTuple (expressions: Expression[]): string[] {\n if (!expressions || expressions.length === 0) return [];\n return expressions.map((e) => this.generate(e) ?? '');\n }\n\n context (tables: Map<string, Table> | Record<string, Table>): Context {\n const map = tables instanceof Map ? tables : new Map(Object.entries(tables));\n return new Context(map, this.env);\n }\n\n table (expressions: unknown[]): Table {\n const names = expressions.map((e) => (e instanceof Expression ? e.aliasOrName : e));\n return new Table(names as string[]);\n }\n\n scan (step: Scan, context: Context): Context {\n let source: string | undefined = undefined;\n\n if (step.source && step.source instanceof Expression) {\n source = step.source.name || step.source.alias;\n } else if (typeof step.source === 'string') {\n source = step.source;\n }\n\n let tableIter: Iterable<unknown>;\n\n if (source === undefined) {\n const staticResult = this.static();\n context = staticResult[0];\n tableIter = staticResult[1];\n } else if (context.has(source)) {\n if (!step.projections?.length && !step.condition) {\n const srcTable = context.tables.get(source);\n if (!srcTable) return context;\n return this.context({ [step.name]: srcTable });\n }\n tableIter = context.tableIter(source);\n } else {\n const scanResult = this.scanTable(step);\n context = scanResult[0];\n tableIter = scanResult[1];\n }\n\n return this.context({ [step.name]: this.projectAndFilter(context, step, tableIter) });\n }\n\n projectAndFilter (context: Context, step: Step, tableIter: Iterable<unknown>): Table {\n const sink = this.table(step.projections?.length ? step.projections : context.columns);\n const condition = this.generate(step.condition);\n const projections = this.generateTuple(step.projections || []);\n\n // Handle self-referential projections: if projections reference scope[stepName] but stepName\n // is not in the context, add an empty table with that name so scope references can resolve.\n // This happens when a Scan step has projections like scope[\"_0\"][\"x\"] and is named \"_0\".\n if (0 < projections.length && projections[0].includes(`scope[\"${step.name}\"]`) && !context.has(step.name)) {\n // Create empty table with sink columns to allow scope references to find it\n const contextTables: Record<string, Table> = {};\n for (const [k, v] of context.tables) {\n if (v) contextTables[k] = v;\n }\n contextTables[step.name] = new Table(sink.columns);\n context = this.context(new Map(Object.entries(contextTables)));\n }\n\n for (const reader of tableIter) {\n const r = Array.isArray(reader) ? reader[0] : reader;\n\n if (step.limit <= sink.length) {\n break;\n }\n\n if (condition && !context.eval(condition)) {\n continue;\n }\n\n if (0 < projections.length) {\n sink.append(context.evalTuple(projections));\n } else {\n sink.append((r as RowReader).row);\n }\n }\n\n return sink;\n }\n\n static (): [Context, RowReader[]] {\n return [this.context(new Map()), [new RowReader([])]];\n }\n\n scanTable (step: Scan): [Context, Iterable<unknown>] {\n const tables = this.tables;\n const source = step.source as TableExpr;\n\n // Build the nested path for table lookup (catalog.db.table)\n let table: unknown;\n\n if (source.args.catalog || source.args.db) {\n // Qualified table reference: try catalog.db.table path\n table = tables;\n if (source.args.catalog) {\n const catalogName = typeof source.args.catalog === 'string'\n ? source.args.catalog\n : (source.args.catalog as Expression).name;\n table = (table as Record<string, unknown>)[catalogName];\n }\n if (source.args.db && table !== undefined) {\n const dbName = typeof source.args.db === 'string'\n ? source.args.db\n : (source.args.db as Expression).name;\n table = (table as Record<string, unknown>)[dbName];\n }\n if (table !== undefined) {\n const tableName = source.name;\n table = (table as Record<string, unknown>)[tableName];\n }\n } else {\n // Unqualified table reference: try flat lookup first\n const tableName = source.name;\n table = (tables as Record<string, unknown>)[tableName];\n\n // If not found in flat structure, search in up to 2 levels deep for catalog.db.table structure\n if (!table || (typeof table === 'object' && !(table instanceof Table))) {\n for (const level1 of Object.values(tables)) {\n if (level1 && typeof level1 === 'object' && !(level1 instanceof Table)) {\n const candidate = (level1 as Record<string, unknown>)[tableName];\n if (candidate instanceof Table) {\n table = candidate;\n break;\n }\n // Try two levels deeper\n for (const level2 of Object.values(level1 as Record<string, unknown>)) {\n if (level2 && typeof level2 === 'object' && !(level2 instanceof Table)) {\n const candidate2 = (level2 as Record<string, unknown>)[tableName];\n if (candidate2 instanceof Table) {\n table = candidate2;\n break;\n }\n }\n }\n }\n if (table instanceof Table) break;\n }\n }\n }\n\n const resultTable = table instanceof Table ? table : new Table([]);\n\n const contextMap = new Map<string, Table>();\n contextMap.set(source.aliasOrName, resultTable);\n const context = this.context(contextMap);\n\n return [context, resultTable[Symbol.iterator]()];\n }\n\n join (step: Join, context: Context): Context {\n const source = step.sourceName;\n\n if (!source) return context;\n const sourceTable = context.tables.get(source) ?? new Table([]);\n let sourceContext = this.context(new Map([[source, sourceTable]]));\n\n const columnRanges = new Map<string, { start: number;\n stop: number; }>();\n columnRanges.set(source, {\n start: 0,\n stop: sourceTable.columns.length,\n });\n\n for (const [name, join] of Object.entries(step.joins || {})) {\n const table = context.tables.get(name) ?? new Table([]);\n const start = Math.max(...Array.from(columnRanges.values()).map((r) => r.stop));\n columnRanges.set(name, {\n start,\n stop: table.columns.length + start,\n });\n\n const joinContext = this.context(new Map([[name, table]]));\n let joinedTable: Table;\n\n const joinEntry = join;\n if (joinEntry.sourceKey.length) {\n joinedTable = this.hashJoin(joinEntry, sourceContext, joinContext);\n } else {\n joinedTable = this.nestedLoopJoin(joinEntry, sourceContext, joinContext);\n }\n\n const nextTables = new Map<string, Table>();\n for (const [n, cr] of columnRanges.entries()) {\n nextTables.set(n, new Table(joinedTable.columns, joinedTable.rows, cr));\n }\n sourceContext = this.context(nextTables);\n\n const condition = this.generate(joinEntry.condition);\n // Only apply condition filter for INNER joins; for LEFT/RIGHT joins, don't filter after the join\n // since the condition was already used to determine which rows match during the join\n const sideStr = String(joinEntry.side).toLowerCase();\n const isLeftOrRight = sideStr === JoinExprKind.LEFT || sideStr === JoinExprKind.RIGHT;\n if (condition && !isLeftOrRight) {\n sourceContext.filter(condition);\n }\n }\n\n if (!step.condition && !step.projections?.length) {\n return sourceContext;\n }\n\n const sink = this.projectAndFilter(\n sourceContext,\n step,\n (function* (ctx: Context) {\n for (const [reader] of ctx) {\n yield reader;\n }\n })(sourceContext),\n );\n\n if (step.projections?.length) {\n return this.context(new Map([[step.name ?? '', sink]]));\n } else {\n const finalTables = new Map<string, Table>();\n for (const [name, table] of sourceContext.tables.entries()) {\n finalTables.set(name, new Table(table.columns, sink.rows, table.columnRange));\n }\n return this.context(finalTables);\n }\n }\n\n nestedLoopJoin (_join: Join['joins'][string], sourceContext: Context, joinContext: Context): Table {\n const table = new Table([...sourceContext.columns, ...joinContext.columns]);\n\n for (const [readerA] of sourceContext) {\n for (const [readerB] of joinContext) {\n table.append([...readerA.row, ...readerB.row]);\n }\n }\n\n return table;\n }\n\n hashJoin (join: Join['joins'][string], sourceContext: Context, joinContext: Context): Table {\n const sourceKey = this.generateTuple(join.sourceKey as Expression[]);\n const joinKey = this.generateTuple(join.joinKey as Expression[]);\n const sideStr = String(join.side).toLowerCase();\n const left = sideStr === JoinExprKind.LEFT;\n const right = sideStr === JoinExprKind.RIGHT;\n\n const results = new Map<string, [unknown[][], unknown[][]]>();\n const leftRows: Array<[string, unknown[]]> = [];\n\n for (const [reader, ctx] of sourceContext) {\n const keyStr = JSON.stringify(ctx.evalTuple(sourceKey));\n if (!results.has(keyStr)) results.set(keyStr, [[], []]);\n // Copy the row to avoid shared reference issues\n results.get(keyStr)?.[0].push([...reader.row]);\n leftRows.push([keyStr, reader.row]);\n }\n\n for (const [reader, ctx] of joinContext) {\n const keyStr = JSON.stringify(ctx.evalTuple(joinKey));\n if (!results.has(keyStr)) results.set(keyStr, [[], []]);\n // Copy the row to avoid shared reference issues\n results.get(keyStr)?.[1].push([...reader.row]);\n }\n\n const table = new Table([...sourceContext.columns, ...joinContext.columns]);\n const leftNulls = [new Array(joinContext.columns.length).fill(undefined)];\n const rightNulls = [new Array(sourceContext.columns.length).fill(undefined)];\n\n if (left) {\n // For LEFT JOIN, output only keys that have left rows\n for (const [, [aGroup, bGroup]] of results.entries()) {\n if (aGroup.length === 0) continue; // Skip keys without left rows\n const finalBGroup = 0 < bGroup.length ? bGroup : leftNulls;\n for (const aRow of aGroup) {\n for (const bRow of finalBGroup) {\n table.append([...aRow, ...bRow]);\n }\n }\n }\n } else if (right) {\n // For RIGHT JOIN, output all right rows (with or without matches)\n for (const [, [aGroup, bGroup]] of results.entries()) {\n const finalAGroup = 0 < aGroup.length ? aGroup : rightNulls;\n for (const aRow of finalAGroup) {\n for (const bRow of bGroup) {\n table.append([...aRow, ...bRow]);\n }\n }\n }\n } else {\n // For INNER JOIN, output only matching rows\n for (const [, [aGroup, bGroup]] of results.entries()) {\n for (const aRow of aGroup) {\n for (const bRow of bGroup) {\n table.append([...aRow, ...bRow]);\n }\n }\n }\n }\n\n return table;\n }\n\n aggregate (step: Aggregate, context: Context): Context {\n const groupBy = this.generateTuple(Object.values(step.group || {}));\n const aggregations = this.generateTuple(step.aggregations || []);\n const operands = this.generateTuple(step.operands || []);\n\n if (0 < operands.length) {\n const operandTable = new Table(this.table(step.operands).columns);\n\n for (const [, ctx] of context) {\n operandTable.append(ctx.evalTuple(operands));\n }\n\n for (let i = 0; i < context.table.rows.length; i++) {\n context.table.rows[i] = [...context.table.rows[i], ...operandTable.rows[i]];\n }\n\n const width = context.columns.length;\n context.addColumns(...operandTable.columns);\n\n const scopedOperandTable = new Table(\n context.columns,\n context.table.rows,\n {\n start: width,\n stop: width + operandTable.columns.length,\n },\n );\n\n const newTables = new Map<string | undefined, Table>(\n [...context.tables.entries()].filter(([k, v]) => typeof k === 'string' && v instanceof Table) as [string, Table][],\n );\n newTables.set(undefined, scopedOperandTable);\n context = this.context(newTables as Map<string, Table>);\n }\n\n context.sort(groupBy);\n\n let group: string | undefined = undefined;\n let start = 0;\n let end = 1;\n const length = context.table.length;\n const groupKeyNames = Object.keys(step.group || {});\n const table = this.table([...groupKeyNames, ...(step.aggregations || [])]);\n\n const addRow = () => {\n const parsedGroup: unknown[] = group ? JSON.parse(group) : [];\n table.append([...parsedGroup, ...context.evalTuple(aggregations)]);\n };\n\n if (0 < length) {\n for (let i = 0; i < length; i++) {\n context.setIndex(i);\n const key = JSON.stringify(context.evalTuple(groupBy));\n group = group === undefined ? key : group;\n end += 1;\n\n if (key !== group) {\n context.setRange(start, end - 2);\n addRow();\n group = key;\n start = end - 2;\n }\n\n if (step.limit <= table.rows.length) {\n break;\n }\n\n if (i === length - 1) {\n context.setRange(start, end - 1);\n addRow();\n }\n }\n } else if (0 < step.limit && groupBy.length === 0) {\n context.setRange(0, 0);\n table.append(context.evalTuple(aggregations));\n }\n\n const nextTables = new Map<string, Table>();\n nextTables.set(step.name, table);\n for (const [name] of context.tables.entries()) {\n nextTables.set(name, table);\n }\n context = this.context(nextTables);\n\n if (step.projections?.length || step.condition) {\n return this.scan(step, context);\n }\n return context;\n }\n\n sort (step: Sort, context: Context): Context {\n const projections = this.generateTuple(step.projections || []);\n const projectionColumns: string[] = (step.projections || []).map((p: Expression) => p.aliasOrName);\n const allColumns = [...context.columns, ...projectionColumns];\n const sink = this.table(allColumns);\n\n const sortKeys = this.generateTuple(step.key || []);\n\n for (const [reader, ctx] of context) {\n const projVals = ctx.evalTuple(projections);\n sink.append([...reader.row, ...projVals]);\n }\n\n const sortTables = new Map<string | undefined, Table>();\n sortTables.set(undefined, sink);\n for (const name of context.tables.keys()) {\n sortTables.set(name, sink);\n }\n const sortContext = this.context(sortTables as Map<string, Table>);\n sortContext.sort(sortKeys);\n\n if (Number.isFinite(step.limit)) {\n sortContext.table.rows = sortContext.table.rows.slice(0, step.limit);\n }\n\n const colStart = context.columns.length;\n const colEnd = allColumns.length;\n const output = new Table(\n projectionColumns,\n sortContext.table.rows.map((r: unknown[]) => r.slice(colStart, colEnd)),\n );\n\n return this.context(new Map([[step.name ?? '', output]]));\n }\n\n setOperation (step: SetOperation, context: Context): Context {\n const left = context.tables.get(step.left ?? '') ?? new Table([]);\n const right = context.tables.get(step.right ?? '') ?? new Table([]);\n\n const sink = this.table(left.columns);\n\n if (step.op && (step.op === IntersectExpr || step.op.prototype instanceof IntersectExpr)) {\n const leftSet = new Set(left.rows.map((r) => JSON.stringify(r)));\n const rightSet = new Set(right.rows.map((r) => JSON.stringify(r)));\n sink.rows = [...leftSet].filter((r) => rightSet.has(r)).map((r) => JSON.parse(r));\n } else if (step.op && (step.op === ExceptExpr || step.op.prototype instanceof ExceptExpr)) {\n const rightSet = new Set(right.rows.map((r) => JSON.stringify(r)));\n sink.rows = left.rows.filter((r) => !rightSet.has(JSON.stringify(r)));\n } else if (step.op && (step.op === UnionExpr || step.op.prototype instanceof UnionExpr) && step.distinct) {\n const combined = new Set([...left.rows, ...right.rows].map((r) => JSON.stringify(r)));\n sink.rows = [...combined].map((r) => JSON.parse(r));\n } else {\n sink.rows = [...left.rows, ...right.rows];\n }\n\n if (Number.isFinite(step.limit)) {\n sink.rows = sink.rows.slice(0, step.limit);\n }\n\n return this.context(new Map([[step.name, sink]]));\n }\n}\n"]}
|