@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.
@@ -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(temporaryStructs?: DatasetColumn[]): Promise<void>;
9
+ init(temporaryColumns?: DatasetColumn[]): Promise<void>;
10
10
  queryBySQL(sql: string): Promise<{
11
11
  performance: {
12
12
  startAt: string;
@@ -1 +1,2 @@
1
1
  export { Dataset } from './dataset';
2
+ export { convertDSLToSQL } from '../sql-builder';
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 isSelectItem = (item)=>'object' == typeof item && 'field' in item;
37
- const isWhereLeaf = (where)=>'field' in where && 'op' in where && 'value' in where;
38
- const isWhereGroup = (where)=>'op' in where && 'conditions' in where;
39
- const isStringOrNumber = (value)=>'string' == typeof value || 'number' == typeof value;
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
- if (isWhereLeaf(where)) {
50
- const { field, op, value } = where;
51
- if ('is null' === op || 'is not null' === op) return `${field} ${op}`;
52
- if ('in' === op || 'not in' === op) {
53
- if (Array.isArray(value)) return `${field} ${op} (${value.map((v)=>escape(v)).join(', ')})`;
54
- }
55
- if ('between' === op || 'not between' === op) {
56
- if (Array.isArray(value) && 2 === value.length && isStringOrNumber(value[0]) && isStringOrNumber(value[1])) {
57
- const value0 = value[0];
58
- const value1 = value[1];
59
- return `${field} ${op} ${escape(value0)} and ${escape(value1)}`;
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
- if (isStringOrNumber(value)) {
63
- const value0 = value;
64
- return `${field} ${op} ${escape(value0)}`;
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
- let sql = 'SELECT';
71
- if (dsl.select && dsl.select.length > 0) {
72
- const selectFields = dsl.select.map((item)=>{
73
- if ('string' == typeof item) return item;
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
- if (item.func) return `${item.func}(${item.field})` + (item.alias ? ` AS "${item.alias}"` : '');
76
- if (item.alias) return `${item.field} AS "${item.alias}"`;
77
- return item.field;
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
- sql += ` ${selectFields.join(', ')}`;
81
- } else sql += ' *';
82
- sql += ` FROM ${tableName}`;
83
- if (dsl.where) {
84
- const whereClause = applyWhere(dsl.where);
85
- if (whereClause) sql += ` WHERE ${whereClause}`;
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(temporaryStructs) {
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 = temporaryStructs || datasetSchema.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, datasetSchema) {
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 deleteDataset(datasetId) {
521
+ async dropDataset(datasetId) {
434
522
  await this.ensureInitialized();
435
523
  await this.indexedDB.deleteDataset(datasetId);
436
524
  }
437
- async listDatasets() {
525
+ async hasDataset(datasetId) {
438
526
  await this.ensureInitialized();
439
- return this.indexedDB.listDatasets();
527
+ const datasets = await this.indexedDB.listDatasets();
528
+ return datasets.some((item)=>item.datasetId === datasetId);
440
529
  }
441
- async connectDataset(datasetId) {
530
+ async listDatasets() {
442
531
  await this.ensureInitialized();
443
- const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
444
- await dataset.init();
445
- return dataset;
532
+ return this.indexedDB.listDatasets();
446
533
  }
447
- async connectTemporaryDataset(datasetId, temporaryDatasetSchema) {
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(temporaryDatasetSchema);
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
@@ -1,3 +1,4 @@
1
1
  export { VQuery } from './vquery';
2
- export { DataSourceBuilder } from './dataSourceBuilder/dataSourceBuilder';
2
+ export { convertDSLToSQL } from './dataset';
3
+ export { DataSourceBuilder } from './data-source-builder/dataSourceBuilder';
3
4
  export * from './utils';
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
- const isSelectItem = (item)=>'object' == typeof item && 'field' in item;
3
- const isWhereLeaf = (where)=>'field' in where && 'op' in where && 'value' in where;
4
- const isWhereGroup = (where)=>'op' in where && 'conditions' in where;
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
- if (isWhereLeaf(where)) {
16
- const { field, op, value } = where;
17
- if ('is null' === op || 'is not null' === op) return `${field} ${op}`;
18
- if ('in' === op || 'not in' === op) {
19
- if (Array.isArray(value)) return `${field} ${op} (${value.map((v)=>escape(v)).join(', ')})`;
20
- }
21
- if ('between' === op || 'not between' === op) {
22
- if (Array.isArray(value) && 2 === value.length && isStringOrNumber(value[0]) && isStringOrNumber(value[1])) {
23
- const value0 = value[0];
24
- const value1 = value[1];
25
- return `${field} ${op} ${escape(value0)} and ${escape(value1)}`;
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
- if (isStringOrNumber(value)) {
29
- const value0 = value;
30
- return `${field} ${op} ${escape(value0)}`;
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
- let sql = 'SELECT';
37
- if (dsl.select && dsl.select.length > 0) {
38
- const selectFields = dsl.select.map((item)=>{
39
- if ('string' == typeof item) return item;
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
- if (item.func) return `${item.func}(${item.field})` + (item.alias ? ` AS "${item.alias}"` : '');
42
- if (item.alias) return `${item.field} AS "${item.alias}"`;
43
- return item.field;
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
- sql += ` ${selectFields.join(', ')}`;
47
- } else sql += ' *';
48
- sql += ` FROM ${tableName}`;
49
- if (dsl.where) {
50
- const whereClause = applyWhere(dsl.where);
51
- if (whereClause) sql += ` WHERE ${whereClause}`;
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(temporaryStructs) {
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 = temporaryStructs || datasetSchema.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, datasetSchema) {
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 deleteDataset(datasetId) {
485
+ async dropDataset(datasetId) {
399
486
  await this.ensureInitialized();
400
487
  await this.indexedDB.deleteDataset(datasetId);
401
488
  }
402
- async listDatasets() {
489
+ async hasDataset(datasetId) {
403
490
  await this.ensureInitialized();
404
- return this.indexedDB.listDatasets();
491
+ const datasets = await this.indexedDB.listDatasets();
492
+ return datasets.some((item)=>item.datasetId === datasetId);
405
493
  }
406
- async connectDataset(datasetId) {
494
+ async listDatasets() {
407
495
  await this.ensureInitialized();
408
- const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
409
- await dataset.init();
410
- return dataset;
496
+ return this.indexedDB.listDatasets();
411
497
  }
412
- async connectTemporaryDataset(datasetId, temporaryDatasetSchema) {
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(temporaryDatasetSchema);
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 };
@@ -0,0 +1,2 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ export declare const applyGroupBy: <DB, TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, fields?: Array<string>) => SelectQueryBuilder<DB, TB, O>;
@@ -0,0 +1,3 @@
1
+ export { applyWhere } from './where';
2
+ export { applyGroupBy } from './groupBy';
3
+ export { applyLimit } from './limit';
@@ -0,0 +1,2 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ export declare const applyLimit: <DB, TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, limit?: number) => SelectQueryBuilder<DB, TB, O>;
@@ -1,2 +1,3 @@
1
1
  import { Where, WhereClause } from '../../types';
2
- export declare const applyWhere: <T>(where: Where<T> | WhereClause<T>) => string;
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,2 @@
1
+ import { QueryDSL } from '../types';
2
+ export declare const convertDSLToSQL: <T, TableName extends string>(dsl: QueryDSL<T>, tableName: TableName) => string;
@@ -0,0 +1 @@
1
+ export { convertDSLToSQL } from './dslToSQL';
@@ -1,6 +1,8 @@
1
- import { Where, WhereClause, WhereGroup, WhereLeaf } from '../../types';
2
- import { SelectItem } from '../../types/QueryDSL/Select';
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;
@@ -12,7 +12,7 @@ export type WhereLeaf<T> = {
12
12
  } & (O extends 'is null' | 'is not null' ? {
13
13
  value?: never;
14
14
  } : O extends 'in' | 'not in' ? {
15
- value: T[K][];
15
+ value: T[K] | T[K][];
16
16
  } : O extends 'between' | 'not between' ? {
17
17
  value: [T[K], T[K]];
18
18
  } : {
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, datasetSchema: DatasetSchema): Promise<void>;
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
- deleteDataset(datasetId: string): Promise<void>;
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.48",
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": "^0.5.1",
24
+ "@rstest/core": "0.6.5",
24
25
  "@types/node": "^22.18.10",
25
- "@rstest/coverage-istanbul": "^0.0.2",
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
  }
@@ -1,2 +0,0 @@
1
- import { QueryDSL } from '../../types';
2
- export declare const convertDSLToSQL: <T>(dsl: QueryDSL<T>, tableName: string) => string;