rawsql-ts 0.8.3-beta → 0.10.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.
Files changed (74) hide show
  1. package/README.md +204 -438
  2. package/dist/esm/index.js +2 -0
  3. package/dist/esm/index.js.map +1 -1
  4. package/dist/esm/models/SqlPrintToken.js +2 -0
  5. package/dist/esm/models/SqlPrintToken.js.map +1 -1
  6. package/dist/esm/models/ValueComponent.js +14 -13
  7. package/dist/esm/models/ValueComponent.js.map +1 -1
  8. package/dist/esm/parsers/SqlPrintTokenParser.js +22 -6
  9. package/dist/esm/parsers/SqlPrintTokenParser.js.map +1 -1
  10. package/dist/esm/transformers/PostgreJsonQueryBuilder.js +215 -0
  11. package/dist/esm/transformers/PostgreJsonQueryBuilder.js.map +1 -0
  12. package/dist/esm/transformers/PostgresArrayEntityCteBuilder.js +231 -0
  13. package/dist/esm/transformers/PostgresArrayEntityCteBuilder.js.map +1 -0
  14. package/dist/esm/transformers/PostgresObjectEntityCteBuilder.js +283 -0
  15. package/dist/esm/transformers/PostgresObjectEntityCteBuilder.js.map +1 -0
  16. package/dist/esm/transformers/SelectableColumnCollector.js +4 -1
  17. package/dist/esm/transformers/SelectableColumnCollector.js.map +1 -1
  18. package/dist/esm/transformers/SqlParamInjector.js +133 -0
  19. package/dist/esm/transformers/SqlParamInjector.js.map +1 -0
  20. package/dist/esm/transformers/SqlPrinter.js +9 -3
  21. package/dist/esm/transformers/SqlPrinter.js.map +1 -1
  22. package/dist/esm/transformers/UpstreamSelectQueryFinder.js +10 -6
  23. package/dist/esm/transformers/UpstreamSelectQueryFinder.js.map +1 -1
  24. package/dist/esm/types/index.d.ts +2 -0
  25. package/dist/esm/types/models/SqlPrintToken.d.ts +2 -0
  26. package/dist/esm/types/models/ValueComponent.d.ts +3 -3
  27. package/dist/esm/types/transformers/PostgreJsonQueryBuilder.d.ts +78 -0
  28. package/dist/esm/types/transformers/PostgresArrayEntityCteBuilder.d.ts +97 -0
  29. package/dist/esm/types/transformers/PostgresObjectEntityCteBuilder.d.ts +140 -0
  30. package/dist/esm/types/transformers/SelectableColumnCollector.d.ts +6 -1
  31. package/dist/esm/types/transformers/SqlParamInjector.d.ts +36 -0
  32. package/dist/esm/types/transformers/UpstreamSelectQueryFinder.d.ts +6 -3
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/models/SqlPrintToken.d.ts +2 -0
  37. package/dist/models/SqlPrintToken.js +2 -0
  38. package/dist/models/SqlPrintToken.js.map +1 -1
  39. package/dist/models/ValueComponent.d.ts +3 -3
  40. package/dist/models/ValueComponent.js +14 -13
  41. package/dist/models/ValueComponent.js.map +1 -1
  42. package/dist/parsers/SelectQueryParser.js +3 -14
  43. package/dist/parsers/SelectQueryParser.js.map +1 -1
  44. package/dist/parsers/SqlPrintTokenParser.js +23 -7
  45. package/dist/parsers/SqlPrintTokenParser.js.map +1 -1
  46. package/dist/transformers/PostgreJsonQueryBuilder.d.ts +78 -0
  47. package/dist/transformers/PostgreJsonQueryBuilder.js +219 -0
  48. package/dist/transformers/PostgreJsonQueryBuilder.js.map +1 -0
  49. package/dist/transformers/PostgresArrayEntityCteBuilder.d.ts +97 -0
  50. package/dist/transformers/PostgresArrayEntityCteBuilder.js +235 -0
  51. package/dist/transformers/PostgresArrayEntityCteBuilder.js.map +1 -0
  52. package/dist/transformers/PostgresObjectEntityCteBuilder.d.ts +140 -0
  53. package/dist/transformers/PostgresObjectEntityCteBuilder.js +287 -0
  54. package/dist/transformers/PostgresObjectEntityCteBuilder.js.map +1 -0
  55. package/dist/transformers/SelectableColumnCollector.d.ts +6 -1
  56. package/dist/transformers/SelectableColumnCollector.js +4 -1
  57. package/dist/transformers/SelectableColumnCollector.js.map +1 -1
  58. package/dist/transformers/SqlFormatter.js +6 -1
  59. package/dist/transformers/SqlFormatter.js.map +1 -1
  60. package/dist/transformers/SqlParamInjector.d.ts +36 -0
  61. package/dist/transformers/SqlParamInjector.js +137 -0
  62. package/dist/transformers/SqlParamInjector.js.map +1 -0
  63. package/dist/transformers/SqlPrinter.js +9 -3
  64. package/dist/transformers/SqlPrinter.js.map +1 -1
  65. package/dist/transformers/UpstreamSelectQueryFinder.d.ts +6 -3
  66. package/dist/transformers/UpstreamSelectQueryFinder.js +10 -6
  67. package/dist/transformers/UpstreamSelectQueryFinder.js.map +1 -1
  68. package/package.json +1 -1
  69. package/dist/esm/types/utils/SqlStaticAnalyzer.d.ts +0 -0
  70. package/dist/esm/utils/SqlStaticAnalyzer.js +0 -2
  71. package/dist/esm/utils/SqlStaticAnalyzer.js.map +0 -1
  72. package/dist/utils/SqlStaticAnalyzer.d.ts +0 -0
  73. package/dist/utils/SqlStaticAnalyzer.js +0 -2
  74. 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 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.
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
- 💡 **Key Advantages**
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
- ![Benchmark Results](https://quickchart.io/chart?c={type:'bar',data:{labels:['Tokens20','Tokens70','Tokens140','Tokens230'],datasets:[{label:'rawsql-ts',data:[0.029,0.075,0.137,0.239],backgroundColor:'rgba(54,162,235,0.8)',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
+ ![Benchmark Results](https://quickchart.io/chart?c={type:'bar',data:{labels:['Tokens20','Tokens70','Tokens140','Tokens230'],datasets:[{label:'rawsql-ts',data:[0.029,0.075,0.137,0.239],backgroundColor:'rgba(54,162,235,0.8)',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
- ## Browser & CDN Ready!
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,277 @@ 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
- ## Formatter Functionality
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
- ### Preset Configurations (SqlFormatter.PRESETS)
66
+ ---
95
67
 
96
- The `SqlFormatter` class provides preset configurations for common SQL dialects. Use these presets to quickly format queries without manually specifying options each time.
68
+ Kickstart your project by dynamically injecting parameters with `SqlParamInjector` for flexible query generation right from the start!
97
69
 
98
70
  ```typescript
99
- const formatter = new SqlFormatter({ preset: 'mysql' });
100
- const { formattedSql } = formatter.format(query);
101
- console.log(formattedSql);
102
- ```
71
+ import { SqlParamInjector, SqlFormatter } from 'rawsql-ts';
103
72
 
104
- **Preset Details:**
105
- - `mysql`: Backtick identifier, `?` parameter, no named parameters
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
- ### Customizing SqlFormatter
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
- You can override any preset option as needed. For example, to use variable-style parameters (`${name}`):
82
+ const injector = new SqlParamInjector();
83
+ // Dynamically inject searchParams into the baseSql
84
+ const query = injector.inject(baseSql, searchParams);
113
85
 
114
- ```typescript
115
- const formatter = new SqlFormatter({
116
- preset: 'postgres',
117
- parameterSymbol: { start: '${', end: '}' },
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
- // => select "user_id", "name" from "users" where "active" = ${active}
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
- ### Configurable Options
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
- ### Usage Example
107
+ ## SelectQueryParser Features
148
108
 
149
- #### Using a Preset
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 { SqlFormatter } from 'rawsql-ts';
112
+ import { SelectQueryParser } from 'rawsql-ts';
153
113
 
154
- const sql = `SELECT user_id, name FROM users WHERE active = TRUE`;
114
+ const sql = `SELECT id, name FROM products WHERE category = 'electronics'`;
155
115
  const query = SelectQueryParser.parse(sql);
156
- const formatter = new SqlFormatter({ preset: 'postgres' });
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
- #### Using Custom Configuration
119
+ For more details on `SelectQueryParser`, see the [SelectQueryParser Usage Guide](./docs/usage-guides/class-SelectQueryParser-usage-guide.md).
165
120
 
166
- ```typescript
167
- import { SqlFormatter } from 'rawsql-ts';
121
+ ---
168
122
 
169
- const sql = `SELECT user_id, name FROM users WHERE active = TRUE`;
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
+ ## SqlFormatter Features
192
124
 
193
- #### Parameterized Query Output
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 sql = 'SELECT * FROM users WHERE id = :id AND status = :status';
199
- const query = SelectQueryParser.parse(sql);
200
- query.setParameter('id', 123);
201
- query.setParameter('status', 'active');
202
-
203
- const formatter = new SqlFormatter({ parameterStyle: 'named' });
204
- const { formattedSql, params } = formatter.format(query);
205
-
206
- console.log(formattedSql);
207
- // => select * from "users" where "id" = :id and "status" = :status
208
- console.log(params);
209
- // => { id: 123, status: 'active' }
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
- ```typescript
215
- const formatter = new SqlFormatter({ parameterStyle: 'anonymous' });
216
- // formattedSql: 'select * from "users" where "id" = ? and "status" = ?'
217
- // params: [123, 'active']
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
- const formatterIndexed = new SqlFormatter({ parameterStyle: 'indexed' });
220
- // formattedSql: 'select * from "users" where "id" = $1 and "status" = $2'
221
- // params: [123, 'active']
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
- `SqlFormatter` ensures parameter indexes are assigned correctly, even for complex queries with CTEs, subqueries, or set operations. This makes it easy to build safe, maintainable, and portable SQL in TypeScript.
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/usage-guides/class-SqlFormatter-usage-guide.md).
258
166
 
259
167
  ---
260
168
 
261
- ## Core SQL Query Classes
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
- ## Advanced Expression-based Methods
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
- ### appendWhereExpr
287
- `appendWhereExpr` is a highly important feature that enables you to add WHERE conditions using the SQL expression of a column, regardless of whether it is a direct column, an alias, a table alias, or even a calculated expression.
288
-
289
- - **Basic Column**
290
- - SQL: `select amount from sales`
291
- - API: `query.appendWhereExpr('amount', expr => `${expr} > 100`)`
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
- // Works for any alias, table alias, or expression!
313
- query.appendWhereExpr('amount', expr => `${expr} > 100`);
314
- ```
181
+ import { SqlParamInjector, SqlFormatter } from 'rawsql-ts';
315
182
 
316
- #### Upstream Query Support
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' });
317
187
 
318
- `Upstream Query Support` is a powerful extension of `appendWhereExpr` that allows you to add WHERE conditions to all relevant upstream queries that provide a specific column, regardless of the query structure. This means you can target columns defined in subqueries, CTEs (WITH clauses), or even branches of UNION/INTERSECT/EXCEPT, and the condition will be automatically inserted at the correct place in the SQL tree.
188
+ const formatter = new SqlFormatter();
189
+ const { formattedSql, params } = formatter.format(injectedQuery);
319
190
 
320
- **What does this mean in practice?**
321
- - If the column is defined in a subquery, the WHERE condition is added inside that subquery.
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.
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' }
195
+ ```
325
196
 
326
- ##### Example: Filtering a CTE
197
+ For more details, see the [SqlParamInjector Usage Guide](./docs/usage-guides/class-SqlParamInjector-usage-guide.md).
327
198
 
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;
199
+ ---
335
200
 
336
- // Add a filter to the CTE using upstream support
337
- query.appendWhereExpr('amount', expr => `${expr} > 100`, { upstream: true });
201
+ ## PostgreJsonQueryBuilder Features
338
202
 
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
- ```
203
+ The `PostgreJsonQueryBuilder` class transforms relational SQL queries into PostgreSQL JSON queries that return hierarchical JSON structures. It automatically handles complex relationships between entities and generates optimized Common Table Expressions (CTEs) for efficient JSON aggregation, making it perfect for building APIs, reports, and data exports.
343
204
 
344
- ##### Example: Filtering All Branches of a UNION
205
+ Key benefits include:
206
+ - **Hierarchical JSON Generation**: Transforms flat relational data into nested JSON objects and arrays
207
+ - **Automatic CTE Management**: Generates optimized CTEs with proper dependency ordering
208
+ - **Flexible Relationship Types**: Supports both object (0..1) and array (1..N) relationships
209
+ - **NULL Handling**: Properly represents missing relationships as NULL instead of empty objects
210
+ - **Performance Optimized**: Uses depth-based processing and JSONB for optimal PostgreSQL performance
211
+ - **Zero Manual Serialization**: Eliminates the need for manual object mapping and JSON construction
345
212
 
346
213
  ```typescript
347
- const query = SelectQueryParser.parse(`
348
- WITH sales_transactions AS (
349
- SELECT transaction_id, customer_id, amount, transaction_date FROM sales_schema.transactions WHERE transaction_date >= CURRENT_DATE - INTERVAL '90 days'
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
214
+ import { SelectQueryParser, PostgreJsonQueryBuilder } from 'rawsql-ts';
215
+
216
+ // Parse your base SQL query
217
+ const baseQuery = SelectQueryParser.parse(`
218
+ SELECT o.order_id, o.order_date, c.customer_name, i.product_name, i.quantity
219
+ FROM orders o
220
+ LEFT JOIN customers c ON o.customer_id = c.customer_id
221
+ LEFT JOIN order_items i ON o.order_id = i.order_id
360
222
  `) as SimpleSelectQuery;
361
223
 
362
- // Add a filter to all upstream queries that provide 'amount'
363
- query.appendWhereExpr('amount', expr => `${expr} > 100`, { upstream: true });
224
+ // Define JSON mapping configuration
225
+ const mapping = {
226
+ rootName: "order",
227
+ rootEntity: { id: "order", name: "Order", columns: { "id": "order_id", "date": "order_date" }},
228
+ nestedEntities: [
229
+ { id: "customer", parentId: "order", propertyName: "customer", relationshipType: "object",
230
+ columns: { "name": "customer_name" }},
231
+ { id: "items", parentId: "order", propertyName: "items", relationshipType: "array",
232
+ columns: { "product": "product_name", "qty": "quantity" }}
233
+ ],
234
+ useJsonb: true
235
+ };
364
236
 
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
237
+ // Transform to JSON query
238
+ const builder = new PostgreJsonQueryBuilder();
239
+ const jsonQuery = builder.buildJson(baseQuery, mapping);
240
+ // Returns optimized PostgreSQL query with CTEs that produces:
241
+ // [{ "id": 1, "date": "2024-01-15", "customer": {"name": "John"}, "items": [{"product": "Widget", "qty": 2}] }]
370
242
  ```
371
243
 
372
- ### appendWhereExpr Use Cases
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.
244
+ For more details, see the [PostgreJsonQueryBuilder Usage Guide](./docs/usage-guides/class-PostgreJsonQueryBuilder-usage-guide.md).
389
245
 
390
246
  ---
391
247
 
392
- ### overrideSelectItemExpr
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.
394
-
395
- ```typescript
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
- ---
248
+ ## SqlSchemaValidator Features
401
249
 
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`).
250
+ 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
251
 
446
- ---
252
+ 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
253
 
448
- ## Usage Example
254
+ Key benefits include:
255
+ - **Schema Validation**: Verifies SQL queries against your database schema.
256
+ - **Table and Column Verification**: Confirms the existence of tables and columns used in the query.
257
+ - **Alias Aware**: Correctly resolves table aliases.
258
+ - **Clear Error Reporting**: Provides descriptive error messages for easy debugging.
259
+ - **Static Analysis**: Allows comparison of SQL-derived schema information with predefined schema definitions.
260
+ - **Automated Regression Testing**: Facilitates unit testing for schema change impacts on queries.
449
261
 
450
262
  ```typescript
451
- import { TableColumnResolver, SelectQueryParser, SelectableColumnCollector, SelectValueCollector, TableSourceCollector, SqlFormatter } from 'rawsql-ts';
263
+ import { SelectQueryParser, SqlSchemaValidator } from 'rawsql-ts';
452
264
 
453
- // TableColumnResolver example
454
- const resolver: TableColumnResolver = (tableName) => {
455
- if (tableName === 'users') return ['user_id', 'user_name', 'email'];
456
- if (tableName === 'posts') return ['post_id', 'user_id', 'title', 'content'];
457
- return [];
265
+ // Define your database schema
266
+ const schema = {
267
+ users: ['user_id', 'user_name', 'email', 'status'],
268
+ orders: ['order_id', 'user_id', 'order_date', 'total_amount']
458
269
  };
459
270
 
460
- const sql = `SELECT u.*, p.title as post_title FROM users u INNER JOIN posts p ON u.user_id = p.user_id`;
461
- const query = SelectQueryParser.parse(sql);
462
- const formatter = new SqlFormatter();
463
-
464
- // Collects information from the SELECT clause.
465
- // To expand wildcards, you must specify a TableColumnResolver.
466
- const selectValueCollector = new SelectValueCollector(resolver);
467
- const selectValues = selectValueCollector.collect(query);
468
- // Log the name and formatted value of each select value
469
- console.log('Select values:');
470
- selectValues.forEach(val => {
471
- console.log(` name: ${val.name}, value: ${formatter.format(val.value).formattedSql}`);
472
- });
473
- /*
474
- Select values:
475
- name: post_title, value: "p"."title"
476
- name: user_id, value: "u"."user_id"
477
- name: user_name, value: "u"."user_name"
478
- name: email, value: "u"."email"
479
- */
271
+ const validator = new SqlSchemaValidator(schema);
272
+
273
+ // Example: Validate a SELECT query
274
+ const validSql = 'SELECT u.user_id, u.user_name FROM users as u WHERE u.status = \'active\'';
275
+ const queryToValidate = SelectQueryParser.parse(validSql);
276
+
277
+ try {
278
+ validator.validate(queryToValidate);
279
+ console.log('Query is valid against the schema.');
280
+ } catch (error) {
281
+ console.error('Schema validation failed:', error.message);
282
+ }
283
+
284
+ // Example: Validate a query with a non-existent column
285
+ const invalidSql = 'SELECT user_id, non_existent_column FROM users';
286
+ const invalidQuery = SelectQueryParser.parse(invalidSql);
287
+
288
+ try {
289
+ validator.validate(invalidQuery);
290
+ } catch (error) {
291
+ console.error('Schema validation error for non-existent column:', error.message);
292
+ // Expected output: Validation failed: Column 'non_existent_column' not found in table 'users'.
293
+ }
480
294
  ```
481
295
 
482
- ```typescript
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
- ```
296
+ For more details on `SqlSchemaValidator`, see the [SqlSchemaValidator Usage Guide](./docs/usage-guides/class-SqlSchemaValidator-usage-guide.md).
536
297
 
537
298
  ---
538
299
 
539
- ## Advanced Example: Table Join
300
+ ## QueryBuilder Features
540
301
 
541
- This example demonstrates how to join two tables using rawsql-ts. You do not need to understand the internal structure or manage aliases manually. By specifying the join key(s), the ON clause is generated automatically.
302
+ `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
303
 
543
304
  ```typescript
544
- import { SelectQueryParser, SqlFormatter, SimpleSelectQuery } from 'rawsql-ts';
545
-
546
- // Parse the base query
547
- const query = SelectQueryParser.parse('SELECT u.user_id, u.name FROM users u') as SimpleSelectQuery;
548
-
549
- // Add LEFT JOIN using the leftJoinRaw method (join on user_id)
550
- query.leftJoinRaw('orders', 'o', ['user_id']);
551
-
552
- // Add WHERE clause
553
- query.appendWhereRaw('o.order_id IS NULL');
554
-
555
- const formatter = new SqlFormatter();
556
- const formattedSql = formatter.format(query).formattedSql;
557
-
558
- console.log(formattedSql);
559
- // => select "u"."user_id", "u"."name" from "users" as "u" left join "orders" as "o" on "u"."user_id" = "o"."user_id" where "o"."order_id" is null
305
+ import { SelectQueryParser, QueryBuilder, SqlFormatter, QueryNormalizer } from 'rawsql-ts';
306
+
307
+ // Example: Convert a SELECT query to an UPDATE query
308
+ const selectSourceSql = 'SELECT id, new_email AS email, last_login FROM user_updates_source WHERE needs_update = TRUE';
309
+ // QueryBuilder.buildUpdateQuery expects a SimpleSelectQuery as input.
310
+ // If your source is a complex query (e.g. with UNIONs or CTEs), normalize it first.
311
+ const normalizedSelectQuery = QueryNormalizer.normalize(SelectQueryParser.parse(selectSourceSql));
312
+
313
+ // Define the target table for the UPDATE and the primary key(s) for joining
314
+ const targetTable = 'users';
315
+ const primaryKeys = ['id']; // Column(s) to match records between source and target
316
+
317
+ const updateQuery = QueryBuilder.buildUpdateQuery(
318
+ normalizedSelectQuery,
319
+ 'd', // Alias of the source query in the FROM clause
320
+ targetTable,
321
+ primaryKeys
322
+ );
323
+
324
+ const formatter = new SqlFormatter({ preset: 'postgres' }); // Using postgres preset for clarity
325
+ const { formattedSql: updateSql } = formatter.format(updateQuery);
326
+
327
+ console.log(updateSql);
328
+ // Example output (actual output depends on the SQL dialect and specific query structure):
329
+ // 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
330
  ```
561
331
 
562
- **Key Points:**
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
332
+ For more details on `QueryBuilder`, see the [QueryBuilder Usage Guide](./docs/usage-guides/class-QueryBuilder-usage-guide.md).
567
333
 
568
334
  ---
569
335