@sqg/sqg 0.2.2 → 0.4.0

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/dist/sqg.mjs CHANGED
@@ -26,7 +26,7 @@ import estree from "prettier/plugins/estree";
26
26
  * This file enables self-documenting CLI help and validation.
27
27
  */
28
28
  /** Supported database engines */
29
- const SUPPORTED_ENGINES = [
29
+ const DB_ENGINES = [
30
30
  "sqlite",
31
31
  "duckdb",
32
32
  "postgres"
@@ -67,14 +67,16 @@ SQL Annotation Syntax:
67
67
  -- EXEC <name> Execute statement (INSERT/UPDATE/DELETE)
68
68
  -- MIGRATE <number> Schema migration (run in order)
69
69
  -- TESTDATA <name> Test data setup (not generated)
70
+ -- TABLE <name> :appender Table for bulk insert appender (DuckDB only)
70
71
 
71
72
  @set <varName> = <value> Define a variable
72
73
  \${varName} Reference a variable in SQL
73
74
 
74
75
  Modifiers:
75
- :one Return single row (or null) instead of array
76
- :pluck Return single column value (requires exactly 1 column)
77
- :all Return all rows (default)
76
+ :one Return single row (or null) instead of array
77
+ :pluck Return single column value (requires exactly 1 column)
78
+ :all Return all rows (default)
79
+ :appender Generate bulk insert appender for TABLE annotation
78
80
 
79
81
  Example:
80
82
  -- MIGRATE 1
@@ -83,6 +85,8 @@ Example:
83
85
  -- QUERY get_user :one
84
86
  @set id = 1
85
87
  SELECT * FROM users WHERE id = \${id};
88
+
89
+ -- TABLE users :appender
86
90
  `.trim();
87
91
  /**
88
92
  * Find similar generator names for typo suggestions
@@ -110,7 +114,7 @@ function formatGeneratorsHelp() {
110
114
  * Format engines for CLI help output
111
115
  */
112
116
  function formatEnginesHelp() {
113
- return SUPPORTED_ENGINES.map((e) => ` ${e}`).join("\n");
117
+ return DB_ENGINES.map((e) => ` ${e}`).join("\n");
114
118
  }
115
119
 
116
120
  //#endregion
@@ -280,21 +284,21 @@ function formatErrorForOutput(err) {
280
284
  //#region src/parser/sql-parser.ts
281
285
  const parser = LRParser.deserialize({
282
286
  version: 14,
283
- 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",
284
- 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_[~",
287
+ states: "&SOVQPOOO_QPO'#CwOdQPO'#CzOiQPO'#CvO!SQQO'#C^OOQO'#Cn'#CnQVQPOOO!aQSO,59cO!lQPO,59fOOQO'#Cp'#CpO!tQQO,59bOOQO'#C}'#C}O#iQQO'#CiOOQO,58x,58xOOQO-E6l-E6lOOQO'#Co'#CoO!aQSO1G.}O!dQSO1G.}OOQO'#Cb'#CbOOQO1G.}1G.}O#yQPO1G/QOOQO-E6n-E6nO$RQPO'#CdOOQO'#Cq'#CqO$WQQO1G.|OOQO'#Cm'#CmOOQO'#Cr'#CrO$xQQO,59TOOQO-E6m-E6mO!dQSO7+$iOOQO7+$i7+$iO%YQPO,59OOOQO-E6o-E6oOOQO-E6p-E6pOOQO<<HT<<HTO%_QQO1G.jOOQO'#Ce'#CeOiQPO7+$UO%jQQO<<Gp",
288
+ stateData: "&l~OiOS~ORPOVQO~OSVO~OSWO~OlXO~OYZOZZO[ZO^ZO_ZO`ZO~OR]PV]Pg]P~PnOT_OlXOmbO~OT_Olna~OlXOofORjaVjaYjaZja[ja^ja_ja`jagja~OliOR]XV]Xg]X~PnOT_Olni~OSoO~OofORjiVjiYjiZji[ji^ji_ji`jigji~OliOR]aV]ag]a~PnOpsO~OYtOZtO[tO~OlXORWyVWyYWyZWy[Wy^Wy_Wy`WygWyoWy~OR`o^iZTmYV_[~",
285
289
  goto: "#prPPsPPPwP!R!VPPP!YPPP!]!a!g!q#T#ZPPP#a#ePP#ePP#iTTOUQcVSn`aRrmTgYhRusR]STj[kQUOR^UQ`VQdWTl`dQYRQaVWeYamvQm`RvuQhYRphQk[RqkTSOUTROUQ[STj[k",
286
290
  nodeNames: "⚠ File QueryBlock BlockCommentStartSpecial Name Modifiers Config LineCommentStartSpecial SetVarLine Value StringLiteral StringLiteralSingle SQLText SQLBlock BlockComment LineComment VarRef BR",
287
291
  maxTerm: 33,
288
292
  skippedNodes: [0],
289
293
  repeatNodeCount: 5,
290
- 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",
294
+ tokenData: "$3b~RqOX#YXY'wYZ(iZ]#Y]^$W^p#Ypq'wqr#Yrs(}st#Ytu6^uw#Ywx9[xz#Yz{%_{}#Y}!OKi!O!P#Y!P!Q#%p!Q![$)l![!]$+U!]!_#Y!_!`$.U!`!b#Y!b!c$/U!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#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!8V!h!oNY!o!p!<w!p!sNY!s!t!DP!t!vNY!v!w!Hu!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!1xZ_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!2k!d!g! ]!g!h!3}!h;'S! ];'S;=`!#e<%lO! ]V!2pX_QOY! ]YZ$WZz! ]z{! w{!d! ]!d!e!3]!e;'S! ];'S;=`!#e<%lO! ]V!3bX_QOY! ]YZ$WZz! ]z{! w{!n! ]!n!o!.]!o;'S! ];'S;=`!#e<%lO! ]V!4SX_QOY! ]YZ$WZz! ]z{! w{!u! ]!u!v!4o!v;'S! ];'S;=`!#e<%lO! ]V!4tX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!5a!w;'S! ];'S;=`!#e<%lO! ]V!5fX_QOY! ]YZ$WZz! ]z{! w{!f! ]!f!g!6R!g;'S! ];'S;=`!#e<%lO! ]V!6WX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!6s!d;'S! ];'S;=`!#e<%lO! ]V!6xX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!7e!w;'S! ];'S;=`!#e<%lO! ]V!7jX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!*X!d;'S! ];'S;=`!#e<%lO! ]V!8^`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!zNY!z!{!9`!{;'SNY;'S;=`!&p<%lONYV!9g`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!:i!h;'SNY;'S;=`!&p<%lONYV!:p`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!eNY!e!f!;r!f;'SNY;'S;=`!&p<%lONYV!;{^VR_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYV!=O`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!kNY!k!l!>Q!l;'SNY;'S;=`!&p<%lONYV!>X`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!iNY!i!j!?Z!j;'SNY;'S;=`!&p<%lONYV!?b`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!@d!u;'SNY;'S;=`!&p<%lONYV!@k`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!Am!d;'SNY;'S;=`!&p<%lONYV!At`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Bv!w;'SNY;'S;=`!&p<%lONYV!B}`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!;r!h;'SNY;'S;=`!&p<%lONYV!DW`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!wNY!w!x!EY!x;'SNY;'S;=`!&p<%lONYV!Ea`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!Fc!h;'SNY;'S;=`!&p<%lONYV!Fj`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!Gl!u;'SNY;'S;=`!&p<%lONYV!Gs`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!{NY!{!|!;r!|;'SNY;'S;=`!&p<%lONYV!H|b_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!JU!d!gNY!g!h!Lh!h;'SNY;'S;=`!&p<%lONYV!J]`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!dNY!d!e!K_!e;'SNY;'S;=`!&p<%lONYV!Kf`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!nNY!n!o!Bv!o;'SNY;'S;=`!&p<%lONYV!Lo`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!uNY!u!v!Mq!v;'SNY;'S;=`!&p<%lONYV!Mx`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Nz!w;'SNY;'S;=`!&p<%lONYV# R`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!fNY!f!g#!T!g;'SNY;'S;=`!&p<%lONYV#![`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d##^!d;'SNY;'S;=`!&p<%lONYV##e`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w#$g!w;'SNY;'S;=`!&p<%lONYV#$n`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!;r!d;'SNY;'S;=`!&p<%lONYV#%u][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{#&n{;'S#Y;'S;=`'q<%lO#YV#&sh[QOX#(_XY#,`YZ#,`Z]#(_]^#)]^p#(_pq#,`qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#9m!Q!g#(_!g!h#>j!h!o#(_!o!p#Bv!p!s#(_!s!t#I`!t!v#(_!v!w#Mp!w;'S#(_;'S;=`#,Y<%lO#(_U#(d][QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_U#)`TOz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]U#)rVOz#)]z{#)o{!P#)]!P!Q#*X!Q;'S#)];'S;=`#*`<%lO#)]U#*`O^QmSU#*cP;=`<%l#)]U#*k_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#+j!Q;'S#(_;'S;=`#,Y<%lO#(_U#+sV^QmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PU#,]P;=`<%l#(_V#,cbOX#)]XY#,`YZ#,`Zp#)]pq#,`qz#)]z{#)o{!g#)]!g!h#-k!h!o#)]!o!p#/k!p!s#)]!s!t#2q!t!v#)]!v!w#4u!w;'S#)];'S;=`#*`<%lO#)]V#-nVOz#)]z{#)o{!z#)]!z!{#.T!{;'S#)];'S;=`#*`<%lO#)]V#.WVOz#)]z{#)o{!g#)]!g!h#.m!h;'S#)];'S;=`#*`<%lO#)]V#.pVOz#)]z{#)o{!e#)]!e!f#/V!f;'S#)];'S;=`#*`<%lO#)]V#/[TRROz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]V#/nVOz#)]z{#)o{!k#)]!k!l#0T!l;'S#)];'S;=`#*`<%lO#)]V#0WVOz#)]z{#)o{!i#)]!i!j#0m!j;'S#)];'S;=`#*`<%lO#)]V#0pVOz#)]z{#)o{!t#)]!t!u#1V!u;'S#)];'S;=`#*`<%lO#)]V#1YVOz#)]z{#)o{!c#)]!c!d#1o!d;'S#)];'S;=`#*`<%lO#)]V#1rVOz#)]z{#)o{!v#)]!v!w#2X!w;'S#)];'S;=`#*`<%lO#)]V#2[VOz#)]z{#)o{!g#)]!g!h#/V!h;'S#)];'S;=`#*`<%lO#)]V#2tVOz#)]z{#)o{!w#)]!w!x#3Z!x;'S#)];'S;=`#*`<%lO#)]V#3^VOz#)]z{#)o{!g#)]!g!h#3s!h;'S#)];'S;=`#*`<%lO#)]V#3vVOz#)]z{#)o{!t#)]!t!u#4]!u;'S#)];'S;=`#*`<%lO#)]V#4`VOz#)]z{#)o{!{#)]!{!|#/V!|;'S#)];'S;=`#*`<%lO#)]V#4xXOz#)]z{#)o{!c#)]!c!d#5e!d!g#)]!g!h#6g!h;'S#)];'S;=`#*`<%lO#)]V#5hVOz#)]z{#)o{!d#)]!d!e#5}!e;'S#)];'S;=`#*`<%lO#)]V#6QVOz#)]z{#)o{!n#)]!n!o#2X!o;'S#)];'S;=`#*`<%lO#)]V#6jVOz#)]z{#)o{!u#)]!u!v#7P!v;'S#)];'S;=`#*`<%lO#)]V#7SVOz#)]z{#)o{!v#)]!v!w#7i!w;'S#)];'S;=`#*`<%lO#)]V#7lVOz#)]z{#)o{!f#)]!f!g#8R!g;'S#)];'S;=`#*`<%lO#)]V#8UVOz#)]z{#)o{!c#)]!c!d#8k!d;'S#)];'S;=`#*`<%lO#)]V#8nVOz#)]z{#)o{!v#)]!v!w#9T!w;'S#)];'S;=`#*`<%lO#)]V#9WVOz#)]z{#)o{!c#)]!c!d#/V!d;'S#)];'S;=`#*`<%lO#)]U#9t]mS[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#:r][QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#;nTOz#;kz{#;}{;'S#;k;'S;=`#<l<%lO#;kQ#<QVOz#;kz{#;}{!P#;k!P!Q#<g!Q;'S#;k;'S;=`#<l<%lO#;kQ#<lO^QQ#<oP;=`<%l#;kQ#<w_[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{!P#:m!P!Q#=v!Q;'S#:m;'S;=`#>d<%lO#:mQ#=}V^Q[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ#>gP;=`<%l#:mV#>o_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!z#(_!z!{#?n!{;'S#(_;'S;=`#,Y<%lO#(_V#?s_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#@r!h;'S#(_;'S;=`#,Y<%lO#(_V#@w_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!e#(_!e!f#Av!f;'S#(_;'S;=`#,Y<%lO#(_V#A}]RR[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_V#B{_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!k#(_!k!l#Cz!l;'S#(_;'S;=`#,Y<%lO#(_V#DP_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!i#(_!i!j#EO!j;'S#(_;'S;=`#,Y<%lO#(_V#ET_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#FS!u;'S#(_;'S;=`#,Y<%lO#(_V#FX_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#GW!d;'S#(_;'S;=`#,Y<%lO#(_V#G]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w#H[!w;'S#(_;'S;=`#,Y<%lO#(_V#Ha_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Av!h;'S#(_;'S;=`#,Y<%lO#(_V#Ie_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!w#(_!w!x#Jd!x;'S#(_;'S;=`#,Y<%lO#(_V#Ji_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Kh!h;'S#(_;'S;=`#,Y<%lO#(_V#Km_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#Ll!u;'S#(_;'S;=`#,Y<%lO#(_V#Lq_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!{#(_!{!|#Av!|;'S#(_;'S;=`#,Y<%lO#(_V#Mua[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Nz!d!g#(_!g!h$#S!h;'S#(_;'S;=`#,Y<%lO#(_V$ P_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!d#(_!d!e$!O!e;'S#(_;'S;=`#,Y<%lO#(_V$!T_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!n#(_!n!o#H[!o;'S#(_;'S;=`#,Y<%lO#(_V$#X_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!u#(_!u!v$$W!v;'S#(_;'S;=`#,Y<%lO#(_V$$]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$%[!w;'S#(_;'S;=`#,Y<%lO#(_V$%a_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!f#(_!f!g$&`!g;'S#(_;'S;=`#,Y<%lO#(_V$&e_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d$'d!d;'S#(_;'S;=`#,Y<%lO#(_V$'i_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$(h!w;'S#(_;'S;=`#,Y<%lO#(_V$(m_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Av!d;'S#(_;'S;=`#,Y<%lO#(_V$)seSP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$)l![!c#Y!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#o;'S#Y;'S;=`'q<%lO#Y~$+Ze[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#Y~$,seT~[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#YV$.]]pP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU$/Z_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#g#Y#g#h$0Y#h;'S#Y;'S;=`'q<%lO#YU$0__[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#X#Y#X#Y$1^#Y;'S#Y;'S;=`'q<%lO#YU$1c_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#h#Y#h#i$2b#i;'S#Y;'S;=`'q<%lO#YU$2i]oQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#Y",
291
295
  tokenizers: [
292
296
  0,
293
297
  1,
294
298
  2
295
299
  ],
296
300
  topRules: { "File": [0, 1] },
297
- tokenPrec: 205
301
+ tokenPrec: 245
298
302
  });
299
303
 
300
304
  //#endregion
@@ -377,17 +381,40 @@ var SQLQuery = class {
377
381
  return missingVars;
378
382
  }
379
383
  };
384
+ /**
385
+ * Represents a TABLE annotation for generating appenders.
386
+ * TABLE annotations specify a table name for which to generate bulk insert appenders.
387
+ */
388
+ var TableInfo = class {
389
+ /** Columns introspected from the database table schema */
390
+ columns = [];
391
+ constructor(filename, id, tableName, includeColumns, hasAppender) {
392
+ this.filename = filename;
393
+ this.id = id;
394
+ this.tableName = tableName;
395
+ this.includeColumns = includeColumns;
396
+ this.hasAppender = hasAppender;
397
+ }
398
+ get skipGenerateFunction() {
399
+ return !this.hasAppender;
400
+ }
401
+ };
380
402
  function parseSQLQueries(filePath, extraVariables) {
381
403
  const content = readFileSync(filePath, "utf-8");
382
404
  consola.info(`Parsing SQL file: ${filePath}`);
383
405
  consola.debug(`File start: ${content.slice(0, 200)}`);
384
406
  const queries = [];
407
+ const tables = [];
385
408
  const cursor = parser.parse(content).cursor();
409
+ function getLineNumber(position) {
410
+ return content.slice(0, position).split("\n").length;
411
+ }
386
412
  function getStr(nodeName, optional = false) {
387
413
  const node = cursor.node.getChild(nodeName);
388
414
  if (!node) {
389
415
  if (optional) return;
390
- throw new Error(`${nodeName} not found`);
416
+ const lineNumber = getLineNumber(cursor.node.from);
417
+ throw new Error(`Node '${nodeName}' not found at line ${lineNumber}`);
391
418
  }
392
419
  return nodeStr(node);
393
420
  }
@@ -532,6 +559,21 @@ function parseSQLQueries(filePath, extraVariables) {
532
559
  if (to > from) sql.appendSql(content.slice(from, to));
533
560
  sql.trim();
534
561
  }
562
+ if (queryType === "TABLE") {
563
+ const hasAppender = modifiers.includes(":appender");
564
+ const tableName = sqlContentStr.trim() || name;
565
+ const includeColumns = [];
566
+ for (const mod of modifiers) {
567
+ const match = mod.match(/:appender\(([^)]+)\)/);
568
+ if (match) includeColumns.push(...match[1].split(",").map((c) => c.trim()));
569
+ }
570
+ const table = new TableInfo(filePath, name, tableName, includeColumns, hasAppender);
571
+ if (queryNames.has(name)) throw SqgError.inFile(`Duplicate name '${name}'`, "DUPLICATE_QUERY", filePath, { suggestion: `Rename one of the tables/queries to have a unique name` });
572
+ queryNames.add(name);
573
+ tables.push(table);
574
+ consola.debug(`Added table: ${name} -> ${tableName} (appender: ${hasAppender})`);
575
+ continue;
576
+ }
535
577
  consola.debug("Parsed query:", {
536
578
  type: queryType,
537
579
  name,
@@ -548,9 +590,13 @@ function parseSQLQueries(filePath, extraVariables) {
548
590
  consola.debug(`Added query: ${name} (${queryType})`);
549
591
  }
550
592
  while (cursor.next());
551
- consola.info(`Total queries parsed: ${queries.length}`);
593
+ consola.info(`Total queries parsed: ${queries.length}, tables: ${tables.length}`);
552
594
  consola.info(`Query names: ${queries.map((q) => q.id).join(", ")}`);
553
- return queries;
595
+ if (tables.length > 0) consola.info(`Table names: ${tables.map((t) => t.id).join(", ")}`);
596
+ return {
597
+ queries,
598
+ tables
599
+ };
554
600
  }
555
601
 
556
602
  //#endregion
@@ -658,6 +704,47 @@ const duckdb = new class {
658
704
  throw error;
659
705
  }
660
706
  }
707
+ async introspectTables(tables) {
708
+ const connection = this.connection;
709
+ if (!connection) throw new DatabaseError("DuckDB connection not initialized", "duckdb", "This is an internal error. Check that migrations completed successfully.");
710
+ for (const table of tables) {
711
+ consola.info(`Introspecting table schema: ${table.tableName}`);
712
+ try {
713
+ const rows = (await connection.runAndReadAll(`DESCRIBE ${table.tableName}`)).getRows();
714
+ function convertType(type) {
715
+ if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
716
+ if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
717
+ name: type.entryNames[index],
718
+ type: convertType(t),
719
+ nullable: true
720
+ })));
721
+ if (type instanceof DuckDBMapType) return new MapType({
722
+ name: "key",
723
+ type: convertType(type.keyType),
724
+ nullable: true
725
+ }, {
726
+ name: "value",
727
+ type: convertType(type.valueType),
728
+ nullable: true
729
+ });
730
+ if (type instanceof DuckDBEnumType) return new EnumType(type.values);
731
+ return type.toString();
732
+ }
733
+ table.columns = rows.map((row) => {
734
+ return {
735
+ name: row[0],
736
+ type: row[1],
737
+ nullable: row[2] !== "NO"
738
+ };
739
+ });
740
+ consola.debug(`Table ${table.tableName} columns:`, table.columns);
741
+ consola.success(`Introspected table: ${table.tableName} (${table.columns.length} columns)`);
742
+ } catch (error) {
743
+ consola.error(`Failed to introspect table '${table.tableName}':`, error);
744
+ throw error;
745
+ }
746
+ }
747
+ }
661
748
  close() {
662
749
  this.connection.closeSync();
663
750
  }
@@ -752,6 +839,27 @@ const postgres = new class {
752
839
  throw error;
753
840
  }
754
841
  }
842
+ async introspectTables(tables) {
843
+ const db = this.db;
844
+ if (!db) throw new DatabaseError("PostgreSQL database not initialized", "postgres", "This is an internal error. Check that migrations completed successfully.");
845
+ for (const table of tables) {
846
+ consola.info(`Introspecting table schema: ${table.tableName}`);
847
+ try {
848
+ table.columns = (await db.query(`SELECT column_name, data_type, is_nullable
849
+ FROM information_schema.columns
850
+ WHERE table_name = $1
851
+ ORDER BY ordinal_position`, [table.tableName])).rows.map((row) => ({
852
+ name: row.column_name,
853
+ type: row.data_type.toUpperCase(),
854
+ nullable: row.is_nullable === "YES"
855
+ }));
856
+ consola.success(`Introspected table: ${table.tableName} (${table.columns.length} columns)`);
857
+ } catch (error) {
858
+ consola.error(`Failed to introspect table '${table.tableName}':`, error);
859
+ throw error;
860
+ }
861
+ }
862
+ }
755
863
  async close() {
756
864
  await this.db.end();
757
865
  await this.dbInitial.query(`DROP DATABASE "${databaseName}"`);
@@ -791,6 +899,20 @@ const sqlite = new class {
791
899
  throw error;
792
900
  }
793
901
  }
902
+ introspectTables(tables) {
903
+ const db = this.db;
904
+ if (!db) throw new DatabaseError("SQLite database not initialized", "sqlite", "This is an internal error. Migrations may have failed silently.");
905
+ for (const table of tables) {
906
+ consola.info(`Introspecting table schema: ${table.tableName}`);
907
+ const info = this.getTableInfo(db, table.tableName);
908
+ table.columns = Array.from(info.values()).map((col) => ({
909
+ name: col.name,
910
+ type: col.type || "TEXT",
911
+ nullable: col.notnull === 0 && col.pk === 0
912
+ }));
913
+ consola.success(`Introspected table: ${table.tableName} (${table.columns.length} columns)`);
914
+ }
915
+ }
794
916
  close() {
795
917
  this.db.close();
796
918
  }
@@ -1206,10 +1328,13 @@ var BaseGenerator = class {
1206
1328
  listType(column) {
1207
1329
  return this.typeMapper.listType(column);
1208
1330
  }
1209
- async beforeGenerate(_projectDir, _gen, _queries) {}
1331
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {}
1210
1332
  isCompatibleWith(_engine) {
1211
1333
  return true;
1212
1334
  }
1335
+ supportsAppenders(_engine) {
1336
+ return false;
1337
+ }
1213
1338
  functionReturnType(query) {
1214
1339
  if (query.isOne) return this.rowType(query);
1215
1340
  return this.typeMapper.formatListType(this.rowType(query));
@@ -1233,6 +1358,9 @@ var JavaGenerator = class extends BaseGenerator {
1233
1358
  super(template, new JavaTypeMapper());
1234
1359
  this.template = template;
1235
1360
  }
1361
+ supportsAppenders(engine) {
1362
+ return engine === "duckdb";
1363
+ }
1236
1364
  getFunctionName(id) {
1237
1365
  return camelCase$1(id);
1238
1366
  }
@@ -1272,7 +1400,7 @@ var JavaGenerator = class extends BaseGenerator {
1272
1400
  readColumn(column, index, path) {
1273
1401
  return this.typeMapper.parseValue(column, `rs.getObject(${index + 1})`, path);
1274
1402
  }
1275
- async beforeGenerate(_projectDir, _gen, _queries) {
1403
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {
1276
1404
  Handlebars.registerHelper("partsToString", (parts) => this.partsToString(parts));
1277
1405
  Handlebars.registerHelper("declareTypes", (queryHelper) => {
1278
1406
  const query = queryHelper.query;
@@ -1324,7 +1452,7 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
1324
1452
  getFunctionName(id) {
1325
1453
  return this.javaGenerator.getFunctionName(id);
1326
1454
  }
1327
- async beforeGenerate(projectDir, gen, queries) {
1455
+ async beforeGenerate(projectDir, gen, queries, tables) {
1328
1456
  const q = queries.filter((q$1) => q$1.isQuery && q$1.isOne || q$1.isMigrate);
1329
1457
  const name = `${gen.name}-jdbc`;
1330
1458
  writeGeneratedFile(projectDir, {
@@ -1332,11 +1460,14 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
1332
1460
  generator: "java/jdbc",
1333
1461
  output: gen.output,
1334
1462
  config: gen.config
1335
- }, this.javaGenerator, name, q);
1463
+ }, this.javaGenerator, name, q, tables, "duckdb");
1336
1464
  }
1337
1465
  isCompatibleWith(engine) {
1338
1466
  return engine === "duckdb";
1339
1467
  }
1468
+ supportsAppenders(_engine) {
1469
+ return true;
1470
+ }
1340
1471
  getFilename(sqlFileName) {
1341
1472
  return this.javaGenerator.getFilename(sqlFileName);
1342
1473
  }
@@ -1344,25 +1475,28 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
1344
1475
  return this.javaGenerator.getClassName(name);
1345
1476
  }
1346
1477
  mapType(column) {
1347
- const { type, nullable } = column;
1478
+ const { type } = column;
1348
1479
  if (typeof type === "string") {
1349
1480
  const mappedType = {
1350
1481
  INTEGER: "IntVector",
1482
+ BIGINT: "BigIntVector",
1351
1483
  BOOLEAN: "BitVector",
1352
1484
  DOUBLE: "Float8Vector",
1353
1485
  FLOAT: "Float4Vector",
1354
1486
  VARCHAR: "VarCharVector",
1355
- TEXT: "VarCharVector"
1487
+ TEXT: "VarCharVector",
1488
+ TIMESTAMP: "TimeStampVector",
1489
+ DATE: "DateDayVector",
1490
+ TIME: "TimeMicroVector"
1356
1491
  }[type.toUpperCase()];
1357
1492
  if (!mappedType) consola.warn("(duckdb-arrow) Mapped type is unknown:", type);
1358
1493
  return mappedType ?? "Object";
1359
1494
  }
1360
- const mockColumn = {
1361
- name: "",
1362
- type,
1363
- nullable
1364
- };
1365
- return this.typeMapper.getTypeName(mockColumn);
1495
+ if (type instanceof ListType) return "ListVector";
1496
+ if (type instanceof StructType) return "StructVector";
1497
+ if (type instanceof MapType) return "MapVector";
1498
+ consola.warn("(duckdb-arrow) Unknown complex type:", type);
1499
+ return "Object";
1366
1500
  }
1367
1501
  mapParameterType(type, nullable) {
1368
1502
  return this.typeMapper.getTypeName({
@@ -1410,8 +1544,45 @@ var TsGenerator = class extends BaseGenerator {
1410
1544
  getClassName(name) {
1411
1545
  return pascalCase$1(name);
1412
1546
  }
1413
- async beforeGenerate(_projectDir, _gen, _queries) {
1547
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {
1414
1548
  Handlebars.registerHelper("quote", (value) => this.quote(value));
1549
+ Handlebars.registerHelper("appendMethod", (column) => {
1550
+ const typeStr = column.type?.toString().toUpperCase() || "";
1551
+ if (typeStr === "INTEGER" || typeStr === "INT" || typeStr === "INT4" || typeStr === "SIGNED") return "Integer";
1552
+ if (typeStr === "SMALLINT" || typeStr === "INT2" || typeStr === "SHORT") return "SmallInt";
1553
+ if (typeStr === "TINYINT" || typeStr === "INT1") return "TinyInt";
1554
+ if (typeStr === "BIGINT" || typeStr === "INT8" || typeStr === "LONG") return "BigInt";
1555
+ if (typeStr === "HUGEINT" || typeStr === "INT128") return "HugeInt";
1556
+ if (typeStr === "UINTEGER" || typeStr === "UINT4") return "UInteger";
1557
+ if (typeStr === "USMALLINT" || typeStr === "UINT2") return "USmallInt";
1558
+ if (typeStr === "UTINYINT" || typeStr === "UINT1") return "UTinyInt";
1559
+ if (typeStr === "UBIGINT" || typeStr === "UINT8") return "UBigInt";
1560
+ if (typeStr === "DOUBLE" || typeStr === "FLOAT8" || typeStr === "NUMERIC" || typeStr === "DECIMAL") return "Double";
1561
+ if (typeStr === "FLOAT" || typeStr === "FLOAT4" || typeStr === "REAL") return "Float";
1562
+ if (typeStr === "BOOLEAN" || typeStr === "BOOL" || typeStr === "LOGICAL") return "Boolean";
1563
+ if (typeStr === "DATE") return "Date";
1564
+ if (typeStr === "TIMESTAMP" || typeStr.includes("TIMESTAMP")) return "Timestamp";
1565
+ if (typeStr === "TIME" || typeStr.includes("TIME")) return "Time";
1566
+ if (typeStr === "BLOB" || typeStr === "BYTEA" || typeStr === "BINARY" || typeStr === "VARBINARY") return "Blob";
1567
+ if (typeStr === "UUID") return "Uuid";
1568
+ if (typeStr === "INTERVAL") return "Interval";
1569
+ return "Varchar";
1570
+ });
1571
+ Handlebars.registerHelper("tsTypeForAppender", (column) => {
1572
+ const typeStr = column.type?.toString().toUpperCase() || "";
1573
+ let baseType;
1574
+ if (typeStr === "INTEGER" || typeStr === "INT" || typeStr === "INT4" || typeStr === "SMALLINT" || typeStr === "INT2" || typeStr === "TINYINT" || typeStr === "INT1" || typeStr === "UINTEGER" || typeStr === "UINT4" || typeStr === "USMALLINT" || typeStr === "UINT2" || typeStr === "UTINYINT" || typeStr === "UINT1" || typeStr === "DOUBLE" || typeStr === "FLOAT8" || typeStr === "FLOAT" || typeStr === "FLOAT4" || typeStr === "REAL") baseType = "number";
1575
+ else if (typeStr === "BIGINT" || typeStr === "INT8" || typeStr === "HUGEINT" || typeStr === "INT128" || typeStr === "UBIGINT" || typeStr === "UINT8") baseType = "bigint";
1576
+ else if (typeStr === "BOOLEAN" || typeStr === "BOOL") baseType = "boolean";
1577
+ else if (typeStr === "DATE") baseType = "DuckDBDateValue";
1578
+ else if (typeStr === "TIMESTAMP" || typeStr.includes("TIMESTAMP")) baseType = "DuckDBTimestampValue";
1579
+ else if (typeStr === "TIME" || typeStr.includes("TIME")) baseType = "DuckDBTimeValue";
1580
+ else if (typeStr === "BLOB" || typeStr === "BYTEA") baseType = "DuckDBBlobValue";
1581
+ else if (typeStr === "UUID") baseType = "string";
1582
+ else baseType = "string";
1583
+ if (column.nullable) return `${baseType} | null`;
1584
+ return baseType;
1585
+ });
1415
1586
  Handlebars.registerHelper("tsType", (column) => {
1416
1587
  const inlineType = (col) => {
1417
1588
  const t = col.type;
@@ -1533,8 +1704,11 @@ var TsDuckDBGenerator = class extends TsGenerator {
1533
1704
  constructor(template) {
1534
1705
  super(template);
1535
1706
  }
1536
- async beforeGenerate(projectDir, gen, queries) {
1537
- await super.beforeGenerate(projectDir, gen, queries);
1707
+ supportsAppenders(_engine) {
1708
+ return true;
1709
+ }
1710
+ async beforeGenerate(projectDir, gen, queries, tables) {
1711
+ await super.beforeGenerate(projectDir, gen, queries, tables);
1538
1712
  Handlebars.registerHelper("tsType", (column) => {
1539
1713
  const inlineType = (col) => {
1540
1714
  const t = col.type;
@@ -1595,7 +1769,7 @@ function getGenerator(generator) {
1595
1769
 
1596
1770
  //#endregion
1597
1771
  //#region src/sqltool.ts
1598
- const GENERATED_FILE_COMMENT = "This file is generated by SQG. Do not edit manually.";
1772
+ const GENERATED_FILE_COMMENT = "This file is generated by SQG (https://sqg.dev). Do not edit manually.";
1599
1773
  const configSchema = z.object({ result: z.record(z.string(), z.string()).optional() });
1600
1774
  var Config = class Config {
1601
1775
  constructor(result) {
@@ -1707,15 +1881,50 @@ var SqlQueryHelper = class {
1707
1881
  return this.generator.typeMapper;
1708
1882
  }
1709
1883
  };
1710
- function generateSourceFile(name, queries, templatePath, generator, config) {
1884
+ /** Util class to help generating appenders for tables */
1885
+ var TableHelper = class {
1886
+ constructor(table, generator) {
1887
+ this.table = table;
1888
+ this.generator = generator;
1889
+ }
1890
+ get id() {
1891
+ return this.table.id;
1892
+ }
1893
+ get tableName() {
1894
+ return this.table.tableName;
1895
+ }
1896
+ get columns() {
1897
+ if (this.table.includeColumns.length > 0) return this.table.columns.filter((c) => this.table.includeColumns.includes(c.name));
1898
+ return this.table.columns;
1899
+ }
1900
+ get skipGenerateFunction() {
1901
+ return this.table.skipGenerateFunction;
1902
+ }
1903
+ get functionName() {
1904
+ return this.generator.getFunctionName(`create_${this.table.id}_appender`);
1905
+ }
1906
+ get className() {
1907
+ return this.generator.getClassName(`${this.table.id}_appender`);
1908
+ }
1909
+ get rowTypeName() {
1910
+ return this.generator.getClassName(`${this.table.id}_row`);
1911
+ }
1912
+ get typeMapper() {
1913
+ return this.generator.typeMapper;
1914
+ }
1915
+ };
1916
+ function generateSourceFile(name, queries, tables, templatePath, generator, engine, config) {
1711
1917
  const templateSrc = readFileSync(templatePath, "utf-8");
1712
1918
  const template = Handlebars.compile(templateSrc);
1713
1919
  Handlebars.registerHelper("mapType", (column) => generator.mapType(column));
1714
1920
  Handlebars.registerHelper("plusOne", (value) => value + 1);
1921
+ const migrations = queries.filter((q) => q.isMigrate).map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q)));
1922
+ const tableHelpers = generator.supportsAppenders(engine) ? tables.filter((t) => !t.skipGenerateFunction).map((t) => new TableHelper(t, generator)) : [];
1715
1923
  return template({
1716
1924
  generatedComment: GENERATED_FILE_COMMENT,
1717
- migrations: queries.filter((q) => q.isMigrate).map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
1925
+ migrations,
1718
1926
  queries: queries.map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
1927
+ tables: tableHelpers,
1719
1928
  className: generator.getClassName(name),
1720
1929
  config
1721
1930
  }, {
@@ -1730,7 +1939,7 @@ const ProjectSchema = z.object({
1730
1939
  version: z.number().describe("Configuration version (currently 1)"),
1731
1940
  name: z.string().min(1, "Project name is required").describe("Project name used for generated class names"),
1732
1941
  sql: z.array(z.object({
1733
- engine: z.enum(SUPPORTED_ENGINES).describe(`Database engine: ${SUPPORTED_ENGINES.join(", ")}`),
1942
+ engine: z.enum(DB_ENGINES).describe(`Database engine: ${DB_ENGINES.join(", ")}`),
1734
1943
  files: z.array(z.string().min(1)).min(1, "At least one SQL file is required").describe("SQL files to process"),
1735
1944
  gen: z.array(z.object({
1736
1945
  generator: z.enum(GENERATOR_NAMES).describe(`Code generator: ${GENERATOR_NAMES.join(", ")}`),
@@ -1781,7 +1990,7 @@ function parseProjectConfig(filePath) {
1781
1990
  const obj = parsed;
1782
1991
  if (obj.sql && Array.isArray(obj.sql)) for (let i = 0; i < obj.sql.length; i++) {
1783
1992
  const sqlConfig = obj.sql[i];
1784
- if (sqlConfig.engine && !SUPPORTED_ENGINES.includes(sqlConfig.engine)) throw new InvalidEngineError(String(sqlConfig.engine), [...SUPPORTED_ENGINES]);
1993
+ if (sqlConfig.engine && !DB_ENGINES.includes(sqlConfig.engine)) throw new InvalidEngineError(String(sqlConfig.engine), [...DB_ENGINES]);
1785
1994
  if (sqlConfig.gen && Array.isArray(sqlConfig.gen)) for (let j = 0; j < sqlConfig.gen.length; j++) {
1786
1995
  const genConfig = sqlConfig.gen[j];
1787
1996
  if (genConfig.generator && !GENERATOR_NAMES.includes(genConfig.generator)) {
@@ -1830,11 +2039,11 @@ function validateQueries(queries) {
1830
2039
  };
1831
2040
  }
1832
2041
  }
1833
- async function writeGeneratedFile(projectDir, gen, generator, file, queries) {
1834
- await generator.beforeGenerate(projectDir, gen, queries);
2042
+ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables, engine) {
2043
+ await generator.beforeGenerate(projectDir, gen, queries, tables);
1835
2044
  const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
1836
2045
  const name = gen.name ?? basename(file, extname(file));
1837
- const sourceFile = generateSourceFile(name, queries, templatePath, generator, gen.config);
2046
+ const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.config);
1838
2047
  const outputPath = getOutputPath(projectDir, name, gen, generator);
1839
2048
  writeFileSync(outputPath, sourceFile);
1840
2049
  consola.success(`Generated ${outputPath}`);
@@ -1922,8 +2131,11 @@ async function processProject(projectPath) {
1922
2131
  const fullPath = join(projectDir, sqlFile);
1923
2132
  if (!existsSync(fullPath)) throw new FileNotFoundError(fullPath, projectDir);
1924
2133
  let queries;
2134
+ let tables;
1925
2135
  try {
1926
- queries = parseSQLQueries(fullPath, extraVariables);
2136
+ const parseResult = parseSQLQueries(fullPath, extraVariables);
2137
+ queries = parseResult.queries;
2138
+ tables = parseResult.tables;
1927
2139
  } catch (e) {
1928
2140
  if (e instanceof SqgError) throw e;
1929
2141
  throw SqgError.inFile(`Failed to parse SQL file: ${e.message}`, "SQL_PARSE_ERROR", sqlFile, { suggestion: "Check SQL syntax and annotation format" });
@@ -1932,6 +2144,7 @@ async function processProject(projectPath) {
1932
2144
  const dbEngine = getDatabaseEngine(sql.engine);
1933
2145
  await dbEngine.initializeDatabase(queries);
1934
2146
  await dbEngine.executeQueries(queries);
2147
+ if (tables.length > 0) await dbEngine.introspectTables(tables);
1935
2148
  validateQueries(queries);
1936
2149
  await dbEngine.close();
1937
2150
  } catch (e) {
@@ -1942,7 +2155,7 @@ async function processProject(projectPath) {
1942
2155
  });
1943
2156
  }
1944
2157
  for (const gen of sql.gen) {
1945
- const outputPath = await writeGeneratedFile(projectDir, gen, getGenerator(gen.generator), sqlFile, queries);
2158
+ const outputPath = await writeGeneratedFile(projectDir, gen, getGenerator(gen.generator), sqlFile, queries, tables, sql.engine);
1946
2159
  files.push(outputPath);
1947
2160
  }
1948
2161
  }
@@ -2200,7 +2413,7 @@ sql:
2200
2413
  async function initProject(options) {
2201
2414
  const engine = options.engine || "sqlite";
2202
2415
  const output = options.output || "./generated";
2203
- if (!SUPPORTED_ENGINES.includes(engine)) throw new InvalidEngineError(engine, [...SUPPORTED_ENGINES]);
2416
+ if (!DB_ENGINES.includes(engine)) throw new InvalidEngineError(engine, [...DB_ENGINES]);
2204
2417
  let generator;
2205
2418
  if (options.generator) {
2206
2419
  if (!(options.generator in SUPPORTED_GENERATORS)) {
@@ -2242,7 +2455,7 @@ Documentation: https://sqg.dev
2242
2455
 
2243
2456
  //#endregion
2244
2457
  //#region src/sqg.ts
2245
- const version = process.env.npm_package_version ?? "0.2.2";
2458
+ const version = process.env.npm_package_version ?? "0.4.0";
2246
2459
  const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
2247
2460
  consola.level = LogLevels.info;
2248
2461
  const program = new Command().name("sqg").description(`${description}
@@ -2292,7 +2505,7 @@ program.argument("<project>", "Path to the project YAML config (sqg.yaml)").hook
2292
2505
  exit(1);
2293
2506
  }
2294
2507
  });
2295
- program.command("init").description("Initialize a new SQG project with example configuration").option("-e, --engine <engine>", `Database engine (${SUPPORTED_ENGINES.join(", ")})`, "sqlite").option("-g, --generator <generator>", `Code generator (${GENERATOR_NAMES.join(", ")})`).option("-o, --output <dir>", "Output directory for generated files", "./generated").option("-f, --force", "Overwrite existing files").action(async (options) => {
2508
+ program.command("init").description("Initialize a new SQG project with example configuration").option("-e, --engine <engine>", `Database engine (${DB_ENGINES.join(", ")})`, "sqlite").option("-g, --generator <generator>", `Code generator (${GENERATOR_NAMES.join(", ")})`).option("-o, --output <dir>", "Output directory for generated files", "./generated").option("-f, --force", "Overwrite existing files").action(async (options) => {
2296
2509
  const parentOpts = program.opts();
2297
2510
  try {
2298
2511
  await initProject(options);
@@ -14,6 +14,9 @@ import org.apache.arrow.vector.complex.ListVector;
14
14
  import org.apache.arrow.vector.ipc.ArrowReader;
15
15
  import org.duckdb.DuckDBConnection;
16
16
  import org.duckdb.DuckDBResultSet;
17
+ {{#if tables.length}}
18
+ import org.duckdb.DuckDBAppender;
19
+ {{/if}}
17
20
  import {{config.package}}.{{className}}Jdbc.*;
18
21
 
19
22
  public class {{className}} {
@@ -46,9 +49,18 @@ public class {{className}} {
46
49
  {{/if}}
47
50
  {{/unless}}
48
51
  {{/each}}
49
- }
50
52
 
53
+ {{#if tables.length}}
54
+ // ==================== Appenders ====================
55
+ {{#each tables}}
51
56
 
57
+ /** Create an appender for bulk inserts into {{tableName}} */
58
+ public {{className}} {{functionName}}() throws SQLException {
59
+ return jdbc.{{functionName}}();
60
+ }
61
+ {{/each}}
62
+ {{/if}}
63
+ }
52
64
 
53
65
 
54
66
  {{#*inline "columnTypesRecord"}}
@@ -57,7 +69,7 @@ public class {{className}} {
57
69
  public record {{rowType}}({{#each columns}}{{type}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
58
70
  {{else}}
59
71
  public record {{rowType}}(PreparedStatement statement, RootAllocator allocator, ArrowReader reader,
60
- {{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) implements AutoCloseable {
72
+ {{#each columns}}{{{mapType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) implements AutoCloseable {
61
73
  public boolean loadNextBatch() throws IOException {
62
74
  return reader.loadNextBatch();
63
75
  }
@@ -87,7 +99,7 @@ int
87
99
  {{#*inline "readVectors"}}
88
100
  var root = reader.getVectorSchemaRoot();
89
101
 
90
- return new {{rowType}}(stmt, allocator, reader, {{#each columns}}({{mapType this}})root.getVector("{{name}}"){{#unless @last}}, {{/unless}}{{/each}});
102
+ return new {{rowType}}(stmt, allocator, reader, {{#each columns}}({{{mapType this}}})root.getVector("{{name}}"){{#unless @last}}, {{/unless}}{{/each}});
91
103
 
92
104
  {{/inline~}}
93
105
 
@@ -21,6 +21,10 @@ import java.util.HashMap;
21
21
  import java.util.Collections;
22
22
  import java.util.UUID;
23
23
  import java.util.function.Function;
24
+ {{#if tables.length}}
25
+ import org.duckdb.DuckDBAppender;
26
+ import org.duckdb.DuckDBConnection;
27
+ {{/if}}
24
28
 
25
29
  public class {{className}} {
26
30
  private final Connection connection;
@@ -139,8 +143,72 @@ public class {{className}} {
139
143
  }
140
144
  {{/unless}}
141
145
  {{/each}}
142
- }
143
146
 
147
+ {{#if tables.length}}
148
+ // ==================== Appenders ====================
149
+ {{#each tables}}
150
+
151
+ /** Create an appender for bulk inserts into {{tableName}} */
152
+ public {{className}} {{functionName}}() throws SQLException {
153
+ return new {{className}}(((DuckDBConnection) connection).createAppender(DuckDBConnection.DEFAULT_SCHEMA, "{{tableName}}"));
154
+ }
155
+ {{/each}}
156
+ {{/if}}
157
+
158
+
159
+ {{#each tables}}
160
+ /** Row type for {{tableName}} appender */
161
+ public record {{rowTypeName}}({{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
162
+
163
+ /** Appender for bulk inserts into {{tableName}} */
164
+ public static class {{className}} implements AutoCloseable {
165
+ private final DuckDBAppender appender;
166
+
167
+ {{className}}(DuckDBAppender appender) {
168
+ this.appender = appender;
169
+ }
170
+
171
+ /** Get the underlying DuckDB appender for advanced operations */
172
+ public DuckDBAppender getAppender() {
173
+ return appender;
174
+ }
175
+
176
+ /** Append a single row */
177
+ public {{className}} append({{rowTypeName}} row) throws SQLException {
178
+ appender.beginRow();
179
+ {{#each columns}}
180
+ appender.append(row.{{name}}());
181
+ {{/each}}
182
+ appender.endRow();
183
+ return this;
184
+ }
185
+
186
+ /** Append a single row with individual values */
187
+ public {{className}} append({{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
188
+ appender.beginRow();
189
+ {{#each columns}}
190
+ appender.append({{name}});
191
+ {{/each}}
192
+ appender.endRow();
193
+ return this;
194
+ }
195
+
196
+ /** Append multiple rows */
197
+ public {{className}} appendMany(Iterable<{{rowTypeName}}> rows) throws SQLException {
198
+ for (var row : rows) {
199
+ append(row);
200
+ }
201
+ return this;
202
+ }
203
+
204
+ /** Flush and close the appender */
205
+ @Override
206
+ public void close() throws SQLException {
207
+ appender.close();
208
+ }
209
+ }
210
+ {{/each}}
211
+ }
144
212
 
145
213
  {{#*inline "columnTypesRecord"}}
146
214
  {{#if isQuery}}
@@ -1,5 +1,5 @@
1
1
  // {{generatedComment}}
2
- import type { DuckDBConnection, DuckDBMaterializedResult } from "@duckdb/node-api";
2
+ import type { DuckDBConnection, DuckDBMaterializedResult, DuckDBAppender, DuckDBDateValue, DuckDBTimeValue, DuckDBTimestampValue, DuckDBBlobValue } from "@duckdb/node-api";
3
3
 
4
4
  export class {{className}} {
5
5
 
@@ -46,7 +46,67 @@ export class {{className}} {
46
46
  {{/unless}}
47
47
 
48
48
  {{/each}}
49
+
50
+ {{#if tables.length}}
51
+ // ==================== Appenders ====================
52
+ {{#each tables}}
53
+
54
+ async {{functionName}}(): Promise<{{className}}> {
55
+ return new {{className}}(await this.conn.createAppender('{{tableName}}'));
56
+ }
57
+ {{/each}}
58
+ {{/if}}
59
+ }
60
+
61
+ {{#each tables}}
62
+ /** Row type for {{tableName}} appender */
63
+ export interface {{rowTypeName}} {
64
+ {{#each columns}}
65
+ {{name}}: {{{tsTypeForAppender this}}};
66
+ {{/each}}
67
+ }
68
+
69
+ /** Appender for bulk inserts into {{tableName}} */
70
+ export class {{className}} {
71
+ constructor(public readonly appender: DuckDBAppender) {}
72
+
73
+ /** Append a single row */
74
+ append(row: {{rowTypeName}}): this {
75
+ {{#each columns}}
76
+ {{#if nullable}}
77
+ if (row.{{name}} === null || row.{{name}} === undefined) {
78
+ this.appender.appendNull();
79
+ } else {
80
+ this.appender.append{{appendMethod this}}(row.{{name}});
81
+ }
82
+ {{else}}
83
+ this.appender.append{{appendMethod this}}(row.{{name}});
84
+ {{/if}}
85
+ {{/each}}
86
+ this.appender.endRow();
87
+ return this;
88
+ }
89
+
90
+ /** Append multiple rows */
91
+ appendMany(rows: {{rowTypeName}}[]): this {
92
+ for (const row of rows) {
93
+ this.append(row);
94
+ }
95
+ return this;
96
+ }
97
+
98
+ /** Flush buffered data to the table */
99
+ flush(): this {
100
+ this.appender.flushSync();
101
+ return this;
102
+ }
103
+
104
+ /** Flush and close the appender */
105
+ close(): void {
106
+ this.appender.closeSync();
107
+ }
49
108
  }
109
+ {{/each}}
50
110
 
51
111
  {{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
52
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqg/sqg",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
5
5
  "type": "module",
6
6
  "bin": {