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.
@@ -1,9 +1,11 @@
1
- import { assert } from '@augment-vir/assert';
2
- import { awaitedForEach, stringify } from '@augment-vir/common';
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 { AlterExpressionAction } from '../../sql/ast.js';
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.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
- });
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
- return undefined;
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 { awaitedForEach, filterMap } from '@augment-vir/common';
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.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 [];
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
- return undefined;
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.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 [];
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
  });
@@ -1,7 +1,5 @@
1
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';
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: SqlAst;
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 = ParseSqlOptions & Omit<AstHandlerParams, 'ast' | 'sql'>;
38
+ export type ExecuteSqlParams = Omit<AstHandlerParams, 'ast' | 'sql'>;
@@ -1,4 +1,4 @@
1
- import { type Sql } from '../sql/sql.js';
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
  *
@@ -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 './sql/ast.js';
19
- export * from './sql/parse-sql.js';
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 './sql/ast.js';
19
- export * from './sql/parse-sql.js';
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
+ }
@@ -1,5 +1,5 @@
1
+ import { type ConsumableValue } from 'sqlite-ast';
1
2
  import { type RequireExactlyOne } from 'type-fest';
2
- import { type ConsumableValue } from '../sql/sql.js';
3
3
  /**
4
4
  * Output from {@link sortValues}.
5
5
  *
@@ -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.2.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.56.0",
47
- "@augment-vir/common": "^31.56.0",
48
- "@augment-vir/node": "^31.56.0",
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
- "sql-template-tag": "^5.2.1"
52
+ "sqlite-ast": "^0.0.7",
53
+ "sqlite-parser": "^1.0.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@augment-vir/test": "^31.56.0",
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[];