@visactor/vquery 0.1.48 → 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 +1 -1
- package/dist/dataset/index.d.ts +1 -0
- package/dist/index.cjs +151 -62
- package/dist/index.d.ts +2 -1
- package/dist/index.js +149 -63
- 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/{dataset/convert/applyWhere.d.ts → sql-builder/builders/where.d.ts} +2 -1
- 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/{dataset/convert → sql-builder}/utils.d.ts +4 -2
- package/dist/types/QueryDSL/Where.d.ts +1 -1
- package/dist/vquery.d.ts +7 -9
- package/package.json +7 -4
- package/dist/dataset/convert/dslToSQL.d.ts +0 -2
- /package/dist/{dataSourceBuilder → data-source-builder}/dataSourceBuilder.d.ts +0 -0
- /package/dist/{dataSourceBuilder → data-source-builder}/index.d.ts +0 -0
|
@@ -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;
|
package/dist/dataset/index.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -29,68 +29,152 @@ __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
|
});
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const applyWhere = (where)=>{
|
|
41
|
-
const escape = (str)=>{
|
|
42
|
-
if ('string' == typeof str) return `'${str.replace(/'/g, "''")}'`;
|
|
43
|
-
return str;
|
|
44
|
-
};
|
|
45
|
-
if (isWhereGroup(where)) {
|
|
46
|
-
const logicalOp = where.op.toUpperCase();
|
|
47
|
-
return `(${where.conditions.map((c)=>applyWhere(c)).join(` ${logicalOp} `)})`;
|
|
37
|
+
const external_kysely_namespaceObject = require("kysely");
|
|
38
|
+
class PostgresDialect {
|
|
39
|
+
createDriver() {
|
|
40
|
+
return new external_kysely_namespaceObject.DummyDriver();
|
|
48
41
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
};
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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)}`;
|
|
65
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);
|
|
66
138
|
}
|
|
67
|
-
return
|
|
139
|
+
return qb;
|
|
140
|
+
};
|
|
141
|
+
const applyLimit = (qb, limit)=>{
|
|
142
|
+
if (limit && 'number' == typeof limit) qb = qb.limit(limit);
|
|
143
|
+
return qb;
|
|
68
144
|
};
|
|
69
145
|
const convertDSLToSQL = (dsl, tableName)=>{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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)=>{
|
|
74
151
|
if (isSelectItem(item)) {
|
|
75
|
-
|
|
76
|
-
if (item.
|
|
77
|
-
|
|
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;
|
|
78
169
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (dsl.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (dsl.groupBy && dsl.groupBy.length > 0) sql += ` GROUP BY ${dsl.groupBy.join(', ')}`;
|
|
88
|
-
if (dsl.orderBy && dsl.orderBy.length > 0) {
|
|
89
|
-
const orderByFields = dsl.orderBy.map((item)=>`${item.field}${item.order ? ` ${item.order.toUpperCase()}` : ''}`);
|
|
90
|
-
sql += ` ORDER BY ${orderByFields.join(', ')}`;
|
|
91
|
-
}
|
|
92
|
-
if (dsl.limit) sql += ` LIMIT ${dsl.limit}`;
|
|
93
|
-
return sql;
|
|
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);
|
|
94
178
|
};
|
|
95
179
|
class Dataset {
|
|
96
180
|
duckDB;
|
|
@@ -101,7 +185,7 @@ class Dataset {
|
|
|
101
185
|
this.indexedDB = indexedDB1;
|
|
102
186
|
this._datasetId = datasetId;
|
|
103
187
|
}
|
|
104
|
-
async init(
|
|
188
|
+
async init(temporaryColumns = []) {
|
|
105
189
|
const readFunctionMap = {
|
|
106
190
|
csv: 'read_csv_auto',
|
|
107
191
|
json: 'read_json_auto',
|
|
@@ -119,7 +203,7 @@ class Dataset {
|
|
|
119
203
|
if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
|
|
120
204
|
const { dataSource } = datasetInfo;
|
|
121
205
|
const datasetSchema = datasetInfo.datasetSchema;
|
|
122
|
-
const columns =
|
|
206
|
+
const columns = temporaryColumns.length > 0 ? temporaryColumns : datasetSchema.columns;
|
|
123
207
|
const readFunction = readFunctionMap[dataSource.type];
|
|
124
208
|
if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
|
|
125
209
|
await this.duckDB.writeFile(this._datasetId, dataSource.blob);
|
|
@@ -146,7 +230,6 @@ class Dataset {
|
|
|
146
230
|
}
|
|
147
231
|
async query(queryDSL) {
|
|
148
232
|
const sql = this.convertDSLToSQL(queryDSL);
|
|
149
|
-
console.log(sql);
|
|
150
233
|
return this.queryBySQL(sql);
|
|
151
234
|
}
|
|
152
235
|
async disconnect() {
|
|
@@ -420,9 +503,14 @@ class VQuery {
|
|
|
420
503
|
this.isInitialized = true;
|
|
421
504
|
}
|
|
422
505
|
}
|
|
423
|
-
async createDataset(datasetId, data, type,
|
|
506
|
+
async createDataset(datasetId, data, type, columns = []) {
|
|
424
507
|
await this.ensureInitialized();
|
|
425
508
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
509
|
+
const datasetSchema = {
|
|
510
|
+
datasetId,
|
|
511
|
+
datasetAlias: datasetId,
|
|
512
|
+
columns: columns
|
|
513
|
+
};
|
|
426
514
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
427
515
|
}
|
|
428
516
|
async updateDataset(datasetId, data, type, datasetSchema) {
|
|
@@ -430,24 +518,23 @@ class VQuery {
|
|
|
430
518
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
431
519
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
432
520
|
}
|
|
433
|
-
async
|
|
521
|
+
async dropDataset(datasetId) {
|
|
434
522
|
await this.ensureInitialized();
|
|
435
523
|
await this.indexedDB.deleteDataset(datasetId);
|
|
436
524
|
}
|
|
437
|
-
async
|
|
525
|
+
async hasDataset(datasetId) {
|
|
438
526
|
await this.ensureInitialized();
|
|
439
|
-
|
|
527
|
+
const datasets = await this.indexedDB.listDatasets();
|
|
528
|
+
return datasets.some((item)=>item.datasetId === datasetId);
|
|
440
529
|
}
|
|
441
|
-
async
|
|
530
|
+
async listDatasets() {
|
|
442
531
|
await this.ensureInitialized();
|
|
443
|
-
|
|
444
|
-
await dataset.init();
|
|
445
|
-
return dataset;
|
|
532
|
+
return this.indexedDB.listDatasets();
|
|
446
533
|
}
|
|
447
|
-
async
|
|
534
|
+
async connectDataset(datasetId, temporaryColumns = []) {
|
|
448
535
|
await this.ensureInitialized();
|
|
449
536
|
const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
|
|
450
|
-
await dataset.init(
|
|
537
|
+
await dataset.init(temporaryColumns);
|
|
451
538
|
return dataset;
|
|
452
539
|
}
|
|
453
540
|
async close() {
|
|
@@ -457,12 +544,14 @@ class VQuery {
|
|
|
457
544
|
}
|
|
458
545
|
exports.DataSourceBuilder = __webpack_exports__.DataSourceBuilder;
|
|
459
546
|
exports.VQuery = __webpack_exports__.VQuery;
|
|
547
|
+
exports.convertDSLToSQL = __webpack_exports__.convertDSLToSQL;
|
|
460
548
|
exports.isBase64Url = __webpack_exports__.isBase64Url;
|
|
461
549
|
exports.isHttpUrl = __webpack_exports__.isHttpUrl;
|
|
462
550
|
exports.isUrl = __webpack_exports__.isUrl;
|
|
463
551
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
464
552
|
"DataSourceBuilder",
|
|
465
553
|
"VQuery",
|
|
554
|
+
"convertDSLToSQL",
|
|
466
555
|
"isBase64Url",
|
|
467
556
|
"isHttpUrl",
|
|
468
557
|
"isUrl"
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,62 +1,145 @@
|
|
|
1
|
+
import { DummyDriver, Kysely, PostgresAdapter, PostgresQueryCompiler, sql as external_kysely_sql } from "kysely";
|
|
1
2
|
import { AsyncDuckDB, ConsoleLogger, selectBundle } from "@duckdb/duckdb-wasm";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const isStringOrNumber = (value)=>'string' == typeof value || 'number' == typeof value;
|
|
6
|
-
const applyWhere = (where)=>{
|
|
7
|
-
const escape = (str)=>{
|
|
8
|
-
if ('string' == typeof str) return `'${str.replace(/'/g, "''")}'`;
|
|
9
|
-
return str;
|
|
10
|
-
};
|
|
11
|
-
if (isWhereGroup(where)) {
|
|
12
|
-
const logicalOp = where.op.toUpperCase();
|
|
13
|
-
return `(${where.conditions.map((c)=>applyWhere(c)).join(` ${logicalOp} `)})`;
|
|
3
|
+
class PostgresDialect {
|
|
4
|
+
createDriver() {
|
|
5
|
+
return new DummyDriver();
|
|
14
6
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
};
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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)}`;
|
|
31
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);
|
|
32
103
|
}
|
|
33
|
-
return
|
|
104
|
+
return qb;
|
|
105
|
+
};
|
|
106
|
+
const applyLimit = (qb, limit)=>{
|
|
107
|
+
if (limit && 'number' == typeof limit) qb = qb.limit(limit);
|
|
108
|
+
return qb;
|
|
34
109
|
};
|
|
35
110
|
const convertDSLToSQL = (dsl, tableName)=>{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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)=>{
|
|
40
116
|
if (isSelectItem(item)) {
|
|
41
|
-
|
|
42
|
-
if (item.
|
|
43
|
-
|
|
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;
|
|
44
134
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (dsl.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (dsl.groupBy && dsl.groupBy.length > 0) sql += ` GROUP BY ${dsl.groupBy.join(', ')}`;
|
|
54
|
-
if (dsl.orderBy && dsl.orderBy.length > 0) {
|
|
55
|
-
const orderByFields = dsl.orderBy.map((item)=>`${item.field}${item.order ? ` ${item.order.toUpperCase()}` : ''}`);
|
|
56
|
-
sql += ` ORDER BY ${orderByFields.join(', ')}`;
|
|
57
|
-
}
|
|
58
|
-
if (dsl.limit) sql += ` LIMIT ${dsl.limit}`;
|
|
59
|
-
return sql;
|
|
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);
|
|
60
143
|
};
|
|
61
144
|
class Dataset {
|
|
62
145
|
duckDB;
|
|
@@ -67,7 +150,7 @@ class Dataset {
|
|
|
67
150
|
this.indexedDB = indexedDB1;
|
|
68
151
|
this._datasetId = datasetId;
|
|
69
152
|
}
|
|
70
|
-
async init(
|
|
153
|
+
async init(temporaryColumns = []) {
|
|
71
154
|
const readFunctionMap = {
|
|
72
155
|
csv: 'read_csv_auto',
|
|
73
156
|
json: 'read_json_auto',
|
|
@@ -85,7 +168,7 @@ class Dataset {
|
|
|
85
168
|
if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
|
|
86
169
|
const { dataSource } = datasetInfo;
|
|
87
170
|
const datasetSchema = datasetInfo.datasetSchema;
|
|
88
|
-
const columns =
|
|
171
|
+
const columns = temporaryColumns.length > 0 ? temporaryColumns : datasetSchema.columns;
|
|
89
172
|
const readFunction = readFunctionMap[dataSource.type];
|
|
90
173
|
if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
|
|
91
174
|
await this.duckDB.writeFile(this._datasetId, dataSource.blob);
|
|
@@ -112,7 +195,6 @@ class Dataset {
|
|
|
112
195
|
}
|
|
113
196
|
async query(queryDSL) {
|
|
114
197
|
const sql = this.convertDSLToSQL(queryDSL);
|
|
115
|
-
console.log(sql);
|
|
116
198
|
return this.queryBySQL(sql);
|
|
117
199
|
}
|
|
118
200
|
async disconnect() {
|
|
@@ -385,9 +467,14 @@ class VQuery {
|
|
|
385
467
|
this.isInitialized = true;
|
|
386
468
|
}
|
|
387
469
|
}
|
|
388
|
-
async createDataset(datasetId, data, type,
|
|
470
|
+
async createDataset(datasetId, data, type, columns = []) {
|
|
389
471
|
await this.ensureInitialized();
|
|
390
472
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
473
|
+
const datasetSchema = {
|
|
474
|
+
datasetId,
|
|
475
|
+
datasetAlias: datasetId,
|
|
476
|
+
columns: columns
|
|
477
|
+
};
|
|
391
478
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
392
479
|
}
|
|
393
480
|
async updateDataset(datasetId, data, type, datasetSchema) {
|
|
@@ -395,24 +482,23 @@ class VQuery {
|
|
|
395
482
|
const dataSource = await DataSourceBuilder.from(type, data).build();
|
|
396
483
|
await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
|
|
397
484
|
}
|
|
398
|
-
async
|
|
485
|
+
async dropDataset(datasetId) {
|
|
399
486
|
await this.ensureInitialized();
|
|
400
487
|
await this.indexedDB.deleteDataset(datasetId);
|
|
401
488
|
}
|
|
402
|
-
async
|
|
489
|
+
async hasDataset(datasetId) {
|
|
403
490
|
await this.ensureInitialized();
|
|
404
|
-
|
|
491
|
+
const datasets = await this.indexedDB.listDatasets();
|
|
492
|
+
return datasets.some((item)=>item.datasetId === datasetId);
|
|
405
493
|
}
|
|
406
|
-
async
|
|
494
|
+
async listDatasets() {
|
|
407
495
|
await this.ensureInitialized();
|
|
408
|
-
|
|
409
|
-
await dataset.init();
|
|
410
|
-
return dataset;
|
|
496
|
+
return this.indexedDB.listDatasets();
|
|
411
497
|
}
|
|
412
|
-
async
|
|
498
|
+
async connectDataset(datasetId, temporaryColumns = []) {
|
|
413
499
|
await this.ensureInitialized();
|
|
414
500
|
const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
|
|
415
|
-
await dataset.init(
|
|
501
|
+
await dataset.init(temporaryColumns);
|
|
416
502
|
return dataset;
|
|
417
503
|
}
|
|
418
504
|
async close() {
|
|
@@ -420,4 +506,4 @@ class VQuery {
|
|
|
420
506
|
await this.duckDB.close();
|
|
421
507
|
}
|
|
422
508
|
}
|
|
423
|
-
export { DataSourceBuilder, VQuery, isBase64Url, isHttpUrl, isUrl };
|
|
509
|
+
export { DataSourceBuilder, VQuery, convertDSLToSQL, isBase64Url, isHttpUrl, isUrl };
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { Where, WhereClause } from '../../types';
|
|
2
|
-
|
|
2
|
+
import type { RawBuilder } from 'kysely';
|
|
3
|
+
export declare const applyWhere: <T>(where: Where<T> | WhereClause<T>) => RawBuilder<boolean>;
|
|
@@ -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';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Where, WhereClause, WhereGroup, WhereLeaf } from '
|
|
2
|
-
import { SelectItem } from '
|
|
1
|
+
import { Where, WhereClause, WhereGroup, WhereLeaf } from '../types';
|
|
2
|
+
import { SelectItem } from '../types/QueryDSL/Select';
|
|
3
3
|
export declare const isSelectItem: <T>(item: keyof T | SelectItem<T>) => item is SelectItem<T>;
|
|
4
4
|
export declare const isWhereLeaf: <T>(where: Where<T> | WhereClause<T>) => where is WhereLeaf<T>;
|
|
5
5
|
export declare const isWhereGroup: <T>(where: Where<T> | WhereClause<T>) => where is WhereGroup<T>;
|
|
6
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;
|
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": "1.30.0"
|
|
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
|