csv-sql-engine 0.2.0 → 0.3.1
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/csv/csv-file.js +10 -8
- package/dist/engine/define-ast-handler.d.ts +2 -2
- package/dist/engine/engine.d.ts +2 -2
- package/dist/engine/engine.js +14 -10
- package/dist/engine/handlers/row-delete.handler.js +50 -43
- package/dist/engine/handlers/row-insert.handler.js +107 -46
- package/dist/engine/handlers/row-select.handler.js +46 -33
- package/dist/engine/handlers/row-update.handler.js +64 -53
- 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/errors/sql.error.d.ts +9 -2
- package/dist/errors/sql.error.js +15 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.js +3 -5
- package/dist/util/ast-node.d.ts +10 -0
- package/dist/util/ast-node.js +23 -0
- package/dist/{engine → util}/sort-values.d.ts +1 -1
- package/dist/util/where-matcher.d.ts +10 -0
- package/dist/util/where-matcher.js +66 -0
- package/package.json +7 -7
- package/dist/engine/where-matcher.d.ts +0 -9
- package/dist/engine/where-matcher.js +0 -52
- 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
- /package/dist/{engine → util}/sort-values.js +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { assert } from '@augment-vir/assert';
|
|
2
|
-
import {
|
|
1
|
+
import { assert, assertWrap } from '@augment-vir/assert';
|
|
2
|
+
import { stringify } from '@augment-vir/common';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
+
import { rename } from 'node:fs/promises';
|
|
4
5
|
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
5
6
|
import { CsvColumnDoesNotExistError, CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
-
import {
|
|
7
|
+
import { SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
8
|
+
import { getAstType } from '../../util/ast-node.js';
|
|
7
9
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
10
|
/**
|
|
9
11
|
* Handles altering tables.
|
|
@@ -12,74 +14,91 @@ import { defineAstHandler } from '../define-ast-handler.js';
|
|
|
12
14
|
*/
|
|
13
15
|
export const tableAlterHandler = defineAstHandler({
|
|
14
16
|
name: 'table-alter',
|
|
15
|
-
async handler({ ast, csvDirPath }) {
|
|
16
|
-
if (ast.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
17
|
+
async handler({ ast, csvDirPath, sql }) {
|
|
18
|
+
if (ast.variant !== 'alter table' || ast.target.type !== 'identifier') {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const tableName = ast.target.name;
|
|
22
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
23
|
+
csvDirPath,
|
|
24
|
+
tableName,
|
|
25
|
+
});
|
|
26
|
+
if (!existsSync(tableFilePath)) {
|
|
27
|
+
throw new CsvTableDoesNotExistError(sanitizedTableName);
|
|
28
|
+
}
|
|
29
|
+
/** Mutate this to apply the table alterations. */
|
|
30
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
31
|
+
const csvHeaders = await readCsvHeaders({
|
|
32
|
+
csvContents,
|
|
33
|
+
sanitizedTableName,
|
|
34
|
+
});
|
|
35
|
+
if (ast.action === 'rename') {
|
|
36
|
+
const newTableName = assertWrap.isTruthy(getAstType(ast.name, 'identifier')?.name, 'Missing new table name.');
|
|
37
|
+
await rename(tableFilePath, nameCsvTableFile({ csvDirPath, tableName: newTableName }).tableFilePath);
|
|
38
|
+
return {
|
|
39
|
+
columnNames: [],
|
|
40
|
+
numberOfRowsAffected: 0,
|
|
41
|
+
values: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
else if (ast.action === 'add') {
|
|
45
|
+
if (!ast.definition || ast.definition.type !== 'definition') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const defaultValue = getAstType(ast.definition.definition.find((entry) => entry.type === 'constraint' && entry.variant === 'default')?.value, 'literal')?.value || '';
|
|
49
|
+
const newHeaderName = assertWrap.isTruthy(ast.definition.name, 'Missing new column name.');
|
|
50
|
+
csvContents.forEach((row, index) => {
|
|
51
|
+
if (index) {
|
|
52
|
+
row.push(defaultValue);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
row.push(newHeaderName);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (ast.action === 'rename, column') {
|
|
60
|
+
const oldHeaderName = ast.oldName;
|
|
61
|
+
const newHeaderName = ast.newName;
|
|
62
|
+
if (!oldHeaderName) {
|
|
63
|
+
throw new Error('No old column name.');
|
|
64
|
+
}
|
|
65
|
+
else if (!newHeaderName) {
|
|
66
|
+
throw new Error('No new column name.');
|
|
67
|
+
}
|
|
68
|
+
let oldHeaderFound = false;
|
|
69
|
+
const newHeaders = csvHeaders.map((header) => {
|
|
70
|
+
if (header === oldHeaderName) {
|
|
71
|
+
oldHeaderFound = true;
|
|
72
|
+
return newHeaderName;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return header;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (!oldHeaderFound) {
|
|
79
|
+
throw new CsvColumnDoesNotExistError(sanitizedTableName, oldHeaderName);
|
|
80
|
+
}
|
|
81
|
+
csvContents[0] = newHeaders;
|
|
82
|
+
}
|
|
83
|
+
else if (ast.action === 'drop') {
|
|
84
|
+
const columnName = ast.column;
|
|
85
|
+
assert.isTruthy(columnName, 'No column name found to drop.');
|
|
86
|
+
const columnIndex = csvHeaders.indexOf(columnName);
|
|
87
|
+
if (columnIndex < 0) {
|
|
88
|
+
throw new CsvColumnDoesNotExistError(sanitizedTableName, columnName);
|
|
89
|
+
}
|
|
90
|
+
csvContents.forEach((row) => {
|
|
91
|
+
row.splice(columnIndex, 1);
|
|
80
92
|
});
|
|
81
|
-
return [];
|
|
82
93
|
}
|
|
83
|
-
|
|
94
|
+
else {
|
|
95
|
+
throw new SqlUnsupportedOperationError(sql, `Forgot to handle alter table action: '${stringify(ast.action)}'`, ast);
|
|
96
|
+
}
|
|
97
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
98
|
+
return {
|
|
99
|
+
columnNames: [],
|
|
100
|
+
numberOfRowsAffected: 0,
|
|
101
|
+
values: [],
|
|
102
|
+
};
|
|
84
103
|
},
|
|
85
104
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
|
-
import {
|
|
2
|
+
import { filterMap } from '@augment-vir/common';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { appendCsvRow, nameCsvTableFile } from '../../csv/csv-file.js';
|
|
5
5
|
import { CsvTableExistsError } from '../../errors/csv.error.js';
|
|
6
|
-
import { SqlMissingColumnsError } from '../../errors/sql.error.js';
|
|
7
|
-
import { AstType, CreateKeyword } from '../../sql/ast.js';
|
|
6
|
+
import { SqlMissingColumnsError, SqlUnsupportedOperationError } from '../../errors/sql.error.js';
|
|
8
7
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
9
8
|
/**
|
|
10
9
|
* Handles creating tables.
|
|
@@ -13,26 +12,41 @@ import { defineAstHandler } from '../define-ast-handler.js';
|
|
|
13
12
|
*/
|
|
14
13
|
export const tableCreateHandler = defineAstHandler({
|
|
15
14
|
name: 'table-create',
|
|
16
|
-
async handler({ ast, csvDirPath, sql }) {
|
|
17
|
-
if (ast.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
if (existsSync(tableFilePath)) {
|
|
24
|
-
throw new CsvTableExistsError(sanitizedTableName);
|
|
25
|
-
}
|
|
26
|
-
const headers = filterMap(ast.create_definitions, (definition) => {
|
|
27
|
-
return definition.column.column;
|
|
28
|
-
}, check.isTruthy);
|
|
29
|
-
if (!headers.length) {
|
|
30
|
-
throw new SqlMissingColumnsError(sql, sanitizedTableName);
|
|
31
|
-
}
|
|
32
|
-
await appendCsvRow(headers, tableFilePath);
|
|
33
|
-
});
|
|
34
|
-
return [];
|
|
15
|
+
async handler({ ast, csvDirPath, sql, rejectUnsupportedOperations }) {
|
|
16
|
+
if (ast.variant !== 'create' ||
|
|
17
|
+
ast.format !== 'table' ||
|
|
18
|
+
!ast.name ||
|
|
19
|
+
ast.name.type !== 'identifier') {
|
|
20
|
+
return undefined;
|
|
35
21
|
}
|
|
36
|
-
|
|
22
|
+
const tableName = ast.name.name;
|
|
23
|
+
if (!tableName) {
|
|
24
|
+
if (rejectUnsupportedOperations) {
|
|
25
|
+
throw new SqlUnsupportedOperationError(sql, 'No table name found when creating a table.', ast);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
30
|
+
csvDirPath,
|
|
31
|
+
tableName,
|
|
32
|
+
});
|
|
33
|
+
if (existsSync(tableFilePath)) {
|
|
34
|
+
throw new CsvTableExistsError(sanitizedTableName);
|
|
35
|
+
}
|
|
36
|
+
const headers = filterMap(ast.definition || [], (definition) => {
|
|
37
|
+
if (definition.type !== 'definition' || !definition.name) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return definition.name;
|
|
41
|
+
}, check.isTruthy);
|
|
42
|
+
if (!headers.length) {
|
|
43
|
+
throw new SqlMissingColumnsError(sql, sanitizedTableName);
|
|
44
|
+
}
|
|
45
|
+
await appendCsvRow(headers, tableFilePath);
|
|
46
|
+
return {
|
|
47
|
+
columnNames: [],
|
|
48
|
+
numberOfRowsAffected: 0,
|
|
49
|
+
values: [],
|
|
50
|
+
};
|
|
37
51
|
},
|
|
38
52
|
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { awaitedForEach } from '@augment-vir/common';
|
|
2
1
|
import { existsSync } from 'node:fs';
|
|
3
2
|
import { rm } from 'node:fs/promises';
|
|
4
|
-
import { nameCsvTableFile } from '../../csv/csv-file.js';
|
|
3
|
+
import { nameCsvTableFile, readCsvFile } from '../../csv/csv-file.js';
|
|
5
4
|
import { CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
-
import { AstType } from '../../sql/ast.js';
|
|
7
5
|
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
6
|
/**
|
|
9
7
|
* Handles dropping tables.
|
|
@@ -13,24 +11,34 @@ import { defineAstHandler } from '../define-ast-handler.js';
|
|
|
13
11
|
export const tableDropHandler = defineAstHandler({
|
|
14
12
|
name: 'table-drop',
|
|
15
13
|
async handler({ ast, csvDirPath }) {
|
|
16
|
-
if (ast.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
14
|
+
if (ast.variant !== 'drop') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const tableName = ast.target.name;
|
|
18
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
19
|
+
csvDirPath,
|
|
20
|
+
tableName,
|
|
21
|
+
});
|
|
22
|
+
if (existsSync(tableFilePath)) {
|
|
23
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
24
|
+
await rm(tableFilePath);
|
|
25
|
+
return {
|
|
26
|
+
columnNames: csvContents[0] || [],
|
|
27
|
+
numberOfRowsAffected: csvContents.length - 1,
|
|
28
|
+
values: csvContents.slice(1),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
else if (ast.condition[0]?.variant === 'if' &&
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
33
|
+
ast.condition[0]?.condition.variant === 'exists') {
|
|
34
|
+
return {
|
|
35
|
+
columnNames: [],
|
|
36
|
+
numberOfRowsAffected: 0,
|
|
37
|
+
values: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new CsvTableDoesNotExistError(sanitizedTableName);
|
|
33
42
|
}
|
|
34
|
-
return undefined;
|
|
35
43
|
},
|
|
36
44
|
});
|
package/dist/engine/params.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
-
import { type
|
|
3
|
-
import { type SqlAst } from '../sql/ast.js';
|
|
4
|
-
import { type Sql } from '../sql/sql.js';
|
|
2
|
+
import { type Sql, type SqliteAst } from 'sqlite-ast';
|
|
5
3
|
/**
|
|
6
4
|
* Options for rejecting unsupported operations (rather than ignoring them).
|
|
7
5
|
*
|
|
@@ -16,26 +14,15 @@ export type RejectUnsupportedOptions = PartialWithUndefined<{
|
|
|
16
14
|
*/
|
|
17
15
|
rejectUnsupportedOperations: boolean;
|
|
18
16
|
}>;
|
|
19
|
-
/**
|
|
20
|
-
* Options for parsing SQL.
|
|
21
|
-
*
|
|
22
|
-
* @category Internal
|
|
23
|
-
*/
|
|
24
|
-
export type ParseSqlOptions = PartialWithUndefined<{
|
|
25
|
-
/**
|
|
26
|
-
* A custom implementation of the Parser from
|
|
27
|
-
* [node-sql-parser](https://npmjs.com/package/node-sql-parser). If not provided, one will be
|
|
28
|
-
* constructed.
|
|
29
|
-
*/
|
|
30
|
-
parser: Parser;
|
|
31
|
-
}> & RejectUnsupportedOptions;
|
|
32
17
|
/**
|
|
33
18
|
* Parameters for AST handlers.
|
|
34
19
|
*
|
|
35
20
|
* @category Internal
|
|
36
21
|
*/
|
|
37
22
|
export type AstHandlerParams = {
|
|
38
|
-
ast:
|
|
23
|
+
ast: Extract<SqliteAst, {
|
|
24
|
+
type: 'statement';
|
|
25
|
+
}>;
|
|
39
26
|
sql: Sql;
|
|
40
27
|
/**
|
|
41
28
|
* Path to the folder or directory containing all the CSV files. (Each CSV file is treated as an
|
|
@@ -48,4 +35,4 @@ export type AstHandlerParams = {
|
|
|
48
35
|
*
|
|
49
36
|
* @category Internal
|
|
50
37
|
*/
|
|
51
|
-
export type ExecuteSqlParams =
|
|
38
|
+
export type ExecuteSqlParams = Omit<AstHandlerParams, 'ast' | 'sql'>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Sql } from '
|
|
1
|
+
import { type Sql, type SqliteAst } from 'sqlite-ast';
|
|
2
2
|
import { CsvSqlEngineError } from './csv-sql-engine.error.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generic SQL related error thrown by the csv-sql-engine package. All SQL related errors from this
|
|
@@ -18,8 +18,9 @@ export declare class SqlError extends CsvSqlEngineError {
|
|
|
18
18
|
export declare class SqlUnsupportedOperationError extends SqlError {
|
|
19
19
|
readonly sql: Sql;
|
|
20
20
|
readonly exactFailure: string | undefined;
|
|
21
|
+
readonly ast: SqliteAst;
|
|
21
22
|
readonly name: string;
|
|
22
|
-
constructor(sql: Sql, exactFailure: string | undefined);
|
|
23
|
+
constructor(sql: Sql, exactFailure: string | undefined, ast: SqliteAst);
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Indicates that the original SQL command was not syntactically correct: it cannot be parsed.
|
|
@@ -32,6 +33,12 @@ export declare class SqlParseError extends SqlError {
|
|
|
32
33
|
readonly name: string;
|
|
33
34
|
constructor(sql: Sql, originalError: unknown);
|
|
34
35
|
}
|
|
36
|
+
export declare class SqlAstError extends SqlError {
|
|
37
|
+
readonly ast: SqliteAst;
|
|
38
|
+
readonly failure: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
constructor(ast: SqliteAst, failure: string);
|
|
41
|
+
}
|
|
35
42
|
/**
|
|
36
43
|
* Indicates that a SQL command is missing expected column definitions.
|
|
37
44
|
*
|
package/dist/errors/sql.error.js
CHANGED
|
@@ -18,17 +18,21 @@ export class SqlError extends CsvSqlEngineError {
|
|
|
18
18
|
export class SqlUnsupportedOperationError extends SqlError {
|
|
19
19
|
sql;
|
|
20
20
|
exactFailure;
|
|
21
|
+
ast;
|
|
21
22
|
name = 'SqlUnsupportedOperationError';
|
|
22
|
-
constructor(sql, exactFailure) {
|
|
23
|
+
constructor(sql, exactFailure, ast) {
|
|
23
24
|
super([
|
|
24
25
|
'Unsupported SQL:',
|
|
25
26
|
exactFailure ? ' ' : '',
|
|
26
27
|
exactFailure ? addSuffix({ value: exactFailure, suffix: '.' }) : '',
|
|
27
28
|
'\n',
|
|
28
29
|
indent(trimLines(sql.sql)),
|
|
30
|
+
'\n',
|
|
31
|
+
indent(JSON.stringify(ast, null, 4)),
|
|
29
32
|
].join(''));
|
|
30
33
|
this.sql = sql;
|
|
31
34
|
this.exactFailure = exactFailure;
|
|
35
|
+
this.ast = ast;
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
@@ -46,6 +50,16 @@ export class SqlParseError extends SqlError {
|
|
|
46
50
|
this.originalError = originalError;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
53
|
+
export class SqlAstError extends SqlError {
|
|
54
|
+
ast;
|
|
55
|
+
failure;
|
|
56
|
+
name = 'SqlAstError';
|
|
57
|
+
constructor(ast, failure) {
|
|
58
|
+
super(`SQLite AST parsing failed: ${failure}:\n${indent(JSON.stringify(ast, null, 4))}`);
|
|
59
|
+
this.ast = ast;
|
|
60
|
+
this.failure = failure;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
/**
|
|
50
64
|
* Indicates that a SQL command is missing expected column definitions.
|
|
51
65
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from 'sqlite-ast';
|
|
1
2
|
export * from './csv/csv-file.js';
|
|
2
3
|
export * from './csv/csv-text.js';
|
|
3
4
|
export * from './engine/define-ast-handler.js';
|
|
@@ -10,11 +11,8 @@ export * from './engine/handlers/table-alter.handler.js';
|
|
|
10
11
|
export * from './engine/handlers/table-create.handler.js';
|
|
11
12
|
export * from './engine/handlers/table-drop.handler.js';
|
|
12
13
|
export * from './engine/params.js';
|
|
13
|
-
export * from './engine/sort-values.js';
|
|
14
|
-
export * from './engine/where-matcher.js';
|
|
15
14
|
export * from './errors/csv-sql-engine.error.js';
|
|
16
15
|
export * from './errors/csv.error.js';
|
|
17
16
|
export * from './errors/sql.error.js';
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './
|
|
20
|
-
export * from './sql/sql.js';
|
|
17
|
+
export * from './util/sort-values.js';
|
|
18
|
+
export * from './util/where-matcher.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from 'sqlite-ast';
|
|
1
2
|
export * from './csv/csv-file.js';
|
|
2
3
|
export * from './csv/csv-text.js';
|
|
3
4
|
export * from './engine/define-ast-handler.js';
|
|
@@ -10,11 +11,8 @@ export * from './engine/handlers/table-alter.handler.js';
|
|
|
10
11
|
export * from './engine/handlers/table-create.handler.js';
|
|
11
12
|
export * from './engine/handlers/table-drop.handler.js';
|
|
12
13
|
export * from './engine/params.js';
|
|
13
|
-
export * from './engine/sort-values.js';
|
|
14
|
-
export * from './engine/where-matcher.js';
|
|
15
14
|
export * from './errors/csv-sql-engine.error.js';
|
|
16
15
|
export * from './errors/csv.error.js';
|
|
17
16
|
export * from './errors/sql.error.js';
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './
|
|
20
|
-
export * from './sql/sql.js';
|
|
17
|
+
export * from './util/sort-values.js';
|
|
18
|
+
export * from './util/where-matcher.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SqliteAstNode } from 'sqlite-ast';
|
|
2
|
+
/** @throws If the request type is not there. */
|
|
3
|
+
export declare function getAstType<const Ast extends SqliteAstNode | undefined, const TypeName extends NonNullable<Ast>['type']>(node: Ast, typeName: TypeName): Extract<Ast, {
|
|
4
|
+
type: TypeName;
|
|
5
|
+
}> | undefined;
|
|
6
|
+
export declare function getAstVariant<const Ast extends Extract<SqliteAstNode, {
|
|
7
|
+
variant: string;
|
|
8
|
+
}> | undefined, const VariantName extends NonNullable<Ast>['variant']>(node: Ast, variant: VariantName): Extract<Ast, {
|
|
9
|
+
variant: VariantName;
|
|
10
|
+
}> | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @throws If the request type is not there. */
|
|
2
|
+
export function getAstType(node, typeName) {
|
|
3
|
+
if (!node) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
else if (node.type === typeName) {
|
|
7
|
+
return node;
|
|
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;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type MaybeArray } from '@augment-vir/common';
|
|
2
|
+
import { type SqliteAstNode } from 'sqlite-ast';
|
|
3
|
+
import { type CsvFile } from '../csv/csv-file.js';
|
|
4
|
+
/**
|
|
5
|
+
* Finds all row indexes that match the given SQL where conditions.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
* @returns An array of row indexes that match the given where condition.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findWhereMatches(expressions: Readonly<MaybeArray<Readonly<SqliteAstNode>>> | undefined, csvContents: Readonly<CsvFile>, csvFilePath: string): number[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ensureArray, extractDuplicates, filterMap, removeDuplicates, removeSuffix, } from '@augment-vir/common';
|
|
2
|
+
import { csvExtension } from '../csv/csv-file.js';
|
|
3
|
+
import { CsvColumnDoesNotExistError, CsvFileMissingHeadersError } from '../errors/csv.error.js';
|
|
4
|
+
/**
|
|
5
|
+
* Finds all row indexes that match the given SQL where conditions.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
* @returns An array of row indexes that match the given where condition.
|
|
9
|
+
*/
|
|
10
|
+
export function findWhereMatches(expressions, csvContents, csvFilePath) {
|
|
11
|
+
/**
|
|
12
|
+
* These must be sorted from greatest to least so that deleting rows does not mess up the
|
|
13
|
+
* indexes.
|
|
14
|
+
*/
|
|
15
|
+
const allIndexes = removeDuplicates((expressions ? ensureArray(expressions) : [undefined]).flatMap((expression) => innerFindWhereMatches(expression, csvContents, csvFilePath))).sort((a, b) => b - a);
|
|
16
|
+
return allIndexes;
|
|
17
|
+
}
|
|
18
|
+
function innerFindWhereMatches(where, csvContents, csvFilePath) {
|
|
19
|
+
if (!where) {
|
|
20
|
+
return csvContents.map((value, index) => index);
|
|
21
|
+
}
|
|
22
|
+
else if (where.type === 'expression' &&
|
|
23
|
+
where.variant === 'operation' &&
|
|
24
|
+
where.format === 'binary') {
|
|
25
|
+
if (where.operation === 'or') {
|
|
26
|
+
return removeDuplicates([
|
|
27
|
+
...innerFindWhereMatches(where.left, csvContents, csvFilePath),
|
|
28
|
+
...innerFindWhereMatches(where.right, csvContents, csvFilePath),
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
else if (where.operation === 'and') {
|
|
32
|
+
return extractDuplicates([
|
|
33
|
+
...innerFindWhereMatches(where.left, csvContents, csvFilePath),
|
|
34
|
+
...innerFindWhereMatches(where.right, csvContents, csvFilePath),
|
|
35
|
+
]).duplicates;
|
|
36
|
+
}
|
|
37
|
+
else if (where.operation === '=') {
|
|
38
|
+
const headers = csvContents[0];
|
|
39
|
+
if (!headers) {
|
|
40
|
+
throw new CsvFileMissingHeadersError(csvFilePath);
|
|
41
|
+
}
|
|
42
|
+
if (where.left.type !== 'identifier' || where.left.variant !== 'column') {
|
|
43
|
+
throw new Error(`Expected column identifier on left side of '=' operation`);
|
|
44
|
+
}
|
|
45
|
+
if (where.right.type !== 'literal') {
|
|
46
|
+
throw new Error(`Expected literal value on right side of '=' operation`);
|
|
47
|
+
}
|
|
48
|
+
const columnIndex = headers.indexOf(where.left.name);
|
|
49
|
+
if (columnIndex < 0) {
|
|
50
|
+
throw new CsvColumnDoesNotExistError(removeSuffix({ value: csvFilePath, suffix: csvExtension }), where.left.name);
|
|
51
|
+
}
|
|
52
|
+
const rightValue = where.right.value;
|
|
53
|
+
return filterMap(csvContents, (row, index) => index, (index, row) => {
|
|
54
|
+
/** Don't select from the header row. */
|
|
55
|
+
const isHeaderRow = !index;
|
|
56
|
+
return !isHeaderRow && String(row[columnIndex]) === rightValue;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
throw new Error(`Unsupported WHERE operation: '${where.operation}'`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error(`Unsupported WHERE expression type: '${where.type}'`);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "csv-sql-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "API for executing SQL statements on CSV files.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CSV",
|
|
@@ -43,17 +43,17 @@
|
|
|
43
43
|
"test:update": "npm run test update"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@augment-vir/assert": "^31.
|
|
47
|
-
"@augment-vir/common": "^31.
|
|
48
|
-
"@augment-vir/node": "^31.
|
|
46
|
+
"@augment-vir/assert": "^31.57.0",
|
|
47
|
+
"@augment-vir/common": "^31.57.0",
|
|
48
|
+
"@augment-vir/node": "^31.57.0",
|
|
49
49
|
"@sinclair/typebox": "^0.34.45",
|
|
50
50
|
"d3-dsv": "^3.0.1",
|
|
51
|
-
"node-sql-parser": "^5.3.13",
|
|
52
51
|
"object-shape-tester": "^6.11.0",
|
|
53
|
-
"
|
|
52
|
+
"sqlite-ast": "^0.0.7",
|
|
53
|
+
"sqlite-parser": "^1.0.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@augment-vir/test": "^31.
|
|
56
|
+
"@augment-vir/test": "^31.57.0",
|
|
57
57
|
"@eslint/eslintrc": "^3.3.3",
|
|
58
58
|
"@eslint/js": "^9.39.2",
|
|
59
59
|
"@stylistic/eslint-plugin": "^5.6.1",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type CsvFile } from '../csv/csv-file.js';
|
|
2
|
-
import { type Where } from '../sql/ast.js';
|
|
3
|
-
/**
|
|
4
|
-
* Finds all row indexes that match the given SQL where conditions.
|
|
5
|
-
*
|
|
6
|
-
* @category Internal
|
|
7
|
-
* @returns An array of row indexes that match the given where condition.
|
|
8
|
-
*/
|
|
9
|
-
export declare function findWhereMatches(where: Readonly<Where>, csvContents: Readonly<CsvFile>, csvFilePath: string): number[];
|