lfapi

view lib/selector.js @ 0:ce6f95d23e1c

Initial checkin
author bsw
date Sat Sep 10 23:31:20 2011 +0200 (2011-09-10)
parents
children
line source
1 var stringthesizer = require('./stringthesizer.js');
3 var quoteLiteral = function(str) {
4 if (str.search(/^"[^"]*"/) >= 0) return str;
5 else return str.replace(/"/g, '""').replace(/[^\.]+/g, '"$&"');
6 };
8 function SQLQuery() {
9 }
10 SQLQuery.prototype.assemble = function(nextPlaceholder) {
11 return stringthesizer.stringthesizer(
12 {
13 nextPlaceholder: nextPlaceholder,
14 valueSeparator: ",",
15 coerce: function(value) {
16 if (value instanceof Selector) return value.toStructure();
17 return value;
18 }
19 },
20 this.toStructure()
21 );
22 }
24 function Selector(from) {
25 this._with = [];
26 this._fields = [];
27 this._distinct = false;
28 this._distinctOn = [];
29 this._from = [];
30 this._where = [];
31 this._groupBy = [];
32 this._having = [];
33 this._combine = [];
34 this._orderBy = [];
35 this._limit = null;
36 this._offset = null;
37 this._readLock = [];
38 this._readLockAll = false;
39 this._writeLock = [];
40 this._writeLockAll = false;
41 if (from != null) this.from(from);
42 }
43 Selector.prototype = new SQLQuery();
44 Selector.prototype.addWith = function(expression, selector) {
45 this._with.push(['$ AS ($)', expression, selector]);
46 return this;
47 };
48 Selector.prototype.addDistinctOn = function(expression) {
49 if (this._distinct) throw "Cannot combine DISTINCT with DISTINCT ON.";
50 this._distinctOn.push(expression);
51 return this;
52 };
53 Selector.prototype.setDistinct = function() {
54 if (this._distinctOn.length > 0) throw "Cannot combine DISTINCT with DISTINCT ON.";
55 this._distinct = true;
56 return this;
57 };
58 Selector.prototype.addFrom = function(expression, alias, condition) {
59 var first = this._from.length == 0;
60 if (!first) {
61 if (condition == null) this._from.push('CROSS JOIN')
62 else this._from.push('INNER JOIN')
63 }
64 if (expression instanceof Selector) {
65 if (alias == null) this._from.push(['($) AS "subquery"', expression]);
66 else this._from.push(['($) AS "$"', expression, alias]);
67 } else {
68 if (alias == null) this._from.push(expression);
69 else this._from.push(['$ AS "$"', expression, alias]);
70 }
71 if (condition != null) {
72 if (first) {
73 this.addWhere(condition);
74 } else {
75 this._from.push('ON');
76 this._from.push(condition);
77 }
78 }
79 return this;
80 };
81 Selector.prototype.addWhere = function(expression) {
82 this._where.push(['($)', expression]);
83 return this;
84 };
85 Selector.prototype.addGroupBy = function(expression) {
86 this._groupBy.push(expression);
87 return this;
88 };
89 Selector.prototype.addHaving = function(expression) {
90 this._having.push(['($)', expression]);
91 return this;
92 };
93 Selector.prototype.addCombine = function(expression) {
94 this._combine.push(expression);
95 return this;
96 };
97 Selector.prototype.addOrderBy = function(expression) {
98 this._orderBy.push(expression);
99 return this;
100 };
101 Selector.prototype.limit = function(count) {
102 this._limit = count;
103 return this;
104 };
105 Selector.prototype.offset = function(count) {
106 this._offset = count;
107 return this;
108 };
109 Selector.prototype.forShare = function() {
110 this._readLockAll = true;
111 return this;
112 };
113 Selector.prototype.forShareOf = function(expression) {
114 this._readLock.push(expression);
115 return this;
116 };
117 Selector.prototype.forUpdate = function() {
118 this._writeLockAll = true;
119 return this;
120 };
121 Selector.prototype.forUpdateOf = function(expression) {
122 this._writeLock.push(expression);
123 return this;
124 };
125 Selector.prototype.resetFields = function() {
126 this._fields = [];
127 return this;
128 };
129 Selector.prototype.addField = function(expression, alias, options) {
130 var self = this;
131 var processOption = function(option) {
132 if (option == "distinct") {
133 if (alias == null) self.addDistinctOn(expression);
134 else self.addDistinctOn(['"$"', alias]);
135 } else if (option == "grouped") {
136 if (alias == null) self.addGroupBy(expression);
137 else self.addGroupBy(['"$"', alias]);
138 } else {
139 throw "Unexpected option passed to addField(...).";
140 }
141 }
142 if (alias == null) this._fields.push(expression);
143 else this._fields.push(['$ AS "$"', expression, alias]);
144 if (options != null) {
145 if (options instanceof Array) options.forEach(processOption);
146 else processOption(options);
147 }
148 return this;
149 };
150 Selector.prototype.join = Selector.prototype.addFrom;
151 Selector.prototype.from = function(expression, alias, condition) {
152 if (this._from.length > 0) {
153 error();
154 }
155 return this.addFrom(expression, alias, condition);
156 };
157 Selector.prototype.leftJoin = function(expression, alias, condition) {
158 var first = this._from.length == 0;
159 if (!first) this._from.push('LEFT OUTER JOIN');
160 if (alias == null) this._from.push(expression);
161 else this._from.push(['$ AS "$"', expression, alias]);
162 if (condition != null) {
163 if (first) {
164 this.addWhere(condition);
165 } else {
166 this._from.push('ON');
167 this._from.push(condition);
168 }
169 }
170 };
171 Selector.prototype.union = function(expression) {
172 this.addCombine(['UNION $', expression]);
173 return this;
174 };
175 Selector.prototype.unionAll = function(expression) {
176 this.addCombine(['UNION ALL $', expression]);
177 return this;
178 };
179 Selector.prototype.intersect = function(expression) {
180 this.addCombine(['INTERSECT $', expression]);
181 return this;
182 };
183 Selector.prototype.intersectAll = function(expression) {
184 this.addCombine(['INTERSECT ALL $', expression]);
185 return this;
186 };
187 Selector.prototype.except = function(expression) {
188 this.addCombine(['EXCEPT $', expression]);
189 return this;
190 };
191 Selector.prototype.exceptAll = function(expression) {
192 this.addCombine(['EXCEPT ALL $', expression]);
193 return this;
194 };
195 Selector.prototype.toStructure = function() {
196 var parts = [];
197 parts.push('SELECT');
198 if (this._distinct) parts.push('DISTINCT');
199 else if (this._distinctOn.length > 0)
200 parts.push(['DISTINCT ON ($$)', ', ', this._distinctOn]);
201 parts.push(["$$", ", ", this._fields]);
202 if (this._from.length > 0) parts.push(['FROM $$', ' ', this._from]);
203 if (this._where.length > 0) parts.push(['WHERE $$', ' AND ', this._where]);
204 if (this._groupBy.length > 0) parts.push(['GROUP BY $$', ', ', this._groupBy]);
205 if (this._having.length > 0) parts.push(['HAVING $$', ' AND ', this._having]);
206 this._combine.forEach(function(entry) { parts.push(entry); });
207 if (this._orderBy.length > 0) parts.push(['ORDER BY $$', ', ', this._orderBy]);
208 if (this._limit != null) parts.push(['LIMIT ?', this._limit]);
209 if (this._offset != null) parts.push(['OFFSET ?', this._offset]);
210 if (this._writeLockAll) parts.push('FOR UPDATE');
211 else {
212 if (this._readLockAll) parts.push('FOR SHARE');
213 else if (this._readLock.length > 0)
214 parts.push(['FOR SHARE OF $$', ', ', this._readLock]);
215 if (this._writeLock.length > 0)
216 parts.push(['FOR UPDATE OF $$', ', ', this._writeLock]);
217 }
218 return ["$$", " ", parts];
219 };
221 function SQLInsert(table) {
222 this._with = [];
223 if (table == null) this._table = null;
224 else this._table = table;
225 this._columns = [];
226 this._values = [];
227 this._query = null;
228 this._returning = [];
229 }
230 SQLInsert.prototype = new SQLQuery();
231 SQLInsert.prototype.addWith = Selector.prototype.addWith;
232 SQLInsert.prototype.table = function(expression) {
233 this._table = expression;
234 return this;
235 };
236 SQLInsert.prototype.addValues = function(mapping) {
237 if (this._query != null) throw "Cannot combine query with values.";
238 for (key in mapping) {
239 this._columns.push(key);
240 this._values.push(['?', mapping[key]]);
241 }
242 };
243 SQLInsert.prototype.addValueExpressions = function(mapping) {
244 if (this._query != null) throw "Cannot combine query with values.";
245 for (key in mapping) {
246 this._columns.push(key);
247 this._values.push(mapping[key]);
248 }
249 };
250 SQLInsert.prototype.query = function(columns, expression) {
251 if (this._values.length > 0) throw "Cannot combine query with values.";
252 this._columns = columns;
253 this._select = expression;
254 };
255 SQLInsert.prototype.addReturning = function(expression, alias) {
256 if (alias == null) this._returning.push(expression);
257 else this._returning.push(['$ AS "$"', expression, alias]);
258 };
259 SQLInsert.prototype.toStructure = function() {
260 var parts = [];
261 parts.push('INSERT INTO');
262 if (this._table == null) throw "Missing table for INSERT.";
263 parts.push(this._table);
264 if (this._columns.length > 0) parts.push(['($$)', ', ', this._columns]);
265 if (this._values.length > 0) parts.push(['VALUES ($$)', ', ', this._values]);
266 else if (this._query == null) parts.push('DEFAULT VALUES');
267 else parts.push(this._query);
268 if (this._returning.length > 0)
269 parts.push(['RETURNING $$', ', ', this._returning]);
270 return ["$$", " ", parts];
271 };
273 function SQLUpdate(table) {
274 this._with = [];
275 if (table == null) this._table = null;
276 else this._table = table;
277 this._columns = [];
278 this._values = [];
279 this._query = null;
280 this._from = [];
281 this._where = [];
282 this._returning = [];
283 }
284 SQLUpdate.prototype = new SQLQuery();
285 SQLUpdate.prototype.addWith = Selector.prototype.addWith;
286 SQLUpdate.prototype.table = function(expression, alias) {
287 if (alias == null) this._table = expression;
288 else this._table = ['$ AS "$"', expression, alias];
289 return this;
290 }
291 SQLUpdate.prototype.addValues = SQLInsert.prototype.addValues;
292 SQLUpdate.prototype.addValueExpressions = SQLInsert.prototype.addValueExpressions;
293 SQLUpdate.prototype.query = SQLInsert.prototype.query;
294 SQLUpdate.prototype.addFrom = Selector.prototype.addFrom;
295 SQLUpdate.prototype.join = Selector.prototype.addFrom;
296 SQLUpdate.prototype.leftJoin = function(expression, alias, condition) {
297 if (this._from.length == 0)
298 throw "First join for UPDATE or DELETE must not be a left join.";
299 this._from.push('LEFT OUTER JOIN');
300 if (alias == null) this._from.push(expression);
301 else this._from.push(['$ AS "$"', expression, alias]);
302 if (condition != null) {
303 this._from.push('ON');
304 this._from.push(condition);
305 }
306 };
307 SQLUpdate.prototype.addWhere = Selector.prototype.addWhere;
308 SQLUpdate.prototype.addReturning = SQLInsert.prototype.addReturning;
309 SQLUpdate.prototype.toStructure = function() {
310 var parts = [];
311 parts.push('UPDATE');
312 if (this._table == null) throw "Missing table for UPDATE.";
313 parts.push(this._table);
314 parts.push('SET');
315 if (this._columns.length == 0) throw "Missing columns for UPDATE.";
316 if (this._query == null) {
317 for (var i=0; i<this._columns.length; i++) {
318 parts.push(
319 [ (i==this._columns.length-1) ? '$ = $' : '$ = $,',
320 this._columns[i],
321 this._values[i]]
322 );
323 }
324 } else {
325 parts.push(['($$) = ($$)', ', ', this._columns, ', ', this._values]);
326 }
327 if (this._from.length > 0) parts.push(['FROM $$', ' ', this._from]);
328 if (this._where.length > 0) parts.push(['WHERE $$', ' AND ', this._where]);
329 if (this._returning.length > 0)
330 parts.push(['RETURNING $$', ', ', this._returning]);
331 return ["$$", " ", parts];
332 };
334 function SQLDelete(table) {
335 this._with = [];
336 if (table == null) this._table = null;
337 else this._table = table;
338 this._from = []; // USING clause
339 this._where = [];
340 this._returning = [];
341 }
342 SQLDelete.prototype = new SQLQuery();
343 SQLDelete.prototype.addWith = Selector.prototype.addWith;
344 SQLDelete.prototype.table = SQLUpdate.prototype.table;
345 SQLDelete.prototype.addValues = SQLInsert.prototype.addValues;
346 SQLDelete.prototype.addValueExpressions = SQLInsert.prototype.addValueExpressions;
347 SQLDelete.prototype.addFrom = Selector.prototype.addFrom;
348 SQLDelete.prototype.addUsing = Selector.prototype.addFrom;
349 SQLDelete.prototype.join = Selector.prototype.addFrom;
350 SQLDelete.prototype.leftJoin = SQLUpdate.prototype.leftJoin;
351 SQLDelete.prototype.addWhere = Selector.prototype.addWhere;
352 SQLDelete.prototype.addReturning = SQLInsert.prototype.addReturning;
353 SQLDelete.prototype.toStructure = function() {
354 var parts = [];
355 parts.push('DELETE FROM');
356 if (this._table == null) throw "Missing table for DELETE.";
357 parts.push(this._table);
358 if (this._from.length > 0) parts.push(['USING $$', ' ', this._from]);
359 if (this._where.length > 0) parts.push(['WHERE $$', ' AND ', this._where]);
360 if (this._returning.length > 0)
361 parts.push(['RETURNING $$', ', ', this._returning]);
362 return ["$$", " ", parts];
363 };
365 function Upserter(table, keys) {
366 if (table == null) this._table = null;
367 else this._table = table;
368 this._columns = [];
369 this._values = [];
370 this._keys = [];
371 if (keys) this.addKeys(keys);
372 }
374 Upserter.prototype.addValues = SQLInsert.prototype.addValues;
375 Upserter.prototype.addValueExpressions = SQLInsert.prototype.addValueExpressions;
376 Upserter.prototype.addKeys = function(keys) {
377 var self = this;
378 keys.forEach(function(key) { self._keys.push(key); });
379 };
380 Upserter.prototype.applyWhere = function(sqlQuery) {
381 for (var i=0; i<this._columns.length; i++) {
382 var column = this._columns[i];
383 var value = this._values[i];
384 for (var j=0; j<this._keys.length; j++) if (this._keys[j] == column) break;
385 if (j<this._keys.length) sqlQuery.addWhere(['$ = $', column, value]);
386 }
387 }
388 Upserter.prototype.getSelector = function() {
389 var selector = new Selector(this._table).addField('NULL');
390 this.applyWhere(selector);
391 return selector;
392 }
393 Upserter.prototype.getSQLInsert = function() {
394 var sqlInsert = new SQLInsert(this._table);
395 sqlInsert._columns = this._columns;
396 sqlInsert._values = this._values;
397 return sqlInsert;
398 }
399 Upserter.prototype.getSQLUpdate = function() {
400 var sqlUpdate = new SQLUpdate(this._table);
401 for (var i =0; i<this._columns.length; i++) {
402 var column = this._columns[i];
403 var value = this._values[i];
404 for (var j=0; j<this._keys.length; j++) if (this._keys[j] == column) break;
405 if (j==this._keys.length) {
406 sqlUpdate._columns.push(column);
407 sqlUpdate._values.push(value);
408 }
409 }
410 if (sqlUpdate._columns.length == 0) return null;
411 this.applyWhere(sqlUpdate);
412 return sqlUpdate;
413 }
416 exports.SQLQuery = SQLQuery;
417 exports.Selector = Selector;
418 exports.Upserter = Upserter;
419 exports.SQLInsert = SQLInsert;
420 exports.SQLUpdate = SQLUpdate;
421 exports.SQLDelete = SQLDelete;

Impressum / About Us