Skip to content

Commit 08af1f6

Browse files
authored
Merge pull request #25 from zeroturnaround/db2-support
IBM DB2 support
2 parents 69a1246 + a0d16a4 commit 08af1f6

File tree

9 files changed

+277
-30
lines changed

9 files changed

+277
-30
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**SQL Formatter** is a JavaScript library for pretty-printing SQL queries.
44
It started as a port of a [PHP Library][], but has since considerably diverged.
5-
It supports [Standard SQL][] and [Couchbase N1QL][] dialects.
5+
It supports [Standard SQL][], [Couchbase N1QL][] and [IBM DB2][] dialects.
66

77
→ [Try the demo.](https://zeroturnaround.github.io/sql-formatter/)
88

@@ -35,15 +35,16 @@ You can also pass in configuration options:
3535

3636
```js
3737
sqlFormatter.format("SELECT *", {
38-
language: "n1ql" // Defaults to "sql"
38+
language: "n1ql", // Defaults to "sql"
3939
indent: " " // Defaults to two spaces
4040
});
4141
```
4242

43-
Currently just two SQL dialects are supported:
43+
Currently just three SQL dialects are supported:
4444

4545
- **sql** - [Standard SQL][]
4646
- **n1ql** - [Couchbase N1QL][]
47+
- **db2** - [IBM DB2][]
4748

4849
### Placeholders replacement
4950

@@ -91,3 +92,4 @@ $ npm run check
9192
[PHP library]: https://github.com/jdorn/sql-formatter
9293
[Standard SQL]: https://en.wikipedia.org/wiki/SQL:2011
9394
[Couchbase N1QL]: http://www.couchbase.com/n1ql
95+
[IBM DB2]: https://www.ibm.com/analytics/us/en/technology/db2/

index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ <h1>SQL Formatter</h1>
9494
<option value="n1ql">
9595
N1QL
9696
</option>
97+
<option value="db2">
98+
DB2
99+
</option>
97100
</select>
98101
</div>
99102
</header>

src/core/Tokenizer.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ export default class Tokenizer {
1212
* @param {String[]} cfg.closeParens Closing parentheses to enable, like ), ]
1313
* @param {String[]} cfg.indexedPlaceholderTypes Prefixes for indexed placeholders, like ?
1414
* @param {String[]} cfg.namedPlaceholderTypes Prefixes for named placeholders, like @ and :
15+
* @param {String[]} cfg.lineCommentTypes Line comments to enable, like # and --
16+
* @param {String[]} cfg.specialWordChars Special chars that can be found inside of words, like @ and #
1517
*/
1618
constructor(cfg) {
17-
this.WORD_REGEX = /^(\w+)/;
1819
this.WHITESPACE_REGEX = /^(\s+)/;
19-
this.LINE_COMMENT_REGEX = /^((?:#|--).*?(?:\n|$))/;
20-
this.BLOCK_COMMENT_REGEX = /^(\/\*[^]*?(?:\*\/|$))/;
2120
this.NUMBER_REGEX = /^((-\s*)?[0-9]+(\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)\b/;
2221
this.OPERATOR_REGEX = /^(!=|<>|==|<=|>=|!<|!>|\|\||::|->>|->|.)/;
2322

23+
this.BLOCK_COMMENT_REGEX = /^(\/\*[^]*?(?:\*\/|$))/;
24+
this.LINE_COMMENT_REGEX = this.createLineCommentRegex(cfg.lineCommentTypes);
25+
2426
this.RESERVED_TOPLEVEL_REGEX = this.createReservedWordRegex(cfg.reservedToplevelWords);
2527
this.RESERVED_NEWLINE_REGEX = this.createReservedWordRegex(cfg.reservedNewlineWords);
2628
this.RESERVED_PLAIN_REGEX = this.createReservedWordRegex(cfg.reservedWords);
2729

30+
this.WORD_REGEX = this.createWordRegex(cfg.specialWordChars);
2831
this.STRING_REGEX = this.createStringRegex(cfg.stringTypes);
2932

3033
this.OPEN_PAREN_REGEX = this.createParenRegex(cfg.openParens);
@@ -38,11 +41,19 @@ export default class Tokenizer {
3841
);
3942
}
4043

44+
createLineCommentRegex(lineCommentTypes) {
45+
return new RegExp(`^((?:${lineCommentTypes.map(c => _.escapeRegExp(c)).join("|")}).*?(?:\n|$))`);
46+
}
47+
4148
createReservedWordRegex(reservedWords) {
4249
const reservedWordsPattern = reservedWords.join("|").replace(/ /g, "\\s+");
4350
return new RegExp(`^(${reservedWordsPattern})\\b`, "i");
4451
}
4552

53+
createWordRegex(specialChars = []) {
54+
return new RegExp(`^([\\w${specialChars.join("")}]+)`);
55+
}
56+
4657
createStringRegex(stringTypes) {
4758
return new RegExp(
4859
"^(" + this.createStringPattern(stringTypes) + ")"
@@ -57,11 +68,11 @@ export default class Tokenizer {
5768
// 5. national character quoted string using N'' or N\' to escape
5869
createStringPattern(stringTypes) {
5970
const patterns = {
71+
"``": "((`[^`]*($|`))+)",
72+
"[]": "((\\[[^\\]]*($|\\]))(\\][^\\]]*($|\\]))*)",
6073
"\"\"": "((\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*(\"|$))+)",
6174
"''": "(('[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)",
6275
"N''": "((N'[^N'\\\\]*(?:\\\\.[^N'\\\\]*)*('|$))+)",
63-
"``": "((`[^`]*($|`))+)",
64-
"[]": "((\\[[^\\]]*($|\\]))(\\][^\\]]*($|\\]))*)",
6576
};
6677

6778
return stringTypes.map(t => patterns[t]).join("|");

src/languages/Db2Formatter.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import Formatter from "../core/Formatter";
2+
import Tokenizer from "../core/Tokenizer";
3+
4+
const reservedWords = [
5+
"ABS", "ACTIVATE", "ALIAS", "ALL", "ALLOCATE", "ALLOW", "ALTER", "ANY", "ARE", "ARRAY", "AS", "ASC",
6+
"ASENSITIVE", "ASSOCIATE", "ASUTIME", "ASYMMETRIC", "AT", "ATOMIC", "ATTRIBUTES", "AUDIT", "AUTHORIZATION", "AUX", "AUXILIARY", "AVG",
7+
"BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BUFFERPOOL", "BY",
8+
"CACHE", "CALL", "CALLED", "CAPTURE", "CARDINALITY", "CASCADED", "CASE", "CAST", "CCSID", "CEIL", "CEILING", "CHAR", "CHARACTER",
9+
"CHARACTER_LENGTH", "CHAR_LENGTH", "CHECK", "CLOB", "CLONE", "CLOSE", "CLUSTER", "COALESCE", "COLLATE", "COLLECT", "COLLECTION",
10+
"COLLID", "COLUMN", "COMMENT", "COMMIT", "CONCAT", "CONDITION", "CONNECT", "CONNECTION", "CONSTRAINT", "CONTAINS", "CONTINUE",
11+
"CONVERT", "CORR", "CORRESPONDING", "COUNT", "COUNT_BIG", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT",
12+
"CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_LC_CTYPE", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_SCHEMA",
13+
"CURRENT_SERVER", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TIMEZONE", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR",
14+
"CYCLE",
15+
"DATA", "DATABASE", "DATAPARTITIONNAME", "DATAPARTITIONNUM", "DATE", "DAY", "DAYS", "DB2GENERAL", "DB2GENRL", "DB2SQL", "DBINFO",
16+
"DBPARTITIONNAME", "DBPARTITIONNUM", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFAULTS", "DEFINITION", "DELETE",
17+
"DENSERANK", "DENSE_RANK", "DEREF", "DESCRIBE", "DESCRIPTOR", "DETERMINISTIC", "DIAGNOSTICS", "DISABLE", "DISALLOW", "DISCONNECT",
18+
"DISTINCT", "DO", "DOCUMENT", "DOUBLE", "DROP", "DSSIZE", "DYNAMIC",
19+
"EACH", "EDITPROC", "ELEMENT", "ELSE", "ELSEIF", "ENABLE", "ENCODING", "ENCRYPTION", "END", "END-EXEC", "ENDING", "ERASE", "ESCAPE",
20+
"EVERY", "EXCEPTION", "EXCLUDING", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXP", "EXPLAIN", "EXTENDED", "EXTERNAL",
21+
"EXTRACT",
22+
"FALSE", "FENCED", "FETCH", "FIELDPROC", "FILE", "FILTER", "FINAL", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FREE", "FULL",
23+
"FUNCTION", "FUSION",
24+
"GENERAL", "GENERATED", "GET", "GLOBAL", "GOTO", "GRANT", "GRAPHIC", "GROUP", "GROUPING",
25+
"HANDLER", "HASH", "HASHED_VALUE", "HINT", "HOLD", "HOUR", "HOURS",
26+
"IDENTITY", "IF", "IMMEDIATE", "IN", "INCLUDING", "INCLUSIVE", "INCREMENT", "INDEX", "INDICATOR", "INDICATORS", "INF", "INFINITY",
27+
"INHERIT", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTEGRITY", "INTERSECTION", "INTERVAL", "INTO",
28+
"IS", "ISOBID", "ISOLATION", "ITERATE",
29+
"JAR", "JAVA",
30+
"KEEP", "KEY",
31+
"LABEL", "LANGUAGE", "LARGE", "LATERAL", "LC_CTYPE", "LEADING", "LEAVE", "LEFT", "LIKE", "LINKTYPE", "LN", "LOCAL",
32+
"LOCALDATE", "LOCALE", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "LOCATORS", "LOCK", "LOCKMAX", "LOCKSIZE", "LONG", "LOOP", "LOWER",
33+
"MAINTAINED", "MATCH", "MATERIALIZED", "MAX", "MAXVALUE", "MEMBER", "MERGE", "METHOD", "MICROSECOND", "MICROSECONDS", "MIN", "MINUTE",
34+
"MINUTES", "MINVALUE", "MOD", "MODE", "MODIFIES", "MODULE", "MONTH", "MONTHS", "MULTISET",
35+
"NAN", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEW_TABLE", "NEXTVAL", "NO", "NOCACHE", "NOCYCLE", "NODENAME", "NODENUMBER",
36+
"NOMAXVALUE", "NOMINVALUE", "NONE", "NOORDER", "NORMALIZE", "NORMALIZED", "NOT", "NULL", "NULLIF", "NULLS", "NUMERIC", "NUMPARTS",
37+
"OBID", "OCTET_LENGTH", "OF", "OFFSET", "OLD", "OLD_TABLE", "ON", "ONLY", "OPEN", "OPTIMIZATION", "OPTIMIZE", "OPTION", "ORDER",
38+
"OUT", "OUTER", "OVER", "OVERLAPS", "OVERLAY", "OVERRIDING",
39+
"PACKAGE", "PADDED", "PAGESIZE", "PARAMETER", "PART", "PARTITION", "PARTITIONED", "PARTITIONING", "PARTITIONS", "PASSWORD", "PATH",
40+
"PERCENTILE_CONT", "PERCENTILE_DISC", "PERCENT_RANK", "PIECESIZE", "PLAN", "POSITION", "POWER", "PRECISION", "PREPARE", "PREVVAL",
41+
"PRIMARY", "PRIQTY", "PRIVILEGES", "PROCEDURE", "PROGRAM", "PSID", "PUBLIC",
42+
"QUERY", "QUERYNO",
43+
"RANGE", "RANK", "READ", "READS", "REAL", "RECOVERY", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REFRESH", "REGR_AVGX",
44+
"REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", "RENAME", "REPEAT",
45+
"RESET", "RESIGNAL", "RESTART", "RESTRICT", "RESULT", "RESULT_SET_LOCATOR", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK",
46+
"ROLLUP", "ROUND_CEILING", "ROUND_DOWN", "ROUND_FLOOR", "ROUND_HALF_DOWN", "ROUND_HALF_EVEN", "ROUND_HALF_UP", "ROUND_UP", "ROUTINE",
47+
"ROW", "ROWNUMBER", "ROWS", "ROWSET", "ROW_NUMBER", "RRN", "RUN",
48+
"SAVEPOINT", "SCHEMA", "SCOPE", "SCRATCHPAD", "SCROLL", "SEARCH", "SECOND", "SECONDS", "SECQTY", "SECURITY", "SENSITIVE",
49+
"SEQUENCE", "SESSION", "SESSION_USER", "SIGNAL", "SIMILAR", "SIMPLE", "SMALLINT", "SNAN", "SOME", "SOURCE", "SPECIFIC",
50+
"SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLID", "SQLSTATE", "SQLWARNING", "SQRT", "STACKED", "STANDARD", "START", "STARTING",
51+
"STATEMENT", "STATIC", "STATMENT", "STAY", "STDDEV_POP", "STDDEV_SAMP", "STOGROUP", "STORES", "STYLE", "SUBMULTISET", "SUBSTRING",
52+
"SUM", "SUMMARY", "SYMMETRIC", "SYNONYM", "SYSFUN", "SYSIBM", "SYSPROC", "SYSTEM", "SYSTEM_USER",
53+
"TABLE", "TABLESAMPLE", "TABLESPACE", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSACTION",
54+
"TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TYPE",
55+
"UESCAPE", "UNDO", "UNIQUE", "UNKNOWN", "UNNEST", "UNTIL", "UPPER", "USAGE", "USER", "USING",
56+
"VALIDPROC", "VALUE", "VARCHAR", "VARIABLE", "VARIANT", "VARYING", "VAR_POP", "VAR_SAMP", "VCAT", "VERSION", "VIEW",
57+
"VOLATILE", "VOLUMES", "WHEN", "WHENEVER", "WHILE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WLM", "WRITE",
58+
"XMLELEMENT", "XMLEXISTS", "XMLNAMESPACES",
59+
"YEAR", "YEARS"
60+
];
61+
62+
const reservedToplevelWords = [
63+
"ADD", "AFTER", "ALTER COLUMN", "ALTER TABLE",
64+
"DELETE FROM",
65+
"EXCEPT",
66+
"FETCH FIRST", "FROM",
67+
"GROUP BY", "GO",
68+
"HAVING",
69+
"INSERT INTO", "INTERSECT",
70+
"LIMIT",
71+
"ORDER BY",
72+
"SELECT", "SET CURRENT SCHEMA", "SET SCHEMA", "SET",
73+
"UNION ALL", "UPDATE",
74+
"VALUES",
75+
"WHERE"
76+
];
77+
78+
const reservedNewlineWords = [
79+
"AND",
80+
"CROSS JOIN",
81+
"INNER JOIN",
82+
"JOIN",
83+
"LEFT JOIN", "LEFT OUTER JOIN",
84+
"OR", "OUTER JOIN",
85+
"RIGHT JOIN", "RIGHT OUTER JOIN"
86+
];
87+
88+
let tokenizer;
89+
90+
export default class Db2Formatter {
91+
/**
92+
* @param {Object} cfg Different set of configurations
93+
*/
94+
constructor(cfg) {
95+
this.cfg = cfg;
96+
}
97+
98+
/**
99+
* Formats DB2 query to make it easier to read
100+
*
101+
* @param {String} query The DB2 query string
102+
* @return {String} formatted string
103+
*/
104+
format(query) {
105+
if (!tokenizer) {
106+
tokenizer = new Tokenizer({
107+
reservedWords,
108+
reservedToplevelWords,
109+
reservedNewlineWords,
110+
stringTypes: [`""`, "''", "``", "[]"],
111+
openParens: ["("],
112+
closeParens: [")"],
113+
indexedPlaceholderTypes: ["?"],
114+
namedPlaceholderTypes: [":"],
115+
lineCommentTypes: ["--"],
116+
specialWordChars: ["#", "@"]
117+
});
118+
}
119+
return new Formatter(this.cfg, tokenizer).format(query);
120+
}
121+
}

src/languages/N1qlFormatter.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,32 @@ const reservedWords = [
2828
];
2929

3030
const reservedToplevelWords = [
31-
"DELETE FROM", "EXCEPT ALL", "EXCEPT", "EXPLAIN DELETE FROM", "EXPLAIN UPDATE", "EXPLAIN UPSERT", "FROM",
32-
"GROUP BY", "HAVING", "INFER", "INSERT INTO", "INTERSECT ALL", "INTERSECT", "LET", "LIMIT", "MERGE", "NEST",
33-
"ORDER BY", "PREPARE", "SELECT", "SET CURRENT SCHEMA", "SET SCHEMA", "SET", "UNION ALL", "UNION", "UNNEST",
34-
"UPDATE", "UPSERT", "USE KEYS", "VALUES", "WHERE"
31+
"DELETE FROM",
32+
"EXCEPT ALL", "EXCEPT", "EXPLAIN DELETE FROM", "EXPLAIN UPDATE", "EXPLAIN UPSERT",
33+
"FROM",
34+
"GROUP BY",
35+
"HAVING",
36+
"INFER", "INSERT INTO", "INTERSECT ALL", "INTERSECT",
37+
"LET", "LIMIT",
38+
"MERGE",
39+
"NEST",
40+
"ORDER BY",
41+
"PREPARE",
42+
"SELECT", "SET CURRENT SCHEMA", "SET SCHEMA", "SET",
43+
"UNION ALL", "UNION", "UNNEST", "UPDATE", "UPSERT", "USE KEYS",
44+
"VALUES",
45+
"WHERE"
3546
];
3647

3748
const reservedNewlineWords = [
38-
"AND", "INNER JOIN", "JOIN", "LEFT JOIN", "LEFT OUTER JOIN", "OR", "OUTER JOIN", "RIGHT JOIN", "RIGHT OUTER JOIN", "XOR"
49+
"AND",
50+
"INNER JOIN",
51+
"JOIN",
52+
"LEFT JOIN",
53+
"LEFT OUTER JOIN",
54+
"OR", "OUTER JOIN",
55+
"RIGHT JOIN", "RIGHT OUTER JOIN",
56+
"XOR"
3957
];
4058

4159
let tokenizer;
@@ -63,7 +81,8 @@ export default class N1qlFormatter {
6381
stringTypes: [`""`, "''", "``"],
6482
openParens: ["(", "[", "{"],
6583
closeParens: [")", "]", "}"],
66-
namedPlaceholderTypes: ["$"]
84+
namedPlaceholderTypes: ["$"],
85+
lineCommentTypes: ["#", "--"]
6786
});
6887
}
6988
return new Formatter(this.cfg, tokenizer).format(query);

src/languages/StandardSqlFormatter.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,31 @@ const reservedWords = [
4242
];
4343

4444
const reservedToplevelWords = [
45-
"ADD", "AFTER", "ALTER COLUMN", "ALTER TABLE", "DELETE FROM", "EXCEPT", "FROM", "GROUP BY", "GO", "HAVING", "INSERT INTO", "INTERSECT",
46-
"LIMIT", "MODIFY", "ORDER BY", "SELECT", "SET CURRENT SCHEMA", "SET SCHEMA", "SET", "UNION ALL", "UNION", "UPDATE", "VALUES", "WHERE"
45+
"ADD", "AFTER", "ALTER COLUMN", "ALTER TABLE",
46+
"DELETE FROM",
47+
"EXCEPT",
48+
"FROM",
49+
"GROUP BY", "GO",
50+
"HAVING",
51+
"INSERT INTO", "INTERSECT",
52+
"LIMIT",
53+
"MODIFY",
54+
"ORDER BY",
55+
"SELECT", "SET CURRENT SCHEMA", "SET SCHEMA", "SET",
56+
"UNION ALL", "UNION", "UPDATE",
57+
"VALUES",
58+
"WHERE"
4759
];
4860

4961
const reservedNewlineWords = [
50-
"AND", "CROSS APPLY", "CROSS JOIN", "INNER JOIN", "JOIN", "LEFT JOIN", "LEFT OUTER JOIN", "OR", "OUTER APPLY", "OUTER JOIN",
51-
"RIGHT JOIN", "RIGHT OUTER JOIN", "XOR"
62+
"AND",
63+
"CROSS APPLY", "CROSS JOIN",
64+
"INNER JOIN",
65+
"JOIN",
66+
"LEFT JOIN", "LEFT OUTER JOIN",
67+
"OR", "OUTER APPLY", "OUTER JOIN",
68+
"RIGHT JOIN", "RIGHT OUTER JOIN",
69+
"XOR"
5270
];
5371

5472
let tokenizer;
@@ -77,7 +95,8 @@ export default class StandardSqlFormatter {
7795
openParens: ["("],
7896
closeParens: [")"],
7997
indexedPlaceholderTypes: ["?"],
80-
namedPlaceholderTypes: ["@", ":"]
98+
namedPlaceholderTypes: ["@", ":"],
99+
lineCommentTypes: ["#", "--"]
81100
});
82101
}
83102
return new Formatter(this.cfg, tokenizer).format(query);

src/sqlFormatter.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Db2Formatter from "./languages/Db2Formatter";
12
import N1qlFormatter from "./languages/N1qlFormatter";
23
import StandardSqlFormatter from "./languages/StandardSqlFormatter";
34

@@ -16,6 +17,8 @@ export default {
1617
cfg = cfg || {};
1718

1819
switch (cfg.language) {
20+
case "db2":
21+
return new Db2Formatter(cfg).format(query);
1922
case "n1ql":
2023
return new N1qlFormatter(cfg).format(query);
2124
default:

0 commit comments

Comments
 (0)