csv-sql-engine 0.3.1 → 0.3.2
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/engine/engine.js +2 -2
- package/dist/engine/handlers/row-delete.handler.js +13 -5
- package/dist/engine/handlers/row-select.handler.js +18 -6
- package/dist/engine/handlers/row-update.handler.js +6 -6
- package/dist/engine/handlers/table-alter.handler.js +7 -3
- package/dist/util/ast-node.d.ts +5 -9
- package/dist/util/ast-node.js +4 -16
- package/dist/util/ast-text.d.ts +7 -0
- package/dist/util/ast-text.js +649 -0
- package/dist/util/sort-values.js +13 -3
- package/dist/util/where-matcher.d.ts +12 -1
- package/dist/util/where-matcher.js +41 -7
- package/package.json +1 -1
package/dist/engine/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
|
-
import { awaitedBlockingMap, ensureErrorAndPrependMessage, wrapInTry, } from '@augment-vir/common';
|
|
2
|
+
import { awaitedBlockingMap, ensureErrorAndPrependMessage, indent, wrapInTry, } from '@augment-vir/common';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { parseSqlite, rawSql } from 'sqlite-ast';
|
|
5
5
|
import { SqlParseError, SqlUnsupportedOperationError } from '../errors/sql.error.js';
|
|
@@ -65,6 +65,6 @@ async function executeIndividualCommand(params) {
|
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
67
|
const errorAst = params.ast;
|
|
68
|
-
throw ensureErrorAndPrependMessage(error, `Failed to execute '${errorAst.variant || errorAst.type}' command
|
|
68
|
+
throw ensureErrorAndPrependMessage(error, `Failed to execute '${errorAst.variant || errorAst.type}' command:\n\n${indent(JSON.stringify(params.ast, null, 4))}\n\n`);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getAst } from '../../util/ast-node.js';
|
|
3
3
|
import { sortValues } from '../../util/sort-values.js';
|
|
4
|
-
import { findWhereMatches } from '../../util/where-matcher.js';
|
|
4
|
+
import { findWhereMatches, MatchSort } from '../../util/where-matcher.js';
|
|
5
5
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
6
6
|
/**
|
|
7
7
|
* Handles deleting rows.
|
|
@@ -14,7 +14,11 @@ export const rowDeleteHandler = defineAstHandler({
|
|
|
14
14
|
if (ast.variant !== 'delete') {
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
-
const tableName =
|
|
17
|
+
const tableName = getAst({
|
|
18
|
+
ast: ast.from,
|
|
19
|
+
property: 'type',
|
|
20
|
+
value: 'identifier',
|
|
21
|
+
})?.name;
|
|
18
22
|
if (!tableName) {
|
|
19
23
|
throw new Error('Missing table name.');
|
|
20
24
|
}
|
|
@@ -27,10 +31,14 @@ export const rowDeleteHandler = defineAstHandler({
|
|
|
27
31
|
csvContents,
|
|
28
32
|
sanitizedTableName,
|
|
29
33
|
});
|
|
30
|
-
const rowIndexesToDelete = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
34
|
+
const rowIndexesToDelete = findWhereMatches(ast.where, csvContents, tableFilePath, MatchSort.Descending);
|
|
31
35
|
const returningRequirement = ast.returning;
|
|
32
36
|
const sqlHeaders = returningRequirement?.map((column) => {
|
|
33
|
-
const columnNode =
|
|
37
|
+
const columnNode = getAst({
|
|
38
|
+
ast: column,
|
|
39
|
+
property: 'type',
|
|
40
|
+
value: 'identifier',
|
|
41
|
+
});
|
|
34
42
|
if (columnNode) {
|
|
35
43
|
return columnNode.name;
|
|
36
44
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { check } from '@augment-vir/assert';
|
|
1
|
+
import { check, checkWrap } from '@augment-vir/assert';
|
|
2
2
|
import { filterMap } from '@augment-vir/common';
|
|
3
3
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders } from '../../csv/csv-file.js';
|
|
4
4
|
import { SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getAst } from '../../util/ast-node.js';
|
|
6
|
+
import { readAstText } from '../../util/ast-text.js';
|
|
6
7
|
import { sortValues } from '../../util/sort-values.js';
|
|
7
|
-
import { findWhereMatches } from '../../util/where-matcher.js';
|
|
8
|
+
import { findWhereMatches, MatchSort } from '../../util/where-matcher.js';
|
|
8
9
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
9
10
|
/**
|
|
10
11
|
* Handles SQL selection.
|
|
@@ -17,7 +18,7 @@ export const rowSelectHandler = defineAstHandler({
|
|
|
17
18
|
if (ast.variant !== 'select') {
|
|
18
19
|
return undefined;
|
|
19
20
|
}
|
|
20
|
-
const tableName =
|
|
21
|
+
const tableName = getAst({ ast: ast.from, property: 'type', value: 'identifier' })?.name;
|
|
21
22
|
if (!tableName) {
|
|
22
23
|
throw new Error('No table name.');
|
|
23
24
|
}
|
|
@@ -30,9 +31,20 @@ export const rowSelectHandler = defineAstHandler({
|
|
|
30
31
|
csvContents,
|
|
31
32
|
sanitizedTableName,
|
|
32
33
|
});
|
|
33
|
-
const
|
|
34
|
+
const limit = ast.limit ? checkWrap.isNumber(Number(readAstText(ast.limit.start))) : -1;
|
|
35
|
+
if (limit == undefined) {
|
|
36
|
+
throw new Error(`Unexpected limit: ${JSON.stringify(ast.limit)}`);
|
|
37
|
+
}
|
|
38
|
+
const offset = ast.limit?.offset
|
|
39
|
+
? checkWrap.isNumber(Number(readAstText(ast.limit.offset)))
|
|
40
|
+
: 0;
|
|
41
|
+
if (offset == undefined) {
|
|
42
|
+
throw new Error(`Unexpected offset: ${JSON.stringify(ast.limit?.offset)}`);
|
|
43
|
+
}
|
|
44
|
+
const rawIndexes = findWhereMatches(ast.where, csvContents, tableFilePath, MatchSort.Ascending);
|
|
45
|
+
const rowIndexesToSelect = limit < 0 ? rawIndexes.slice(offset) : rawIndexes.slice(offset, offset + limit);
|
|
34
46
|
const columnNames = filterMap(ast.result, (result) => {
|
|
35
|
-
return
|
|
47
|
+
return getAst({ ast: result, property: 'type', value: 'identifier' })?.name;
|
|
36
48
|
}, (value) => {
|
|
37
49
|
if (check.isString(value)) {
|
|
38
50
|
return true;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { assertWrap } from '@augment-vir/assert';
|
|
2
2
|
import { createCsvHeaderMaps, nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile, } from '../../csv/csv-file.js';
|
|
3
3
|
import { CsvColumnDoesNotExistError } from '../../errors/csv.error.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getAst } from '../../util/ast-node.js';
|
|
5
5
|
import { sortValues } from '../../util/sort-values.js';
|
|
6
|
-
import { findWhereMatches } from '../../util/where-matcher.js';
|
|
6
|
+
import { findWhereMatches, MatchSort } from '../../util/where-matcher.js';
|
|
7
7
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
8
|
/**
|
|
9
9
|
* Handles updating rows.
|
|
@@ -16,7 +16,7 @@ export const rowUpdateHandler = defineAstHandler({
|
|
|
16
16
|
if (ast.variant !== 'update') {
|
|
17
17
|
return undefined;
|
|
18
18
|
}
|
|
19
|
-
const tableName =
|
|
19
|
+
const tableName = getAst({ ast: ast.into, property: 'type', value: 'identifier' })?.name;
|
|
20
20
|
if (!tableName) {
|
|
21
21
|
throw new Error('No table name');
|
|
22
22
|
}
|
|
@@ -30,7 +30,7 @@ export const rowUpdateHandler = defineAstHandler({
|
|
|
30
30
|
sanitizedTableName,
|
|
31
31
|
});
|
|
32
32
|
const csvHeaderIndexes = createCsvHeaderMaps(csvHeaders);
|
|
33
|
-
const rowIndexesToUpdate = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
33
|
+
const rowIndexesToUpdate = findWhereMatches(ast.where, csvContents, tableFilePath, MatchSort.Ascending);
|
|
34
34
|
const returningRequirement = ast.returning;
|
|
35
35
|
rowIndexesToUpdate.forEach((rowIndexToUpdate) => {
|
|
36
36
|
const row = assertWrap.isTruthy(csvContents[rowIndexToUpdate], `Invalid row index '${rowIndexToUpdate}'.`);
|
|
@@ -40,7 +40,7 @@ export const rowUpdateHandler = defineAstHandler({
|
|
|
40
40
|
if (!headerIndex) {
|
|
41
41
|
throw new CsvColumnDoesNotExistError(sanitizedTableName, columnName);
|
|
42
42
|
}
|
|
43
|
-
const valueNode =
|
|
43
|
+
const valueNode = getAst({ ast: set.value, property: 'type', value: 'literal' });
|
|
44
44
|
if (!valueNode) {
|
|
45
45
|
throw new Error(`Unexpected set type: ${set.value.type}`);
|
|
46
46
|
}
|
|
@@ -48,7 +48,7 @@ export const rowUpdateHandler = defineAstHandler({
|
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
const sqlHeaders = returningRequirement?.map((column) => {
|
|
51
|
-
const columnNode =
|
|
51
|
+
const columnNode = getAst({ ast: column, property: 'type', value: 'identifier' });
|
|
52
52
|
if (columnNode) {
|
|
53
53
|
return columnNode.name;
|
|
54
54
|
}
|
|
@@ -5,7 +5,7 @@ import { rename } from 'node:fs/promises';
|
|
|
5
5
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
6
6
|
import { CsvColumnDoesNotExistError, CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
7
7
|
import { SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getAst } from '../../util/ast-node.js';
|
|
9
9
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
10
10
|
/**
|
|
11
11
|
* Handles altering tables.
|
|
@@ -33,7 +33,7 @@ export const tableAlterHandler = defineAstHandler({
|
|
|
33
33
|
sanitizedTableName,
|
|
34
34
|
});
|
|
35
35
|
if (ast.action === 'rename') {
|
|
36
|
-
const newTableName = assertWrap.isTruthy(
|
|
36
|
+
const newTableName = assertWrap.isTruthy(getAst({ ast: ast.name, property: 'type', value: 'identifier' })?.name, 'Missing new table name.');
|
|
37
37
|
await rename(tableFilePath, nameCsvTableFile({ csvDirPath, tableName: newTableName }).tableFilePath);
|
|
38
38
|
return {
|
|
39
39
|
columnNames: [],
|
|
@@ -45,7 +45,11 @@ export const tableAlterHandler = defineAstHandler({
|
|
|
45
45
|
if (!ast.definition || ast.definition.type !== 'definition') {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
const defaultValue =
|
|
48
|
+
const defaultValue = getAst({
|
|
49
|
+
ast: ast.definition.definition.find((entry) => entry.type === 'constraint' && entry.variant === 'default')?.value,
|
|
50
|
+
property: 'type',
|
|
51
|
+
value: 'literal',
|
|
52
|
+
})?.value || '';
|
|
49
53
|
const newHeaderName = assertWrap.isTruthy(ast.definition.name, 'Missing new column name.');
|
|
50
54
|
csvContents.forEach((row, index) => {
|
|
51
55
|
if (index) {
|
package/dist/util/ast-node.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type SqliteAstNode } from 'sqlite-ast';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
variant: string;
|
|
8
|
-
}> | undefined, const VariantName extends NonNullable<Ast>['variant']>(node: Ast, variant: VariantName): Extract<Ast, {
|
|
9
|
-
variant: VariantName;
|
|
10
|
-
}> | undefined;
|
|
2
|
+
export declare function getAst<const Ast extends (SqliteAstNode & Record<PropertyToCheck, any>) | undefined, const PropertyToCheck extends string, const ValueToCheck>({ ast, property, value, }: {
|
|
3
|
+
ast: Ast;
|
|
4
|
+
property: PropertyToCheck;
|
|
5
|
+
value: ValueToCheck;
|
|
6
|
+
}): Extract<Ast, Record<PropertyToCheck, ValueToCheck>> | undefined;
|
package/dist/util/ast-node.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (!node) {
|
|
1
|
+
export function getAst({ ast, property, value, }) {
|
|
2
|
+
if (!ast) {
|
|
4
3
|
return undefined;
|
|
5
4
|
}
|
|
6
|
-
else if (
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
else {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export function getAstVariant(node, variant) {
|
|
14
|
-
if (!node) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
else if (node.variant === variant) {
|
|
18
|
-
return node;
|
|
5
|
+
else if (ast[property] === value) {
|
|
6
|
+
return ast;
|
|
19
7
|
}
|
|
20
8
|
else {
|
|
21
9
|
return undefined;
|
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively converts an AST node back to its SQL text representation.
|
|
3
|
+
*
|
|
4
|
+
* @category Internal
|
|
5
|
+
*/
|
|
6
|
+
export function readAstText(node) {
|
|
7
|
+
if (!node) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
switch (node.type) {
|
|
11
|
+
case 'literal':
|
|
12
|
+
return readLiteralText(node);
|
|
13
|
+
case 'identifier':
|
|
14
|
+
return readIdentifierText(node);
|
|
15
|
+
case 'expression':
|
|
16
|
+
return readExpressionText(node);
|
|
17
|
+
case 'function':
|
|
18
|
+
return readFunctionText(node);
|
|
19
|
+
case 'statement':
|
|
20
|
+
return readStatementText(node);
|
|
21
|
+
case 'constraint':
|
|
22
|
+
return readConstraintText(node);
|
|
23
|
+
case 'definition':
|
|
24
|
+
return readDefinitionText(node);
|
|
25
|
+
case 'condition':
|
|
26
|
+
return readConditionText(node);
|
|
27
|
+
case 'join':
|
|
28
|
+
return readJoinText(node);
|
|
29
|
+
case 'map':
|
|
30
|
+
return readMapText(node);
|
|
31
|
+
case 'assignment':
|
|
32
|
+
return readAssignmentText(node);
|
|
33
|
+
case 'compound':
|
|
34
|
+
return readCompoundText(node);
|
|
35
|
+
case 'datatype':
|
|
36
|
+
return readDatatypeText(node);
|
|
37
|
+
case 'variable':
|
|
38
|
+
return readVariableText(node);
|
|
39
|
+
case 'values':
|
|
40
|
+
return 'DEFAULT VALUES';
|
|
41
|
+
case 'error':
|
|
42
|
+
return readErrorText(node);
|
|
43
|
+
case 'event':
|
|
44
|
+
return readEventText(node);
|
|
45
|
+
case 'module':
|
|
46
|
+
return readModuleText(node);
|
|
47
|
+
default:
|
|
48
|
+
// Fallback for any unhandled type
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function readLiteralText(node) {
|
|
53
|
+
if (node.variant === 'text') {
|
|
54
|
+
return `'${node.value}'`;
|
|
55
|
+
}
|
|
56
|
+
else if (node.variant === 'null') {
|
|
57
|
+
return 'NULL';
|
|
58
|
+
}
|
|
59
|
+
else if (node.variant === 'blob') {
|
|
60
|
+
return node.value;
|
|
61
|
+
}
|
|
62
|
+
return node.value;
|
|
63
|
+
}
|
|
64
|
+
function readIdentifierText(node) {
|
|
65
|
+
let text = node.name;
|
|
66
|
+
if (node.columns) {
|
|
67
|
+
text += `(${readNodeArray(node.columns)})`;
|
|
68
|
+
}
|
|
69
|
+
if (node.alias) {
|
|
70
|
+
text += ` AS ${node.alias}`;
|
|
71
|
+
}
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
function readExpressionText(node) {
|
|
75
|
+
if ('format' in node) {
|
|
76
|
+
if (node.format === 'binary') {
|
|
77
|
+
const left = readAstText(node.left);
|
|
78
|
+
const right = readAstText(node.right);
|
|
79
|
+
let text = `${left} ${node.operation.toUpperCase()} ${right}`;
|
|
80
|
+
if (node.escape) {
|
|
81
|
+
text += ` ESCAPE ${readAstText(node.escape)}`;
|
|
82
|
+
}
|
|
83
|
+
return withAlias(text, node);
|
|
84
|
+
}
|
|
85
|
+
else if (node.format === 'unary') {
|
|
86
|
+
if (node.variant === 'operation') {
|
|
87
|
+
if ('collate' in node) {
|
|
88
|
+
const expr = readAstText(node.expression);
|
|
89
|
+
const collation = node.collate.map((c) => c.name).join(' ');
|
|
90
|
+
return `${expr} COLLATE ${collation}`;
|
|
91
|
+
}
|
|
92
|
+
const expr = readAstText(node.expression);
|
|
93
|
+
const op = node.operator.toUpperCase();
|
|
94
|
+
// Prefix operators like NOT, -, +, ~
|
|
95
|
+
if (op === '-' || op === '+' || op === '~') {
|
|
96
|
+
return withAlias(`${op}${expr}`, node);
|
|
97
|
+
}
|
|
98
|
+
if (op === 'NOT') {
|
|
99
|
+
return withAlias(`${op} ${expr}`, node);
|
|
100
|
+
}
|
|
101
|
+
return withAlias(`${expr} ${op}`, node);
|
|
102
|
+
}
|
|
103
|
+
else if (node.variant === 'cast') {
|
|
104
|
+
const expr = readAstText(node.expression);
|
|
105
|
+
const datatype = readDatatypeText(node.as);
|
|
106
|
+
return withAlias(`CAST(${expr} AS ${datatype})`, node);
|
|
107
|
+
}
|
|
108
|
+
// exists variant
|
|
109
|
+
const expr = readAstText(node.expression);
|
|
110
|
+
return `${node.operator.toUpperCase()} ${expr}`;
|
|
111
|
+
}
|
|
112
|
+
// table format
|
|
113
|
+
const target = readAstText(node.target);
|
|
114
|
+
const expr = readAstText(node.expression);
|
|
115
|
+
const variant = node.variant === 'recursive' ? 'RECURSIVE' : '';
|
|
116
|
+
return `${target} AS ${variant}(${expr})`.trim();
|
|
117
|
+
}
|
|
118
|
+
switch (node.variant) {
|
|
119
|
+
case 'list':
|
|
120
|
+
return readExpressionList(node.expression);
|
|
121
|
+
case 'order': {
|
|
122
|
+
const expr = readAstText(node.expression);
|
|
123
|
+
const dir = node.direction ? ` ${node.direction.toUpperCase()}` : '';
|
|
124
|
+
return `${expr}${dir}`;
|
|
125
|
+
}
|
|
126
|
+
case 'limit': {
|
|
127
|
+
const start = readAstText(node.start);
|
|
128
|
+
const offset = node.offset ? ` OFFSET ${readAstText(node.offset)}` : '';
|
|
129
|
+
return `${start}${offset}`;
|
|
130
|
+
}
|
|
131
|
+
case 'exists':
|
|
132
|
+
return node.operator.toUpperCase();
|
|
133
|
+
case 'case':
|
|
134
|
+
return withAlias(readCaseText(node.expression), node);
|
|
135
|
+
default:
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function readExpressionList(expression) {
|
|
140
|
+
if (expression == null) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(expression)) {
|
|
144
|
+
return expression.map(readAstText).join(', ');
|
|
145
|
+
}
|
|
146
|
+
return readAstText(expression);
|
|
147
|
+
}
|
|
148
|
+
function readCaseText(conditions) {
|
|
149
|
+
const parts = conditions.map((cond) => {
|
|
150
|
+
if (cond.type === 'condition') {
|
|
151
|
+
if (cond.variant === 'when') {
|
|
152
|
+
return `WHEN ${readAstText(cond.condition)} THEN ${readAstText(cond.consequent)}`;
|
|
153
|
+
}
|
|
154
|
+
else if (cond.variant === 'else') {
|
|
155
|
+
return `ELSE ${readAstText(cond.consequent)}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return '';
|
|
159
|
+
});
|
|
160
|
+
return `CASE ${parts.join(' ')} END`;
|
|
161
|
+
}
|
|
162
|
+
function readFunctionText(node) {
|
|
163
|
+
const name = node.name.name.toUpperCase();
|
|
164
|
+
let args;
|
|
165
|
+
if (node.args.type === 'identifier') {
|
|
166
|
+
args = '*';
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
args = readExpressionList(node.args.expression);
|
|
170
|
+
}
|
|
171
|
+
const text = `${name}(${args})`;
|
|
172
|
+
return withAlias(text, node);
|
|
173
|
+
}
|
|
174
|
+
function readStatementText(node) {
|
|
175
|
+
switch (node.variant) {
|
|
176
|
+
case 'select':
|
|
177
|
+
return readSelectText(node);
|
|
178
|
+
case 'insert':
|
|
179
|
+
return readInsertText(node);
|
|
180
|
+
case 'update':
|
|
181
|
+
return readUpdateText(node);
|
|
182
|
+
case 'delete':
|
|
183
|
+
return readDeleteText(node);
|
|
184
|
+
case 'create':
|
|
185
|
+
return readCreateText(node);
|
|
186
|
+
case 'drop':
|
|
187
|
+
return readDropText(node);
|
|
188
|
+
case 'compound':
|
|
189
|
+
return readCompoundStatementText(node);
|
|
190
|
+
case 'transaction':
|
|
191
|
+
return readTransactionText(node);
|
|
192
|
+
case 'alter table':
|
|
193
|
+
return readAlterTableText(node);
|
|
194
|
+
case 'pragma':
|
|
195
|
+
return readPragmaText(node);
|
|
196
|
+
case 'attach':
|
|
197
|
+
return readAttachText(node);
|
|
198
|
+
case 'detach':
|
|
199
|
+
return readDetachText(node);
|
|
200
|
+
case 'vacuum':
|
|
201
|
+
return readVacuumText(node);
|
|
202
|
+
case 'reindex':
|
|
203
|
+
return readReindexText(node);
|
|
204
|
+
case 'analyze':
|
|
205
|
+
return readAnalyzeText(node);
|
|
206
|
+
case 'release':
|
|
207
|
+
case 'savepoint':
|
|
208
|
+
return readSavepointText(node);
|
|
209
|
+
default:
|
|
210
|
+
return '';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function readSelectText(node) {
|
|
214
|
+
const parts = [];
|
|
215
|
+
if (node.with) {
|
|
216
|
+
parts.push(`WITH ${readNodeArray(node.with)}`);
|
|
217
|
+
}
|
|
218
|
+
parts.push('SELECT');
|
|
219
|
+
if (node.distinct) {
|
|
220
|
+
parts.push('DISTINCT');
|
|
221
|
+
}
|
|
222
|
+
parts.push(readNodeArray(node.result));
|
|
223
|
+
if (node.from) {
|
|
224
|
+
parts.push(`FROM ${readAstText(node.from)}`);
|
|
225
|
+
}
|
|
226
|
+
if (node.where) {
|
|
227
|
+
parts.push(`WHERE ${readNodeArray(node.where, ' AND ')}`);
|
|
228
|
+
}
|
|
229
|
+
if (node.group) {
|
|
230
|
+
parts.push(`GROUP BY ${readExpressionList(node.group.expression)}`);
|
|
231
|
+
}
|
|
232
|
+
if (node.having) {
|
|
233
|
+
parts.push(`HAVING ${readAstText(node.having)}`);
|
|
234
|
+
}
|
|
235
|
+
if (node.order) {
|
|
236
|
+
parts.push(`ORDER BY ${readNodeArray(node.order)}`);
|
|
237
|
+
}
|
|
238
|
+
if (node.limit) {
|
|
239
|
+
parts.push(`LIMIT ${readAstText(node.limit)}`);
|
|
240
|
+
}
|
|
241
|
+
return withAlias(parts.join(' '), node);
|
|
242
|
+
}
|
|
243
|
+
function readInsertText(node) {
|
|
244
|
+
const parts = [];
|
|
245
|
+
if (node.or) {
|
|
246
|
+
parts.push(`INSERT OR ${node.or.toUpperCase()}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
parts.push('INSERT');
|
|
250
|
+
}
|
|
251
|
+
parts.push(`INTO ${readAstText(node.into)}`);
|
|
252
|
+
if (Array.isArray(node.result)) {
|
|
253
|
+
const values = node.result
|
|
254
|
+
.map((r) => {
|
|
255
|
+
const expr = r.expression;
|
|
256
|
+
return `(${readExpressionList(expr)})`;
|
|
257
|
+
})
|
|
258
|
+
.join(', ');
|
|
259
|
+
parts.push(`VALUES ${values}`);
|
|
260
|
+
}
|
|
261
|
+
else if ('type' in node.result && node.result.type === 'values') {
|
|
262
|
+
parts.push('DEFAULT VALUES');
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
parts.push(readAstText(node.result));
|
|
266
|
+
}
|
|
267
|
+
if (node.returning) {
|
|
268
|
+
parts.push(`RETURNING ${readNodeArray(node.returning)}`);
|
|
269
|
+
}
|
|
270
|
+
return parts.join(' ');
|
|
271
|
+
}
|
|
272
|
+
function readUpdateText(node) {
|
|
273
|
+
const parts = [];
|
|
274
|
+
if (node.or) {
|
|
275
|
+
parts.push(`UPDATE OR ${node.or.toUpperCase()}`);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
parts.push('UPDATE');
|
|
279
|
+
}
|
|
280
|
+
parts.push(readAstText(node.into));
|
|
281
|
+
const setClause = node.set.map((s) => readAssignmentText(s)).join(', ');
|
|
282
|
+
parts.push(`SET ${setClause}`);
|
|
283
|
+
if (node.where) {
|
|
284
|
+
parts.push(`WHERE ${readNodeArray(node.where, ' AND ')}`);
|
|
285
|
+
}
|
|
286
|
+
if (node.limit) {
|
|
287
|
+
parts.push(`LIMIT ${readAstText(node.limit)}`);
|
|
288
|
+
}
|
|
289
|
+
if (node.returning) {
|
|
290
|
+
parts.push(`RETURNING ${readNodeArray(node.returning)}`);
|
|
291
|
+
}
|
|
292
|
+
return parts.join(' ');
|
|
293
|
+
}
|
|
294
|
+
function readDeleteText(node) {
|
|
295
|
+
const parts = [
|
|
296
|
+
'DELETE FROM',
|
|
297
|
+
readAstText(node.from),
|
|
298
|
+
];
|
|
299
|
+
if (node.where) {
|
|
300
|
+
parts.push(`WHERE ${readNodeArray(node.where, ' AND ')}`);
|
|
301
|
+
}
|
|
302
|
+
if (node.limit) {
|
|
303
|
+
parts.push(`LIMIT ${readAstText(node.limit)}`);
|
|
304
|
+
}
|
|
305
|
+
if (node.returning) {
|
|
306
|
+
parts.push(`RETURNING ${readNodeArray(node.returning)}`);
|
|
307
|
+
}
|
|
308
|
+
return parts.join(' ');
|
|
309
|
+
}
|
|
310
|
+
function readCreateText(node) {
|
|
311
|
+
const parts = ['CREATE'];
|
|
312
|
+
if (node.temporary) {
|
|
313
|
+
parts.push('TEMPORARY');
|
|
314
|
+
}
|
|
315
|
+
if (node.unique) {
|
|
316
|
+
parts.push('UNIQUE');
|
|
317
|
+
}
|
|
318
|
+
parts.push(node.format.toUpperCase());
|
|
319
|
+
if (node.condition) {
|
|
320
|
+
parts.push('IF NOT EXISTS');
|
|
321
|
+
}
|
|
322
|
+
if (node.name) {
|
|
323
|
+
parts.push(readAstText(node.name));
|
|
324
|
+
}
|
|
325
|
+
if (node.format === 'table' && node.definition) {
|
|
326
|
+
parts.push(`(${readNodeArray(node.definition)})`);
|
|
327
|
+
}
|
|
328
|
+
if (node.format === 'index' && node.on) {
|
|
329
|
+
parts.push(`ON ${readAstText(node.on)}`);
|
|
330
|
+
}
|
|
331
|
+
if (node.format === 'view' && node.result) {
|
|
332
|
+
parts.push(`AS ${readAstText(node.result)}`);
|
|
333
|
+
}
|
|
334
|
+
if (node.format === 'trigger') {
|
|
335
|
+
if (node.event) {
|
|
336
|
+
parts.push(readEventText(node.event));
|
|
337
|
+
}
|
|
338
|
+
if (node.on) {
|
|
339
|
+
parts.push(`ON ${readAstText(node.on)}`);
|
|
340
|
+
}
|
|
341
|
+
if (node.by) {
|
|
342
|
+
parts.push(`FOR EACH ${node.by.toUpperCase()}`);
|
|
343
|
+
}
|
|
344
|
+
if (node.when) {
|
|
345
|
+
parts.push(`WHEN ${readAstText(node.when)}`);
|
|
346
|
+
}
|
|
347
|
+
if (node.action) {
|
|
348
|
+
parts.push(`BEGIN ${readNodeArray(node.action, '; ')}; END`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return parts.join(' ');
|
|
352
|
+
}
|
|
353
|
+
function readDropText(node) {
|
|
354
|
+
const parts = [
|
|
355
|
+
'DROP',
|
|
356
|
+
node.format.toUpperCase(),
|
|
357
|
+
];
|
|
358
|
+
if (node.condition.length > 0) {
|
|
359
|
+
parts.push('IF EXISTS');
|
|
360
|
+
}
|
|
361
|
+
parts.push(readAstText(node.target));
|
|
362
|
+
return parts.join(' ');
|
|
363
|
+
}
|
|
364
|
+
function readCompoundStatementText(node) {
|
|
365
|
+
const first = readAstText(node.statement);
|
|
366
|
+
const rest = node.compound.map((c) => `${c.variant.toUpperCase()} ${readAstText(c.statement)}`);
|
|
367
|
+
return [
|
|
368
|
+
first,
|
|
369
|
+
...rest,
|
|
370
|
+
].join(' ');
|
|
371
|
+
}
|
|
372
|
+
function readTransactionText(node) {
|
|
373
|
+
const parts = [node.action.toUpperCase()];
|
|
374
|
+
if (node.defer) {
|
|
375
|
+
parts.push(node.defer.toUpperCase());
|
|
376
|
+
}
|
|
377
|
+
parts.push('TRANSACTION');
|
|
378
|
+
if (node.savepoint) {
|
|
379
|
+
parts.push(node.savepoint.name);
|
|
380
|
+
}
|
|
381
|
+
return parts.join(' ');
|
|
382
|
+
}
|
|
383
|
+
function readAlterTableText(node) {
|
|
384
|
+
const parts = [
|
|
385
|
+
'ALTER TABLE',
|
|
386
|
+
readAstText(node.target),
|
|
387
|
+
];
|
|
388
|
+
if (node.action === 'rename') {
|
|
389
|
+
if (node.column && node.newName) {
|
|
390
|
+
parts.push(`RENAME COLUMN ${node.column} TO ${node.newName}`);
|
|
391
|
+
}
|
|
392
|
+
else if (node.newName) {
|
|
393
|
+
parts.push(`RENAME TO ${node.newName}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else if (node.action === 'add') {
|
|
397
|
+
if (node.definition) {
|
|
398
|
+
parts.push(`ADD COLUMN ${readAstText(node.definition)}`);
|
|
399
|
+
}
|
|
400
|
+
else if (node.name) {
|
|
401
|
+
parts.push(`ADD COLUMN ${readAstText(node.name)}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else if (node.action === 'drop' && node.column) {
|
|
405
|
+
parts.push(`DROP COLUMN ${node.column}`);
|
|
406
|
+
}
|
|
407
|
+
return parts.join(' ');
|
|
408
|
+
}
|
|
409
|
+
function readPragmaText(node) {
|
|
410
|
+
const name = node.target.name;
|
|
411
|
+
if (node.args.expression) {
|
|
412
|
+
return `PRAGMA ${name} = ${readAstText(node.args.expression)}`;
|
|
413
|
+
}
|
|
414
|
+
return `PRAGMA ${name}`;
|
|
415
|
+
}
|
|
416
|
+
function readAttachText(node) {
|
|
417
|
+
return `ATTACH ${readAstText(node.target)} AS ${readAstText(node.attach)}`;
|
|
418
|
+
}
|
|
419
|
+
function readDetachText(node) {
|
|
420
|
+
return `DETACH ${node.target.name}`;
|
|
421
|
+
}
|
|
422
|
+
function readVacuumText(node) {
|
|
423
|
+
if (node.target) {
|
|
424
|
+
return `VACUUM ${node.target.name}`;
|
|
425
|
+
}
|
|
426
|
+
return 'VACUUM';
|
|
427
|
+
}
|
|
428
|
+
function readReindexText(node) {
|
|
429
|
+
if (node.target) {
|
|
430
|
+
return `REINDEX ${node.target}`;
|
|
431
|
+
}
|
|
432
|
+
return 'REINDEX';
|
|
433
|
+
}
|
|
434
|
+
function readAnalyzeText(node) {
|
|
435
|
+
if (node.target) {
|
|
436
|
+
return `ANALYZE ${node.target}`;
|
|
437
|
+
}
|
|
438
|
+
return 'ANALYZE';
|
|
439
|
+
}
|
|
440
|
+
function readSavepointText(node) {
|
|
441
|
+
const action = node.variant === 'release' ? 'RELEASE' : 'SAVEPOINT';
|
|
442
|
+
return `${action} ${node.target.savepoint.name}`;
|
|
443
|
+
}
|
|
444
|
+
function readConstraintText(node) {
|
|
445
|
+
const parts = [];
|
|
446
|
+
switch (node.variant) {
|
|
447
|
+
case 'primary key':
|
|
448
|
+
if (node.name) {
|
|
449
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
450
|
+
}
|
|
451
|
+
parts.push('PRIMARY KEY');
|
|
452
|
+
if (node.direction) {
|
|
453
|
+
parts.push(node.direction.toUpperCase());
|
|
454
|
+
}
|
|
455
|
+
if (node.conflict) {
|
|
456
|
+
parts.push(`ON CONFLICT ${node.conflict.toUpperCase()}`);
|
|
457
|
+
}
|
|
458
|
+
if (node.autoIncrement) {
|
|
459
|
+
parts.push('AUTOINCREMENT');
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
462
|
+
case 'not null':
|
|
463
|
+
if (node.name) {
|
|
464
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
465
|
+
}
|
|
466
|
+
parts.push('NOT NULL');
|
|
467
|
+
if (node.conflict) {
|
|
468
|
+
parts.push(`ON CONFLICT ${node.conflict.toUpperCase()}`);
|
|
469
|
+
}
|
|
470
|
+
break;
|
|
471
|
+
case 'null':
|
|
472
|
+
if (node.name) {
|
|
473
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
474
|
+
}
|
|
475
|
+
parts.push('NULL');
|
|
476
|
+
break;
|
|
477
|
+
case 'unique':
|
|
478
|
+
if (node.name) {
|
|
479
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
480
|
+
}
|
|
481
|
+
parts.push('UNIQUE');
|
|
482
|
+
if (node.conflict) {
|
|
483
|
+
parts.push(`ON CONFLICT ${node.conflict.toUpperCase()}`);
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
case 'check':
|
|
487
|
+
if (node.name) {
|
|
488
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
489
|
+
}
|
|
490
|
+
parts.push(`CHECK (${readAstText(node.expression)})`);
|
|
491
|
+
break;
|
|
492
|
+
case 'default':
|
|
493
|
+
if (node.name) {
|
|
494
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
495
|
+
}
|
|
496
|
+
parts.push(`DEFAULT ${readAstText(node.value)}`);
|
|
497
|
+
break;
|
|
498
|
+
case 'foreign key':
|
|
499
|
+
if (node.name) {
|
|
500
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
501
|
+
}
|
|
502
|
+
parts.push(`REFERENCES ${readAstText(node.references)}`);
|
|
503
|
+
if (node.action) {
|
|
504
|
+
for (const act of node.action) {
|
|
505
|
+
parts.push(`ON ${act.variant.toUpperCase()} ${act.action.toUpperCase()}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (node.defer) {
|
|
509
|
+
parts.push(node.defer.toUpperCase());
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
case 'collate':
|
|
513
|
+
if (node.name) {
|
|
514
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
515
|
+
}
|
|
516
|
+
parts.push(`COLLATE ${node.collate.collate.map((c) => c.name).join(' ')}`);
|
|
517
|
+
break;
|
|
518
|
+
case 'join':
|
|
519
|
+
parts.push(node.format.toUpperCase());
|
|
520
|
+
if (node.on) {
|
|
521
|
+
parts.push(`ON ${readAstText(node.on)}`);
|
|
522
|
+
}
|
|
523
|
+
if (node.using) {
|
|
524
|
+
const cols = node.using.columns.map((c) => c.name).join(', ');
|
|
525
|
+
parts.push(`USING (${cols})`);
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
return parts.join(' ');
|
|
530
|
+
}
|
|
531
|
+
function readDefinitionText(node) {
|
|
532
|
+
if (node.variant === 'column') {
|
|
533
|
+
const parts = [node.name];
|
|
534
|
+
parts.push(readDatatypeText(node.datatype));
|
|
535
|
+
if (node.definition.length > 0) {
|
|
536
|
+
parts.push(readNodeArray(node.definition, ' '));
|
|
537
|
+
}
|
|
538
|
+
return parts.join(' ');
|
|
539
|
+
}
|
|
540
|
+
// constraint variant
|
|
541
|
+
const parts = [];
|
|
542
|
+
if (node.name) {
|
|
543
|
+
parts.push(`CONSTRAINT ${node.name}`);
|
|
544
|
+
}
|
|
545
|
+
if (node.columns) {
|
|
546
|
+
parts.push(`(${readNodeArray(node.columns)})`);
|
|
547
|
+
}
|
|
548
|
+
if (node.definition.length > 0) {
|
|
549
|
+
parts.push(readNodeArray(node.definition, ' '));
|
|
550
|
+
}
|
|
551
|
+
return parts.join(' ');
|
|
552
|
+
}
|
|
553
|
+
function readConditionText(node) {
|
|
554
|
+
switch (node.variant) {
|
|
555
|
+
case 'when':
|
|
556
|
+
return `WHEN ${readAstText(node.condition)} THEN ${readAstText(node.consequent)}`;
|
|
557
|
+
case 'else':
|
|
558
|
+
return `ELSE ${readAstText(node.consequent)}`;
|
|
559
|
+
case 'if':
|
|
560
|
+
return `IF ${readAstText(node.condition)}`;
|
|
561
|
+
default:
|
|
562
|
+
return '';
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function readJoinText(node) {
|
|
566
|
+
const parts = [];
|
|
567
|
+
parts.push(node.variant.toUpperCase());
|
|
568
|
+
parts.push(readAstText(node.source));
|
|
569
|
+
if (node.constraint) {
|
|
570
|
+
parts.push(readAstText(node.constraint));
|
|
571
|
+
}
|
|
572
|
+
return parts.join(' ');
|
|
573
|
+
}
|
|
574
|
+
function readMapText(node) {
|
|
575
|
+
const source = readAstText(node.source);
|
|
576
|
+
const joins = node.map.map(readAstText).join(' ');
|
|
577
|
+
return `${source} ${joins}`;
|
|
578
|
+
}
|
|
579
|
+
function readAssignmentText(node) {
|
|
580
|
+
return `${node.target.name} = ${readAstText(node.value)}`;
|
|
581
|
+
}
|
|
582
|
+
function readCompoundText(node) {
|
|
583
|
+
return `${node.variant.toUpperCase()} ${readAstText(node.statement)}`;
|
|
584
|
+
}
|
|
585
|
+
function readDatatypeText(node) {
|
|
586
|
+
let text = node.variant.toUpperCase();
|
|
587
|
+
if (node.args) {
|
|
588
|
+
text += `(${readExpressionList(node.args.expression)})`;
|
|
589
|
+
}
|
|
590
|
+
return text;
|
|
591
|
+
}
|
|
592
|
+
function readVariableText(node) {
|
|
593
|
+
let text;
|
|
594
|
+
switch (node.format) {
|
|
595
|
+
case 'named':
|
|
596
|
+
text = `:${node.name}`;
|
|
597
|
+
break;
|
|
598
|
+
case 'numbered':
|
|
599
|
+
text = `?${node.name}`;
|
|
600
|
+
break;
|
|
601
|
+
case 'tcl':
|
|
602
|
+
text = `$${node.name}`;
|
|
603
|
+
break;
|
|
604
|
+
default:
|
|
605
|
+
text = `?`;
|
|
606
|
+
}
|
|
607
|
+
if (node.suffix) {
|
|
608
|
+
text += node.suffix;
|
|
609
|
+
}
|
|
610
|
+
return text;
|
|
611
|
+
}
|
|
612
|
+
function readErrorText(node) {
|
|
613
|
+
const parts = ['RAISE'];
|
|
614
|
+
if (node.action) {
|
|
615
|
+
parts.push(`(${node.action.toUpperCase()}`);
|
|
616
|
+
if (node.message) {
|
|
617
|
+
parts.push(`, ${readAstText(node.message)}`);
|
|
618
|
+
}
|
|
619
|
+
parts.push(')');
|
|
620
|
+
}
|
|
621
|
+
return parts.join('');
|
|
622
|
+
}
|
|
623
|
+
function readEventText(node) {
|
|
624
|
+
const parts = [];
|
|
625
|
+
if (node.occurs) {
|
|
626
|
+
parts.push(node.occurs.toUpperCase());
|
|
627
|
+
}
|
|
628
|
+
parts.push(node.event.toUpperCase());
|
|
629
|
+
if (node.of) {
|
|
630
|
+
parts.push(`OF ${readNodeArray(node.of)}`);
|
|
631
|
+
}
|
|
632
|
+
return parts.join(' ');
|
|
633
|
+
}
|
|
634
|
+
function readModuleText(node) {
|
|
635
|
+
const args = readExpressionList(node.args.expression);
|
|
636
|
+
return `USING ${node.name}(${args})`;
|
|
637
|
+
}
|
|
638
|
+
function readNodeArray(nodes, separator = ', ') {
|
|
639
|
+
if (!nodes) {
|
|
640
|
+
return '';
|
|
641
|
+
}
|
|
642
|
+
return nodes.map(readAstText).join(separator);
|
|
643
|
+
}
|
|
644
|
+
function withAlias(text, node) {
|
|
645
|
+
if (node.alias) {
|
|
646
|
+
return `${text} AS ${node.alias}`;
|
|
647
|
+
}
|
|
648
|
+
return text;
|
|
649
|
+
}
|
package/dist/util/sort-values.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the column name from a potentially fully qualified identifier. E.g., `main.users.email`
|
|
3
|
+
* -> `email`, `users.email` -> `email`, `email` -> `email`
|
|
4
|
+
*/
|
|
5
|
+
function extractColumnName(header) {
|
|
6
|
+
const parts = header.split('.');
|
|
7
|
+
return parts[parts.length - 1] || header;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Sorts values for CSV insertion or reading and handle interpolated values.
|
|
3
11
|
*
|
|
4
12
|
* @category Internal
|
|
5
13
|
*/
|
|
6
14
|
export function sortValues({ csvFileHeaderOrder, sqlQueryHeaderOrder, from, unconsumedInterpolationValues, }) {
|
|
7
|
-
const fromOrder = from.sqlQuery ? sqlQueryHeaderOrder : csvFileHeaderOrder;
|
|
8
|
-
const toOrder = (from.sqlQuery ? csvFileHeaderOrder : sqlQueryHeaderOrder)
|
|
15
|
+
const fromOrder = (from.sqlQuery ? sqlQueryHeaderOrder : csvFileHeaderOrder).map(extractColumnName);
|
|
16
|
+
const toOrder = (from.sqlQuery ? csvFileHeaderOrder : sqlQueryHeaderOrder)
|
|
17
|
+
.flatMap((header) => {
|
|
9
18
|
if (header === '*') {
|
|
10
19
|
return csvFileHeaderOrder;
|
|
11
20
|
}
|
|
12
21
|
else {
|
|
13
22
|
return header;
|
|
14
23
|
}
|
|
15
|
-
})
|
|
24
|
+
})
|
|
25
|
+
.map(extractColumnName);
|
|
16
26
|
const values = (from.csvFile || from.sqlQuery).map((valueRow) => {
|
|
17
27
|
const mappedValueRow = valueRow.map((value) => {
|
|
18
28
|
if (value === '?') {
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { type MaybeArray } from '@augment-vir/common';
|
|
2
2
|
import { type SqliteAstNode } from 'sqlite-ast';
|
|
3
3
|
import { type CsvFile } from '../csv/csv-file.js';
|
|
4
|
+
/**
|
|
5
|
+
* Used to determine how matches returned from {@link findWhereMatches} should be sorted.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
*/
|
|
9
|
+
export declare enum MatchSort {
|
|
10
|
+
/** 0 index first */
|
|
11
|
+
Ascending = "ascending",
|
|
12
|
+
/** 0 index last */
|
|
13
|
+
Descending = "descending"
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Finds all row indexes that match the given SQL where conditions.
|
|
6
17
|
*
|
|
7
18
|
* @category Internal
|
|
8
19
|
* @returns An array of row indexes that match the given where condition.
|
|
9
20
|
*/
|
|
10
|
-
export declare function findWhereMatches(expressions: Readonly<MaybeArray<Readonly<SqliteAstNode>>> | undefined, csvContents: Readonly<CsvFile>, csvFilePath: string): number[];
|
|
21
|
+
export declare function findWhereMatches(expressions: Readonly<MaybeArray<Readonly<SqliteAstNode>>> | undefined, csvContents: Readonly<CsvFile>, csvFilePath: string, sort: MatchSort): number[];
|
|
@@ -1,23 +1,46 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
1
2
|
import { ensureArray, extractDuplicates, filterMap, removeDuplicates, removeSuffix, } from '@augment-vir/common';
|
|
2
3
|
import { csvExtension } from '../csv/csv-file.js';
|
|
3
4
|
import { CsvColumnDoesNotExistError, CsvFileMissingHeadersError } from '../errors/csv.error.js';
|
|
5
|
+
/**
|
|
6
|
+
* Used to determine how matches returned from {@link findWhereMatches} should be sorted.
|
|
7
|
+
*
|
|
8
|
+
* @category Internal
|
|
9
|
+
*/
|
|
10
|
+
export var MatchSort;
|
|
11
|
+
(function (MatchSort) {
|
|
12
|
+
/** 0 index first */
|
|
13
|
+
MatchSort["Ascending"] = "ascending";
|
|
14
|
+
/** 0 index last */
|
|
15
|
+
MatchSort["Descending"] = "descending";
|
|
16
|
+
})(MatchSort || (MatchSort = {}));
|
|
4
17
|
/**
|
|
5
18
|
* Finds all row indexes that match the given SQL where conditions.
|
|
6
19
|
*
|
|
7
20
|
* @category Internal
|
|
8
21
|
* @returns An array of row indexes that match the given where condition.
|
|
9
22
|
*/
|
|
10
|
-
export function findWhereMatches(expressions, csvContents, csvFilePath) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
export function findWhereMatches(expressions, csvContents, csvFilePath, sort) {
|
|
24
|
+
const allIndexes = removeDuplicates((expressions ? ensureArray(expressions) : [undefined]).flatMap((expression) => innerFindWhereMatches(expression, csvContents, csvFilePath))).sort((a, b) => {
|
|
25
|
+
if (sort === MatchSort.Descending) {
|
|
26
|
+
return b - a;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return a - b;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
16
32
|
return allIndexes;
|
|
17
33
|
}
|
|
34
|
+
function getAllIndexes(csvContents) {
|
|
35
|
+
return csvContents
|
|
36
|
+
.map((value, index) => index)
|
|
37
|
+
.filter(
|
|
38
|
+
/** Exclude the first index, which is headers. */
|
|
39
|
+
check.isTruthy);
|
|
40
|
+
}
|
|
18
41
|
function innerFindWhereMatches(where, csvContents, csvFilePath) {
|
|
19
42
|
if (!where) {
|
|
20
|
-
return csvContents
|
|
43
|
+
return getAllIndexes(csvContents);
|
|
21
44
|
}
|
|
22
45
|
else if (where.type === 'expression' &&
|
|
23
46
|
where.variant === 'operation' &&
|
|
@@ -39,6 +62,17 @@ function innerFindWhereMatches(where, csvContents, csvFilePath) {
|
|
|
39
62
|
if (!headers) {
|
|
40
63
|
throw new CsvFileMissingHeadersError(csvFilePath);
|
|
41
64
|
}
|
|
65
|
+
/** Handle no-op WHERE clauses like `1=1` that always evaluate to true. */
|
|
66
|
+
if (where.left.type === 'literal' && where.right.type === 'literal') {
|
|
67
|
+
if (where.left.value === where.right.value) {
|
|
68
|
+
/** Always true - return all rows (excluding headers). */
|
|
69
|
+
return getAllIndexes(csvContents);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
/** Always false - return no rows. */
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
42
76
|
if (where.left.type !== 'identifier' || where.left.variant !== 'column') {
|
|
43
77
|
throw new Error(`Expected column identifier on left side of '=' operation`);
|
|
44
78
|
}
|