sql-love 1.0.2 → 1.0.4
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 +52 -32
- package/dist/index.cjs +16 -0
- package/dist/index.d.ts +732 -9
- package/dist/index.mjs +16 -0
- package/package.json +29 -12
- package/.github/workflows/node.js.yml +0 -37
- package/.prettierrc.json +0 -13
- package/CHANGELOG.md +0 -39
- package/CONTRIBUTING.md +0 -85
- package/assets/sql-love-logo.png +0 -0
- package/codecov.yml +0 -7
- package/dist/index.js +0 -9
- package/dist/src/Parser/Parser.d.ts +0 -157
- package/dist/src/Parser/Parser.js +0 -304
- package/dist/src/SelectBuilder/SelectBuilder.d.ts +0 -379
- package/dist/src/SelectBuilder/SelectBuilder.js +0 -810
- package/dist/src/extractGrouped/extractGrouped.d.ts +0 -17
- package/dist/src/extractGrouped/extractGrouped.js +0 -31
- package/dist/src/extractIndexed/extractIndexed.d.ts +0 -12
- package/dist/src/extractIndexed/extractIndexed.js +0 -23
- package/dist/src/extractLookup/extractLookup.d.ts +0 -9
- package/dist/src/extractLookup/extractLookup.js +0 -21
- package/dist/src/getPagination/getPagination.d.ts +0 -16
- package/dist/src/getPagination/getPagination.js +0 -40
- package/dist/src/runMysql/runMysql.d.ts +0 -58
- package/dist/src/runMysql/runMysql.js +0 -61
- package/dist/src/runPrisma/runPrisma.d.ts +0 -41
- package/dist/src/runPrisma/runPrisma.js +0 -34
- package/dist/src/substrCount/substrCount.d.ts +0 -6
- package/dist/src/substrCount/substrCount.js +0 -14
- package/dist/src/toSafeJson/toSafeJson.d.ts +0 -25
- package/dist/src/toSafeJson/toSafeJson.js +0 -45
- package/index.ts +0 -19
- package/src/Parser/Parser.spec.ts +0 -226
- package/src/Parser/Parser.ts +0 -325
- package/src/SelectBuilder/SelectBuilder.spec.ts +0 -785
- package/src/SelectBuilder/SelectBuilder.ts +0 -914
- package/src/extractGrouped/extractGrouped.spec.ts +0 -43
- package/src/extractGrouped/extractGrouped.ts +0 -36
- package/src/extractIndexed/extractIndexed.spec.ts +0 -40
- package/src/extractIndexed/extractIndexed.ts +0 -28
- package/src/extractLookup/extractLookup.spec.ts +0 -38
- package/src/extractLookup/extractLookup.ts +0 -33
- package/src/getPagination/getPagination.spec.ts +0 -75
- package/src/getPagination/getPagination.ts +0 -42
- package/src/runMysql/runMysql.spec.ts +0 -286
- package/src/runMysql/runMysql.ts +0 -106
- package/src/runPrisma/runPrisma.spec.ts +0 -99
- package/src/runPrisma/runPrisma.ts +0 -75
- package/src/substrCount/substrCount.spec.ts +0 -29
- package/src/substrCount/substrCount.ts +0 -14
- package/src/toSafeJson/toSafeJson.spec.ts +0 -28
- package/src/toSafeJson/toSafeJson.ts +0 -56
- package/tsconfig.build.json +0 -10
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -11
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import SelectBuilder from '../SelectBuilder/SelectBuilder';
|
|
2
|
-
|
|
3
|
-
const trim = s => s.replace(/\s+/g, ' ').trim();
|
|
4
|
-
|
|
5
|
-
describe('column handler', () => {
|
|
6
|
-
it('should recognize asterisk', () => {
|
|
7
|
-
const query = new SelectBuilder('SELECT * FROM mytable');
|
|
8
|
-
expect(query._columns).toEqual(['*']);
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
describe('comment handler', () => {
|
|
12
|
-
it('should strip single-line comments -- dashes', () => {
|
|
13
|
-
const query = new SelectBuilder(
|
|
14
|
-
`
|
|
15
|
-
SELECT
|
|
16
|
-
-- stuff
|
|
17
|
-
a,
|
|
18
|
-
-- more stuff
|
|
19
|
-
b -- and even more stuff
|
|
20
|
-
FROM mytable
|
|
21
|
-
`
|
|
22
|
-
);
|
|
23
|
-
expect(trim(query.compile().sql)).toBe('SELECT a, b FROM mytable');
|
|
24
|
-
});
|
|
25
|
-
it('should strip single-line comments #hash', () => {
|
|
26
|
-
const query = new SelectBuilder(
|
|
27
|
-
`
|
|
28
|
-
SELECT
|
|
29
|
-
#stuff
|
|
30
|
-
a,
|
|
31
|
-
# more stuff
|
|
32
|
-
b #and even more stuff
|
|
33
|
-
FROM mytable
|
|
34
|
-
`
|
|
35
|
-
);
|
|
36
|
-
expect(trim(query.compile().sql)).toBe('SELECT a, b FROM mytable');
|
|
37
|
-
});
|
|
38
|
-
it('should strip multi-line comments', () => {
|
|
39
|
-
const query = new SelectBuilder(
|
|
40
|
-
`
|
|
41
|
-
SELECT
|
|
42
|
-
/*
|
|
43
|
-
stuff
|
|
44
|
-
*/
|
|
45
|
-
a,
|
|
46
|
-
/* more stuff */ b
|
|
47
|
-
FROM mytable
|
|
48
|
-
`
|
|
49
|
-
);
|
|
50
|
-
expect(trim(query.compile().sql)).toBe('SELECT a, b FROM mytable');
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe('subquery handler', () => {
|
|
54
|
-
it('should handle column subqueries', () => {
|
|
55
|
-
const normalized =
|
|
56
|
-
'SELECT a, (SELECT * FROM tbl2) AS b FROM mytable WHERE mycol = 1';
|
|
57
|
-
const query = new SelectBuilder(normalized);
|
|
58
|
-
expect(trim(query.compile().sql)).toBe(normalized);
|
|
59
|
-
});
|
|
60
|
-
it('should handle column subqueries with SELECT EXISTS', () => {
|
|
61
|
-
const normalized =
|
|
62
|
-
'SELECT a, (SELECT EXISTS (SELECT * FROM tbl2)) AS b FROM mytable WHERE mycol = 1';
|
|
63
|
-
const query = new SelectBuilder(normalized);
|
|
64
|
-
expect(trim(query.compile().sql)).toBe(normalized);
|
|
65
|
-
});
|
|
66
|
-
it('should handle IF() column expressions', () => {
|
|
67
|
-
const normalized =
|
|
68
|
-
'SELECT a, IF(b > 0) AS is_gt_0, IF(b > 100) AS is_gt_100 FROM mytable WHERE mycol = 1';
|
|
69
|
-
const query = new SelectBuilder(normalized);
|
|
70
|
-
expect(trim(query.compile().sql)).toBe(normalized);
|
|
71
|
-
});
|
|
72
|
-
it('should handle IN() expressions in WHERE', () => {
|
|
73
|
-
const normalized =
|
|
74
|
-
'SELECT * FROM mytable WHERE mycol IN (SELECT id FROM othertable)';
|
|
75
|
-
const query = new SelectBuilder(normalized);
|
|
76
|
-
expect(trim(query.compile().sql)).toBe(normalized);
|
|
77
|
-
});
|
|
78
|
-
it('should handle IN() expressions in JOINs', () => {
|
|
79
|
-
const normalized =
|
|
80
|
-
'SELECT * FROM a INNER JOIN b ON b.id = a.b_id AND b.status IN (SELECT id FROM statuses) WHERE a.id > 10';
|
|
81
|
-
const query = new SelectBuilder(normalized);
|
|
82
|
-
expect(trim(query.compile().sql)).toBe(normalized);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
describe('table handler', () => {
|
|
86
|
-
it('should parse single table', () => {
|
|
87
|
-
const query = new SelectBuilder('SELECT * FROM a');
|
|
88
|
-
expect(query._tables).toEqual(['a']);
|
|
89
|
-
});
|
|
90
|
-
it('should parse comma-separated tables', () => {
|
|
91
|
-
const query = new SelectBuilder('SELECT * FROM a, b');
|
|
92
|
-
expect(query._tables).toEqual(['a', 'b']);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe('column handler', () => {
|
|
96
|
-
it('should parse single column', () => {
|
|
97
|
-
const query = new SelectBuilder('SELECT a FROM b');
|
|
98
|
-
expect(query._columns).toEqual(['a']);
|
|
99
|
-
});
|
|
100
|
-
it('should parse comma-separated columns', () => {
|
|
101
|
-
const query = new SelectBuilder('SELECT a, b FROM c');
|
|
102
|
-
expect(query._columns).toEqual(['a', 'b']);
|
|
103
|
-
});
|
|
104
|
-
it('should handle expressions with one comma', () => {
|
|
105
|
-
const query = new SelectBuilder("SELECT a, CONCAT('b',b) FROM c");
|
|
106
|
-
expect(query._columns).toEqual(['a', "CONCAT('b',b)"]);
|
|
107
|
-
});
|
|
108
|
-
it('should handle expressions with 2 commas', () => {
|
|
109
|
-
const query = new SelectBuilder(
|
|
110
|
-
"SELECT a, CONCAT(fname, ' ', lname) FROM users"
|
|
111
|
-
);
|
|
112
|
-
expect(query._columns).toEqual(['a', "CONCAT(fname, ' ', lname)"]);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
describe('option handler', () => {
|
|
116
|
-
it('should save SQL_CALC_FOUND_ROWS as an option', () => {
|
|
117
|
-
const query = new SelectBuilder('SELECT SQL_CALC_FOUND_ROWS * FROM a');
|
|
118
|
-
expect(query._columns).toEqual(['*']);
|
|
119
|
-
expect(query._options).toEqual(['SQL_CALC_FOUND_ROWS']);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe('JOIN handler', () => {
|
|
123
|
-
const joins = [
|
|
124
|
-
'JOIN',
|
|
125
|
-
'INNER JOIN',
|
|
126
|
-
'LEFT JOIN',
|
|
127
|
-
'LEFT OUTER JOIN',
|
|
128
|
-
'OUTER JOIN',
|
|
129
|
-
'RIGHT JOIN',
|
|
130
|
-
'RIGHT OUTER JOIN',
|
|
131
|
-
'CROSS JOIN',
|
|
132
|
-
'FULL JOIN',
|
|
133
|
-
'FULL OUTER JOIN',
|
|
134
|
-
];
|
|
135
|
-
joins.forEach(join => {
|
|
136
|
-
it(`should handle ${join}`, () => {
|
|
137
|
-
const sql = `SELECT * FROM a ${join} b ON b.id = a.b_id`;
|
|
138
|
-
const query = new SelectBuilder(sql);
|
|
139
|
-
expect(trim(query.compile().sql)).toBe(sql);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe('conditions handler', () => {
|
|
144
|
-
it('should parse WHERE 1 = 1', () => {
|
|
145
|
-
const query = new SelectBuilder('SELECT * FROM mytable WHERE 1 = 1');
|
|
146
|
-
expect(query._wheres).toEqual(['1 = 1']);
|
|
147
|
-
});
|
|
148
|
-
it("should parse WHERE '1'", () => {
|
|
149
|
-
const query = new SelectBuilder("SELECT * FROM mytable WHERE '1'");
|
|
150
|
-
expect(query._wheres).toEqual(["'1'"]);
|
|
151
|
-
});
|
|
152
|
-
it('should parse WHERE true', () => {
|
|
153
|
-
const query = new SelectBuilder('SELECT * FROM mytable WHERE true');
|
|
154
|
-
expect(query._wheres).toEqual(['true']);
|
|
155
|
-
});
|
|
156
|
-
it('should parse single WHERE', () => {
|
|
157
|
-
const query = new SelectBuilder('SELECT * FROM mytable WHERE mycol = 1');
|
|
158
|
-
expect(query._wheres).toEqual(['mycol = 1']);
|
|
159
|
-
});
|
|
160
|
-
it('should parse two WHERE clauses joined by AND', () => {
|
|
161
|
-
const query = new SelectBuilder(
|
|
162
|
-
'SELECT * FROM mytable WHERE mycol = 1 AND scheduled < NOW()'
|
|
163
|
-
);
|
|
164
|
-
expect(query._wheres).toEqual(['mycol = 1', 'scheduled < NOW()']);
|
|
165
|
-
});
|
|
166
|
-
it('should parse two WHERE clauses joined by OR', () => {
|
|
167
|
-
const query = new SelectBuilder(
|
|
168
|
-
'SELECT * FROM mytable WHERE mycol = 1 OR scheduled < NOW()'
|
|
169
|
-
);
|
|
170
|
-
expect(query._wheres).toEqual(['(mycol = 1 OR scheduled < NOW())']);
|
|
171
|
-
});
|
|
172
|
-
it('should parse OR then AND', () => {
|
|
173
|
-
const query = new SelectBuilder(
|
|
174
|
-
'SELECT * FROM mytable WHERE a = 1 OR b = 2 AND c = 3'
|
|
175
|
-
);
|
|
176
|
-
expect(query._wheres).toEqual(['(a = 1 OR b = 2)', 'c = 3']);
|
|
177
|
-
});
|
|
178
|
-
it('should parse OR then AND with parens', () => {
|
|
179
|
-
const query = new SelectBuilder(
|
|
180
|
-
'SELECT * FROM mytable WHERE (a = 1 OR b = 2) AND c = 3'
|
|
181
|
-
);
|
|
182
|
-
expect(query._wheres).toEqual(['(a = 1 OR b = 2)', 'c = 3']);
|
|
183
|
-
});
|
|
184
|
-
it('should parse AND then OR', () => {
|
|
185
|
-
const query = new SelectBuilder(
|
|
186
|
-
'SELECT * FROM mytable WHERE a = 1 AND b = 2 OR c = 3'
|
|
187
|
-
);
|
|
188
|
-
expect(query._wheres).toEqual(['a = 1', '(b = 2 OR c = 3)']);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
it('should parse GROUP BY and HAVING', () => {
|
|
192
|
-
const query = new SelectBuilder(
|
|
193
|
-
'SELECT name, COUNT(*) FROM mytable GROUP BY name HAVING COUNT(*) > 1'
|
|
194
|
-
);
|
|
195
|
-
expect(query._groupBys).toEqual(['name']);
|
|
196
|
-
expect(query._havings).toEqual(['COUNT(*) > 1']);
|
|
197
|
-
});
|
|
198
|
-
it('should parse ORDER BY', () => {
|
|
199
|
-
const query = new SelectBuilder('SELECT * FROM users ORDER BY id DESC');
|
|
200
|
-
expect(query._orderBys).toEqual(['id DESC']);
|
|
201
|
-
});
|
|
202
|
-
describe('pagination', () => {
|
|
203
|
-
it('should parse LIMIT with number', () => {
|
|
204
|
-
const query = new SelectBuilder('SELECT * FROM mytable LIMIT 1');
|
|
205
|
-
expect(query._limit).toBe(1);
|
|
206
|
-
});
|
|
207
|
-
it('should parse OFFSET with number', () => {
|
|
208
|
-
const query = new SelectBuilder('SELECT * FROM mytable LIMIT 5 OFFSET 10');
|
|
209
|
-
expect(query._offset).toBe(10);
|
|
210
|
-
});
|
|
211
|
-
it('should parse OFFSET, LIMIT with numbers', () => {
|
|
212
|
-
const query = new SelectBuilder('SELECT * FROM mytable LIMIT 10, 5');
|
|
213
|
-
expect(query._limit).toBe(5);
|
|
214
|
-
expect(query._offset).toBe(10);
|
|
215
|
-
});
|
|
216
|
-
it('should parse OFFSET # ROWS', () => {
|
|
217
|
-
const query = new SelectBuilder('SELECT * FROM mytable OFFSET 10 ROWS');
|
|
218
|
-
expect(query._offset).toBe(10);
|
|
219
|
-
});
|
|
220
|
-
it('should parse FETCH NEXT # ROWS ONLY', () => {
|
|
221
|
-
const query = new SelectBuilder(
|
|
222
|
-
'SELECT * FROM mytable FETCH NEXT 10 ROWS ONLY'
|
|
223
|
-
);
|
|
224
|
-
expect(query._limit).toBe(10);
|
|
225
|
-
});
|
|
226
|
-
});
|
package/src/Parser/Parser.ts
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import type Select from '../SelectBuilder/SelectBuilder';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Parse SQL and populate onto a Select query object
|
|
5
|
-
*/
|
|
6
|
-
export default class Parser {
|
|
7
|
-
query: Select;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Create a new instance
|
|
11
|
-
* @param query A Select object on which to build parsed conditions
|
|
12
|
-
*/
|
|
13
|
-
constructor(query: Select) {
|
|
14
|
-
this.query = query;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Strip single-line and multi-line comment blocks
|
|
19
|
-
* @param sql The SQL string
|
|
20
|
-
* @return The SQL string without comments
|
|
21
|
-
* @private
|
|
22
|
-
*/
|
|
23
|
-
_stripComments(sql: string) {
|
|
24
|
-
// multiline comments
|
|
25
|
-
sql = sql.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
-
// single line comments -- dashes
|
|
27
|
-
sql = sql.replace(/--([\r\n]|\s+[^\r\n]+[\r\n])/g, '');
|
|
28
|
-
// single line comments #hashes
|
|
29
|
-
sql = sql.replace(/#[^\r\n]+[\r\n]/g, '');
|
|
30
|
-
return sql;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Before splitting into SQL clauses, extract some regex-able subQueries
|
|
35
|
-
* @param sql The unparsed sql string
|
|
36
|
-
* @return An array with new sql and subQueries
|
|
37
|
-
* @private
|
|
38
|
-
*/
|
|
39
|
-
_extractSubQueries(sql: string) {
|
|
40
|
-
const subQueries = {};
|
|
41
|
-
let i = 0;
|
|
42
|
-
const extractor = $0 => {
|
|
43
|
-
const placeholder = `~~SUBQUERY_${i++}~~`;
|
|
44
|
-
subQueries[placeholder] = $0;
|
|
45
|
-
return placeholder;
|
|
46
|
-
};
|
|
47
|
-
// subselect in FROM clause
|
|
48
|
-
sql = sql.replace(/\(\s*SELECT\s+.+\)\s+AS\s+[^\s,]+/, extractor);
|
|
49
|
-
// IF() in FROM clause
|
|
50
|
-
sql = sql.replace(/\bIF\s*\(.+\)\s+AS\s+[^\s,]+/, extractor);
|
|
51
|
-
// IN (SELECT *) in JOINs, WHERE or HAVING
|
|
52
|
-
sql = sql.replace(/\bIN\s*\(\s*SELECT\s.+?\)/, extractor);
|
|
53
|
-
return { sql, subQueries };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Inject column subQueries back into this object
|
|
58
|
-
* @param subQueries The lookup of extracted subQueries
|
|
59
|
-
* @private
|
|
60
|
-
*/
|
|
61
|
-
_injectSubQueries(subQueries: Record<string, string>) {
|
|
62
|
-
const replacer = $0 => {
|
|
63
|
-
return subQueries[$0] || $0;
|
|
64
|
-
};
|
|
65
|
-
const mapper = clause => {
|
|
66
|
-
return clause.replace(/~~SUBQUERY_\d+~~/g, replacer);
|
|
67
|
-
};
|
|
68
|
-
this.query._columns = this.query._columns.map(replacer);
|
|
69
|
-
this.query._joins = this.query._joins.map(mapper);
|
|
70
|
-
this.query._wheres = this.query._wheres.map(mapper);
|
|
71
|
-
this.query._havings = this.query._havings.map(mapper);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Split SQL into clauses (used by parse())
|
|
76
|
-
* @param sql The SQL to split
|
|
77
|
-
* @return
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
_split(sql: string) {
|
|
81
|
-
const splitter =
|
|
82
|
-
/(?:^|\s)(SELECT|FROM|(?:INNER|LEFT\s+OUTER|RIGHT\s+OUTER|LEFT|RIGHT|CROSS|FULL|FULL\s+OUTER)\s+JOIN|WHERE|GROUP\s+BY|HAVING|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT)\b/i;
|
|
83
|
-
return sql.split(splitter);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Get a QuickSelect object representing the given SQL SELECT statement
|
|
88
|
-
* @param rawSql The raw SQL for the SELECT statement
|
|
89
|
-
* @return True if the SQL was parsed successfully
|
|
90
|
-
*/
|
|
91
|
-
parse(rawSql: string) {
|
|
92
|
-
const stripped = this._stripComments(rawSql);
|
|
93
|
-
const { sql, subQueries } = this._extractSubQueries(stripped);
|
|
94
|
-
const expressions = this._split(sql);
|
|
95
|
-
let i = 1;
|
|
96
|
-
while (i < expressions.length) {
|
|
97
|
-
const rawKeyword = expressions[i++].trim();
|
|
98
|
-
const keyword = rawKeyword.toUpperCase().replace(/\s+/g, '_');
|
|
99
|
-
const clause = expressions[i++].trim();
|
|
100
|
-
const handler = `_handle${keyword}`;
|
|
101
|
-
this[handler](clause);
|
|
102
|
-
}
|
|
103
|
-
this._injectSubQueries(subQueries);
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Handle SQL_CALC_FOUND_ROWS and column names
|
|
109
|
-
* @param clause The clause after the SELECT
|
|
110
|
-
* @private
|
|
111
|
-
*/
|
|
112
|
-
_handleSELECT(clause: string) {
|
|
113
|
-
let columns = clause.split(/s*,s*/);
|
|
114
|
-
// now handle parenthesis expressions that contain commas
|
|
115
|
-
let buffer = '';
|
|
116
|
-
columns.forEach((column, i) => {
|
|
117
|
-
if (i === 0) {
|
|
118
|
-
const optionRegex = /^(SQL_CALC_FOUND_ROWS)\s+/i;
|
|
119
|
-
const match = column.match(optionRegex);
|
|
120
|
-
if (match) {
|
|
121
|
-
this.query.option(match[1]);
|
|
122
|
-
column = column.replace(optionRegex, '');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (buffer.length) {
|
|
126
|
-
// we are in the middle of an expression containing parenthesis
|
|
127
|
-
if (column.indexOf(')') > 0) {
|
|
128
|
-
// we have an end parenthesis
|
|
129
|
-
buffer += column;
|
|
130
|
-
this.query.column(buffer.trim());
|
|
131
|
-
buffer = '';
|
|
132
|
-
} else {
|
|
133
|
-
buffer += column + ',';
|
|
134
|
-
}
|
|
135
|
-
} else if (column.match(/\([^)]+$/)) {
|
|
136
|
-
buffer = column + ',';
|
|
137
|
-
} else {
|
|
138
|
-
this.query.column(column.trim());
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Handle table names
|
|
145
|
-
* @param clause The clause after the FROM
|
|
146
|
-
* @private
|
|
147
|
-
*/
|
|
148
|
-
_handleFROM(clause: string) {
|
|
149
|
-
const tables = clause.split(/\s*,\s*/);
|
|
150
|
-
tables.forEach(table => this.query.table(table));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Handle INNER JOIN statements
|
|
155
|
-
* @param clause The clause after the INNER JOIN
|
|
156
|
-
* @private
|
|
157
|
-
*/
|
|
158
|
-
_handleINNER_JOIN(clause: string) {
|
|
159
|
-
this.query.innerJoin(clause);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Handle LEFT JOIN statements
|
|
164
|
-
* @param clause The clause after the LEFT JOIN
|
|
165
|
-
* @private
|
|
166
|
-
*/
|
|
167
|
-
_handleLEFT_JOIN(clause: string) {
|
|
168
|
-
this.query.leftJoin(clause);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Handle LEFT OUTER JOIN statements
|
|
173
|
-
* @param clause The clause after the LEFT OUTER JOIN
|
|
174
|
-
* @private
|
|
175
|
-
*/
|
|
176
|
-
_handleLEFT_OUTER_JOIN(clause: string) {
|
|
177
|
-
this.query.leftOuterJoin(clause);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Handle RIGHT JOIN statements
|
|
182
|
-
* @param clause The clause after the RIGHT JOIN
|
|
183
|
-
* @private
|
|
184
|
-
*/
|
|
185
|
-
_handleRIGHT_JOIN(clause: string) {
|
|
186
|
-
this.query.rightJoin(clause);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Handle RIGHT OUTER JOIN statements
|
|
191
|
-
* @param clause The clause after the RIGHT OUTER JOIN
|
|
192
|
-
* @private
|
|
193
|
-
*/
|
|
194
|
-
_handleRIGHT_OUTER_JOIN(clause: string) {
|
|
195
|
-
this.query.rightOuterJoin(clause);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Handle CROSS JOIN statements
|
|
200
|
-
* @param clause The clause after the CROSS JOIN
|
|
201
|
-
* @private
|
|
202
|
-
*/
|
|
203
|
-
_handleCROSS_JOIN(clause: string) {
|
|
204
|
-
this.query.crossJoin(clause);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Handle FULL JOIN statements
|
|
209
|
-
* @param clause The clause after the FULL JOIN
|
|
210
|
-
* @private
|
|
211
|
-
*/
|
|
212
|
-
_handleFULL_JOIN(clause: string) {
|
|
213
|
-
this.query.fullJoin(clause);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Handle FULL OUTER JOIN statements
|
|
218
|
-
* @param clause The clause after the FULL OUTER JOIN
|
|
219
|
-
* @private
|
|
220
|
-
*/
|
|
221
|
-
_handleFULL_OUTER_JOIN(clause: string) {
|
|
222
|
-
this.query.fullOuterJoin(clause);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Handle WHERE conditions
|
|
227
|
-
* @param clause All the conditions after WHERE
|
|
228
|
-
* @private
|
|
229
|
-
*/
|
|
230
|
-
_handleWHERE(clause: string) {
|
|
231
|
-
if (/^(1|'1'|true)$/i.test(clause)) {
|
|
232
|
-
this.query._wheres.push(clause);
|
|
233
|
-
} else {
|
|
234
|
-
this._handleConditions('where', clause);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Handle HAVING statements
|
|
240
|
-
* @param clause All the conditions after HAVING
|
|
241
|
-
* @private
|
|
242
|
-
*/
|
|
243
|
-
_handleHAVING(clause: string) {
|
|
244
|
-
this._handleConditions('having', clause);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Build a conditions list
|
|
249
|
-
* @param type Either where or having
|
|
250
|
-
* @param clause The expressions following the type keyword
|
|
251
|
-
* @private
|
|
252
|
-
*/
|
|
253
|
-
_handleConditions(type: 'where' | 'having', clause: string) {
|
|
254
|
-
const andGroups = clause.split(/\bAND\b/i);
|
|
255
|
-
andGroups.forEach(andGroup => {
|
|
256
|
-
const orPieces = andGroup.split(/\bOR\b/i).map(str => str.trim());
|
|
257
|
-
if (orPieces.length === 1) {
|
|
258
|
-
// no OR operators
|
|
259
|
-
const fn = type; // either where or having
|
|
260
|
-
this.query[fn](orPieces[0]);
|
|
261
|
-
} else {
|
|
262
|
-
// some OR operators
|
|
263
|
-
// either orWhere or orHaving
|
|
264
|
-
const orFn = type === 'where' ? 'orWhere' : 'orHaving';
|
|
265
|
-
this.query[orFn](orPieces.map(p => [p]));
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Handle GROUP BY statements
|
|
272
|
-
* @param clause The clauses after the GROUP BY
|
|
273
|
-
* @private
|
|
274
|
-
*/
|
|
275
|
-
_handleGROUP_BY(clause: string) {
|
|
276
|
-
const columns = clause.split(/\s*,\s*/);
|
|
277
|
-
columns.forEach(column => this.query.groupBy(column));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Handle ORDER BY statements
|
|
282
|
-
* @param clause The clause after the ORDER BY
|
|
283
|
-
* @private
|
|
284
|
-
*/
|
|
285
|
-
_handleORDER_BY(clause: string) {
|
|
286
|
-
const columns = clause.split(/\s*,\s*/);
|
|
287
|
-
columns.forEach(column => this.query.orderBy(column));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Handle LIMIT statements including "LIMIT #" and "LIMIT #, #"
|
|
292
|
-
* @param clause The clause after the LIMIT
|
|
293
|
-
* @private
|
|
294
|
-
*/
|
|
295
|
-
_handleLIMIT(clause: string) {
|
|
296
|
-
const offsetLimit = clause.match(/^(\d+|\?|:\w+)\s*,\s*(\d+|\?|:\w+)$/);
|
|
297
|
-
if (offsetLimit) {
|
|
298
|
-
this.query.offset(offsetLimit[1]);
|
|
299
|
-
this.query.limit(offsetLimit[2]);
|
|
300
|
-
} else {
|
|
301
|
-
clause = clause.replace(/ ROWS$/i, '');
|
|
302
|
-
this.query.limit(clause);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Handle OFFSET statements
|
|
308
|
-
* @param clause The number after the OFFSET
|
|
309
|
-
* @private
|
|
310
|
-
*/
|
|
311
|
-
_handleOFFSET(clause: string) {
|
|
312
|
-
clause = clause.replace(/^(\d+) ROWS$/i, '$1');
|
|
313
|
-
this.query.offset(clause);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Handle OFFSET statements
|
|
318
|
-
* @param clause The number after the OFFSET
|
|
319
|
-
* @private
|
|
320
|
-
*/
|
|
321
|
-
_handleFETCH_NEXT(clause: string) {
|
|
322
|
-
clause = clause.replace(/^(\d+) ROWS ONLY$/i, '$1');
|
|
323
|
-
this.query.limit(clause);
|
|
324
|
-
}
|
|
325
|
-
}
|