@duckdbfan/drizzle-duckdb 0.0.6 → 1.3.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.
Files changed (55) hide show
  1. package/README.md +344 -62
  2. package/dist/bin/duckdb-introspect.d.ts +2 -0
  3. package/dist/client.d.ts +42 -0
  4. package/dist/columns.d.ts +142 -0
  5. package/dist/dialect.d.ts +27 -2
  6. package/dist/driver.d.ts +53 -37
  7. package/dist/duckdb-introspect.mjs +2890 -0
  8. package/dist/helpers.d.ts +1 -0
  9. package/dist/helpers.mjs +360 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.mjs +3071 -209
  12. package/dist/introspect.d.ts +74 -0
  13. package/dist/migrator.d.ts +3 -2
  14. package/dist/olap.d.ts +46 -0
  15. package/dist/operators.d.ts +8 -0
  16. package/dist/options.d.ts +7 -0
  17. package/dist/pool.d.ts +30 -0
  18. package/dist/select-builder.d.ts +31 -0
  19. package/dist/session.d.ts +33 -8
  20. package/dist/sql/ast-transformer.d.ts +33 -0
  21. package/dist/sql/result-mapper.d.ts +9 -0
  22. package/dist/sql/selection.d.ts +2 -0
  23. package/dist/sql/visitors/array-operators.d.ts +5 -0
  24. package/dist/sql/visitors/column-qualifier.d.ts +10 -0
  25. package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
  26. package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
  27. package/dist/utils.d.ts +2 -5
  28. package/dist/value-wrappers-core.d.ts +42 -0
  29. package/dist/value-wrappers.d.ts +8 -0
  30. package/package.json +53 -16
  31. package/src/bin/duckdb-introspect.ts +181 -0
  32. package/src/client.ts +528 -0
  33. package/src/columns.ts +510 -1
  34. package/src/dialect.ts +111 -15
  35. package/src/driver.ts +266 -180
  36. package/src/helpers.ts +18 -0
  37. package/src/index.ts +8 -1
  38. package/src/introspect.ts +935 -0
  39. package/src/migrator.ts +10 -5
  40. package/src/olap.ts +190 -0
  41. package/src/operators.ts +27 -0
  42. package/src/options.ts +25 -0
  43. package/src/pool.ts +274 -0
  44. package/src/select-builder.ts +110 -0
  45. package/src/session.ts +306 -66
  46. package/src/sql/ast-transformer.ts +170 -0
  47. package/src/sql/result-mapper.ts +303 -0
  48. package/src/sql/selection.ts +60 -0
  49. package/src/sql/visitors/array-operators.ts +214 -0
  50. package/src/sql/visitors/column-qualifier.ts +586 -0
  51. package/src/sql/visitors/generate-series-alias.ts +291 -0
  52. package/src/sql/visitors/union-with-hoister.ts +106 -0
  53. package/src/utils.ts +2 -216
  54. package/src/value-wrappers-core.ts +168 -0
  55. package/src/value-wrappers.ts +165 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * AST visitor to rewrite Postgres style generate_series aliases.
3
+ *
4
+ * Postgres lets you reference a generate_series alias as a column:
5
+ * FROM generate_series(...) AS gs
6
+ * SELECT gs::date
7
+ *
8
+ * DuckDB treats gs as a table alias, and the column is generate_series.
9
+ * This visitor rewrites unqualified column refs that match a
10
+ * generate_series alias to gs.generate_series.
11
+ */
12
+
13
+ import type {
14
+ AST,
15
+ Binary,
16
+ ColumnRefItem,
17
+ ExpressionValue,
18
+ From,
19
+ Join,
20
+ Select,
21
+ OrderBy,
22
+ Column,
23
+ } from 'node-sql-parser';
24
+
25
+ function getColumnName(col: ColumnRefItem): string | null {
26
+ if (typeof col.column === 'string') {
27
+ return col.column;
28
+ }
29
+ if (col.column && 'expr' in col.column && col.column.expr?.value) {
30
+ return String(col.column.expr.value);
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function isColumnRef(expr: ExpressionValue): expr is ColumnRefItem {
36
+ return (
37
+ typeof expr === 'object' &&
38
+ expr !== null &&
39
+ 'type' in expr &&
40
+ expr.type === 'column_ref'
41
+ );
42
+ }
43
+
44
+ function isBinaryExpr(
45
+ expr: ExpressionValue | Binary | null | undefined
46
+ ): expr is Binary {
47
+ return (
48
+ !!expr &&
49
+ typeof expr === 'object' &&
50
+ 'type' in expr &&
51
+ (expr as { type?: string }).type === 'binary_expr'
52
+ );
53
+ }
54
+
55
+ function getGenerateSeriesAliases(from: Select['from']): Set<string> {
56
+ const aliases = new Set<string>();
57
+ if (!from || !Array.isArray(from)) return aliases;
58
+
59
+ for (const f of from) {
60
+ if ('expr' in f && f.expr && typeof f.expr === 'object') {
61
+ const exprObj = f.expr as Record<string, unknown>;
62
+ if (exprObj.type === 'function' && 'name' in exprObj) {
63
+ const nameObj = exprObj.name as Record<string, unknown> | undefined;
64
+ const nameParts = nameObj?.name as
65
+ | Array<Record<string, unknown>>
66
+ | undefined;
67
+ const fnName = nameParts?.[0]?.value;
68
+ if (
69
+ typeof fnName === 'string' &&
70
+ fnName.toLowerCase() === 'generate_series'
71
+ ) {
72
+ const alias = typeof f.as === 'string' ? f.as : null;
73
+ if (alias && !alias.includes('(')) {
74
+ aliases.add(alias);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ return aliases;
82
+ }
83
+
84
+ function rewriteAliasColumnRef(col: ColumnRefItem, alias: string): void {
85
+ col.table = alias;
86
+ col.column = { expr: { type: 'default', value: 'generate_series' } };
87
+ }
88
+
89
+ function walkExpression(
90
+ expr: ExpressionValue | null | undefined,
91
+ aliases: Set<string>
92
+ ): boolean {
93
+ if (!expr || typeof expr !== 'object') return false;
94
+
95
+ let transformed = false;
96
+ const exprObj = expr as Record<string, unknown>;
97
+
98
+ if (isColumnRef(expr)) {
99
+ if (!('table' in expr) || !expr.table) {
100
+ const colName = getColumnName(expr);
101
+ if (colName && aliases.has(colName)) {
102
+ rewriteAliasColumnRef(expr, colName);
103
+ transformed = true;
104
+ }
105
+ }
106
+ return transformed;
107
+ }
108
+
109
+ if (isBinaryExpr(expr)) {
110
+ const binary = expr as Binary;
111
+ transformed =
112
+ walkExpression(binary.left as ExpressionValue, aliases) || transformed;
113
+ transformed =
114
+ walkExpression(binary.right as ExpressionValue, aliases) || transformed;
115
+ return transformed;
116
+ }
117
+
118
+ if (exprObj.type === 'unary_expr' && exprObj.expr) {
119
+ transformed =
120
+ walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
121
+ }
122
+
123
+ if (exprObj.type === 'cast' && exprObj.expr) {
124
+ transformed =
125
+ walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
126
+ }
127
+
128
+ if (exprObj.type === 'case') {
129
+ if (exprObj.expr) {
130
+ transformed =
131
+ walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
132
+ }
133
+ if (Array.isArray(exprObj.args)) {
134
+ for (const whenClause of exprObj.args as Array<Record<string, unknown>>) {
135
+ if (whenClause.cond) {
136
+ transformed =
137
+ walkExpression(whenClause.cond as ExpressionValue, aliases) ||
138
+ transformed;
139
+ }
140
+ if (whenClause.result) {
141
+ transformed =
142
+ walkExpression(whenClause.result as ExpressionValue, aliases) ||
143
+ transformed;
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ if ('args' in exprObj && exprObj.args) {
150
+ const args = exprObj.args as Record<string, unknown>;
151
+ if (Array.isArray(args.value)) {
152
+ for (const arg of args.value as ExpressionValue[]) {
153
+ transformed = walkExpression(arg, aliases) || transformed;
154
+ }
155
+ } else if (args.expr) {
156
+ transformed =
157
+ walkExpression(args.expr as ExpressionValue, aliases) || transformed;
158
+ }
159
+ }
160
+
161
+ if ('over' in exprObj && exprObj.over && typeof exprObj.over === 'object') {
162
+ const over = exprObj.over as Record<string, unknown>;
163
+ if (Array.isArray(over.partition)) {
164
+ for (const part of over.partition as ExpressionValue[]) {
165
+ transformed = walkExpression(part, aliases) || transformed;
166
+ }
167
+ }
168
+ if (Array.isArray(over.orderby)) {
169
+ for (const order of over.orderby as ExpressionValue[]) {
170
+ transformed = walkExpression(order, aliases) || transformed;
171
+ }
172
+ }
173
+ }
174
+
175
+ if ('ast' in exprObj && exprObj.ast) {
176
+ const subAst = exprObj.ast as Select;
177
+ if (subAst.type === 'select') {
178
+ transformed = walkSelect(subAst) || transformed;
179
+ }
180
+ }
181
+
182
+ if (exprObj.type === 'expr_list' && Array.isArray(exprObj.value)) {
183
+ for (const item of exprObj.value as ExpressionValue[]) {
184
+ transformed = walkExpression(item, aliases) || transformed;
185
+ }
186
+ }
187
+
188
+ return transformed;
189
+ }
190
+
191
+ function walkFrom(from: Select['from'], aliases: Set<string>): boolean {
192
+ if (!from || !Array.isArray(from)) return false;
193
+
194
+ let transformed = false;
195
+
196
+ for (const f of from) {
197
+ if ('join' in f) {
198
+ const join = f as Join;
199
+ transformed =
200
+ walkExpression(join.on as ExpressionValue, aliases) || transformed;
201
+ }
202
+ if ('expr' in f && f.expr && 'ast' in f.expr) {
203
+ transformed = walkSelect(f.expr.ast as Select) || transformed;
204
+ }
205
+ }
206
+
207
+ return transformed;
208
+ }
209
+
210
+ function walkSelect(select: Select): boolean {
211
+ let transformed = false;
212
+ const aliases = getGenerateSeriesAliases(select.from);
213
+
214
+ if (select.with) {
215
+ for (const cte of select.with) {
216
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
217
+ if (cteSelect && cteSelect.type === 'select') {
218
+ transformed = walkSelect(cteSelect as Select) || transformed;
219
+ }
220
+ }
221
+ }
222
+
223
+ transformed = walkFrom(select.from, aliases) || transformed;
224
+
225
+ transformed = walkExpression(select.where, aliases) || transformed;
226
+
227
+ if (select.having) {
228
+ if (Array.isArray(select.having)) {
229
+ for (const h of select.having) {
230
+ transformed =
231
+ walkExpression(h as ExpressionValue, aliases) || transformed;
232
+ }
233
+ } else {
234
+ transformed =
235
+ walkExpression(select.having as ExpressionValue, aliases) ||
236
+ transformed;
237
+ }
238
+ }
239
+
240
+ if (Array.isArray(select.columns)) {
241
+ for (const col of select.columns as Column[]) {
242
+ if ('expr' in col) {
243
+ transformed =
244
+ walkExpression(col.expr as ExpressionValue, aliases) || transformed;
245
+ }
246
+ }
247
+ }
248
+
249
+ if (Array.isArray(select.groupby)) {
250
+ for (const g of select.groupby as ExpressionValue[]) {
251
+ transformed = walkExpression(g, aliases) || transformed;
252
+ }
253
+ }
254
+
255
+ if (Array.isArray(select.orderby)) {
256
+ for (const order of select.orderby as OrderBy[]) {
257
+ if (order.expr) {
258
+ transformed =
259
+ walkExpression(order.expr as ExpressionValue, aliases) || transformed;
260
+ }
261
+ }
262
+ }
263
+
264
+ if (select._orderby) {
265
+ for (const order of select._orderby as OrderBy[]) {
266
+ if (order.expr) {
267
+ transformed =
268
+ walkExpression(order.expr as ExpressionValue, aliases) || transformed;
269
+ }
270
+ }
271
+ }
272
+
273
+ if (select._next) {
274
+ transformed = walkSelect(select._next) || transformed;
275
+ }
276
+
277
+ return transformed;
278
+ }
279
+
280
+ export function rewriteGenerateSeriesAliases(ast: AST | AST[]): boolean {
281
+ const statements = Array.isArray(ast) ? ast : [ast];
282
+ let transformed = false;
283
+
284
+ for (const stmt of statements) {
285
+ if (stmt.type === 'select') {
286
+ transformed = walkSelect(stmt as Select) || transformed;
287
+ }
288
+ }
289
+
290
+ return transformed;
291
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * AST visitor to hoist WITH clauses out of UNION and other set operations.
3
+ *
4
+ * Drizzle can emit SQL like:
5
+ * (with a as (...) select ...) union (with b as (...) select ...)
6
+ *
7
+ * DuckDB 1.4.x has an internal binder bug for this pattern.
8
+ * We merge per arm CTEs into a single top level WITH when names do not collide.
9
+ */
10
+
11
+ import type { AST, Select, From } from 'node-sql-parser';
12
+
13
+ function getCteName(cte: { name?: unknown }): string | null {
14
+ const nameObj = cte.name as Record<string, unknown> | undefined;
15
+ if (!nameObj) return null;
16
+ const value = nameObj.value;
17
+ if (typeof value === 'string') return value;
18
+ return null;
19
+ }
20
+
21
+ function hoistWithInSelect(select: Select): boolean {
22
+ if (!select.set_op || !select._next) return false;
23
+
24
+ const arms: Select[] = [];
25
+ let current: Select | null = select;
26
+ while (current && current.type === 'select') {
27
+ arms.push(current);
28
+ current = current._next as Select | null;
29
+ }
30
+
31
+ const mergedWith: NonNullable<Select['with']> = [];
32
+ const seen = new Set<string>();
33
+ let hasWithBeyondFirst = false;
34
+
35
+ for (const arm of arms) {
36
+ if (arm.with && arm.with.length > 0) {
37
+ if (arm !== arms[0]) {
38
+ hasWithBeyondFirst = true;
39
+ }
40
+ for (const cte of arm.with) {
41
+ const cteName = getCteName(cte);
42
+ if (!cteName) return false;
43
+ if (seen.has(cteName)) {
44
+ return false;
45
+ }
46
+ seen.add(cteName);
47
+ mergedWith.push(cte);
48
+ }
49
+ }
50
+ }
51
+
52
+ if (!hasWithBeyondFirst) return false;
53
+
54
+ arms[0].with = mergedWith;
55
+ if ('parentheses_symbol' in arms[0]) {
56
+ (arms[0] as Select & { parentheses_symbol?: boolean }).parentheses_symbol =
57
+ false;
58
+ }
59
+ for (let i = 1; i < arms.length; i++) {
60
+ arms[i].with = null;
61
+ }
62
+
63
+ return true;
64
+ }
65
+
66
+ function walkSelect(select: Select): boolean {
67
+ let transformed = false;
68
+
69
+ if (select.with) {
70
+ for (const cte of select.with) {
71
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
72
+ if (cteSelect && cteSelect.type === 'select') {
73
+ transformed = walkSelect(cteSelect as Select) || transformed;
74
+ }
75
+ }
76
+ }
77
+
78
+ if (Array.isArray(select.from)) {
79
+ for (const from of select.from as From[]) {
80
+ if ('expr' in from && from.expr && 'ast' in from.expr) {
81
+ transformed = walkSelect(from.expr.ast as Select) || transformed;
82
+ }
83
+ }
84
+ }
85
+
86
+ transformed = hoistWithInSelect(select) || transformed;
87
+
88
+ if (select._next) {
89
+ transformed = walkSelect(select._next) || transformed;
90
+ }
91
+
92
+ return transformed;
93
+ }
94
+
95
+ export function hoistUnionWith(ast: AST | AST[]): boolean {
96
+ const statements = Array.isArray(ast) ? ast : [ast];
97
+ let transformed = false;
98
+
99
+ for (const stmt of statements) {
100
+ if (stmt.type === 'select') {
101
+ transformed = walkSelect(stmt as Select) || transformed;
102
+ }
103
+ }
104
+
105
+ return transformed;
106
+ }
package/src/utils.ts CHANGED
@@ -1,216 +1,2 @@
1
- /* Marked as internal in the original source, so we need to copy it here */
2
- import {
3
- type SelectedFieldsOrdered,
4
- type AnyColumn,
5
- type DriverValueDecoder,
6
- is,
7
- Column,
8
- SQL,
9
- getTableName,
10
- sql,
11
- } from 'drizzle-orm';
12
- import { PgColumn, type SelectedFields } from 'drizzle-orm/pg-core';
13
-
14
- // Need to get around "decoder" property being marked as internal
15
- type SQLInternal<T = unknown> = SQL<T> & {
16
- decoder: DriverValueDecoder<T, any>;
17
- };
18
-
19
- export function mapResultRow<TResult>(
20
- columns: SelectedFieldsOrdered<AnyColumn>,
21
- row: unknown[],
22
- joinsNotNullableMap: Record<string, boolean> | undefined
23
- ): TResult {
24
- // Key -> nested object key, value -> table name if all fields in the nested object are from the same table, false otherwise
25
- const nullifyMap: Record<string, string | false> = {};
26
-
27
- const result = columns.reduce<Record<string, any>>(
28
- (result, { path, field }, columnIndex) => {
29
- let decoder: DriverValueDecoder<unknown, unknown>;
30
- if (is(field, Column)) {
31
- decoder = field;
32
- } else if (is(field, SQL)) {
33
- decoder = (field as SQLInternal).decoder;
34
- } else {
35
- decoder = (field.sql as SQLInternal).decoder;
36
- }
37
- let node = result;
38
- for (const [pathChunkIndex, pathChunk] of path.entries()) {
39
- if (pathChunkIndex < path.length - 1) {
40
- if (!(pathChunk in node)) {
41
- node[pathChunk] = {};
42
- }
43
- node = node[pathChunk];
44
- continue;
45
- }
46
-
47
- const rawValue = row[columnIndex]!;
48
-
49
- const value = (node[pathChunk] =
50
- rawValue === null ? null : decoder.mapFromDriverValue(rawValue));
51
-
52
- if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
53
- const objectName = path[0]!;
54
- if (!(objectName in nullifyMap)) {
55
- nullifyMap[objectName] =
56
- value === null ? getTableName(field.table) : false;
57
- } else if (
58
- typeof nullifyMap[objectName] === 'string' &&
59
- nullifyMap[objectName] !== getTableName(field.table)
60
- ) {
61
- nullifyMap[objectName] = false;
62
- }
63
- continue;
64
- }
65
-
66
- // may need to add a condition for non-Aliased SQL
67
- if (
68
- joinsNotNullableMap &&
69
- is(field, SQL.Aliased) &&
70
- path.length === 2
71
- ) {
72
- const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
73
- const tableName = col?.table && getTableName(col?.table);
74
-
75
- if (!tableName) {
76
- continue;
77
- }
78
-
79
- const objectName = path[0]!;
80
-
81
- if (!(objectName in nullifyMap)) {
82
- nullifyMap[objectName] = value === null ? tableName : false;
83
- continue;
84
- }
85
-
86
- if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
87
- nullifyMap[objectName] = false;
88
- }
89
- continue;
90
- }
91
- }
92
- return result;
93
- },
94
- {}
95
- );
96
-
97
- // Nullify all nested objects from nullifyMap that are nullable
98
- if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
99
- for (const [objectName, tableName] of Object.entries(nullifyMap)) {
100
- if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
101
- result[objectName] = null;
102
- }
103
- }
104
- }
105
-
106
- return result as TResult;
107
- }
108
-
109
- export function aliasFields(
110
- fields: SelectedFields,
111
- fullJoin = false
112
- ): SelectedFields {
113
- return Object.fromEntries(
114
- Object.entries(fields)
115
- .filter(([key]) => key !== 'enableRLS')
116
- .map(([key, value]) => {
117
- if (fullJoin && is(value, Column)) {
118
- return [
119
- key,
120
- sql`${value}`.as(`${getTableName(value.table)}.${value.name}`),
121
- ];
122
- }
123
-
124
- if (fullJoin && is(value, SQL)) {
125
- const col = value
126
- .getSQL()
127
- .queryChunks.find((chunk) => is(chunk, Column));
128
-
129
- const tableName = col?.table && getTableName(col?.table);
130
-
131
- return [key, value.as(tableName ? `${tableName}.${key}` : key)];
132
- }
133
-
134
- if (is(value, SQL) || is(value, Column)) {
135
- return [key, (is(value, SQL) ? value : sql`${value}`).as(key)];
136
- }
137
-
138
- if (is(value, SQL.Aliased)) {
139
- return [key, value];
140
- }
141
-
142
- // todo: should probably make this recursive?
143
- if (typeof value === 'object') {
144
- const parentKey = key;
145
-
146
- return [
147
- key,
148
- Object.fromEntries(
149
- Object.entries(value)
150
- .filter(([childKey]) => childKey !== 'enableRLS')
151
- .map(([childKey, childValue]) => [
152
- childKey,
153
- (is(childValue, SQL) ? childValue : sql`${childValue}`).as(
154
- `${parentKey}.${childKey}`
155
- ),
156
- ])
157
- ),
158
- ];
159
- }
160
-
161
- return [key, value];
162
- })
163
- );
164
- }
165
-
166
- // DuckDB names returned variables differently than Postgres
167
- // so we need to remap them to match the Postgres names
168
-
169
- const selectionRegex = /select\s+(.+)\s+from/i;
170
- // const tableIdPropSelectionRegex = /("(.+)"\."(.+)")(\s+as\s+'?(.+?)'?\.'?(.+?)'?)?/i;
171
- const tableIdPropSelectionRegex = new RegExp(
172
- [
173
- `("(.+)"\\."(.+)")`, // table identifier + property
174
- `(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`, // optional AS clause
175
- ].join(''),
176
- 'i'
177
- );
178
- const noTableIdPropSelectionRegex = /"(.+)"(\s+as\s+'?\1'?)?/i;
179
-
180
- const tablePropRegex = /"(.+)"\."(.+)"/i;
181
- const asClauseRegex = /as\s+(.+)$/i;
182
- const aliasRegex = /as\s+'?(.+)'?\.'?(.+)'?$/i;
183
-
184
- /* Takes an SQL query as a string, and adds or updates "AS" clauses
185
- * to the form: `AS 'table_name.column_name'`
186
- * instead of : `AS "table_name"."column_name"`
187
- */
188
- export function queryAdapter(query: string): string {
189
- // Things to consider:
190
- // - need to handle nested selects
191
- // - what about full joins?
192
- const selection = selectionRegex.exec(query);
193
-
194
- if (selection?.length !== 2) {
195
- return query;
196
- }
197
-
198
- const fields = selection[1].split(',').map((field) => {
199
- const trimmedField = field.trim();
200
-
201
- // - different scenarios:
202
- // - no table identifier + no AS clause -> ignore
203
- // - no table identifier + AS clause -> ensure AS clause format
204
- // - table identifier + no AS clause -> add AS clause
205
- // - table identifier + AS clause -> ensure AS clause format
206
- const propSelection = tableIdPropSelectionRegex
207
- .exec(trimmedField)
208
- ?.filter(Boolean);
209
-
210
- if (!propSelection) {
211
- return trimmedField;
212
- }
213
- });
214
-
215
- return query.replace(selection[1], fields.join(', '));
216
- }
1
+ export { aliasFields } from './sql/selection.ts';
2
+ export { mapResultRow } from './sql/result-mapper.ts';