rawsql-ts 0.8.3-beta → 0.9.0-beta
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/README.md +167 -448
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/models/ValueComponent.js +1 -1
- package/dist/esm/models/ValueComponent.js.map +1 -1
- package/dist/esm/transformers/SelectableColumnCollector.js +4 -1
- package/dist/esm/transformers/SelectableColumnCollector.js.map +1 -1
- package/dist/esm/transformers/SqlParamInjector.js +133 -0
- package/dist/esm/transformers/SqlParamInjector.js.map +1 -0
- package/dist/esm/transformers/UpstreamSelectQueryFinder.js +10 -6
- package/dist/esm/transformers/UpstreamSelectQueryFinder.js.map +1 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/models/ValueComponent.d.ts +1 -1
- package/dist/esm/types/transformers/SelectableColumnCollector.d.ts +6 -1
- package/dist/esm/types/transformers/SqlParamInjector.d.ts +36 -0
- package/dist/esm/types/transformers/UpstreamSelectQueryFinder.d.ts +6 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models/ValueComponent.d.ts +1 -1
- package/dist/models/ValueComponent.js +1 -1
- package/dist/models/ValueComponent.js.map +1 -1
- package/dist/transformers/SelectableColumnCollector.d.ts +6 -1
- package/dist/transformers/SelectableColumnCollector.js +4 -1
- package/dist/transformers/SelectableColumnCollector.js.map +1 -1
- package/dist/transformers/SqlParamInjector.d.ts +36 -0
- package/dist/transformers/SqlParamInjector.js +137 -0
- package/dist/transformers/SqlParamInjector.js.map +1 -0
- package/dist/transformers/UpstreamSelectQueryFinder.d.ts +6 -3
- package/dist/transformers/UpstreamSelectQueryFinder.js +10 -6
- package/dist/transformers/UpstreamSelectQueryFinder.js.map +1 -1
- package/package.json +1 -1
- package/dist/esm/types/utils/SqlStaticAnalyzer.d.ts +0 -0
- package/dist/esm/utils/SqlStaticAnalyzer.js +0 -2
- package/dist/esm/utils/SqlStaticAnalyzer.js.map +0 -1
- package/dist/utils/SqlStaticAnalyzer.d.ts +0 -0
- package/dist/utils/SqlStaticAnalyzer.js +0 -2
- package/dist/utils/SqlStaticAnalyzer.js.map +0 -1
package/README.md
CHANGED
@@ -8,32 +8,30 @@
|
|
8
8
|
|
9
9
|
🌐 [Online Demo (GitHub Pages)](https://mk3008.github.io/rawsql-ts/)
|
10
10
|
|
11
|
-
rawsql-ts is a high-performance SQL parser and AST transformer library written in TypeScript. It
|
11
|
+
rawsql-ts is a high-performance SQL parser and AST transformer library written in TypeScript. It empowers you to represent raw SQL as objects, enabling flexible manipulation of SQL statements directly within your program. This object-oriented approach allows for partial transformation, decomposition into manageable components, and recombination as needed, dramatically improving the maintainability and reusability of complex SQL.
|
12
|
+
|
13
|
+
It is designed for extensibility and advanced SQL analysis, with initial focus on PostgreSQL syntax but not limited to it. The library enables easy SQL parsing, transformation, and analysis for a wide range of SQL dialects.
|
12
14
|
|
13
15
|
> [!Note]
|
14
16
|
> This library is currently in beta. The API may change until the v1.0 release.
|
15
17
|
|
16
18
|
---
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
With rawsql-ts, raw SQL can be represented as objects, enabling flexible manipulation of SQL statements directly within your program. Objectified SQL can be partially transformed, decomposed into manageable components, and recombined as needed. This approach dramatically improves the maintainability and reusability of complex SQL, making even large-scale queries easy to manage and refactor.
|
21
|
-
|
22
|
-
---
|
23
|
-
|
24
|
-
## Features
|
20
|
+
## Key Features
|
25
21
|
|
26
22
|
- Zero dependencies: fully self-contained and lightweight
|
27
23
|
- High-speed SQL parsing and AST analysis (over 3x faster than major libraries)
|
28
24
|
- Rich utilities for SQL structure transformation and analysis
|
29
25
|
- Advanced SQL formatting capabilities, including multi-line formatting and customizable styles
|
26
|
+
- Dynamic SQL parameter injection for building flexible search queries with `SqlParamInjector`
|
27
|
+
- Static query validation and regression testing against your database schema with `SqlSchemaValidator`, enabling early error detection and robust unit tests for schema changes.
|
30
28
|
|
31
|
-
',borderColor:'rgba(54,162,235,1)',borderWidth:1},{label:'node-sql-parser',data:[0.210,0.223,0.420,0.871],backgroundColor:'rgba(255,206,86,0.8)',borderColor:'rgba(255,206,86,1)',borderWidth:1},{label:'sql-formatter',data:[0.228,0.547,1.057,1.906],backgroundColor:'rgba(255,99,132,0.8)',borderColor:'rgba(255,99,132,1)',borderWidth:1}]},options:{plugins:{legend:{labels:{color:'black'}}},scales:{x:{ticks:{color:'black'}},y:{ticks:{color:'black'}}},backgroundColor:'white'}})
|
29
|
+
',borderColor:'rgba(54,162,235,1)',borderWidth:1},{label:'node-sql-parser',data:[0.210,0.223,0.420,0.871],backgroundColor:'rgba(255,206,86,0.8)',borderColor:'rgba(255,206,86,1)',borderWidth:1},{label:'sql-formatter',data:[0.228,0.547,1.057,1.906],backgroundColor:'rgba(255,99,132,0.8)',borderColor:'rgba(255,99,132,1)',borderWidth:1}]},options:{plugins:{legend:{labels:{color:'black'}}},scales:{x:{ticks:{color:'black'}},y:{ticks:{color:'black'}}},backgroundColor:'white'}}&width=700&height=450)
|
32
30
|
|
33
31
|
> [!Note]
|
34
32
|
> The "Mean" column represents the average time taken to process a query. Lower values indicate faster performance. For more details, see the [Benchmark](#benchmarks).
|
35
33
|
|
36
|
-
##
|
34
|
+
## Browser & CDN Ready
|
37
35
|
|
38
36
|
You can use rawsql-ts directly in modern browsers via CDN (unpkg/jsdelivr)!
|
39
37
|
No Node.js dependencies, no build tools required.
|
@@ -61,509 +59,230 @@ Just import it like this:
|
|
61
59
|
npm install rawsql-ts
|
62
60
|
```
|
63
61
|
|
64
|
-
## Quick Start
|
65
|
-
|
66
|
-
```typescript
|
67
|
-
import { SelectQueryParser, SqlFormatter } from 'rawsql-ts';
|
68
|
-
|
69
|
-
const sql = `SELECT user_id, name FROM users WHERE active = TRUE`;
|
70
|
-
const query = SelectQueryParser.parse(sql);
|
71
|
-
const formatter = new SqlFormatter();
|
72
|
-
const { formattedSql } = formatter.format(query);
|
73
|
-
|
74
|
-
console.log(formattedSql);
|
75
|
-
// => select "user_id", "name" from "users" where "active" = true
|
76
|
-
```
|
77
|
-
|
78
62
|
---
|
79
63
|
|
80
|
-
##
|
81
|
-
|
82
|
-
The `SqlFormatter` class in rawsql-ts is the recommended way to format SQL queries. It provides advanced SQL formatting capabilities, including support for indentation, keyword casing, and line breaks, making it ideal for generating human-readable SQL.
|
83
|
-
|
84
|
-
> [!Note]
|
85
|
-
> While the older `Formatter` class is still available for backward compatibility, `SqlFormatter` offers enhanced functionality and is the preferred choice for new projects. `SqlFormatter` is available starting from version 0.7.
|
86
|
-
|
87
|
-
### Key Features of SqlFormatter
|
88
|
-
|
89
|
-
- **Indentation and Keyword Casing**: Supports customizable indentation and keyword casing (e.g., UPPERCASE, lowercase).
|
90
|
-
- **Preset Configurations**: Includes presets for common SQL dialects (MySQL, PostgreSQL, SQL Server, SQLite) to simplify configuration.
|
91
|
-
- **Customizable Options**: Allows fine-grained control over formatting styles, such as comma placement, parameter styles, and newline characters.
|
92
|
-
- **Parameterized Query Formatting**: Supports anonymous (`?`), indexed (`$1`, `$2`), and named (`:name`, `@name`) parameter styles for compatibility with various database systems.
|
64
|
+
## Quick Start
|
93
65
|
|
94
|
-
|
66
|
+
---
|
95
67
|
|
96
|
-
|
68
|
+
Kickstart your project by dynamically injecting parameters with `SqlParamInjector` for flexible query generation right from the start!
|
97
69
|
|
98
70
|
```typescript
|
99
|
-
|
100
|
-
const { formattedSql } = formatter.format(query);
|
101
|
-
console.log(formattedSql);
|
102
|
-
```
|
71
|
+
import { SqlParamInjector, SqlFormatter } from 'rawsql-ts';
|
103
72
|
|
104
|
-
|
105
|
-
|
106
|
-
- `postgres`: Double quote identifier, `$` parameter, indexed parameters
|
107
|
-
- `sqlserver`: Square bracket identifier, `@` parameter, named parameters
|
108
|
-
- `sqlite`: Double quote identifier, `:` parameter, named parameters
|
73
|
+
// Define a base SQL query with an alias, using TRUE for boolean conditions
|
74
|
+
const baseSql = `SELECT u.user_id, u.user_name, u.email FROM users as u WHERE u.active = TRUE`;
|
109
75
|
|
110
|
-
|
76
|
+
// Imagine you have search parameters from a user's input
|
77
|
+
const searchParams = {
|
78
|
+
user_name: { like: '%Alice%' }, // Find users whose name contains 'Alice'
|
79
|
+
email: 'specific.email@example.com' // And have a specific email
|
80
|
+
};
|
111
81
|
|
112
|
-
|
82
|
+
const injector = new SqlParamInjector();
|
83
|
+
// Dynamically inject searchParams into the baseSql
|
84
|
+
const query = injector.inject(baseSql, searchParams);
|
113
85
|
|
114
|
-
|
115
|
-
const formatter = new SqlFormatter({
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
const { formattedSql } = formatter.format(query);
|
86
|
+
// Format the dynamically generated query (e.g., using PostgreSQL preset)
|
87
|
+
const formatter = new SqlFormatter({ preset: 'postgres' });
|
88
|
+
const { formattedSql, params } = formatter.format(query);
|
89
|
+
|
90
|
+
console.log('Dynamically Generated SQL:');
|
120
91
|
console.log(formattedSql);
|
121
|
-
//
|
92
|
+
// Expected output (PostgreSQL style):
|
93
|
+
// select "u"."user_id", "u"."user_name", "u"."email"
|
94
|
+
// from "users" as "u"
|
95
|
+
// where "u"."active" = true
|
96
|
+
// and "u"."user_name" like :user_name_like
|
97
|
+
// and "u"."email" = :email
|
98
|
+
|
99
|
+
console.log('\\nParameters:');
|
100
|
+
console.log(params);
|
101
|
+
// Expected output:
|
102
|
+
// { user_name_like: '%Alice%', email: 'specific.email@example.com' }
|
122
103
|
```
|
123
104
|
|
124
|
-
|
125
|
-
|
126
|
-
SqlFormatter supports a wide range of options to customize the output:
|
127
|
-
|
128
|
-
- `identifierEscape`: How identifiers are escaped (e.g., `"`, `[`, `` ` ``).
|
129
|
-
- `parameterSymbol`: The symbol or pattern for parameters (e.g., `:`, `@`, `?`, or `{ start: '${', end: '}' }`).
|
130
|
-
- `parameterStyle`: Controls the parameter style (anonymous, indexed, or named).
|
131
|
-
- `indentSize`: Number of spaces or tabs for indentation.
|
132
|
-
- `keywordCase`: Casing for SQL keywords (`upper`, `lower`, or `none`).
|
133
|
-
- `commaBreak`: Placement of commas (`before` or `after`).
|
134
|
-
- `andBreak`: Placement of `AND`/`OR` in conditions (`before` or `after`).
|
135
|
-
- `newline`: Specifies the newline character used in the output (e.g., `\n`, `\r\n`, or a single space for compact formatting).
|
136
|
-
|
137
|
-
### Parameterized Query Formatting
|
138
|
-
|
139
|
-
`SqlFormatter` supports advanced parameterized query formatting for all major SQL dialects. You can output SQL with parameters in three styles:
|
140
|
-
|
141
|
-
- **Anonymous** (`?`): For MySQL and similar drivers. Parameters are output as `?` and values are provided as an array.
|
142
|
-
- **Indexed** (`$1`, `$2`, ...): For PostgreSQL and compatible drivers. Parameters are output as `$1`, `$2`, ... and values are provided as an array in the correct order.
|
143
|
-
- **Named** (`:name`, `@name`): For SQL Server, SQLite, and ORMs. Parameters are output as `:name` or `@name` and values are provided as an object.
|
144
|
-
|
145
|
-
The `parameterStyle` option in `SqlFormatter` allows you to control the parameter style, or you can use built-in presets (e.g., `mysql`, `postgres`, `sqlserver`, `sqlite`) to apply the correct style automatically.
|
105
|
+
---
|
146
106
|
|
147
|
-
|
107
|
+
## SQL Parsing Features
|
148
108
|
|
149
|
-
|
109
|
+
rawsql-ts provides robust parsers for `SELECT`, `INSERT`, and `UPDATE` statements, automatically handling SQL comments and providing detailed error messages. By converting SQL into a generic Abstract Syntax Tree (AST), it enables a wide variety of transformation processes.
|
150
110
|
|
151
111
|
```typescript
|
152
|
-
import {
|
112
|
+
import { SelectQueryParser } from 'rawsql-ts';
|
153
113
|
|
154
|
-
const sql = `SELECT
|
114
|
+
const sql = `SELECT id, name FROM products WHERE category = 'electronics'`;
|
155
115
|
const query = SelectQueryParser.parse(sql);
|
156
|
-
|
157
|
-
const { formattedSql } = formatter.format(query);
|
158
|
-
console.log(formattedSql);
|
159
|
-
/*
|
160
|
-
select "user_id", "name" from "users" where "active" = true
|
161
|
-
*/
|
116
|
+
// query object now holds the AST of the SQL
|
162
117
|
```
|
163
118
|
|
164
|
-
|
119
|
+
For more details on `SelectQueryParser`, see the [SelectQueryParser Usage Guide](./docs/class-SelectQueryParser-usage-guide.md).
|
165
120
|
|
166
|
-
|
167
|
-
import { SqlFormatter } from 'rawsql-ts';
|
121
|
+
---
|
168
122
|
|
169
|
-
|
170
|
-
const query = SelectQueryParser.parse(sql);
|
171
|
-
const formatter = new SqlFormatter({
|
172
|
-
identifierEscape: { start: '`', end: '`' },
|
173
|
-
parameterSymbol: '?',
|
174
|
-
parameterStyle: 'anonymous',
|
175
|
-
indentSize: 2,
|
176
|
-
keywordCase: 'upper',
|
177
|
-
newline: '\r\n',
|
178
|
-
commaBreak: 'before', // Specify the "before comma" option
|
179
|
-
});
|
180
|
-
const { formattedSql } = formatter.format(query);
|
181
|
-
console.log(formattedSql);
|
182
|
-
/*
|
183
|
-
SELECT
|
184
|
-
`user_id`
|
185
|
-
, `name`
|
186
|
-
FROM
|
187
|
-
`users`
|
188
|
-
WHERE
|
189
|
-
`active` = ?
|
190
|
-
*/
|
191
|
-
```
|
123
|
+
## SQL Formatter Features
|
192
124
|
|
193
|
-
|
125
|
+
The `SqlFormatter` class is the recommended way to format SQL queries, offering advanced capabilities like indentation, keyword casing, and multi-line formatting.
|
126
|
+
It also allows for detailed style customization. For example, you can define your own formatting rules:
|
194
127
|
|
195
128
|
```typescript
|
196
129
|
import { SelectQueryParser, SqlFormatter } from 'rawsql-ts';
|
197
130
|
|
198
|
-
const
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
For anonymous or indexed styles, simply change the `parameterStyle` option:
|
131
|
+
const customStyle = {
|
132
|
+
identifierEscape: {
|
133
|
+
start: "",
|
134
|
+
end: ""
|
135
|
+
},
|
136
|
+
parameterSymbol: ":",
|
137
|
+
parameterStyle: "named",
|
138
|
+
indentSize: 4,
|
139
|
+
indentChar: " ",
|
140
|
+
newline: "\n",
|
141
|
+
keywordCase: "lower",
|
142
|
+
commaBreak: "before",
|
143
|
+
andBreak: "before"
|
144
|
+
};
|
213
145
|
|
214
|
-
|
215
|
-
const
|
216
|
-
|
217
|
-
|
146
|
+
const sqlToFormat = `SELECT u.user_id, u.user_name FROM users as u WHERE status = :active ORDER BY created_at DESC;`;
|
147
|
+
const queryToFormat = SelectQueryParser.parse(sqlToFormat);
|
148
|
+
const customFormatter = new SqlFormatter(customStyle);
|
149
|
+
const { formattedSql: customFormattedSql } = customFormatter.format(queryToFormat);
|
218
150
|
|
219
|
-
|
220
|
-
|
221
|
-
|
151
|
+
console.log(customFormattedSql);
|
152
|
+
/*
|
153
|
+
select
|
154
|
+
u.user_id
|
155
|
+
, u.user_name
|
156
|
+
from
|
157
|
+
users as u
|
158
|
+
where
|
159
|
+
status = :active
|
160
|
+
order by
|
161
|
+
created_at desc;
|
162
|
+
*/
|
222
163
|
```
|
223
164
|
|
224
|
-
|
225
|
-
|
226
|
-
> [!Tip]
|
227
|
-
> Use named parameters in your source code for better readability and maintainability. You can always output the final SQL and parameters in the style required by your database client (e.g., anonymous or indexed) at formatting time.
|
228
|
-
|
229
|
-
---
|
230
|
-
|
231
|
-
## Main Parser Features
|
232
|
-
|
233
|
-
- All parsers automatically remove SQL comments before parsing.
|
234
|
-
- Detailed error messages are provided for all parsing errors.
|
235
|
-
- Highly accurate and advanced tokenization is used for robust SQL analysis.
|
236
|
-
|
237
|
-
> [!Note]
|
238
|
-
> All parsers in rawsql-ts have been tested with PostgreSQL syntax, but they are capable of parsing any generic SQL statement that does not use a DBMS-specific dialect.
|
239
|
-
|
240
|
-
- **SelectQueryParser**
|
241
|
-
The main class for converting SELECT and VALUES statements into AST. Fully supports CTEs (WITH), UNION/INTERSECT/EXCEPT, subqueries, and PostgreSQL-style syntax.
|
242
|
-
- `parse(sql: string): SelectQuery`
|
243
|
-
Converts a SQL string to an AST. Throws an exception on error.
|
244
|
-
- In this library, a "select query" is represented as one of the following types:
|
245
|
-
- `SimpleSelectQuery`: A standard SELECT statement with all major clauses (WHERE, GROUP BY, JOIN, etc.)
|
246
|
-
- `BinarySelectQuery`: A set operation query such as UNION, INTERSECT, or EXCEPT
|
247
|
-
- `ValuesQuery`: An inline VALUES table (e.g., `VALUES (1, 'a'), (2, 'b')`)
|
248
|
-
|
249
|
-
- **InsertQueryParser**
|
250
|
-
The main class for parsing `INSERT INTO` statements and converting them into AST. Supports PostgreSQL-style INSERT with or without column lists, as well as `INSERT ... SELECT` and `INSERT ... VALUES` forms.
|
251
|
-
- `parse(sql: string): InsertQuery`
|
252
|
-
Converts an INSERT SQL string to an AST. Throws an exception on error.
|
253
|
-
|
254
|
-
- **UpdateQueryParser**
|
255
|
-
The main class for parsing `UPDATE` statements and converting them into AST. Supports PostgreSQL-style UPDATE with optional CTE (WITH clause), table alias, SET, WHERE, FROM, and RETURNING clauses.
|
256
|
-
- `parse(sql: string): UpdateQuery`
|
257
|
-
Converts an UPDATE SQL string to an AST. Throws an exception on error.
|
165
|
+
For more details, see the [SqlFormatter Usage Guide](./docs/class-SqlFormatter-usage-guide.md).
|
258
166
|
|
259
167
|
---
|
260
168
|
|
261
|
-
##
|
262
|
-
|
263
|
-
- **SimpleSelectQuery**
|
264
|
-
Represents a standard SELECT statement. Supports all major clauses such as WHERE, GROUP BY, JOIN, and CTE.
|
265
|
-
- `toUnion`, `toUnionAll`, ... for UNION operations
|
266
|
-
- `appendWhere`, `appendWhereRaw` to add WHERE conditions
|
267
|
-
- `appendWhereExpr` to add a WHERE condition using the column's SQL expression (see below)
|
268
|
-
- `overrideSelectItemExpr` to override a SELECT item using its SQL expression (see below)
|
269
|
-
- `innerJoin`, `leftJoin`, ... to add JOINs
|
270
|
-
- `toSource` to wrap as a subquery
|
271
|
-
- `appendWith`, `appendWithRaw` to add CTEs
|
272
|
-
|
273
|
-
- **BinarySelectQuery**
|
274
|
-
Represents binary SQL queries such as UNION, INTERSECT, and EXCEPT.
|
275
|
-
- `union`, `intersect`, ... to combine queries
|
276
|
-
- `toSource` to wrap as a subquery
|
277
|
-
- `unionRaw`, ... to combine with raw SQL
|
278
|
-
|
279
|
-
- **ValuesQuery**
|
280
|
-
For inline tables like `VALUES (1, 'a'), (2, 'b')`.
|
281
|
-
- Can be used as a subquery or converted to SELECT with QueryNormalizer
|
282
|
-
---
|
169
|
+
## SqlParamInjector Features
|
283
170
|
|
284
|
-
|
171
|
+
The `SqlParamInjector` class revolutionizes how you build dynamic search queries. Instead of manually constructing different SQL statements for various search conditions, you simply provide a fixed base SQL and a state object. `SqlParamInjector` then dynamically injects parameters and automatically generates the optimal WHERE conditions.
|
285
172
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
- **
|
290
|
-
|
291
|
-
|
292
|
-
- Result: `where amount > 100`
|
293
|
-
|
294
|
-
- **Alias**
|
295
|
-
- SQL: `select fee as amount from sales`
|
296
|
-
- API: `query.appendWhereExpr('amount', expr => `${expr} > 100`)`
|
297
|
-
- Result: `where fee > 100`
|
298
|
-
|
299
|
-
- **Table Alias**
|
300
|
-
- SQL: `select s.fee as amount from sales as s`
|
301
|
-
- API: `query.appendWhereExpr('amount', expr => `${expr} > 100`)`
|
302
|
-
- Result: `where s.fee > 100`
|
303
|
-
|
304
|
-
- **Expression**
|
305
|
-
- SQL: `select quantity * pack_size as amount from sales`
|
306
|
-
- API: `query.appendWhereExpr('amount', expr => `${expr} > 100`)`
|
307
|
-
- Result: `where quantity * pack_size > 100`
|
308
|
-
|
309
|
-
As long as the column is named (or aliased) as `amount`, `appendWhereExpr` will detect and use the correct SQL expression for the WHERE clause—even if it is a complex calculation or uses table aliases.
|
173
|
+
Key benefits include:
|
174
|
+
- **Simplified Query Management**: Prepare a single base SQL; `SqlParamInjector` handles the variations.
|
175
|
+
- **Effortless Optimal Queries**: Just pass a state object, and it generates a highly efficient query.
|
176
|
+
- **Performance-Oriented**: Conditions are intelligently inserted as close to the data source as possible, significantly improving query performance by filtering data early.
|
177
|
+
- **Zero Conditional Logic in Code**: Forget writing complex IF statements in your application code to handle different filters.
|
178
|
+
- **Enhanced SQL Reusability**: Your base SQL remains clean and can be reused across different scenarios with varying search criteria.
|
310
179
|
|
311
180
|
```typescript
|
312
|
-
|
313
|
-
query.appendWhereExpr('amount', expr => `${expr} > 100`);
|
314
|
-
```
|
315
|
-
|
316
|
-
#### Upstream Query Support
|
181
|
+
import { SqlParamInjector, SqlFormatter } from 'rawsql-ts';
|
317
182
|
|
318
|
-
|
183
|
+
const sql = `SELECT u.user_id, u.user_name FROM users as u WHERE u.active = TRUE`;
|
184
|
+
const injector = new SqlParamInjector();
|
185
|
+
// Inject parameters and generate WHERE conditions
|
186
|
+
const injectedQuery = injector.inject(sql, { user_id: 42, user_name: 'Alice' });
|
319
187
|
|
320
|
-
|
321
|
-
|
322
|
-
- If the column is defined in a CTE (WITH clause), the WHERE condition is added inside the CTE.
|
323
|
-
- If the column is provided by multiple upstream queries (e.g., UNION branches), the condition is added to all relevant branches.
|
324
|
-
- You do not need to know or traverse the query structure yourself—just specify the column name, and `appendWhereExpr` with `{ upstream: true }` will do the rest.
|
325
|
-
|
326
|
-
##### Example: Filtering a CTE
|
327
|
-
|
328
|
-
```typescript
|
329
|
-
const query = SelectQueryParser.parse(`
|
330
|
-
WITH temp_sales AS (
|
331
|
-
SELECT id, amount, date FROM sales WHERE date >= '2024-01-01'
|
332
|
-
)
|
333
|
-
SELECT * FROM temp_sales
|
334
|
-
`) as SimpleSelectQuery;
|
335
|
-
|
336
|
-
// Add a filter to the CTE using upstream support
|
337
|
-
query.appendWhereExpr('amount', expr => `${expr} > 100`, { upstream: true });
|
338
|
-
|
339
|
-
const sql = new SqlFormatter().format(query).formattedSql;
|
340
|
-
console.log(sql);
|
341
|
-
// => with "temp_sales" as (select "id", "amount", "date" from "sales" where "date" >= '2024-01-01' and "amount" > 100) select * from "temp_sales"
|
342
|
-
```
|
343
|
-
|
344
|
-
##### Example: Filtering All Branches of a UNION
|
188
|
+
const formatter = new SqlFormatter();
|
189
|
+
const { formattedSql, params } = formatter.format(injectedQuery);
|
345
190
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
),
|
351
|
-
support_transactions AS (
|
352
|
-
SELECT support_id AS transaction_id, user_id AS customer_id, fee AS amount, support_date AS transaction_date FROM support_schema.support_fees WHERE support_date >= CURRENT_DATE - INTERVAL '90 days'
|
353
|
-
)
|
354
|
-
SELECT * FROM (
|
355
|
-
SELECT * FROM sales_transactions
|
356
|
-
UNION ALL
|
357
|
-
SELECT * FROM support_transactions
|
358
|
-
) d
|
359
|
-
ORDER BY transaction_date DESC
|
360
|
-
`) as SimpleSelectQuery;
|
361
|
-
|
362
|
-
// Add a filter to all upstream queries that provide 'amount'
|
363
|
-
query.appendWhereExpr('amount', expr => `${expr} > 100`, { upstream: true });
|
364
|
-
|
365
|
-
const sql = new SqlFormatter().format(query).formattedSql;
|
366
|
-
console.log(sql);
|
367
|
-
// => with "sales_transactions" as (select ... where ... and "amount" > 100),
|
368
|
-
// "support_transactions" as (select ... where ... and "fee" > 100)
|
369
|
-
// select * from (... union all ...) as "d" order by "transaction_date" desc
|
191
|
+
console.log(formattedSql);
|
192
|
+
// Output: select "u"."user_id", "u"."user_name" from "users" as "u" where "u"."active" = true and "u"."user_id" = :user_id and "u"."user_name" = :user_name
|
193
|
+
console.log(params);
|
194
|
+
// Output: { user_id: 42, user_name: 'Alice' }
|
370
195
|
```
|
371
196
|
|
372
|
-
|
373
|
-
|
374
|
-
`appendWhereExpr` is especially useful in the following scenarios:
|
375
|
-
|
376
|
-
- **Dynamic Search Conditions for Complex Reports**
|
377
|
-
Easily inject arbitrary search filters into deeply nested or highly complex queries, such as those used in reporting or analytics dashboards. This enables flexible, user-driven filtering without manual SQL string manipulation.
|
378
|
-
|
379
|
-
- **Performance-Critical Query Construction**
|
380
|
-
Build high-performance queries by programmatically adding WHERE conditions only when needed, ensuring that unnecessary filters are not included and that the generated SQL remains as efficient as possible.
|
381
|
-
|
382
|
-
- **Generic Access Control and Security Filters**
|
383
|
-
Apply reusable access control or security-related WHERE clauses (e.g., tenant isolation, user-based restrictions) across all relevant queries, regardless of their internal structure. This helps enforce consistent data access policies throughout your application.
|
384
|
-
|
385
|
-
> [!TIP]
|
386
|
-
> Upstream Query Support is especially useful for large, complex SQL with multiple layers of subqueries, CTEs, or set operations. You can add filters or conditions without worrying about the internal structure—just specify the column name!
|
387
|
-
>
|
388
|
-
> You can focus on developing and maintaining RawSQL itself, without being bothered by troublesome variable search conditions.
|
197
|
+
For more details, see the [SqlParamInjector Usage Guide](./docs/class-SqlParamInjector-usage-guide.md).
|
389
198
|
|
390
199
|
---
|
391
200
|
|
392
|
-
|
393
|
-
Overrides a SELECT item using its SQL expression. The callback receives the original SQL expression as a string and returns a new SQL string.
|
201
|
+
## SqlSchemaValidator Features
|
394
202
|
|
395
|
-
|
396
|
-
// Override the SELECT item 'journal_date' to use greatest(journal_date, DATE '2025-01-01')
|
397
|
-
query.overrideSelectItemExpr('journal_date', expr => `greatest(${expr}, DATE '2025-01-01')`);
|
398
|
-
```
|
399
|
-
|
400
|
-
---
|
401
|
-
|
402
|
-
## AST Transformer Features
|
403
|
-
|
404
|
-
A suite of utilities for transforming and analyzing SQL ASTs.
|
405
|
-
|
406
|
-
### Main Transformers
|
407
|
-
|
408
|
-
- **SqlFormatter**
|
409
|
-
Converts ASTs to formatted SQL strings. Handles identifier escaping. Supports both single-line (compact) and multi-line (formatted) styles.
|
410
|
-
- **SelectValueCollector**
|
411
|
-
Extracts all columns, aliases, and expressions from SELECT clauses. Supports wildcard expansion (e.g., `*`, `table.*`) with TableColumnResolver.
|
412
|
-
- **SelectableColumnCollector**
|
413
|
-
Collects all columns available from root FROM/JOIN sources.
|
414
|
-
- **TableSourceCollector**
|
415
|
-
Collects all table and subquery sources from FROM and JOIN clauses.
|
416
|
-
- **CTECollector**
|
417
|
-
Collects all CTEs from WITH clauses, subqueries, and UNION queries.
|
418
|
-
- **UpstreamSelectQueryFinder**
|
419
|
-
Finds upstream SELECT queries that provide specific columns by traversing CTEs, subqueries, and UNION branches.
|
420
|
-
- **CTENormalizer**
|
421
|
-
Consolidates all CTEs into a single root-level WITH clause. Throws an error if duplicate CTE names with different definitions are found.
|
422
|
-
- **QueryNormalizer**
|
423
|
-
Converts any SELECT/UNION/VALUES query into a standard SimpleSelectQuery. Handles subquery wrapping and automatic column name generation.
|
424
|
-
|
425
|
-
- **QueryBuilder**
|
426
|
-
Converts any SELECT/UNION/VALUES query into a standard SimpleSelectQuery. Handles subquery wrapping and automatic column name generation.
|
427
|
-
Supports CREATE TABLE ... AS SELECT ... conversion:
|
428
|
-
- `QueryBuilder.buildCreateTableQuery(query, tableName, isTemporary?)` creates a `CreateTableQuery` from any SELECT query.
|
429
|
-
Supports combining multiple queries:
|
430
|
-
- `QueryBuilder.buildBinaryQuery(queries, operator)` combines an array of SelectQuery objects into a single BinarySelectQuery using the specified set operator (e.g., 'union', 'intersect', 'except').
|
431
|
-
Supports INSERT and UPDATE statement generation from SELECT:
|
432
|
-
- `QueryBuilder.buildInsertQuery(selectQuery, tableName)` creates an `InsertQuery` from a `SimpleSelectQuery` and a target table name.
|
433
|
-
The columns are inferred from the select query. Throws if columns cannot be determined.
|
434
|
-
- `QueryBuilder.buildUpdateQuery(selectQuery, selectSourceName, updateTableExprRaw, primaryKeys)` creates an `UpdateQuery` from a `SimpleSelectQuery`, the source alias, the update target table, and primary key(s).
|
435
|
-
This generates an UPDATE ... SET ... FROM ... WHERE ... statement using the SELECT as the value source. Throws if PK columns are missing or ambiguous.
|
436
|
-
|
437
|
-
- **TableColumnResolver**
|
438
|
-
A function type for resolving column names from a table name, mainly used for wildcard expansion (e.g., `table.*`). Used by analyzers like SelectValueCollector.
|
439
|
-
```typescript
|
440
|
-
export type TableColumnResolver = (tableName: string) => string[];
|
441
|
-
```
|
442
|
-
|
443
|
-
> [!NOTE]
|
444
|
-
> As of version 0.4.0-beta, the class previously named `QueryConverter` has been renamed to `QueryBuilder`, and its methods have been updated for consistency. The new `buildBinaryQuery` method was also introduced, allowing you to combine multiple `SelectQuery` objects into a single set operation query. These are breaking changes. If you were using `QueryConverter` in earlier versions, please update your code to use `QueryBuilder` and the new method names (e.g., `buildCreateTableQuery`, `buildBinaryQuery`).
|
203
|
+
The `SqlSchemaValidator` class helps ensure your SQL queries are valid against a predefined database schema. It can extract schema information about the physical tables your SQL query depends on. By comparing this extracted information with your defined schema (e.g., a schema definition class), you can statically verify the query's behavior. This enables you to perform regression testing as part of your unit tests when schema definitions change, ensuring that your queries remain compatible.
|
445
204
|
|
446
|
-
|
205
|
+
It checks if the tables and columns referenced in your query actually exist in your schema, and it understands table aliases. If there's a problem, it gives you a clear error message telling you what's wrong and where.
|
447
206
|
|
448
|
-
|
207
|
+
Key benefits include:
|
208
|
+
- **Schema Validation**: Verifies SQL queries against your database schema.
|
209
|
+
- **Table and Column Verification**: Confirms the existence of tables and columns used in the query.
|
210
|
+
- **Alias Aware**: Correctly resolves table aliases.
|
211
|
+
- **Clear Error Reporting**: Provides descriptive error messages for easy debugging.
|
212
|
+
- **Static Analysis**: Allows comparison of SQL-derived schema information with predefined schema definitions.
|
213
|
+
- **Automated Regression Testing**: Facilitates unit testing for schema change impacts on queries.
|
449
214
|
|
450
215
|
```typescript
|
451
|
-
import {
|
216
|
+
import { SelectQueryParser, SqlSchemaValidator } from 'rawsql-ts';
|
452
217
|
|
453
|
-
//
|
454
|
-
const
|
455
|
-
|
456
|
-
|
457
|
-
return [];
|
218
|
+
// Define your database schema
|
219
|
+
const schema = {
|
220
|
+
users: ['user_id', 'user_name', 'email', 'status'],
|
221
|
+
orders: ['order_id', 'user_id', 'order_date', 'total_amount']
|
458
222
|
};
|
459
223
|
|
460
|
-
const
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
224
|
+
const validator = new SqlSchemaValidator(schema);
|
225
|
+
|
226
|
+
// Example: Validate a SELECT query
|
227
|
+
const validSql = 'SELECT u.user_id, u.user_name FROM users as u WHERE u.status = \'active\'';
|
228
|
+
const queryToValidate = SelectQueryParser.parse(validSql);
|
229
|
+
|
230
|
+
try {
|
231
|
+
validator.validate(queryToValidate);
|
232
|
+
console.log('Query is valid against the schema.');
|
233
|
+
} catch (error) {
|
234
|
+
console.error('Schema validation failed:', error.message);
|
235
|
+
}
|
236
|
+
|
237
|
+
// Example: Validate a query with a non-existent column
|
238
|
+
const invalidSql = 'SELECT user_id, non_existent_column FROM users';
|
239
|
+
const invalidQuery = SelectQueryParser.parse(invalidSql);
|
240
|
+
|
241
|
+
try {
|
242
|
+
validator.validate(invalidQuery);
|
243
|
+
} catch (error) {
|
244
|
+
console.error('Schema validation error for non-existent column:', error.message);
|
245
|
+
// Expected output: Validation failed: Column 'non_existent_column' not found in table 'users'.
|
246
|
+
}
|
480
247
|
```
|
481
248
|
|
482
|
-
|
483
|
-
// Collects selectable columns from the FROM/JOIN clauses.
|
484
|
-
// You can get accurate information by specifying a TableColumnResolver.
|
485
|
-
// If omitted, the information will be inferred from the query content.
|
486
|
-
const selectableColumnCollector = new SelectableColumnCollector(resolver);
|
487
|
-
const selectableColumns = selectableColumnCollector.collect(query);
|
488
|
-
// Log detailed info for each selectable column
|
489
|
-
console.log('Selectable columns:');
|
490
|
-
selectableColumns.forEach(val => {
|
491
|
-
console.log(` name: ${val.name}, value: ${formatter.format(val.value).formattedSql}`);
|
492
|
-
});
|
493
|
-
/*
|
494
|
-
Selectable columns:
|
495
|
-
name: post_title, value: "p"."title"
|
496
|
-
name: user_id, value: "u"."user_id"
|
497
|
-
name: user_name, value: "u"."user_name"
|
498
|
-
name: email, value: "u"."email"
|
499
|
-
name: post_id, value: "p"."post_id"
|
500
|
-
name: title, value: "p"."title"
|
501
|
-
name: content, value: "p"."content"
|
502
|
-
*/
|
503
|
-
```
|
504
|
-
|
505
|
-
```typescript
|
506
|
-
// Create Table from SELECT Example
|
507
|
-
import { QueryBuilder, SelectQueryParser, SqlFormatter } from 'rawsql-ts';
|
508
|
-
|
509
|
-
const select = SelectQueryParser.parse('SELECT id, name FROM users');
|
510
|
-
const create = QueryBuilder.buildCreateTableQuery(select, 'my_table');
|
511
|
-
const sqlCreate = new SqlFormatter().format(create).formattedSql;
|
512
|
-
console.log(sqlCreate);
|
513
|
-
// => create table "my_table" as select "id", "name" from "users"
|
514
|
-
|
515
|
-
const createTemp = QueryBuilder.buildCreateTableQuery(select, 'tmp_table', true);
|
516
|
-
const sqlTemp = new SqlFormatter().format(createTemp).formattedSql;
|
517
|
-
console.log(sqlTemp);
|
518
|
-
// => create temporary table "tmp_table" as select "id", "name" from "users"
|
519
|
-
```
|
520
|
-
|
521
|
-
```typescript
|
522
|
-
// Retrieves physical table sources.
|
523
|
-
const tableSourceCollector = new TableSourceCollector();
|
524
|
-
const sources = tableSourceCollector.collect(query);
|
525
|
-
// Log detailed info for each source
|
526
|
-
console.log('Sources:');
|
527
|
-
sources.forEach(src => {
|
528
|
-
console.log(` name: ${src.getSourceName()}`);
|
529
|
-
});
|
530
|
-
/*
|
531
|
-
TableSources:
|
532
|
-
name: users
|
533
|
-
name: posts
|
534
|
-
*/
|
535
|
-
```
|
249
|
+
For more details on `SqlSchemaValidator`, see the [SqlSchemaValidator Usage Guide](./docs/class-SqlSchemaValidator-usage-guide.md).
|
536
250
|
|
537
251
|
---
|
538
252
|
|
539
|
-
##
|
253
|
+
## QueryBuilder Features
|
540
254
|
|
541
|
-
|
255
|
+
`QueryBuilder` is a powerful utility that enhances the management and generation of SQL modification queries (such as `INSERT` or `UPDATE`) by leveraging select queries. This approach significantly improves the maintainability of complex data manipulation logic. It allows for the conversion of select queries into corresponding update-type queries, streamlining development and ensuring consistency.
|
542
256
|
|
543
257
|
```typescript
|
544
|
-
import { SelectQueryParser, SqlFormatter,
|
545
|
-
|
546
|
-
//
|
547
|
-
const
|
548
|
-
|
549
|
-
//
|
550
|
-
|
551
|
-
|
552
|
-
//
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
const
|
557
|
-
|
558
|
-
|
559
|
-
|
258
|
+
import { SelectQueryParser, QueryBuilder, SqlFormatter, QueryNormalizer } from 'rawsql-ts';
|
259
|
+
|
260
|
+
// Example: Convert a SELECT query to an UPDATE query
|
261
|
+
const selectSourceSql = 'SELECT id, new_email AS email, last_login FROM user_updates_source WHERE needs_update = TRUE';
|
262
|
+
// QueryBuilder.buildUpdateQuery expects a SimpleSelectQuery as input.
|
263
|
+
// If your source is a complex query (e.g. with UNIONs or CTEs), normalize it first.
|
264
|
+
const normalizedSelectQuery = QueryNormalizer.normalize(SelectQueryParser.parse(selectSourceSql));
|
265
|
+
|
266
|
+
// Define the target table for the UPDATE and the primary key(s) for joining
|
267
|
+
const targetTable = 'users';
|
268
|
+
const primaryKeys = ['id']; // Column(s) to match records between source and target
|
269
|
+
|
270
|
+
const updateQuery = QueryBuilder.buildUpdateQuery(
|
271
|
+
normalizedSelectQuery,
|
272
|
+
'd', // Alias of the source query in the FROM clause
|
273
|
+
targetTable,
|
274
|
+
primaryKeys
|
275
|
+
);
|
276
|
+
|
277
|
+
const formatter = new SqlFormatter({ preset: 'postgres' }); // Using postgres preset for clarity
|
278
|
+
const { formattedSql: updateSql } = formatter.format(updateQuery);
|
279
|
+
|
280
|
+
console.log(updateSql);
|
281
|
+
// Example output (actual output depends on the SQL dialect and specific query structure):
|
282
|
+
// update "users" set "email" = "d"."email", "last_login" = "d"."last_login" from (SELECT id, new_email AS email, last_login FROM user_updates_source WHERE needs_update = TRUE) as "d" where "users"."id" = "d"."id"
|
560
283
|
```
|
561
284
|
|
562
|
-
|
563
|
-
- No need to understand internal implementation or alias management
|
564
|
-
- Specify only the join key(s) (e.g., `['user_id']`); the ON clause is generated automatically
|
565
|
-
- Subqueries and aliases are handled automatically
|
566
|
-
- You can join queries without detailed knowledge of SQL structure or AST internals
|
285
|
+
For more details on `QueryBuilder`, see the [QueryBuilder Usage Guide](./docs/class-QueryBuilder-usage-guide.md).
|
567
286
|
|
568
287
|
---
|
569
288
|
|