csv-sql-engine 0.0.0 → 0.0.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/LICENSE-CC0 +121 -0
- package/LICENSE-MIT +21 -0
- package/README.md +49 -0
- package/dist/augments/trim-lines.d.ts +6 -0
- package/dist/augments/trim-lines.js +12 -0
- package/dist/csv/csv-file.d.ts +70 -0
- package/dist/csv/csv-file.js +98 -0
- package/dist/csv/csv-text.d.ts +52 -0
- package/dist/csv/csv-text.js +79 -0
- package/dist/engine/define-ast-handler.d.ts +27 -0
- package/dist/engine/define-ast-handler.js +8 -0
- package/dist/engine/engine.d.ts +16 -0
- package/dist/engine/engine.js +66 -0
- package/dist/engine/handlers/row-delete.handler.d.ts +6 -0
- package/dist/engine/handlers/row-delete.handler.js +51 -0
- package/dist/engine/handlers/row-insert.handler.d.ts +6 -0
- package/dist/engine/handlers/row-insert.handler.js +54 -0
- package/dist/engine/handlers/row-select.handler.d.ts +6 -0
- package/dist/engine/handlers/row-select.handler.js +46 -0
- package/dist/engine/handlers/row-update.handler.d.ts +6 -0
- package/dist/engine/handlers/row-update.handler.js +61 -0
- package/dist/engine/handlers/table-alter.handler.d.ts +6 -0
- package/dist/engine/handlers/table-alter.handler.js +85 -0
- package/dist/engine/handlers/table-create.handler.d.ts +6 -0
- package/dist/engine/handlers/table-create.handler.js +38 -0
- package/dist/engine/handlers/table-drop.handler.d.ts +6 -0
- package/dist/engine/handlers/table-drop.handler.js +36 -0
- package/dist/engine/params.d.ts +51 -0
- package/dist/engine/params.js +1 -0
- package/dist/engine/where-matcher.d.ts +9 -0
- package/dist/engine/where-matcher.js +52 -0
- package/dist/errors/csv-sql-engine.error.d.ts +9 -0
- package/dist/errors/csv-sql-engine.error.js +9 -0
- package/dist/errors/csv.error.d.ts +57 -0
- package/dist/errors/csv.error.js +68 -0
- package/dist/errors/sql.error.d.ts +45 -0
- package/dist/errors/sql.error.js +64 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/sql/ast.d.ts +472 -0
- package/dist/sql/ast.js +266 -0
- package/dist/sql/parse-sql.d.ts +9 -0
- package/dist/sql/parse-sql.js +38 -0
- package/dist/sql/sql.d.ts +24 -0
- package/dist/sql/sql.js +26 -0
- package/package.json +99 -11
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
3
|
+
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
4
|
+
import { sortValues } from '../../csv/csv-text.js';
|
|
5
|
+
import { AstType } from '../../sql/ast.js';
|
|
6
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
7
|
+
import { findWhereMatches } from '../where-matcher.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles deleting rows.
|
|
10
|
+
*
|
|
11
|
+
* @category SQL Handler
|
|
12
|
+
*/
|
|
13
|
+
export const rowDeleteHandler = defineAstHandler({
|
|
14
|
+
name: 'row-delete',
|
|
15
|
+
async handler({ ast, csvDirPath }) {
|
|
16
|
+
if (ast.type === AstType.Delete) {
|
|
17
|
+
const tableNames = ast.table.map((table) => table.table);
|
|
18
|
+
const returning = 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 returning = returningRequirement
|
|
31
|
+
? csvContents
|
|
32
|
+
.filter((row, index) => rowIndexesToDelete.includes(index))
|
|
33
|
+
.map((row) => sortValues({
|
|
34
|
+
csvFileHeaderOrder: csvHeaders,
|
|
35
|
+
sqlQueryHeaderOrder: returningRequirement.columns.map((column) => column.expr.column),
|
|
36
|
+
from: {
|
|
37
|
+
csvFile: row,
|
|
38
|
+
},
|
|
39
|
+
}))
|
|
40
|
+
: undefined;
|
|
41
|
+
rowIndexesToDelete.forEach((rowIndexToDelete) => {
|
|
42
|
+
csvContents.splice(rowIndexToDelete, 1);
|
|
43
|
+
});
|
|
44
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
45
|
+
return returning;
|
|
46
|
+
});
|
|
47
|
+
return returning.flat().filter(check.isTruthy);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
3
|
+
import { appendCsvRow, nameCsvTableFile, readCsvHeaders } from '../../csv/csv-file.js';
|
|
4
|
+
import { sortValues } from '../../csv/csv-text.js';
|
|
5
|
+
import { AstType } from '../../sql/ast.js';
|
|
6
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
7
|
+
/**
|
|
8
|
+
* Handles inserting rows.
|
|
9
|
+
*
|
|
10
|
+
* @category SQL Handler
|
|
11
|
+
*/
|
|
12
|
+
export const rowInsertHandler = defineAstHandler({
|
|
13
|
+
name: 'row-insert',
|
|
14
|
+
async handler({ ast, csvDirPath }) {
|
|
15
|
+
if (ast.type === AstType.Insert) {
|
|
16
|
+
const tableNames = ast.table.map((table) => table.table);
|
|
17
|
+
const returning = await awaitedBlockingMap(tableNames, async (tableName) => {
|
|
18
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
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 values = ast.columns
|
|
28
|
+
? sortValues({
|
|
29
|
+
csvFileHeaderOrder,
|
|
30
|
+
sqlQueryHeaderOrder: ast.columns,
|
|
31
|
+
from: {
|
|
32
|
+
sqlQuery: rawValues,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
: rawValues;
|
|
36
|
+
await appendCsvRow(values, tableFilePath);
|
|
37
|
+
if (ast.returning) {
|
|
38
|
+
return sortValues({
|
|
39
|
+
csvFileHeaderOrder,
|
|
40
|
+
sqlQueryHeaderOrder: ast.returning.columns.map((column) => column.expr.column),
|
|
41
|
+
from: {
|
|
42
|
+
csvFile: values,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return returning.filter(check.isTruthy);
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
3
|
+
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
4
|
+
import { sortValues } from '../../csv/csv-text.js';
|
|
5
|
+
import { AstType } from '../../sql/ast.js';
|
|
6
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
7
|
+
import { findWhereMatches } from '../where-matcher.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles SQL selection.
|
|
10
|
+
*
|
|
11
|
+
* @category SQL Handler
|
|
12
|
+
*/
|
|
13
|
+
export const rowSelectHandler = defineAstHandler({
|
|
14
|
+
name: 'row-select',
|
|
15
|
+
async handler({ ast, csvDirPath }) {
|
|
16
|
+
if (ast.type === AstType.Select) {
|
|
17
|
+
const tableNames = ast.from.map((table) => table.table);
|
|
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
|
+
const selection = csvContents
|
|
31
|
+
.filter((row, index) => rowIndexesToSelect.includes(index))
|
|
32
|
+
.map((row) => sortValues({
|
|
33
|
+
csvFileHeaderOrder: csvHeaders,
|
|
34
|
+
sqlQueryHeaderOrder: columnNames,
|
|
35
|
+
from: {
|
|
36
|
+
csvFile: row,
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
40
|
+
return selection;
|
|
41
|
+
});
|
|
42
|
+
return allSelections.flat().filter(check.isTruthy);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { assertWrap, check } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedBlockingMap } from '@augment-vir/common';
|
|
3
|
+
import { createCsvHeaderMaps, nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile, } from '../../csv/csv-file.js';
|
|
4
|
+
import { sortValues } from '../../csv/csv-text.js';
|
|
5
|
+
import { CsvColumnDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
+
import { AstType } from '../../sql/ast.js';
|
|
7
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
|
+
import { findWhereMatches } from '../where-matcher.js';
|
|
9
|
+
/**
|
|
10
|
+
* Handles updating rows.
|
|
11
|
+
*
|
|
12
|
+
* @category SQL Handler
|
|
13
|
+
*/
|
|
14
|
+
export const rowUpdateHandler = defineAstHandler({
|
|
15
|
+
name: 'row-update',
|
|
16
|
+
async handler({ ast, csvDirPath }) {
|
|
17
|
+
if (ast.type === AstType.Update) {
|
|
18
|
+
const tableNames = ast.table.map((table) => table.table);
|
|
19
|
+
const returning = await awaitedBlockingMap(tableNames, async (tableName) => {
|
|
20
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
21
|
+
csvDirPath,
|
|
22
|
+
tableName,
|
|
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 returning = returningRequirement
|
|
44
|
+
? csvContents
|
|
45
|
+
.filter((row, index) => rowIndexesToUpdate.includes(index))
|
|
46
|
+
.map((row) => sortValues({
|
|
47
|
+
csvFileHeaderOrder: csvHeaders,
|
|
48
|
+
sqlQueryHeaderOrder: returningRequirement.columns.map((column) => column.expr.column),
|
|
49
|
+
from: {
|
|
50
|
+
csvFile: row,
|
|
51
|
+
},
|
|
52
|
+
}))
|
|
53
|
+
: undefined;
|
|
54
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
55
|
+
return returning;
|
|
56
|
+
});
|
|
57
|
+
return returning.flat().filter(check.isTruthy);
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { assert } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedForEach, stringify } from '@augment-vir/common';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { nameCsvTableFile, readCsvFile, readCsvHeaders, writeCsvFile } from '../../csv/csv-file.js';
|
|
5
|
+
import { CsvColumnDoesNotExistError, CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
+
import { AlterExpressionAction } from '../../sql/ast.js';
|
|
7
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles altering tables.
|
|
10
|
+
*
|
|
11
|
+
* @category SQL Handler
|
|
12
|
+
*/
|
|
13
|
+
export const tableAlterHandler = defineAstHandler({
|
|
14
|
+
name: 'table-alter',
|
|
15
|
+
async handler({ ast, csvDirPath }) {
|
|
16
|
+
if (ast.type === 'alter') {
|
|
17
|
+
const tableNames = ast.table.map((table) => table.table);
|
|
18
|
+
await awaitedForEach(tableNames, async (tableName) => {
|
|
19
|
+
await awaitedForEach(ast.expr, async (expression) => {
|
|
20
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
21
|
+
csvDirPath,
|
|
22
|
+
tableName,
|
|
23
|
+
});
|
|
24
|
+
if (!existsSync(tableFilePath)) {
|
|
25
|
+
throw new CsvTableDoesNotExistError(sanitizedTableName);
|
|
26
|
+
}
|
|
27
|
+
/** Mutate this to apply the table alterations. */
|
|
28
|
+
const csvContents = await readCsvFile(tableFilePath);
|
|
29
|
+
const csvHeaders = await readCsvHeaders({
|
|
30
|
+
csvContents,
|
|
31
|
+
sanitizedTableName,
|
|
32
|
+
});
|
|
33
|
+
if (expression.action === AlterExpressionAction.Add) {
|
|
34
|
+
const defaultValue = expression.default_val?.value.value || '';
|
|
35
|
+
const newHeaderName = expression.column.column;
|
|
36
|
+
csvContents.forEach((row, index) => {
|
|
37
|
+
if (index) {
|
|
38
|
+
row.push(defaultValue);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
row.push(newHeaderName);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (expression.action === AlterExpressionAction.Rename) {
|
|
46
|
+
const oldHeaderName = expression.old_column.column;
|
|
47
|
+
const newHeaderName = expression.column.column;
|
|
48
|
+
let oldHeaderFound = false;
|
|
49
|
+
const newHeaders = csvHeaders.map((header) => {
|
|
50
|
+
if (header === oldHeaderName) {
|
|
51
|
+
oldHeaderFound = true;
|
|
52
|
+
return newHeaderName;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
return header;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (!oldHeaderFound) {
|
|
59
|
+
throw new CsvColumnDoesNotExistError(sanitizedTableName, oldHeaderName);
|
|
60
|
+
}
|
|
61
|
+
csvContents[0] = newHeaders;
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
63
|
+
}
|
|
64
|
+
else if (expression.action === AlterExpressionAction.Drop) {
|
|
65
|
+
const columnName = expression.column.column;
|
|
66
|
+
const columnIndex = csvHeaders.indexOf(columnName);
|
|
67
|
+
if (columnIndex < 0) {
|
|
68
|
+
throw new CsvColumnDoesNotExistError(sanitizedTableName, columnName);
|
|
69
|
+
}
|
|
70
|
+
csvContents.forEach((row) => {
|
|
71
|
+
row.splice(columnIndex, 1);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
assert.tsType(expression).equals();
|
|
76
|
+
assert.never(`Forgot to handle expression action ${stringify(expression)}`);
|
|
77
|
+
}
|
|
78
|
+
await writeCsvFile(tableFilePath, csvContents);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
},
|
|
85
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { awaitedForEach, filterMap } from '@augment-vir/common';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { appendCsvRow, nameCsvTableFile } from '../../csv/csv-file.js';
|
|
5
|
+
import { CsvTableExistsError } from '../../errors/csv.error.js';
|
|
6
|
+
import { SqlMissingColumnsError } from '../../errors/sql.error.js';
|
|
7
|
+
import { AstType, CreateKeyword } from '../../sql/ast.js';
|
|
8
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
9
|
+
/**
|
|
10
|
+
* Handles creating tables.
|
|
11
|
+
*
|
|
12
|
+
* @category SQL Handler
|
|
13
|
+
*/
|
|
14
|
+
export const tableCreateHandler = defineAstHandler({
|
|
15
|
+
name: 'table-create',
|
|
16
|
+
async handler({ ast, csvDirPath, sql }) {
|
|
17
|
+
if (ast.type === AstType.Create && ast.keyword === CreateKeyword.Table) {
|
|
18
|
+
await awaitedForEach(ast.table, async (table) => {
|
|
19
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
20
|
+
csvDirPath,
|
|
21
|
+
tableName: table.table,
|
|
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 [];
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { awaitedForEach } from '@augment-vir/common';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import { nameCsvTableFile } from '../../csv/csv-file.js';
|
|
5
|
+
import { CsvTableDoesNotExistError } from '../../errors/csv.error.js';
|
|
6
|
+
import { AstType } from '../../sql/ast.js';
|
|
7
|
+
import { defineAstHandler } from '../define-ast-handler.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles dropping tables.
|
|
10
|
+
*
|
|
11
|
+
* @category SQL Handler
|
|
12
|
+
*/
|
|
13
|
+
export const tableDropHandler = defineAstHandler({
|
|
14
|
+
name: 'table-drop',
|
|
15
|
+
async handler({ ast, csvDirPath }) {
|
|
16
|
+
if (ast.type === AstType.Drop) {
|
|
17
|
+
await awaitedForEach(ast.name, async (table) => {
|
|
18
|
+
const { tableFilePath, sanitizedTableName } = nameCsvTableFile({
|
|
19
|
+
csvDirPath,
|
|
20
|
+
tableName: table.table,
|
|
21
|
+
});
|
|
22
|
+
if (existsSync(tableFilePath)) {
|
|
23
|
+
await rm(tableFilePath);
|
|
24
|
+
}
|
|
25
|
+
else if (ast.prefix === 'if exists') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
throw new CsvTableDoesNotExistError(sanitizedTableName);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type Parser } from 'node-sql-parser';
|
|
3
|
+
import { type SqlAst } from '../sql/ast.js';
|
|
4
|
+
import { type Sql } from '../sql/sql.js';
|
|
5
|
+
/**
|
|
6
|
+
* Options for rejecting unsupported operations (rather than ignoring them).
|
|
7
|
+
*
|
|
8
|
+
* @category Internal
|
|
9
|
+
*/
|
|
10
|
+
export type RejectUnsupportedOptions = PartialWithUndefined<{
|
|
11
|
+
/**
|
|
12
|
+
* - If set to `true`, unsupported operations will throw errors.
|
|
13
|
+
* - When left to `false`, unsupported operations are simply ignored.
|
|
14
|
+
*
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
rejectUnsupportedOperations: boolean;
|
|
18
|
+
}>;
|
|
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
|
+
/**
|
|
33
|
+
* Parameters for AST handlers.
|
|
34
|
+
*
|
|
35
|
+
* @category Internal
|
|
36
|
+
*/
|
|
37
|
+
export type AstHandlerParams = {
|
|
38
|
+
ast: SqlAst;
|
|
39
|
+
sql: Sql;
|
|
40
|
+
/**
|
|
41
|
+
* Path to the folder or directory containing all the CSV files. (Each CSV file is treated as an
|
|
42
|
+
* individual table.)
|
|
43
|
+
*/
|
|
44
|
+
csvDirPath: string;
|
|
45
|
+
} & RejectUnsupportedOptions;
|
|
46
|
+
/**
|
|
47
|
+
* Parameters for executing SQL.
|
|
48
|
+
*
|
|
49
|
+
* @category Internal
|
|
50
|
+
*/
|
|
51
|
+
export type ExecuteSqlParams = ParseSqlOptions & Omit<AstHandlerParams, 'ast' | 'sql'>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
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[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { assert } from '@augment-vir/assert';
|
|
2
|
+
import { extractDuplicates, filterMap, removeDuplicates, removeSuffix } from '@augment-vir/common';
|
|
3
|
+
import { csvExtension } from '../csv/csv-file.js';
|
|
4
|
+
import { CsvColumnDoesNotExistError, CsvFileMissingHeadersError } from '../errors/csv.error.js';
|
|
5
|
+
import { WhereOperator } from '../sql/ast.js';
|
|
6
|
+
/**
|
|
7
|
+
* Finds all row indexes that match the given SQL where conditions.
|
|
8
|
+
*
|
|
9
|
+
* @category Internal
|
|
10
|
+
* @returns An array of row indexes that match the given where condition.
|
|
11
|
+
*/
|
|
12
|
+
export function findWhereMatches(where, csvContents, csvFilePath) {
|
|
13
|
+
/**
|
|
14
|
+
* These must be sorted from greatest to least so that deleting rows does not mess up the
|
|
15
|
+
* indexes.
|
|
16
|
+
*/
|
|
17
|
+
return innerFindWhereMatches(where, csvContents, csvFilePath).sort((a, b) => b - a);
|
|
18
|
+
}
|
|
19
|
+
function innerFindWhereMatches(where, csvContents, csvFilePath) {
|
|
20
|
+
if (where.operator === WhereOperator.Or) {
|
|
21
|
+
return removeDuplicates([
|
|
22
|
+
...innerFindWhereMatches(where.left, csvContents, csvFilePath),
|
|
23
|
+
...innerFindWhereMatches(where.right, csvContents, csvFilePath),
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
else if (where.operator === WhereOperator.And) {
|
|
27
|
+
return extractDuplicates([
|
|
28
|
+
...innerFindWhereMatches(where.left, csvContents, csvFilePath),
|
|
29
|
+
...innerFindWhereMatches(where.right, csvContents, csvFilePath),
|
|
30
|
+
]).duplicates;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
32
|
+
}
|
|
33
|
+
else if (where.operator === WhereOperator.Equals) {
|
|
34
|
+
const headers = csvContents[0];
|
|
35
|
+
if (!headers) {
|
|
36
|
+
throw new CsvFileMissingHeadersError(csvFilePath);
|
|
37
|
+
}
|
|
38
|
+
const columnIndex = headers.indexOf(where.left.column);
|
|
39
|
+
if (columnIndex < 0) {
|
|
40
|
+
throw new CsvColumnDoesNotExistError(removeSuffix({ value: csvFilePath, suffix: csvExtension }), where.left.column);
|
|
41
|
+
}
|
|
42
|
+
return filterMap(csvContents, (row, index) => index, (index, row) => {
|
|
43
|
+
/** Don't select from the header row. */
|
|
44
|
+
const isHeaderRow = !index;
|
|
45
|
+
return !isHeaderRow && String(row[columnIndex]) === String(where.right.value);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
assert.tsType(where.operator).equals();
|
|
50
|
+
throw new Error(`Forgot to implement WHERE operation: '${String(where.operator)}'`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CsvSqlEngineError } from './csv-sql-engine.error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generic CSV related error thrown by the csv-sql-engine package. All CSV related errors from this
|
|
4
|
+
* package extend this error class.
|
|
5
|
+
*
|
|
6
|
+
* @category Error
|
|
7
|
+
*/
|
|
8
|
+
export declare class CsvError extends CsvSqlEngineError {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Indicates that an table CSV file already exists when it shouldn't.
|
|
13
|
+
*
|
|
14
|
+
* @category Error
|
|
15
|
+
*/
|
|
16
|
+
export declare class CsvTableExistsError extends CsvError {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
constructor(tableName: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Indicates that an expected table CSV file does not actually exist.
|
|
22
|
+
*
|
|
23
|
+
* @category Error
|
|
24
|
+
*/
|
|
25
|
+
export declare class CsvTableDoesNotExistError extends CsvError {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
constructor(tableName: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Indicates that parsing of an existing CSV file failed.
|
|
31
|
+
*
|
|
32
|
+
* @category Error
|
|
33
|
+
*/
|
|
34
|
+
export declare class CsvParseError extends CsvError {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Indicates that a CSV file is missing headers.
|
|
39
|
+
*
|
|
40
|
+
* @category Error
|
|
41
|
+
*/
|
|
42
|
+
export declare class CsvFileMissingHeadersError extends CsvParseError {
|
|
43
|
+
readonly csvFilePath: string;
|
|
44
|
+
readonly name: string;
|
|
45
|
+
constructor(csvFilePath: string);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Indicates that an expected column in a CSV file does not actually exist.
|
|
49
|
+
*
|
|
50
|
+
* @category Error
|
|
51
|
+
*/
|
|
52
|
+
export declare class CsvColumnDoesNotExistError extends CsvError {
|
|
53
|
+
readonly sanitizedTableName: string;
|
|
54
|
+
readonly missingColumName: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
constructor(sanitizedTableName: string, missingColumName: string);
|
|
57
|
+
}
|