tantan-typeorm-gs 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 +21 -0
- package/README.md +31 -0
- package/bun.lock +300 -0
- package/docs/authentication.md +118 -0
- package/docs/configuration.md +680 -0
- package/docs/contributing.md +177 -0
- package/docs/crud.md +351 -0
- package/docs/custom-client.md +165 -0
- package/docs/development.md +243 -0
- package/docs/entities.md +502 -0
- package/docs/find-options.md +296 -0
- package/docs/google-cloud.md +101 -0
- package/docs/installation.md +51 -0
- package/docs/integration-testing.md +178 -0
- package/docs/limitations.md +223 -0
- package/docs/pagination.md +384 -0
- package/docs/performance.md +171 -0
- package/docs/security.md +153 -0
- package/docs/sorting.md +0 -0
- package/docs/supported-features.md +310 -0
- package/docs/synchronization.md +203 -0
- package/package.json +43 -0
- package/src/client/index.ts +589 -0
- package/src/core/data-source.ts +47 -0
- package/src/core/driver.ts +290 -0
- package/src/core/error.ts +144 -0
- package/src/core/memory.ts +152 -0
- package/src/core/query/interpreter.ts +949 -0
- package/src/core/query/runner.ts +1297 -0
- package/src/core/query/types.ts +155 -0
- package/src/core/schema-builder.ts +64 -0
- package/src/core/types.ts +113 -0
- package/src/core/utils.ts +13 -0
- package/src/index.ts +3 -0
- package/tantan-typeorm-gs.code-workspace +8 -0
- package/test/base/0001-data-source.test.ts +252 -0
- package/test/base/0002-operator.test.ts +616 -0
- package/test/base/0003-select.test.ts +281 -0
- package/test/base/0004-aggregate.test.ts +213 -0
- package/test/base/0005-transcation.test.ts +1566 -0
- package/test/base/0006-relation.test.ts +1611 -0
- package/test/base/0007-logging.test.ts +182 -0
- package/test/base/0008-soft-delete.test.ts +649 -0
- package/test/google-sheets/0001-client.test.ts +1705 -0
- package/test/google-sheets/0002-worksheet-management.test.ts +408 -0
- package/test/google-sheets/0003-data-source.test.ts +1935 -0
- package/test/google-sheets/0004-transactions.test.ts +952 -0
- package/test/google-sheets/0005-operator.test.ts +1124 -0
- package/test/google-sheets/0006-object-criteria.test.ts +283 -0
- package/test/google-sheets/0007-performance.test.ts +532 -0
- package/test/public-api.test.ts +34 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ObjectLiteral,
|
|
3
|
+
ReplicationMode,
|
|
4
|
+
Table, TableCheck,
|
|
5
|
+
TableColumn,
|
|
6
|
+
TableExclusion,
|
|
7
|
+
TableForeignKey,
|
|
8
|
+
TableIndex,
|
|
9
|
+
TableUnique,
|
|
10
|
+
View
|
|
11
|
+
} from "typeorm";
|
|
12
|
+
import { BaseQueryRunner } from "typeorm/query-runner/BaseQueryRunner.js";
|
|
13
|
+
import { Broadcaster } from "typeorm/subscriber/Broadcaster.js";
|
|
14
|
+
import type { IsolationLevel } from "typeorm/driver/types/IsolationLevel.js";
|
|
15
|
+
import type { ReadStream } from "typeorm/platform/PlatformTools.js";
|
|
16
|
+
import type { GoogleSheetsDriver } from "../driver.js";
|
|
17
|
+
import { GoogleSheetsQueryInterpreter } from "./interpreter.js";
|
|
18
|
+
import
|
|
19
|
+
{
|
|
20
|
+
GoogleSheetsDuplicatePrimaryKeyError,
|
|
21
|
+
GoogleSheetsGenaralError,
|
|
22
|
+
GoogleSheetsMissingPrimaryKeyError,
|
|
23
|
+
GoogleSheetsParseError,
|
|
24
|
+
GoogleSheetsUnsupportedOperationError
|
|
25
|
+
} from "../error.js";
|
|
26
|
+
import type { JoinCondition, JoinedRow, JoinOperand, JoinQuery } from "./types.js";
|
|
27
|
+
import type { GoogleSheetsRow } from "../types.js";
|
|
28
|
+
|
|
29
|
+
export class GoogleSheetsQueryRunner extends BaseQueryRunner
|
|
30
|
+
{
|
|
31
|
+
private readonly interpreter = new GoogleSheetsQueryInterpreter();
|
|
32
|
+
private distinctParameterValues = new Map<string, unknown[]>();
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly driver: GoogleSheetsDriver,
|
|
36
|
+
mode: ReplicationMode,
|
|
37
|
+
)
|
|
38
|
+
{
|
|
39
|
+
super();
|
|
40
|
+
|
|
41
|
+
this.driver = driver;
|
|
42
|
+
this.dataSource = driver.dataSource;
|
|
43
|
+
this.broadcaster = new Broadcaster(this);
|
|
44
|
+
this.mode = mode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected async loadTables(_tablePaths?: string[]): Promise<Table[]>
|
|
48
|
+
{
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected async loadViews(_tablePaths?: string[]): Promise<View[]>
|
|
53
|
+
{
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async connect(): Promise<any>
|
|
58
|
+
{
|
|
59
|
+
await this.driver.connect();
|
|
60
|
+
|
|
61
|
+
return this.driver.client;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async release(): Promise<void>
|
|
65
|
+
{
|
|
66
|
+
if (this.isReleased) return;
|
|
67
|
+
|
|
68
|
+
this.isReleased = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async query(query: string, parameters?: any[] | ObjectLiteral, useStructuredResult?: boolean): Promise<any>
|
|
72
|
+
{
|
|
73
|
+
try
|
|
74
|
+
{
|
|
75
|
+
if (this.isReleased) throw new GoogleSheetsGenaralError('QueryRunner already released.')
|
|
76
|
+
|
|
77
|
+
this.driver.dataSource.logger.logQuery(query, parameters, this);
|
|
78
|
+
|
|
79
|
+
const normalizedQuery = query.trim().toUpperCase();
|
|
80
|
+
|
|
81
|
+
if (normalizedQuery.startsWith('INSERT INTO')) return this.executeInsert(query, parameters, useStructuredResult);
|
|
82
|
+
if (normalizedQuery.startsWith('UPDATE')) return this.executeUpdate(query, parameters, useStructuredResult);
|
|
83
|
+
if (normalizedQuery.startsWith('DELETE FROM')) return this.executeDelete(query, parameters, useStructuredResult);
|
|
84
|
+
if (/\bWHERE\s+EXISTS\s*\(/i.test(query)) return this.executeExists(query, parameters, useStructuredResult);
|
|
85
|
+
if (/^SELECT\s+DISTINCT\s+"distinctAlias"\./i.test(query.trim()))
|
|
86
|
+
{
|
|
87
|
+
return this.executeDistinctPagination(query, parameters, useStructuredResult);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const from = this.interpreter.parseFrom(query);
|
|
91
|
+
const rows = await this.driver.client.getRows(from.table);
|
|
92
|
+
const joins = this.interpreter.parseJoins(query);
|
|
93
|
+
|
|
94
|
+
let joinedRows: JoinedRow[];
|
|
95
|
+
|
|
96
|
+
if (joins.length > 0) joinedRows = await this.executeJoins(rows, from.alias, joins, parameters);
|
|
97
|
+
else joinedRows = rows.map((row) => ({ [from.alias]: row }));
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* WHERE
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
const where = this.interpreter.parseWhereExpression(query);
|
|
104
|
+
|
|
105
|
+
let filteredRows = joinedRows;
|
|
106
|
+
|
|
107
|
+
if (where)
|
|
108
|
+
{
|
|
109
|
+
filteredRows =
|
|
110
|
+
joinedRows.filter(
|
|
111
|
+
(row) => this.interpreter.evaluateWhereExpression(where, (tableAlias, column) =>
|
|
112
|
+
{
|
|
113
|
+
const value =
|
|
114
|
+
row[tableAlias]?.[column];
|
|
115
|
+
|
|
116
|
+
const metadata =
|
|
117
|
+
this.getEntityMetadata(
|
|
118
|
+
tableAlias === from.alias
|
|
119
|
+
? from.table
|
|
120
|
+
: tableAlias,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const columnMetadata =
|
|
124
|
+
metadata?.columns.find(
|
|
125
|
+
(item) => item.databaseName === column,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (!columnMetadata)
|
|
129
|
+
{
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return this.driver.prepareHydratedValue(
|
|
134
|
+
value,
|
|
135
|
+
columnMetadata,
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
(parameterName) =>
|
|
139
|
+
{
|
|
140
|
+
/**
|
|
141
|
+
* SQL numeric literal:
|
|
142
|
+
*
|
|
143
|
+
* IN (1)
|
|
144
|
+
* IN (10)
|
|
145
|
+
* IN (-1)
|
|
146
|
+
* IN (1.5)
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
if (/^-?\d+(?:\.\d+)?$/.test(parameterName)) return Number(parameterName);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* SQL string literal:
|
|
153
|
+
*
|
|
154
|
+
* IN ('Basuni')
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
if (/^'.*'$/.test(parameterName)) return parameterName.slice(1, -1);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* ORM parameter:
|
|
161
|
+
*
|
|
162
|
+
* :orm_param_0
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
const spreadParameterMatch = parameterName.match(/^:?\.\.\.(.+)$/);
|
|
166
|
+
|
|
167
|
+
if (spreadParameterMatch?.[1])
|
|
168
|
+
{
|
|
169
|
+
const values = this.distinctParameterValues.get(spreadParameterMatch[1]);
|
|
170
|
+
|
|
171
|
+
if (values !== undefined) return values;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const parameterIndex = Number(parameterName.match(/\d+$/)?.[0] ?? -1);
|
|
175
|
+
|
|
176
|
+
if (Array.isArray(parameters)) return parameters[parameterIndex];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Named parameters.
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
if (parameters && typeof parameters === 'object') return parameters[parameterName];
|
|
183
|
+
|
|
184
|
+
return undefined;
|
|
185
|
+
},
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* AGGREGATE
|
|
192
|
+
*/
|
|
193
|
+
const aggregates = this.interpreter.parseAggregates(query);
|
|
194
|
+
|
|
195
|
+
if (aggregates.length > 0)
|
|
196
|
+
{
|
|
197
|
+
const record: Record<string, unknown> = {};
|
|
198
|
+
|
|
199
|
+
for (const aggregate of aggregates)
|
|
200
|
+
{
|
|
201
|
+
record[aggregate.alias] = this.interpreter.evaluateAggregate(filteredRows, aggregate);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result = {
|
|
205
|
+
raw: [record],
|
|
206
|
+
records: [record],
|
|
207
|
+
affected: undefined,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (useStructuredResult) return result;
|
|
211
|
+
|
|
212
|
+
return result.raw;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* SELECT
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
const columns = this.interpreter.parseSelectColumns(query);
|
|
220
|
+
|
|
221
|
+
let records = filteredRows
|
|
222
|
+
.map(
|
|
223
|
+
(row) =>
|
|
224
|
+
{
|
|
225
|
+
const record: Record<string, unknown> = {};
|
|
226
|
+
|
|
227
|
+
for (const column of columns)
|
|
228
|
+
{
|
|
229
|
+
/**
|
|
230
|
+
* Literal:
|
|
231
|
+
*
|
|
232
|
+
* 1 AS "count"
|
|
233
|
+
*/
|
|
234
|
+
if (/^\d+$/.test(column.column))
|
|
235
|
+
{
|
|
236
|
+
record[column.alias] = Number(column.column);
|
|
237
|
+
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
record[column.alias] = row[column.tableAlias]?.[column.column];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return record;
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* ORDER BY
|
|
250
|
+
*/
|
|
251
|
+
const orderBy = this.interpreter.parseOrderBy(query);
|
|
252
|
+
|
|
253
|
+
if (orderBy.length > 0)
|
|
254
|
+
{
|
|
255
|
+
records.sort((left, right) =>
|
|
256
|
+
{
|
|
257
|
+
for (const order of orderBy)
|
|
258
|
+
{
|
|
259
|
+
const key = `${order.tableAlias}_${order.column}`;
|
|
260
|
+
const leftValue = left[key];
|
|
261
|
+
const rightValue = right[key];
|
|
262
|
+
|
|
263
|
+
if (leftValue === rightValue) continue;
|
|
264
|
+
if (leftValue === undefined || leftValue === null) return order.direction === 'ASC' ? -1 : 1;
|
|
265
|
+
if (rightValue === undefined || rightValue === null) return order.direction === 'ASC' ? 1 : -1;
|
|
266
|
+
|
|
267
|
+
const comparison = leftValue < rightValue ? -1 : 1;
|
|
268
|
+
|
|
269
|
+
return order.direction === 'ASC' ? comparison : -comparison;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return 0;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* LIMIT / OFFSET
|
|
278
|
+
*/
|
|
279
|
+
const pagination = this.interpreter.parsePagination(query);
|
|
280
|
+
|
|
281
|
+
if (pagination.offset !== undefined) records = records.slice(pagination.offset);
|
|
282
|
+
if (pagination.limit !== undefined) records = records.slice(0, pagination.limit);
|
|
283
|
+
|
|
284
|
+
const result = {
|
|
285
|
+
raw: records,
|
|
286
|
+
records,
|
|
287
|
+
affected: undefined,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (useStructuredResult) return result;
|
|
291
|
+
|
|
292
|
+
return result.raw;
|
|
293
|
+
} catch (error)
|
|
294
|
+
{
|
|
295
|
+
this.driver.dataSource.logger.logQueryError(
|
|
296
|
+
error instanceof Error ? error : String(error),
|
|
297
|
+
query,
|
|
298
|
+
parameters,
|
|
299
|
+
this,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private extractSheetName(sql: string): string
|
|
307
|
+
{
|
|
308
|
+
const match = sql.match(/(?:FROM|INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+"([^"]+)"/i);
|
|
309
|
+
|
|
310
|
+
if (!match)
|
|
311
|
+
{
|
|
312
|
+
throw new GoogleSheetsGenaralError(`Unable to determine Google Sheets worksheet from query: ${sql}`)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return match[1]!;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private getEntityMetadata(tableName: string)
|
|
319
|
+
{
|
|
320
|
+
return this.connection.entityMetadatas.find(
|
|
321
|
+
(metadata) => metadata.tableName === tableName || metadata.name === tableName
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private resolveParameter(value: string, parameters?: any[] | Record<string, any>): unknown
|
|
326
|
+
{
|
|
327
|
+
const parameterMatch = value.match(/^:(.+)$/);
|
|
328
|
+
|
|
329
|
+
if (!parameterMatch?.[1]) throw new GoogleSheetsGenaralError(`Unsupported parameter value: ${value}`);
|
|
330
|
+
|
|
331
|
+
const parameterName = parameterMatch[1];
|
|
332
|
+
|
|
333
|
+
if (Array.isArray(parameters))
|
|
334
|
+
{
|
|
335
|
+
const parameterIndex = parameterName.match(/^orm_param_(\d+)$/);
|
|
336
|
+
|
|
337
|
+
if (!parameterIndex?.[1]) throw new GoogleSheetsGenaralError(`Unable to resolve parameter: ${value}`);
|
|
338
|
+
|
|
339
|
+
return parameters[Number(parameterIndex[1])];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (parameters) return parameters[parameterName];
|
|
343
|
+
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private async assignIncrementGeneratedIds(sheetName: string, rows: GoogleSheetsRow[], columnName: string)
|
|
348
|
+
: Promise<void>
|
|
349
|
+
{
|
|
350
|
+
const existingRows = await this.driver.client.getRows(sheetName);
|
|
351
|
+
|
|
352
|
+
let nextId = existingRows.reduce((max, currentRow) =>
|
|
353
|
+
{
|
|
354
|
+
const value = Number(currentRow[columnName]);
|
|
355
|
+
|
|
356
|
+
return Number.isFinite(value) ? Math.max(max, value) : max;
|
|
357
|
+
}, 0) + 1;
|
|
358
|
+
|
|
359
|
+
for (const row of rows) row[columnName] = nextId++;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private assignUuidGeneratedIds(rows: GoogleSheetsRow[], columnName: string): void
|
|
363
|
+
{
|
|
364
|
+
for (const row of rows) row[columnName] = crypto.randomUUID();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private async executeInsert(query: string, parameters?: any[] | Record<string, any>, useStructuredResult = false)
|
|
368
|
+
: Promise<any>
|
|
369
|
+
{
|
|
370
|
+
const sheetName = this.extractSheetName(query);
|
|
371
|
+
|
|
372
|
+
const insert = this.interpreter.parseInsertQuery(query);
|
|
373
|
+
|
|
374
|
+
const rows: Record<string, unknown>[] = [];
|
|
375
|
+
|
|
376
|
+
for (const values of insert.values)
|
|
377
|
+
{
|
|
378
|
+
const row: Record<string, unknown> = {};
|
|
379
|
+
|
|
380
|
+
for (let index = 0; index < insert.columns.length; index++)
|
|
381
|
+
{
|
|
382
|
+
const column = insert.columns[index];
|
|
383
|
+
const value = values[index];
|
|
384
|
+
|
|
385
|
+
if (column === undefined || value === undefined)
|
|
386
|
+
{
|
|
387
|
+
throw new GoogleSheetsGenaralError('INSERT columns and values are inconsistent.');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (value.trim().toUpperCase() === 'DEFAULT')
|
|
391
|
+
{
|
|
392
|
+
row[column] = undefined;
|
|
393
|
+
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
row[column] = this.resolveParameter(value, parameters);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
rows.push(row);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const metadata = this.getEntityMetadata(sheetName);
|
|
404
|
+
|
|
405
|
+
if (metadata)
|
|
406
|
+
{
|
|
407
|
+
const now = new Date();
|
|
408
|
+
|
|
409
|
+
for (const row of rows)
|
|
410
|
+
{
|
|
411
|
+
// Resolve generated/default values first.
|
|
412
|
+
for (const column of metadata.columns)
|
|
413
|
+
{
|
|
414
|
+
if (row[column.databaseName] !== undefined) continue;
|
|
415
|
+
|
|
416
|
+
if (column.isCreateDate || column.isUpdateDate)
|
|
417
|
+
{
|
|
418
|
+
row[column.databaseName] = now;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (column.default !== undefined)
|
|
423
|
+
{
|
|
424
|
+
row[column.databaseName] = typeof column.default === 'function'
|
|
425
|
+
? column.default()
|
|
426
|
+
: column.default;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Validate column constraints.
|
|
431
|
+
for (const column of metadata.columns)
|
|
432
|
+
{
|
|
433
|
+
const value = row[column.databaseName];
|
|
434
|
+
|
|
435
|
+
if (value !== undefined && value !== null) continue;
|
|
436
|
+
|
|
437
|
+
if (column.isPrimary && !column.isGenerated)
|
|
438
|
+
{
|
|
439
|
+
throw new GoogleSheetsMissingPrimaryKeyError();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (column.isGenerated) continue;
|
|
443
|
+
|
|
444
|
+
if (column.isNullable) continue;
|
|
445
|
+
|
|
446
|
+
throw new GoogleSheetsGenaralError(`Column "${column.databaseName}" cannot be null.`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const generatedColumn = metadata?.columns.find((column) => column.isPrimary && column.isGenerated);
|
|
452
|
+
|
|
453
|
+
const explicitPrimaryKeyRows = generatedColumn
|
|
454
|
+
? rows.filter((row) => row[generatedColumn.databaseName] !== undefined)
|
|
455
|
+
: [];
|
|
456
|
+
|
|
457
|
+
if (generatedColumn)
|
|
458
|
+
{
|
|
459
|
+
const generationStrategy = generatedColumn.generationStrategy as
|
|
460
|
+
| 'increment'
|
|
461
|
+
| 'uuid'
|
|
462
|
+
| 'identity'
|
|
463
|
+
| 'rowid'
|
|
464
|
+
| undefined;
|
|
465
|
+
|
|
466
|
+
switch (generationStrategy)
|
|
467
|
+
{
|
|
468
|
+
case 'increment':
|
|
469
|
+
{
|
|
470
|
+
const generatedRows = rows.filter((row) => !explicitPrimaryKeyRows.includes(row));
|
|
471
|
+
|
|
472
|
+
await this.assignIncrementGeneratedIds(sheetName, generatedRows, generatedColumn.databaseName);
|
|
473
|
+
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
case 'uuid':
|
|
478
|
+
{
|
|
479
|
+
const generatedRows = rows.filter((row) => !explicitPrimaryKeyRows.includes(row));
|
|
480
|
+
|
|
481
|
+
this.assignUuidGeneratedIds(generatedRows, generatedColumn.databaseName);
|
|
482
|
+
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case 'identity':
|
|
487
|
+
throw new GoogleSheetsGenaralError(
|
|
488
|
+
'PrimaryGeneratedColumn("identity") is not supported by Google Sheets driver.'
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
case 'rowid':
|
|
492
|
+
throw new GoogleSheetsGenaralError(
|
|
493
|
+
'PrimaryGeneratedColumn("rowid") is not supported by Google Sheets driver.'
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
default:
|
|
497
|
+
throw new GoogleSheetsGenaralError(
|
|
498
|
+
`Unsupported generation strategy: ${String(generatedColumn.generationStrategy)}`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (explicitPrimaryKeyRows.length > 0)
|
|
504
|
+
{
|
|
505
|
+
const existingRows = await this.driver.client.getRows(sheetName);
|
|
506
|
+
|
|
507
|
+
for (const row of explicitPrimaryKeyRows)
|
|
508
|
+
{
|
|
509
|
+
const duplicate =
|
|
510
|
+
existingRows.some(
|
|
511
|
+
(existingRow) => existingRow[generatedColumn!.databaseName] === row[generatedColumn!.databaseName]
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
if (duplicate)
|
|
515
|
+
{
|
|
516
|
+
throw new GoogleSheetsDuplicatePrimaryKeyError();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
await this.driver.client.insertRows(sheetName, rows);
|
|
522
|
+
|
|
523
|
+
const result = {
|
|
524
|
+
raw: rows,
|
|
525
|
+
records: rows,
|
|
526
|
+
affected: rows.length,
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
if (useStructuredResult) return result;
|
|
530
|
+
|
|
531
|
+
return result.raw;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private async executeUpdate(query: string, parameters?: any[] | Record<string, any>, useStructuredResult = false)
|
|
535
|
+
: Promise<any>
|
|
536
|
+
{
|
|
537
|
+
const update = this.interpreter.parseUpdateQuery(query);
|
|
538
|
+
|
|
539
|
+
const updateValues: Record<string, any> = {};
|
|
540
|
+
|
|
541
|
+
for (const assignment of update.assignments)
|
|
542
|
+
{
|
|
543
|
+
if (assignment.value.toUpperCase() === 'CURRENT_TIMESTAMP')
|
|
544
|
+
{
|
|
545
|
+
updateValues[assignment.column] = new Date();
|
|
546
|
+
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (assignment.value.toUpperCase() === 'NULL')
|
|
551
|
+
{
|
|
552
|
+
updateValues[assignment.column] = null;
|
|
553
|
+
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
updateValues[assignment.column] = this.resolveParameter(assignment.value, parameters);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
const affected = await this.driver.client.updateRows(update.table, (row) =>
|
|
562
|
+
{
|
|
563
|
+
const result = this.interpreter.evaluateWhereExpression(
|
|
564
|
+
update.where,
|
|
565
|
+
(_tableAlias, column) => row[column],
|
|
566
|
+
(parameter) => this.resolveParameter(parameter, parameters)
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
return result;
|
|
570
|
+
},
|
|
571
|
+
updateValues,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const result = {
|
|
575
|
+
raw: [],
|
|
576
|
+
records: [],
|
|
577
|
+
affected,
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
if (useStructuredResult) return result;
|
|
581
|
+
|
|
582
|
+
return result.raw;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private async executeDelete(query: string, parameters?: any[] | Record<string, any>, useStructuredResult = false)
|
|
586
|
+
: Promise<any>
|
|
587
|
+
{
|
|
588
|
+
const deletion = this.interpreter.parseDeleteQuery(query);
|
|
589
|
+
|
|
590
|
+
const affected = await this.driver.client.deleteRows(deletion.table,
|
|
591
|
+
(row) =>
|
|
592
|
+
this.interpreter.evaluateWhereExpression(deletion.where,
|
|
593
|
+
(_tableAlias, column) => row[column],
|
|
594
|
+
(parameter) => this.resolveParameter(parameter, parameters)
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const result = {
|
|
599
|
+
raw: [],
|
|
600
|
+
records: [],
|
|
601
|
+
affected,
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
if (useStructuredResult) return result;
|
|
605
|
+
|
|
606
|
+
return result.raw;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private async executeExists(query: string, parameters?: any[] | Record<string, any>, useStructuredResult = false)
|
|
610
|
+
: Promise<any>
|
|
611
|
+
{
|
|
612
|
+
const existsMatch = query.match(
|
|
613
|
+
/WHERE\s+EXISTS\s*\(\s*SELECT\s+1\s+FROM\s+"([^"]+)"\s+"([^"]+)"\s+WHERE\s+\(\((.+?)\)\)\s*\)\s+LIMIT\s+1/i,
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
if (!existsMatch?.[1] || !existsMatch[2] || !existsMatch[3])
|
|
617
|
+
{
|
|
618
|
+
throw new GoogleSheetsGenaralError(`Unable to parse EXISTS query: ${query}`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const sheetName = existsMatch[1];
|
|
622
|
+
const alias = existsMatch[2];
|
|
623
|
+
const whereQuery = existsMatch[3];
|
|
624
|
+
|
|
625
|
+
const rows = await this.driver.client.getRows(sheetName);
|
|
626
|
+
|
|
627
|
+
const where = this.interpreter.parseWhereExpression(`SELECT * FROM "${sheetName}" "${alias}" WHERE ${whereQuery}`);
|
|
628
|
+
|
|
629
|
+
if (!where) throw new GoogleSheetsParseError(`Unable to parse EXISTS WHERE expression: ${whereQuery}`)
|
|
630
|
+
|
|
631
|
+
const exists = rows.some((row) =>
|
|
632
|
+
this.interpreter.evaluateWhereExpression(where,
|
|
633
|
+
(column) => row[column],
|
|
634
|
+
(parameterName) =>
|
|
635
|
+
{
|
|
636
|
+
const parameterIndex = Number(parameterName.match(/\d+$/)?.[0] ?? -1);
|
|
637
|
+
|
|
638
|
+
return Array.isArray(parameters) ? parameters[parameterIndex] : undefined;
|
|
639
|
+
})
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const records = exists ? [{ row_exists: 1 }] : [];
|
|
643
|
+
|
|
644
|
+
const result = {
|
|
645
|
+
raw: records,
|
|
646
|
+
records,
|
|
647
|
+
affected: undefined,
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
if (useStructuredResult) return result;
|
|
651
|
+
|
|
652
|
+
return result.raw;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private async executeJoins(
|
|
656
|
+
baseRows: GoogleSheetsRow[],
|
|
657
|
+
baseAlias: string,
|
|
658
|
+
joins: JoinQuery[],
|
|
659
|
+
parameters?: any[] | Record<string, any>,
|
|
660
|
+
): Promise<JoinedRow[]>
|
|
661
|
+
{
|
|
662
|
+
let joinedRows: JoinedRow[] = baseRows.map((row) => ({ [baseAlias]: row }));
|
|
663
|
+
|
|
664
|
+
for (const join of joins)
|
|
665
|
+
{
|
|
666
|
+
const joinedData = await this.driver.client.getRows(join.table);
|
|
667
|
+
|
|
668
|
+
joinedRows = joinedRows.flatMap((joinedRow) =>
|
|
669
|
+
{
|
|
670
|
+
const matches = joinedData.filter(
|
|
671
|
+
(candidate) => join.conditions.every(
|
|
672
|
+
(condition) => this.evaluateJoinCondition(condition, joinedRow, candidate, join.alias, parameters)
|
|
673
|
+
),
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
if (matches.length === 0)
|
|
677
|
+
{
|
|
678
|
+
if (join.type === 'LEFT') return [{ ...joinedRow, [join.alias]: undefined }];
|
|
679
|
+
|
|
680
|
+
return [];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return matches.map((candidate) => ({ ...joinedRow, [join.alias]: candidate }));
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return joinedRows;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private evaluateJoinCondition(
|
|
691
|
+
condition: JoinCondition,
|
|
692
|
+
joinedRow: JoinedRow,
|
|
693
|
+
candidate: GoogleSheetsRow,
|
|
694
|
+
joinedAlias: string,
|
|
695
|
+
parameters?: any[] | Record<string, any>,
|
|
696
|
+
): boolean
|
|
697
|
+
{
|
|
698
|
+
const resolveOperand = (operand: JoinOperand): unknown =>
|
|
699
|
+
{
|
|
700
|
+
if (operand.type === 'parameter') return this.resolveParameter(operand.value, parameters);
|
|
701
|
+
if (operand.tableAlias === joinedAlias) return candidate[operand.column];
|
|
702
|
+
|
|
703
|
+
return joinedRow[operand.tableAlias]?.[operand.column];
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const left = resolveOperand(condition.left);
|
|
707
|
+
|
|
708
|
+
if (condition.operator === 'IS NULL') return left === null || left === undefined;
|
|
709
|
+
|
|
710
|
+
if (condition.operator === 'IS NOT NULL') return left !== null && left !== undefined;
|
|
711
|
+
|
|
712
|
+
const right = resolveOperand(condition.right!);
|
|
713
|
+
|
|
714
|
+
return left === right;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private async executeDistinctPagination(
|
|
718
|
+
query: string, parameters?: any[] | Record<string, any>, useStructuredResult = false)
|
|
719
|
+
: Promise<any>
|
|
720
|
+
{
|
|
721
|
+
/**
|
|
722
|
+
* Extract inner query:
|
|
723
|
+
*
|
|
724
|
+
* FROM (
|
|
725
|
+
* SELECT ...
|
|
726
|
+
* ) "distinctAlias"
|
|
727
|
+
*/
|
|
728
|
+
|
|
729
|
+
const subqueryMatch = query.match(/\bFROM\s*\((SELECT[\s\S]+)\)\s+"distinctAlias"/i);
|
|
730
|
+
|
|
731
|
+
if (!subqueryMatch?.[1]) throw new GoogleSheetsParseError('Unable to parse DISTINCT pagination subquery.');
|
|
732
|
+
|
|
733
|
+
const innerQuery = subqueryMatch[1];
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Execute inner query normally.
|
|
737
|
+
*
|
|
738
|
+
* Example result:
|
|
739
|
+
*
|
|
740
|
+
* [
|
|
741
|
+
* {
|
|
742
|
+
* user_id: 1,
|
|
743
|
+
* user_name: 'Basuni',
|
|
744
|
+
* post_id: 1,
|
|
745
|
+
* },
|
|
746
|
+
* {
|
|
747
|
+
* user_id: 1,
|
|
748
|
+
* user_name: 'Basuni',
|
|
749
|
+
* post_id: 2,
|
|
750
|
+
* },
|
|
751
|
+
* {
|
|
752
|
+
* user_id: 2,
|
|
753
|
+
* user_name: 'Budi',
|
|
754
|
+
* post_id: 3,
|
|
755
|
+
* },
|
|
756
|
+
* ]
|
|
757
|
+
*/
|
|
758
|
+
|
|
759
|
+
const innerResult = await this.query(innerQuery, parameters, true);
|
|
760
|
+
const innerRows = innerResult.records as Record<string, unknown>[];
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Parse outer SELECT.
|
|
764
|
+
*
|
|
765
|
+
* Example:
|
|
766
|
+
*
|
|
767
|
+
* SELECT DISTINCT
|
|
768
|
+
* "distinctAlias"."user_id" AS "ids_user_id",
|
|
769
|
+
* "distinctAlias"."user_id",
|
|
770
|
+
* "distinctAlias"."post_id"
|
|
771
|
+
*/
|
|
772
|
+
|
|
773
|
+
const selectMatch = query.match(/^SELECT\s+DISTINCT\s+([\s\S]+?)\s+FROM\s*\(/i);
|
|
774
|
+
|
|
775
|
+
if (!selectMatch?.[1]) throw new GoogleSheetsParseError('Unable to parse DISTINCT pagination SELECT.');
|
|
776
|
+
|
|
777
|
+
const selectExpressions = selectMatch[1].split(',').map((expression) => expression.trim());
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Parse selected columns.
|
|
781
|
+
*/
|
|
782
|
+
|
|
783
|
+
const selectedColumns = selectExpressions.map((expression) =>
|
|
784
|
+
{
|
|
785
|
+
const match = expression.match(/^"distinctAlias"\."([^"]+)"(?:\s+AS\s+"([^"]+)")?$/i);
|
|
786
|
+
|
|
787
|
+
if (!match?.[1])
|
|
788
|
+
{
|
|
789
|
+
throw new GoogleSheetsParseError(`Unable to parse DISTINCT pagination expression: ${expression}`);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
column: match[1],
|
|
794
|
+
alias: match[2] ?? match[1],
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* TypeORM names the primary ID column:
|
|
800
|
+
*
|
|
801
|
+
* ids_<column>
|
|
802
|
+
*
|
|
803
|
+
* Example:
|
|
804
|
+
*
|
|
805
|
+
* user_id
|
|
806
|
+
* ↓
|
|
807
|
+
* ids_user_id
|
|
808
|
+
*
|
|
809
|
+
* Find the entity ID column from the outer SELECT.
|
|
810
|
+
*/
|
|
811
|
+
|
|
812
|
+
const primaryColumn = selectedColumns.find((selected) => selected.alias.startsWith('ids_'));
|
|
813
|
+
|
|
814
|
+
if (!primaryColumn) throw new GoogleSheetsParseError('Unable to determine DISTINCT pagination primary column.');
|
|
815
|
+
|
|
816
|
+
const from = this.interpreter.parseFrom(query);
|
|
817
|
+
|
|
818
|
+
const metadata = this.getEntityMetadata(from.table);
|
|
819
|
+
|
|
820
|
+
const primaryColumnMetadata =
|
|
821
|
+
metadata?.columns.find(
|
|
822
|
+
(column) => column.databaseName === primaryColumn.column.replace(`${from.alias}_`, ''),
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Convert inner rows into outer SELECT records.
|
|
827
|
+
*
|
|
828
|
+
* Example:
|
|
829
|
+
*
|
|
830
|
+
* inner row:
|
|
831
|
+
*
|
|
832
|
+
* {
|
|
833
|
+
* user_id: 1,
|
|
834
|
+
* post_id: 1
|
|
835
|
+
* }
|
|
836
|
+
*
|
|
837
|
+
* becomes:
|
|
838
|
+
*
|
|
839
|
+
* {
|
|
840
|
+
* ids_user_id: 1,
|
|
841
|
+
* user_id: 1,
|
|
842
|
+
* post_id: 1
|
|
843
|
+
* }
|
|
844
|
+
*/
|
|
845
|
+
|
|
846
|
+
const records = innerRows.map((row) =>
|
|
847
|
+
{
|
|
848
|
+
const record: Record<string, unknown> = {};
|
|
849
|
+
|
|
850
|
+
for (const selected of selectedColumns) record[selected.alias] = row[selected.column];
|
|
851
|
+
|
|
852
|
+
return record;
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* DISTINCT by primary/entity ID.
|
|
857
|
+
*
|
|
858
|
+
* IMPORTANT:
|
|
859
|
+
*
|
|
860
|
+
* Do NOT use the whole record here.
|
|
861
|
+
*
|
|
862
|
+
* With:
|
|
863
|
+
*
|
|
864
|
+
* user post
|
|
865
|
+
* 1 1
|
|
866
|
+
* 1 2
|
|
867
|
+
* 2 3
|
|
868
|
+
*
|
|
869
|
+
* DISTINCT must produce:
|
|
870
|
+
*
|
|
871
|
+
* 1
|
|
872
|
+
* 2
|
|
873
|
+
*
|
|
874
|
+
* rather than treating:
|
|
875
|
+
*
|
|
876
|
+
* (1, 1)
|
|
877
|
+
* (1, 2)
|
|
878
|
+
*
|
|
879
|
+
* as two separate entities.
|
|
880
|
+
*/
|
|
881
|
+
|
|
882
|
+
const distinctRecords: Record<string, unknown>[] = [];
|
|
883
|
+
const seen = new Set<string>();
|
|
884
|
+
|
|
885
|
+
for (const record of records)
|
|
886
|
+
{
|
|
887
|
+
const primaryValue = record[primaryColumn.alias];
|
|
888
|
+
const key = JSON.stringify(primaryValue);
|
|
889
|
+
|
|
890
|
+
if (seen.has(key)) continue;
|
|
891
|
+
|
|
892
|
+
seen.add(key);
|
|
893
|
+
distinctRecords.push(record);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const distinctValues = distinctRecords.map(
|
|
897
|
+
(record) =>
|
|
898
|
+
{
|
|
899
|
+
const value = record[primaryColumn.alias];
|
|
900
|
+
|
|
901
|
+
if (!primaryColumnMetadata) return value;
|
|
902
|
+
|
|
903
|
+
return this.driver.prepareHydratedValue(value, primaryColumnMetadata);
|
|
904
|
+
},
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
this.distinctParameterValues.set('orm_distinct_ids', distinctValues);
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* ORDER BY
|
|
911
|
+
*
|
|
912
|
+
* Example:
|
|
913
|
+
*
|
|
914
|
+
* ORDER BY
|
|
915
|
+
* "distinctAlias"."user_id" ASC,
|
|
916
|
+
* "distinctAlias"."post_id" ASC,
|
|
917
|
+
* "user_id" ASC
|
|
918
|
+
*/
|
|
919
|
+
|
|
920
|
+
const orderByMatch = query.match(/\bORDER\s+BY\s+([\s\S]+?)(?:\s+LIMIT\s+\d+|\s+OFFSET\s+\d+|$)/i);
|
|
921
|
+
|
|
922
|
+
if (orderByMatch?.[1])
|
|
923
|
+
{
|
|
924
|
+
const expressions = orderByMatch[1].split(',').map((expression) => expression.trim());
|
|
925
|
+
|
|
926
|
+
distinctRecords.sort((left, right) =>
|
|
927
|
+
{
|
|
928
|
+
for (const expression of expressions)
|
|
929
|
+
{
|
|
930
|
+
/**
|
|
931
|
+
* Qualified:
|
|
932
|
+
*
|
|
933
|
+
* "distinctAlias"."user_id" ASC
|
|
934
|
+
*/
|
|
935
|
+
|
|
936
|
+
const qualifiedMatch = expression.match(/^"distinctAlias"\."([^"]+)"(?:\s+(ASC|DESC))?$/i);
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Unqualified:
|
|
940
|
+
*
|
|
941
|
+
* "user_id" ASC
|
|
942
|
+
*/
|
|
943
|
+
|
|
944
|
+
const unqualifiedMatch = expression.match(/^"([^"]+)"(?:\s+(ASC|DESC))?$/i);
|
|
945
|
+
const column = qualifiedMatch?.[1] ?? unqualifiedMatch?.[1];
|
|
946
|
+
|
|
947
|
+
if (!column) continue;
|
|
948
|
+
|
|
949
|
+
const direction = (qualifiedMatch?.[2] ?? unqualifiedMatch?.[2] ?? 'ASC').toUpperCase();
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Outer SELECT may have either:
|
|
953
|
+
*
|
|
954
|
+
* user_id
|
|
955
|
+
*
|
|
956
|
+
* or:
|
|
957
|
+
*
|
|
958
|
+
* ids_user_id
|
|
959
|
+
*/
|
|
960
|
+
|
|
961
|
+
const leftValue = left[column] ?? left[`ids_${column}`];
|
|
962
|
+
const rightValue = right[column] ?? right[`ids_${column}`];
|
|
963
|
+
|
|
964
|
+
if (leftValue === rightValue) continue;
|
|
965
|
+
if (leftValue === undefined || leftValue === null) return direction === 'ASC' ? -1 : 1;
|
|
966
|
+
if (rightValue === undefined || rightValue === null) return direction === 'ASC' ? 1 : -1;
|
|
967
|
+
|
|
968
|
+
const comparison = leftValue < rightValue ? -1 : 1;
|
|
969
|
+
|
|
970
|
+
return direction === 'ASC' ? comparison : -comparison;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return 0;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* OFFSET
|
|
979
|
+
*/
|
|
980
|
+
|
|
981
|
+
const offsetMatch = query.match(/\bOFFSET\s+(\d+)/i);
|
|
982
|
+
|
|
983
|
+
if (offsetMatch?.[1]) distinctRecords.splice(0, Number(offsetMatch[1]));
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* LIMIT
|
|
987
|
+
*/
|
|
988
|
+
|
|
989
|
+
const limitMatch = query.match(/\bLIMIT\s+(\d+)/i);
|
|
990
|
+
|
|
991
|
+
if (limitMatch?.[1]) distinctRecords.splice(Number(limitMatch[1]));
|
|
992
|
+
|
|
993
|
+
const result = {
|
|
994
|
+
raw: distinctRecords,
|
|
995
|
+
records: distinctRecords,
|
|
996
|
+
affected: undefined,
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
if (useStructuredResult) return result;
|
|
1000
|
+
|
|
1001
|
+
return result.raw;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
async stream(_query: string, parameters?: any[], _onEnd?: Function, _onError?: Function): Promise<ReadStream>
|
|
1005
|
+
{
|
|
1006
|
+
return this.unsupported('stream')
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
async startTransaction(_isolationLevel?: IsolationLevel): Promise<void>
|
|
1010
|
+
{
|
|
1011
|
+
return this.unsupported('transactions')
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async commitTransaction(): Promise<void>
|
|
1015
|
+
{
|
|
1016
|
+
return this.unsupported('transactions')
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
async rollbackTransaction(): Promise<void>
|
|
1020
|
+
{
|
|
1021
|
+
return this.unsupported('transactions')
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async clearDatabase(_database?: string): Promise<void>
|
|
1025
|
+
{
|
|
1026
|
+
return this.unsupported('clear-database')
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async getDatabases(): Promise<string[]>
|
|
1030
|
+
{
|
|
1031
|
+
return [];
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
async getSchemas(_database?: string): Promise<string[]>
|
|
1035
|
+
{
|
|
1036
|
+
return [];
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
async hasDatabase(_database: string): Promise<boolean>
|
|
1040
|
+
{
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async getCurrentDatabase(): Promise<string | undefined>
|
|
1045
|
+
{
|
|
1046
|
+
return undefined;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
async hasSchema(_schema: string): Promise<boolean>
|
|
1050
|
+
{
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async getCurrentSchema(): Promise<string | undefined>
|
|
1055
|
+
{
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async hasTable(_table: Table | string): Promise<boolean>
|
|
1060
|
+
{
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
async hasColumn(_table: Table | string, _columnName: string): Promise<boolean>
|
|
1065
|
+
{
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
async createDatabase(_database: string, _ifNotExists?: boolean): Promise<void>
|
|
1070
|
+
{
|
|
1071
|
+
return this.unsupported('create-database');
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
async dropDatabase(_database: string, _ifExists?: boolean): Promise<void>
|
|
1075
|
+
{
|
|
1076
|
+
return this.unsupported('drop-databse');
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
async createSchema(_schemaPath: string, _ifNotExists?: boolean): Promise<void>
|
|
1080
|
+
{
|
|
1081
|
+
return this.unsupported('create-schema');
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async dropSchema(
|
|
1085
|
+
_schemaPath: string,
|
|
1086
|
+
_ifExists?: boolean,
|
|
1087
|
+
_isCascade?: boolean,
|
|
1088
|
+
): Promise<void>
|
|
1089
|
+
{
|
|
1090
|
+
return this.unsupported('drop-schema');
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
async createTable(
|
|
1094
|
+
_table: Table,
|
|
1095
|
+
_ifNotExists?: boolean,
|
|
1096
|
+
_createForeignKeys?: boolean,
|
|
1097
|
+
_createIndices?: boolean,
|
|
1098
|
+
): Promise<void>
|
|
1099
|
+
{
|
|
1100
|
+
return this.unsupported('create-table');
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
async dropTable(_table: Table | string, _ifExists?: boolean, _dropForeignKeys?: boolean, _dropIndices?: boolean)
|
|
1104
|
+
: Promise<void>
|
|
1105
|
+
{
|
|
1106
|
+
return this.unsupported('drop-table');
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async createView(_view: View, _syncWithMetadata?: boolean, _oldView?: View): Promise<void>
|
|
1110
|
+
{
|
|
1111
|
+
return this.unsupported('create-view');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
async dropView(_view: View | string, _ifExists?: boolean): Promise<void>
|
|
1115
|
+
{
|
|
1116
|
+
return this.unsupported('drop-view');
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
async renameTable(_oldTableOrName: Table | string, _newTableName: string): Promise<void>
|
|
1120
|
+
{
|
|
1121
|
+
return this.unsupported('rename-table');
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
async changeTableComment(_tableOrName: Table | string, _comment?: string): Promise<void>
|
|
1125
|
+
{
|
|
1126
|
+
return this.unsupported('change=table-comment');
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
async addColumn(_table: Table | string, _column: TableColumn): Promise<void>
|
|
1130
|
+
{
|
|
1131
|
+
return this.unsupported('add-column');
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async addColumns(_table: Table | string, _columns: TableColumn[]): Promise<void>
|
|
1135
|
+
{
|
|
1136
|
+
return this.unsupported('add-columns');
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async renameColumn(_table: Table | string, _oldColumnOrName: TableColumn | string, _newColumnOrName: TableColumn | string)
|
|
1140
|
+
: Promise<void>
|
|
1141
|
+
{
|
|
1142
|
+
return this.unsupported('rename-column');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
async changeColumn(_table: Table | string, _oldColumn: TableColumn | string, _newColumn: TableColumn): Promise<void>
|
|
1146
|
+
{
|
|
1147
|
+
return this.unsupported('change-column');
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
async changeColumns(_table: Table | string, _changedColumns: { oldColumn: TableColumn; newColumn: TableColumn; }[])
|
|
1151
|
+
: Promise<void>
|
|
1152
|
+
{
|
|
1153
|
+
return this.unsupported('change-columns');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
async dropColumn(_table: Table | string, _column: TableColumn | string, _ifExists?: boolean): Promise<void>
|
|
1157
|
+
{
|
|
1158
|
+
return this.unsupported('drop-column');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
async dropColumns(_table: Table | string, _columns: TableColumn[] | string[], _ifExists?: boolean): Promise<void>
|
|
1162
|
+
{
|
|
1163
|
+
return this.unsupported('drop-columns');
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async createPrimaryKey(_table: Table | string, _columnNames: string[], _constraintName?: string): Promise<void>
|
|
1167
|
+
{
|
|
1168
|
+
return this.unsupported('create=primary-key');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
async updatePrimaryKeys(_table: Table | string, _columns: TableColumn[]): Promise<void>
|
|
1172
|
+
{
|
|
1173
|
+
return this.unsupported('update-primary-keys');
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async dropPrimaryKey(_table: Table | string, _constraintName?: string, _ifExists?: boolean): Promise<void>
|
|
1177
|
+
{
|
|
1178
|
+
return this.unsupported('drop=-pimary-key');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
async createUniqueConstraint(_table: Table | string, _uniqueConstraint: TableUnique): Promise<void>
|
|
1182
|
+
{
|
|
1183
|
+
return this.unsupported('create-unique-constraint');
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
async createUniqueConstraints(_table: Table | string, _uniqueConstraints: TableUnique[]): Promise<void>
|
|
1187
|
+
{
|
|
1188
|
+
return this.unsupported('createunique-constraints');
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async dropUniqueConstraint(_table: Table | string, _uniqueOrName: TableUnique | string, _ifExists?: boolean)
|
|
1192
|
+
: Promise<void>
|
|
1193
|
+
{
|
|
1194
|
+
return this.unsupported('drop-unique-constraint');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
async dropUniqueConstraints(_table: Table | string, _uniqueConstraints: TableUnique[], _ifExists?: boolean,)
|
|
1198
|
+
: Promise<void>
|
|
1199
|
+
{
|
|
1200
|
+
return this.unsupported('drop-unique-constraints');
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
async createCheckConstraint(_table: Table | string, _checkConstraint: TableCheck): Promise<void>
|
|
1204
|
+
{
|
|
1205
|
+
return this.unsupported('create-check-constraint');
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async createCheckConstraints(_table: Table | string, _checkConstraints: TableCheck[]): Promise<void>
|
|
1209
|
+
{
|
|
1210
|
+
return this.unsupported('create-check-constraints');
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
async dropCheckConstraint(_table: Table | string, _checkOrName: TableCheck | string, _ifExists?: boolean)
|
|
1214
|
+
: Promise<void>
|
|
1215
|
+
{
|
|
1216
|
+
return this.unsupported('drop-check-constraint');
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
async dropCheckConstraints(_table: Table | string, _checkConstraints: TableCheck[], _ifExists?: boolean)
|
|
1220
|
+
: Promise<void>
|
|
1221
|
+
{
|
|
1222
|
+
return this.unsupported('drop-check-constraints');
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async createExclusionConstraint(_table: Table | string, _exclusionConstraint: TableExclusion): Promise<void>
|
|
1226
|
+
{
|
|
1227
|
+
return this.unsupported('create-exclusion-constraint');
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
async createExclusionConstraints(_table: Table | string, _exclusionConstraints: TableExclusion[]): Promise<void>
|
|
1231
|
+
{
|
|
1232
|
+
return this.unsupported('create-exclusion-constraints');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async dropExclusionConstraint(_table: Table | string, _exclusionOrName: TableExclusion | string, _ifExists?: boolean)
|
|
1236
|
+
: Promise<void>
|
|
1237
|
+
{
|
|
1238
|
+
return this.unsupported('drop-exclusion-constraint');
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
async dropExclusionConstraints(_table: Table | string, _exclusionConstraints: TableExclusion[], _ifExists?: boolean)
|
|
1242
|
+
: Promise<void>
|
|
1243
|
+
{
|
|
1244
|
+
return this.unsupported('drop-exclusion-constraints');
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
async createForeignKey(_table: Table | string, _foreignKey: TableForeignKey): Promise<void>
|
|
1248
|
+
{
|
|
1249
|
+
return this.unsupported('create-foreign-key');
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
async createForeignKeys(_table: Table | string, _foreignKeys: TableForeignKey[]): Promise<void>
|
|
1253
|
+
{
|
|
1254
|
+
return this.unsupported('create-foreign-keys');
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
async dropForeignKey(_table: Table | string, _foreignKeyOrName: TableForeignKey | string, _ifExists?: boolean)
|
|
1258
|
+
: Promise<void>
|
|
1259
|
+
{
|
|
1260
|
+
return this.unsupported('drop-foreign-key');
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async dropForeignKeys(_table: Table | string, _foreignKeys: TableForeignKey[], _ifExists?: boolean): Promise<void>
|
|
1264
|
+
{
|
|
1265
|
+
return this.unsupported('drop-foreign-keys');
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
async createIndex(_table: Table | string, _index: TableIndex): Promise<void>
|
|
1269
|
+
{
|
|
1270
|
+
return this.unsupported('create-index');
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
async createIndices(_table: Table | string, _indices: TableIndex[]): Promise<void>
|
|
1274
|
+
{
|
|
1275
|
+
return this.unsupported('create-indeices');
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
async dropIndex(_table: Table | string, _index: TableIndex | string, _ifExists?: boolean): Promise<void>
|
|
1279
|
+
{
|
|
1280
|
+
return this.unsupported('drop-index');
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async dropIndices(_table: Table | string, _indices: TableIndex[], _ifExists?: boolean): Promise<void>
|
|
1284
|
+
{
|
|
1285
|
+
return this.unsupported('drop-indices');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
async clearTable(_tableName: string, _options?: { cascade?: boolean }): Promise<void>
|
|
1289
|
+
{
|
|
1290
|
+
return this.unsupported('clear-table');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private unsupported(operation: string): never
|
|
1294
|
+
{
|
|
1295
|
+
throw new GoogleSheetsUnsupportedOperationError(operation);
|
|
1296
|
+
}
|
|
1297
|
+
}
|