csv-sql-engine 0.2.0 → 0.3.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/dist/engine/define-ast-handler.d.ts +1 -1
- package/dist/engine/engine.d.ts +2 -2
- package/dist/engine/engine.js +14 -10
- package/dist/engine/handlers/row-delete.handler.js +48 -41
- package/dist/engine/handlers/row-insert.handler.js +106 -45
- package/dist/engine/handlers/row-select.handler.js +44 -31
- package/dist/engine/handlers/row-update.handler.js +62 -51
- package/dist/engine/handlers/table-alter.handler.js +89 -70
- package/dist/engine/handlers/table-create.handler.js +37 -23
- package/dist/engine/handlers/table-drop.handler.js +29 -21
- package/dist/engine/params.d.ts +5 -18
- package/dist/engine/sort-values.d.ts +1 -1
- package/dist/engine/where-matcher.d.ts +3 -2
- package/dist/engine/where-matcher.js +45 -31
- package/dist/errors/sql.error.d.ts +9 -2
- package/dist/errors/sql.error.js +15 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/util/ast-node.d.ts +10 -0
- package/dist/util/ast-node.js +23 -0
- package/package.json +7 -7
- package/dist/sql/ast.d.ts +0 -472
- package/dist/sql/ast.js +0 -266
- package/dist/sql/parse-sql.d.ts +0 -9
- package/dist/sql/parse-sql.js +0 -38
- package/dist/sql/sql.d.ts +0 -33
- package/dist/sql/sql.js +0 -39
|
@@ -20,7 +20,7 @@ export type AstHandler = {
|
|
|
20
20
|
* Return `undefined` to mark this AST as not-handled. That means that other handlers should be
|
|
21
21
|
* used instead.
|
|
22
22
|
*/
|
|
23
|
-
handler: (params: Readonly<AstHandlerParams>) => MaybePromise<AstHandlerResult
|
|
23
|
+
handler: (params: Readonly<AstHandlerParams>) => MaybePromise<AstHandlerResult | undefined>;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* Used to define new handlers.
|
package/dist/engine/engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Sql } from '
|
|
1
|
+
import { type Sql } from 'sqlite-ast';
|
|
2
2
|
import { type AstHandler, type AstHandlerResult } from './define-ast-handler.js';
|
|
3
3
|
import { type ExecuteSqlParams } from './params.js';
|
|
4
4
|
/**
|
|
@@ -13,4 +13,4 @@ export declare const allAstHandlers: ReadonlyArray<Readonly<AstHandler>>;
|
|
|
13
13
|
*
|
|
14
14
|
* @category Main
|
|
15
15
|
*/
|
|
16
|
-
export declare function executeSql(sqlInput: Sql | string, params: Readonly<ExecuteSqlParams>): Promise<AstHandlerResult[]
|
|
16
|
+
export declare function executeSql(sqlInput: Sql | string, params: Readonly<ExecuteSqlParams>): Promise<AstHandlerResult[]>;
|
package/dist/engine/engine.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
|
-
import { awaitedBlockingMap, ensureErrorAndPrependMessage } from '@augment-vir/common';
|
|
2
|
+
import { awaitedBlockingMap, ensureErrorAndPrependMessage, wrapInTry, } from '@augment-vir/common';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { rawSql } from '../sql/sql.js';
|
|
4
|
+
import { parseSqlite, rawSql } from 'sqlite-ast';
|
|
5
|
+
import { SqlParseError, SqlUnsupportedOperationError } from '../errors/sql.error.js';
|
|
7
6
|
import { rowDeleteHandler } from './handlers/row-delete.handler.js';
|
|
8
7
|
import { rowInsertHandler } from './handlers/row-insert.handler.js';
|
|
9
8
|
import { rowSelectHandler } from './handlers/row-select.handler.js';
|
|
@@ -34,17 +33,21 @@ export const allAstHandlers = [
|
|
|
34
33
|
export async function executeSql(sqlInput, params) {
|
|
35
34
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
36
35
|
const sql = check.isString(sqlInput) ? rawSql(sqlInput) : sqlInput;
|
|
37
|
-
const
|
|
36
|
+
const astList = wrapInTry(() => parseSqlite(sql), {
|
|
37
|
+
handleError(error) {
|
|
38
|
+
throw new SqlParseError(sql, error);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
38
41
|
await mkdir(params.csvDirPath, {
|
|
39
42
|
recursive: true,
|
|
40
43
|
});
|
|
41
|
-
return await awaitedBlockingMap(
|
|
44
|
+
return (await awaitedBlockingMap(astList, async (ast) => {
|
|
42
45
|
return await executeIndividualCommand({
|
|
43
46
|
...params,
|
|
44
47
|
ast,
|
|
45
48
|
sql,
|
|
46
49
|
});
|
|
47
|
-
});
|
|
50
|
+
})).filter(check.isTruthy);
|
|
48
51
|
}
|
|
49
52
|
async function executeIndividualCommand(params) {
|
|
50
53
|
try {
|
|
@@ -56,11 +59,12 @@ async function executeIndividualCommand(params) {
|
|
|
56
59
|
}
|
|
57
60
|
/** If nothing handled the query, then we don't support it. */
|
|
58
61
|
if (params.rejectUnsupportedOperations) {
|
|
59
|
-
throw new SqlUnsupportedOperationError(params.sql, undefined);
|
|
62
|
+
throw new SqlUnsupportedOperationError(params.sql, undefined, params.ast);
|
|
60
63
|
}
|
|
61
|
-
return
|
|
64
|
+
return undefined;
|
|
62
65
|
}
|
|
63
66
|
catch (error) {
|
|
64
|
-
|
|
67
|
+
const errorAst = params.ast;
|
|
68
|
+
throw ensureErrorAndPrependMessage(error, `Failed to execute '${errorAst.variant || errorAst.type}' command.`);
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { check } from '@augment-vir/assert';
|
|
2
|
-
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
3
1
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
4
|
-
import {
|
|
2
|
+
import { getAstType } from '../../util/ast-node.js';
|
|
5
3
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
6
4
|
import { sortValues } from '../sort-values.js';
|
|
7
5
|
import { findWhereMatches } from '../where-matcher.js';
|
|
@@ -13,44 +11,53 @@ import { findWhereMatches } from '../where-matcher.js';
|
|
|
13
11
|
export const rowDeleteHandler = defineAstHandler({
|
|
14
12
|
name: 'row-delete',
|
|
15
13
|
async handler({ ast, csvDirPath, sql }) {
|
|
16
|
-
if (ast.
|
|
17
|
-
|
|
18
|
-
const results = await awaitedBlockingMap(tableNames, async (tableName) => {
|
|
19
|
-
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
20
|
-
csvDirPath,
|
|
21
|
-
tableName,
|
|
22
|
-
});
|
|
23
|
-
const csvContents = await readCsvFile(tableFilePath);
|
|
24
|
-
const csvHeaders = await readCsvHeaders({
|
|
25
|
-
csvContents,
|
|
26
|
-
sanitizedTableName,
|
|
27
|
-
});
|
|
28
|
-
const rowIndexesToDelete = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
29
|
-
const returningRequirement = ast.returning;
|
|
30
|
-
const result = returningRequirement
|
|
31
|
-
? sortValues({
|
|
32
|
-
csvFileHeaderOrder: csvHeaders,
|
|
33
|
-
sqlQueryHeaderOrder: returningRequirement.columns.map((column) => column.expr.column),
|
|
34
|
-
from: {
|
|
35
|
-
csvFile: csvContents.filter((row, index) => rowIndexesToDelete.includes(index)),
|
|
36
|
-
},
|
|
37
|
-
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
38
|
-
})
|
|
39
|
-
: {
|
|
40
|
-
columnNames: [],
|
|
41
|
-
values: [],
|
|
42
|
-
};
|
|
43
|
-
rowIndexesToDelete.forEach((rowIndexToDelete) => {
|
|
44
|
-
csvContents.splice(rowIndexToDelete, 1);
|
|
45
|
-
});
|
|
46
|
-
await writeCsvFile(tableFilePath, csvContents);
|
|
47
|
-
return {
|
|
48
|
-
...result,
|
|
49
|
-
numberOfRowsAffected: rowIndexesToDelete.length,
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
return results.flat().filter(check.isTruthy);
|
|
14
|
+
if (ast.variant !== 'delete') {
|
|
15
|
+
return undefined;
|
|
53
16
|
}
|
|
54
|
-
|
|
17
|
+
const tableName = getAstType(ast.from, 'identifier')?.name;
|
|
18
|
+
if (!tableName) {
|
|
19
|
+
throw new Error('Missing table name.');
|
|
20
|
+
}
|
|
21
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
22
|
+
csvDirPath,
|
|
23
|
+
tableName,
|
|
24
|
+
});
|
|
25
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
26
|
+
const csvHeaders = await readCsvHeaders({
|
|
27
|
+
csvContents,
|
|
28
|
+
sanitizedTableName,
|
|
29
|
+
});
|
|
30
|
+
const rowIndexesToDelete = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
31
|
+
const returningRequirement = ast.returning;
|
|
32
|
+
const sqlHeaders = returningRequirement?.map((column) => {
|
|
33
|
+
const columnNode = getAstType(column, 'identifier');
|
|
34
|
+
if (columnNode) {
|
|
35
|
+
return columnNode.name;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Unexpected return column type: ${column.type}`);
|
|
39
|
+
}
|
|
40
|
+
}) || [];
|
|
41
|
+
const result = returningRequirement
|
|
42
|
+
? sortValues({
|
|
43
|
+
csvFileHeaderOrder: csvHeaders,
|
|
44
|
+
sqlQueryHeaderOrder: sqlHeaders,
|
|
45
|
+
from: {
|
|
46
|
+
csvFile: csvContents.filter((row, index) => rowIndexesToDelete.includes(index)),
|
|
47
|
+
},
|
|
48
|
+
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
49
|
+
})
|
|
50
|
+
: {
|
|
51
|
+
columnNames: [],
|
|
52
|
+
values: [],
|
|
53
|
+
};
|
|
54
|
+
rowIndexesToDelete.forEach((rowIndexToDelete) => {
|
|
55
|
+
csvContents.splice(rowIndexToDelete, 1);
|
|
56
|
+
});
|
|
57
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
58
|
+
return {
|
|
59
|
+
...result,
|
|
60
|
+
numberOfRowsAffected: rowIndexesToDelete.length,
|
|
61
|
+
};
|
|
55
62
|
},
|
|
56
63
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { assertWrap, check } from '@augment-vir/assert';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { filterMap } from '@augment-vir/common';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { appendCsvRow, nameCsvTableFile, readCsvFile, readCsvHeaders } from '../../csv/csv-file.js';
|
|
5
|
+
import { CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
+
import { SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
5
7
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
6
8
|
import { sortValues } from '../sort-values.js';
|
|
7
9
|
/**
|
|
@@ -11,48 +13,107 @@ import { sortValues } from '../sort-values.js';
|
|
|
11
13
|
*/
|
|
12
14
|
export const rowInsertHandler = defineAstHandler({
|
|
13
15
|
name: 'row-insert',
|
|
14
|
-
async handler({ ast, csvDirPath, sql }) {
|
|
15
|
-
if (ast.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
csvDirPath,
|
|
20
|
-
tableName,
|
|
21
|
-
});
|
|
22
|
-
const rawValues = ast.values.values.flatMap((value) => value.value.flatMap((value) => String(value.value)));
|
|
23
|
-
const csvFileHeaderOrder = await readCsvHeaders({
|
|
24
|
-
csvFilePath: tableFilePath,
|
|
25
|
-
sanitizedTableName,
|
|
26
|
-
});
|
|
27
|
-
const newRow = assertWrap.isDefined(sortValues({
|
|
28
|
-
csvFileHeaderOrder,
|
|
29
|
-
sqlQueryHeaderOrder: ast.columns || csvFileHeaderOrder,
|
|
30
|
-
from: {
|
|
31
|
-
sqlQuery: [rawValues],
|
|
32
|
-
},
|
|
33
|
-
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
34
|
-
}).values[0], 'No sorted row retrieved.');
|
|
35
|
-
await appendCsvRow(newRow, tableFilePath);
|
|
36
|
-
const readResult = ast.returning
|
|
37
|
-
? sortValues({
|
|
38
|
-
csvFileHeaderOrder,
|
|
39
|
-
sqlQueryHeaderOrder: ast.returning.columns.map((column) => column.expr.column),
|
|
40
|
-
from: {
|
|
41
|
-
csvFile: [newRow],
|
|
42
|
-
},
|
|
43
|
-
unconsumedInterpolationValues: undefined,
|
|
44
|
-
})
|
|
45
|
-
: {
|
|
46
|
-
columnNames: [],
|
|
47
|
-
values: [],
|
|
48
|
-
};
|
|
49
|
-
return {
|
|
50
|
-
...readResult,
|
|
51
|
-
numberOfRowsAffected: 1,
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
return results.filter(check.isTruthy);
|
|
16
|
+
async handler({ ast, csvDirPath, sql, rejectUnsupportedOperations }) {
|
|
17
|
+
if (ast.variant !== 'insert' ||
|
|
18
|
+
ast.into.type !== 'identifier' ||
|
|
19
|
+
(ast.into.variant !== 'table' && ast.into.format !== 'table')) {
|
|
20
|
+
return;
|
|
55
21
|
}
|
|
56
|
-
|
|
22
|
+
const tableName = ast.into.name;
|
|
23
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
24
|
+
csvDirPath,
|
|
25
|
+
tableName,
|
|
26
|
+
});
|
|
27
|
+
if (!existsSync(tableFilePath)) {
|
|
28
|
+
throw new CsvTableDoesNotExistError(sanitizedTableName);
|
|
29
|
+
}
|
|
30
|
+
/** Mutate this to apply the table alterations. */
|
|
31
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
32
|
+
const csvHeaders = await readCsvHeaders({
|
|
33
|
+
csvContents,
|
|
34
|
+
sanitizedTableName,
|
|
35
|
+
});
|
|
36
|
+
const sqlHeaders = ast.into.columns
|
|
37
|
+
? filterMap(ast.into.columns, (column) => {
|
|
38
|
+
if (column.type === 'identifier' && column.variant === 'column') {
|
|
39
|
+
return column.name;
|
|
40
|
+
}
|
|
41
|
+
else if (rejectUnsupportedOperations) {
|
|
42
|
+
throw new SqlUnsupportedOperationError(sql, `Unsupported column definition: '${JSON.stringify(column)}'`, ast);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}, check.isTruthy)
|
|
48
|
+
: csvHeaders;
|
|
49
|
+
if (!check.isArray(ast.result)) {
|
|
50
|
+
if (rejectUnsupportedOperations) {
|
|
51
|
+
throw new SqlUnsupportedOperationError(sql, `Unsupported result: '${JSON.stringify(ast.result)}'`, ast);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const resultExpression = assertWrap.isTruthy(ast.result[0], 'No result.');
|
|
58
|
+
if (resultExpression.type !== 'expression' ||
|
|
59
|
+
resultExpression.variant !== 'list' ||
|
|
60
|
+
!check.isArray(resultExpression.expression)) {
|
|
61
|
+
if (rejectUnsupportedOperations) {
|
|
62
|
+
throw new SqlUnsupportedOperationError(sql, `Expected expression: ${JSON.stringify(resultExpression)}`, ast);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const rawValues = filterMap(resultExpression.expression, (entry) => {
|
|
69
|
+
if (entry.type === 'identifier' || entry.type === 'variable') {
|
|
70
|
+
return entry.name;
|
|
71
|
+
}
|
|
72
|
+
else if (entry.type === 'literal') {
|
|
73
|
+
return entry.value;
|
|
74
|
+
}
|
|
75
|
+
else if (rejectUnsupportedOperations) {
|
|
76
|
+
throw new SqlUnsupportedOperationError(sql, `Unsupported expression entry: ${JSON.stringify(entry)}`, ast);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}, check.isTruthy);
|
|
82
|
+
const newRow = assertWrap.isTruthy(sortValues({
|
|
83
|
+
csvFileHeaderOrder: csvHeaders,
|
|
84
|
+
sqlQueryHeaderOrder: sqlHeaders,
|
|
85
|
+
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
86
|
+
from: {
|
|
87
|
+
sqlQuery: [rawValues],
|
|
88
|
+
},
|
|
89
|
+
}).values[0], 'No sorted row retrieved.');
|
|
90
|
+
await appendCsvRow(newRow, tableFilePath);
|
|
91
|
+
const readResult = ast.returning
|
|
92
|
+
? sortValues({
|
|
93
|
+
csvFileHeaderOrder: csvHeaders,
|
|
94
|
+
sqlQueryHeaderOrder: filterMap(ast.returning, (entry) => {
|
|
95
|
+
if (entry.type === 'identifier') {
|
|
96
|
+
return entry.name;
|
|
97
|
+
}
|
|
98
|
+
else if (rejectUnsupportedOperations) {
|
|
99
|
+
throw new Error(`Unsupported returning entry: ${JSON.stringify(entry)}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}, check.isTruthy),
|
|
105
|
+
from: {
|
|
106
|
+
csvFile: [newRow],
|
|
107
|
+
},
|
|
108
|
+
unconsumedInterpolationValues: undefined,
|
|
109
|
+
})
|
|
110
|
+
: {
|
|
111
|
+
columnNames: [],
|
|
112
|
+
values: [],
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
...readResult,
|
|
116
|
+
numberOfRowsAffected: 1,
|
|
117
|
+
};
|
|
57
118
|
},
|
|
58
119
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
|
-
import {
|
|
2
|
+
import { filterMap } from '@augment-vir/common';
|
|
3
3
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders } from '../../csv/csv-file.js';
|
|
4
|
-
import {
|
|
4
|
+
import { SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
5
|
+
import { getAstType } from '../../util/ast-node.js';
|
|
5
6
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
6
7
|
import { sortValues } from '../sort-values.js';
|
|
7
8
|
import { findWhereMatches } from '../where-matcher.js';
|
|
@@ -12,35 +13,47 @@ import { findWhereMatches } from '../where-matcher.js';
|
|
|
12
13
|
*/
|
|
13
14
|
export const rowSelectHandler = defineAstHandler({
|
|
14
15
|
name: 'row-select',
|
|
15
|
-
async handler({ ast, csvDirPath, sql }) {
|
|
16
|
-
if (ast.
|
|
17
|
-
|
|
18
|
-
const allSelections = await awaitedBlockingMap(tableNames, async (tableName) => {
|
|
19
|
-
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
20
|
-
csvDirPath,
|
|
21
|
-
tableName,
|
|
22
|
-
});
|
|
23
|
-
const csvContents = await readCsvFile(tableFilePath);
|
|
24
|
-
const csvHeaders = await readCsvHeaders({
|
|
25
|
-
csvContents,
|
|
26
|
-
sanitizedTableName,
|
|
27
|
-
});
|
|
28
|
-
const rowIndexesToSelect = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
29
|
-
const columnNames = ast.columns.map((column) => column.expr.column);
|
|
30
|
-
return {
|
|
31
|
-
...sortValues({
|
|
32
|
-
csvFileHeaderOrder: csvHeaders,
|
|
33
|
-
sqlQueryHeaderOrder: columnNames,
|
|
34
|
-
from: {
|
|
35
|
-
csvFile: csvContents.filter((row, index) => rowIndexesToSelect.includes(index)),
|
|
36
|
-
},
|
|
37
|
-
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
38
|
-
}),
|
|
39
|
-
numberOfRowsAffected: 0,
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
return allSelections.flat().filter(check.isTruthy);
|
|
16
|
+
async handler({ ast, csvDirPath, sql, rejectUnsupportedOperations }) {
|
|
17
|
+
if (ast.variant !== 'select') {
|
|
18
|
+
return undefined;
|
|
43
19
|
}
|
|
44
|
-
|
|
20
|
+
const tableName = getAstType(ast.from, 'identifier')?.name;
|
|
21
|
+
if (!tableName) {
|
|
22
|
+
throw new Error('No table name.');
|
|
23
|
+
}
|
|
24
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
25
|
+
csvDirPath,
|
|
26
|
+
tableName,
|
|
27
|
+
});
|
|
28
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
29
|
+
const csvHeaders = await readCsvHeaders({
|
|
30
|
+
csvContents,
|
|
31
|
+
sanitizedTableName,
|
|
32
|
+
});
|
|
33
|
+
const rowIndexesToSelect = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
34
|
+
const columnNames = filterMap(ast.result, (result) => {
|
|
35
|
+
return getAstType(result, 'identifier')?.name;
|
|
36
|
+
}, (value) => {
|
|
37
|
+
if (check.isString(value)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
else if (rejectUnsupportedOperations) {
|
|
41
|
+
throw new SqlUnsupportedOperationError(sql, 'Result name is not a string.', ast);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
...sortValues({
|
|
49
|
+
csvFileHeaderOrder: csvHeaders,
|
|
50
|
+
sqlQueryHeaderOrder: columnNames,
|
|
51
|
+
from: {
|
|
52
|
+
csvFile: csvContents.filter((row, index) => rowIndexesToSelect.includes(index)),
|
|
53
|
+
},
|
|
54
|
+
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
55
|
+
}),
|
|
56
|
+
numberOfRowsAffected: 0,
|
|
57
|
+
};
|
|
45
58
|
},
|
|
46
59
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { assertWrap
|
|
2
|
-
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
1
|
+
import { assertWrap } from '@augment-vir/assert';
|
|
3
2
|
import { createCsvHeaderMaps, nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile, } from '../../csv/csv-file.js';
|
|
4
3
|
import { CsvColumnDoesNotExistError } from '../../errors/csv.error.js';
|
|
5
|
-
import {
|
|
4
|
+
import { getAstType } from '../../util/ast-node.js';
|
|
6
5
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
7
6
|
import { sortValues } from '../sort-values.js';
|
|
8
7
|
import { findWhereMatches } from '../where-matcher.js';
|
|
@@ -14,54 +13,66 @@ import { findWhereMatches } from '../where-matcher.js';
|
|
|
14
13
|
export const rowUpdateHandler = defineAstHandler({
|
|
15
14
|
name: 'row-update',
|
|
16
15
|
async handler({ ast, csvDirPath, sql }) {
|
|
17
|
-
if (ast.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
const csvContents = await readCsvFile(tableFilePath);
|
|
25
|
-
const csvHeaders = await readCsvHeaders({
|
|
26
|
-
csvContents,
|
|
27
|
-
sanitizedTableName,
|
|
28
|
-
});
|
|
29
|
-
const csvHeaderIndexes = createCsvHeaderMaps(csvHeaders);
|
|
30
|
-
const rowIndexesToUpdate = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
31
|
-
const returningRequirement = ast.returning;
|
|
32
|
-
rowIndexesToUpdate.forEach((rowIndexToUpdate) => {
|
|
33
|
-
const row = assertWrap.isDefined(csvContents[rowIndexToUpdate], `Invalid row index '${rowIndexToUpdate}'.`);
|
|
34
|
-
ast.set.forEach((set) => {
|
|
35
|
-
const columnName = set.column;
|
|
36
|
-
const headerIndex = csvHeaderIndexes.byName[columnName];
|
|
37
|
-
if (!headerIndex) {
|
|
38
|
-
throw new CsvColumnDoesNotExistError(sanitizedTableName, columnName);
|
|
39
|
-
}
|
|
40
|
-
row[headerIndex] = set.value.value;
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
const sqlHeaders = returningRequirement?.columns.map((column) => column.expr.column) || [];
|
|
44
|
-
const result = returningRequirement
|
|
45
|
-
? sortValues({
|
|
46
|
-
csvFileHeaderOrder: csvHeaders,
|
|
47
|
-
sqlQueryHeaderOrder: sqlHeaders,
|
|
48
|
-
from: {
|
|
49
|
-
csvFile: csvContents.filter((row, index) => rowIndexesToUpdate.includes(index)),
|
|
50
|
-
},
|
|
51
|
-
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
52
|
-
})
|
|
53
|
-
: {
|
|
54
|
-
columnNames: [],
|
|
55
|
-
values: [],
|
|
56
|
-
};
|
|
57
|
-
await writeCsvFile(tableFilePath, csvContents);
|
|
58
|
-
return {
|
|
59
|
-
...result,
|
|
60
|
-
numberOfRowsAffected: rowIndexesToUpdate.length,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
return results.flat().filter(check.isTruthy);
|
|
16
|
+
if (ast.variant !== 'update') {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const tableName = getAstType(ast.into, 'identifier')?.name;
|
|
20
|
+
if (!tableName) {
|
|
21
|
+
throw new Error('No table name');
|
|
64
22
|
}
|
|
65
|
-
|
|
23
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
24
|
+
csvDirPath,
|
|
25
|
+
tableName,
|
|
26
|
+
});
|
|
27
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
28
|
+
const csvHeaders = await readCsvHeaders({
|
|
29
|
+
csvContents,
|
|
30
|
+
sanitizedTableName,
|
|
31
|
+
});
|
|
32
|
+
const csvHeaderIndexes = createCsvHeaderMaps(csvHeaders);
|
|
33
|
+
const rowIndexesToUpdate = findWhereMatches(ast.where, csvContents, tableFilePath);
|
|
34
|
+
const returningRequirement = ast.returning;
|
|
35
|
+
rowIndexesToUpdate.forEach((rowIndexToUpdate) => {
|
|
36
|
+
const row = assertWrap.isTruthy(csvContents[rowIndexToUpdate], `Invalid row index '${rowIndexToUpdate}'.`);
|
|
37
|
+
ast.set.forEach((set) => {
|
|
38
|
+
const columnName = set.target.name;
|
|
39
|
+
const headerIndex = csvHeaderIndexes.byName[columnName];
|
|
40
|
+
if (!headerIndex) {
|
|
41
|
+
throw new CsvColumnDoesNotExistError(sanitizedTableName, columnName);
|
|
42
|
+
}
|
|
43
|
+
const valueNode = getAstType(set.value, 'literal');
|
|
44
|
+
if (!valueNode) {
|
|
45
|
+
throw new Error(`Unexpected set type: ${set.value.type}`);
|
|
46
|
+
}
|
|
47
|
+
row[headerIndex] = valueNode.value;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
const sqlHeaders = returningRequirement?.map((column) => {
|
|
51
|
+
const columnNode = getAstType(column, 'identifier');
|
|
52
|
+
if (columnNode) {
|
|
53
|
+
return columnNode.name;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
throw new Error(`Unexpected return column type: ${column.type}`);
|
|
57
|
+
}
|
|
58
|
+
}) || [];
|
|
59
|
+
const result = returningRequirement
|
|
60
|
+
? sortValues({
|
|
61
|
+
csvFileHeaderOrder: csvHeaders,
|
|
62
|
+
sqlQueryHeaderOrder: sqlHeaders,
|
|
63
|
+
from: {
|
|
64
|
+
csvFile: csvContents.filter((row, index) => rowIndexesToUpdate.includes(index)),
|
|
65
|
+
},
|
|
66
|
+
unconsumedInterpolationValues: sql.unconsumedValues,
|
|
67
|
+
})
|
|
68
|
+
: {
|
|
69
|
+
columnNames: [],
|
|
70
|
+
values: [],
|
|
71
|
+
};
|
|
72
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
73
|
+
return {
|
|
74
|
+
...result,
|
|
75
|
+
numberOfRowsAffected: rowIndexesToUpdate.length,
|
|
76
|
+
};
|
|
66
77
|
},
|
|
67
78
|
});
|