@sqg/sqg 0.1.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/LICENSE +201 -0
- package/dist/sqg.mjs +1569 -0
- package/dist/templates/better-sqlite3.hbs +96 -0
- package/dist/templates/java-duckdb-arrow.hbs +106 -0
- package/dist/templates/java-jdbc.hbs +169 -0
- package/dist/templates/typescript-duckdb.hbs +89 -0
- package/package.json +69 -0
package/dist/sqg.mjs
ADDED
|
@@ -0,0 +1,1569 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exit } from "node:process";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import consola, { LogLevels } from "consola";
|
|
5
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
8
|
+
import Handlebars from "handlebars";
|
|
9
|
+
import YAML from "yaml";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { DuckDBEnumType, DuckDBInstance, DuckDBListType, DuckDBMapType, DuckDBStructType } from "@duckdb/node-api";
|
|
12
|
+
import { LRParser } from "@lezer/lr";
|
|
13
|
+
import { camelCase, isNotNil, pascalCase, sortBy } from "es-toolkit";
|
|
14
|
+
import { Client } from "pg";
|
|
15
|
+
import types from "pg-types";
|
|
16
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
17
|
+
import { camelCase as camelCase$1, pascalCase as pascalCase$1 } from "es-toolkit/string";
|
|
18
|
+
import prettier from "prettier/standalone";
|
|
19
|
+
import prettierPluginJava from "prettier-plugin-java";
|
|
20
|
+
import typescriptPlugin from "prettier/parser-typescript";
|
|
21
|
+
import estree from "prettier/plugins/estree";
|
|
22
|
+
|
|
23
|
+
//#region src/parser/sql-parser.ts
|
|
24
|
+
const parser = LRParser.deserialize({
|
|
25
|
+
version: 14,
|
|
26
|
+
states: "&SOVQPOOO_QPO'#CwOdQPO'#CzOiQPO'#CvOnQQO'#C^OOQO'#Cn'#CnQVQPOOO!SQSO,59cO!_QPO,59fOOQO'#Cp'#CpO!gQQO,59bOOQO'#C}'#C}O#RQQO'#CiOOQO,58x,58xOOQO-E6l-E6lOOQO'#Co'#CoO!SQSO1G.}O!VQSO1G.}OOQO'#Cb'#CbOOQO1G.}1G.}O#cQPO1G/QOOQO-E6n-E6nO#kQPO'#CdOOQO'#Cq'#CqO#pQQO1G.|OOQO'#Cm'#CmOOQO'#Cr'#CrO$XQQO,59TOOQO-E6m-E6mO!VQSO7+$iOOQO7+$i7+$iO$iQPO,59OOOQO-E6o-E6oOOQO-E6p-E6pOOQO<<HT<<HTO$nQQO1G.jOOQO'#Ce'#CeOiQPO7+$UO$yQQO<<Gp",
|
|
27
|
+
stateData: "%r~OiOS~ORPOVQO~OSVO~OSWO~OlXO~OYZOZZO[ZO^ZO_ZO`ZO~OT_OlXOmbO~OT_Olna~OlXOofOYjaZja[ja^ja_ja`ja~OliOR]XV]Xg]X~PnOT_Olni~OSoO~OofOYjiZji[ji^ji_ji`ji~OliOR]aV]ag]a~PnOpsO~OYtOZtO[tO~OlXOYWyZWy[Wy^Wy_Wy`WyoWy~OR`o^iZTmYV_[~",
|
|
28
|
+
goto: "#prPPsPPPwP!R!VPPP!YPPP!]!a!g!q#T#ZPPP#a#ePP#ePP#iTTOUQcVSn`aRrmTgYhRusR]STj[kQUOR^UQ`VQdWTl`dQYRQaVWeYamvQm`RvuQhYRphQk[RqkTSOUTROUQ[STj[k",
|
|
29
|
+
nodeNames: "⚠ File QueryBlock BlockCommentStartSpecial Name Modifiers Config LineCommentStartSpecial SetVarLine Value StringLiteral StringLiteralSingle SQLText SQLBlock BlockComment LineComment VarRef BR",
|
|
30
|
+
maxTerm: 33,
|
|
31
|
+
skippedNodes: [0],
|
|
32
|
+
repeatNodeCount: 5,
|
|
33
|
+
tokenData: "$+x~RqOX#YXY'wYZ(iZ]#Y]^$W^p#Ypq'wqr#Yrs(}st#Ytu6^uw#Ywx9[xz#Yz{%_{}#Y}!OKi!O!P#Y!P!Q# n!Q![$!S![!]$#l!]!_#Y!_!`$&l!`!b#Y!b!c$'l!c!}$!S!}#R#Y#R#S$!S#S#T#Y#T#o$!S#o;'S#Y;'S;=`'q<%lO#YU#_][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YS$ZTOz$Wz{$j{;'S$W;'S;=`%X<%lO$WS$mVOz$Wz{$j{!P$W!P!Q%S!Q;'S$W;'S;=`%X<%lO$WS%XOmSS%[P;=`<%l$WU%d_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!P#Y!P!Q&c!Q;'S#Y;'S;=`'q<%lO#YU&jVmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'UV[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'nP;=`<%l'PU'tP;=`<%l#Y~'|Xi~OX$WXY'wYp$Wpq'wqz$Wz{$j{;'S$W;'S;=`%X<%lO$W~(nTl~Oz$Wz{$j{;'S$W;'S;=`%X<%lO$WU)Sb[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U*_ZOY*[YZ$WZr*[rs+Qsz*[z{+f{#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U+VTYQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU+i]OY*[YZ$WZr*[rs+Qsz*[z{+f{!P*[!P!Q,b!Q#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U,gWmSOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-SWOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-qOYQQ-tTOY-PYZ-PZ;'S-P;'S;=`.T<%lO-PQ.WP;=`<%l-PU.^VOY*[YZ*[Zz*[z{+f{;'S*[;'S;=`.s<%lO*[U.vP;=`<%l*[U/Q]YQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU0Od[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{!P(}!P!Q1^!Q#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U1e_mS[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ2i_[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ3oVYQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ4Z[[QOX2dXY-PYZ-PZ]2d]^-P^p2dpq-Pqt2dtu-Pu;'S2d;'S;=`5P<%lO2dQ5SP;=`<%l2dU5[^[QOX(}XY*[YZ*[Z](}]^*[^p(}pq*[qt(}tu*[uz(}z{/y{;'S(};'S;=`6W<%lO(}U6ZP;=`<%l(}U6cV[QOz$Wz{$j{#o$W#o#p6x#p;'S$W;'S;=`%X<%lO$WU6{]Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o;'S$W;'S;=`%X<%lO$WU7w_Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o#q$W#q#r8v#r;'S$W;'S;=`%X<%lO$WU8{T`QOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU9ab[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[U:lZOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU;dTZQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU;v]OY:iYZ$WZw:iwx;_xz:iz{;s{!P:i!P!Q<o!Q#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU<tWmSOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q=aWOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>OOZQQ>RXOY=^YZ=^Zw=^wx>nx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>sWZQOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q?`P;=`<%l=^U?fZOY:iYZ:iZw:iwx@Xxz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU@^ZZQOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iUASP;=`<%l:iUA^]ZQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YUB[d[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{!P9[!P!QCj!Q#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UCq_mS[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQDu_[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQE{VZQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQFg`[QOXDpXY=^YZ=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxGix#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQGp_ZQ[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQHrP;=`<%lDpUHzb[QOX9[XY:iYZ:iZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxJSxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UJZbZQ[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UKfP;=`<%l9[VKn_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{}#Y}!OLm!O;'S#Y;'S;=`'q<%lO#YVLtf_Q[QOXNYXY!&vYZ$WZ]NY]^! ]^pNYpq!&vqtNYtu! ]uzNYz{!#k{!gNY!g!h!6m!h!oNY!o!p!;_!p!sNY!s!t!Bg!t!vNY!v!w!G]!w;'SNY;'S;=`!&p<%lONYUNa^_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYU! bV_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]U! |X_QOY! ]YZ$WZz! ]z{! w{!P! ]!P!Q!!i!Q;'S! ];'S;=`!#e<%lO! ]U!!pSmS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#RS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#bP;=`<%l!!|U!#hP;=`<%l! ]U!#r`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!PNY!P!Q!$t!Q;'SNY;'S;=`!&p<%lONYU!$}ZmS_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!%wZ_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!&mP;=`<%l!%pU!&sP;=`<%lNYV!&{b_QOX! ]XY!&vYZ$WZp! ]pq!&vqz! ]z{! w{!g! ]!g!h!(T!h!o! ]!o!p!*u!p!s! ]!s!t!.}!t!v! ]!v!w!1s!w;'S! ];'S;=`!#e<%lO! ]V!(YX_QOY! ]YZ$WZz! ]z{! w{!z! ]!z!{!(u!{;'S! ];'S;=`!#e<%lO! ]V!(zX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!)g!h;'S! ];'S;=`!#e<%lO! ]V!)lX_QOY! ]YZ$WZz! ]z{! w{!e! ]!e!f!*X!f;'S! ];'S;=`!#e<%lO! ]V!*`VVR_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]V!*zX_QOY! ]YZ$WZz! ]z{! w{!k! ]!k!l!+g!l;'S! ];'S;=`!#e<%lO! ]V!+lX_QOY! ]YZ$WZz! ]z{! w{!i! ]!i!j!,X!j;'S! ];'S;=`!#e<%lO! ]V!,^X_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!,y!u;'S! ];'S;=`!#e<%lO! ]V!-OX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!-k!d;'S! ];'S;=`!#e<%lO! ]V!-pX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!.]!w;'S! ];'S;=`!#e<%lO! ]V!.bX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!*X!h;'S! ];'S;=`!#e<%lO! ]V!/SX_QOY! ]YZ$WZz! ]z{! w{!w! ]!w!x!/o!x;'S! ];'S;=`!#e<%lO! ]V!/tX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!0a!h;'S! ];'S;=`!#e<%lO! ]V!0fX_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!1R!u;'S! ];'S;=`!#e<%lO! ]V!1WX_QOY! ]YZ$WZz! ]z{! w{!{! ]!{!|!*X!|;'S! ];'S;=`!#e<%lO! ]V!1xX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!2e!h;'S! ];'S;=`!#e<%lO! ]V!2jX_QOY! ]YZ$WZz! ]z{! w{!u! ]!u!v!3V!v;'S! ];'S;=`!#e<%lO! ]V!3[X_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!3w!w;'S! ];'S;=`!#e<%lO! ]V!3|X_QOY! ]YZ$WZz! ]z{! w{!f! ]!f!g!4i!g;'S! ];'S;=`!#e<%lO! ]V!4nX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!5Z!d;'S! ];'S;=`!#e<%lO! ]V!5`X_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!5{!w;'S! ];'S;=`!#e<%lO! ]V!6QX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!*X!d;'S! ];'S;=`!#e<%lO! ]V!6t`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!zNY!z!{!7v!{;'SNY;'S;=`!&p<%lONYV!7}`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!9P!h;'SNY;'S;=`!&p<%lONYV!9W`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!eNY!e!f!:Y!f;'SNY;'S;=`!&p<%lONYV!:c^VR_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYV!;f`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!kNY!k!l!<h!l;'SNY;'S;=`!&p<%lONYV!<o`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!iNY!i!j!=q!j;'SNY;'S;=`!&p<%lONYV!=x`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!>z!u;'SNY;'S;=`!&p<%lONYV!?R`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!@T!d;'SNY;'S;=`!&p<%lONYV!@[`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!A^!w;'SNY;'S;=`!&p<%lONYV!Ae`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!:Y!h;'SNY;'S;=`!&p<%lONYV!Bn`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!wNY!w!x!Cp!x;'SNY;'S;=`!&p<%lONYV!Cw`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!Dy!h;'SNY;'S;=`!&p<%lONYV!EQ`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!FS!u;'SNY;'S;=`!&p<%lONYV!FZ`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!{NY!{!|!:Y!|;'SNY;'S;=`!&p<%lONYV!Gd`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!Hf!h;'SNY;'S;=`!&p<%lONYV!Hm`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!uNY!u!v!Io!v;'SNY;'S;=`!&p<%lONYV!Iv`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Jx!w;'SNY;'S;=`!&p<%lONYV!KP`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!fNY!f!g!LR!g;'SNY;'S;=`!&p<%lONYV!LY`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!M[!d;'SNY;'S;=`!&p<%lONYV!Mc`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Ne!w;'SNY;'S;=`!&p<%lONYV!Nl`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!:Y!d;'SNY;'S;=`!&p<%lONYV# s][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{#!l{;'S#Y;'S;=`'q<%lO#YV#!qh[QOX#$]XY#(^YZ#(^Z]#$]]^#%Z^p#$]pq#(^qt#$]tu#%Zuz#$]z{#&d{!P#$]!P!Q#4c!Q!g#$]!g!h#9`!h!o#$]!o!p#=l!p!s#$]!s!t#DU!t!v#$]!v!w#Hf!w;'S#$];'S;=`#(W<%lO#$]U#$b][QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{;'S#$];'S;=`#(W<%lO#$]U#%^TOz#%Zz{#%m{;'S#%Z;'S;=`#&^<%lO#%ZU#%pVOz#%Zz{#%m{!P#%Z!P!Q#&V!Q;'S#%Z;'S;=`#&^<%lO#%ZU#&^O^QmSU#&aP;=`<%l#%ZU#&i_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!P#$]!P!Q#'h!Q;'S#$];'S;=`#(W<%lO#$]U#'qV^QmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PU#(ZP;=`<%l#$]V#(abOX#%ZXY#(^YZ#(^Zp#%Zpq#(^qz#%Zz{#%m{!g#%Z!g!h#)i!h!o#%Z!o!p#+i!p!s#%Z!s!t#.o!t!v#%Z!v!w#0s!w;'S#%Z;'S;=`#&^<%lO#%ZV#)lVOz#%Zz{#%m{!z#%Z!z!{#*R!{;'S#%Z;'S;=`#&^<%lO#%ZV#*UVOz#%Zz{#%m{!g#%Z!g!h#*k!h;'S#%Z;'S;=`#&^<%lO#%ZV#*nVOz#%Zz{#%m{!e#%Z!e!f#+T!f;'S#%Z;'S;=`#&^<%lO#%ZV#+YTRROz#%Zz{#%m{;'S#%Z;'S;=`#&^<%lO#%ZV#+lVOz#%Zz{#%m{!k#%Z!k!l#,R!l;'S#%Z;'S;=`#&^<%lO#%ZV#,UVOz#%Zz{#%m{!i#%Z!i!j#,k!j;'S#%Z;'S;=`#&^<%lO#%ZV#,nVOz#%Zz{#%m{!t#%Z!t!u#-T!u;'S#%Z;'S;=`#&^<%lO#%ZV#-WVOz#%Zz{#%m{!c#%Z!c!d#-m!d;'S#%Z;'S;=`#&^<%lO#%ZV#-pVOz#%Zz{#%m{!v#%Z!v!w#.V!w;'S#%Z;'S;=`#&^<%lO#%ZV#.YVOz#%Zz{#%m{!g#%Z!g!h#+T!h;'S#%Z;'S;=`#&^<%lO#%ZV#.rVOz#%Zz{#%m{!w#%Z!w!x#/X!x;'S#%Z;'S;=`#&^<%lO#%ZV#/[VOz#%Zz{#%m{!g#%Z!g!h#/q!h;'S#%Z;'S;=`#&^<%lO#%ZV#/tVOz#%Zz{#%m{!t#%Z!t!u#0Z!u;'S#%Z;'S;=`#&^<%lO#%ZV#0^VOz#%Zz{#%m{!{#%Z!{!|#+T!|;'S#%Z;'S;=`#&^<%lO#%ZV#0vVOz#%Zz{#%m{!g#%Z!g!h#1]!h;'S#%Z;'S;=`#&^<%lO#%ZV#1`VOz#%Zz{#%m{!u#%Z!u!v#1u!v;'S#%Z;'S;=`#&^<%lO#%ZV#1xVOz#%Zz{#%m{!v#%Z!v!w#2_!w;'S#%Z;'S;=`#&^<%lO#%ZV#2bVOz#%Zz{#%m{!f#%Z!f!g#2w!g;'S#%Z;'S;=`#&^<%lO#%ZV#2zVOz#%Zz{#%m{!c#%Z!c!d#3a!d;'S#%Z;'S;=`#&^<%lO#%ZV#3dVOz#%Zz{#%m{!v#%Z!v!w#3y!w;'S#%Z;'S;=`#&^<%lO#%ZV#3|VOz#%Zz{#%m{!c#%Z!c!d#+T!d;'S#%Z;'S;=`#&^<%lO#%ZU#4j]mS[QOX#5cXZ#6aZ]#5c]^#6a^p#5cpq#6aqt#5ctu#6auz#5cz{#7h{;'S#5c;'S;=`#9Y<%lO#5cQ#5h][QOX#5cXZ#6aZ]#5c]^#6a^p#5cpq#6aqt#5ctu#6auz#5cz{#7h{;'S#5c;'S;=`#9Y<%lO#5cQ#6dTOz#6az{#6s{;'S#6a;'S;=`#7b<%lO#6aQ#6vVOz#6az{#6s{!P#6a!P!Q#7]!Q;'S#6a;'S;=`#7b<%lO#6aQ#7bO^QQ#7eP;=`<%l#6aQ#7m_[QOX#5cXZ#6aZ]#5c]^#6a^p#5cpq#6aqt#5ctu#6auz#5cz{#7h{!P#5c!P!Q#8l!Q;'S#5c;'S;=`#9Y<%lO#5cQ#8sV^Q[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ#9]P;=`<%l#5cV#9e_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!z#$]!z!{#:d!{;'S#$];'S;=`#(W<%lO#$]V#:i_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!g#$]!g!h#;h!h;'S#$];'S;=`#(W<%lO#$]V#;m_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!e#$]!e!f#<l!f;'S#$];'S;=`#(W<%lO#$]V#<s]RR[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{;'S#$];'S;=`#(W<%lO#$]V#=q_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!k#$]!k!l#>p!l;'S#$];'S;=`#(W<%lO#$]V#>u_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!i#$]!i!j#?t!j;'S#$];'S;=`#(W<%lO#$]V#?y_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!t#$]!t!u#@x!u;'S#$];'S;=`#(W<%lO#$]V#@}_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!c#$]!c!d#A|!d;'S#$];'S;=`#(W<%lO#$]V#BR_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!v#$]!v!w#CQ!w;'S#$];'S;=`#(W<%lO#$]V#CV_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!g#$]!g!h#<l!h;'S#$];'S;=`#(W<%lO#$]V#DZ_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!w#$]!w!x#EY!x;'S#$];'S;=`#(W<%lO#$]V#E__[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!g#$]!g!h#F^!h;'S#$];'S;=`#(W<%lO#$]V#Fc_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!t#$]!t!u#Gb!u;'S#$];'S;=`#(W<%lO#$]V#Gg_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!{#$]!{!|#<l!|;'S#$];'S;=`#(W<%lO#$]V#Hk_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!g#$]!g!h#Ij!h;'S#$];'S;=`#(W<%lO#$]V#Io_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!u#$]!u!v#Jn!v;'S#$];'S;=`#(W<%lO#$]V#Js_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!v#$]!v!w#Kr!w;'S#$];'S;=`#(W<%lO#$]V#Kw_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!f#$]!f!g#Lv!g;'S#$];'S;=`#(W<%lO#$]V#L{_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!c#$]!c!d#Mz!d;'S#$];'S;=`#(W<%lO#$]V#NP_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!v#$]!v!w$ O!w;'S#$];'S;=`#(W<%lO#$]V$ T_[QOX#$]XZ#%ZZ]#$]]^#%Z^p#$]pq#%Zqt#$]tu#%Zuz#$]z{#&d{!c#$]!c!d#<l!d;'S#$];'S;=`#(W<%lO#$]V$!ZeSP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$!S![!c#Y!c!}$!S!}#R#Y#R#S$!S#S#T#Y#T#o$!S#o;'S#Y;'S;=`'q<%lO#Y~$#qe[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$%S![!c#Y!c!}$%S!}#R#Y#R#S$%S#S#T#Y#T#o$%S#o;'S#Y;'S;=`'q<%lO#Y~$%ZeT~[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$%S![!c#Y!c!}$%S!}#R#Y#R#S$%S#S#T#Y#T#o$%S#o;'S#Y;'S;=`'q<%lO#YV$&s]pP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU$'q_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#g#Y#g#h$(p#h;'S#Y;'S;=`'q<%lO#YU$(u_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#X#Y#X#Y$)t#Y;'S#Y;'S;=`'q<%lO#YU$)y_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#h#Y#h#i$*x#i;'S#Y;'S;=`'q<%lO#YU$+P]oQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#Y",
|
|
34
|
+
tokenizers: [
|
|
35
|
+
0,
|
|
36
|
+
1,
|
|
37
|
+
2
|
|
38
|
+
],
|
|
39
|
+
topRules: { "File": [0, 1] },
|
|
40
|
+
tokenPrec: 205
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/sql-query.ts
|
|
45
|
+
var ListType = class {
|
|
46
|
+
constructor(baseType) {
|
|
47
|
+
this.baseType = baseType;
|
|
48
|
+
}
|
|
49
|
+
toString() {
|
|
50
|
+
return `${this.baseType.toString()}[]`;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var StructType = class {
|
|
54
|
+
constructor(fields) {
|
|
55
|
+
this.fields = fields;
|
|
56
|
+
}
|
|
57
|
+
toString() {
|
|
58
|
+
return `STRUCT(${this.fields.map((f) => `"${f.name}" ${f.type.toString()}`).join(", ")})`;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var MapType = class {
|
|
62
|
+
constructor(keyType, valueType) {
|
|
63
|
+
this.keyType = keyType;
|
|
64
|
+
this.valueType = valueType;
|
|
65
|
+
}
|
|
66
|
+
toString() {
|
|
67
|
+
return `MAP(${this.keyType.toString()}, ${this.valueType.toString()})`;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var EnumType = class {
|
|
71
|
+
constructor(values) {
|
|
72
|
+
this.values = values;
|
|
73
|
+
}
|
|
74
|
+
toString() {
|
|
75
|
+
return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var SQLQuery = class {
|
|
79
|
+
columns;
|
|
80
|
+
allColumns;
|
|
81
|
+
constructor(filename, id, rawQuery, queryAnonymous, queryNamed, queryPositional, type, isOne, isPluck, variables, config) {
|
|
82
|
+
this.filename = filename;
|
|
83
|
+
this.id = id;
|
|
84
|
+
this.rawQuery = rawQuery;
|
|
85
|
+
this.queryAnonymous = queryAnonymous;
|
|
86
|
+
this.queryNamed = queryNamed;
|
|
87
|
+
this.queryPositional = queryPositional;
|
|
88
|
+
this.type = type;
|
|
89
|
+
this.isOne = isOne;
|
|
90
|
+
this.isPluck = isPluck;
|
|
91
|
+
this.variables = variables;
|
|
92
|
+
this.config = config;
|
|
93
|
+
this.columns = [];
|
|
94
|
+
}
|
|
95
|
+
get isQuery() {
|
|
96
|
+
return this.type === "QUERY";
|
|
97
|
+
}
|
|
98
|
+
get isExec() {
|
|
99
|
+
return this.type === "EXEC";
|
|
100
|
+
}
|
|
101
|
+
get isMigrate() {
|
|
102
|
+
return this.type === "MIGRATE";
|
|
103
|
+
}
|
|
104
|
+
get isTestdata() {
|
|
105
|
+
return this.type === "TESTDATA";
|
|
106
|
+
}
|
|
107
|
+
get skipGenerateFunction() {
|
|
108
|
+
return this.isTestdata || this.isMigrate || this.id.startsWith("_");
|
|
109
|
+
}
|
|
110
|
+
validateVariables() {
|
|
111
|
+
const missingVars = [];
|
|
112
|
+
const varRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
113
|
+
let match;
|
|
114
|
+
while (true) {
|
|
115
|
+
match = varRegex.exec(this.rawQuery);
|
|
116
|
+
if (match === null) break;
|
|
117
|
+
const varName = match[1];
|
|
118
|
+
if (!this.variables.has(varName)) missingVars.push(varName);
|
|
119
|
+
}
|
|
120
|
+
return missingVars;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function parseSQLQueries(filePath, extraVariables) {
|
|
124
|
+
const content = readFileSync(filePath, "utf-8");
|
|
125
|
+
consola.info(`Parsing SQL file: ${filePath}`);
|
|
126
|
+
consola.debug(`File start: ${content.slice(0, 200)}`);
|
|
127
|
+
const queries = [];
|
|
128
|
+
const cursor = parser.parse(content).cursor();
|
|
129
|
+
function getStr(nodeName, optional = false) {
|
|
130
|
+
const node = cursor.node.getChild(nodeName);
|
|
131
|
+
if (!node) {
|
|
132
|
+
if (optional) return;
|
|
133
|
+
throw new Error(`${nodeName} not found`);
|
|
134
|
+
}
|
|
135
|
+
return nodeStr(node);
|
|
136
|
+
}
|
|
137
|
+
function nodeStr(node) {
|
|
138
|
+
return content.slice(node.from, node.to);
|
|
139
|
+
}
|
|
140
|
+
const queryNames = /* @__PURE__ */ new Set();
|
|
141
|
+
do
|
|
142
|
+
if (cursor.name === "QueryBlock") {
|
|
143
|
+
const queryType = (getStr("LineCommentStartSpecial", true) ?? getStr("BlockCommentStartSpecial")).replace("--", "").replace("/*", "").trim();
|
|
144
|
+
const name = getStr("Name").trim();
|
|
145
|
+
const modifiers = cursor.node.getChildren("Modifiers").map((node) => nodeStr(node));
|
|
146
|
+
const isOne = modifiers.includes(":one");
|
|
147
|
+
const isPluck = modifiers.includes(":pluck");
|
|
148
|
+
let configStr = getStr("Config", true);
|
|
149
|
+
if (configStr?.endsWith("*/")) configStr = configStr.slice(0, -2);
|
|
150
|
+
let config = null;
|
|
151
|
+
if (configStr) config = Config.fromYaml(name, filePath, configStr);
|
|
152
|
+
const setVars = cursor.node.getChildren("SetVarLine");
|
|
153
|
+
const variables = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const setVar of setVars) {
|
|
155
|
+
const varName = nodeStr(setVar.getChild("Name"));
|
|
156
|
+
const value = nodeStr(setVar.getChild("Value"));
|
|
157
|
+
variables.set(varName, value.trim());
|
|
158
|
+
}
|
|
159
|
+
function getVariable(varName) {
|
|
160
|
+
if (variables.has(varName)) return variables.get(varName);
|
|
161
|
+
for (const extraVariable of extraVariables) if (extraVariable.name === varName) {
|
|
162
|
+
variables.set(varName, extraVariable.value);
|
|
163
|
+
return extraVariable.value;
|
|
164
|
+
}
|
|
165
|
+
throw error(`Variable '${varName}' not found`);
|
|
166
|
+
}
|
|
167
|
+
const sqlNode = cursor.node.getChild("SQLBlock");
|
|
168
|
+
if (!sqlNode) throw new Error(`'SQLBlock' not found`);
|
|
169
|
+
const sqlContentStr = nodeStr(sqlNode).trim();
|
|
170
|
+
const sqlCursor = sqlNode.cursor();
|
|
171
|
+
let from = -1;
|
|
172
|
+
let to = -1;
|
|
173
|
+
function error(message) {
|
|
174
|
+
return /* @__PURE__ */ new Error(`${message} in ${filePath} query '${name}': ${message}`);
|
|
175
|
+
}
|
|
176
|
+
class SQLQueryBuilder {
|
|
177
|
+
sqlParts = [];
|
|
178
|
+
appendSql(sql$1) {
|
|
179
|
+
this.sqlParts.push(sql$1);
|
|
180
|
+
}
|
|
181
|
+
appendVariable(varName, value) {
|
|
182
|
+
this.sqlParts.push({
|
|
183
|
+
name: varName,
|
|
184
|
+
value
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
trim() {
|
|
188
|
+
const lastPart = this.sqlParts.length > 0 ? this.sqlParts[this.sqlParts.length - 1] : null;
|
|
189
|
+
if (lastPart && typeof lastPart === "string") this.sqlParts[this.sqlParts.length - 1] = lastPart.trimEnd();
|
|
190
|
+
}
|
|
191
|
+
parameters() {
|
|
192
|
+
return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
|
|
193
|
+
}
|
|
194
|
+
toSqlWithAnonymousPlaceholders() {
|
|
195
|
+
let sql$1 = "";
|
|
196
|
+
const sqlParts = [];
|
|
197
|
+
for (const part of this.sqlParts) if (typeof part === "string") {
|
|
198
|
+
sql$1 += part;
|
|
199
|
+
sqlParts.push(part);
|
|
200
|
+
} else {
|
|
201
|
+
if (sql$1.length > 0) {
|
|
202
|
+
const last = sql$1[sql$1.length - 1];
|
|
203
|
+
if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql$1 += " ";
|
|
204
|
+
}
|
|
205
|
+
sql$1 += "?";
|
|
206
|
+
if (part.name.startsWith("sources_")) sqlParts.push(part);
|
|
207
|
+
else sqlParts.push("?");
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
parameters: this.parameters(),
|
|
211
|
+
sql: sql$1,
|
|
212
|
+
sqlParts
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
toSqlWithPositionalPlaceholders() {
|
|
216
|
+
const parameters = [];
|
|
217
|
+
return {
|
|
218
|
+
parameters,
|
|
219
|
+
sqlParts: [],
|
|
220
|
+
sql: this.sqlParts.map((part) => {
|
|
221
|
+
if (typeof part === "string") return part;
|
|
222
|
+
const varName = part.name;
|
|
223
|
+
const value = part.value;
|
|
224
|
+
let pos = parameters.findIndex((p) => p.name === varName);
|
|
225
|
+
if (pos < 0) {
|
|
226
|
+
parameters.push({
|
|
227
|
+
name: varName,
|
|
228
|
+
value
|
|
229
|
+
});
|
|
230
|
+
pos = parameters.length - 1;
|
|
231
|
+
}
|
|
232
|
+
return ` $${pos + 1} `;
|
|
233
|
+
}).join("").trim()
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
toSqlWithNamedPlaceholders() {
|
|
237
|
+
return {
|
|
238
|
+
parameters: this.parameters(),
|
|
239
|
+
sqlParts: [],
|
|
240
|
+
sql: this.sqlParts.map((part) => {
|
|
241
|
+
if (typeof part === "string") return part;
|
|
242
|
+
return `$${part.name}`;
|
|
243
|
+
}).join("").trim()
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const sql = new SQLQueryBuilder();
|
|
248
|
+
if (sqlCursor.firstChild()) {
|
|
249
|
+
do {
|
|
250
|
+
const child = sqlCursor.node;
|
|
251
|
+
if (child.name === "BlockComment" || child.name === "LineComment") {
|
|
252
|
+
if (to > from) sql.appendSql(content.slice(from, to));
|
|
253
|
+
from = child.to;
|
|
254
|
+
sql.appendSql(" ");
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (child.name === "VarRef") {
|
|
258
|
+
const varRef = nodeStr(child);
|
|
259
|
+
if (!varRef.startsWith("${") || !varRef.endsWith("}")) throw error(`Invalid variable reference: ${varRef}`);
|
|
260
|
+
const varName = varRef.replace("${", "").replace("}", "");
|
|
261
|
+
const value = getVariable(varName);
|
|
262
|
+
if (to > from) sql.appendSql(content.slice(from, to));
|
|
263
|
+
from = child.to;
|
|
264
|
+
sql.appendVariable(varName, value);
|
|
265
|
+
} else {
|
|
266
|
+
if (from < 0) from = child.from;
|
|
267
|
+
to = child.to;
|
|
268
|
+
}
|
|
269
|
+
} while (sqlCursor.nextSibling());
|
|
270
|
+
if (to > from) sql.appendSql(content.slice(from, to));
|
|
271
|
+
sql.trim();
|
|
272
|
+
}
|
|
273
|
+
consola.debug("Parsed query:", {
|
|
274
|
+
type: queryType,
|
|
275
|
+
name,
|
|
276
|
+
modifiers,
|
|
277
|
+
variables: Object.fromEntries(variables),
|
|
278
|
+
sqlContent: sqlContentStr,
|
|
279
|
+
sql,
|
|
280
|
+
config
|
|
281
|
+
});
|
|
282
|
+
const query = new SQLQuery(filePath, name, sqlContentStr, sql.toSqlWithAnonymousPlaceholders(), sql.toSqlWithNamedPlaceholders(), sql.toSqlWithPositionalPlaceholders(), queryType, isOne, isPluck, variables, config);
|
|
283
|
+
if (queryNames.has(name)) throw new Error(`Duplicate query name in ${filePath}: ${name}`);
|
|
284
|
+
queryNames.add(name);
|
|
285
|
+
queries.push(query);
|
|
286
|
+
consola.debug(`Added query: ${name} (${queryType})`);
|
|
287
|
+
}
|
|
288
|
+
while (cursor.next());
|
|
289
|
+
consola.info(`Total queries parsed: ${queries.length}`);
|
|
290
|
+
consola.info(`Query names: ${queries.map((q) => q.id).join(", ")}`);
|
|
291
|
+
return queries;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/db/types.ts
|
|
296
|
+
async function initializeDatabase(queries, execQueries) {
|
|
297
|
+
const migrationQueries = queries.filter((q) => q.isMigrate);
|
|
298
|
+
sortBy(migrationQueries, [(q) => Number(q.id.split("_")[1])]);
|
|
299
|
+
for (const query of migrationQueries) try {
|
|
300
|
+
await execQueries(query);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
consola.error("Failed to initialize database:" + error.message + " when running query:\n\n " + query.rawQuery);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
const testdataQueries = queries.filter((q) => q.isTestdata);
|
|
306
|
+
for (const query of testdataQueries) try {
|
|
307
|
+
await execQueries(query);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
consola.error("Failed to initialize testdata:" + error.message + " when running query:\n\n " + query.rawQuery);
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
if (migrationQueries.length + testdataQueries.length === 0) consola.warn("No migration or testdata queries found");
|
|
313
|
+
consola.success("Database initialized successfully");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/db/duckdb.ts
|
|
318
|
+
const duckdb = new class {
|
|
319
|
+
db;
|
|
320
|
+
connection;
|
|
321
|
+
async initializeDatabase(queries) {
|
|
322
|
+
this.db = await DuckDBInstance.create(":memory:");
|
|
323
|
+
this.connection = await this.db.connect();
|
|
324
|
+
await initializeDatabase(queries, async (query) => {
|
|
325
|
+
await this.connection.run(query.rawQuery);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async executeQueries(queries) {
|
|
329
|
+
const connection = this.connection;
|
|
330
|
+
if (!connection) throw new Error("Database not initialized");
|
|
331
|
+
try {
|
|
332
|
+
const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
|
|
333
|
+
for (const query of executableQueries) {
|
|
334
|
+
consola.info(`Executing query: ${query.id}`);
|
|
335
|
+
await this.executeQuery(connection, query);
|
|
336
|
+
if (query.isQuery) {}
|
|
337
|
+
consola.success(`Query ${query.id} executed successfully`);
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
consola.error("Error executing queries:", error.message);
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async executeQuery(connection, query) {
|
|
345
|
+
const statement = query.queryAnonymous;
|
|
346
|
+
try {
|
|
347
|
+
consola.debug("Query:", statement.sql, statement.sqlParts);
|
|
348
|
+
const sql = statement.sqlParts.map((part) => {
|
|
349
|
+
if (typeof part === "string") return part;
|
|
350
|
+
return ` ${part.value} `;
|
|
351
|
+
}).join("");
|
|
352
|
+
const stmt = await connection.prepare(sql);
|
|
353
|
+
for (let i = 0; i < stmt.parameterCount; i++) stmt.bindValue(i + 1, statement.parameters[i].value);
|
|
354
|
+
if (query.isQuery) {
|
|
355
|
+
const result = await stmt.stream();
|
|
356
|
+
const columnNames = result.columnNames();
|
|
357
|
+
const columnTypes = result.columnTypes();
|
|
358
|
+
consola.debug("Columns:", columnNames);
|
|
359
|
+
consola.debug("Types:", columnTypes.map((t) => `${t.toString()} / ${t.constructor.name}`));
|
|
360
|
+
function convertType(type) {
|
|
361
|
+
if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
|
|
362
|
+
if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
|
|
363
|
+
name: type.entryNames[index],
|
|
364
|
+
type: convertType(t),
|
|
365
|
+
nullable: true
|
|
366
|
+
})));
|
|
367
|
+
if (type instanceof DuckDBMapType) return new MapType({
|
|
368
|
+
name: "key",
|
|
369
|
+
type: convertType(type.keyType),
|
|
370
|
+
nullable: true
|
|
371
|
+
}, {
|
|
372
|
+
name: "value",
|
|
373
|
+
type: convertType(type.valueType),
|
|
374
|
+
nullable: true
|
|
375
|
+
});
|
|
376
|
+
if (type instanceof DuckDBEnumType) return new EnumType(type.values);
|
|
377
|
+
return type.toString();
|
|
378
|
+
}
|
|
379
|
+
query.columns = columnNames.map((name, index) => ({
|
|
380
|
+
name,
|
|
381
|
+
type: convertType(columnTypes[index]),
|
|
382
|
+
nullable: true
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
if (query.isQuery) {
|
|
386
|
+
if (query.isOne) return await stmt.runAndRead();
|
|
387
|
+
return await stmt.runAndReadAll();
|
|
388
|
+
}
|
|
389
|
+
return await stmt.run();
|
|
390
|
+
} catch (error) {
|
|
391
|
+
consola.error(`Failed to execute query '${query.id}':`, error);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
close() {
|
|
396
|
+
this.connection.closeSync();
|
|
397
|
+
}
|
|
398
|
+
}();
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/db/postgres.ts
|
|
402
|
+
const databaseName = "sqg-db-temp";
|
|
403
|
+
const connectionString = "postgresql://sqg:secret@localhost:15432/sqg-db";
|
|
404
|
+
const connectionStringTemp = `postgresql://sqg:secret@localhost:15432/${databaseName}`;
|
|
405
|
+
const typeIdToName = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
|
|
407
|
+
const postgres = new class {
|
|
408
|
+
dbInitial;
|
|
409
|
+
db;
|
|
410
|
+
async initializeDatabase(queries) {
|
|
411
|
+
this.dbInitial = new Client({ connectionString });
|
|
412
|
+
this.db = new Client({ connectionString: connectionStringTemp });
|
|
413
|
+
await this.dbInitial.connect();
|
|
414
|
+
try {
|
|
415
|
+
await this.dbInitial.query(`DROP DATABASE "${databaseName}";`);
|
|
416
|
+
} catch (error) {}
|
|
417
|
+
try {
|
|
418
|
+
await this.dbInitial.query(`CREATE DATABASE "${databaseName}";`);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
consola.error("Error creating database:", error);
|
|
421
|
+
}
|
|
422
|
+
await this.db.connect();
|
|
423
|
+
await initializeDatabase(queries, async (query) => {
|
|
424
|
+
await this.db.query(query.rawQuery);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async executeQueries(queries) {
|
|
428
|
+
const db = this.db;
|
|
429
|
+
if (!db) throw new Error("Database not initialized");
|
|
430
|
+
try {
|
|
431
|
+
const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
|
|
432
|
+
for (const query of executableQueries) {
|
|
433
|
+
consola.debug(`Executing query: ${query.id}`);
|
|
434
|
+
await this.executeQuery(db, query);
|
|
435
|
+
if (query.isQuery) {}
|
|
436
|
+
consola.success(`Query ${query.id} executed successfully`);
|
|
437
|
+
}
|
|
438
|
+
} catch (error) {
|
|
439
|
+
consola.error("Error executing queries:", error.message);
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async executeQuery(db, query) {
|
|
444
|
+
const statement = query.queryPositional;
|
|
445
|
+
try {
|
|
446
|
+
consola.info("Query:", statement.sql);
|
|
447
|
+
let result;
|
|
448
|
+
try {
|
|
449
|
+
await db.query("BEGIN");
|
|
450
|
+
result = await db.query(statement.sql, statement.parameters);
|
|
451
|
+
} finally {
|
|
452
|
+
await db.query("ROLLBACK");
|
|
453
|
+
}
|
|
454
|
+
if (query.isQuery) {
|
|
455
|
+
const columnNames = result.fields.map((field) => field.name);
|
|
456
|
+
const columnTypes = result.fields.map((field) => {
|
|
457
|
+
return typeIdToName.get(field.dataTypeID) || `type_${field.dataTypeID}`;
|
|
458
|
+
});
|
|
459
|
+
consola.debug("Columns:", columnNames);
|
|
460
|
+
consola.debug("Types:", columnTypes);
|
|
461
|
+
query.columns = columnNames.map((name, index) => ({
|
|
462
|
+
name,
|
|
463
|
+
type: columnTypes[index],
|
|
464
|
+
nullable: true
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
if (query.isQuery) {
|
|
468
|
+
if (query.isOne) return result.rows[0] || null;
|
|
469
|
+
return result.rows;
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
consola.error(`Failed to execute query '${query.id}':`, error);
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async close() {
|
|
478
|
+
await this.db.end();
|
|
479
|
+
await this.dbInitial.query(`DROP DATABASE "${databaseName}"`);
|
|
480
|
+
await this.dbInitial.end();
|
|
481
|
+
}
|
|
482
|
+
}();
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/db/sqlite.ts
|
|
486
|
+
const sqlite = new class {
|
|
487
|
+
db;
|
|
488
|
+
async initializeDatabase(queries) {
|
|
489
|
+
const db = new BetterSqlite3(":memory:");
|
|
490
|
+
await initializeDatabase(queries, (query) => {
|
|
491
|
+
db.exec(query.rawQuery);
|
|
492
|
+
return Promise.resolve();
|
|
493
|
+
});
|
|
494
|
+
this.db = db;
|
|
495
|
+
}
|
|
496
|
+
executeQueries(queries) {
|
|
497
|
+
const db = this.db;
|
|
498
|
+
if (!db) throw new Error("Database not initialized");
|
|
499
|
+
try {
|
|
500
|
+
const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
|
|
501
|
+
for (const query of executableQueries) {
|
|
502
|
+
consola.info(`Executing query: ${query.id}`);
|
|
503
|
+
this.executeQuery(db, query);
|
|
504
|
+
if (query.isQuery) {}
|
|
505
|
+
consola.success(`Query ${query.id} executed successfully`);
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
consola.error("Error executing queries:", error.message);
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
close() {
|
|
513
|
+
this.db.close();
|
|
514
|
+
}
|
|
515
|
+
getTableInfo(db, table) {
|
|
516
|
+
const info = db.pragma(`table_info('${table}')`);
|
|
517
|
+
return new Map(info.map((col) => [col.name, col]));
|
|
518
|
+
}
|
|
519
|
+
executeQuery(db, query) {
|
|
520
|
+
const statement = query.queryAnonymous;
|
|
521
|
+
try {
|
|
522
|
+
consola.debug("Query:", statement.sql);
|
|
523
|
+
const stmt = db.prepare(statement.sql);
|
|
524
|
+
if (query.isQuery) {
|
|
525
|
+
const info = stmt.columns();
|
|
526
|
+
const tables = new Set(info.map((col) => col.table).filter(isNotNil));
|
|
527
|
+
const data = new Map(Array.from(tables).map((table) => [table, this.getTableInfo(db, table)]));
|
|
528
|
+
query.columns = info.map((col) => {
|
|
529
|
+
const colInfo = col.table ? data.get(col.table)?.get(col.name) : null;
|
|
530
|
+
return {
|
|
531
|
+
name: col.name,
|
|
532
|
+
type: col.type || "unknown",
|
|
533
|
+
nullable: colInfo?.pk === 0 && colInfo?.notnull === 0
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
if (query.isQuery) {
|
|
538
|
+
if (query.isOne) return stmt.get(...statement.parameters.map((p) => p.value));
|
|
539
|
+
return stmt.all(...statement.parameters.map((p) => p.value));
|
|
540
|
+
}
|
|
541
|
+
return stmt.run(...statement.parameters.map((p) => p.value));
|
|
542
|
+
} catch (error) {
|
|
543
|
+
consola.error(`Failed to execute query '${query.id}' in ${query.filename}:\n ${statement.sql} \n ${statement.parameters.map((p) => p.value).join(", ")}`, error);
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}();
|
|
548
|
+
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/db/index.ts
|
|
551
|
+
function getDatabaseEngine(engine) {
|
|
552
|
+
switch (engine) {
|
|
553
|
+
case "sqlite": return sqlite;
|
|
554
|
+
case "duckdb": return duckdb;
|
|
555
|
+
case "postgres": return postgres;
|
|
556
|
+
default: throw new Error(`Unsupported database engine: ${engine}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/type-mapping.ts
|
|
562
|
+
/**
|
|
563
|
+
* Abstract base class for mapping SQL column types to target language types.
|
|
564
|
+
* Subclasses implement language-specific type mappings (e.g., Java, TypeScript).
|
|
565
|
+
*/
|
|
566
|
+
var TypeMapper = class {
|
|
567
|
+
/**
|
|
568
|
+
* Returns the target language type name for a given SQL column.
|
|
569
|
+
* Handles complex types (lists, structs, maps) by recursively resolving nested types.
|
|
570
|
+
* @param column - The column information including name, type, and nullability
|
|
571
|
+
* @param path - Optional prefix path for nested type references (e.g., "OuterStruct.")
|
|
572
|
+
* @returns The fully qualified type name in the target language
|
|
573
|
+
*/
|
|
574
|
+
getTypeName(column, path = "") {
|
|
575
|
+
if (column.type instanceof ListType) {
|
|
576
|
+
const elementType = this.getTypeName({
|
|
577
|
+
name: column.name,
|
|
578
|
+
type: column.type.baseType,
|
|
579
|
+
nullable: true
|
|
580
|
+
});
|
|
581
|
+
return path + this.formatListType(elementType);
|
|
582
|
+
}
|
|
583
|
+
if (column.type instanceof StructType) return path + this.formatStructTypeName(column.name);
|
|
584
|
+
if (column.type instanceof MapType) return path + this.formatMapTypeName(column.name);
|
|
585
|
+
if (!column.type) throw new Error(`Expected ColumnType ${JSON.stringify(column)}`);
|
|
586
|
+
return this.mapPrimitiveType(column.type.toString(), column.nullable);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Wraps a column's type in the target language's list/array type.
|
|
590
|
+
* @param column - The column whose type should be wrapped in a list
|
|
591
|
+
* @returns The list type representation (e.g., "List<String>" for Java)
|
|
592
|
+
*/
|
|
593
|
+
listType(column) {
|
|
594
|
+
const baseType = this.getTypeName(column);
|
|
595
|
+
return this.formatListType(baseType);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Generates type declarations for complex types (structs, nested lists).
|
|
599
|
+
* For structs, generates a full type/interface declaration.
|
|
600
|
+
* For lists, recursively processes the element type.
|
|
601
|
+
* @param column - The column containing a complex type
|
|
602
|
+
* @param path - Optional prefix path for nested type references
|
|
603
|
+
* @returns Generated type declaration code, or empty string for primitive types
|
|
604
|
+
*/
|
|
605
|
+
getDeclarations(column, path = "") {
|
|
606
|
+
if (column.type instanceof StructType) return this.generateStructDeclaration(column, path);
|
|
607
|
+
if (column.type instanceof ListType) return this.getDeclarations({
|
|
608
|
+
name: column.name,
|
|
609
|
+
type: column.type.baseType,
|
|
610
|
+
nullable: true
|
|
611
|
+
}, path);
|
|
612
|
+
return "";
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
/**
|
|
616
|
+
* Type mapper for generating Java types from SQL column types.
|
|
617
|
+
* Maps SQL types to Java types (e.g., INTEGER -> Integer, VARCHAR -> String).
|
|
618
|
+
* Generates Java records for struct types and handles Java reserved keywords.
|
|
619
|
+
*/
|
|
620
|
+
var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
621
|
+
typeMap = {
|
|
622
|
+
INTEGER: "Integer",
|
|
623
|
+
REAL: "Double",
|
|
624
|
+
TEXT: "String",
|
|
625
|
+
BLOB: "byte[]",
|
|
626
|
+
BOOLEAN: "Boolean",
|
|
627
|
+
DATE: "LocalDate",
|
|
628
|
+
DATETIME: "LocalDateTime",
|
|
629
|
+
TIMESTAMP: "LocalDateTime",
|
|
630
|
+
NULL: "null",
|
|
631
|
+
UNKNOWN: "Object",
|
|
632
|
+
DOUBLE: "Double",
|
|
633
|
+
FLOAT: "Float",
|
|
634
|
+
VARCHAR: "String",
|
|
635
|
+
TINYINT: "Byte",
|
|
636
|
+
SMALLINT: "Short",
|
|
637
|
+
BIGINT: "Long",
|
|
638
|
+
HUGEINT: "BigInteger",
|
|
639
|
+
UHUGEINT: "BigInteger",
|
|
640
|
+
UTINYINT: "Short",
|
|
641
|
+
USMALLINT: "Integer",
|
|
642
|
+
UINTEGER: "Long",
|
|
643
|
+
UBIGINT: "BigInteger",
|
|
644
|
+
TIME: "LocalTime",
|
|
645
|
+
"TIME WITH TIME ZONE": "OffsetTime",
|
|
646
|
+
TIMESTAMP_S: "Instant",
|
|
647
|
+
TIMESTAMP_MS: "Instant",
|
|
648
|
+
TIMESTAMP_NS: "Instant",
|
|
649
|
+
"TIMESTAMP WITH TIME ZONE": "Instant",
|
|
650
|
+
UUID: "UUID",
|
|
651
|
+
INTERVAL: "String",
|
|
652
|
+
BIT: "String",
|
|
653
|
+
BIGNUM: "BigDecimal",
|
|
654
|
+
INT4: "Integer"
|
|
655
|
+
};
|
|
656
|
+
static javaReservedKeywords = new Set([
|
|
657
|
+
"abstract",
|
|
658
|
+
"assert",
|
|
659
|
+
"boolean",
|
|
660
|
+
"break",
|
|
661
|
+
"byte",
|
|
662
|
+
"case",
|
|
663
|
+
"catch",
|
|
664
|
+
"char",
|
|
665
|
+
"class",
|
|
666
|
+
"const",
|
|
667
|
+
"continue",
|
|
668
|
+
"default",
|
|
669
|
+
"do",
|
|
670
|
+
"double",
|
|
671
|
+
"else",
|
|
672
|
+
"enum",
|
|
673
|
+
"extends",
|
|
674
|
+
"final",
|
|
675
|
+
"finally",
|
|
676
|
+
"float",
|
|
677
|
+
"for",
|
|
678
|
+
"goto",
|
|
679
|
+
"if",
|
|
680
|
+
"implements",
|
|
681
|
+
"import",
|
|
682
|
+
"instanceof",
|
|
683
|
+
"int",
|
|
684
|
+
"interface",
|
|
685
|
+
"long",
|
|
686
|
+
"native",
|
|
687
|
+
"new",
|
|
688
|
+
"package",
|
|
689
|
+
"private",
|
|
690
|
+
"protected",
|
|
691
|
+
"public",
|
|
692
|
+
"return",
|
|
693
|
+
"short",
|
|
694
|
+
"static",
|
|
695
|
+
"strictfp",
|
|
696
|
+
"super",
|
|
697
|
+
"switch",
|
|
698
|
+
"synchronized",
|
|
699
|
+
"this",
|
|
700
|
+
"throw",
|
|
701
|
+
"throws",
|
|
702
|
+
"transient",
|
|
703
|
+
"try",
|
|
704
|
+
"void",
|
|
705
|
+
"volatile",
|
|
706
|
+
"while",
|
|
707
|
+
"true",
|
|
708
|
+
"false",
|
|
709
|
+
"null"
|
|
710
|
+
]);
|
|
711
|
+
mapPrimitiveType(type, _nullable) {
|
|
712
|
+
const upperType = type.toString().toUpperCase();
|
|
713
|
+
const mappedType = this.typeMap[upperType];
|
|
714
|
+
if (mappedType) return mappedType;
|
|
715
|
+
if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) return "BigDecimal";
|
|
716
|
+
if (upperType.startsWith("ENUM(")) return "String";
|
|
717
|
+
if (upperType.startsWith("UNION(")) return "Object";
|
|
718
|
+
if (/\[\d+\]/.test(upperType)) return "Object";
|
|
719
|
+
console.warn("Mapped type is unknown:", type);
|
|
720
|
+
return "Object";
|
|
721
|
+
}
|
|
722
|
+
formatListType(elementType) {
|
|
723
|
+
return `List<${elementType}>`;
|
|
724
|
+
}
|
|
725
|
+
formatStructTypeName(fieldName) {
|
|
726
|
+
return `${pascalCase(fieldName)}Result`;
|
|
727
|
+
}
|
|
728
|
+
generateStructDeclaration(column, path = "") {
|
|
729
|
+
if (!(column.type instanceof StructType)) throw new Error(`Expected StructType ${column}`);
|
|
730
|
+
const structName = this.formatStructTypeName(column.name);
|
|
731
|
+
const newPath = `${path}${structName}.`;
|
|
732
|
+
const children = column.type.fields.map((field) => {
|
|
733
|
+
return this.getDeclarations({
|
|
734
|
+
name: field.name,
|
|
735
|
+
type: field.type,
|
|
736
|
+
nullable: true
|
|
737
|
+
}, newPath);
|
|
738
|
+
}).join("\n");
|
|
739
|
+
const fields = column.type.fields.map((field) => {
|
|
740
|
+
return `${this.getTypeName(field)} ${this.varName(field.name)}`;
|
|
741
|
+
}).join(", ");
|
|
742
|
+
const fromAttributes = ` private static ${structName} fromAttributes(Object[] v) {
|
|
743
|
+
return new ${structName}(${column.type.fields.map((f, i) => `${this.parseValue(f, `v[${i}]`, "")}`).join(",\n")});
|
|
744
|
+
}`;
|
|
745
|
+
return `public record ${structName}(${fields}) {
|
|
746
|
+
${path.length > 0 ? fromAttributes : ""}
|
|
747
|
+
${children}
|
|
748
|
+
}`;
|
|
749
|
+
}
|
|
750
|
+
formatMapTypeName(fieldName) {
|
|
751
|
+
return "HashMap";
|
|
752
|
+
}
|
|
753
|
+
varName(str) {
|
|
754
|
+
const name = camelCase(str);
|
|
755
|
+
if (JavaTypeMapper.javaReservedKeywords.has(name)) return `${name}_`;
|
|
756
|
+
return name;
|
|
757
|
+
}
|
|
758
|
+
parseValue(column, value, path) {
|
|
759
|
+
if (column.type instanceof ListType) {
|
|
760
|
+
const elementType = this.getTypeName({
|
|
761
|
+
name: column.name,
|
|
762
|
+
type: column.type.baseType,
|
|
763
|
+
nullable: true
|
|
764
|
+
}, path);
|
|
765
|
+
if (column.type.baseType instanceof StructType) return `arrayOfStructToList((Array)${value}, ${elementType}::fromAttributes)`;
|
|
766
|
+
if (column.type.baseType instanceof ListType) return `multiDimArrayToList((Array)${value}, ${this.getInnermostType(column.type)}[].class)`;
|
|
767
|
+
return `arrayToList((Array)${value}, ${elementType}[].class)`;
|
|
768
|
+
}
|
|
769
|
+
if (column.type instanceof StructType) return `${path}${this.formatStructTypeName(column.name)}.fromAttributes(getAttr((Struct)${value}))`;
|
|
770
|
+
return `(${this.getTypeName(column)})${value}`;
|
|
771
|
+
}
|
|
772
|
+
getInnermostType(type) {
|
|
773
|
+
let current = type.baseType;
|
|
774
|
+
while (current instanceof ListType) current = current.baseType;
|
|
775
|
+
return this.getTypeName({
|
|
776
|
+
name: "",
|
|
777
|
+
type: current,
|
|
778
|
+
nullable: true
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* Type mapper for generating TypeScript types from SQL column types.
|
|
784
|
+
* Maps SQL types to TypeScript types (e.g., INTEGER -> number, VARCHAR -> string).
|
|
785
|
+
* Generates TypeScript interfaces for struct types and handles DuckDB's complex types.
|
|
786
|
+
*/
|
|
787
|
+
var TypeScriptTypeMapper = class extends TypeMapper {
|
|
788
|
+
/**
|
|
789
|
+
* Returns the TypeScript type name for a given SQL column.
|
|
790
|
+
* Overrides base to handle DuckDB's map type with key-value entry arrays.
|
|
791
|
+
*/
|
|
792
|
+
getTypeName(column, path = "") {
|
|
793
|
+
if (column.type instanceof MapType) return `{ entries: { key: ${this.getTypeName({
|
|
794
|
+
name: "key",
|
|
795
|
+
type: column.type.keyType.type,
|
|
796
|
+
nullable: true
|
|
797
|
+
})}; value: ${this.getTypeName({
|
|
798
|
+
name: "value",
|
|
799
|
+
type: column.type.valueType.type,
|
|
800
|
+
nullable: true
|
|
801
|
+
})} }[] }`;
|
|
802
|
+
return super.getTypeName(column, path);
|
|
803
|
+
}
|
|
804
|
+
typeMap = {
|
|
805
|
+
INTEGER: "number",
|
|
806
|
+
REAL: "number",
|
|
807
|
+
TEXT: "string",
|
|
808
|
+
BLOB: "{ bytes: Uint8Array }",
|
|
809
|
+
BOOLEAN: "boolean",
|
|
810
|
+
DATE: "{ days: number }",
|
|
811
|
+
DATETIME: "{ micros: bigint }",
|
|
812
|
+
TIMESTAMP: "{ micros: bigint }",
|
|
813
|
+
NULL: "null",
|
|
814
|
+
UNKNOWN: "unknown",
|
|
815
|
+
DOUBLE: "number",
|
|
816
|
+
FLOAT: "number",
|
|
817
|
+
VARCHAR: "string",
|
|
818
|
+
TINYINT: "number",
|
|
819
|
+
SMALLINT: "number",
|
|
820
|
+
BIGINT: "bigint",
|
|
821
|
+
HUGEINT: "bigint",
|
|
822
|
+
UHUGEINT: "bigint",
|
|
823
|
+
UTINYINT: "number",
|
|
824
|
+
USMALLINT: "number",
|
|
825
|
+
UINTEGER: "number",
|
|
826
|
+
UBIGINT: "bigint",
|
|
827
|
+
TIME: "{ micros: bigint }",
|
|
828
|
+
"TIME WITH TIME ZONE": "{ micros: bigint; offset: number }",
|
|
829
|
+
TIMESTAMP_S: "{ seconds: bigint }",
|
|
830
|
+
TIMESTAMP_MS: "{ millis: bigint }",
|
|
831
|
+
TIMESTAMP_NS: "{ nanos: bigint }",
|
|
832
|
+
"TIMESTAMP WITH TIME ZONE": "{ micros: bigint }",
|
|
833
|
+
UUID: "{ hugeint: bigint }",
|
|
834
|
+
INTERVAL: "{ months: number; days: number; micros: bigint }",
|
|
835
|
+
BIT: "{ data: Uint8Array }",
|
|
836
|
+
BIGNUM: "bigint",
|
|
837
|
+
INT4: "number"
|
|
838
|
+
};
|
|
839
|
+
mapPrimitiveType(type, nullable) {
|
|
840
|
+
const upperType = type.toUpperCase();
|
|
841
|
+
const mappedType = this.typeMap[upperType];
|
|
842
|
+
if (mappedType) return nullable ? `${mappedType} | null` : mappedType;
|
|
843
|
+
if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) {
|
|
844
|
+
const baseType = "{ width: number; scale: number; value: bigint }";
|
|
845
|
+
return nullable ? `${baseType} | null` : baseType;
|
|
846
|
+
}
|
|
847
|
+
if (upperType.startsWith("ENUM(")) {
|
|
848
|
+
const baseType = "string";
|
|
849
|
+
return nullable ? `${baseType} | null` : baseType;
|
|
850
|
+
}
|
|
851
|
+
if (upperType.startsWith("UNION(")) {
|
|
852
|
+
const baseType = "{ tag: string; value: unknown }";
|
|
853
|
+
return nullable ? `${baseType} | null` : baseType;
|
|
854
|
+
}
|
|
855
|
+
const fixedArrayMatch = upperType.match(/^([A-Z_]+)\[(\d+)\](\[\d+\])*$/);
|
|
856
|
+
if (fixedArrayMatch) {
|
|
857
|
+
const baseTypeName = fixedArrayMatch[1];
|
|
858
|
+
const baseType = this.typeMap[baseTypeName];
|
|
859
|
+
if (baseType) {
|
|
860
|
+
const dimensions = (upperType.match(/\[\d+\]/g) || []).length;
|
|
861
|
+
let result = baseType;
|
|
862
|
+
for (let i = 0; i < dimensions; i++) result = `{ items: (${result} | null)[] }`;
|
|
863
|
+
return nullable ? `${result} | null` : result;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (/\[\d+\]/.test(upperType)) return "{ items: unknown[] }";
|
|
867
|
+
console.warn("Mapped type is unknown:", type);
|
|
868
|
+
return "unknown";
|
|
869
|
+
}
|
|
870
|
+
formatListType(elementType) {
|
|
871
|
+
return `{ items: (${elementType})[] }`;
|
|
872
|
+
}
|
|
873
|
+
formatStructTypeName(fieldName) {
|
|
874
|
+
return `${pascalCase(fieldName)}Struct`;
|
|
875
|
+
}
|
|
876
|
+
formatMapTypeName(fieldName) {
|
|
877
|
+
return "Map";
|
|
878
|
+
}
|
|
879
|
+
generateStructDeclaration(column) {
|
|
880
|
+
if (!(column.type instanceof StructType)) throw new Error("Expected StructType");
|
|
881
|
+
return `interface ${this.formatStructTypeName(column.name)} {\n entries: {\n${column.type.fields.map((field) => {
|
|
882
|
+
const fieldType = this.getTypeName({
|
|
883
|
+
name: field.name,
|
|
884
|
+
type: field.type,
|
|
885
|
+
nullable: true
|
|
886
|
+
});
|
|
887
|
+
return ` ${field.name}: ${fieldType};`;
|
|
888
|
+
}).join("\n")}\n };\n}`;
|
|
889
|
+
}
|
|
890
|
+
parseValue(column, value) {
|
|
891
|
+
return "/// TODO: parseValue";
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
//#endregion
|
|
896
|
+
//#region src/generators/base-generator.ts
|
|
897
|
+
var BaseGenerator = class {
|
|
898
|
+
constructor(template, typeMapper) {
|
|
899
|
+
this.template = template;
|
|
900
|
+
this.typeMapper = typeMapper;
|
|
901
|
+
}
|
|
902
|
+
mapType(column) {
|
|
903
|
+
return this.typeMapper.getTypeName(column);
|
|
904
|
+
}
|
|
905
|
+
mapParameterType(type, nullable) {
|
|
906
|
+
return this.mapType({
|
|
907
|
+
name: "",
|
|
908
|
+
type,
|
|
909
|
+
nullable
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
listType(column) {
|
|
913
|
+
return this.typeMapper.listType(column);
|
|
914
|
+
}
|
|
915
|
+
async beforeGenerate(_projectDir, _gen, _queries) {}
|
|
916
|
+
isCompatibleWith(_engine) {
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
functionReturnType(query) {
|
|
920
|
+
if (query.isOne) return this.rowType(query);
|
|
921
|
+
return this.typeMapper.formatListType(this.rowType(query));
|
|
922
|
+
}
|
|
923
|
+
rowType(query) {
|
|
924
|
+
if (query.isPluck) return this.mapType({
|
|
925
|
+
...query.columns[0],
|
|
926
|
+
name: query.id
|
|
927
|
+
});
|
|
928
|
+
return this.getClassName(`${query.id}_Result`);
|
|
929
|
+
}
|
|
930
|
+
getStatement(q) {
|
|
931
|
+
return q.queryAnonymous;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/generators/java-generator.ts
|
|
937
|
+
var JavaGenerator = class extends BaseGenerator {
|
|
938
|
+
constructor(template) {
|
|
939
|
+
super(template, new JavaTypeMapper());
|
|
940
|
+
this.template = template;
|
|
941
|
+
}
|
|
942
|
+
getFunctionName(id) {
|
|
943
|
+
return camelCase$1(id);
|
|
944
|
+
}
|
|
945
|
+
getFilename(sqlFileName) {
|
|
946
|
+
return `${pascalCase$1(sqlFileName)}.java`;
|
|
947
|
+
}
|
|
948
|
+
getClassName(name) {
|
|
949
|
+
return pascalCase$1(name);
|
|
950
|
+
}
|
|
951
|
+
partsToString(parts) {
|
|
952
|
+
const stringParts = [];
|
|
953
|
+
function addPart(str, quote) {
|
|
954
|
+
if (quote && stringParts.length > 0) {
|
|
955
|
+
const last = stringParts[stringParts.length - 1];
|
|
956
|
+
if (last.quote) {
|
|
957
|
+
last.str += str;
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
stringParts.push({
|
|
962
|
+
str,
|
|
963
|
+
quote
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
for (const part of parts) if (typeof part === "string") addPart(part, true);
|
|
967
|
+
else {
|
|
968
|
+
addPart(" '", true);
|
|
969
|
+
addPart(part.name, false);
|
|
970
|
+
addPart("'", true);
|
|
971
|
+
}
|
|
972
|
+
return stringParts.map((part) => {
|
|
973
|
+
if (part.quote && (part.str.includes("\n") || part.str.includes("\""))) return `"""\n${part.str}"""`;
|
|
974
|
+
if (part.quote) return `"${part.str}"`;
|
|
975
|
+
return part.str;
|
|
976
|
+
}).join(" + ");
|
|
977
|
+
}
|
|
978
|
+
readColumn(column, index, path) {
|
|
979
|
+
return this.typeMapper.parseValue(column, `rs.getObject(${index + 1})`, path);
|
|
980
|
+
}
|
|
981
|
+
async beforeGenerate(_projectDir, _gen, _queries) {
|
|
982
|
+
Handlebars.registerHelper("partsToString", (parts) => this.partsToString(parts));
|
|
983
|
+
Handlebars.registerHelper("declareTypes", (queryHelper) => {
|
|
984
|
+
const query = queryHelper.query;
|
|
985
|
+
if (queryHelper.isPluck) return queryHelper.typeMapper.getDeclarations({
|
|
986
|
+
...query.columns[0],
|
|
987
|
+
name: query.id
|
|
988
|
+
}, " ");
|
|
989
|
+
return queryHelper.typeMapper.getDeclarations(query.allColumns);
|
|
990
|
+
});
|
|
991
|
+
Handlebars.registerHelper("readColumns", (queryHelper) => {
|
|
992
|
+
const query = queryHelper.query;
|
|
993
|
+
if (queryHelper.isPluck) return this.readColumn({
|
|
994
|
+
...query.columns[0],
|
|
995
|
+
name: query.id
|
|
996
|
+
}, 0, "");
|
|
997
|
+
const result = [];
|
|
998
|
+
const rowType = this.rowType(queryHelper.query);
|
|
999
|
+
for (let i = 0; i < queryHelper.columns.length; i++) {
|
|
1000
|
+
const column = queryHelper.columns[i];
|
|
1001
|
+
result.push(this.readColumn(column, i, `${rowType}.`));
|
|
1002
|
+
}
|
|
1003
|
+
return `new ${rowType}(${result.join(",\n")})`;
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
async afterGenerate(outputPath) {
|
|
1007
|
+
try {
|
|
1008
|
+
consola.debug("Formatting file:", outputPath);
|
|
1009
|
+
const code = readFileSync(outputPath, "utf-8");
|
|
1010
|
+
writeFileSync(outputPath, await prettier.format(code, {
|
|
1011
|
+
parser: "java",
|
|
1012
|
+
plugins: [prettierPluginJava],
|
|
1013
|
+
tabWidth: 4
|
|
1014
|
+
}));
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
consola.error("Failed to format Java file:", error);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
//#endregion
|
|
1022
|
+
//#region src/generators/java-duckdb-arrow-generator.ts
|
|
1023
|
+
var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
1024
|
+
javaGenerator;
|
|
1025
|
+
constructor(template) {
|
|
1026
|
+
super(template, new JavaTypeMapper());
|
|
1027
|
+
this.template = template;
|
|
1028
|
+
this.javaGenerator = new JavaGenerator("templates/java-jdbc.hbs");
|
|
1029
|
+
}
|
|
1030
|
+
getFunctionName(id) {
|
|
1031
|
+
return this.javaGenerator.getFunctionName(id);
|
|
1032
|
+
}
|
|
1033
|
+
async beforeGenerate(projectDir, gen, queries) {
|
|
1034
|
+
const q = queries.filter((q$1) => q$1.isQuery && q$1.isOne || q$1.isMigrate);
|
|
1035
|
+
const name = `${gen.name}-jdbc`;
|
|
1036
|
+
writeGeneratedFile(projectDir, {
|
|
1037
|
+
name,
|
|
1038
|
+
generator: "java/jdbc",
|
|
1039
|
+
output: gen.output,
|
|
1040
|
+
config: gen.config
|
|
1041
|
+
}, this.javaGenerator, name, q);
|
|
1042
|
+
}
|
|
1043
|
+
isCompatibleWith(engine) {
|
|
1044
|
+
return engine === "duckdb";
|
|
1045
|
+
}
|
|
1046
|
+
getFilename(sqlFileName) {
|
|
1047
|
+
return this.javaGenerator.getFilename(sqlFileName);
|
|
1048
|
+
}
|
|
1049
|
+
getClassName(name) {
|
|
1050
|
+
return this.javaGenerator.getClassName(name);
|
|
1051
|
+
}
|
|
1052
|
+
mapType(column) {
|
|
1053
|
+
const { type, nullable } = column;
|
|
1054
|
+
if (typeof type === "string") {
|
|
1055
|
+
const mappedType = {
|
|
1056
|
+
INTEGER: "IntVector",
|
|
1057
|
+
BOOLEAN: "BitVector",
|
|
1058
|
+
DOUBLE: "Float8Vector",
|
|
1059
|
+
FLOAT: "Float4Vector",
|
|
1060
|
+
VARCHAR: "VarCharVector",
|
|
1061
|
+
TEXT: "VarCharVector"
|
|
1062
|
+
}[type.toUpperCase()];
|
|
1063
|
+
if (!mappedType) consola.warn("(duckdb-arrow) Mapped type is unknown:", type);
|
|
1064
|
+
return mappedType ?? "Object";
|
|
1065
|
+
}
|
|
1066
|
+
const mockColumn = {
|
|
1067
|
+
name: "",
|
|
1068
|
+
type,
|
|
1069
|
+
nullable
|
|
1070
|
+
};
|
|
1071
|
+
return this.typeMapper.getTypeName(mockColumn);
|
|
1072
|
+
}
|
|
1073
|
+
mapParameterType(type, nullable) {
|
|
1074
|
+
return this.typeMapper.getTypeName({
|
|
1075
|
+
name: "",
|
|
1076
|
+
type,
|
|
1077
|
+
nullable
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
listType(type) {
|
|
1081
|
+
const mockColumn = {
|
|
1082
|
+
name: "",
|
|
1083
|
+
type,
|
|
1084
|
+
nullable: false
|
|
1085
|
+
};
|
|
1086
|
+
return this.typeMapper.listType(mockColumn);
|
|
1087
|
+
}
|
|
1088
|
+
async afterGenerate(outputPath) {
|
|
1089
|
+
return this.javaGenerator.afterGenerate(outputPath);
|
|
1090
|
+
}
|
|
1091
|
+
functionReturnType(query) {
|
|
1092
|
+
if (query.isOne) return this.javaGenerator.rowType(query);
|
|
1093
|
+
return this.rowType(query);
|
|
1094
|
+
}
|
|
1095
|
+
rowType(query) {
|
|
1096
|
+
if (query.isOne) return this.javaGenerator.rowType(query);
|
|
1097
|
+
return this.getClassName(`${query.id}_Result`);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
//#endregion
|
|
1102
|
+
//#region src/generators/typescript-generator.ts
|
|
1103
|
+
var TsGenerator = class extends BaseGenerator {
|
|
1104
|
+
constructor(template) {
|
|
1105
|
+
super(template, new TypeScriptTypeMapper());
|
|
1106
|
+
}
|
|
1107
|
+
getFunctionName(id) {
|
|
1108
|
+
return camelCase$1(id);
|
|
1109
|
+
}
|
|
1110
|
+
isCompatibleWith(_engine) {
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
getFilename(sqlFileName) {
|
|
1114
|
+
return `${sqlFileName}.ts`;
|
|
1115
|
+
}
|
|
1116
|
+
getClassName(name) {
|
|
1117
|
+
return pascalCase$1(name);
|
|
1118
|
+
}
|
|
1119
|
+
async beforeGenerate(_projectDir, _gen, _queries) {
|
|
1120
|
+
Handlebars.registerHelper("quote", (value) => this.quote(value));
|
|
1121
|
+
Handlebars.registerHelper("tsType", (column) => {
|
|
1122
|
+
const inlineType = (col) => {
|
|
1123
|
+
const t = col.type;
|
|
1124
|
+
const withNullability = (base) => {
|
|
1125
|
+
if (!col.nullable) return base;
|
|
1126
|
+
if (/\bnull\b/.test(base)) return base;
|
|
1127
|
+
return `${base} | null`;
|
|
1128
|
+
};
|
|
1129
|
+
if (t instanceof ListType) {
|
|
1130
|
+
const element = inlineType({
|
|
1131
|
+
name: col.name,
|
|
1132
|
+
type: t.baseType,
|
|
1133
|
+
nullable: true
|
|
1134
|
+
});
|
|
1135
|
+
return withNullability(`${element.includes(" | ") ? `(${element})` : element}[]`);
|
|
1136
|
+
}
|
|
1137
|
+
if (t instanceof MapType) return withNullability(`Map<${inlineType({
|
|
1138
|
+
name: "key",
|
|
1139
|
+
type: t.keyType.type,
|
|
1140
|
+
nullable: false
|
|
1141
|
+
})}, ${inlineType({
|
|
1142
|
+
name: "value",
|
|
1143
|
+
type: t.valueType.type,
|
|
1144
|
+
nullable: true
|
|
1145
|
+
})}>`);
|
|
1146
|
+
if (t instanceof StructType) {
|
|
1147
|
+
const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
1148
|
+
return withNullability(`{ ${t.fields.map((f) => {
|
|
1149
|
+
return `${isValidIdent(f.name) ? f.name : JSON.stringify(f.name)}: ${inlineType({
|
|
1150
|
+
name: f.name,
|
|
1151
|
+
type: f.type,
|
|
1152
|
+
nullable: true
|
|
1153
|
+
})}`;
|
|
1154
|
+
}).join("; ")} }`);
|
|
1155
|
+
}
|
|
1156
|
+
if (t instanceof EnumType) return withNullability(t.values.map((v) => JSON.stringify(v)).join(" | "));
|
|
1157
|
+
return this.typeMapper.getTypeName(col);
|
|
1158
|
+
};
|
|
1159
|
+
return inlineType(column);
|
|
1160
|
+
});
|
|
1161
|
+
Handlebars.registerHelper("declareTypes", (queryHelper) => {
|
|
1162
|
+
const typeMapper = queryHelper.typeMapper;
|
|
1163
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
1164
|
+
const visit = (column) => {
|
|
1165
|
+
const t = column.type;
|
|
1166
|
+
if (t instanceof ListType) {
|
|
1167
|
+
visit({
|
|
1168
|
+
name: column.name,
|
|
1169
|
+
type: t.baseType,
|
|
1170
|
+
nullable: true
|
|
1171
|
+
});
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (t instanceof StructType) {
|
|
1175
|
+
const typeName = typeMapper.getTypeName(column);
|
|
1176
|
+
if (!declarations.has(typeName)) declarations.set(typeName, typeMapper.getDeclarations(column));
|
|
1177
|
+
for (const field of t.fields) visit({
|
|
1178
|
+
name: field.name,
|
|
1179
|
+
type: field.type,
|
|
1180
|
+
nullable: true
|
|
1181
|
+
});
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (t instanceof MapType) {
|
|
1185
|
+
visit({
|
|
1186
|
+
name: column.name,
|
|
1187
|
+
type: t.keyType.type,
|
|
1188
|
+
nullable: true
|
|
1189
|
+
});
|
|
1190
|
+
visit({
|
|
1191
|
+
name: column.name,
|
|
1192
|
+
type: t.valueType.type,
|
|
1193
|
+
nullable: true
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
if (queryHelper.isPluck) {
|
|
1198
|
+
const col = queryHelper.columns[0];
|
|
1199
|
+
visit({
|
|
1200
|
+
name: col.name,
|
|
1201
|
+
type: col.type,
|
|
1202
|
+
nullable: true
|
|
1203
|
+
});
|
|
1204
|
+
} else for (const col of queryHelper.columns) visit({
|
|
1205
|
+
name: col.name,
|
|
1206
|
+
type: col.type,
|
|
1207
|
+
nullable: true
|
|
1208
|
+
});
|
|
1209
|
+
return Array.from(declarations.values()).filter(Boolean).join("\n\n");
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
async afterGenerate(outputPath) {
|
|
1213
|
+
try {
|
|
1214
|
+
consola.debug("Formatting file:", outputPath);
|
|
1215
|
+
const code = readFileSync(outputPath, "utf-8");
|
|
1216
|
+
writeFileSync(outputPath, await prettier.format(code, {
|
|
1217
|
+
parser: "typescript",
|
|
1218
|
+
plugins: [typescriptPlugin, estree]
|
|
1219
|
+
}));
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
consola.error("Failed to format file:", error);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
quote(value) {
|
|
1225
|
+
return value.includes("\n") || value.includes("'") ? `\`${value}\`` : `'${value}'`;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
//#endregion
|
|
1230
|
+
//#region src/generators/typescript-duckdb-generator.ts
|
|
1231
|
+
/**
|
|
1232
|
+
* TypeScript generator for DuckDB.
|
|
1233
|
+
* DuckDB's Node API returns complex types as wrapper objects:
|
|
1234
|
+
* - Lists as { items: T[] }
|
|
1235
|
+
* - Structs as { entries: { field1: T1, ... } }
|
|
1236
|
+
* - Maps as { entries: { key: K, value: V }[] }
|
|
1237
|
+
*/
|
|
1238
|
+
var TsDuckDBGenerator = class extends TsGenerator {
|
|
1239
|
+
constructor(template) {
|
|
1240
|
+
super(template);
|
|
1241
|
+
}
|
|
1242
|
+
async beforeGenerate(projectDir, gen, queries) {
|
|
1243
|
+
await super.beforeGenerate(projectDir, gen, queries);
|
|
1244
|
+
Handlebars.registerHelper("tsType", (column) => {
|
|
1245
|
+
const inlineType = (col) => {
|
|
1246
|
+
const t = col.type;
|
|
1247
|
+
const withNullability = (base) => {
|
|
1248
|
+
if (!col.nullable) return base;
|
|
1249
|
+
if (/\bnull\b/.test(base)) return base;
|
|
1250
|
+
return `${base} | null`;
|
|
1251
|
+
};
|
|
1252
|
+
if (t instanceof ListType) {
|
|
1253
|
+
const element = inlineType({
|
|
1254
|
+
name: col.name,
|
|
1255
|
+
type: t.baseType,
|
|
1256
|
+
nullable: true
|
|
1257
|
+
});
|
|
1258
|
+
return withNullability(`{ items: ${element.includes(" | ") ? `(${element})` : element}[] }`);
|
|
1259
|
+
}
|
|
1260
|
+
if (t instanceof MapType) return withNullability(`{ entries: { key: ${inlineType({
|
|
1261
|
+
name: "key",
|
|
1262
|
+
type: t.keyType.type,
|
|
1263
|
+
nullable: true
|
|
1264
|
+
})}; value: ${inlineType({
|
|
1265
|
+
name: "value",
|
|
1266
|
+
type: t.valueType.type,
|
|
1267
|
+
nullable: true
|
|
1268
|
+
})} }[] }`);
|
|
1269
|
+
if (t instanceof StructType) {
|
|
1270
|
+
const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
1271
|
+
return withNullability(`{ entries: { ${t.fields.map((f) => {
|
|
1272
|
+
return `${isValidIdent(f.name) ? f.name : JSON.stringify(f.name)}: ${inlineType({
|
|
1273
|
+
name: f.name,
|
|
1274
|
+
type: f.type,
|
|
1275
|
+
nullable: true
|
|
1276
|
+
})}`;
|
|
1277
|
+
}).join("; ")} } }`);
|
|
1278
|
+
}
|
|
1279
|
+
if (t instanceof EnumType) return withNullability(t.values.map((v) => JSON.stringify(v)).join(" | "));
|
|
1280
|
+
return this.typeMapper.getTypeName(col);
|
|
1281
|
+
};
|
|
1282
|
+
return inlineType(column);
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/generators/index.ts
|
|
1289
|
+
function getGenerator(generator) {
|
|
1290
|
+
switch (generator) {
|
|
1291
|
+
case "java/jdbc": return new JavaGenerator("templates/java-jdbc.hbs");
|
|
1292
|
+
case "java/duckdb-arrow": return new JavaDuckDBArrowGenerator("templates/java-duckdb-arrow.hbs");
|
|
1293
|
+
case "typescript/better-sqlite3": return new TsGenerator("templates/better-sqlite3.hbs");
|
|
1294
|
+
case "typescript/duckdb": return new TsDuckDBGenerator("templates/typescript-duckdb.hbs");
|
|
1295
|
+
default: throw new Error(`Unsupported generator: ${generator}`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
//#endregion
|
|
1300
|
+
//#region src/sqltool.ts
|
|
1301
|
+
const GENERATED_FILE_COMMENT = "This file is generated by SQG. Do not edit manually.";
|
|
1302
|
+
const configSchema = z.object({ result: z.record(z.string(), z.string()).optional() });
|
|
1303
|
+
var Config = class Config {
|
|
1304
|
+
constructor(result) {
|
|
1305
|
+
this.result = result;
|
|
1306
|
+
}
|
|
1307
|
+
getColumnInfo(name) {
|
|
1308
|
+
return this.result.get(name);
|
|
1309
|
+
}
|
|
1310
|
+
static fromYaml(name, filePath, configStr) {
|
|
1311
|
+
let configObj;
|
|
1312
|
+
try {
|
|
1313
|
+
configObj = YAML.parse(configStr);
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
throw new Error(`Error parsing YAML config for query ${name} in ${filePath}: \n${configStr}\n ${e}`);
|
|
1316
|
+
}
|
|
1317
|
+
const result = configSchema.safeParse(configObj);
|
|
1318
|
+
if (!result.success) throw new Error(`Error parsing config for query ${name} in ${filePath}: \n${configStr}\n ${result.error}`);
|
|
1319
|
+
const columnMap = /* @__PURE__ */ new Map();
|
|
1320
|
+
for (const [name$1, info] of Object.entries(result.data.result ?? {})) {
|
|
1321
|
+
const parts = info.trim().split(" ").map((part) => part.trim());
|
|
1322
|
+
let type;
|
|
1323
|
+
let nullable = true;
|
|
1324
|
+
if (parts.length === 1) type = parts[0];
|
|
1325
|
+
else if (parts.length === 2) {
|
|
1326
|
+
type = parts[0];
|
|
1327
|
+
if (parts[1].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name$1} in ${filePath}: \n${configStr}\n ${info}`);
|
|
1328
|
+
nullable = true;
|
|
1329
|
+
} else if (parts.length === 3) {
|
|
1330
|
+
type = parts[0];
|
|
1331
|
+
if (parts[1].toLocaleLowerCase() !== "not" || parts[2].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name$1} in ${filePath}: \n${configStr}\n ${info}`);
|
|
1332
|
+
nullable = false;
|
|
1333
|
+
} else throw new Error(`Invalid config for column ${name$1} in ${filePath}: \n${configStr}\n ${info}`);
|
|
1334
|
+
columnMap.set(name$1, {
|
|
1335
|
+
name: name$1,
|
|
1336
|
+
type,
|
|
1337
|
+
nullable
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
return new Config(columnMap);
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
/** Util class to help generating a query with a given generator */
|
|
1344
|
+
var SqlQueryHelper = class {
|
|
1345
|
+
constructor(query, generator, statement) {
|
|
1346
|
+
this.query = query;
|
|
1347
|
+
this.generator = generator;
|
|
1348
|
+
this.statement = statement;
|
|
1349
|
+
}
|
|
1350
|
+
get id() {
|
|
1351
|
+
return this.query.id;
|
|
1352
|
+
}
|
|
1353
|
+
get isQuery() {
|
|
1354
|
+
return this.query.isQuery;
|
|
1355
|
+
}
|
|
1356
|
+
get isExec() {
|
|
1357
|
+
return this.query.isExec;
|
|
1358
|
+
}
|
|
1359
|
+
get isMigrate() {
|
|
1360
|
+
return this.query.isMigrate;
|
|
1361
|
+
}
|
|
1362
|
+
get isPluck() {
|
|
1363
|
+
return this.query.isPluck;
|
|
1364
|
+
}
|
|
1365
|
+
get isOne() {
|
|
1366
|
+
return this.query.isOne;
|
|
1367
|
+
}
|
|
1368
|
+
get parameterNames() {
|
|
1369
|
+
return this.statement.parameters.map((param) => param.name);
|
|
1370
|
+
}
|
|
1371
|
+
get skipGenerateFunction() {
|
|
1372
|
+
return this.query.skipGenerateFunction;
|
|
1373
|
+
}
|
|
1374
|
+
get parameters() {
|
|
1375
|
+
const vars = new Map(this.variables.map((param) => [param.name, param.type]));
|
|
1376
|
+
return this.statement.parameters.map((param) => ({
|
|
1377
|
+
name: param.name,
|
|
1378
|
+
type: vars.get(param.name)
|
|
1379
|
+
}));
|
|
1380
|
+
}
|
|
1381
|
+
get columns() {
|
|
1382
|
+
if (!(this.query.allColumns.type instanceof StructType)) throw new Error(`Expected StructType ${this.query.allColumns.type}`);
|
|
1383
|
+
return this.query.allColumns.type.fields;
|
|
1384
|
+
}
|
|
1385
|
+
get variables() {
|
|
1386
|
+
return Array.from(this.query.variables.entries()).map(([name, value]) => ({
|
|
1387
|
+
name,
|
|
1388
|
+
type: this.generator.mapParameterType(detectParameterType(value), false)
|
|
1389
|
+
}));
|
|
1390
|
+
}
|
|
1391
|
+
get sqlQuery() {
|
|
1392
|
+
return this.statement.sql;
|
|
1393
|
+
}
|
|
1394
|
+
get sqlQueryParts() {
|
|
1395
|
+
return this.statement.sqlParts;
|
|
1396
|
+
}
|
|
1397
|
+
get rowTypeStr() {
|
|
1398
|
+
return this.generator.rowType(this.query);
|
|
1399
|
+
}
|
|
1400
|
+
get functionReturnType() {
|
|
1401
|
+
return new Handlebars.SafeString(this.generator.functionReturnType(this.query));
|
|
1402
|
+
}
|
|
1403
|
+
get rowType() {
|
|
1404
|
+
return new Handlebars.SafeString(this.rowTypeStr);
|
|
1405
|
+
}
|
|
1406
|
+
get functionName() {
|
|
1407
|
+
return this.generator.getFunctionName(this.query.id);
|
|
1408
|
+
}
|
|
1409
|
+
get typeMapper() {
|
|
1410
|
+
return this.generator.typeMapper;
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
function generateSourceFile(name, queries, templatePath, generator, config) {
|
|
1414
|
+
const templateSrc = readFileSync(templatePath, "utf-8");
|
|
1415
|
+
const template = Handlebars.compile(templateSrc);
|
|
1416
|
+
Handlebars.registerHelper("mapType", (column) => generator.mapType(column));
|
|
1417
|
+
Handlebars.registerHelper("plusOne", (value) => value + 1);
|
|
1418
|
+
return template({
|
|
1419
|
+
generatedComment: GENERATED_FILE_COMMENT,
|
|
1420
|
+
migrations: queries.filter((q) => q.isMigrate).map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
|
|
1421
|
+
queries: queries.map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
|
|
1422
|
+
className: generator.getClassName(name),
|
|
1423
|
+
config
|
|
1424
|
+
}, {
|
|
1425
|
+
allowProtoPropertiesByDefault: true,
|
|
1426
|
+
allowProtoMethodsByDefault: true
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
const ProjectSchema = z.object({
|
|
1430
|
+
version: z.number(),
|
|
1431
|
+
name: z.string(),
|
|
1432
|
+
sql: z.array(z.object({
|
|
1433
|
+
engine: z.string(),
|
|
1434
|
+
files: z.array(z.string()),
|
|
1435
|
+
gen: z.array(z.object({
|
|
1436
|
+
generator: z.string(),
|
|
1437
|
+
name: z.string().optional(),
|
|
1438
|
+
template: z.string().optional(),
|
|
1439
|
+
output: z.string(),
|
|
1440
|
+
config: z.any().optional()
|
|
1441
|
+
}))
|
|
1442
|
+
})),
|
|
1443
|
+
sources: z.array(z.object({
|
|
1444
|
+
path: z.string(),
|
|
1445
|
+
name: z.string().optional()
|
|
1446
|
+
})).optional()
|
|
1447
|
+
});
|
|
1448
|
+
var ExtraVariable = class {
|
|
1449
|
+
constructor(name, value) {
|
|
1450
|
+
this.name = name;
|
|
1451
|
+
this.value = value;
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
function createExtraVariables(sources) {
|
|
1455
|
+
return sources.map((source) => {
|
|
1456
|
+
const path = source.path;
|
|
1457
|
+
const resolvedPath = path.replace("$HOME", homedir());
|
|
1458
|
+
const varName = `sources_${(source.name ?? basename(path, extname(resolvedPath))).replace(/\s+/g, "_")}`;
|
|
1459
|
+
consola.info("Extra variable:", varName, resolvedPath);
|
|
1460
|
+
return new ExtraVariable(varName, `'${resolvedPath}'`);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
function parseProjectConfig(filePath) {
|
|
1464
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1465
|
+
const result = ProjectSchema.safeParse(YAML.parse(content));
|
|
1466
|
+
if (!result.success) {
|
|
1467
|
+
const prettyError = z.prettifyError(result.error);
|
|
1468
|
+
throw new Error(`Error parsing project file ${filePath}:\n${prettyError}`);
|
|
1469
|
+
}
|
|
1470
|
+
return result.data;
|
|
1471
|
+
}
|
|
1472
|
+
function detectParameterType(value) {
|
|
1473
|
+
const num = Number(value);
|
|
1474
|
+
if (!Number.isNaN(num)) {
|
|
1475
|
+
if (Number.isInteger(num)) return "INTEGER";
|
|
1476
|
+
return "REAL";
|
|
1477
|
+
}
|
|
1478
|
+
if (value.toLowerCase() === "true" || value.toLowerCase() === "false") return "BOOLEAN";
|
|
1479
|
+
return "TEXT";
|
|
1480
|
+
}
|
|
1481
|
+
function getOutputPath(projectDir, sqlFileName, gen, generator) {
|
|
1482
|
+
const pathParts = [];
|
|
1483
|
+
if (!gen.output.startsWith("/")) pathParts.push(projectDir);
|
|
1484
|
+
if (gen.output.endsWith("/")) {
|
|
1485
|
+
const name = generator.getFilename(sqlFileName);
|
|
1486
|
+
pathParts.push(gen.output, name);
|
|
1487
|
+
} else pathParts.push(gen.output);
|
|
1488
|
+
const outputPath = join(...pathParts);
|
|
1489
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1490
|
+
return outputPath;
|
|
1491
|
+
}
|
|
1492
|
+
function validateQueries(queries) {
|
|
1493
|
+
for (const query of queries) {
|
|
1494
|
+
if (query.isQuery && query.isPluck && query.columns.length !== 1) throw new Error(`Query ${query.id} in ${query.filename} has the ':pluck: option, must have exactly one column, but has ${query.columns.length} columns`);
|
|
1495
|
+
const columns = query.columns.map((col) => {
|
|
1496
|
+
const configColumn = query.config?.getColumnInfo(col.name);
|
|
1497
|
+
if (configColumn) return configColumn;
|
|
1498
|
+
return col;
|
|
1499
|
+
});
|
|
1500
|
+
query.allColumns = {
|
|
1501
|
+
name: query.id,
|
|
1502
|
+
nullable: false,
|
|
1503
|
+
type: new StructType(columns)
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
async function writeGeneratedFile(projectDir, gen, generator, file, queries) {
|
|
1508
|
+
await generator.beforeGenerate(projectDir, gen, queries);
|
|
1509
|
+
const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
|
|
1510
|
+
const name = gen.name ?? basename(file, extname(file));
|
|
1511
|
+
const sourceFile = generateSourceFile(name, queries, templatePath, generator, gen.config);
|
|
1512
|
+
const outputPath = getOutputPath(projectDir, name, gen, generator);
|
|
1513
|
+
writeFileSync(outputPath, sourceFile);
|
|
1514
|
+
consola.success(`Generated ${outputPath}`);
|
|
1515
|
+
await generator.afterGenerate(outputPath);
|
|
1516
|
+
return outputPath;
|
|
1517
|
+
}
|
|
1518
|
+
async function processProject(projectPath) {
|
|
1519
|
+
const projectDir = resolve(dirname(projectPath));
|
|
1520
|
+
const project = parseProjectConfig(projectPath);
|
|
1521
|
+
const extraVariables = createExtraVariables(project.sources ?? []);
|
|
1522
|
+
if (extraVariables.length > 0) consola.info("Extra variables:", extraVariables);
|
|
1523
|
+
const files = [];
|
|
1524
|
+
for (const sql of project.sql) for (const sqlFile of sql.files) {
|
|
1525
|
+
let queries;
|
|
1526
|
+
try {
|
|
1527
|
+
queries = parseSQLQueries(join(projectDir, sqlFile), extraVariables);
|
|
1528
|
+
const dbEngine = getDatabaseEngine(sql.engine);
|
|
1529
|
+
await dbEngine.initializeDatabase(queries);
|
|
1530
|
+
await dbEngine.executeQueries(queries);
|
|
1531
|
+
validateQueries(queries);
|
|
1532
|
+
await dbEngine.close();
|
|
1533
|
+
} catch (e) {
|
|
1534
|
+
consola.error(`Error processing SQL file ${sqlFile}: ${e}`);
|
|
1535
|
+
throw e;
|
|
1536
|
+
}
|
|
1537
|
+
for (const gen of sql.gen) {
|
|
1538
|
+
const generator = getGenerator(gen.generator);
|
|
1539
|
+
if (!generator.isCompatibleWith(sql.engine)) throw new Error(`File ${sqlFile}: Generator ${gen.generator} is not compatible with engine ${sql.engine}`);
|
|
1540
|
+
const outputPath = await writeGeneratedFile(projectDir, gen, generator, sqlFile, queries);
|
|
1541
|
+
files.push(outputPath);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return files;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
//#endregion
|
|
1548
|
+
//#region src/sqg.ts
|
|
1549
|
+
const version = process.env.npm_package_version ?? "0.1.1";
|
|
1550
|
+
const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
|
|
1551
|
+
consola.level = LogLevels.info;
|
|
1552
|
+
const program = new Command().name("sqg").description(description).version(version, "-v, --version", "output the version number").option("--verbose", "Enable debug logging").argument("<project>", "Path to the project YAML config").showHelpAfterError().showSuggestionAfterError().hook("preAction", (thisCommand) => {
|
|
1553
|
+
if (thisCommand.opts().verbose) consola.level = LogLevels.debug;
|
|
1554
|
+
}).action(async (projectPath) => {
|
|
1555
|
+
await processProject(projectPath);
|
|
1556
|
+
});
|
|
1557
|
+
if (process.argv.length <= 2) {
|
|
1558
|
+
program.outputHelp();
|
|
1559
|
+
exit(1);
|
|
1560
|
+
}
|
|
1561
|
+
try {
|
|
1562
|
+
await program.parseAsync(process.argv);
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
consola.error(err);
|
|
1565
|
+
exit(1);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
//#endregion
|
|
1569
|
+
export { };
|