@visactor/vquery 0.1.47 → 0.1.49
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/dataset/dataset.d.ts +12 -2
- package/dist/dataset/index.d.ts +1 -0
- package/dist/index.cjs +168 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.js +166 -13
- package/dist/sql-builder/builders/groupBy.d.ts +2 -0
- package/dist/sql-builder/builders/index.d.ts +3 -0
- package/dist/sql-builder/builders/limit.d.ts +2 -0
- package/dist/sql-builder/builders/where.d.ts +3 -0
- package/dist/sql-builder/compile/index.d.ts +1 -0
- package/dist/sql-builder/compile/inlineParameters.d.ts +1 -0
- package/dist/sql-builder/dialect/index.d.ts +1 -0
- package/dist/sql-builder/dialect/postgresDialect.d.ts +11 -0
- package/dist/sql-builder/dslToSQL.d.ts +2 -0
- package/dist/sql-builder/index.d.ts +1 -0
- package/dist/sql-builder/utils.d.ts +8 -0
- package/dist/types/QueryDSL/GroupBy.d.ts +1 -0
- package/dist/types/QueryDSL/OrderBy.d.ts +4 -0
- package/dist/types/QueryDSL/QueryDSL.d.ts +11 -0
- package/dist/types/QueryDSL/Select.d.ts +7 -0
- package/dist/types/QueryDSL/Where.d.ts +24 -0
- package/dist/types/QueryDSL/demo.d.ts +1 -0
- package/dist/types/QueryDSL/index.d.ts +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/vquery.d.ts +7 -9
- package/package.json +7 -4
- /package/dist/{dataSourceBuilder → data-source-builder}/dataSourceBuilder.d.ts +0 -0
- /package/dist/{dataSourceBuilder → data-source-builder}/index.d.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DatasetColumn } from '../types';
|
|
1
|
+
import { DatasetColumn, QueryDSL } from '../types';
|
|
2
2
|
import { DuckDB } from '../db/duckDb';
|
|
3
3
|
import { IndexedDB } from '../db/indexedDb';
|
|
4
4
|
export declare class Dataset {
|
|
@@ -6,7 +6,7 @@ export declare class Dataset {
|
|
|
6
6
|
private indexedDB;
|
|
7
7
|
private _datasetId;
|
|
8
8
|
constructor(duckDB: DuckDB, indexedDB: IndexedDB, datasetId: string);
|
|
9
|
-
init(
|
|
9
|
+
init(temporaryColumns?: DatasetColumn[]): Promise<void>;
|
|
10
10
|
queryBySQL(sql: string): Promise<{
|
|
11
11
|
performance: {
|
|
12
12
|
startAt: string;
|
|
@@ -16,6 +16,16 @@ export declare class Dataset {
|
|
|
16
16
|
dataset: any[];
|
|
17
17
|
table: any;
|
|
18
18
|
}>;
|
|
19
|
+
convertDSLToSQL<T extends Record<string, number | string>>(queryDSL: QueryDSL<T>): string;
|
|
20
|
+
query<T extends Record<string, number | string>>(queryDSL: QueryDSL<T>): Promise<{
|
|
21
|
+
performance: {
|
|
22
|
+
startAt: string;
|
|
23
|
+
endAt: string;
|
|
24
|
+
duration: number;
|
|
25
|
+
};
|
|
26
|
+
dataset: any[];
|
|
27
|
+
table: any;
|
|
28
|
+
}>;
|
|
19
29
|
disconnect(): Promise<void>;
|
|
20
30
|
get datasetId(): string;
|
|
21
31
|
}
|
package/dist/dataset/index.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -29,10 +29,153 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
29
29
|
__webpack_require__.d(__webpack_exports__, {
|
|
30
30
|
DataSourceBuilder: ()=>DataSourceBuilder,
|
|
31
31
|
isHttpUrl: ()=>isHttpUrl,
|
|
32
|
+
convertDSLToSQL: ()=>convertDSLToSQL,
|
|
32
33
|
isBase64Url: ()=>isBase64Url,
|
|
33
34
|
isUrl: ()=>isUrl,
|
|
34
35
|
VQuery: ()=>VQuery
|
|
35
36
|
});
|
|
37
|
+
const external_kysely_namespaceObject = require("kysely");
|
|
38
|
+
class PostgresDialect {
|
|
39
|
+
createDriver() {
|
|
40
|
+
return new external_kysely_namespaceObject.DummyDriver();
|
|
41
|
+
}
|
|
42
|
+
createQueryCompiler() {
|
|
43
|
+
return new external_kysely_namespaceObject.PostgresQueryCompiler();
|
|
44
|
+
}
|
|
45
|
+
createAdapter() {
|
|
46
|
+
return new external_kysely_namespaceObject.PostgresAdapter();
|
|
47
|
+
}
|
|
48
|
+
createIntrospector(db) {
|
|
49
|
+
class NullIntrospector {
|
|
50
|
+
async getSchemas() {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
async getTables(options) {
|
|
54
|
+
options?.withInternalKyselyTables;
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
async getMetadata(options) {
|
|
58
|
+
options?.withInternalKyselyTables;
|
|
59
|
+
return {
|
|
60
|
+
tables: []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return new NullIntrospector();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const isSelectItem = (item)=>'object' == typeof item && 'field' in item;
|
|
68
|
+
const escapeValue = (value)=>{
|
|
69
|
+
if (null === value) return 'null';
|
|
70
|
+
if ('string' == typeof value) return `'${value.replace(/'/g, "''")}'`;
|
|
71
|
+
if ('number' == typeof value) return `${value}`;
|
|
72
|
+
if ('boolean' == typeof value) return value ? 'TRUE' : 'FALSE';
|
|
73
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
74
|
+
};
|
|
75
|
+
const inlineParameters = (sql, params)=>{
|
|
76
|
+
if (0 === params.length) return sql;
|
|
77
|
+
if (sql.includes('?')) {
|
|
78
|
+
let out = sql;
|
|
79
|
+
for (const p of params)out = out.replace(/\?/, escapeValue(p));
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
if (/\$\d+/.test(sql)) return sql.replace(/\$(\d+)/g, (_, idx)=>{
|
|
83
|
+
const i = Number(idx) - 1;
|
|
84
|
+
const v = params[i];
|
|
85
|
+
return escapeValue(v);
|
|
86
|
+
});
|
|
87
|
+
return sql;
|
|
88
|
+
};
|
|
89
|
+
const applyWhere = (where)=>{
|
|
90
|
+
const toRaw = (w)=>{
|
|
91
|
+
if ('op' in w && 'conditions' in w) {
|
|
92
|
+
const parts = w.conditions.map((c)=>toRaw(c));
|
|
93
|
+
const sep = (0, external_kysely_namespaceObject.sql)` ${external_kysely_namespaceObject.sql.raw(w.op)} `;
|
|
94
|
+
return (0, external_kysely_namespaceObject.sql)`(${external_kysely_namespaceObject.sql.join(parts, sep)})`;
|
|
95
|
+
}
|
|
96
|
+
const leaf = w;
|
|
97
|
+
const field = leaf.field;
|
|
98
|
+
const value = leaf.value;
|
|
99
|
+
switch(leaf.op){
|
|
100
|
+
case 'is null':
|
|
101
|
+
return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is null`;
|
|
102
|
+
case 'is not null':
|
|
103
|
+
return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is not null`;
|
|
104
|
+
case 'in':
|
|
105
|
+
{
|
|
106
|
+
const items = Array.isArray(value) ? value : [
|
|
107
|
+
value
|
|
108
|
+
];
|
|
109
|
+
return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} in (${external_kysely_namespaceObject.sql.join(items.map((v)=>external_kysely_namespaceObject.sql.val(v)))})`;
|
|
110
|
+
}
|
|
111
|
+
case 'not in':
|
|
112
|
+
{
|
|
113
|
+
const items = Array.isArray(value) ? value : [
|
|
114
|
+
value
|
|
115
|
+
];
|
|
116
|
+
return (0, external_kysely_namespaceObject.sql)`not ${external_kysely_namespaceObject.sql.ref(field)} in (${external_kysely_namespaceObject.sql.join(items.map((v)=>external_kysely_namespaceObject.sql.val(v)))})`;
|
|
117
|
+
}
|
|
118
|
+
case 'between':
|
|
119
|
+
{
|
|
120
|
+
const [a, b] = value;
|
|
121
|
+
return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} between (${external_kysely_namespaceObject.sql.val(a)}, ${external_kysely_namespaceObject.sql.val(b)})`;
|
|
122
|
+
}
|
|
123
|
+
case 'not between':
|
|
124
|
+
{
|
|
125
|
+
const [a, b] = value;
|
|
126
|
+
return (0, external_kysely_namespaceObject.sql)`not ${external_kysely_namespaceObject.sql.ref(field)} between (${external_kysely_namespaceObject.sql.val(a)}, ${external_kysely_namespaceObject.sql.val(b)})`;
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} ${external_kysely_namespaceObject.sql.raw(leaf.op)} ${external_kysely_namespaceObject.sql.val(value)}`;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return toRaw(where);
|
|
133
|
+
};
|
|
134
|
+
const applyGroupBy = (qb, fields)=>{
|
|
135
|
+
if (fields && fields.length > 0) {
|
|
136
|
+
const exprs = fields.map((f)=>external_kysely_namespaceObject.sql.id(f));
|
|
137
|
+
qb = qb.groupBy(exprs);
|
|
138
|
+
}
|
|
139
|
+
return qb;
|
|
140
|
+
};
|
|
141
|
+
const applyLimit = (qb, limit)=>{
|
|
142
|
+
if (limit && 'number' == typeof limit) qb = qb.limit(limit);
|
|
143
|
+
return qb;
|
|
144
|
+
};
|
|
145
|
+
const convertDSLToSQL = (dsl, tableName)=>{
|
|
146
|
+
const db = new external_kysely_namespaceObject.Kysely({
|
|
147
|
+
dialect: new PostgresDialect()
|
|
148
|
+
});
|
|
149
|
+
let qb = db.selectFrom(tableName);
|
|
150
|
+
qb = dsl.select && dsl.select.length > 0 ? qb.select((eb)=>dsl.select.map((item)=>{
|
|
151
|
+
if (isSelectItem(item)) {
|
|
152
|
+
const field = item.field;
|
|
153
|
+
if (item.func) {
|
|
154
|
+
const alias = item.alias ?? field;
|
|
155
|
+
switch(item.func){
|
|
156
|
+
case 'avg':
|
|
157
|
+
return eb.fn.avg(field).as(alias);
|
|
158
|
+
case 'sum':
|
|
159
|
+
return eb.fn.sum(field).as(alias);
|
|
160
|
+
case 'min':
|
|
161
|
+
return eb.fn.min(field).as(alias);
|
|
162
|
+
case 'max':
|
|
163
|
+
return eb.fn.max(field).as(alias);
|
|
164
|
+
case 'count':
|
|
165
|
+
return eb.fn.count(field).as(alias);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return item.alias ? eb.ref(field).as(item.alias) : field;
|
|
169
|
+
}
|
|
170
|
+
return item;
|
|
171
|
+
})) : qb.selectAll();
|
|
172
|
+
if (dsl.where) qb = qb.where(applyWhere(dsl.where));
|
|
173
|
+
qb = applyGroupBy(qb, dsl.groupBy);
|
|
174
|
+
if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy)qb = qb.orderBy(o.field, o.order ?? 'asc');
|
|
175
|
+
qb = applyLimit(qb, dsl.limit);
|
|
176
|
+
const compiled = qb.compile();
|
|
177
|
+
return inlineParameters(compiled.sql, compiled.parameters);
|
|
178
|
+
};
|
|
36
179
|
class Dataset {
|
|
37
180
|
duckDB;
|
|
38
181
|
indexedDB;
|
|
@@ -42,7 +185,7 @@ class Dataset {
|
|
|
42
185
|
this.indexedDB = indexedDB1;
|
|
43
186
|
this._datasetId = datasetId;
|
|
44
187
|
}
|
|
45
|
-
async init(
|
|
188
|
+
async init(temporaryColumns = []) {
|
|
46
189
|
const readFunctionMap = {
|
|
47
190
|
csv: 'read_csv_auto',
|
|
48
191
|
json: 'read_json_auto',
|
|
@@ -60,7 +203,7 @@ class Dataset {
|
|
|
60
203
|
if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
|
|
61
204
|
const { dataSource } = datasetInfo;
|
|
62
205
|
const datasetSchema = datasetInfo.datasetSchema;
|
|
63
|
-
const columns =
|
|
206
|
+
const columns = temporaryColumns.length > 0 ? temporaryColumns : datasetSchema.columns;
|
|
64
207
|
const readFunction = readFunctionMap[dataSource.type];
|
|
65
208
|
if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
|
|
66
209
|
await this.duckDB.writeFile(this._datasetId, dataSource.blob);
|
|
@@ -82,6 +225,13 @@ class Dataset {
|
|
|
82
225
|
}
|
|
83
226
|
};
|
|
84
227
|
}
|
|
228
|
+
convertDSLToSQL(queryDSL) {
|
|
229
|
+
return convertDSLToSQL(queryDSL, this.datasetId);
|
|
230
|
+
}
|
|
231
|
+
async query(queryDSL) {
|
|
232
|
+
const sql = this.convertDSLToSQL(queryDSL);
|
|
233
|
+
return this.queryBySQL(sql);
|
|
234
|
+
}
|
|
85
235
|
async disconnect() {
|
|
86
236
|
await this.duckDB.query(`DROP VIEW IF EXISTS "${this._datasetId}"`);
|
|
87
237
|
}
|
|
@@ -353,9 +503,14 @@ class VQuery {
|
|
|
353
503
|
this.isInitialized = true;
|
|
354
504
|
}
|
|
355
505
|
}
|
|
356
|
-
async createDataset(datasetId, data, type,
|
|
506
|
+
async createDataset(datasetId, data, type, columns = []) {
|
|
357
507
|
await this.ensureInitialized();
|
|
358
508
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
509
|
+
const datasetSchema = {
|
|
510
|
+
datasetId,
|
|
511
|
+
datasetAlias: datasetId,
|
|
512
|
+
columns: columns
|
|
513
|
+
};
|
|
359
514
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
360
515
|
}
|
|
361
516
|
async updateDataset(datasetId, data, type, datasetSchema) {
|
|
@@ -363,24 +518,23 @@ class VQuery {
|
|
|
363
518
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
364
519
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
365
520
|
}
|
|
366
|
-
async
|
|
521
|
+
async dropDataset(datasetId) {
|
|
367
522
|
await this.ensureInitialized();
|
|
368
523
|
await this.indexedDB.deleteDataset(datasetId);
|
|
369
524
|
}
|
|
370
|
-
async
|
|
525
|
+
async hasDataset(datasetId) {
|
|
371
526
|
await this.ensureInitialized();
|
|
372
|
-
|
|
527
|
+
const datasets = await this.indexedDB.listDatasets();
|
|
528
|
+
return datasets.some((item)=>item.datasetId === datasetId);
|
|
373
529
|
}
|
|
374
|
-
async
|
|
530
|
+
async listDatasets() {
|
|
375
531
|
await this.ensureInitialized();
|
|
376
|
-
|
|
377
|
-
await dataset.init();
|
|
378
|
-
return dataset;
|
|
532
|
+
return this.indexedDB.listDatasets();
|
|
379
533
|
}
|
|
380
|
-
async
|
|
534
|
+
async connectDataset(datasetId, temporaryColumns = []) {
|
|
381
535
|
await this.ensureInitialized();
|
|
382
536
|
const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
|
|
383
|
-
await dataset.init(
|
|
537
|
+
await dataset.init(temporaryColumns);
|
|
384
538
|
return dataset;
|
|
385
539
|
}
|
|
386
540
|
async close() {
|
|
@@ -390,12 +544,14 @@ class VQuery {
|
|
|
390
544
|
}
|
|
391
545
|
exports.DataSourceBuilder = __webpack_exports__.DataSourceBuilder;
|
|
392
546
|
exports.VQuery = __webpack_exports__.VQuery;
|
|
547
|
+
exports.convertDSLToSQL = __webpack_exports__.convertDSLToSQL;
|
|
393
548
|
exports.isBase64Url = __webpack_exports__.isBase64Url;
|
|
394
549
|
exports.isHttpUrl = __webpack_exports__.isHttpUrl;
|
|
395
550
|
exports.isUrl = __webpack_exports__.isUrl;
|
|
396
551
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
397
552
|
"DataSourceBuilder",
|
|
398
553
|
"VQuery",
|
|
554
|
+
"convertDSLToSQL",
|
|
399
555
|
"isBase64Url",
|
|
400
556
|
"isHttpUrl",
|
|
401
557
|
"isUrl"
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,146 @@
|
|
|
1
|
+
import { DummyDriver, Kysely, PostgresAdapter, PostgresQueryCompiler, sql as external_kysely_sql } from "kysely";
|
|
1
2
|
import { AsyncDuckDB, ConsoleLogger, selectBundle } from "@duckdb/duckdb-wasm";
|
|
3
|
+
class PostgresDialect {
|
|
4
|
+
createDriver() {
|
|
5
|
+
return new DummyDriver();
|
|
6
|
+
}
|
|
7
|
+
createQueryCompiler() {
|
|
8
|
+
return new PostgresQueryCompiler();
|
|
9
|
+
}
|
|
10
|
+
createAdapter() {
|
|
11
|
+
return new PostgresAdapter();
|
|
12
|
+
}
|
|
13
|
+
createIntrospector(db) {
|
|
14
|
+
class NullIntrospector {
|
|
15
|
+
async getSchemas() {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
async getTables(options) {
|
|
19
|
+
options?.withInternalKyselyTables;
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
async getMetadata(options) {
|
|
23
|
+
options?.withInternalKyselyTables;
|
|
24
|
+
return {
|
|
25
|
+
tables: []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return new NullIntrospector();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const isSelectItem = (item)=>'object' == typeof item && 'field' in item;
|
|
33
|
+
const escapeValue = (value)=>{
|
|
34
|
+
if (null === value) return 'null';
|
|
35
|
+
if ('string' == typeof value) return `'${value.replace(/'/g, "''")}'`;
|
|
36
|
+
if ('number' == typeof value) return `${value}`;
|
|
37
|
+
if ('boolean' == typeof value) return value ? 'TRUE' : 'FALSE';
|
|
38
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
39
|
+
};
|
|
40
|
+
const inlineParameters = (sql, params)=>{
|
|
41
|
+
if (0 === params.length) return sql;
|
|
42
|
+
if (sql.includes('?')) {
|
|
43
|
+
let out = sql;
|
|
44
|
+
for (const p of params)out = out.replace(/\?/, escapeValue(p));
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
if (/\$\d+/.test(sql)) return sql.replace(/\$(\d+)/g, (_, idx)=>{
|
|
48
|
+
const i = Number(idx) - 1;
|
|
49
|
+
const v = params[i];
|
|
50
|
+
return escapeValue(v);
|
|
51
|
+
});
|
|
52
|
+
return sql;
|
|
53
|
+
};
|
|
54
|
+
const applyWhere = (where)=>{
|
|
55
|
+
const toRaw = (w)=>{
|
|
56
|
+
if ('op' in w && 'conditions' in w) {
|
|
57
|
+
const parts = w.conditions.map((c)=>toRaw(c));
|
|
58
|
+
const sep = external_kysely_sql` ${external_kysely_sql.raw(w.op)} `;
|
|
59
|
+
return external_kysely_sql`(${external_kysely_sql.join(parts, sep)})`;
|
|
60
|
+
}
|
|
61
|
+
const leaf = w;
|
|
62
|
+
const field = leaf.field;
|
|
63
|
+
const value = leaf.value;
|
|
64
|
+
switch(leaf.op){
|
|
65
|
+
case 'is null':
|
|
66
|
+
return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
|
|
67
|
+
case 'is not null':
|
|
68
|
+
return external_kysely_sql`${external_kysely_sql.ref(field)} is not null`;
|
|
69
|
+
case 'in':
|
|
70
|
+
{
|
|
71
|
+
const items = Array.isArray(value) ? value : [
|
|
72
|
+
value
|
|
73
|
+
];
|
|
74
|
+
return external_kysely_sql`${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
|
|
75
|
+
}
|
|
76
|
+
case 'not in':
|
|
77
|
+
{
|
|
78
|
+
const items = Array.isArray(value) ? value : [
|
|
79
|
+
value
|
|
80
|
+
];
|
|
81
|
+
return external_kysely_sql`not ${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
|
|
82
|
+
}
|
|
83
|
+
case 'between':
|
|
84
|
+
{
|
|
85
|
+
const [a, b] = value;
|
|
86
|
+
return external_kysely_sql`${external_kysely_sql.ref(field)} between (${external_kysely_sql.val(a)}, ${external_kysely_sql.val(b)})`;
|
|
87
|
+
}
|
|
88
|
+
case 'not between':
|
|
89
|
+
{
|
|
90
|
+
const [a, b] = value;
|
|
91
|
+
return external_kysely_sql`not ${external_kysely_sql.ref(field)} between (${external_kysely_sql.val(a)}, ${external_kysely_sql.val(b)})`;
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(leaf.op)} ${external_kysely_sql.val(value)}`;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return toRaw(where);
|
|
98
|
+
};
|
|
99
|
+
const applyGroupBy = (qb, fields)=>{
|
|
100
|
+
if (fields && fields.length > 0) {
|
|
101
|
+
const exprs = fields.map((f)=>external_kysely_sql.id(f));
|
|
102
|
+
qb = qb.groupBy(exprs);
|
|
103
|
+
}
|
|
104
|
+
return qb;
|
|
105
|
+
};
|
|
106
|
+
const applyLimit = (qb, limit)=>{
|
|
107
|
+
if (limit && 'number' == typeof limit) qb = qb.limit(limit);
|
|
108
|
+
return qb;
|
|
109
|
+
};
|
|
110
|
+
const convertDSLToSQL = (dsl, tableName)=>{
|
|
111
|
+
const db = new Kysely({
|
|
112
|
+
dialect: new PostgresDialect()
|
|
113
|
+
});
|
|
114
|
+
let qb = db.selectFrom(tableName);
|
|
115
|
+
qb = dsl.select && dsl.select.length > 0 ? qb.select((eb)=>dsl.select.map((item)=>{
|
|
116
|
+
if (isSelectItem(item)) {
|
|
117
|
+
const field = item.field;
|
|
118
|
+
if (item.func) {
|
|
119
|
+
const alias = item.alias ?? field;
|
|
120
|
+
switch(item.func){
|
|
121
|
+
case 'avg':
|
|
122
|
+
return eb.fn.avg(field).as(alias);
|
|
123
|
+
case 'sum':
|
|
124
|
+
return eb.fn.sum(field).as(alias);
|
|
125
|
+
case 'min':
|
|
126
|
+
return eb.fn.min(field).as(alias);
|
|
127
|
+
case 'max':
|
|
128
|
+
return eb.fn.max(field).as(alias);
|
|
129
|
+
case 'count':
|
|
130
|
+
return eb.fn.count(field).as(alias);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return item.alias ? eb.ref(field).as(item.alias) : field;
|
|
134
|
+
}
|
|
135
|
+
return item;
|
|
136
|
+
})) : qb.selectAll();
|
|
137
|
+
if (dsl.where) qb = qb.where(applyWhere(dsl.where));
|
|
138
|
+
qb = applyGroupBy(qb, dsl.groupBy);
|
|
139
|
+
if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy)qb = qb.orderBy(o.field, o.order ?? 'asc');
|
|
140
|
+
qb = applyLimit(qb, dsl.limit);
|
|
141
|
+
const compiled = qb.compile();
|
|
142
|
+
return inlineParameters(compiled.sql, compiled.parameters);
|
|
143
|
+
};
|
|
2
144
|
class Dataset {
|
|
3
145
|
duckDB;
|
|
4
146
|
indexedDB;
|
|
@@ -8,7 +150,7 @@ class Dataset {
|
|
|
8
150
|
this.indexedDB = indexedDB1;
|
|
9
151
|
this._datasetId = datasetId;
|
|
10
152
|
}
|
|
11
|
-
async init(
|
|
153
|
+
async init(temporaryColumns = []) {
|
|
12
154
|
const readFunctionMap = {
|
|
13
155
|
csv: 'read_csv_auto',
|
|
14
156
|
json: 'read_json_auto',
|
|
@@ -26,7 +168,7 @@ class Dataset {
|
|
|
26
168
|
if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
|
|
27
169
|
const { dataSource } = datasetInfo;
|
|
28
170
|
const datasetSchema = datasetInfo.datasetSchema;
|
|
29
|
-
const columns =
|
|
171
|
+
const columns = temporaryColumns.length > 0 ? temporaryColumns : datasetSchema.columns;
|
|
30
172
|
const readFunction = readFunctionMap[dataSource.type];
|
|
31
173
|
if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
|
|
32
174
|
await this.duckDB.writeFile(this._datasetId, dataSource.blob);
|
|
@@ -48,6 +190,13 @@ class Dataset {
|
|
|
48
190
|
}
|
|
49
191
|
};
|
|
50
192
|
}
|
|
193
|
+
convertDSLToSQL(queryDSL) {
|
|
194
|
+
return convertDSLToSQL(queryDSL, this.datasetId);
|
|
195
|
+
}
|
|
196
|
+
async query(queryDSL) {
|
|
197
|
+
const sql = this.convertDSLToSQL(queryDSL);
|
|
198
|
+
return this.queryBySQL(sql);
|
|
199
|
+
}
|
|
51
200
|
async disconnect() {
|
|
52
201
|
await this.duckDB.query(`DROP VIEW IF EXISTS "${this._datasetId}"`);
|
|
53
202
|
}
|
|
@@ -318,9 +467,14 @@ class VQuery {
|
|
|
318
467
|
this.isInitialized = true;
|
|
319
468
|
}
|
|
320
469
|
}
|
|
321
|
-
async createDataset(datasetId, data, type,
|
|
470
|
+
async createDataset(datasetId, data, type, columns = []) {
|
|
322
471
|
await this.ensureInitialized();
|
|
323
472
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
473
|
+
const datasetSchema = {
|
|
474
|
+
datasetId,
|
|
475
|
+
datasetAlias: datasetId,
|
|
476
|
+
columns: columns
|
|
477
|
+
};
|
|
324
478
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
325
479
|
}
|
|
326
480
|
async updateDataset(datasetId, data, type, datasetSchema) {
|
|
@@ -328,24 +482,23 @@ class VQuery {
|
|
|
328
482
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
329
483
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
330
484
|
}
|
|
331
|
-
async
|
|
485
|
+
async dropDataset(datasetId) {
|
|
332
486
|
await this.ensureInitialized();
|
|
333
487
|
await this.indexedDB.deleteDataset(datasetId);
|
|
334
488
|
}
|
|
335
|
-
async
|
|
489
|
+
async hasDataset(datasetId) {
|
|
336
490
|
await this.ensureInitialized();
|
|
337
|
-
|
|
491
|
+
const datasets = await this.indexedDB.listDatasets();
|
|
492
|
+
return datasets.some((item)=>item.datasetId === datasetId);
|
|
338
493
|
}
|
|
339
|
-
async
|
|
494
|
+
async listDatasets() {
|
|
340
495
|
await this.ensureInitialized();
|
|
341
|
-
|
|
342
|
-
await dataset.init();
|
|
343
|
-
return dataset;
|
|
496
|
+
return this.indexedDB.listDatasets();
|
|
344
497
|
}
|
|
345
|
-
async
|
|
498
|
+
async connectDataset(datasetId, temporaryColumns = []) {
|
|
346
499
|
await this.ensureInitialized();
|
|
347
500
|
const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
|
|
348
|
-
await dataset.init(
|
|
501
|
+
await dataset.init(temporaryColumns);
|
|
349
502
|
return dataset;
|
|
350
503
|
}
|
|
351
504
|
async close() {
|
|
@@ -353,4 +506,4 @@ class VQuery {
|
|
|
353
506
|
await this.duckDB.close();
|
|
354
507
|
}
|
|
355
508
|
}
|
|
356
|
-
export { DataSourceBuilder, VQuery, isBase64Url, isHttpUrl, isUrl };
|
|
509
|
+
export { DataSourceBuilder, VQuery, convertDSLToSQL, isBase64Url, isHttpUrl, isUrl };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { inlineParameters } from './inlineParameters';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const inlineParameters: (sql: string, params: readonly unknown[]) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PostgresDialect } from './postgresDialect';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Dialect, DummyDriver } from 'kysely';
|
|
2
|
+
import { PostgresQueryCompiler } from 'kysely';
|
|
3
|
+
import { PostgresAdapter } from 'kysely';
|
|
4
|
+
import { Kysely } from 'kysely';
|
|
5
|
+
import type { DatabaseIntrospector } from 'kysely';
|
|
6
|
+
export declare class PostgresDialect implements Dialect {
|
|
7
|
+
createDriver(): DummyDriver;
|
|
8
|
+
createQueryCompiler(): PostgresQueryCompiler;
|
|
9
|
+
createAdapter(): PostgresAdapter;
|
|
10
|
+
createIntrospector<DB = unknown>(db: Kysely<DB>): DatabaseIntrospector;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { convertDSLToSQL } from './dslToSQL';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Where, WhereClause, WhereGroup, WhereLeaf } from '../types';
|
|
2
|
+
import { SelectItem } from '../types/QueryDSL/Select';
|
|
3
|
+
export declare const isSelectItem: <T>(item: keyof T | SelectItem<T>) => item is SelectItem<T>;
|
|
4
|
+
export declare const isWhereLeaf: <T>(where: Where<T> | WhereClause<T>) => where is WhereLeaf<T>;
|
|
5
|
+
export declare const isWhereGroup: <T>(where: Where<T> | WhereClause<T>) => where is WhereGroup<T>;
|
|
6
|
+
export declare const isStringOrNumber: (value: unknown) => value is string | number;
|
|
7
|
+
export declare const escapeLiteral: <T>(value: T[keyof T]) => T[keyof T];
|
|
8
|
+
export declare const escapeValue: (value: unknown) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type GroupBy<T> = Array<keyof T>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GroupBy } from './GroupBy';
|
|
2
|
+
import { OrderBy } from './OrderBy';
|
|
3
|
+
import { Select } from './Select';
|
|
4
|
+
import { Where } from './Where';
|
|
5
|
+
export interface QueryDSL<Table> {
|
|
6
|
+
select: Select<Table>;
|
|
7
|
+
where?: Where<Table>;
|
|
8
|
+
groupBy?: GroupBy<Table>;
|
|
9
|
+
orderBy?: OrderBy<Table>;
|
|
10
|
+
limit?: number;
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Where<T> = WhereGroup<T>;
|
|
2
|
+
export type WhereGroup<T> = {
|
|
3
|
+
op: 'and' | 'or';
|
|
4
|
+
conditions: Array<WhereClause<T>>;
|
|
5
|
+
};
|
|
6
|
+
export type WhereClause<T> = WhereLeaf<T> | WhereGroup<T>;
|
|
7
|
+
export type WhereLeaf<T> = {
|
|
8
|
+
[K in keyof T]: {
|
|
9
|
+
[O in Operator]: {
|
|
10
|
+
field: K;
|
|
11
|
+
op: O;
|
|
12
|
+
} & (O extends 'is null' | 'is not null' ? {
|
|
13
|
+
value?: never;
|
|
14
|
+
} : O extends 'in' | 'not in' ? {
|
|
15
|
+
value: T[K] | T[K][];
|
|
16
|
+
} : O extends 'between' | 'not between' ? {
|
|
17
|
+
value: [T[K], T[K]];
|
|
18
|
+
} : {
|
|
19
|
+
value: T[K];
|
|
20
|
+
});
|
|
21
|
+
}[Operator];
|
|
22
|
+
}[keyof T];
|
|
23
|
+
type Operator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'like' | 'not like' | 'ilike' | 'not ilike' | 'in' | 'not in' | 'between' | 'not between' | 'is null' | 'is not null';
|
|
24
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
package/dist/vquery.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare class VQuery {
|
|
|
9
9
|
/**
|
|
10
10
|
* 创建数据集,根据表结构和数据,存储信息到indexedDB
|
|
11
11
|
*/
|
|
12
|
-
createDataset(datasetId: string, data: string | ArrayBuffer | Blob | TidyDatum[], type: DataSourceType,
|
|
12
|
+
createDataset(datasetId: string, data: string | ArrayBuffer | Blob | TidyDatum[], type: DataSourceType, columns?: DatasetColumn[]): Promise<void>;
|
|
13
13
|
/**
|
|
14
14
|
* 修改数据集,更新信息到indexedDB内
|
|
15
15
|
*/
|
|
@@ -17,7 +17,11 @@ export declare class VQuery {
|
|
|
17
17
|
/**
|
|
18
18
|
* 删除数据集,从indexdb移除数据集
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
dropDataset(datasetId: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* 检查数据集是否存在
|
|
23
|
+
*/
|
|
24
|
+
hasDataset(datasetId: string): Promise<boolean>;
|
|
21
25
|
/**
|
|
22
26
|
* 获取所有可用数据集
|
|
23
27
|
*/
|
|
@@ -29,13 +33,7 @@ export declare class VQuery {
|
|
|
29
33
|
/**
|
|
30
34
|
* 连接数据集,返回数据集信息,从indexedDB获取表结构,使用DuckDB在内存中创建表
|
|
31
35
|
*/
|
|
32
|
-
connectDataset(datasetId: string): Promise<Dataset>;
|
|
33
|
-
/**
|
|
34
|
-
* 连接临时数据集,返回数据集信息,从indexedDB获取表结构,使用DuckDB在内存中创建表
|
|
35
|
-
* @param datasetId
|
|
36
|
-
* @returns
|
|
37
|
-
*/
|
|
38
|
-
connectTemporaryDataset(datasetId: string, temporaryDatasetSchema?: DatasetColumn[]): Promise<Dataset>;
|
|
36
|
+
connectDataset(datasetId: string, temporaryColumns?: DatasetColumn[]): Promise<Dataset>;
|
|
39
37
|
/**
|
|
40
38
|
* 关闭所有数据集连接,释放DuckDB资源
|
|
41
39
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visactor/vquery",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@duckdb/duckdb-wasm": "
|
|
18
|
+
"@duckdb/duckdb-wasm": "1.30.0",
|
|
19
|
+
"kysely": "^0.27.2"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@eslint/js": "^9.35.0",
|
|
22
23
|
"@rslib/core": "^0.15.1",
|
|
23
|
-
"@rstest/core": "
|
|
24
|
+
"@rstest/core": "0.6.5",
|
|
24
25
|
"@types/node": "^22.18.10",
|
|
25
|
-
"@rstest/coverage-istanbul": "
|
|
26
|
+
"@rstest/coverage-istanbul": "0.0.5",
|
|
26
27
|
"eslint": "^9.35.0",
|
|
27
28
|
"globals": "^16.3.0",
|
|
28
29
|
"typescript": "^5.9.3",
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
"dev": "rslib build --watch",
|
|
34
35
|
"lint": "eslint .",
|
|
35
36
|
"test": "rstest",
|
|
37
|
+
"test:update": "rstest --update",
|
|
38
|
+
"test:coverage": "rstest --coverage && open ./coverage/index.html",
|
|
36
39
|
"type-check": "tsc --noEmit"
|
|
37
40
|
}
|
|
38
41
|
}
|
|
File without changes
|
|
File without changes
|