Skip to content

Commit b4b16e7

Browse files
authored
Merge placeholder-replacement
2 parents a619460 + 96b92af commit b4b16e7

12 files changed

+299
-61
lines changed

.editorconfig

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# EditorConfig helps developers define and maintain
2-
# consistent coding styles between different editors and IDEs.
3-
41
root = true
52

63
[*]
@@ -11,5 +8,6 @@ insert_final_newline = true
118
indent_style = space
129
indent_size = 4
1310

14-
[*.md]
15-
trim_trailing_whitespace = false
11+
[{package.json,.travis.yml}]
12+
indent_style = space
13+
indent_size = 2

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,31 @@ Currently just two SQL dialects are supported:
4545
- **sql** - [Standard SQL][]
4646
- **n1ql** - [Couchbase N1QL][]
4747

48+
### Placeholders replacement
49+
50+
```js
51+
// Named placeholders
52+
sqlFormatter.format("SELECT * FROM tbl WHERE foo = @foo", {
53+
params: {foo: "'bar'"}
54+
}));
55+
56+
// Indexed placeholders
57+
sqlFormatter.format("SELECT * FROM tbl WHERE foo = ?", {
58+
params: ["'bar'"]
59+
}));
60+
```
61+
62+
Both result in:
63+
64+
```
65+
SELECT
66+
*
67+
FROM
68+
tbl
69+
WHERE
70+
foo = 'bar'
71+
```
72+
4873
## Usage without NPM
4974

5075
If you don't use a module bundler, clone the repository, run `npm install` and grab a file from `/dist` directory to use inside a `<script>` tag.

src/core/Formatter.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import _ from "lodash";
2-
import sqlTokenTypes from "./tokenTypes";
2+
import tokenTypes from "./tokenTypes";
33
import Indentation from "./Indentation";
44
import InlineBlock from "./InlineBlock";
5+
import Params from "./Params";
56

67
export default class Formatter {
78
/**
89
* @param {Object} cfg
9-
* @param {Object} cfg.indent
10+
* @param {Object} cfg.indent
11+
* @param {Object} cfg.params
1012
* @param {Tokenizer} tokenizer
1113
*/
1214
constructor(cfg, tokenizer) {
1315
this.cfg = cfg || {};
1416
this.indentation = new Indentation(this.cfg.indent);
1517
this.inlineBlock = new InlineBlock();
18+
this.params = new Params(this.cfg.params);
1619
this.tokenizer = tokenizer;
1720
this.previousReservedWord = {};
1821
}
1922

2023
/**
21-
* Format the whitespace in a SQL string to make it easier to read.
24+
* Formats whitespaces in a SQL string to make it easier to read.
2225
*
2326
* @param {String} query The SQL query string
2427
* @return {String} formatted query
@@ -34,33 +37,36 @@ export default class Formatter {
3437
let formattedQuery = "";
3538

3639
tokens.forEach((token, index) => {
37-
if (token.type === sqlTokenTypes.WHITESPACE) {
40+
if (token.type === tokenTypes.WHITESPACE) {
3841
return;
3942
}
40-
else if (token.type === sqlTokenTypes.LINE_COMMENT) {
43+
else if (token.type === tokenTypes.LINE_COMMENT) {
4144
formattedQuery = this.formatLineComment(token, formattedQuery);
4245
}
43-
else if (token.type === sqlTokenTypes.BLOCK_COMMENT) {
46+
else if (token.type === tokenTypes.BLOCK_COMMENT) {
4447
formattedQuery = this.formatBlockComment(token, formattedQuery);
4548
}
46-
else if (token.type === sqlTokenTypes.RESERVED_TOPLEVEL) {
49+
else if (token.type === tokenTypes.RESERVED_TOPLEVEL) {
4750
formattedQuery = this.formatToplevelReservedWord(token, formattedQuery);
4851
this.previousReservedWord = token;
4952
}
50-
else if (token.type === sqlTokenTypes.RESERVED_NEWLINE) {
53+
else if (token.type === tokenTypes.RESERVED_NEWLINE) {
5154
formattedQuery = this.formatNewlineReservedWord(token, formattedQuery);
5255
this.previousReservedWord = token;
5356
}
54-
else if (token.type === sqlTokenTypes.RESERVED) {
57+
else if (token.type === tokenTypes.RESERVED) {
5558
formattedQuery = this.formatWithSpaces(token, formattedQuery);
5659
this.previousReservedWord = token;
5760
}
58-
else if (token.type === sqlTokenTypes.OPEN_PAREN) {
61+
else if (token.type === tokenTypes.OPEN_PAREN) {
5962
formattedQuery = this.formatOpeningParentheses(tokens, index, formattedQuery);
6063
}
61-
else if (token.type === sqlTokenTypes.CLOSE_PAREN) {
64+
else if (token.type === tokenTypes.CLOSE_PAREN) {
6265
formattedQuery = this.formatClosingParentheses(token, formattedQuery);
6366
}
67+
else if (token.type === tokenTypes.PLACEHOLDER) {
68+
formattedQuery = this.formatPlaceholder(token, formattedQuery);
69+
}
6470
else if (token.value === ",") {
6571
formattedQuery = this.formatComma(token, formattedQuery);
6672
}
@@ -113,7 +119,7 @@ export default class Formatter {
113119
formatOpeningParentheses(tokens, index, query) {
114120
// Take out the preceding space unless there was whitespace there in the original query or another opening parens
115121
const previousToken = tokens[index - 1];
116-
if (previousToken && previousToken.type !== sqlTokenTypes.WHITESPACE && previousToken.type !== sqlTokenTypes.OPEN_PAREN) {
122+
if (previousToken && previousToken.type !== tokenTypes.WHITESPACE && previousToken.type !== tokenTypes.OPEN_PAREN) {
117123
query = _.trimEnd(query);
118124
}
119125
query += tokens[index].value;
@@ -139,6 +145,10 @@ export default class Formatter {
139145
}
140146
}
141147

148+
formatPlaceholder(token, query) {
149+
return query + this.params.get(token) + " ";
150+
}
151+
142152
// Commas start a new line (unless within inline parentheses or SQL "LIMIT" clause)
143153
formatComma(token, query) {
144154
query = _.trimEnd(query) + token.value + " ";

src/core/InlineBlock.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import sqlTokenTypes from "./tokenTypes";
1+
import tokenTypes from "./tokenTypes";
22

33
const INLINE_MAX_LENGTH = 50;
44

@@ -63,10 +63,10 @@ export default class InlineBlock {
6363
return false;
6464
}
6565

66-
if (token.type === sqlTokenTypes.OPEN_PAREN) {
66+
if (token.type === tokenTypes.OPEN_PAREN) {
6767
level++;
6868
}
69-
else if (token.type === sqlTokenTypes.CLOSE_PAREN) {
69+
else if (token.type === tokenTypes.CLOSE_PAREN) {
7070
level--;
7171
if (level === 0) {
7272
return true;
@@ -83,10 +83,10 @@ export default class InlineBlock {
8383
// Reserved words that cause newlines, comments and semicolons
8484
// are not allowed inside inline parentheses block
8585
isForbiddenToken({type, value}) {
86-
return type === sqlTokenTypes.RESERVED_TOPLEVEL ||
87-
type === sqlTokenTypes.RESERVED_NEWLINE ||
88-
type === sqlTokenTypes.COMMENT ||
89-
type === sqlTokenTypes.BLOCK_COMMENT ||
86+
return type === tokenTypes.RESERVED_TOPLEVEL ||
87+
type === tokenTypes.RESERVED_NEWLINE ||
88+
type === tokenTypes.COMMENT ||
89+
type === tokenTypes.BLOCK_COMMENT ||
9090
value === ";";
9191
}
9292
}

src/core/Params.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Handles placeholder replacement with given params.
3+
*/
4+
export default class Params {
5+
/**
6+
* @param {Object} params
7+
*/
8+
constructor(params) {
9+
this.params = params;
10+
this.index = 0;
11+
}
12+
13+
/**
14+
* Returns param value that matches given placeholder with param key.
15+
* @param {Object} token
16+
* @param {String} token.key Placeholder key
17+
* @param {String} token.value Placeholder value
18+
* @return {String} param or token.value when params are missing
19+
*/
20+
get({key, value}) {
21+
if (!this.params) {
22+
return value;
23+
}
24+
if (key) {
25+
return this.params[key];
26+
}
27+
return this.params[this.index ++];
28+
}
29+
}

0 commit comments

Comments
 (0)