Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ For the show statements, please refer to the [MySQL Docs about SHOW Statements](
* ALTER_INDEX
* ALTER_PROCEDURE
* ANON_BLOCK (BigQuery and Oracle dialects only)
* DELIMITER (MySQL dialect only — sets the statement terminator used by the
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an example to the readme of identifying a set of queries after a DELIMITER change and how the user should use the information provided by the library to interpret each query.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a "Working with MySQL DELIMITER" section to the README. It shows:

  • A DELIMITER-using input with a CREATE PROCEDURE and a reset to ;.
  • The resulting identify() output with newDelimiter/endStatement populated on the relevant statements.
  • A short code snippet showing how a consumer should interpret the results: skip type === 'DELIMITER' (client-side only, NO_OP) and strip endStatement from each remaining statement's text before sending it to the server.

Also added an "Each returned statement has…" bullet list in the API section documenting every field, including the new endStatement and newDelimiter.


Generated by Claude Code

client for subsequent statements, e.g. `DELIMITER $$` / `DELIMITER ;`)
* SHOW_BINARY (MySQL and generic dialects only)
* SHOW_BINLOG (MySQL and generic dialects only)
* SHOW_CHARACTER (MySQL and generic dialects only)
Expand Down Expand Up @@ -103,6 +105,8 @@ Execution types allow to know what is the query behavior
* `MODIFICATION:` is when the query modificate the database somehow (structure or data)
* `INFORMATION:` is show some data information such as a profile data
* `ANON_BLOCK: ` is for an anonymous block query which may contain multiple statements of unknown type (BigQuery and Oracle dialects only)
* `NO_OP:` the statement has no effect on the database server; currently used for `DELIMITER`, which is a client-side directive that changes how subsequent statements are split
* `TRANSACTION:` transaction-control statements like `BEGIN`, `COMMIT`, `ROLLBACK`
* `UNKNOWN`: (only available if strict mode is disabled)

## Installation
Expand Down Expand Up @@ -153,6 +157,69 @@ console.log(statements);
1. `strict (bool)`: allow disable strict mode which will ignore unknown types *(default=true)*
2. `dialect (string)`: Specify your database dialect, values: `generic`, `mysql`, `oracle`, `psql`, `sqlite` and `mssql`. *(default=generic)*

Each returned statement has:

* `start`, `end`, `text`: position and raw text (including the terminator).
* `type`, `executionType`.
* `parameters`, `tables`, `columns`.
* `delimiter` (optional): the terminator string that ended this statement (e.g. `;`, `$`, `$$`). Absent if the statement ran to EOF without a terminator, or for `DELIMITER` statements (terminated by end-of-line).
* `newDelimiter` (optional, only on `DELIMITER` statements): the new terminator string that should be used for the statements that follow.

## Working with MySQL `DELIMITER`

The `mysql` dialect understands the client-side `DELIMITER` directive used by the `mysql` CLI, MySQL Workbench, etc. to author stored programs whose bodies contain inner `;` terminators. Pass `{ dialect: 'mysql' }` to enable it.

```js
import { identify } from 'sql-query-identifier';

const statements = identify(
`DELIMITER $$
CREATE PROCEDURE foo()
BEGIN
SELECT 1;
SELECT 2;
END$$
DELIMITER ;
SELECT 3;`,
{ dialect: 'mysql' },
);
```

`statements` is:

```js
[
{ type: 'DELIMITER', executionType: 'NO_OP', text: 'DELIMITER $$', newDelimiter: '$$', /* ... */ },
{ type: 'CREATE_PROCEDURE', executionType: 'MODIFICATION', text: 'CREATE PROCEDURE foo()\nBEGIN\n SELECT 1;\n SELECT 2;\nEND$$', delimiter: '$$', /* ... */ },
{ type: 'DELIMITER', executionType: 'NO_OP', text: 'DELIMITER ;', newDelimiter: ';', /* ... */ },
{ type: 'SELECT', executionType: 'LISTING', text: 'SELECT 3;', delimiter: ';', /* ... */ },
]
```

Because `DELIMITER` is a client-side directive (the server never sees it), its `executionType` is `NO_OP`. To execute the identified statements against a MySQL server, skip any with `type === 'DELIMITER'` and strip the `delimiter` from each remaining statement's `text` before sending it:

```js
for (const stmt of statements) {
if (stmt.type === 'DELIMITER') continue; // client-side only
const sql = stmt.delimiter
? stmt.text.slice(0, -stmt.delimiter.length)
: stmt.text;
await connection.query(sql);
}
```

### DELIMITER validation

The parser rejects delimiter values that would break subsequent tokenization:

* Empty argument (`DELIMITER` with no value)
* Backslash (`\`) — matches mysql-shell's explicit rejection
* String/identifier quote characters (`'`, `"`, `` ` ``)
* Inline comment markers (`--`, `#`)
* Block-comment characters (`/`, `*`)

In strict mode (the default), an invalid `DELIMITER` throws. In non-strict mode, the `DELIMITER` statement is still returned but without a `newDelimiter` field, and the previous delimiter is kept — matching mysql-shell's behaviour of leaving the old delimiter in effect when an argument is rejected.

## Contributing

It is required to use [editorconfig](https://editorconfig.org/) and please write and run specs before pushing any changes:
Expand Down
19 changes: 17 additions & 2 deletions src/defines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export type StatementType =
| 'COMMIT'
| 'ROLLBACK'
| 'ANON_BLOCK'
| 'DELIMITER'
| 'UNKNOWN';

export type ExecutionType =
Expand All @@ -83,6 +84,7 @@ export type ExecutionType =
| 'INFORMATION'
| 'ANON_BLOCK'
| 'TRANSACTION'
| 'NO_OP'
| 'UNKNOWN';

export interface ParamTypes {
Expand Down Expand Up @@ -126,14 +128,26 @@ export interface IdentifyResult {
parameters: string[];
tables: TableReference[];
columns: ColumnReference[];
/**
* The terminator string (e.g. `;`, `$`, `$$`) that ended this statement.
* `undefined` when the statement ran to EOF without a terminator, or for
* `DELIMITER` statements (which are terminated by end-of-line, not a
* delimiter).
*/
delimiter?: string;
/**
* Only set for statements of type `DELIMITER`. The new terminator string
* that should be used for statements that follow.
*/
newDelimiter?: string;
}

export interface Statement {
start: number;
end: number;
type?: StatementType;
executionType?: ExecutionType;
endStatement?: string;
delimiter?: string;
canEnd?: boolean;
definer?: number;
algorithm?: number;
Expand All @@ -142,6 +156,7 @@ export interface Statement {
tables: TableReference[];
columns: ColumnReference[];
isCte?: boolean;
newDelimiter?: string;
}

export interface ConcreteStatement extends Statement {
Expand All @@ -162,7 +177,7 @@ export interface Token {
| 'comment-inline'
| 'comment-block'
| 'string'
| 'semicolon'
| 'delimiter'
| 'keyword'
| 'parameter'
| 'table'
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export function identify(query: string, options: IdentifyOptions = {}): Identify
tables: statement.tables || [],
columns: statement.columns || [],
};
// DELIMITER's internal `delimiter` is a `\n` sentinel, not a real
// terminator; don't expose it to consumers.
if (statement.type !== 'DELIMITER' && statement.delimiter) {
result.delimiter = statement.delimiter;
}
if (statement.newDelimiter) {
result.newDelimiter = statement.newDelimiter;
}
return result;
});
}
Expand Down
Loading
Loading