Skip to content

Commit 2c8c15c

Browse files
committed
Merge PR #386: Add paramTypes config option
2 parents 385ee58 + 6c67e4e commit 2c8c15c

28 files changed

+298
-113
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ All fields are optional and all fields that are not specified will be filled wit
157157
- [**`denseOperators`**](docs/denseOperators.md) packs operators densely without spaces.
158158
- [**`newlineBeforeSemicolon`**](docs/newlineBeforeSemicolon.md) places semicolon on separate line.
159159
- [**`params`**](docs/params.md) collection of values for placeholder replacement.
160+
- [**`paramsTypes`**](docs/paramTypes.md) specifies parameter placeholders types to support.
160161

161162
### Usage without NPM
162163

docs/paramTypes.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# paramTypes
2+
3+
Specifies parameter types to support when parsing SQL prepared statements.
4+
5+
## Motivation
6+
7+
While some SQL dialects have built-in support for prepared statements,
8+
others do not and instead rely on 3rd party libraries to emulate it,
9+
while yet others might have built-in support for prepared statements,
10+
but the syntax depends on the driver used to connect to the database.
11+
12+
By default SQL Formatter supports only the built-in prepared statement
13+
syntax of an SQL dialect as documented in [params option documentation][params].
14+
For example if you're using PostgreSQL you can use the `$nr` syntax:
15+
16+
```ts
17+
format('SELECT * FROM users WHERE name = $1 AND age < $2', { language: 'postgresql' });
18+
```
19+
20+
However if you're connecting to the database using a Java DB connection library,
21+
you might expect to use `:name` placeholders for parameters:
22+
23+
```ts
24+
format('SELECT * FROM users WHERE name = :name AND age < :age', { language: 'postgresql' });
25+
```
26+
27+
This gets by default formatted like so:
28+
29+
```sql
30+
SELECT
31+
*
32+
FROM
33+
users
34+
WHERE
35+
name = : name
36+
AND age < : age
37+
```
38+
39+
To fix it, you'd need to specify with `paramTypes` config
40+
that you're using `:`-prefixed named placeholders:
41+
42+
```ts
43+
format('SELECT * FROM users WHERE name = :name AND age < :name', {
44+
language: 'postgresql',
45+
paramTypes: { named: [':'] },
46+
});
47+
```
48+
49+
After which you'll get the correct result:
50+
51+
```sql
52+
SELECT
53+
*
54+
FROM
55+
users
56+
WHERE
57+
name = :name
58+
AND age < :age
59+
```
60+
61+
## Option value
62+
63+
An object with the following following optional fields:
64+
65+
- **`positional`**: `boolean`. True to enable `?` placeholders, false to disable them.
66+
- **`numbered`**: `Array<"?" | ":" | "$">`. To allow for `?1`, `:2` and/or `$3` syntax for numbered placholders.
67+
- **`named`**: `Array<":" | "@" | "$">`. To allow for `:name`, `@name` and/or `$name` syntax for named placholders.
68+
- **`quoted`**: `Array<":" | "@" | "$">`. To allow for `:"name"`, `@"name"` and/or `$"name"` syntax for quoted placholders.
69+
Note that the type of quotes dependes on the quoted identifiers supported by a dialect.
70+
For example in MySQL using `paramTypes: {quoted: [':']}` would allow you to use `` :`name` `` syntax,
71+
while in Transact-SQL `:"name"` and `:[name]` would work instead.
72+
See [identifier syntax wiki page][] for information about differences in support quoted identifiers.
73+
74+
Note that using this config will override the by default supported placeholders types.
75+
For example PL/SQL supports numbered (`:1`) and named (`:name`) placeholders by default.
76+
When you provide the following `paramTypes` configuration:
77+
78+
```js
79+
paramTypes: { positional: true, numbered: [], named: [':', '@'] }
80+
```
81+
82+
The result will be:
83+
84+
- `?` positional placeholders are supported
85+
- `:1` numbered placeholders are no more supported
86+
- `:name` is still supported and `@name` named placeholder is also supported.
87+
88+
## Parameter value substitution
89+
90+
This config option can be used together with [params][] to substitute the placeholders with actual values.
91+
92+
[params]: ./params.md
93+
[identifier syntax wiki page]: https://github.com/sql-formatter-org/sql-formatter/wiki/identifiers

docs/params.md

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,49 @@ WHERE
8080
AND age = 27
8181
```
8282

83+
### Quoted placeholders
84+
85+
Some dialects (BigQuery, Transact SQL) also support quoted names for placeholders:
86+
87+
```js
88+
format('SELECT * FROM persons WHERE fname = @`first name` AND age = @`age`', {
89+
params: { 'first name': "'John'", 'age': '27' },
90+
language: 'bigquery',
91+
});
92+
```
93+
94+
Results in:
95+
96+
```sql
97+
SELECT
98+
*
99+
FROM
100+
persons
101+
WHERE
102+
fname = 'John'
103+
AND age = 27
104+
```
105+
83106
## Available placeholder types
84107

85-
The placeholder types available depend on SQL dialect used:
86-
87-
- sql - `?`, `?1`
88-
- bigquery - `?`, `?1`
89-
- db2 - `?`, `?1`, `:name`
90-
- hive - `?`, `?1`
91-
- mariadb - `?`, `?1`
92-
- mysql - `?`, `?1`
93-
- n1ql - `$name`
94-
- plsql - `?`, `?1`, `:name`
95-
- postgresql - `$`, `$1`, `:name`
96-
- redshift - `?`, `?1`, `@name`, `#name`, `$name`
97-
- sparksql - `?`, `?1`, `$name`
98-
- tsql - `@name`
108+
The placeholder types available by default depend on SQL dialect used:
109+
110+
- sql - `?`
111+
- bigquery - `?`, `@name`, `` @`name` ``
112+
- db2 - `?`, `:name`
113+
- hive - _no support_
114+
- mariadb - `?`
115+
- mysql - `?`
116+
- n1ql - `?`, `$1`, `$name`
117+
- plsql - `:1`, `:name`
118+
- postgresql - `$1`
119+
- redshift - `$1`
120+
- sqlite - `?`, `?1`, `:name`, `@name`, `$name`
121+
- spark - _no support_
122+
- tsql - `@name`, `@"name"`, `@[name]`
123+
- trino - _no support_
124+
125+
If you need to use a different placeholder syntax than the builtin one,
126+
you can configure the supported placeholder types using the [paramTypes][] config option.
127+
128+
[paramtypes]: ./paramTypes.md

src/FormatOptions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { SqlLanguage } from './sqlFormatter';
22
import { type ParamItems } from './formatter/Params';
33
import Formatter from './formatter/Formatter';
4+
import { ParamTypes } from './lexer/TokenizerOptions';
45

56
export type IndentStyle = 'standard' | 'tabularLeft' | 'tabularRight';
67

@@ -24,4 +25,5 @@ export interface FormatOptions {
2425
denseOperators: boolean;
2526
newlineBeforeSemicolon: boolean;
2627
params?: ParamItems | string[];
28+
paramTypes?: ParamTypes;
2729
}

src/formatter/ExpressionFormatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export default class ExpressionFormatter {
215215
case TokenType.VARIABLE:
216216
case TokenType.NAMED_PARAMETER:
217217
case TokenType.QUOTED_PARAMETER:
218-
case TokenType.INDEXED_PARAMETER:
218+
case TokenType.NUMBERED_PARAMETER:
219219
case TokenType.POSITIONAL_PARAMETER:
220220
return this.formatLiteral(token);
221221
default:

src/formatter/Formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default class Formatter {
4646
* @return {string} The formatter query
4747
*/
4848
public format(query: string): string {
49-
const tokens = this.cachedTokenizer().tokenize(query);
49+
const tokens = this.cachedTokenizer().tokenize(query, this.cfg.paramTypes || {});
5050
const ast = new Parser(tokens).parse();
5151
const formattedQuery = this.formatAst(ast);
5252
const finalQuery = this.postFormat(formattedQuery);

src/languages/bigquery/bigquery.formatter.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,7 @@ export default class BigQueryFormatter extends Formatter {
161161
],
162162
identTypes: ['``'],
163163
identChars: { dashes: true },
164-
positionalParams: true,
165-
namedParamTypes: ['@'],
166-
quotedParamTypes: ['@'],
164+
paramTypes: { positional: true, named: ['@'], quoted: ['@'] },
167165
lineCommentTypes: ['--', '#'],
168166
operators: BigQueryFormatter.operators,
169167
postProcess,

src/languages/db2/db2.formatter.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ export default class Db2Formatter extends Formatter {
181181
reservedFunctionNames: functions,
182182
stringTypes: [{ quote: "''", prefixes: ['G', 'N', 'X', 'BX', 'GX', 'UX', 'U&'] }],
183183
identTypes: [`""`],
184-
positionalParams: true,
185-
namedParamTypes: [':'],
184+
paramTypes: { positional: true, named: [':'] },
186185
paramChars: { first: '@#$', rest: '@#$' },
187186
operators: Db2Formatter.operators,
188187
});

src/languages/mariadb/mariadb.formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export default class MariaDbFormatter extends Formatter {
281281
{ quote: "''", prefixes: ['@'], requirePrefix: true },
282282
{ quote: '``', prefixes: ['@'], requirePrefix: true },
283283
],
284-
positionalParams: true,
284+
paramTypes: { positional: true },
285285
lineCommentTypes: ['--', '#'],
286286
operators: MariaDbFormatter.operators,
287287
postProcess,

src/languages/mysql/mysql.formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export default class MySqlFormatter extends Formatter {
249249
{ quote: "''", prefixes: ['@'], requirePrefix: true },
250250
{ quote: '``', prefixes: ['@'], requirePrefix: true },
251251
],
252-
positionalParams: true,
252+
paramTypes: { positional: true },
253253
lineCommentTypes: ['--', '#'],
254254
operators: MySqlFormatter.operators,
255255
postProcess,

0 commit comments

Comments
 (0)