@leonardovida-md/drizzle-neo-duckdb 1.0.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/LICENSE +201 -0
- package/README.md +354 -0
- package/dist/bin/duckdb-introspect.d.ts +2 -0
- package/dist/client.d.ts +10 -0
- package/dist/columns.d.ts +129 -0
- package/dist/dialect.d.ts +11 -0
- package/dist/driver.d.ts +37 -0
- package/dist/duckdb-introspect.mjs +1364 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +1564 -0
- package/dist/introspect.d.ts +53 -0
- package/dist/migrator.d.ts +4 -0
- package/dist/select-builder.d.ts +31 -0
- package/dist/session.d.ts +62 -0
- package/dist/sql/query-rewriters.d.ts +2 -0
- package/dist/sql/result-mapper.d.ts +2 -0
- package/dist/sql/selection.d.ts +2 -0
- package/dist/utils.d.ts +3 -0
- package/package.json +73 -0
- package/src/bin/duckdb-introspect.ts +117 -0
- package/src/client.ts +110 -0
- package/src/columns.ts +429 -0
- package/src/dialect.ts +136 -0
- package/src/driver.ts +131 -0
- package/src/index.ts +5 -0
- package/src/introspect.ts +853 -0
- package/src/migrator.ts +25 -0
- package/src/select-builder.ts +114 -0
- package/src/session.ts +274 -0
- package/src/sql/query-rewriters.ts +147 -0
- package/src/sql/result-mapper.ts +303 -0
- package/src/sql/selection.ts +67 -0
- package/src/utils.ts +3 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { RowData } from './client.ts';
|
|
2
|
+
import type { DuckDBDatabase } from './driver.ts';
|
|
3
|
+
export interface IntrospectOptions {
|
|
4
|
+
schemas?: string[];
|
|
5
|
+
includeViews?: boolean;
|
|
6
|
+
useCustomTimeTypes?: boolean;
|
|
7
|
+
mapJsonAsDuckDbJson?: boolean;
|
|
8
|
+
importBasePath?: string;
|
|
9
|
+
}
|
|
10
|
+
interface DuckDbIndexRow extends RowData {
|
|
11
|
+
schema_name: string;
|
|
12
|
+
table_name: string;
|
|
13
|
+
index_name: string;
|
|
14
|
+
is_unique: boolean | null;
|
|
15
|
+
expressions: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface IntrospectedColumn {
|
|
18
|
+
name: string;
|
|
19
|
+
dataType: string;
|
|
20
|
+
columnDefault: string | null;
|
|
21
|
+
nullable: boolean;
|
|
22
|
+
characterLength: number | null;
|
|
23
|
+
numericPrecision: number | null;
|
|
24
|
+
numericScale: number | null;
|
|
25
|
+
}
|
|
26
|
+
export interface IntrospectedConstraint {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
columns: string[];
|
|
30
|
+
referencedTable?: {
|
|
31
|
+
name: string;
|
|
32
|
+
schema: string;
|
|
33
|
+
columns: string[];
|
|
34
|
+
};
|
|
35
|
+
rawExpression?: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface IntrospectedTable {
|
|
38
|
+
schema: string;
|
|
39
|
+
name: string;
|
|
40
|
+
kind: 'table' | 'view';
|
|
41
|
+
columns: IntrospectedColumn[];
|
|
42
|
+
constraints: IntrospectedConstraint[];
|
|
43
|
+
indexes: DuckDbIndexRow[];
|
|
44
|
+
}
|
|
45
|
+
export interface IntrospectResult {
|
|
46
|
+
files: {
|
|
47
|
+
schemaTs: string;
|
|
48
|
+
metaJson: IntrospectedTable[];
|
|
49
|
+
relationsTs?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function introspect(db: DuckDBDatabase, opts?: IntrospectOptions): Promise<IntrospectResult>;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MigrationConfig } from 'drizzle-orm/migrator';
|
|
2
|
+
import type { DuckDBDatabase } from './driver.ts';
|
|
3
|
+
export type DuckDbMigrationConfig = MigrationConfig | string;
|
|
4
|
+
export declare function migrate<TSchema extends Record<string, unknown>>(db: DuckDBDatabase<TSchema>, config: DuckDbMigrationConfig): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PgSelectBuilder, type CreatePgSelectFromBuilderMode, type SelectedFields, type TableLikeHasEmptySelection } from 'drizzle-orm/pg-core/query-builders';
|
|
2
|
+
import { PgColumn, PgTable, type PgSession } from 'drizzle-orm/pg-core';
|
|
3
|
+
import { Subquery, ViewBaseConfig, type SQLWrapper } from 'drizzle-orm';
|
|
4
|
+
import { PgViewBase } from 'drizzle-orm/pg-core/view-base';
|
|
5
|
+
import type { GetSelectTableName, GetSelectTableSelection } from 'drizzle-orm/query-builders/select.types';
|
|
6
|
+
import { SQL, type ColumnsSelection } from 'drizzle-orm/sql/sql';
|
|
7
|
+
import type { DuckDBDialect } from './dialect.ts';
|
|
8
|
+
import { type DrizzleTypeError } from 'drizzle-orm/utils';
|
|
9
|
+
interface PgViewBaseInternal<TName extends string = string, TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection> extends PgViewBase<TName, TExisting, TSelectedFields> {
|
|
10
|
+
[ViewBaseConfig]?: {
|
|
11
|
+
selectedFields: SelectedFields;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare class DuckDBSelectBuilder<TSelection extends SelectedFields | undefined, TBuilderMode extends 'db' | 'qb' = 'db'> extends PgSelectBuilder<TSelection, TBuilderMode> {
|
|
15
|
+
private _fields;
|
|
16
|
+
private _session;
|
|
17
|
+
private _dialect;
|
|
18
|
+
private _withList;
|
|
19
|
+
private _distinct;
|
|
20
|
+
constructor(config: {
|
|
21
|
+
fields: TSelection;
|
|
22
|
+
session: PgSession | undefined;
|
|
23
|
+
dialect: DuckDBDialect;
|
|
24
|
+
withList?: Subquery[];
|
|
25
|
+
distinct?: boolean | {
|
|
26
|
+
on: (PgColumn | SQLWrapper)[];
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
from<TFrom extends PgTable | Subquery | PgViewBaseInternal | SQL>(source: TableLikeHasEmptySelection<TFrom> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TFrom): CreatePgSelectFromBuilderMode<TBuilderMode, GetSelectTableName<TFrom>, TSelection extends undefined ? GetSelectTableSelection<TFrom> : TSelection, TSelection extends undefined ? 'single' : 'partial'>;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { entityKind } from 'drizzle-orm/entity';
|
|
2
|
+
import type { Logger } from 'drizzle-orm/logger';
|
|
3
|
+
import { PgTransaction } from 'drizzle-orm/pg-core';
|
|
4
|
+
import type { SelectedFieldsOrdered } from 'drizzle-orm/pg-core/query-builders/select.types';
|
|
5
|
+
import type { PgTransactionConfig, PreparedQueryConfig, PgQueryResultHKT } from 'drizzle-orm/pg-core/session';
|
|
6
|
+
import { PgPreparedQuery, PgSession } from 'drizzle-orm/pg-core/session';
|
|
7
|
+
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
|
|
8
|
+
import { type Query, SQL } from 'drizzle-orm/sql/sql';
|
|
9
|
+
import type { Assume } from 'drizzle-orm/utils';
|
|
10
|
+
import type { DuckDBDialect } from './dialect.ts';
|
|
11
|
+
import type { DuckDBClientLike, RowData } from './client.ts';
|
|
12
|
+
export type { DuckDBClientLike, RowData } from './client.ts';
|
|
13
|
+
export declare class DuckDBPreparedQuery<T extends PreparedQueryConfig> extends PgPreparedQuery<T> {
|
|
14
|
+
private client;
|
|
15
|
+
private dialect;
|
|
16
|
+
private queryString;
|
|
17
|
+
private params;
|
|
18
|
+
private logger;
|
|
19
|
+
private fields;
|
|
20
|
+
private _isResponseInArrayMode;
|
|
21
|
+
private customResultMapper;
|
|
22
|
+
private rewriteArrays;
|
|
23
|
+
private rejectStringArrayLiterals;
|
|
24
|
+
private warnOnStringArrayLiteral?;
|
|
25
|
+
static readonly [entityKind]: string;
|
|
26
|
+
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined, rewriteArrays: boolean, rejectStringArrayLiterals: boolean, warnOnStringArrayLiteral?: ((sql: string) => void) | undefined);
|
|
27
|
+
execute(placeholderValues?: Record<string, unknown> | undefined): Promise<T['execute']>;
|
|
28
|
+
all(placeholderValues?: Record<string, unknown> | undefined): Promise<T['all']>;
|
|
29
|
+
isResponseInArrayMode(): boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface DuckDBSessionOptions {
|
|
32
|
+
logger?: Logger;
|
|
33
|
+
rewriteArrays?: boolean;
|
|
34
|
+
rejectStringArrayLiterals?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare class DuckDBSession<TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = Record<string, never>> extends PgSession<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
37
|
+
private client;
|
|
38
|
+
private schema;
|
|
39
|
+
private options;
|
|
40
|
+
static readonly [entityKind]: string;
|
|
41
|
+
protected dialect: DuckDBDialect;
|
|
42
|
+
private logger;
|
|
43
|
+
private rewriteArrays;
|
|
44
|
+
private rejectStringArrayLiterals;
|
|
45
|
+
private hasWarnedArrayLiteral;
|
|
46
|
+
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, schema: RelationalSchemaConfig<TSchema> | undefined, options?: DuckDBSessionOptions);
|
|
47
|
+
prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(query: Query, fields: SelectedFieldsOrdered | undefined, name: string | undefined, isResponseInArrayMode: boolean, customResultMapper?: (rows: unknown[][]) => T['execute']): PgPreparedQuery<T>;
|
|
48
|
+
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
49
|
+
private warnOnStringArrayLiteral;
|
|
50
|
+
}
|
|
51
|
+
export declare class DuckDBTransaction<TFullSchema extends Record<string, unknown>, TSchema extends TablesRelationalConfig> extends PgTransaction<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
52
|
+
static readonly [entityKind]: string;
|
|
53
|
+
rollback(): never;
|
|
54
|
+
getTransactionConfigSQL(config: PgTransactionConfig): SQL;
|
|
55
|
+
setTransaction(config: PgTransactionConfig): Promise<void>;
|
|
56
|
+
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
57
|
+
}
|
|
58
|
+
export type GenericRowData<T extends RowData = RowData> = T;
|
|
59
|
+
export type GenericTableData<T = RowData> = T[];
|
|
60
|
+
export interface DuckDBQueryResultHKT extends PgQueryResultHKT {
|
|
61
|
+
type: GenericTableData<Assume<this['row'], RowData>>;
|
|
62
|
+
}
|
package/dist/utils.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leonardovida-md/drizzle-neo-duckdb",
|
|
3
|
+
"module": "./dist/index.mjs",
|
|
4
|
+
"main": "./dist/index.mjs",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun build --target=node ./src/bin/duckdb-introspect.ts --outfile=./dist/duckdb-introspect.mjs --packages=external && bun run build:declarations",
|
|
11
|
+
"build:declarations": "tsc --emitDeclarationOnly --project tsconfig.types.json",
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"t": "vitest --watch --ui"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"duckdb-introspect": "dist/duckdb-introspect.mjs"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@duckdb/node-api": "1.4.2-r.1",
|
|
20
|
+
"drizzle-orm": "^0.40.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependenciesMeta": {
|
|
23
|
+
"@duckdb/node-api": {
|
|
24
|
+
"optional": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@duckdb/node-api": "1.4.2-r.1",
|
|
29
|
+
"@types/bun": "^1.2.5",
|
|
30
|
+
"@types/uuid": "^10.0.0",
|
|
31
|
+
"@vitest/ui": "^1.6.0",
|
|
32
|
+
"drizzle-orm": "0.40.0",
|
|
33
|
+
"prettier": "^3.5.3",
|
|
34
|
+
"typescript": "^5.8.2",
|
|
35
|
+
"uuid": "^10.0.0",
|
|
36
|
+
"vitest": "^1.6.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/leonardovida-md/drizzle-neo-duckdb.git"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"import": "./dist/index.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"sideEffects": false,
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"registry": "https://registry.npmjs.org"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.17"
|
|
56
|
+
},
|
|
57
|
+
"packageManager": "bun@1.2.2",
|
|
58
|
+
"keywords": [
|
|
59
|
+
"drizzle",
|
|
60
|
+
"duckdb"
|
|
61
|
+
],
|
|
62
|
+
"author": "M L",
|
|
63
|
+
"license": "Apache-2.0",
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/leonardovida-md/drizzle-neo-duckdb/issues"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/leonardovida-md/drizzle-neo-duckdb#readme",
|
|
68
|
+
"files": [
|
|
69
|
+
"src/**/*.ts",
|
|
70
|
+
"dist/*.mjs",
|
|
71
|
+
"dist/**/*.d.ts"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { DuckDBInstance } from '@duckdb/node-api';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { drizzle } from '../index.ts';
|
|
7
|
+
import { introspect } from '../introspect.ts';
|
|
8
|
+
|
|
9
|
+
interface CliOptions {
|
|
10
|
+
url?: string;
|
|
11
|
+
schemas?: string[];
|
|
12
|
+
outFile: string;
|
|
13
|
+
includeViews: boolean;
|
|
14
|
+
useCustomTimeTypes: boolean;
|
|
15
|
+
importBasePath?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv: string[]): CliOptions {
|
|
19
|
+
const options: CliOptions = {
|
|
20
|
+
outFile: path.resolve(process.cwd(), 'drizzle/schema.ts'),
|
|
21
|
+
includeViews: false,
|
|
22
|
+
useCustomTimeTypes: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
26
|
+
const arg = argv[i]!;
|
|
27
|
+
switch (arg) {
|
|
28
|
+
case '--url':
|
|
29
|
+
options.url = argv[++i];
|
|
30
|
+
break;
|
|
31
|
+
case '--schema':
|
|
32
|
+
case '--schemas':
|
|
33
|
+
options.schemas = argv[++i]?.split(',').map((s) => s.trim()).filter(Boolean);
|
|
34
|
+
break;
|
|
35
|
+
case '--out':
|
|
36
|
+
case '--outFile':
|
|
37
|
+
options.outFile = path.resolve(process.cwd(), argv[++i] ?? 'drizzle/schema.ts');
|
|
38
|
+
break;
|
|
39
|
+
case '--include-views':
|
|
40
|
+
case '--includeViews':
|
|
41
|
+
options.includeViews = true;
|
|
42
|
+
break;
|
|
43
|
+
case '--use-pg-time':
|
|
44
|
+
options.useCustomTimeTypes = false;
|
|
45
|
+
break;
|
|
46
|
+
case '--import-base':
|
|
47
|
+
options.importBasePath = argv[++i];
|
|
48
|
+
break;
|
|
49
|
+
case '--help':
|
|
50
|
+
case '-h':
|
|
51
|
+
printHelp();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
default:
|
|
54
|
+
if (arg.startsWith('-')) {
|
|
55
|
+
console.warn(`Unknown option ${arg}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function printHelp(): void {
|
|
64
|
+
console.log(`duckdb-introspect
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
bun x duckdb-introspect --url <duckdb path|md:> [--schema my_schema] [--out ./drizzle/schema.ts]
|
|
68
|
+
|
|
69
|
+
Options:
|
|
70
|
+
--url DuckDB database path (e.g. :memory:, ./local.duckdb, md:)
|
|
71
|
+
--schema Comma separated schema list (defaults to all non-system schemas)
|
|
72
|
+
--out Output file (default: ./drizzle/schema.ts)
|
|
73
|
+
--include-views Include views in the generated schema
|
|
74
|
+
--use-pg-time Use pg-core timestamp/date/time instead of DuckDB custom helpers
|
|
75
|
+
--import-base Override import path for duckdb helpers (default: package name)
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const options = parseArgs(process.argv.slice(2));
|
|
81
|
+
if (!options.url) {
|
|
82
|
+
printHelp();
|
|
83
|
+
throw new Error('Missing required --url');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const instanceOptions =
|
|
87
|
+
options.url.startsWith('md:') && process.env.MOTHERDUCK_TOKEN
|
|
88
|
+
? { motherduck_token: process.env.MOTHERDUCK_TOKEN }
|
|
89
|
+
: undefined;
|
|
90
|
+
|
|
91
|
+
const instance = await DuckDBInstance.create(options.url, instanceOptions);
|
|
92
|
+
const connection = await instance.connect();
|
|
93
|
+
const db = drizzle(connection);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await introspect(db, {
|
|
97
|
+
schemas: options.schemas,
|
|
98
|
+
includeViews: options.includeViews,
|
|
99
|
+
useCustomTimeTypes: options.useCustomTimeTypes,
|
|
100
|
+
importBasePath: options.importBasePath,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await mkdir(path.dirname(options.outFile), { recursive: true });
|
|
104
|
+
await writeFile(options.outFile, result.files.schemaTs, 'utf8');
|
|
105
|
+
|
|
106
|
+
console.log(`Wrote schema to ${options.outFile}`);
|
|
107
|
+
} finally {
|
|
108
|
+
if ('closeSync' in connection && typeof connection.closeSync === 'function') {
|
|
109
|
+
connection.closeSync();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main().catch((err) => {
|
|
115
|
+
console.error(err instanceof Error ? err.message : err);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
});
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listValue,
|
|
3
|
+
type DuckDBConnection,
|
|
4
|
+
type DuckDBValue,
|
|
5
|
+
} from '@duckdb/node-api';
|
|
6
|
+
|
|
7
|
+
export type DuckDBClientLike = DuckDBConnection;
|
|
8
|
+
export type RowData = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export interface PrepareParamsOptions {
|
|
11
|
+
rejectStringArrayLiterals?: boolean;
|
|
12
|
+
warnOnStringArrayLiteral?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isPgArrayLiteral(value: string): boolean {
|
|
16
|
+
return value.startsWith('{') && value.endsWith('}');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parsePgArrayLiteral(value: string): unknown {
|
|
20
|
+
const json = value.replace(/{/g, '[').replace(/}/g, ']');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(json);
|
|
24
|
+
} catch {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let warnedArrayLiteral = false;
|
|
30
|
+
|
|
31
|
+
export function prepareParams(
|
|
32
|
+
params: unknown[],
|
|
33
|
+
options: PrepareParamsOptions = {}
|
|
34
|
+
): unknown[] {
|
|
35
|
+
return params.map((param) => {
|
|
36
|
+
if (typeof param === 'string' && isPgArrayLiteral(param)) {
|
|
37
|
+
if (options.rejectStringArrayLiterals) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!warnedArrayLiteral && options.warnOnStringArrayLiteral) {
|
|
44
|
+
warnedArrayLiteral = true;
|
|
45
|
+
options.warnOnStringArrayLiteral();
|
|
46
|
+
}
|
|
47
|
+
return parsePgArrayLiteral(param);
|
|
48
|
+
}
|
|
49
|
+
return param;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toNodeApiValue(value: unknown): DuckDBValue {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return listValue(value.map((inner) => toNodeApiValue(inner)));
|
|
56
|
+
}
|
|
57
|
+
return value as DuckDBValue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function closeClientConnection(
|
|
61
|
+
connection: DuckDBConnection
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
if ('close' in connection && typeof connection.close === 'function') {
|
|
64
|
+
await connection.close();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
'closeSync' in connection &&
|
|
70
|
+
typeof connection.closeSync === 'function'
|
|
71
|
+
) {
|
|
72
|
+
connection.closeSync();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
'disconnectSync' in connection &&
|
|
78
|
+
typeof connection.disconnectSync === 'function'
|
|
79
|
+
) {
|
|
80
|
+
connection.disconnectSync();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function executeOnClient(
|
|
85
|
+
client: DuckDBClientLike,
|
|
86
|
+
query: string,
|
|
87
|
+
params: unknown[]
|
|
88
|
+
): Promise<RowData[]> {
|
|
89
|
+
const values =
|
|
90
|
+
params.length > 0
|
|
91
|
+
? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
|
|
92
|
+
: undefined;
|
|
93
|
+
const result = await client.run(query, values);
|
|
94
|
+
const rows = await result.getRowsJS();
|
|
95
|
+
const columns = result.columnNames();
|
|
96
|
+
const seen: Record<string, number> = {};
|
|
97
|
+
const uniqueColumns = columns.map((col) => {
|
|
98
|
+
const count = seen[col] ?? 0;
|
|
99
|
+
seen[col] = count + 1;
|
|
100
|
+
return count === 0 ? col : `${col}_${count}`;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return (rows ?? []).map((vals) => {
|
|
104
|
+
const obj: Record<string, unknown> = {};
|
|
105
|
+
uniqueColumns.forEach((col, idx) => {
|
|
106
|
+
obj[col] = vals[idx];
|
|
107
|
+
});
|
|
108
|
+
return obj;
|
|
109
|
+
}) as RowData[];
|
|
110
|
+
}
|