@sqg/sqg 0.9.0 → 0.11.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
@@ -680,7 +680,7 @@ var EnumType = class {
680
680
  var SQLQuery = class {
681
681
  columns;
682
682
  allColumns;
683
- /** Database-reported parameter types (variable name → SQL type name), set by database adapters */
683
+ /** Database-reported parameter types (variable name → SQL type), set by database adapters */
684
684
  parameterTypes;
685
685
  constructor(filename, id, rawQuery, queryAnonymous, queryNamed, queryPositional, type, isOne, isPluck, variables, config) {
686
686
  this.filename = filename;
@@ -966,6 +966,25 @@ async function initializeDatabase(queries, execQueries) {
966
966
 
967
967
  //#endregion
968
968
  //#region src/db/duckdb.ts
969
+ function convertType(type) {
970
+ if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
971
+ if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
972
+ name: type.entryNames[index],
973
+ type: convertType(t),
974
+ nullable: true
975
+ })));
976
+ if (type instanceof DuckDBMapType) return new MapType({
977
+ name: "key",
978
+ type: convertType(type.keyType),
979
+ nullable: true
980
+ }, {
981
+ name: "value",
982
+ type: convertType(type.valueType),
983
+ nullable: true
984
+ });
985
+ if (type instanceof DuckDBEnumType) return new EnumType(type.values);
986
+ return type.toString();
987
+ }
969
988
  const duckdb = new class {
970
989
  db;
971
990
  connection;
@@ -1005,6 +1024,15 @@ const duckdb = new class {
1005
1024
  return ` ${part.value} `;
1006
1025
  }).join("");
1007
1026
  const stmt = await connection.prepare(sql);
1027
+ if (stmt.parameterCount > 0) {
1028
+ const paramTypes = /* @__PURE__ */ new Map();
1029
+ for (let i = 0; i < stmt.parameterCount; i++) {
1030
+ const paramType = stmt.parameterType(i + 1);
1031
+ paramTypes.set(statement.parameters[i].name, convertType(paramType));
1032
+ }
1033
+ query.parameterTypes = paramTypes;
1034
+ consola.debug("Parameter types:", Object.fromEntries(paramTypes));
1035
+ }
1008
1036
  for (let i = 0; i < stmt.parameterCount; i++) stmt.bindValue(i + 1, statement.parameters[i].value);
1009
1037
  if (query.isQuery) {
1010
1038
  const result = await stmt.stream();
@@ -1012,25 +1040,6 @@ const duckdb = new class {
1012
1040
  const columnTypes = result.columnTypes();
1013
1041
  consola.debug("Columns:", columnNames);
1014
1042
  consola.debug("Types:", columnTypes.map((t) => `${t.toString()} / ${t.constructor.name}`));
1015
- function convertType(type) {
1016
- if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
1017
- if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
1018
- name: type.entryNames[index],
1019
- type: convertType(t),
1020
- nullable: true
1021
- })));
1022
- if (type instanceof DuckDBMapType) return new MapType({
1023
- name: "key",
1024
- type: convertType(type.keyType),
1025
- nullable: true
1026
- }, {
1027
- name: "value",
1028
- type: convertType(type.valueType),
1029
- nullable: true
1030
- });
1031
- if (type instanceof DuckDBEnumType) return new EnumType(type.values);
1032
- return type.toString();
1033
- }
1034
1043
  query.columns = columnNames.map((name, index) => ({
1035
1044
  name,
1036
1045
  type: convertType(columnTypes[index]),
@@ -1053,33 +1062,17 @@ const duckdb = new class {
1053
1062
  for (const table of tables) {
1054
1063
  consola.info(`Introspecting table schema: ${table.tableName}`);
1055
1064
  try {
1056
- const rows = (await connection.runAndReadAll(`DESCRIBE ${table.tableName}`)).getRows();
1057
- function convertType(type) {
1058
- if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
1059
- if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
1060
- name: type.entryNames[index],
1061
- type: convertType(t),
1062
- nullable: true
1063
- })));
1064
- if (type instanceof DuckDBMapType) return new MapType({
1065
- name: "key",
1066
- type: convertType(type.keyType),
1067
- nullable: true
1068
- }, {
1069
- name: "value",
1070
- type: convertType(type.valueType),
1071
- nullable: true
1072
- });
1073
- if (type instanceof DuckDBEnumType) return new EnumType(type.values);
1074
- return type.toString();
1075
- }
1076
- table.columns = rows.map((row) => {
1077
- return {
1078
- name: row[0],
1079
- type: row[1],
1080
- nullable: row[2] !== "NO"
1081
- };
1082
- });
1065
+ const descRows = (await connection.runAndReadAll(`DESCRIBE ${table.tableName}`)).getRows();
1066
+ const nullabilityMap = /* @__PURE__ */ new Map();
1067
+ for (const row of descRows) nullabilityMap.set(row[0], row[2] !== "NO");
1068
+ const stream = await (await connection.prepare(`SELECT * FROM ${table.tableName} LIMIT 0`)).stream();
1069
+ const columnNames = stream.columnNames();
1070
+ const columnTypes = stream.columnTypes();
1071
+ table.columns = columnNames.map((name, index) => ({
1072
+ name,
1073
+ type: convertType(columnTypes[index]),
1074
+ nullable: nullabilityMap.get(name) ?? true
1075
+ }));
1083
1076
  consola.debug(`Table ${table.tableName} columns:`, table.columns);
1084
1077
  consola.success(`Introspected table: ${table.tableName} (${table.columns.length} columns)`);
1085
1078
  } catch (error) {
@@ -1208,10 +1201,10 @@ const postgres = new class {
1208
1201
  return value;
1209
1202
  });
1210
1203
  if (statement.parameters.length > 0) try {
1211
- await db.query(`DEALLOCATE ALL`);
1204
+ await db.query("DEALLOCATE ALL");
1212
1205
  await db.query(`PREPARE sqg_param_check AS ${statement.sql}`);
1213
1206
  const paramTypeResult = await db.query(`SELECT unnest(parameter_types)::oid AS oid FROM pg_prepared_statements WHERE name = 'sqg_param_check'`);
1214
- await db.query(`DEALLOCATE sqg_param_check`);
1207
+ await db.query("DEALLOCATE sqg_param_check");
1215
1208
  if (paramTypeResult.rows.length === statement.parameters.length) {
1216
1209
  const paramTypes = /* @__PURE__ */ new Map();
1217
1210
  for (let i = 0; i < statement.parameters.length; i++) {
@@ -1466,7 +1459,7 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
1466
1459
  TIMESTAMP_S: "Instant",
1467
1460
  TIMESTAMP_MS: "Instant",
1468
1461
  TIMESTAMP_NS: "Instant",
1469
- "TIMESTAMP WITH TIME ZONE": "Instant",
1462
+ "TIMESTAMP WITH TIME ZONE": "OffsetDateTime",
1470
1463
  UUID: "UUID",
1471
1464
  INTERVAL: "String",
1472
1465
  BIT: "String",
@@ -1607,6 +1600,7 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
1607
1600
  const upperType = column.type?.toString().toUpperCase() ?? "";
1608
1601
  if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime((java.sql.Timestamp)${value})`;
1609
1602
  if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime((java.sql.Timestamp)${value})`;
1603
+ if (upperType === "TIMESTAMP WITH TIME ZONE") return `(OffsetDateTime)${value}`;
1610
1604
  if (upperType === "DATE") return `toLocalDate((java.sql.Date)${value})`;
1611
1605
  if (upperType === "TIME") return `toLocalTime((java.sql.Time)${value})`;
1612
1606
  if (upperType.startsWith("_")) {
@@ -1864,6 +1858,9 @@ var JavaGenerator = class extends BaseGenerator {
1864
1858
  }, " ");
1865
1859
  return queryHelper.typeMapper.getDeclarations(query.allColumns);
1866
1860
  });
1861
+ Handlebars.registerHelper("appenderType", (column) => {
1862
+ return this.mapType(column);
1863
+ });
1867
1864
  Handlebars.registerHelper("readColumns", (queryHelper) => {
1868
1865
  const query = queryHelper.query;
1869
1866
  if (queryHelper.isPluck) return this.readColumn({
@@ -1983,6 +1980,24 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
1983
1980
 
1984
1981
  //#endregion
1985
1982
  //#region src/generators/typescript-generator.ts
1983
+ /** Resolve a ColumnType to its DuckDB type constant name (e.g. "VARCHAR", "INTEGER") for use in generated code. */
1984
+ function resolveElementType(baseType) {
1985
+ if (baseType instanceof ListType) return `new DuckDBListType(${resolveElementType(baseType.baseType)})`;
1986
+ return {
1987
+ VARCHAR: "VARCHAR",
1988
+ TEXT: "VARCHAR",
1989
+ INTEGER: "INTEGER",
1990
+ INT: "INTEGER",
1991
+ BIGINT: "BIGINT",
1992
+ DOUBLE: "DOUBLE",
1993
+ FLOAT: "FLOAT",
1994
+ BOOLEAN: "BOOLEAN",
1995
+ DATE: "DATE",
1996
+ TIMESTAMP: "TIMESTAMP",
1997
+ SMALLINT: "SMALLINT",
1998
+ TINYINT: "TINYINT"
1999
+ }[baseType.toString().toUpperCase()] || "VARCHAR";
2000
+ }
1986
2001
  var TsGenerator = class extends BaseGenerator {
1987
2002
  constructor(template) {
1988
2003
  super(template, new TypeScriptTypeMapper());
@@ -2002,6 +2017,7 @@ var TsGenerator = class extends BaseGenerator {
2002
2017
  async beforeGenerate(_projectDir, _gen, _queries, _tables) {
2003
2018
  Handlebars.registerHelper("quote", (value) => this.quote(value));
2004
2019
  Handlebars.registerHelper("appendMethod", (column) => {
2020
+ if (column.type instanceof ListType) return "List";
2005
2021
  const typeStr = column.type?.toString().toUpperCase() || "";
2006
2022
  if (typeStr === "INTEGER" || typeStr === "INT" || typeStr === "INT4" || typeStr === "SIGNED") return "Integer";
2007
2023
  if (typeStr === "SMALLINT" || typeStr === "INT2" || typeStr === "SHORT") return "SmallInt";
@@ -2023,7 +2039,15 @@ var TsGenerator = class extends BaseGenerator {
2023
2039
  if (typeStr === "INTERVAL") return "Interval";
2024
2040
  return "Varchar";
2025
2041
  });
2042
+ Handlebars.registerHelper("appendListTypeArg", (column) => {
2043
+ if (!(column.type instanceof ListType)) return "";
2044
+ return `, new DuckDBListType(${resolveElementType(column.type.baseType)})`;
2045
+ });
2026
2046
  Handlebars.registerHelper("tsTypeForAppender", (column) => {
2047
+ if (column.type instanceof ListType) {
2048
+ const baseType$1 = "readonly DuckDBValue[]";
2049
+ return column.nullable ? `${baseType$1} | null` : baseType$1;
2050
+ }
2027
2051
  const typeStr = column.type?.toString().toUpperCase() || "";
2028
2052
  let baseType;
2029
2053
  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";
@@ -2164,6 +2188,21 @@ var TsDuckDBGenerator = class extends TsGenerator {
2164
2188
  }
2165
2189
  async beforeGenerate(projectDir, gen, queries, tables) {
2166
2190
  await super.beforeGenerate(projectDir, gen, queries, tables);
2191
+ Handlebars.registerHelper("hasListParams", (queryHelper) => {
2192
+ const paramTypes = queryHelper.query.parameterTypes;
2193
+ if (!paramTypes) return false;
2194
+ for (const [, colType] of paramTypes) if (colType instanceof ListType) return true;
2195
+ return false;
2196
+ });
2197
+ Handlebars.registerHelper("bindStatements", (queryHelper) => {
2198
+ const paramNames = queryHelper.parameterNames;
2199
+ const paramTypes = queryHelper.query.parameterTypes;
2200
+ return paramNames.map((name, i) => {
2201
+ const colType = paramTypes?.get(name);
2202
+ if (colType instanceof ListType) return `stmt.bindList(${i + 1}, ${name}.items, new DuckDBListType(${resolveElementType(colType.baseType)}));`;
2203
+ return `stmt.bindValue(${i + 1}, ${name});`;
2204
+ }).join("\n ");
2205
+ });
2167
2206
  Handlebars.registerHelper("tsType", (column) => {
2168
2207
  const inlineType = (col) => {
2169
2208
  const t = col.type;
@@ -2311,10 +2350,24 @@ var SqlQueryHelper = class {
2311
2350
  }
2312
2351
  get parameters() {
2313
2352
  const vars = new Map(this.variables.map((param) => [param.name, param.type]));
2314
- return this.statement.parameters.map((param) => ({
2315
- name: param.name,
2316
- type: vars.get(param.name)
2317
- }));
2353
+ return this.statement.parameters.map((param) => {
2354
+ const rawType = this.query.parameterTypes?.get(param.name);
2355
+ let isArray = false;
2356
+ let arrayBaseType = null;
2357
+ if (rawType instanceof ListType) {
2358
+ isArray = true;
2359
+ arrayBaseType = rawType.baseType.toString();
2360
+ } else if (typeof rawType === "string" && rawType.startsWith("_")) {
2361
+ isArray = true;
2362
+ arrayBaseType = rawType.substring(1);
2363
+ }
2364
+ return {
2365
+ name: param.name,
2366
+ type: vars.get(param.name),
2367
+ isArray,
2368
+ arrayBaseType
2369
+ };
2370
+ });
2318
2371
  }
2319
2372
  get columns() {
2320
2373
  if (!(this.query.allColumns.type instanceof StructType)) throw new Error(`Expected StructType ${this.query.allColumns.type}`);
@@ -2533,7 +2586,10 @@ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tab
2533
2586
  await generator.beforeGenerate(projectDir, gen, queries, tables);
2534
2587
  const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
2535
2588
  const name = gen.name ?? basename(file, extname(file));
2536
- const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.projectName ?? name, gen.config);
2589
+ const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.projectName ?? name, {
2590
+ migrations: true,
2591
+ ...gen.config
2592
+ });
2537
2593
  if (writeToStdout) {
2538
2594
  process.stdout.write(sourceFile);
2539
2595
  if (!sourceFile.endsWith("\n")) process.stdout.write("\n");
@@ -2687,7 +2743,7 @@ async function processProject(projectPath) {
2687
2743
  //#region src/mcp-server.ts
2688
2744
  const server = new Server({
2689
2745
  name: "sqg-mcp",
2690
- version: process.env.npm_package_version ?? "0.9.0"
2746
+ version: process.env.npm_package_version ?? "0.11.0"
2691
2747
  }, { capabilities: {
2692
2748
  tools: {},
2693
2749
  resources: {}
@@ -3016,7 +3072,7 @@ async function startMcpServer() {
3016
3072
 
3017
3073
  //#endregion
3018
3074
  //#region src/sqg.ts
3019
- const version = process.env.npm_package_version ?? "0.9.0";
3075
+ const version = process.env.npm_package_version ?? "0.11.0";
3020
3076
  const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
3021
3077
  consola.level = LogLevels.info;
3022
3078
  const program = new Command().name("sqg").description(`${description}
@@ -17,11 +17,17 @@ import java.time.OffsetDateTime;
17
17
  import java.time.OffsetTime;
18
18
  import java.util.ArrayList;
19
19
  import java.util.Arrays;
20
+ import java.util.Iterator;
20
21
  import java.util.List;
21
22
  import java.util.HashMap;
22
23
  import java.util.Collections;
24
+ import java.util.NoSuchElementException;
25
+ import java.util.Spliterator;
26
+ import java.util.Spliterators;
23
27
  import java.util.UUID;
24
28
  import java.util.function.Function;
29
+ import java.util.stream.Stream;
30
+ import java.util.stream.StreamSupport;
25
31
  {{#if tables.length}}
26
32
  import org.duckdb.DuckDBAppender;
27
33
  import org.duckdb.DuckDBConnection;
@@ -203,6 +209,37 @@ public class {{className}} {
203
209
  {{> execute}}
204
210
  }
205
211
  }
212
+ {{#if isQuery}}{{#unless isOne}}
213
+ public Stream<{{rowType}}> {{functionName}}Stream({{#each variables}}{{{type}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
214
+ var stmt = connection.prepareStatement({{{partsToString sqlQueryParts}}});
215
+ {{#each parameters}}{{#if isArray}}stmt.setArray({{plusOne @index}}, connection.createArrayOf("{{arrayBaseType}}", {{name}}.toArray()));
216
+ {{else}}stmt.setObject({{plusOne @index}}, {{name}});
217
+ {{/if}}{{/each}}
218
+ var rs = stmt.executeQuery();
219
+ var iter = new Iterator<{{rowType}}>() {
220
+ private Boolean hasNext = null;
221
+ public boolean hasNext() {
222
+ if (hasNext == null) {
223
+ try { hasNext = rs.next(); }
224
+ catch (SQLException e) { throw new RuntimeException(e); }
225
+ }
226
+ return hasNext;
227
+ }
228
+ public {{rowType}} next() {
229
+ if (!hasNext()) throw new NoSuchElementException();
230
+ hasNext = null;
231
+ try { return {{> readRow}}; }
232
+ catch (SQLException e) { throw new RuntimeException(e); }
233
+ }
234
+ };
235
+ return StreamSupport.stream(
236
+ Spliterators.spliteratorUnknownSize(iter, Spliterator.ORDERED), false
237
+ ).onClose(() -> {
238
+ try { rs.close(); stmt.close(); }
239
+ catch (SQLException e) { throw new RuntimeException(e); }
240
+ });
241
+ }
242
+ {{/unless}}{{/if}}
206
243
  {{/unless}}
207
244
  {{/each}}
208
245
 
@@ -220,7 +257,7 @@ public class {{className}} {
220
257
 
221
258
  {{#each tables}}
222
259
  /** Row type for {{tableName}} appender */
223
- public record {{rowTypeName}}({{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
260
+ public record {{rowTypeName}}({{#each columns}}{{{appenderType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
224
261
 
225
262
  /** Appender for bulk inserts into {{tableName}} */
226
263
  public static class {{className}} implements AutoCloseable {
@@ -246,7 +283,7 @@ public static class {{className}} implements AutoCloseable {
246
283
  }
247
284
 
248
285
  /** Append a single row with individual values */
249
- public {{className}} append({{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
286
+ public {{className}} append({{#each columns}}{{{appenderType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
250
287
  appender.beginRow();
251
288
  {{#each columns}}
252
289
  appender.append({{name}});
@@ -291,8 +328,9 @@ int
291
328
  {{/inline~}}
292
329
 
293
330
  {{#*inline "execute"}}
294
- {{#each parameterNames}}stmt.setObject({{plusOne @index}}, {{this}});
295
- {{/each}}
331
+ {{#each parameters}}{{#if isArray}}stmt.setArray({{plusOne @index}}, connection.createArrayOf("{{arrayBaseType}}", {{name}}.toArray()));
332
+ {{else}}stmt.setObject({{plusOne @index}}, {{name}});
333
+ {{/if}}{{/each}}
296
334
  {{#if isQuery}}
297
335
  try(var rs = stmt.executeQuery()) {
298
336
  {{#if isOne}}
@@ -33,36 +33,32 @@ export class {{className}} {
33
33
 
34
34
  {{#if config.migrations}}
35
35
  static async applyMigrations(db: Database, projectName = '{{projectName}}'): Promise<void> {
36
- const createStmt = await db.prepare(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
36
+ const createStmt = db.prepare(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
37
37
  project TEXT NOT NULL,
38
38
  migration_id TEXT NOT NULL,
39
39
  applied_at TEXT NOT NULL DEFAULT (datetime('now')),
40
40
  PRIMARY KEY (project, migration_id)
41
41
  )`);
42
42
  await createStmt.run();
43
- const tx = await db.transaction('write');
44
- try {
45
- const selectStmt = await tx.prepare('SELECT migration_id FROM _sqg_migrations WHERE project = ?');
46
- const rows = await selectStmt.all(projectName) as { migration_id: string }[];
47
- const applied = new Set(rows.map(r => r.migration_id));
48
- const migrations: [string, string][] = [
49
- {{#each migrations}}
50
- ['{{{id}}}', {{{quote sqlQuery}}}],
51
- {{/each}}
52
- ];
43
+ const selectStmt = db.prepare('SELECT migration_id FROM _sqg_migrations WHERE project = ?');
44
+ const rows = await selectStmt.all(projectName) as { migration_id: string }[];
45
+ const applied = new Set(rows.map(r => r.migration_id));
46
+ const migrations: [string, string][] = [
47
+ {{#each migrations}}
48
+ ['{{{id}}}', {{{quote sqlQuery}}}],
49
+ {{/each}}
50
+ ];
51
+ const applyFn = db.transaction(async () => {
53
52
  for (const [id, sql] of migrations) {
54
53
  if (!applied.has(id)) {
55
- const execStmt = await tx.prepare(sql);
54
+ const execStmt = db.prepare(sql);
56
55
  await execStmt.run();
57
- const insertStmt = await tx.prepare('INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)');
56
+ const insertStmt = db.prepare('INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)');
58
57
  await insertStmt.run(projectName, id);
59
58
  }
60
59
  }
61
- await tx.commit();
62
- } catch (e) {
63
- await tx.rollback();
64
- throw e;
65
- }
60
+ });
61
+ await applyFn();
66
62
  }
67
63
  {{/if}}
68
64
 
@@ -1,5 +1,5 @@
1
1
  // {{generatedComment}}
2
- import type { DuckDBConnection, DuckDBMaterializedResult, DuckDBAppender, DuckDBDateValue, DuckDBTimeValue, DuckDBTimestampValue, DuckDBBlobValue } from "@duckdb/node-api";
2
+ import { DuckDBListType, listValue, VARCHAR, INTEGER, BIGINT, DOUBLE, FLOAT, BOOLEAN, SMALLINT, TINYINT, type DuckDBConnection, type DuckDBMaterializedResult, type DuckDBAppender, type DuckDBDateValue, type DuckDBTimeValue, type DuckDBTimestampValue, type DuckDBBlobValue, type DuckDBValue } from "@duckdb/node-api";
3
3
 
4
4
  export class {{className}} {
5
5
 
@@ -60,6 +60,28 @@ export class {{className}} {
60
60
  {{#unless skipGenerateFunction}}
61
61
  async {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{> returnType }}> {
62
62
  const sql = {{{quote sqlQuery}}};
63
+ {{#if (hasListParams this)}}
64
+ const stmt = await this.conn.prepare(sql);
65
+ {{{bindStatements this}}}
66
+ {{#if isQuery}}
67
+ const reader = await stmt.runAndReadAll();
68
+ {{#if isPluck}}
69
+ {{#if isOne}}
70
+ return reader.getRows()[0]?.[0] as {{> rowType}} | undefined;
71
+ {{else}}
72
+ return reader.getRows().map((row) => row[0] as {{> rowType}});
73
+ {{/if}}
74
+ {{else}}
75
+ {{#if isOne}}
76
+ return reader.getRowObjects()[0] as {{> rowType}} | undefined;
77
+ {{else}}
78
+ return reader.getRowObjects() as {{> rowType}}[];
79
+ {{/if}}
80
+ {{/if}}
81
+ {{else}}
82
+ return await stmt.run();
83
+ {{/if}}
84
+ {{else}}
63
85
  {{#if isQuery}}
64
86
  const reader = await this.conn.runAndReadAll(sql,[{{> params}}]);
65
87
  {{#if isPluck}}
@@ -78,6 +100,7 @@ export class {{className}} {
78
100
  {{else}}
79
101
  return await this.conn.run(sql,[{{> params}}]);
80
102
  {{/if}}
103
+ {{/if}}
81
104
  }
82
105
  {{/unless}}
83
106
 
@@ -113,10 +136,10 @@ export class {{className}} {
113
136
  if (row.{{name}} === null || row.{{name}} === undefined) {
114
137
  this.appender.appendNull();
115
138
  } else {
116
- this.appender.append{{appendMethod this}}(row.{{name}});
139
+ this.appender.append{{appendMethod this}}(row.{{name}}{{{appendListTypeArg this}}});
117
140
  }
118
141
  {{else}}
119
- this.appender.append{{appendMethod this}}(row.{{name}});
142
+ this.appender.append{{appendMethod this}}(row.{{name}}{{{appendListTypeArg this}}});
120
143
  {{/if}}
121
144
  {{/each}}
122
145
  this.appender.endRow();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqg/sqg",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
5
5
  "type": "module",
6
6
  "bin": {