@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.
@@ -0,0 +1,96 @@
1
+ // {{generatedComment}}
2
+ import type { Database, RunResult, Statement } from 'better-sqlite3';
3
+
4
+ export class {{className}} {
5
+ private statements = new Map<string, Statement>();
6
+
7
+ constructor(private db: Database) {}
8
+
9
+ private prepare<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(id: string, query: string, isPluck = false) {
10
+ let stmt = this.statements.get(id);
11
+ if (!stmt) {
12
+ stmt = this.db.prepare(query);
13
+ if (isPluck) {
14
+ stmt = stmt.pluck();
15
+ }
16
+ this.statements.set(id, stmt);
17
+ }
18
+ return stmt as Statement<BindParameters, Result>;
19
+ }
20
+
21
+ static getMigrations(): string[] {
22
+ return [
23
+ {{#each migrations}}
24
+ {{{quote sqlQuery}}},
25
+ {{/each}}
26
+ ];
27
+ }
28
+
29
+ static getQueryNames(): Map<string, keyof {{className}}> {
30
+ return new Map([
31
+ {{#each queries}} {{#unless skipGenerateFunction}}
32
+ ["{{id}}", "{{functionName}}"],{{/unless}}{{/each}}]
33
+ );
34
+ }
35
+
36
+ {{#each queries}}
37
+ {{#unless skipGenerateFunction}}
38
+ {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): {{> returnType }} {
39
+ const stmt = this.prepare<[{{> paramTypes}}], {{> resultType}}>('{{id}}',
40
+ {{{quote sqlQuery}}}{{#if isPluck}}, true{{/if}});
41
+ {{> execute}}
42
+ }
43
+ {{/unless}}
44
+ {{/each}}
45
+ }
46
+
47
+ {{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
48
+
49
+ {{#*inline "paramTypes"}}{{#each parameters}}{{type}} {{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
50
+
51
+ {{#*inline "columnTypes"}}{{#each columns}}{{name}}: {{mapType .}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
52
+
53
+ {{#*inline "rowType"}}
54
+ {{#if isQuery~}}
55
+ {{#if isPluck~}}
56
+ {{#if columns.length~}} {{mapType (lookup columns 0)}} {{else}}any{{/if~}}
57
+ {{~else~}}
58
+ {{#if columns.length}}{ {{> columnTypes}} }{{else}}any{{/if~}}
59
+ {{/if~}}
60
+ {{~else~}}
61
+ any
62
+ {{~/if~}}
63
+ {{/inline~}}
64
+
65
+ {{#*inline "resultType"}}
66
+ {{#if isQuery~}}
67
+ {{> rowType}}
68
+ {{~else~}}
69
+ any
70
+ {{~/if~}}
71
+ {{/inline~}}
72
+
73
+
74
+ {{#*inline "returnType"}}
75
+ {{#if isQuery~}}
76
+ {{#if isOne~}}
77
+ {{> rowType}} | undefined
78
+ {{~else~}}
79
+ {{> rowType}}[]
80
+ {{/if~}}
81
+ {{~else~}}
82
+ RunResult
83
+ {{~/if~}}
84
+ {{/inline~}}
85
+
86
+ {{#*inline "execute"}}
87
+ {{#if isQuery}}
88
+ {{#if isOne}}
89
+ return stmt.get({{> params}});
90
+ {{else}}
91
+ return stmt.all({{> params}});
92
+ {{/if}}
93
+ {{else}}
94
+ return stmt.run({{> params}});
95
+ {{/if}}
96
+ {{/inline}}
@@ -0,0 +1,106 @@
1
+ // {{generatedComment}}
2
+ package {{config.package}};
3
+
4
+ import java.io.IOException;
5
+ import java.sql.Connection;
6
+ import java.sql.PreparedStatement;
7
+ import java.sql.ResultSet;
8
+ import java.sql.SQLException;
9
+ import java.util.ArrayList;
10
+ import java.util.List;
11
+ import org.apache.arrow.memory.RootAllocator;
12
+ import org.apache.arrow.vector.*;
13
+ import org.apache.arrow.vector.complex.ListVector;
14
+ import org.apache.arrow.vector.ipc.ArrowReader;
15
+ import org.duckdb.DuckDBConnection;
16
+ import org.duckdb.DuckDBResultSet;
17
+ import {{config.package}}.{{className}}Jdbc.*;
18
+
19
+ public class {{className}} {
20
+ private final DuckDBConnection connection;
21
+ private final {{className}}Jdbc jdbc;
22
+
23
+ public {{className}}(DuckDBConnection connection) {
24
+ this.connection = connection;
25
+ this.jdbc = new {{className}}Jdbc(connection);
26
+ }
27
+
28
+
29
+ public static List<String> getMigrations() {
30
+ return {{className}}Jdbc.getMigrations();
31
+ }
32
+
33
+ {{#each queries}}
34
+ {{#unless skipGenerateFunction}}
35
+ {{#if isOne}}
36
+ public {{> returnType}} {{functionName}}({{#each variables}}{{type}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
37
+ return jdbc.{{functionName}}({{#each variables}}{{name}}{{#unless @last}}, {{/unless}}{{/each}});
38
+ }
39
+ {{else}}
40
+ {{>columnTypesRecord}}
41
+ public {{> returnType}} {{functionName}}({{#each variables}}{{type}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException, IOException {
42
+ var stmt = connection.prepareStatement("""
43
+ {{{sqlQuery}}}""");
44
+ {{> execute}}
45
+ }
46
+ {{/if}}
47
+ {{/unless}}
48
+ {{/each}}
49
+ }
50
+
51
+
52
+
53
+
54
+ {{#*inline "columnTypesRecord"}}
55
+ {{#if isQuery}}
56
+ {{#if isOne}}
57
+ public record {{rowType}}({{#each columns}}{{type}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
58
+ {{else}}
59
+ public record {{rowType}}(PreparedStatement statement, RootAllocator allocator, ArrowReader reader,
60
+ {{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) implements AutoCloseable {
61
+ public boolean loadNextBatch() throws IOException {
62
+ return reader.loadNextBatch();
63
+ }
64
+
65
+ public int getRowCount() throws IOException {
66
+ return reader.getVectorSchemaRoot().getRowCount();
67
+ }
68
+
69
+ public void close() throws IOException, SQLException {
70
+ reader.close();
71
+ allocator.close();
72
+ statement.close();
73
+ }
74
+ }
75
+ {{/if}}
76
+ {{/if}}
77
+ {{/inline}}
78
+
79
+ {{#*inline "returnType"}}
80
+ {{#if isQuery~}}
81
+ {{functionReturnType}}
82
+ {{~else~}}
83
+ int
84
+ {{~/if~}}
85
+ {{/inline~}}
86
+
87
+ {{#*inline "readVectors"}}
88
+ var root = reader.getVectorSchemaRoot();
89
+
90
+ return new {{rowType}}(stmt, allocator, reader, {{#each columns}}({{mapType this}})root.getVector("{{name}}"){{#unless @last}}, {{/unless}}{{/each}});
91
+
92
+ {{/inline~}}
93
+
94
+
95
+ {{#*inline "execute"}}
96
+ {{#each parameterNames}}stmt.setObject({{plusOne @index}}, {{this}});
97
+ {{/each}}
98
+ {{#if isQuery}}
99
+ var rs = (DuckDBResultSet) stmt.executeQuery();
100
+ var allocator = new RootAllocator();
101
+ var reader = (ArrowReader) rs.arrowExportStream(allocator, 65536);
102
+ {{> readVectors}}
103
+ {{else}}
104
+ return stmt.executeUpdate();
105
+ {{/if}}
106
+ {{/inline}}
@@ -0,0 +1,169 @@
1
+ // {{generatedComment}}
2
+ package {{config.package}};
3
+
4
+ import java.sql.Array;
5
+ import java.sql.Connection;
6
+ import java.sql.PreparedStatement;
7
+ import java.sql.ResultSet;
8
+ import java.sql.SQLException;
9
+ import java.sql.Struct;
10
+ import java.math.BigDecimal;
11
+ import java.math.BigInteger;
12
+ import java.time.Instant;
13
+ import java.time.LocalDate;
14
+ import java.time.LocalDateTime;
15
+ import java.time.LocalTime;
16
+ import java.time.OffsetTime;
17
+ import java.util.ArrayList;
18
+ import java.util.Arrays;
19
+ import java.util.List;
20
+ import java.util.HashMap;
21
+ import java.util.Collections;
22
+ import java.util.UUID;
23
+ import java.util.function.Function;
24
+
25
+ public class {{className}} {
26
+ private final Connection connection;
27
+
28
+ public {{className}}(Connection connection) {
29
+ this.connection = connection;
30
+ }
31
+
32
+ private static Object[] getObjectArray(Array array) {
33
+ if (array == null) {
34
+ return null;
35
+ }
36
+ try {
37
+ return (Object[]) array.getArray();
38
+ } catch (SQLException e) {
39
+ throw new RuntimeException(e);
40
+ }
41
+ }
42
+ private static Object[] getAttr(Struct struct) {
43
+ if (struct == null) {
44
+ return null;
45
+ }
46
+ try {
47
+ return struct.getAttributes();
48
+ } catch (SQLException e) {
49
+ throw new RuntimeException(e);
50
+ }
51
+ }
52
+
53
+ private static <K> List<K> arrayToList(
54
+ Array array,
55
+ Class<? extends K[]> baseType
56
+ ) {
57
+ var objectArray = getObjectArray(array);
58
+ if (objectArray == null) {
59
+ return null;
60
+ }
61
+ return Collections.unmodifiableList(
62
+ Arrays.asList(
63
+ Arrays.copyOf(objectArray, objectArray.length, baseType)
64
+ )
65
+ );
66
+ }
67
+
68
+ private static <K> List<K> arrayOfStructToList(
69
+ Array array,
70
+ Function<Object[], K> convert
71
+ ) {
72
+ var objectArray = getObjectArray(array);
73
+ if (objectArray == null) {
74
+ return null;
75
+ }
76
+ var list = new ArrayList<K>(objectArray.length);
77
+ for (Object obj : objectArray) {
78
+ list.add(obj != null ? convert.apply(getAttr((Struct)obj)) : null);
79
+ }
80
+ return Collections.unmodifiableList(list);
81
+ }
82
+
83
+ @SuppressWarnings("unchecked")
84
+ private static <K> List<List<K>> multiDimArrayToList(
85
+ Array array,
86
+ Class<? extends K[]> baseType
87
+ ) {
88
+ var objectArray = getObjectArray(array);
89
+ if (objectArray == null) {
90
+ return null;
91
+ }
92
+ var list = new ArrayList<List<K>>(objectArray.length);
93
+ for (Object obj : objectArray) {
94
+ if (obj == null) {
95
+ list.add(null);
96
+ } else if (obj instanceof Array) {
97
+ list.add(arrayToList((Array)obj, baseType));
98
+ } else if (obj instanceof Object[]) {
99
+ var inner = (Object[]) obj;
100
+ list.add(Collections.unmodifiableList(
101
+ Arrays.asList(Arrays.copyOf(inner, inner.length, baseType))
102
+ ));
103
+ } else {
104
+ throw new RuntimeException("Unexpected type in multi-dimensional array: " + obj.getClass());
105
+ }
106
+ }
107
+ return Collections.unmodifiableList(list);
108
+ }
109
+
110
+ private static final List<String> migrations = List.of(
111
+ {{#each migrations}}"""
112
+ {{{sqlQuery}}}"""{{#unless @last}},{{/unless}}
113
+ {{/each}}
114
+ );
115
+
116
+ public static List<String> getMigrations() {
117
+ return migrations;
118
+ }
119
+
120
+ {{#each queries}}
121
+ {{#unless skipGenerateFunction}}
122
+ {{>columnTypesRecord}}
123
+ public {{> returnType}} {{functionName}}({{#each variables}}{{{type}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
124
+ try (var stmt = connection.prepareStatement({{{partsToString sqlQueryParts}}})) {
125
+ {{> execute}}
126
+ }
127
+ }
128
+ {{/unless}}
129
+ {{/each}}
130
+ }
131
+
132
+
133
+ {{#*inline "columnTypesRecord"}}
134
+ {{#if isQuery}}
135
+ {{{declareTypes this}}}
136
+ {{/if}}
137
+ {{/inline}}
138
+
139
+ {{#*inline "returnType"}}
140
+ {{#if isQuery~}}
141
+ {{functionReturnType}}
142
+ {{~else~}}
143
+ int
144
+ {{~/if~}}
145
+ {{/inline~}}
146
+
147
+ {{#*inline "readRow"}}
148
+ {{{readColumns this}}}
149
+ {{/inline~}}
150
+
151
+ {{#*inline "execute"}}
152
+ {{#each parameterNames}}stmt.setObject({{plusOne @index}}, {{this}});
153
+ {{/each}}
154
+ {{#if isQuery}}
155
+ try(var rs = stmt.executeQuery()) {
156
+ {{#if isOne}}
157
+ return rs.next() ? {{> readRow}} : null;
158
+ {{else}}
159
+ var results = new ArrayList<{{rowType}}>();
160
+ while (rs.next()) {
161
+ results.add({{> readRow}});
162
+ }
163
+ return results;
164
+ {{/if}}
165
+ }
166
+ {{else}}
167
+ return stmt.executeUpdate();
168
+ {{/if}}
169
+ {{/inline}}
@@ -0,0 +1,89 @@
1
+ // {{generatedComment}}
2
+ import type { DuckDBConnection, DuckDBMaterializedResult } from "@duckdb/node-api";
3
+
4
+ export class {{className}} {
5
+
6
+ constructor(private conn: DuckDBConnection) {}
7
+
8
+ static getMigrations(): string[] {
9
+ return [
10
+ {{#each migrations}}
11
+ {{{quote sqlQuery}}},
12
+ {{/each}}
13
+ ];
14
+ }
15
+
16
+ static getQueryNames(): Map<string, keyof {{className}}> {
17
+ return new Map([
18
+ {{#each queries}} {{#unless skipGenerateFunction}}
19
+ ["{{id}}", "{{functionName}}"],{{/unless}}{{/each}}]
20
+ );
21
+ }
22
+
23
+ {{#each queries}}
24
+ {{#unless skipGenerateFunction}}
25
+ async {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{> returnType }}> {
26
+ const sql = {{{quote sqlQuery}}};
27
+ {{#if isQuery}}
28
+ const reader = await this.conn.runAndReadAll(sql,[{{> params}}]);
29
+ {{#if isPluck}}
30
+ {{#if isOne}}
31
+ return reader.getRows()[0]?.[0] as {{> rowType}} | undefined;
32
+ {{else}}
33
+ return reader.getRows().map((row) => row[0] as {{> rowType}});
34
+ {{/if}}
35
+ {{else}}
36
+ {{#if isOne}}
37
+ return reader.getRowObjects()[0] as {{> rowType}} | undefined;
38
+ {{else}}
39
+ return reader.getRowObjects() as {{> rowType}}[];
40
+ {{/if}}
41
+ {{/if}}
42
+ {{else}}
43
+ return await this.conn.run(sql,[{{> params}}]);
44
+ {{/if}}
45
+ }
46
+ {{/unless}}
47
+
48
+ {{/each}}
49
+ }
50
+
51
+ {{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
52
+
53
+ {{#*inline "paramTypes"}}{{#each parameters}}{{type}} {{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
54
+
55
+ {{#*inline "columnTypes"}}{{#each columns}}{{name}}: {{{tsType .}}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
56
+
57
+ {{#*inline "rowType"}}
58
+ {{#if isQuery~}}
59
+ {{#if isPluck~}}
60
+ {{#if columns.length~}} {{{tsType (lookup columns 0)}}} {{else}}any{{/if~}}
61
+ {{~else~}}
62
+ {{#if columns.length}}{ {{> columnTypes}} }{{else}}any{{/if~}}
63
+ {{/if~}}
64
+ {{~else~}}
65
+ any
66
+ {{~/if~}}
67
+ {{/inline~}}
68
+
69
+ {{#*inline "resultType"}}
70
+ {{#if isQuery~}}
71
+ {{> rowType}}
72
+ {{~else~}}
73
+ any
74
+ {{~/if~}}
75
+ {{/inline~}}
76
+
77
+
78
+ {{#*inline "returnType"}}
79
+ {{#if isQuery~}}
80
+ {{#if isOne~}}
81
+ {{> rowType}} | undefined
82
+ {{~else~}}
83
+ ({{> rowType}})[]
84
+ {{/if~}}
85
+ {{~else~}}
86
+ DuckDBMaterializedResult
87
+ {{~/if~}}
88
+ {{/inline~}}
89
+
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@sqg/sqg",
3
+ "version": "0.1.1",
4
+ "description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
5
+ "type": "module",
6
+ "bin": {
7
+ "sqg": "./dist/sqg.mjs"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/sqg-dev/sqg.git"
18
+ },
19
+ "homepage": "https://sqg.dev",
20
+ "bugs": {
21
+ "url": "https://github.com/sqg-dev/sqg/issues"
22
+ },
23
+ "keywords": [],
24
+ "author": "Uwe Maurer",
25
+ "license": "Apache-2.0",
26
+ "dependencies": {
27
+ "@biomejs/biome": "2.3.3",
28
+ "@duckdb/node-api": "1.4.3-r.2",
29
+ "@duckdb/node-bindings-linux-x64": "1.4.3-r.2",
30
+ "@lezer-unofficial/printer": "^1.0.1",
31
+ "@lezer/common": "^1.5.0",
32
+ "@lezer/generator": "^1.8.0",
33
+ "@lezer/lr": "^1.4.6",
34
+ "better-sqlite3": "^12.5.0",
35
+ "commander": "^14.0.2",
36
+ "consola": "^3.4.2",
37
+ "dotenv": "^17.2.3",
38
+ "es-toolkit": "^1.43.0",
39
+ "handlebars": "^4.7.8",
40
+ "pg": "^8.16.3",
41
+ "pg-types": "^4.1.0",
42
+ "prettier": "^3.7.4",
43
+ "prettier-plugin-java": "^2.7.7",
44
+ "yaml": "^2.8.2",
45
+ "zod": "^4.3.5"
46
+ },
47
+ "devDependencies": {
48
+ "@types/better-sqlite3": "^7.6.13",
49
+ "@types/node": "^25.0.3",
50
+ "@types/pg": "^8.16.0",
51
+ "@vitest/ui": "^4.0.16",
52
+ "tsdown": "0.18.0",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.0.16"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc --noEmit && tsdown && cp -r src/templates dist",
59
+ "lezer-gen": "lezer-generator src/parser/sql.grammar -o src/parser/sql-parser.ts",
60
+ "test-grammar": "tsx src/test-grammar.ts",
61
+ "check": "biome check --write src/",
62
+ "check:errors": "pnpm biome check --write --diagnostic-level=error src/",
63
+ "test": "vitest",
64
+ "coverage": "vitest run --coverage",
65
+ "test:ui": "vitest --ui",
66
+ "test:run": "vitest run",
67
+ "sqg": "tsx src/sqg.ts"
68
+ }
69
+ }