├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── dist
├── squel-basic.js
├── squel-basic.min.js
├── squel.d.ts
├── squel.js
└── squel.min.js
├── gulpfile.js
├── package-lock.json
├── package.json
├── performance
└── select.js
├── src
├── core.js
├── mssql.js
├── mysql.js
└── postgres.js
└── test
├── baseclasses.test.coffee
├── blocks.test.coffee
├── case.test.coffee
├── custom.test.coffee
├── delete.test.coffee
├── expressions.test.coffee
├── insert.test.coffee
├── mssql.test.coffee
├── mysql.test.coffee
├── postgres.test.coffee
├── select.test.coffee
├── testbase.coffee
└── update.test.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | node_modules/
4 | .sass-cache/
5 | docs/
6 | test-coverage/
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | test-coverage
3 | docs
4 | src
5 | .*
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "lts/*"
5 | - "node"
6 |
7 | branches:
8 | only:
9 | - master
10 |
11 | script:
12 | - "npm test"
13 |
14 | after_success:
15 | - bash <(curl -s https://codecov.io/bash)
16 |
17 | notifications:
18 | email:
19 | - ram@hiddentao.com
20 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog for [squel](https://github.com/hiddentao/squel)
2 |
3 | ## 28 Jul 2020 (5.13.1)
4 | * Deprecate in NPM (#384)
5 |
6 | ## 18 Jun 2019 (5.13.0)
7 | * Added note to top of README stating that Squel is no longer actively maintained.
8 |
9 | ## 9 Jul 2018 (5.12.2)
10 | * Fix Node.js CVE (update growl dev dependency)
11 |
12 | ## 17 Jul 2017 (5.11.1)
13 | * #322 - Tyepscript definitions
14 | * #321 - Allow `LIMIT` and `OFFSET` with `0`
15 | * #320 - Ensure string formatter doesn't get lost in cloning
16 |
17 | ## 17 May 2017 (5.10.0)
18 | * #317 - Postgres `ON CONFLICT` improvements, thanks [alexturek](https://github.com/alexturek)
19 |
20 | ## 21 Apr 2017 (5.9.1)
21 | * Performance improvements (#309, #310) - thanks [schmod](https://github.com/schmod)
22 |
23 | ## 13 Apr 2017 (5.9.0)
24 | * Enable custom value handlers to return values that do not get automatically nested - #292
25 |
26 | ## 28 Feb 2017 (5.8.0)
27 | * #301 - Add `rstr()` to enable "raw" nesting of query builders
28 | * Renamed `_isSquelBuilder()` call to `isSquelBuilder()`
29 |
30 | ## 6 Feb 2017 (5.7.0)
31 | * #288 - more flexible `RETURNING` clauses
32 |
33 | ## 7 Jan 2017 (5.6.0)
34 | * #256 - expression nesting
35 |
36 | ## 24 Dec 2016 (5.5.1)
37 | * #255, #283 - mixing flavours
38 |
39 | ## 15 Oct 2016 (5.5.0)
40 | * #118 - pass extra formatting options (when available) to custom value handler
41 | * #273 - parameterized `LIMIT` and `OFFSET` queries
42 |
43 | ## 15 Sep 2016 (5.4.3)
44 | * #266 - Postgres `ON CONFLICT` support
45 |
46 | ## 27 Aug 2016 (5.4.2)
47 | * A better check for detecting when custom value formatting has been applied.
48 | * Allow for any builder to passed in as an expression
49 |
50 | ## 26 Aug 2016 (5.3.4)
51 | * #261 - passing a string for `order` clause
52 |
53 | ## 12 Jul 2016 (5.3.3)
54 | * #249 - Postgres `DISTINCT ON` clause
55 |
56 | ## 13 Jun 2016 (5.3.2)
57 | * #234 - Fix handling of expression field names
58 |
59 | ## 5 Jun 2016 (5.3.1)
60 | * #158, #239 - Support for CTE queries (`WITH` clause)
61 | * #242 - Fix auto-quoting table names
62 | * Removed bower.json
63 |
64 | ## 18 May 2016 (5.2.1)
65 | * Re-fix for #109 - custom string formatting wasn't quite working
66 |
67 | ## 18 May 2016 (5.2.0)
68 | * Fix for #109 - custom string formatting function enabled
69 | * Fix for #235 - fix a regression
70 |
71 | ## 14 May 2016 (5.1.0)
72 | * Fix for #231 - try not to add extra brackets
73 | * Fix for #233 - ability to specify target table in `DELETE` queries
74 |
75 | ## 17 Apr 2016 (5.0.4)
76 | * Fix for #227 - MSSQL insert without fields fails
77 |
78 | ## 13 Apr 2016 (5.0.3)
79 | * Fix for #225 - auto-quote field names had stopped working
80 |
81 | ## 11 Apr 2016 (5.0.2)
82 | * Fix for #226 - empty expressions in where clause
83 |
84 | ## 6 Apr 2016 (5.0.1)
85 | * Fix for #223 - array looping should not use `for-in`
86 |
87 | ## 29 Mar 2016 (5.0.0)
88 | * Complete architectural rewrite - see #201
89 |
90 | ## 23 Mar 2016 (4.4.2)
91 | * Fix for #220 and #221 and other similar issues
92 |
93 | ## 20 Mar 2016 (4.4.1)
94 | * Fixed for #219
95 |
96 | ## 19 Mar 2016 (4.4.0)
97 | * Ported coffeescript to ES6
98 |
99 | ## 29 Feb 2016 (4.3.3)
100 | * Fix for #216
101 |
102 | ## 24 Feb 2016 (4.3.2)
103 | * Fix for #210
104 |
105 | ## 18 Feb 2016 (4.3.1)
106 | * #208 - Rework expressions to allow for easier cloning.
107 |
108 | ## 18 Feb 2016 (4.3.0)
109 | * #207 - Added `CASE` clauses and `useAsForTableAliasNames` option.
110 |
111 | ## 17 Feb 2016 (4.2.4)
112 | * #199 - Added `FROM` to `UPDATE` for postgres flavour
113 |
114 | ## 20 Jan 2016 (4.2.3)
115 | * Placeholder parameter character is now configurable
116 | * Guide docs now print results below code
117 | * Re-instituted CHANGELOG.md
118 | * Can now get current flavour of Squel using `flavour` prop
119 |
120 | ## 13 Nov 2015 (4.2.2)
121 | * Merged #191
122 |
123 | ## 30 Aug 2014 (3.8.1)
124 | * #90 - custom value handlers with primitives
125 | * #87 - OrderBlock not compatible by values
126 |
127 | ## 11 Aug 2014 (3.7.0)
128 | * #76 - MSSQL flavour
129 | * #85 - Using expressions in .where() followed by .toParam()
130 |
131 | ## 30 July 2014 (3.6.1)
132 | * Better fix for #82
133 | * Treat `*` as a special case when auto-quoting field names
134 | * Fix for #84
135 |
136 | ## 19 July 2014 (3.5.0)
137 | * #82 - `ON DUPLIATE KEY UPDATE` enchancements
138 | * #25, #72, #73 - parameter substitution in expressions
139 | * #79 - smarter automatic fieldname quoting
140 | * #75 - disable automatic string quoting on a per-field basis
141 | * #55 - specify sub-query as a field
142 | * #80, #81 - Bugfixes
143 |
144 | ## 17 May 2014 (3.4.1)
145 | * #62 - can specify query separator string
146 |
147 | ## 15 May 2014 (3.3.0)
148 | * Shifted `replaceSingleQuotes` and related option into Squel core.
149 |
150 | ## 9 May 2014 (3.2.0)
151 | * Added DELETE..RETURNING for Postgres (#60)
152 | * Auto-generate version string (#61)
153 | * Don't commit docs/ folder anymore. Also don't auto-build docs as part of build.
154 |
155 | ## 21 Mar 2014 (3.1.1)
156 | * Don't format parameter values returned from the toParam() call, unless their custom value types (#54)
157 |
158 | ## 20 Mar 2014 (3.0.1)
159 | * Added `setFields` and `setFieldRows` to make setting multple fields and inserting multiple rows easier (#50)
160 | * Removed `usingValuePlaceholders` option that was deprecated in 2.0.0
161 |
162 | ## 16 Dec 2013 (2.0.0)
163 | * Added `RETURNING` clause to `UPDATE` queries for Postgres flavour (#42)
164 | * Added better support for parameterized queries (#34)
165 | * Added `squel.VERSION` constant
166 |
167 |
168 | ## 7 Oct 2013 (1.2.1)
169 | * Added ON DUPLICATE KEY UPDATE clause for MySQL flavour (#36)
170 | * Added single quote replacement option for Postgres flavour (#35)
171 |
172 |
173 | ## 2 Oct 2013 (1.2)
174 | * Switched from Make to Grunt
175 | * Added `fields()` method to SELECT builder (#29)
176 | * Expression trees can now be cloned (#31)
177 | * Added concept of SQL 'flavours' and merged in the Postgres `RETURNING` command #33
178 |
179 |
180 | ## 10 Jun 2013 (1.1.3)
181 | * Table names in SELECT queries can now be queries themselves (i.e. SQL sub statements)
182 |
183 |
184 | ## 2 Jun 2013 (1.1.2)
185 | * Parameterised WHERE clauses now supported.
186 | * Custom value types can now be handled in a special way. Global and per-instance handlers supported.
187 |
188 |
189 | ## 27 Mar 2013 (1.1)
190 | * Squel can now be customized to include proprietary commands and queries.
191 | * AMD support added.
192 |
193 |
194 | ## 4 Jan 2013 (1.0.6)
195 | * Squel can now be told to auto-quote table and field names.
196 |
197 |
198 | ## 3 Nov 2012 (1.0.5)
199 |
200 | * DELETE queries can now contain JOINs
201 | * Query builder instances can be clone()'d
202 | * Cleaner and more thorough tests (and replaced Vows with Mocha, Sinon and Chai)
203 | * Fixed documentation errors
204 |
205 |
206 | ## 17 Aug 2012 (1.0.4)
207 |
208 | * QueryBuilder base class for all query builders
209 | * Exporting query builders
210 | * Escaping strings with single quotes for PostgreSQL compatibility
211 | * Migrating to make
212 |
213 |
214 | ## 27 Jan 2012 (1.0.3)
215 |
216 | * Added 'usingValuePlaceholders' option for INSERT and UPDATE query builders.
217 |
218 |
219 | ## 20 Dec 2011 (1.0.0)
220 |
221 | * Initial version.
222 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute to Squel
2 |
3 | This guide guidelines for those wishing to contribute to Squel.
4 |
5 | ## Contributor license agreement
6 |
7 | By submitting code as an individual or as an entity you agree that your code is [licensed the same as Squel](README.md).
8 |
9 | ## Issues and pull requests
10 |
11 | Issues and merge requests should be in English and contain appropriate language for audiences of all ages.
12 |
13 | We will only accept a merge requests which meets the following criteria:
14 |
15 | * Squel.js and squel.min.js have been rebuilt using `npm run build`.
16 | * Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code)
17 | * Can be merged without problems (if not please use: `git rebase master`)
18 | * Does not break any existing functionality
19 | * Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
20 | * Keeps the Squel code base clean and well structured
21 | * Contains functionality we think other users will benefit from too
22 | * Doesn't add unnessecary configuration options since they complicate future changes
23 | * Update the docs in the `gh-pages` branch and in the master `README.md` if necessary
24 |
25 |
26 | ## Release process (for core contributors)
27 |
28 | See `RELEASE.md`.
29 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) [Ramesh Nair](http://www.hiddentao.com/)
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NOTE: Squel is no longer actively maintained. I only have time for occasional bugfixes and small-scale work. If you are interested in helping with squel maintenance the help would be welcome. Alternatively, please use another library - we recommend [knex](https://knexjs.org).
2 |
3 | # squel - SQL query string builder
4 |
5 | [](http://travis-ci.org/hiddentao/squel)
6 | [](https://cdnjs.com/libraries/squel)
7 | [](https://badge.fury.io/js/squel)
8 | [](https://www.npmjs.com/package/squel)
9 | [](https://discord.gg/PBAR2Bz)
10 | [](https://twitter.com/hiddentao)
11 |
12 | A flexible and powerful SQL query string builder for Javascript.
13 |
14 | Full documentation (guide and API) at [https://hiddentao.github.io/squel](https://hiddentao.github.io/squel).
15 |
16 | ## Features
17 |
18 | * Works in node.js and in the browser.
19 | * Supports the standard SQL queries: SELECT, UPDATE, INSERT and DELETE.
20 | * Supports non-standard commands for popular DB engines such as MySQL.
21 | * Supports paramterized queries for safe value escaping.
22 | * Can be customized to build any query or command of your choosing.
23 | * Uses method chaining for ease of use.
24 | * Small: ~7 KB minified and gzipped
25 | * And much more, [see the guide..](https://hiddentao.github.io/squel)
26 |
27 | **WARNING: Do not ever pass queries generated on the client side to your web server for execution.** Such a configuration would make it trivial for a casual attacker to execute arbitrary queries—as with an SQL-injection vector, but much easier to exploit and practically impossible to protect against.
28 |
29 | _Note: Squel is suitable for production use, but you may wish to consider more
30 | actively developed alternatives such as [Knex](http://knexjs.org/)_
31 |
32 | ## Installation
33 |
34 | Install using [npm](http://npmjs.org/):
35 |
36 | ```bash
37 | $ npm install squel
38 | ```
39 |
40 | ## Available files
41 |
42 | * `squel.js` - unminified version of Squel with the standard commands and all available non-standard commands added
43 | * `squel.min.js` - minified version of `squel.js`
44 | * `squel-basic.js` - unminified version of Squel with only the standard SQL commands
45 | * `squel-basic.min.js` - minified version of `squel-basic.js`
46 |
47 |
48 | ## Examples
49 |
50 | Before running the examples ensure you have `squel` installed and enabled at the top of your script:
51 |
52 | var squel = require("squel");
53 |
54 | ### SELECT
55 |
56 | ```javascript
57 | // SELECT * FROM table
58 | squel.select()
59 | .from("table")
60 | .toString()
61 |
62 | // SELECT t1.id, t2.name FROM table `t1` LEFT JOIN table2 `t2` ON (t1.id = t2.id) WHERE (t2.name <> 'Mark') AND (t2.name <> 'John') GROUP BY t1.id
63 | squel.select()
64 | .from("table", "t1")
65 | .field("t1.id")
66 | .field("t2.name")
67 | .left_join("table2", "t2", "t1.id = t2.id")
68 | .group("t1.id")
69 | .where("t2.name <> 'Mark'")
70 | .where("t2.name <> 'John'")
71 | .toString()
72 |
73 | // SELECT `t1`.`id`, `t1`.`name` as "My name", `t1`.`started` as "Date" FROM table `t1` WHERE age IN (RANGE(1, 1.2)) ORDER BY id ASC LIMIT 20
74 | squel.select({ autoQuoteFieldNames: true })
75 | .from("table", "t1")
76 | .field("t1.id")
77 | .field("t1.name", "My name")
78 | .field("t1.started", "Date")
79 | .where("age IN ?", squel.str('RANGE(?, ?)', 1, 1.2))
80 | .order("id")
81 | .limit(20)
82 | .toString()
83 | ```
84 |
85 | You can build parameterized queries:
86 |
87 | ```js
88 | /*
89 | {
90 | text: "SELECT `t1`.`id`, `t1`.`name` as "My name", `t1`.`started` as "Date" FROM table `t1` WHERE age IN (RANGE(?, ?)) ORDER BY id ASC LIMIT 20",
91 | values: [1, 1.2]
92 | }
93 | */
94 | squel.select({ autoQuoteFieldNames: true })
95 | .from("table", "t1")
96 | .field("t1.id")
97 | .field("t1.name", "My name")
98 | .field("t1.started", "Date")
99 | .where("age IN ?", squel.str('RANGE(?, ?)', 1, 1.2))
100 | .order("id")
101 | .limit(20)
102 | .toParam()
103 | ```
104 |
105 |
106 | You can use nested queries:
107 |
108 | ```javascript
109 | // SELECT s.id FROM (SELECT * FROM students) `s` INNER JOIN (SELECT id FROM marks) `m` ON (m.id = s.id)
110 | squel.select()
111 | .from( squel.select().from('students'), 's' )
112 | .field('id')
113 | .join( squel.select().from('marks').field('id'), 'm', 'm.id = s.id' )
114 | .toString()
115 | ```
116 |
117 | ### UPDATE
118 |
119 | ```javascript
120 | // UPDATE test SET f1 = 1
121 | squel.update()
122 | .table("test")
123 | .set("f1", 1)
124 | .toString()
125 |
126 | // UPDATE test, test2, test3 AS `a` SET test.id = 1, test2.val = 1.2, a.name = "Ram", a.email = NULL, a.count = a.count + 1
127 | squel.update()
128 | .table("test")
129 | .set("test.id", 1)
130 | .table("test2")
131 | .set("test2.val", 1.2)
132 | .table("test3","a")
133 | .setFields({
134 | "a.name": "Ram",
135 | "a.email": null,
136 | "a.count = a.count + 1": undefined
137 | })
138 | .toString()
139 | ```
140 |
141 | ### INSERT
142 |
143 | ```javascript
144 | // INSERT INTO test (f1) VALUES (1)
145 | squel.insert()
146 | .into("test")
147 | .set("f1", 1)
148 | .toString()
149 |
150 | // INSERT INTO test (name, age) VALUES ('Thomas', 29), ('Jane', 31)
151 | squel.insert()
152 | .into("test")
153 | .setFieldsRows([
154 | { name: "Thomas", age: 29 },
155 | { name: "Jane", age: 31 }
156 | ])
157 | .toString()
158 | ```
159 |
160 | ### DELETE
161 |
162 | ```javascript
163 | // DELETE FROM test
164 | squel.delete()
165 | .from("test")
166 | .toString()
167 |
168 | // DELETE FROM table1 WHERE (table1.id = 2) ORDER BY id DESC LIMIT 2
169 | squel.delete()
170 | .from("table1")
171 | .where("table1.id = ?", 2)
172 | .order("id", false)
173 | .limit(2)
174 | ```
175 |
176 | ### Paramterized queries
177 |
178 | Use the `useParam()` method to obtain a parameterized query with a separate list of formatted parameter values:
179 |
180 | ```javascript
181 | // { text: "INSERT INTO test (f1, f2, f3, f4, f5) VALUES (?, ?, ?, ?, ?)", values: [1, 1.2, "TRUE", "blah", "NULL"] }
182 | squel.insert()
183 | .into("test")
184 | .set("f1", 1)
185 | .set("f2", 1.2)
186 | .set("f3", true)
187 | .set("f4", "blah")
188 | .set("f5", null)
189 | .toParam()
190 | ```
191 |
192 |
193 | ### Expression builder
194 |
195 | There is also an expression builder which allows you to build complex expressions for `WHERE` and `ON` clauses:
196 |
197 | ```javascript
198 | // test = 3 OR test = 4
199 | squel.expr()
200 | .or("test = 3")
201 | .or("test = 4")
202 | .toString()
203 |
204 | // test = 3 AND (inner = 1 OR inner = 2) OR (inner = 3 AND inner = 4 OR (inner IN ('str1, 'str2', NULL)))
205 | squel.expr()
206 | .and("test = 3")
207 | .and(
208 | squel.expr()
209 | .or("inner = 1")
210 | .or("inner = 2")
211 | )
212 | .or(
213 | squel.expr()
214 | .and("inner = ?", 3)
215 | .and("inner = ?", 4)
216 | .or(
217 | squel.expr()
218 | .and("inner IN ?", ['str1', 'str2', null])
219 | )
220 | )
221 | .toString()
222 |
223 | // SELECT * FROM test INNER JOIN test2 ON (test.id = test2.id) WHERE (test = 3 OR test = 4)
224 | squel.select()
225 | .join( "test2", null, squel.expr().and("test.id = test2.id") )
226 | .where( squel.expr().or("test = 3").or("test = 4") )
227 | ```
228 |
229 | ### Custom value types
230 |
231 | By default Squel does not support the use of object instances as field values. Instead it lets you tell it how you want
232 | specific object types to be handled:
233 |
234 | ```javascript
235 | // handler for objects of type Date
236 | squel.registerValueHandler(Date, function(date) {
237 | return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
238 | });
239 |
240 | squel.update().
241 | .table('students')
242 | .set('start_date', new Date(2013, 5, 1))
243 | .toString()
244 |
245 | // UPDATE students SET start_date = '2013/6/1'
246 | ```
247 |
248 |
249 | _Note that custom value handlers can be overridden on a per-instance basis (see the [docs](https://hiddentao.github.io/squel))_
250 |
251 | ### Custom queries
252 |
253 | Squel allows you to override the built-in query builders with your own as well as create your own types of queries:
254 |
255 | ```javascript
256 | // ------------------------------------------------------
257 | // Setup the PRAGMA query builder
258 | // ------------------------------------------------------
259 | var util = require('util'); // to use util.inherits() from node.js
260 |
261 | var CommandBlock = function() {};
262 | util.inherits(CommandBlock, squel.cls.Block);
263 |
264 | // private method - will not get exposed within the query builder
265 | CommandBlock.prototype._command = function(_command) {
266 | this._command = _command;
267 | }
268 |
269 | // public method - will get exposed within the query builder
270 | CommandBlock.prototype.compress = function() {
271 | this._command('compress');
272 | };
273 |
274 | CommandBlock.prototype.buildStr = function() {
275 | return this._command.toUpperCase();
276 | };
277 |
278 |
279 | // generic parameter block
280 | var ParamBlock = function() {};
281 | util.inherits(ParamBlock, squel.cls.Block);
282 |
283 | ParamBlock.prototype.param = function(p) {
284 | this._p = p;
285 | };
286 |
287 | ParamBlock.prototype.buildStr = function() {
288 | return this._p;
289 | };
290 |
291 |
292 | // pragma query builder
293 | var PragmaQuery = function(options) {
294 | squel.cls.QueryBuilder.call(this, options, [
295 | new squel.cls.StringBlock(options, 'PRAGMA'),
296 | new CommandBlock(),
297 | new ParamBlock()
298 | ]);
299 | };
300 | util.inherits(PragmaQuery, squel.cls.QueryBuilder);
301 |
302 |
303 | // convenience method (we can override built-in squel methods this way too)
304 | squel.pragma = function(options) {
305 | return new PragmaQuery(options)
306 | };
307 |
308 |
309 | // ------------------------------------------------------
310 | // Build a PRAGMA query
311 | // ------------------------------------------------------
312 |
313 | squel.pragma()
314 | .compress()
315 | .param('test')
316 | .toString();
317 |
318 | // 'PRAGMA COMPRESS test'
319 | ```
320 |
321 | Examples of custom queries in the wild:
322 |
323 | * https://github.com/bostrt/squel-top-start-at (blog post about it: http://blog.bostrt.net/extending-squel-js/)
324 |
325 |
326 | ## Non-standard SQL
327 |
328 | Squel supports the standard SQL commands and reserved words. However a number of database engines provide their own
329 | non-standard commands. To make things easy Squel allows for different 'flavours' of SQL to be loaded and used.
330 |
331 | At the moment Squel provides `mysql`, `mssql` and `postgres` flavours which augment query builders with additional commands (e.g. `INSERT ... RETURNING`
332 | for use with Postgres).
333 |
334 | To use this in node.js:
335 |
336 | ```javascript
337 | var squel = require('squel').useFlavour('postgres');
338 | ```
339 |
340 | For the browser:
341 |
342 | ```html
343 |
344 |
347 | ```
348 |
349 | (Internally the flavour setup method simply utilizes the [custom query mechanism](http://hiddentao.github.io/squel/#custom_queries) to effect changes).
350 |
351 | Read the the [API docs](http://hiddentao.github.io/squel/api.html) to find out available commands. Flavours of SQL which get added to
352 | Squel in the future will be usable in the above manner.
353 |
354 | ## Building it
355 |
356 | To build the code and run the tests:
357 |
358 | $ npm install
359 | $ npm test <-- this will build the code and run the tests
360 |
361 | ## Releasing it
362 |
363 | Instructions for creating a new release of squel are in `RELEASE.md`.
364 |
365 |
366 | ## Contributing
367 |
368 | Contributions are welcome! Please see `CONTRIBUTING.md`.
369 |
370 | ## Older verions
371 |
372 | **Note: The latest Squel version only works on Node 0.12 or above. Please use Squel 4.4.1 for Node <0.12. The [old 4.x docs](http://hiddentao.github.io/squel/v4/index.html) are also still available.**
373 |
374 |
375 | ## Ports to other languages
376 |
377 | * .NET - https://github.com/seymourpoler/Squel.net
378 | * Crystal - https://github.com/seymourpoler/Squel.crystal
379 |
380 | ## License
381 |
382 | MIT - see LICENSE.md
383 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | ## Creating a new release
2 |
3 | 1. Make final commits
4 | 2. Push to `master` on Github and wait for build to pass
5 | 3. Update `version` field in `package.json`
6 | 4. Update `CHANGELOG.md` with details of release
7 | 5. Run `npm test`
8 | 6. Commit and push to Github
9 | 7. Tag commit as
10 | 8. Push all tags to Github: `git push --tags`
11 | 9. Publish to NPM: `npm publish`
12 | 10. Announce to world!
13 |
14 |
--------------------------------------------------------------------------------
/dist/squel-basic.min.js:
--------------------------------------------------------------------------------
1 | /*! squel | https://github.com/hiddentao/squel | BSD license */!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.squel=e()}(this,function(){"use strict";function t(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function e(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t,e){return t.length?t+e:t}function i(t){for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n0&&void 0!==arguments[0]?arguments[0]:null,c={isSquelBuilder:function(t){return t&&!!t._toParamString}},_=function(t){return!c.isSquelBuilder(t)||!t.options.rawNesting};c.DefaultQueryBuilderOptions={autoQuoteTableNames:!1,autoQuoteFieldNames:!1,autoQuoteAliasNames:!0,useAsForTableAliasNames:!1,nameQuoteCharacter:"`",tableAliasQuoteCharacter:"`",fieldAliasQuoteCharacter:'"',valueHandlers:[],parameterCharacter:"?",numberedParameters:!1,numberedParametersPrefix:"$",numberedParametersStartAt:1,replaceSingleQuotes:!1,singleQuoteReplacement:"''",separator:" ",stringFormatter:null,rawNesting:!1},c.globalValueHandlers=[],c.registerValueHandler=function(t,e){u(c.globalValueHandlers,t,e)},c.Cloneable=function(){function t(){r(this,t)}return v(t,[{key:"clone",value:function(){var t=new this.constructor;return i(t,l(i({},this)))}}]),t}(),c.BaseBuilder=function(n){function o(e){r(this,o);var n=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this)),a=JSON.parse(JSON.stringify(c.DefaultQueryBuilderOptions));return["stringFormatter"].forEach(function(t){a[t]=c.DefaultQueryBuilderOptions[t]}),n.options=i({},a,e),n}return e(o,n),v(o,[{key:"registerValueHandler",value:function(t,e){return u(this.options.valueHandlers,t,e),this}},{key:"_sanitizeExpression",value:function(t){if(!c.isSquelBuilder(t)&&"string"!=typeof t)throw new Error("expression must be a string or builder instance");return t}},{key:"_sanitizeName",value:function(t,e){if("string"!=typeof t)throw new Error(e+" must be a string");return t}},{key:"_sanitizeField",value:function(t){return c.isSquelBuilder(t)||(t=this._sanitizeName(t,"field name")),t}},{key:"_sanitizeBaseBuilder",value:function(t){if(c.isSquelBuilder(t))return t;throw new Error("must be a builder instance")}},{key:"_sanitizeTable",value:function(t){if("string"!=typeof t)try{t=this._sanitizeBaseBuilder(t)}catch(e){throw new Error("table name must be a string or a builder")}else t=this._sanitizeName(t,"table");return t}},{key:"_sanitizeTableAlias",value:function(t){return this._sanitizeName(t,"table alias")}},{key:"_sanitizeFieldAlias",value:function(t){return this._sanitizeName(t,"field alias")}},{key:"_sanitizeLimitOffset",value:function(t){if(t=parseInt(t),0>t||isNaN(t))throw new Error("limit/offset must be >= 0");return t}},{key:"_sanitizeValue",value:function(t){var e="undefined"==typeof t?"undefined":d(t);if(null===t);else if("string"===e||"number"===e||"boolean"===e);else if(c.isSquelBuilder(t));else{var r=!!s(t,this.options.valueHandlers,c.globalValueHandlers);if(!r)throw new Error("field value must be a string, number, boolean, null or one of the registered custom value types")}return t}},{key:"_escapeValue",value:function(t){return this.options.replaceSingleQuotes&&t?t.replace(/\'/g,this.options.singleQuoteReplacement):t}},{key:"_formatTableName",value:function(t){if(this.options.autoQuoteTableNames){var e=this.options.nameQuoteCharacter;t=""+e+t+e}return t}},{key:"_formatFieldAlias",value:function(t){if(this.options.autoQuoteAliasNames){var e=this.options.fieldAliasQuoteCharacter;t=""+e+t+e}return t}},{key:"_formatTableAlias",value:function(t){if(this.options.autoQuoteAliasNames){var e=this.options.tableAliasQuoteCharacter;t=""+e+t+e}return this.options.useAsForTableAliasNames?"AS "+t:t}},{key:"_formatFieldName",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(this.options.autoQuoteFieldNames){var r=this.options.nameQuoteCharacter;t=e.ignorePeriodsForFieldNameQuotes?""+r+t+r:t.split(".").map(function(t){return"*"===t?t:""+r+t+r}).join(".")}return t}},{key:"_formatCustomValue",value:function(t,e,r){var n=s(t,this.options.valueHandlers,c.globalValueHandlers);return n&&(t=n(t,e,r),t&&t.rawNesting)?{formatted:!0,rawNesting:!0,value:t.value}:{formatted:!!n,value:t}}},{key:"_formatValueForParamArray",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return a(t)?t.map(function(t){return e._formatValueForParamArray(t,r)}):this._formatCustomValue(t,!0,r).value}},{key:"_formatValueForQueryString",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=this._formatCustomValue(t,!1,r),i=n.rawNesting,o=n.formatted,l=n.value;if(o)return i?l:this._applyNestingFormatting(l,_(t));if(a(l))l=l.map(function(t){return e._formatValueForQueryString(t)}),l=this._applyNestingFormatting(l.join(", "),_(l));else{var u="undefined"==typeof l?"undefined":d(l);if(null===l)l="NULL";else if("boolean"===u)l=l?"TRUE":"FALSE";else if(c.isSquelBuilder(l))l=this._applyNestingFormatting(l.toString(),_(l));else if("number"!==u){if("string"===u&&this.options.stringFormatter)return this.options.stringFormatter(l);if(r.dontQuote)l=""+l;else{var s=this._escapeValue(l);l="'"+s+"'"}}}return l}},{key:"_applyNestingFormatting",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(t&&"string"==typeof t&&e&&!this.options.rawNesting){var r="("===t.charAt(0)&&")"===t.charAt(t.length-1);if(r)for(var n=0,i=1;t.length-1>++n;){var o=t.charAt(n);if("("===o)i++;else if(")"===o&&(i--,1>i)){r=!1;break}}r||(t="("+t+")")}return t}},{key:"_buildString",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.nested,i=r.buildParameterized,o=r.formattingOptions;e=e||[],t=t||"";for(var l="",u=-1,s=[],f=this.options.parameterCharacter,h=0;t.length>h;)if(t.substr(h,f.length)===f){var v=e[++u];if(i)if(c.isSquelBuilder(v)){var d=v._toParamString({buildParameterized:i,nested:!0});l+=d.text,d.values.forEach(function(t){return s.push(t)})}else if(v=this._formatValueForParamArray(v,o),a(v)){var _=v.map(function(){return f}).join(", ");l+="("+_+")",v.forEach(function(t){return s.push(t)})}else l+=f,s.push(v);else l+=this._formatValueForQueryString(v,o);h+=f.length}else l+=t.charAt(h),h++;return{text:this._applyNestingFormatting(l,!!n),values:s}}},{key:"_buildManyStrings",value:function(t,e){for(var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=[],i=[],o=0;t.length>o;++o){var a=t[o],l=e[o],u=this._buildString(a,l,{buildParameterized:r.buildParameterized,nested:!1}),s=u.text,f=u.values;n.push(s),f.forEach(function(t){return i.push(t)})}return n=n.join(this.options.separator),{text:n.length?this._applyNestingFormatting(n,!!r.nested):"",values:i}}},{key:"_toParamString",value:function(t){throw new Error("Not yet implemented")}},{key:"toString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this._toParamString(t).text}},{key:"toParam",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this._toParamString(i({},t,{buildParameterized:!0}))}}]),o}(c.Cloneable),c.Expression=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._nodes=[],n}return e(i,n),v(i,[{key:"and",value:function(t){for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n1?e-1:0),n=1;n0&&void 0!==arguments[0]?arguments[0]:{},e=[],r=[],n=!0,i=!1,o=void 0;try{for(var a,l=this._nodes[Symbol.iterator]();!(n=(a=l.next()).done);n=!0){var u=a.value,s=u.type,f=u.expr,h=u.para,v=c.isSquelBuilder(f)?f._toParamString({buildParameterized:t.buildParameterized,nested:!0}):this._buildString(f,h,{buildParameterized:t.buildParameterized}),d=v.text,_=v.values;e.length&&e.push(s),e.push(d),_.forEach(function(t){return r.push(t)})}}catch(p){i=!0,o=p}finally{try{!n&&l["return"]&&l["return"]()}finally{if(i)throw o}}return e=e.join(" "),{text:this._applyNestingFormatting(e,!!t.nested),values:r}}}]),i}(c.BaseBuilder),c.Case=function(a){function l(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};r(this,l);var a=t(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,n));return o(e)&&(n=e,e=null),e&&(a._fieldName=a._sanitizeField(e)),a.options=i({},c.DefaultQueryBuilderOptions,n),a._cases=[],a._elseValue=null,a}return e(l,a),v(l,[{key:"when",value:function(t){for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n0&&void 0!==arguments[0]?arguments[0]:{},e="",r=[],i=!0,o=!1,a=void 0;try{for(var l,u=this._cases[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var s=l.value,f=s.expression,c=s.values,h=s.result;e=n(e," ");var v=this._buildString(f,c,{buildParameterized:t.buildParameterized,nested:!0});e+="WHEN "+v.text+" THEN "+this._formatValueForQueryString(h),v.values.forEach(function(t){return r.push(t)})}}catch(d){o=!0,a=d}finally{try{!i&&u["return"]&&u["return"]()}finally{if(o)throw a}}return e.length?(e+=" ELSE "+this._formatValueForQueryString(this._elseValue)+" END",this._fieldName&&(e=this._fieldName+" "+e),e="CASE "+e):e=this._formatValueForQueryString(this._elseValue),{text:e,values:r}}}]),l}(c.BaseBuilder),c.Block=function(n){function i(e){return r(this,i),t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e))}return e(i,n),v(i,[{key:"exposedMethods",value:function(){for(var t={},e=this;e;)Object.getOwnPropertyNames(e).forEach(function(r){"constructor"===r||"function"!=typeof e[r]||"_"===r.charAt(0)||c.Block.prototype[r]||(t[r]=e[r])}),e=Object.getPrototypeOf(e);return t}}]),i}(c.BaseBuilder),c.StringBlock=function(n){function i(e,n){r(this,i);var o=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return o._str=n,o}return e(i,n),v(i,[{key:"_toParamString",value:function(){arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{text:this._str,values:[]}}}]),i}(c.Block),c.FunctionBlock=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._strings=[],n._values=[],n}return e(i,n),v(i,[{key:"function",value:function(t){this._strings.push(t);for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n0&&void 0!==arguments[0]?arguments[0]:{};return this._buildManyStrings(this._strings,this._values,t)}}]),i}(c.Block),c.registerValueHandler(c.FunctionBlock,function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return e?t.toParam():t.toString()}),c.AbstractTableBlock=function(i){function o(e,n){r(this,o);var i=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e));return i._tables=[],i}return e(o,i),v(o,[{key:"_table",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;e=e?this._sanitizeTableAlias(e):e,t=this._sanitizeTable(t),this.options.singleTable&&(this._tables=[]),this._tables.push({table:t,alias:e})}},{key:"_hasTable",value:function(){return 00&&void 0!==arguments[0]?arguments[0]:{},e="",r=[];if(this._hasTable()){var i=!0,o=!1,a=void 0;try{for(var l,u=this._tables[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var s=l.value,f=s.table,h=s.alias;e=n(e,", ");var v=void 0;if(c.isSquelBuilder(f)){var d=f._toParamString({buildParameterized:t.buildParameterized,nested:!0}),_=d.text,p=d.values;v=_,p.forEach(function(t){return r.push(t)})}else v=this._formatTableName(f);h&&(v+=" "+this._formatTableAlias(h)),e+=v}}catch(y){o=!0,a=y}finally{try{!i&&u["return"]&&u["return"]()}finally{if(o)throw a}}this.options.prefix&&(e=this.options.prefix+" "+e)}return{text:e,values:r}}}]),o}(c.Block),c.TargetTableBlock=function(n){function i(){return r(this,i),t(this,(i.__proto__||Object.getPrototypeOf(i)).apply(this,arguments))}return e(i,n),v(i,[{key:"target",value:function(t){this._table(t)}}]),i}(c.AbstractTableBlock),c.UpdateTableBlock=function(n){function i(){return r(this,i),t(this,(i.__proto__||Object.getPrototypeOf(i)).apply(this,arguments))}return e(i,n),v(i,[{key:"table",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;this._table(t,e)}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!this._hasTable())throw new Error("table() needs to be called");return h(i.prototype.__proto__||Object.getPrototypeOf(i.prototype),"_toParamString",this).call(this,t)}}]),i}(c.AbstractTableBlock),c.FromTableBlock=function(n){function o(e){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,i({},e,{prefix:"FROM"})))}return e(o,n),v(o,[{key:"from",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;this._table(t,e)}}]),o}(c.AbstractTableBlock),c.IntoTableBlock=function(n){function o(e){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,i({},e,{prefix:"INTO",singleTable:!0})))}return e(o,n),v(o,[{key:"into",value:function(t){this._table(t)}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!this._hasTable())throw new Error("into() needs to be called");return h(o.prototype.__proto__||Object.getPrototypeOf(o.prototype),"_toParamString",this).call(this,t)}}]),o}(c.AbstractTableBlock),c.GetFieldBlock=function(i){function o(e){r(this,o);var n=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e));return n._fields=[],n}return e(o,i),v(o,[{key:"fields",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(a(t)){var r=!0,n=!1,i=void 0;try{for(var o,l=t[Symbol.iterator]();!(r=(o=l.next()).done);r=!0){var u=o.value;this.field(u,null,e)}}catch(s){n=!0,i=s}finally{try{!r&&l["return"]&&l["return"]()}finally{if(n)throw i}}}else for(var f in t){var c=t[f];this.field(f,c,e)}}},{key:"field",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};e=e?this._sanitizeFieldAlias(e):e,t=this._sanitizeField(t);var n=this._fields.filter(function(r){return r.name===t&&r.alias===e});return n.length?this:void this._fields.push({name:t,alias:e,options:r})}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.queryBuilder,r=t.buildParameterized,i="",o=[],a=!0,l=!1,u=void 0;try{for(var s,f=this._fields[Symbol.iterator]();!(a=(s=f.next()).done);a=!0){var h=s.value;i=n(i,", ");var v=h.name,d=h.alias,_=h.options;if("string"==typeof v)i+=this._formatFieldName(v,_);else{var p=v._toParamString({nested:!0,buildParameterized:r});i+=p.text,p.values.forEach(function(t){return o.push(t)})}d&&(i+=" AS "+this._formatFieldAlias(d))}}catch(y){l=!0,u=y}finally{try{!a&&f["return"]&&f["return"]()}finally{if(l)throw u}}if(!i.length){var g=e&&e.getBlock(c.FromTableBlock);g&&g._hasTable()&&(i="*")}return{text:i,values:o}}}]),o}(c.Block),c.AbstractSetFieldBlock=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._reset(),n}return e(i,n),v(i,[{key:"_reset",value:function(){this._fields=[],this._values=[[]],this._valueOptions=[[]]}},{key:"_set",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(this._values.length>1)throw new Error("Cannot set multiple rows of fields this way.");"undefined"!=typeof e&&(e=this._sanitizeValue(e)),t=this._sanitizeField(t);var n=this._fields.indexOf(t);-1===n&&(this._fields.push(t),n=this._fields.length-1),this._values[0][n]=e,this._valueOptions[0][n]=r}},{key:"_setFields",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if("object"!==("undefined"==typeof t?"undefined":d(t)))throw new Error("Expected an object but got "+("undefined"==typeof t?"undefined":d(t)));for(var r in t)this._set(r,t[r],e)}},{key:"_setFieldsRows",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!a(t))throw new Error("Expected an array of objects but got "+("undefined"==typeof t?"undefined":d(t)));this._reset();for(var r=0;t.length>r;++r){var n=t[r];for(var i in n){var o=n[i];i=this._sanitizeField(i),o=this._sanitizeValue(o);var l=this._fields.indexOf(i);if(00&&void 0!==arguments[0]?arguments[0]:{},e=t.buildParameterized;if(0>=this._fields.length)throw new Error("set() needs to be called");for(var r="",i=[],o=0;oa.indexOf("=")&&(a=a+" = "+this.options.parameterCharacter);var u=this._buildString(a,[l],{buildParameterized:e,formattingOptions:this._valueOptions[0][o]});r+=u.text,u.values.forEach(function(t){return i.push(t)})}return{text:"SET "+r,values:i}}}]),o}(c.AbstractSetFieldBlock),c.InsertFieldValueBlock=function(i){function o(){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).apply(this,arguments))}return e(o,i),v(o,[{key:"set",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};this._set(t,e,r)}},{key:"setFields",value:function(t,e){this._setFields(t,e)}},{key:"setFieldsRows",value:function(t,e){this._setFieldsRows(t,e)}},{key:"_toParamString",value:function(){for(var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=e.buildParameterized,i=this._fields.map(function(e){return t._formatFieldName(e)}).join(", "),o=[],a=[],l=0;l0&&void 0!==arguments[0]?arguments[0]:{},e="",r=[];if(this._fields.length&&this._query){var n=this._query._toParamString({buildParameterized:t.buildParameterized,nested:!0}),i=n.text,o=n.values;e="("+this._fields.join(", ")+") "+this._applyNestingFormatting(i),r=o}return{text:e,values:r}}}]),i}(c.Block),c.DistinctBlock=function(n){function i(){return r(this,i),t(this,(i.__proto__||Object.getPrototypeOf(i)).apply(this,arguments))}return e(i,n),v(i,[{key:"distinct",value:function(){this._useDistinct=!0}},{key:"_toParamString",value:function(){return{text:this._useDistinct?"DISTINCT":"",values:[]}}}]),i}(c.Block),c.GroupByBlock=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._groups=[],n}return e(i,n),v(i,[{key:"group",value:function(t){this._groups.push(this._sanitizeField(t))}},{key:"_toParamString",value:function(){arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{text:this._groups.length?"GROUP BY "+this._groups.join(", "):"",values:[]}}}]),i}(c.Block),c.AbstractVerbSingleValueBlock=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._value=null,n}return e(i,n),v(i,[{key:"_setValue",value:function(t){this._value=null!==t?this._sanitizeLimitOffset(t):t}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=null!==this._value?this.options.verb+" "+this.options.parameterCharacter:"",r=null!==this._value?[this._value]:[];return this._buildString(e,r,t)}}]),i}(c.Block),c.OffsetBlock=function(n){function o(e){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,i({},e,{verb:"OFFSET"})))}return e(o,n),v(o,[{key:"offset",value:function(t){this._setValue(t)}}]),o}(c.AbstractVerbSingleValueBlock),c.LimitBlock=function(n){function o(e){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,i({},e,{verb:"LIMIT"})))}return e(o,n),v(o,[{key:"limit",value:function(t){this._setValue(t)}}]),o}(c.AbstractVerbSingleValueBlock),c.AbstractConditionBlock=function(n){function i(e){r(this,i);var n=t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e));return n._conditions=[],n}return e(i,n),v(i,[{key:"_condition",value:function(t){t=this._sanitizeExpression(t);for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n0&&void 0!==arguments[0]?arguments[0]:{},e=[],r=[],n=!0,i=!1,o=void 0;try{for(var a,l=this._conditions[Symbol.iterator]();!(n=(a=l.next()).done);n=!0){var u=a.value,s=u.expr,f=u.values,h=c.isSquelBuilder(s)?s._toParamString({buildParameterized:t.buildParameterized}):this._buildString(s,f,{buildParameterized:t.buildParameterized});h.text.length&&e.push(h.text),h.values.forEach(function(t){return r.push(t)})}}catch(v){i=!0,o=v}finally{try{!n&&l["return"]&&l["return"]()}finally{if(i)throw o}}return e.length&&(e=e.join(") AND (")),{text:e.length?this.options.verb+" ("+e+")":"",values:r}}}]),i}(c.Block),c.WhereBlock=function(n){function o(e){return r(this,o),t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,i({},e,{verb:"WHERE"})))}return e(o,n),v(o,[{key:"where",value:function(t){for(var e=arguments.length,r=Array(e>1?e-1:0),n=1;n1?e-1:0),n=1;n2?r-2:0),i=2;i0&&void 0!==arguments[0]?arguments[0]:{},e="",r=[],i=!0,o=!1,l=void 0;try{for(var u,s=this._orders[Symbol.iterator]();!(i=(u=s.next()).done);i=!0){var f=u.value,c=f.field,h=f.dir,v=f.values;e=n(e,", ");var d=this._buildString(c,v,{buildParameterized:t.buildParameterized});e+=d.text,a(d.values)&&d.values.forEach(function(t){return r.push(t)}),null!==h&&(e+=" "+h)}}catch(_){o=!0,l=_}finally{try{!i&&s["return"]&&s["return"]()}finally{if(o)throw l}}return{text:e.length?"ORDER BY "+e:"",values:r}}}]),o}(c.Block),c.JoinBlock=function(i){function o(e){r(this,o);var n=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e));return n._joins=[],n}return e(o,i),v(o,[{key:"join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"INNER";t=this._sanitizeTable(t,!0),e=e?this._sanitizeTableAlias(e):e,r=r?this._sanitizeExpression(r):r,this._joins.push({type:n,table:t,alias:e,condition:r})}},{key:"left_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"LEFT")}},{key:"right_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"RIGHT")}},{key:"outer_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"OUTER")}},{key:"left_outer_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"LEFT OUTER")}},{key:"full_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"FULL")}},{key:"cross_join",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.join(t,e,r,"CROSS")}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e="",r=[],i=!0,o=!1,a=void 0;try{for(var l,u=this._joins[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var s=l.value,f=s.type,h=s.table,v=s.alias,d=s.condition;e=n(e,this.options.separator);var _=void 0;if(c.isSquelBuilder(h)){var p=h._toParamString({buildParameterized:t.buildParameterized,nested:!0});p.values.forEach(function(t){return r.push(t)}),_=p.text}else _=this._formatTableName(h);if(e+=f+" JOIN "+_,v&&(e+=" "+this._formatTableAlias(v)),d){e+=" ON ";var y=void 0;y=c.isSquelBuilder(d)?d._toParamString({buildParameterized:t.buildParameterized}):this._buildString(d,[],{buildParameterized:t.buildParameterized}),e+=this._applyNestingFormatting(y.text),y.values.forEach(function(t){return r.push(t)})}}}catch(g){o=!0,a=g}finally{try{!i&&u["return"]&&u["return"]()}finally{if(o)throw a}}return{text:e,values:r}}}]),o}(c.Block),c.UnionBlock=function(i){function o(e){r(this,o);var n=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e));return n._unions=[],n}return e(o,i),v(o,[{key:"union",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"UNION";t=this._sanitizeTable(t),this._unions.push({type:e,table:t})}},{key:"union_all",value:function(t){this.union(t,"UNION ALL")}},{key:"_toParamString",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e="",r=[],i=!0,o=!1,a=void 0;try{for(var l,u=this._unions[Symbol.iterator]();!(i=(l=u.next()).done);i=!0){var s=l.value,f=s.type,h=s.table;e=n(e,this.options.separator);var v=void 0;if(h instanceof c.BaseBuilder){var d=h._toParamString({buildParameterized:t.buildParameterized,nested:!0});v=d.text,d.values.forEach(function(t){return r.push(t)})}else e=this._formatTableName(h);e+=f+" "+v}}catch(_){o=!0,a=_}finally{try{!i&&u["return"]&&u["return"]()}finally{if(o)throw a}}return{text:e,values:r}}}]),o}(c.Block),c.QueryBuilder=function(n){function o(e,n){r(this,o);var i=t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e));i.blocks=n||[];var a=!0,l=!1,u=void 0;try{for(var s,f=i.blocks[Symbol.iterator]();!(a=(s=f.next()).done);a=!0){var c=s.value,h=c.exposedMethods();for(var v in h){var d=h[v];if(void 0!==i[v])throw new Error("Builder already has a builder method called: "+v);!function(t,e,r){i[e]=function(){for(var e=arguments.length,n=Array(e),o=0;o0&&void 0!==arguments[0]?arguments[0]:{};e=i({},this.options,e);var r=this.blocks.map(function(r){return r._toParamString({buildParameterized:e.buildParameterized,queryBuilder:t})}),n=r.map(function(t){return t.text}),o=r.map(function(t){return t.values}),a=n.filter(function(t){return 01&&void 0!==arguments[1]?arguments[1]:null;return r(this,i),n=n||[new c.StringBlock(e,"SELECT"),new c.FunctionBlock(e),new c.DistinctBlock(e),new c.GetFieldBlock(e),new c.FromTableBlock(e),new c.JoinBlock(e),new c.WhereBlock(e),new c.GroupByBlock(e),new c.HavingBlock(e),new c.OrderByBlock(e),new c.LimitBlock(e),new c.OffsetBlock(e),new c.UnionBlock(e)],t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e,n))}return e(i,n),i}(c.QueryBuilder),c.Update=function(n){function i(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return r(this,i),n=n||[new c.StringBlock(e,"UPDATE"),new c.UpdateTableBlock(e),new c.SetFieldBlock(e),new c.WhereBlock(e),new c.OrderByBlock(e),new c.LimitBlock(e)],t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e,n))}return e(i,n),i}(c.QueryBuilder),c.Delete=function(n){function o(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return r(this,o),n=n||[new c.StringBlock(e,"DELETE"),new c.TargetTableBlock(e),new c.FromTableBlock(i({},e,{singleTable:!0})),new c.JoinBlock(e),new c.WhereBlock(e),new c.OrderByBlock(e),new c.LimitBlock(e)],t(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,e,n))}return e(o,n),o}(c.QueryBuilder),c.Insert=function(n){function i(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return r(this,i),n=n||[new c.StringBlock(e,"INSERT"),new c.IntoTableBlock(e),new c.InsertFieldValueBlock(e),new c.InsertFieldsFromQueryBlock(e)],t(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,e,n))}return e(i,n),i}(c.QueryBuilder);var p={VERSION:"5.13.1",flavour:f,expr:function(t){return new c.Expression(t)},"case":function(t,e){return new c.Case(t,e)},select:function(t,e){return new c.Select(t,e)},update:function(t,e){return new c.Update(t,e)},insert:function(t,e){return new c.Insert(t,e)},"delete":function(t,e){return new c.Delete(t,e)},str:function(){var t=new c.FunctionBlock;return t["function"].apply(t,arguments),t},rstr:function(){var t=new c.FunctionBlock({rawNesting:!0});return t["function"].apply(t,arguments),t},registerValueHandler:c.registerValueHandler};return p.remove=p["delete"],p.cls=c,p}var h=function p(t,e,r){null===t&&(t=Function.prototype);var n=Object.getOwnPropertyDescriptor(t,e);if(void 0===n){var i=Object.getPrototypeOf(t);return null===i?void 0:p(i,e,r)}if("value"in n)return n.value;var o=n.get;if(void 0!==o)return o.call(r)},v=function(){function t(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:null;if(!t)return _;if(_.flavours[t]instanceof Function){var e=c(t);return _.flavours[t].call(null,e),e.flavours=_.flavours,e.useFlavour=_.useFlavour,e}throw new Error("Flavour not available: "+t)},_});
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | require('coffee-script/register');
2 |
3 | const gulp = require('gulp'),
4 | istanbul = require('gulp-istanbul'),
5 | umd = require('gulp-umd'),
6 | path = require('path'),
7 | concat = require('gulp-concat'),
8 | insert = require('gulp-insert'),
9 | mocha = require('gulp-mocha'),
10 | babel = require('gulp-babel'),
11 | replace = require('gulp-replace'),
12 | uglify = require('gulp-uglify'),
13 | runSequence = require('run-sequence'),
14 | argv = require('yargs').argv;
15 |
16 |
17 | const onlyTest = argv.onlyTest || argv.limitTest;
18 |
19 |
20 | const SQUEL_VERSION = require('./package.json').version;
21 |
22 |
23 | gulp.task('build-basic', function() {
24 |
25 | return gulp.src([
26 | './src/core.js',
27 | ])
28 | .pipe( concat('squel-basic.js') )
29 | .pipe( replace(/<>/i, SQUEL_VERSION) )
30 | .pipe( babel({
31 | presets: ['env']
32 | }) )
33 | .pipe( umd({
34 | exports: function (file) {
35 | return 'squel';
36 | },
37 | namespace: function(file) {
38 | return 'squel';
39 | }
40 | }))
41 | .pipe( gulp.dest('./dist') )
42 | .pipe( uglify() )
43 | .pipe( insert.prepend('/*! squel | https://github.com/hiddentao/squel | BSD license */') )
44 | .pipe( concat('squel-basic.min.js') )
45 | .pipe( gulp.dest('./dist') )
46 | });
47 |
48 |
49 | gulp.task('build-full', function() {
50 | return gulp.src([
51 | './src/core.js',
52 | './src/mssql.js',
53 | './src/mysql.js',
54 | './src/postgres.js',
55 | ])
56 | .pipe( concat('squel.js') )
57 | .pipe( replace(/<>/i, SQUEL_VERSION) )
58 | .pipe( babel({
59 | presets: ['env']
60 | }) )
61 | .pipe( umd({
62 | exports: function (file) {
63 | return 'squel';
64 | },
65 | namespace: function(file) {
66 | return 'squel';
67 | }
68 | }))
69 | .pipe( gulp.dest('./dist') )
70 | .pipe( uglify() )
71 | .pipe( insert.prepend('/*! squel | https://github.com/hiddentao/squel | BSD license */') )
72 | .pipe( concat('squel.min.js') )
73 | .pipe( gulp.dest('./dist') )
74 | });
75 |
76 |
77 | gulp.task('build', ['build-basic', 'build-full']);
78 |
79 |
80 | gulp.task('pre-test', function () {
81 | return gulp.src(['dist/*.js'])
82 | .pipe(istanbul())
83 | .pipe(istanbul.hookRequire());
84 | });
85 |
86 |
87 | gulp.task('test', ['pre-test'], function () {
88 | return gulp.src(onlyTest || [
89 | './test/baseclasses.test.coffee',
90 | './test/blocks.test.coffee',
91 | './test/case.test.coffee',
92 | './test/custom.test.coffee',
93 | './test/delete.test.coffee',
94 | './test/expressions.test.coffee',
95 | './test/insert.test.coffee',
96 | './test/select.test.coffee',
97 | './test/update.test.coffee',
98 | './test/mssql.test.coffee',
99 | './test/mysql.test.coffee',
100 | './test/postgres.test.coffee',
101 | ], { read: false })
102 | .pipe(mocha({
103 | ui: 'exports',
104 | reporter: 'spec',
105 | }))
106 | .pipe(istanbul.writeReports({
107 | dir: './test-coverage',
108 | }))
109 | // .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
110 | ;
111 | });
112 |
113 |
114 |
115 | gulp.task('default', function(cb) {
116 | runSequence(['build'], 'test', cb);
117 | });
118 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "squel",
3 | "description": "SQL query string builder",
4 | "version": "5.13.1",
5 | "author": "Ramesh Nair (http://www.hiddentao.com/)",
6 | "contributors": [
7 | "Ramesh Nair (http://www.hiddentao.com/)",
8 | "Sergej Brjuchanov "
9 | ],
10 | "dependencies": {},
11 | "devDependencies": {
12 | "babel-preset-env": "^1.6.0",
13 | "benchmark": "^2.1.4",
14 | "chai": "1.5.x",
15 | "coffee-script": "^1.10.0",
16 | "growl": "^1.10.5",
17 | "gulp": "^3.9.1",
18 | "gulp-babel": "^6.1.2",
19 | "gulp-concat": "^2.6.0",
20 | "gulp-insert": "^0.5.0",
21 | "gulp-istanbul": "^1.1.1",
22 | "gulp-mocha": "^2.2.0",
23 | "gulp-replace": "^0.5.4",
24 | "gulp-uglify": "^1.5.3",
25 | "gulp-umd": "^0.2.0",
26 | "knex": "^0.14.6",
27 | "load-grunt-tasks": "~0.1.0",
28 | "mocha": "1.9.x",
29 | "run-sequence": "^1.1.5",
30 | "sinon": "1.6.x",
31 | "time-grunt": "~0.1.1",
32 | "uglify-js": "1.3.x",
33 | "underscore": "1.4.x",
34 | "yargs": "^4.7.1"
35 | },
36 | "keywords": [
37 | "sql",
38 | "database",
39 | "rdbms"
40 | ],
41 | "main": "dist/squel.js",
42 | "types": "dist/squel.d.ts",
43 | "scripts": {
44 | "preinstall": "npx npm-force-resolutions",
45 | "test": "gulp",
46 | "test-performance": "node performance/select.js",
47 | "build": "gulp",
48 | "prepublish": "npm run build"
49 | },
50 | "resolutions": {
51 | "graceful-fs": "4.2.4"
52 | },
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/hiddentao/squel.git"
56 | },
57 | "engines": {
58 | "node": ">= 0.12.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/performance/select.js:
--------------------------------------------------------------------------------
1 | const Benchmark = require('benchmark')
2 | const knex = require('knex')({ client: 'mysql' })
3 | const squel = require('../')
4 |
5 | const suite = new Benchmark.Suite()
6 |
7 | // add tests
8 | suite
9 | .add('Knex', function() {
10 | knex.select('title', 'author', 'year').from('books').toString()
11 | })
12 | .add('Squel', function() {
13 | squel.select().fields(['title', 'author', 'year']).from('books').toString()
14 | })
15 | // add listeners
16 | .on('cycle', function(event) {
17 | console.log(String(event.target));
18 | })
19 | .on('complete', function() {
20 | console.log('Fastest is ' + this.filter('fastest').map('name'));
21 | })
22 | .run({ 'async': false });
23 |
--------------------------------------------------------------------------------
/src/mssql.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiddentao/squel/afa1cb52a61e89404e193bf4e36c2e79dd21c020/src/mssql.js
--------------------------------------------------------------------------------
/src/mysql.js:
--------------------------------------------------------------------------------
1 | // This file contains additional Squel commands for use with MySQL
2 |
3 | squel.flavours['mysql'] = function(_squel) {
4 | let cls = _squel.cls;
5 |
6 | // ON DUPLICATE KEY UPDATE ...
7 | cls.MysqlOnDuplicateKeyUpdateBlock = class extends cls.AbstractSetFieldBlock {
8 | onDupUpdate (field, value, options) {
9 | this._set(field, value, options);
10 | }
11 |
12 | _toParamString (options = {}) {
13 | let totalStr = "",
14 | totalValues = [];
15 |
16 | for (let i = 0; i < this._fields.length; ++i) {
17 | totalStr = _pad(totalStr, ', ');
18 |
19 | let field = this._fields[i];
20 |
21 | let value = this._values[0][i];
22 |
23 | let valueOptions = this._valueOptions[0][i];
24 |
25 | // e.g. if field is an expression such as: count = count + 1
26 | if (typeof value === 'undefined') {
27 | totalStr += field;
28 | } else {
29 | let ret = this._buildString(
30 | `${field} = ${this.options.parameterCharacter}`,
31 | [value],
32 | {
33 | buildParameterized: options.buildParameterized,
34 | formattingOptions: valueOptions,
35 | }
36 | );
37 |
38 | totalStr += ret.text;
39 | ret.values.forEach(value => totalValues.push(value));
40 | }
41 | }
42 |
43 | return {
44 | text: !totalStr.length ? "" : `ON DUPLICATE KEY UPDATE ${totalStr}`,
45 | values: totalValues,
46 | };
47 | }
48 | }
49 |
50 |
51 | // INSERT query builder.
52 | cls.Insert = class extends cls.QueryBuilder {
53 | constructor (options, blocks = null) {
54 | blocks = blocks || [
55 | new cls.StringBlock(options, 'INSERT'),
56 | new cls.IntoTableBlock(options),
57 | new cls.InsertFieldValueBlock(options),
58 | new cls.InsertFieldsFromQueryBlock(options),
59 | new cls.MysqlOnDuplicateKeyUpdateBlock(options),
60 | ];
61 |
62 | super(options, blocks);
63 | }
64 | }
65 |
66 | // REPLACE query builder.
67 | cls.Replace = class extends cls.QueryBuilder {
68 | constructor (options, blocks = null) {
69 | blocks = blocks || [
70 | new cls.StringBlock(options, 'REPLACE'),
71 | new cls.IntoTableBlock(options),
72 | new cls.InsertFieldValueBlock(options),
73 | new cls.InsertFieldsFromQueryBlock(options),
74 | ];
75 |
76 | super(options, blocks);
77 | }
78 | }
79 |
80 |
81 | _squel.replace = function(options, blocks){
82 | return new cls.Replace(options, blocks);
83 | }
84 |
85 | };
86 |
--------------------------------------------------------------------------------
/src/postgres.js:
--------------------------------------------------------------------------------
1 | // This file contains additional Squel commands for use with the Postgres DB engine
2 | squel.flavours['postgres'] = function(_squel) {
3 | let cls = _squel.cls;
4 |
5 | cls.DefaultQueryBuilderOptions.numberedParameters = true;
6 | cls.DefaultQueryBuilderOptions.numberedParametersStartAt = 1;
7 | cls.DefaultQueryBuilderOptions.autoQuoteAliasNames = false;
8 | cls.DefaultQueryBuilderOptions.useAsForTableAliasNames = true;
9 |
10 | cls.PostgresOnConflictKeyUpdateBlock = class extends cls.AbstractSetFieldBlock {
11 | onConflict (conflictFields, fields) {
12 | this._onConflict = true;
13 | if (!conflictFields) {
14 | return;
15 | }
16 | if (!_isArray(conflictFields)) {
17 | conflictFields = [conflictFields];
18 | }
19 | this._dupFields = conflictFields.map(this._sanitizeField.bind(this));
20 |
21 | if(fields) {
22 | Object.keys(fields).forEach((key) => {
23 | this._set(key, fields[key]);
24 | });
25 | }
26 | }
27 |
28 | _toParamString (options = {}) {
29 | let totalStr = "",
30 | totalValues = [];
31 |
32 | for (let i = 0; i < this._fields.length; ++i) {
33 | totalStr = _pad(totalStr, ', ');
34 |
35 | let field = this._fields[i];
36 |
37 | let value = this._values[0][i];
38 |
39 | let valueOptions = this._valueOptions[0][i];
40 |
41 | // e.g. if field is an expression such as: count = count + 1
42 | if (typeof value === 'undefined') {
43 | totalStr += field;
44 | } else {
45 | let ret = this._buildString(
46 | `${field} = ${this.options.parameterCharacter}`,
47 | [value],
48 | {
49 | buildParameterized: options.buildParameterized,
50 | formattingOptions: valueOptions,
51 | }
52 | );
53 |
54 | totalStr += ret.text;
55 | ret.values.forEach(value => totalValues.push(value));
56 | }
57 | }
58 |
59 | const returned = {
60 | text: '',
61 | values: totalValues,
62 | };
63 |
64 | if (this._onConflict) {
65 | // note the trailing whitespace after the join
66 | const conflictFields = this._dupFields ? `(${this._dupFields.join(', ')}) ` : '';
67 | const action = totalStr.length ? `UPDATE SET ${totalStr}` : `NOTHING`;
68 | returned.text = `ON CONFLICT ${conflictFields}DO ${action}`;
69 | }
70 |
71 | return returned;
72 | }
73 | }
74 |
75 | // RETURNING
76 | cls.ReturningBlock = class extends cls.Block {
77 | constructor (options) {
78 | super(options);
79 | this._fields = [];
80 | }
81 |
82 | returning (field, alias = null, options = {}) {
83 | alias = alias ? this._sanitizeFieldAlias(alias) : alias;
84 | field = this._sanitizeField(field);
85 |
86 | // if field-alias combo already present then don't add
87 | let existingField = this._fields.filter((f) => {
88 | return f.name === field && f.alias === alias;
89 | });
90 | if (existingField.length) {
91 | return this;
92 | }
93 |
94 | this._fields.push({
95 | name: field,
96 | alias: alias,
97 | options: options,
98 | });
99 | }
100 |
101 | _toParamString (options = {}) {
102 | let { queryBuilder, buildParameterized } = options;
103 |
104 | let totalStr = '',
105 | totalValues = [];
106 |
107 | for (let field of this._fields) {
108 | totalStr = _pad(totalStr, ", ");
109 |
110 | let { name, alias, options } = field;
111 |
112 | if (typeof name === 'string') {
113 | totalStr += this._formatFieldName(name, options);
114 | } else {
115 | let ret = name._toParamString({
116 | nested: true,
117 | buildParameterized: buildParameterized,
118 | });
119 |
120 | totalStr += ret.text;
121 | ret.values.forEach(value => totalValues.push(value));
122 | }
123 |
124 | if (alias) {
125 | totalStr += ` AS ${this._formatFieldAlias(alias)}`;
126 | }
127 | }
128 |
129 | return {
130 | text: totalStr.length > 0 ? `RETURNING ${totalStr}` : '',
131 | values: totalValues
132 | }
133 | }
134 | }
135 |
136 | // WITH
137 | cls.WithBlock = class extends cls.Block {
138 | constructor (options) {
139 | super(options);
140 | this._tables = [];
141 | }
142 |
143 | with (alias, table) {
144 | this._tables.push({alias, table});
145 | }
146 |
147 | _toParamString(options = {}) {
148 | var parts = [];
149 | var values = [];
150 |
151 | for (let {alias, table} of this._tables) {
152 | let ret = table._toParamString({
153 | buildParameterized: options.buildParameterized,
154 | nested: true
155 | });
156 |
157 | parts.push(`${alias} AS ${ret.text}`);
158 | ret.values.forEach(value => values.push(value));
159 | }
160 |
161 | return {
162 | text: parts.length ? `WITH ${parts.join(', ')}` : '',
163 | values
164 | };
165 | }
166 | }
167 |
168 | // DISTINCT [ON]
169 | cls.DistinctOnBlock = class extends cls.Block {
170 | constructor(options) {
171 | super(options);
172 |
173 | this._distinctFields = [];
174 | }
175 |
176 | distinct(...fields) {
177 | this._useDistinct = true;
178 |
179 | // Add all fields to the DISTINCT ON clause.
180 | fields.forEach((field) => {
181 | this._distinctFields.push(this._sanitizeField(field));
182 | });
183 | }
184 |
185 | _toParamString() {
186 | let text = '';
187 |
188 | if (this._useDistinct) {
189 | text = 'DISTINCT';
190 |
191 | if (this._distinctFields.length) {
192 | text += ` ON (${this._distinctFields.join(', ')})`;
193 | }
194 | }
195 |
196 | return {
197 | text,
198 | values: []
199 | };
200 | }
201 | }
202 |
203 | // SELECT query builder.
204 | cls.Select = class extends cls.QueryBuilder {
205 | constructor (options, blocks = null) {
206 | blocks = blocks || [
207 | new cls.WithBlock(options),
208 | new cls.StringBlock(options, 'SELECT'),
209 | new cls.FunctionBlock(options),
210 | new cls.DistinctOnBlock(options),
211 | new cls.GetFieldBlock(options),
212 | new cls.FromTableBlock(options),
213 | new cls.JoinBlock(options),
214 | new cls.WhereBlock(options),
215 | new cls.GroupByBlock(options),
216 | new cls.HavingBlock(options),
217 | new cls.OrderByBlock(options),
218 | new cls.LimitBlock(options),
219 | new cls.OffsetBlock(options),
220 | new cls.UnionBlock(options)
221 | ];
222 |
223 | super(options, blocks);
224 | }
225 | }
226 |
227 | // INSERT query builder
228 | cls.Insert = class extends cls.QueryBuilder {
229 | constructor (options, blocks = null) {
230 | blocks = blocks || [
231 | new cls.WithBlock(options),
232 | new cls.StringBlock(options, 'INSERT'),
233 | new cls.IntoTableBlock(options),
234 | new cls.InsertFieldValueBlock(options),
235 | new cls.InsertFieldsFromQueryBlock(options),
236 | new cls.PostgresOnConflictKeyUpdateBlock(options),
237 | new cls.ReturningBlock(options),
238 | ];
239 |
240 | super(options, blocks);
241 | }
242 | }
243 |
244 | // UPDATE query builder
245 | cls.Update = class extends cls.QueryBuilder {
246 | constructor (options, blocks = null) {
247 | blocks = blocks || [
248 | new cls.WithBlock(options),
249 | new cls.StringBlock(options, 'UPDATE'),
250 | new cls.UpdateTableBlock(options),
251 | new cls.SetFieldBlock(options),
252 | new cls.FromTableBlock(options),
253 | new cls.WhereBlock(options),
254 | new cls.OrderByBlock(options),
255 | new cls.LimitBlock(options),
256 | new cls.ReturningBlock(options),
257 | ];
258 |
259 | super(options, blocks);
260 | }
261 | }
262 |
263 | // DELETE query builder
264 | cls.Delete = class extends cls.QueryBuilder {
265 | constructor (options, blocks = null) {
266 | blocks = blocks || [
267 | new cls.WithBlock(options),
268 | new cls.StringBlock(options, 'DELETE'),
269 | new cls.TargetTableBlock(options),
270 | new cls.FromTableBlock(_extend({}, options, {
271 | singleTable: true
272 | })),
273 | new cls.JoinBlock(options),
274 | new cls.WhereBlock(options),
275 | new cls.OrderByBlock(options),
276 | new cls.LimitBlock(options),
277 | new cls.ReturningBlock(options),
278 | ];
279 |
280 | super(options, blocks);
281 | }
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/test/baseclasses.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 | test['Version number'] =
32 | assert.same squel.VERSION, require('../package.json').version
33 |
34 | test['Default flavour'] =
35 | assert.isNull squel.flavour
36 |
37 |
38 | test['Cloneable base class'] =
39 | '>> clone()': ->
40 |
41 | class Child extends squel.cls.Cloneable
42 | constructor: ->
43 | @a = 1
44 | @b = 2.2
45 | @c = true
46 | @d = 'str'
47 | @e = [1]
48 | @f = { a: 1 }
49 |
50 | child = new Child()
51 |
52 | copy = child.clone()
53 | assert.instanceOf copy, Child
54 |
55 | child.a = 2
56 | child.b = 3.2
57 | child.c = false
58 | child.d = 'str2'
59 | child.e.push(2)
60 | child.f.b = 1
61 |
62 | assert.same copy.a, 1
63 | assert.same copy.b, 2.2
64 | assert.same copy.c, true
65 | assert.same copy.d, 'str'
66 | assert.same copy.e, [1]
67 | assert.same copy.f, { a: 1 }
68 |
69 |
70 | test['Default query builder options'] =
71 | 'default options': ->
72 | assert.same {
73 | autoQuoteTableNames: false
74 | autoQuoteFieldNames: false
75 | autoQuoteAliasNames: true
76 | useAsForTableAliasNames: false
77 | nameQuoteCharacter: '`'
78 | tableAliasQuoteCharacter: '`'
79 | fieldAliasQuoteCharacter: '"'
80 | valueHandlers: []
81 | parameterCharacter: '?'
82 | numberedParameters: false
83 | numberedParametersPrefix: '$'
84 | numberedParametersStartAt: 1
85 | replaceSingleQuotes: false
86 | singleQuoteReplacement: '\'\''
87 | separator: ' '
88 | stringFormatter: null
89 | rawNesting: false
90 | }, squel.cls.DefaultQueryBuilderOptions
91 |
92 |
93 |
94 | test['Register global custom value handler'] =
95 | 'beforeEach': ->
96 | @originalHandlers = [].concat(squel.cls.globalValueHandlers)
97 | squel.cls.globalValueHandlers = []
98 | 'afterEach': ->
99 | squel.cls.globalValueHandlers = @originalHandlers
100 | 'default': ->
101 | handler = -> 'test'
102 | squel.registerValueHandler(Date, handler)
103 | squel.registerValueHandler(Object, handler)
104 | squel.registerValueHandler('boolean', handler)
105 |
106 | assert.same 3, squel.cls.globalValueHandlers.length
107 | assert.same { type: Date, handler: handler }, squel.cls.globalValueHandlers[0]
108 | assert.same { type: Object, handler: handler }, squel.cls.globalValueHandlers[1]
109 | assert.same { type: 'boolean', handler: handler }, squel.cls.globalValueHandlers[2]
110 |
111 | 'type should be class constructor': ->
112 | assert.throws (-> squel.registerValueHandler 1, null), "type must be a class constructor or string"
113 |
114 | 'handler should be function': ->
115 | class MyClass
116 | assert.throws (-> squel.registerValueHandler MyClass, 1), 'handler must be a function'
117 |
118 | 'overrides existing handler': ->
119 | handler = -> 'test'
120 | handler2 = -> 'test2'
121 | squel.registerValueHandler(Date, handler)
122 | squel.registerValueHandler(Date, handler2)
123 |
124 | assert.same 1, squel.cls.globalValueHandlers.length
125 | assert.same { type: Date, handler: handler2 }, squel.cls.globalValueHandlers[0]
126 |
127 |
128 | test['str()'] =
129 | constructor: ->
130 | f = squel.str('GETDATE(?)', 12, 23)
131 | assert.ok (f instanceof squel.cls.FunctionBlock)
132 | assert.same 'GETDATE(?)', f._strings[0]
133 | assert.same [12, 23], f._values[0]
134 |
135 | 'custom value handler':
136 | beforeEach: ->
137 | @inst = squel.str('G(?,?)', 12, 23, 65)
138 |
139 | handlerConfig = _.find squel.cls.globalValueHandlers, (hc) ->
140 | hc.type is squel.cls.FunctionBlock
141 |
142 | @handler = handlerConfig.handler
143 |
144 | toString: ->
145 | assert.same @inst.toString(), @handler(@inst)
146 | toParam: ->
147 | assert.same @inst.toParam(), @handler(@inst, true)
148 |
149 |
150 | test['rstr()'] =
151 | constructor: ->
152 | f = squel.rstr('GETDATE(?)', 12, 23)
153 | assert.ok (f instanceof squel.cls.FunctionBlock)
154 | assert.same 'GETDATE(?)', f._strings[0]
155 | assert.same [12, 23], f._values[0]
156 |
157 | vsStr: ->
158 | f1 = squel.str('OUTER(?)', squel.str('INNER(?)', 2))
159 | assert.same 'OUTER((INNER(2)))', f1.toString()
160 | f2 = squel.str('OUTER(?)', squel.rstr('INNER(?)', 2))
161 | assert.same 'OUTER(INNER(2))', f2.toString()
162 |
163 | 'custom value handler':
164 | beforeEach: ->
165 | @inst = squel.rstr('G(?,?)', 12, 23, 65)
166 |
167 | handlerConfig = _.find squel.cls.globalValueHandlers, (hc) ->
168 | hc.type is squel.cls.FunctionBlock
169 |
170 | @handler = handlerConfig.handler
171 |
172 | toString: ->
173 | assert.same @inst.toString(), @handler(@inst)
174 | toParam: ->
175 | assert.same @inst.toParam(), @handler(@inst, true)
176 |
177 |
178 | test['Load an SQL flavour'] =
179 | beforeEach: ->
180 | @flavoursBackup = squel.flavours
181 | squel.flavours = {}
182 |
183 | afterEach: ->
184 | squel.flavours = @flavoursBackup
185 |
186 | 'invalid flavour': ->
187 | assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
188 |
189 | 'flavour reference should be a function': ->
190 | squel.flavours['test'] = 'blah'
191 | assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
192 |
193 | 'flavour setup function gets executed': ->
194 | squel.flavours['test'] = test.mocker.spy()
195 | ret = squel.useFlavour 'test'
196 | assert.ok squel.flavours['test'].calledOnce
197 | assert.ok !!ret.select()
198 |
199 | 'can switch flavours': ->
200 | squel.flavours['test'] = test.mocker.spy( (s) ->
201 | s.cls.dummy = 1
202 | )
203 | squel.flavours['test2'] = test.mocker.spy( (s) ->
204 | s.cls.dummy2 = 2
205 | )
206 | ret = squel.useFlavour 'test'
207 | assert.same ret.cls.dummy, 1
208 |
209 | ret = squel.useFlavour 'test2'
210 | assert.same ret.cls.dummy, undefined
211 | assert.same ret.cls.dummy2, 2
212 |
213 | ret = squel.useFlavour()
214 | assert.same ret.cls.dummy, undefined
215 | assert.same ret.cls.dummy2, undefined
216 |
217 | 'can get current flavour': ->
218 | flavour = 'test'
219 | squel.flavours[flavour] = test.mocker.spy()
220 |
221 | ret = squel.useFlavour flavour
222 | assert.same ret.flavour, flavour
223 |
224 | 'can mix flavours - #255': ->
225 | squel.flavours.flavour1 = (s) -> s
226 | squel.flavours.flavour2 = (s) -> s
227 | squel1 = squel.useFlavour 'flavour1'
228 | squel2 = squel.useFlavour 'flavour2'
229 |
230 | expr1 = squel1.expr().and('1 = 1')
231 | assert.same squel2.select().from('test', 't').where(expr1).toString(), 'SELECT * FROM test `t` WHERE (1 = 1)'
232 |
233 |
234 |
235 | test['Builder base class'] =
236 | beforeEach: ->
237 | @cls = squel.cls.BaseBuilder
238 | @inst = new @cls
239 |
240 | @originalHandlers = [].concat(squel.cls.globalValueHandlers)
241 |
242 | afterEach: ->
243 | squel.cls.globalValueHandlers = @originalHandlers
244 |
245 | 'instanceof Cloneable': ->
246 | assert.instanceOf @inst, squel.cls.Cloneable
247 |
248 | 'constructor':
249 | 'default options': ->
250 | assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
251 |
252 | 'overridden options': ->
253 | @inst = new @cls
254 | dummy1: 'str'
255 | dummy2: 12.3
256 | usingValuePlaceholders: true
257 | dummy3: true,
258 | globalValueHandlers: [1]
259 |
260 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
261 | dummy1: 'str'
262 | dummy2: 12.3
263 | usingValuePlaceholders: true
264 | dummy3: true
265 | globalValueHandlers: [1]
266 |
267 | assert.same expectedOptions, @inst.options
268 |
269 |
270 | 'registerValueHandler':
271 | 'afterEach': ->
272 | squel.cls.globalValueHandlers = []
273 |
274 | 'default': ->
275 | handler = -> 'test'
276 | @inst.registerValueHandler(Date, handler)
277 | @inst.registerValueHandler(Object, handler)
278 | @inst.registerValueHandler('number', handler)
279 |
280 | assert.same 3, @inst.options.valueHandlers.length
281 | assert.same { type: Date, handler: handler }, @inst.options.valueHandlers[0]
282 | assert.same { type: Object, handler: handler }, @inst.options.valueHandlers[1]
283 | assert.same { type: 'number', handler: handler }, @inst.options.valueHandlers[2]
284 |
285 | 'type should be class constructor': ->
286 | assert.throws (=> @inst.registerValueHandler 1, null), "type must be a class constructor or string"
287 |
288 | 'handler should be function': ->
289 | class MyClass
290 | assert.throws (=> @inst.registerValueHandler MyClass, 1), 'handler must be a function'
291 |
292 | 'returns instance for chainability': ->
293 | handler = -> 'test'
294 | assert.same @inst, @inst.registerValueHandler(Date, handler)
295 |
296 | 'overrides existing handler': ->
297 | handler = -> 'test'
298 | handler2 = -> 'test2'
299 | @inst.registerValueHandler(Date, handler)
300 | @inst.registerValueHandler(Date, handler2)
301 |
302 | assert.same 1, @inst.options.valueHandlers.length
303 | assert.same { type: Date, handler: handler2 }, @inst.options.valueHandlers[0]
304 |
305 | 'does not touch global value handlers list': ->
306 | oldGlobalHandlers = squel.cls.globalValueHandlers
307 |
308 | handler = -> 'test'
309 | @inst.registerValueHandler(Date, handler)
310 |
311 | assert.same oldGlobalHandlers, squel.cls.globalValueHandlers
312 |
313 |
314 | '_sanitizeExpression':
315 | 'if Expression':
316 | 'empty expression': ->
317 | e = squel.expr()
318 | assert.same e, @inst._sanitizeExpression(e)
319 | 'non-empty expression': ->
320 | e = squel.expr().and("s.name <> 'Fred'")
321 | assert.same e, @inst._sanitizeExpression(e)
322 |
323 | 'if Expression': ->
324 | s = squel.str('s')
325 | assert.same s, @inst._sanitizeExpression(s)
326 |
327 | 'if string': ->
328 | s = 'BLA BLA'
329 | assert.same 'BLA BLA', @inst._sanitizeExpression(s)
330 |
331 | 'if neither expression, builder nor String': ->
332 | testFn = => @inst._sanitizeExpression(1)
333 | assert.throws testFn, 'expression must be a string or builder instance'
334 |
335 |
336 | '_sanitizeName':
337 | beforeEach: ->
338 | test.mocker.spy @inst, '_sanitizeName'
339 |
340 | 'if string': ->
341 | assert.same 'bla', @inst._sanitizeName('bla')
342 |
343 | 'if boolean': ->
344 | assert.throws (=> @inst._sanitizeName(true, 'bla')), 'bla must be a string'
345 |
346 | 'if integer': ->
347 | assert.throws (=> @inst._sanitizeName(1)), 'undefined must be a string'
348 |
349 | 'if float': ->
350 | assert.throws (=> @inst._sanitizeName(1.2, 'meh')), 'meh must be a string'
351 |
352 | 'if array': ->
353 | assert.throws (=> @inst._sanitizeName([1], 'yes')), 'yes must be a string'
354 |
355 | 'if object': ->
356 | assert.throws (=> @inst._sanitizeName(new Object, 'yes')), 'yes must be a string'
357 |
358 | 'if null': ->
359 | assert.throws (=> @inst._sanitizeName(null, 'no')), 'no must be a string'
360 |
361 | 'if undefined': ->
362 | assert.throws (=> @inst._sanitizeName(undefined, 'no')), 'no must be a string'
363 |
364 |
365 | '_sanitizeField':
366 | 'default': ->
367 | test.mocker.spy @inst, '_sanitizeName'
368 |
369 | assert.same 'abc', @inst._sanitizeField('abc')
370 |
371 | assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field name'
372 |
373 | 'QueryBuilder': ->
374 | s = squel.select().from('scores').field('MAX(score)')
375 | assert.same s, @inst._sanitizeField(s)
376 |
377 |
378 | '_sanitizeBaseBuilder':
379 | 'is not base builder': ->
380 | assert.throws (=> @inst._sanitizeBaseBuilder(null)), 'must be a builder instance'
381 |
382 | 'is a query builder': ->
383 | qry = squel.select()
384 | assert.same qry, @inst._sanitizeBaseBuilder(qry)
385 |
386 |
387 | '_sanitizeTable':
388 | 'default': ->
389 | test.mocker.spy @inst, '_sanitizeName'
390 |
391 | assert.same 'abc', @inst._sanitizeTable('abc')
392 |
393 | assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table'
394 |
395 | 'not a string': ->
396 | assert.throws (=> @inst._sanitizeTable(null)), 'table name must be a string or a builder'
397 |
398 | 'query builder': ->
399 | select = squel.select()
400 | assert.same select, @inst._sanitizeTable(select, true)
401 |
402 |
403 | '_sanitizeFieldAlias': ->
404 | 'default': ->
405 | test.mocker.spy @inst, '_sanitizeName'
406 |
407 | @inst._sanitizeFieldAlias('abc')
408 |
409 | assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field alias'
410 |
411 |
412 | '_sanitizeTableAlias': ->
413 | 'default': ->
414 | test.mocker.spy @inst, '_sanitizeName'
415 |
416 | @inst._sanitizeTableAlias('abc')
417 |
418 | assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table alias'
419 |
420 | '_sanitizeLimitOffset':
421 | 'undefined': ->
422 | assert.throws (=> @inst._sanitizeLimitOffset()), 'limit/offset must be >= 0'
423 |
424 | 'null': ->
425 | assert.throws (=> @inst._sanitizeLimitOffset null), 'limit/offset must be >= 0'
426 |
427 | 'float': ->
428 | assert.same 1, @inst._sanitizeLimitOffset 1.2
429 |
430 | 'boolean': ->
431 | assert.throws (=> @inst._sanitizeLimitOffset false), 'limit/offset must be >= 0'
432 |
433 | 'string': ->
434 | assert.same 2, @inst._sanitizeLimitOffset '2'
435 |
436 | 'array': ->
437 | assert.same 3, @inst._sanitizeLimitOffset [3]
438 |
439 | 'object': ->
440 | assert.throws (=> @inst._sanitizeLimitOffset(new Object)), 'limit/offset must be >= 0'
441 |
442 | 'number >= 0': ->
443 | assert.same 0, @inst._sanitizeLimitOffset 0
444 | assert.same 1, @inst._sanitizeLimitOffset 1
445 |
446 | 'number < 0': ->
447 | assert.throws (=> @inst._sanitizeLimitOffset(-1)), 'limit/offset must be >= 0'
448 |
449 |
450 |
451 | '_sanitizeValue':
452 | beforeEach: ->
453 | test.mocker.spy @inst, '_sanitizeValue'
454 |
455 | afterEach: ->
456 | squel.cls.globalValueHandlers = []
457 |
458 | 'if string': ->
459 | assert.same 'bla', @inst._sanitizeValue('bla')
460 |
461 | 'if boolean': ->
462 | assert.same true, @inst._sanitizeValue(true)
463 | assert.same false, @inst._sanitizeValue(false)
464 |
465 | 'if integer': ->
466 | assert.same -1, @inst._sanitizeValue(-1)
467 | assert.same 0, @inst._sanitizeValue(0)
468 | assert.same 1, @inst._sanitizeValue(1)
469 |
470 | 'if float': ->
471 | assert.same -1.2, @inst._sanitizeValue(-1.2)
472 | assert.same 1.2, @inst._sanitizeValue(1.2)
473 |
474 | 'if array': ->
475 | assert.throws (=> @inst._sanitizeValue([1])), 'field value must be a string, number, boolean, null or one of the registered custom value types'
476 |
477 | 'if object': ->
478 | assert.throws (=> @inst._sanitizeValue(new Object)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
479 |
480 | 'if null': ->
481 | assert.same null, @inst._sanitizeValue(null)
482 |
483 | 'if BaseBuilder': ->
484 | s = squel.select()
485 | assert.same s, @inst._sanitizeValue(s)
486 |
487 | 'if undefined': ->
488 | assert.throws (=> @inst._sanitizeValue(undefined)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
489 |
490 | 'custom handlers':
491 | 'global': ->
492 | squel.registerValueHandler(Date, _.identity)
493 | date = new Date
494 | assert.same date, @inst._sanitizeValue(date)
495 |
496 | 'instance': ->
497 | @inst.registerValueHandler(Date, _.identity)
498 | date = new Date
499 | assert.same date, @inst._sanitizeValue(date)
500 |
501 |
502 | '_escapeValue': ->
503 | @inst.options.replaceSingleQuotes = false
504 | assert.same "te'st", @inst._escapeValue("te'st")
505 |
506 | @inst.options.replaceSingleQuotes = true
507 | assert.same "te''st", @inst._escapeValue("te'st")
508 |
509 | @inst.options.singleQuoteReplacement = '--'
510 | assert.same "te--st", @inst._escapeValue("te'st")
511 |
512 | @inst.options.singleQuoteReplacement = '--'
513 | assert.same undefined, @inst._escapeValue()
514 |
515 | '_formatTableName':
516 | 'default': ->
517 | assert.same 'abc', @inst._formatTableName('abc')
518 |
519 | 'auto quote names':
520 | beforeEach: ->
521 | @inst.options.autoQuoteTableNames = true
522 |
523 | 'default quote character': ->
524 | assert.same '`abc`', @inst._formatTableName('abc')
525 |
526 | 'custom quote character': ->
527 | @inst.options.nameQuoteCharacter = '|'
528 | assert.same '|abc|', @inst._formatTableName('abc')
529 |
530 |
531 | '_formatTableAlias':
532 | 'default': ->
533 | assert.same '`abc`', @inst._formatTableAlias('abc')
534 |
535 | 'custom quote character': ->
536 | @inst.options.tableAliasQuoteCharacter = '~'
537 | assert.same '~abc~', @inst._formatTableAlias('abc')
538 |
539 | 'auto quote alias names is OFF': ->
540 | @inst.options.autoQuoteAliasNames = false
541 | assert.same 'abc', @inst._formatTableAlias('abc')
542 |
543 | 'AS is turned ON': ->
544 | @inst.options.autoQuoteAliasNames = false
545 | @inst.options.useAsForTableAliasNames = true
546 | assert.same 'AS abc', @inst._formatTableAlias('abc')
547 |
548 |
549 |
550 | '_formatFieldAlias':
551 | default: ->
552 | assert.same '"abc"', @inst._formatFieldAlias('abc')
553 |
554 | 'custom quote character': ->
555 | @inst.options.fieldAliasQuoteCharacter = '~'
556 | assert.same '~abc~', @inst._formatFieldAlias('abc')
557 |
558 | 'auto quote alias names is OFF': ->
559 | @inst.options.autoQuoteAliasNames = false
560 | assert.same 'abc', @inst._formatFieldAlias('abc')
561 |
562 |
563 | '_formatFieldName':
564 | default: ->
565 | assert.same 'abc', @inst._formatFieldName('abc')
566 |
567 | 'auto quote names':
568 | beforeEach: ->
569 | @inst.options.autoQuoteFieldNames = true
570 |
571 | 'default quote character': ->
572 | assert.same '`abc`.`def`', @inst._formatFieldName('abc.def')
573 |
574 | 'do not quote *': ->
575 | assert.same '`abc`.*', @inst._formatFieldName('abc.*')
576 |
577 | 'custom quote character': ->
578 | @inst.options.nameQuoteCharacter = '|'
579 | assert.same '|abc|.|def|', @inst._formatFieldName('abc.def')
580 |
581 | 'ignore periods when quoting': ->
582 | assert.same '`abc.def`', @inst._formatFieldName('abc.def', ignorePeriodsForFieldNameQuotes: true)
583 |
584 |
585 | '_formatCustomValue':
586 | 'not a custom value type': ->
587 | assert.same { formatted: false, value: null }, @inst._formatCustomValue(null)
588 | assert.same { formatted: false, value: 'abc' }, @inst._formatCustomValue('abc')
589 | assert.same { formatted: false, value: 12 }, @inst._formatCustomValue(12)
590 | assert.same { formatted: false, value: 1.2 }, @inst._formatCustomValue(1.2)
591 | assert.same { formatted: false, value: true }, @inst._formatCustomValue(true)
592 | assert.same { formatted: false, value: false }, @inst._formatCustomValue(false)
593 |
594 | 'custom value type':
595 | 'global': ->
596 | class MyClass
597 | myObj = new MyClass
598 |
599 | squel.registerValueHandler MyClass, () -> 3.14
600 | squel.registerValueHandler 'boolean', (v) -> 'a' + v
601 |
602 | assert.same { formatted: true, value: 3.14 }, @inst._formatCustomValue(myObj)
603 | assert.same { formatted: true, value: 'atrue' }, @inst._formatCustomValue(true)
604 |
605 | 'instance': ->
606 | class MyClass
607 | myObj = new MyClass
608 |
609 | @inst.registerValueHandler MyClass, () -> 3.14
610 | @inst.registerValueHandler 'number', (v) -> v + 'a'
611 |
612 | assert.same { formatted: true, value: 3.14}, @inst._formatCustomValue(myObj)
613 | assert.same { formatted: true, value: '5.2a'}, @inst._formatCustomValue(5.2)
614 |
615 | 'instance handler takes precedence over global': ->
616 | @inst.registerValueHandler Date, (d) -> 'hello'
617 | squel.registerValueHandler Date, (d) -> 'goodbye'
618 |
619 | assert.same { formatted: true, value: "hello"}, @inst._formatCustomValue(new Date)
620 |
621 | @inst = new @cls
622 | valueHandlers: []
623 | assert.same { formatted: true, value: "goodbye"}, @inst._formatCustomValue(new Date)
624 |
625 | 'whether to format for parameterized output': ->
626 | @inst.registerValueHandler Date, (d, asParam) ->
627 | return if asParam then 'foo' else 'bar'
628 |
629 | val = new Date()
630 |
631 | assert.same { formatted: true, value: 'foo'}, @inst._formatCustomValue(val, true)
632 | assert.same { formatted: true, value: 'bar'}, @inst._formatCustomValue(val)
633 |
634 | 'additional formatting options': ->
635 | @inst.registerValueHandler Date, (d, asParam, options) ->
636 | return if options.dontQuote then 'foo' else '"foo"'
637 |
638 | val = new Date()
639 |
640 | assert.same { formatted: true, value: 'foo'}, @inst._formatCustomValue(val, true, { dontQuote: true })
641 | assert.same { formatted: true, value: '"foo"'}, @inst._formatCustomValue(val, true, { dontQuote: false })
642 |
643 | 'return raw': ->
644 | @inst.registerValueHandler Date, (d) ->
645 | return {
646 | rawNesting: true,
647 | value: 'foo'
648 | }
649 |
650 | val = new Date()
651 |
652 | assert.same { rawNesting: true, formatted: true, value: 'foo'}, @inst._formatCustomValue(val, true)
653 |
654 | '_formatValueForParamArray':
655 | 'Query builder': ->
656 | s = squel.select().from('table')
657 | assert.same s, @inst._formatValueForParamArray(s)
658 |
659 | 'else calls _formatCustomValue': ->
660 | spy = test.mocker.stub @inst, '_formatCustomValue', (v, asParam) ->
661 | { formatted: true, value: 'test' + (if asParam then 'foo' else 'bar') }
662 |
663 | assert.same 'testfoo', @inst._formatValueForParamArray(null)
664 | assert.same 'testfoo', @inst._formatValueForParamArray('abc')
665 | assert.same 'testfoo', @inst._formatValueForParamArray(12)
666 | assert.same 'testfoo', @inst._formatValueForParamArray(1.2)
667 |
668 | opts = { dummy: true }
669 | assert.same 'testfoo', @inst._formatValueForParamArray(true, opts)
670 |
671 | assert.same 'testfoo', @inst._formatValueForParamArray(false)
672 |
673 | assert.same 6, spy.callCount
674 |
675 | assert.same spy.getCall(4).args[2], opts
676 |
677 | 'Array - recursively calls itself on each element': ->
678 | spy = test.mocker.spy @inst, '_formatValueForParamArray'
679 |
680 | v = [ squel.select().from('table'), 1.2 ]
681 |
682 | opts = { dummy: true }
683 | res = @inst._formatValueForParamArray(v, opts)
684 |
685 | assert.same v, res
686 |
687 | assert.same 3, spy.callCount
688 | assert.ok spy.calledWith v[0]
689 | assert.ok spy.calledWith v[1]
690 |
691 | assert.same spy.getCall(1).args[1], opts
692 |
693 |
694 | '_formatValueForQueryString':
695 | 'null': ->
696 | assert.same 'NULL', @inst._formatValueForQueryString(null)
697 |
698 | 'boolean': ->
699 | assert.same 'TRUE', @inst._formatValueForQueryString(true)
700 | assert.same 'FALSE', @inst._formatValueForQueryString(false)
701 |
702 | 'integer': ->
703 | assert.same 12, @inst._formatValueForQueryString(12)
704 |
705 | 'float': ->
706 | assert.same 1.2, @inst._formatValueForQueryString(1.2)
707 |
708 | 'string':
709 | 'have string formatter function': ->
710 | @inst.options.stringFormatter = (str) -> "N(#{str})"
711 |
712 | assert.same "N(test)", @inst._formatValueForQueryString('test')
713 |
714 | 'default': ->
715 | escapedValue = undefined
716 | test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
717 |
718 | assert.same "'test'", @inst._formatValueForQueryString('test')
719 |
720 | assert.ok @inst._escapeValue.calledWithExactly('test')
721 | escapedValue = 'blah'
722 | assert.same "'blah'", @inst._formatValueForQueryString('test')
723 |
724 | 'dont quote': ->
725 | escapedValue = undefined
726 | test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
727 |
728 | assert.same "test", @inst._formatValueForQueryString('test', dontQuote: true )
729 |
730 | assert.ok @inst._escapeValue.notCalled
731 |
732 | 'Array - recursively calls itself on each element': ->
733 | spy = test.mocker.spy @inst, '_formatValueForQueryString'
734 |
735 | expected = "('test', 123, TRUE, 1.2, NULL)"
736 | assert.same expected, @inst._formatValueForQueryString([ 'test', 123, true, 1.2, null ])
737 |
738 | assert.same 6, spy.callCount
739 | assert.ok spy.calledWith 'test'
740 | assert.ok spy.calledWith 123
741 | assert.ok spy.calledWith true
742 | assert.ok spy.calledWith 1.2
743 | assert.ok spy.calledWith null
744 |
745 | 'BaseBuilder': ->
746 | spy = test.mocker.stub @inst, '_applyNestingFormatting', (v) => "{{#{v}}}"
747 | s = squel.select().from('table')
748 | assert.same '{{SELECT * FROM table}}', @inst._formatValueForQueryString(s)
749 |
750 | 'checks to see if it is custom value type first': ->
751 | test.mocker.stub @inst, '_formatCustomValue', (val, asParam) ->
752 | { formatted: true, value: 12 + (if asParam then 25 else 65) }
753 | test.mocker.stub @inst, '_applyNestingFormatting', (v) -> "{#{v}}"
754 | assert.same '{77}', @inst._formatValueForQueryString(123)
755 |
756 | '#292 - custom value type specifies raw nesting': ->
757 | test.mocker.stub @inst, '_formatCustomValue', (val, asParam) ->
758 | { rawNesting: true, formatted: true, value: 12 }
759 | test.mocker.stub @inst, '_applyNestingFormatting', (v) -> "{#{v}}"
760 | assert.same 12, @inst._formatValueForQueryString(123)
761 |
762 |
763 | '_applyNestingFormatting':
764 | default: ->
765 | assert.same '(77)', @inst._applyNestingFormatting('77')
766 | assert.same '((77)', @inst._applyNestingFormatting('(77')
767 | assert.same '(77))', @inst._applyNestingFormatting('77)')
768 | assert.same '(77)', @inst._applyNestingFormatting('(77)')
769 | 'no nesting': ->
770 | assert.same '77', @inst._applyNestingFormatting('77', false)
771 | 'rawNesting turned on': ->
772 | @inst = new @cls({ rawNesting: true })
773 | assert.same '77', @inst._applyNestingFormatting('77')
774 |
775 |
776 | '_buildString':
777 | 'empty': ->
778 | assert.same @inst._buildString('', []), {
779 | text: '',
780 | values: [],
781 | }
782 | 'no params':
783 | 'non-parameterized': ->
784 | assert.same @inst._buildString('abc = 3', []), {
785 | text: 'abc = 3',
786 | values: []
787 | }
788 | 'parameterized': ->
789 | assert.same @inst._buildString('abc = 3', [], { buildParameterized: true }), {
790 | text: 'abc = 3',
791 | values: []
792 | }
793 | 'non-array':
794 | 'non-parameterized': ->
795 | assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null]), {
796 | text: 'a = 2 \'abc\' FALSE NULL',
797 | values: []
798 | }
799 | 'parameterized': ->
800 | assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null], { buildParameterized: true }), {
801 | text: 'a = ? ? ? ?',
802 | values: [2, 'abc', false, null]
803 | }
804 | 'array': ->
805 | 'non-parameterized': ->
806 | assert.same @inst._buildString('a = ?', [[1,2,3]]), {
807 | text: 'a = (1, 2, 3)',
808 | values: [],
809 | }
810 | 'parameterized': ->
811 | assert.same @inst._buildString('a = ?', [[1,2,3]], { buildParameterized: true }), {
812 | text: 'a = (?, ?, ?)',
813 | values: [1, 2, 3]
814 | }
815 | 'nested builder': ->
816 | beforeEach:
817 | @s = squel.select().from('master').where('b = ?', 5)
818 | 'non-parameterized': ->
819 | assert.same @inst._buildString('a = ?', [@s]), {
820 | text: 'a = (SELECT * FROM master WHERE (b = 5))',
821 | values: []
822 | }
823 | 'parameterized': ->
824 | assert.same @inst._buildString('a = ?', [@s], { buildParameterized: true }), {
825 | text: 'a = (SELECT * FROM master WHERE (b = ?))',
826 | values: [5]
827 | }
828 | 'return nested output':
829 | 'non-parameterized': ->
830 | assert.same @inst._buildString('a = ?', [3], { nested: true }), {
831 | text: '(a = 3)',
832 | values: []
833 | }
834 | 'parameterized': ->
835 | assert.same @inst._buildString('a = ?', [3], { buildParameterized: true, nested: true }), {
836 | text: '(a = ?)',
837 | values: [3]
838 | }
839 | 'string formatting options': ->
840 | options =
841 | formattingOptions:
842 | dontQuote: true
843 |
844 | assert.same @inst._buildString('a = ?', ['NOW()'], options), {
845 | text: 'a = NOW()',
846 | values: []
847 | }
848 | 'passes formatting options even when doing parameterized query': ->
849 | spy = test.mocker.spy @inst, '_formatValueForParamArray'
850 |
851 | options =
852 | buildParameterized: true
853 | formattingOptions:
854 | dontQuote: true
855 |
856 | @inst._buildString('a = ?', [3], options)
857 |
858 | assert.same spy.getCall(0).args[1], options.formattingOptions
859 | 'custom parameter character':
860 | beforeEach: ->
861 | @inst.options.parameterCharacter = '@@'
862 |
863 | 'non-parameterized': ->
864 | assert.same @inst._buildString('a = @@', [[1,2,3]]), {
865 | text: 'a = (1, 2, 3)',
866 | values: [],
867 | }
868 | 'parameterized': ->
869 | assert.same @inst._buildString('a = @@', [[1,2,3]], { buildParameterized: true }), {
870 | text: 'a = (@@, @@, @@)',
871 | values: [1,2,3],
872 | }
873 |
874 | '_buildManyStrings':
875 | 'empty': ->
876 | assert.same @inst._buildManyStrings([], []), {
877 | text: '',
878 | values: [],
879 | }
880 | 'simple':
881 | beforeEach: ->
882 | @strings = [
883 | 'a = ?',
884 | 'b IN ? AND c = ?'
885 | ]
886 |
887 | @values = [
888 | ['elephant'],
889 | [[1,2,3], 4]
890 | ]
891 |
892 | 'non-parameterized': ->
893 | assert.same @inst._buildManyStrings(@strings, @values), {
894 | text: 'a = \'elephant\' b IN (1, 2, 3) AND c = 4',
895 | values: [],
896 | }
897 | 'parameterized': ->
898 | assert.same @inst._buildManyStrings(@strings, @values, { buildParameterized: true }), {
899 | text: 'a = ? b IN (?, ?, ?) AND c = ?',
900 | values: ['elephant', 1, 2, 3, 4],
901 | }
902 |
903 | 'return nested':
904 | 'non-parameterized': ->
905 | assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { nested: true }), {
906 | text: '(a = 1 b = 2)',
907 | values: [],
908 | }
909 | 'parameterized': ->
910 | assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true, nested: true }), {
911 | text: '(a = ? b = ?)',
912 | values: [1, 2],
913 | }
914 |
915 | 'custom separator':
916 | beforeEach: ->
917 | @inst.options.separator = '|'
918 | 'non-parameterized': ->
919 | assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]]), {
920 | text: 'a = 1|b = 2',
921 | values: [],
922 | }
923 | 'parameterized': ->
924 | assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true}), {
925 | text: 'a = ?|b = ?',
926 | values: [1, 2],
927 | }
928 |
929 | 'toParam': ->
930 | spy = test.mocker.stub @inst, '_toParamString', ->
931 | {
932 | text: 'dummy'
933 | values: [1]
934 | }
935 |
936 | options = {test: 2}
937 | assert.same @inst.toParam(options), {
938 | text: 'dummy'
939 | values: [1]
940 | }
941 |
942 | spy.should.have.been.calledOnce
943 | assert.same spy.getCall(0).args[0].test, 2
944 | assert.same spy.getCall(0).args[0].buildParameterized, true
945 |
946 | 'toString': ->
947 | spy = test.mocker.stub @inst, '_toParamString', ->
948 | {
949 | text: 'dummy'
950 | values: [1]
951 | }
952 |
953 | options = {test: 2}
954 | assert.same @inst.toString(options), 'dummy'
955 |
956 | spy.should.have.been.calledOnce
957 | assert.same spy.getCall(0).args[0], options
958 |
959 |
960 | test['QueryBuilder base class'] =
961 | beforeEach: ->
962 | @cls = squel.cls.QueryBuilder
963 | @inst = new @cls
964 |
965 | 'instanceof base builder': ->
966 | assert.instanceOf @inst, squel.cls.BaseBuilder
967 |
968 | 'constructor':
969 | 'default options': ->
970 | assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
971 |
972 | 'overridden options': ->
973 | @inst = new @cls
974 | dummy1: 'str'
975 | dummy2: 12.3
976 | usingValuePlaceholders: true
977 | dummy3: true
978 |
979 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
980 | dummy1: 'str'
981 | dummy2: 12.3
982 | usingValuePlaceholders: true
983 | dummy3: true
984 |
985 | assert.same expectedOptions, @inst.options
986 |
987 | 'default blocks - none': ->
988 | assert.same [], @inst.blocks
989 |
990 | 'blocks passed in':
991 | 'exposes block methods': ->
992 | limitExposedMethodsSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'exposedMethods');
993 | distinctExposedMethodsSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'exposedMethods');
994 | limitSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'limit')
995 | distinctSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'distinct')
996 |
997 | blocks = [
998 | new squel.cls.LimitBlock(),
999 | new squel.cls.DistinctBlock()
1000 | ]
1001 |
1002 | @inst = new @cls({}, blocks)
1003 |
1004 | assert.ok limitExposedMethodsSpy.calledOnce
1005 | assert.ok distinctExposedMethodsSpy.calledOnce
1006 |
1007 | assert.typeOf @inst.distinct, 'function'
1008 | assert.typeOf @inst.limit, 'function'
1009 |
1010 | assert.same @inst, @inst.limit(2)
1011 | assert.ok limitSpy.calledOnce
1012 | assert.ok limitSpy.calledOn(blocks[0])
1013 |
1014 | assert.same @inst, @inst.distinct()
1015 | assert.ok distinctSpy.calledOnce
1016 | assert.ok distinctSpy.calledOn(blocks[1])
1017 |
1018 |
1019 | 'cannot expose the same method twice': ->
1020 | blocks = [
1021 | new squel.cls.DistinctBlock(),
1022 | new squel.cls.DistinctBlock()
1023 | ]
1024 |
1025 | try
1026 | @inst = new @cls({}, blocks)
1027 | throw new Error 'should not reach here'
1028 | catch err
1029 | assert.same 'Error: Builder already has a builder method called: distinct', err.toString()
1030 |
1031 |
1032 | 'updateOptions()':
1033 | 'updates query builder options': ->
1034 | oldOptions = _.extend({}, @inst.options)
1035 |
1036 | @inst.updateOptions
1037 | updated: false
1038 |
1039 | expected = _.extend oldOptions,
1040 | updated: false
1041 |
1042 | assert.same expected, @inst.options
1043 |
1044 | 'updates building block options': ->
1045 | @inst.blocks = [
1046 | new squel.cls.Block()
1047 | ]
1048 | oldOptions = _.extend({}, @inst.blocks[0].options)
1049 |
1050 | @inst.updateOptions
1051 | updated: false
1052 |
1053 | expected = _.extend oldOptions,
1054 | updated: false
1055 |
1056 | assert.same expected, @inst.blocks[0].options
1057 |
1058 |
1059 |
1060 | 'toString()':
1061 | 'returns empty if no blocks': ->
1062 | assert.same '', @inst.toString()
1063 |
1064 | 'skips empty block strings': ->
1065 | @inst.blocks = [
1066 | new squel.cls.StringBlock({}, ''),
1067 | ]
1068 |
1069 | assert.same '', @inst.toString()
1070 |
1071 | 'returns final query string': ->
1072 | i = 1
1073 | toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', ->
1074 | {
1075 | text: "ret#{++i}"
1076 | values: []
1077 | }
1078 |
1079 | @inst.blocks = [
1080 | new squel.cls.StringBlock({}, 'STR1'),
1081 | new squel.cls.StringBlock({}, 'STR2'),
1082 | new squel.cls.StringBlock({}, 'STR3')
1083 | ]
1084 |
1085 | assert.same 'ret2 ret3 ret4', @inst.toString()
1086 |
1087 | assert.ok toStringSpy.calledThrice
1088 | assert.ok toStringSpy.calledOn(@inst.blocks[0])
1089 | assert.ok toStringSpy.calledOn(@inst.blocks[1])
1090 | assert.ok toStringSpy.calledOn(@inst.blocks[2])
1091 |
1092 |
1093 | 'toParam()':
1094 | 'returns empty if no blocks': ->
1095 | assert.same { text: '', values: [] }, @inst.toParam()
1096 |
1097 | 'skips empty block strings': ->
1098 | @inst.blocks = [
1099 | new squel.cls.StringBlock({}, ''),
1100 | ]
1101 |
1102 | assert.same { text: '', values: [] }, @inst.toParam()
1103 |
1104 | 'returns final query string': ->
1105 | @inst.blocks = [
1106 | new squel.cls.StringBlock({}, 'STR1'),
1107 | new squel.cls.StringBlock({}, 'STR2'),
1108 | new squel.cls.StringBlock({}, 'STR3')
1109 | ]
1110 |
1111 | i = 1
1112 | toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', ->
1113 | {
1114 | text: "ret#{++i}"
1115 | values: []
1116 | }
1117 |
1118 | assert.same { text: 'ret2 ret3 ret4', values: [] }, @inst.toParam()
1119 |
1120 | assert.ok toStringSpy.calledThrice
1121 | assert.ok toStringSpy.calledOn(@inst.blocks[0])
1122 | assert.ok toStringSpy.calledOn(@inst.blocks[1])
1123 | assert.ok toStringSpy.calledOn(@inst.blocks[2])
1124 |
1125 | 'returns query with unnumbered parameters': ->
1126 | @inst.blocks = [
1127 | new squel.cls.WhereBlock({}),
1128 | ]
1129 |
1130 | @inst.blocks[0]._toParamString = test.mocker.spy -> {
1131 | text: 'a = ? AND b in (?, ?)',
1132 | values: [1, 2, 3]
1133 | }
1134 |
1135 | assert.same { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]}, @inst.toParam()
1136 |
1137 | 'returns query with numbered parameters': ->
1138 | @inst = new @cls
1139 | numberedParameters: true
1140 |
1141 | @inst.blocks = [
1142 | new squel.cls.WhereBlock({}),
1143 | ]
1144 |
1145 | test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> {
1146 | text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
1147 | }
1148 |
1149 | assert.same @inst.toParam(), { text: 'a = $1 AND b in ($2, $3)', values: [1, 2, 3]}
1150 |
1151 | 'returns query with numbered parameters and custom prefix': ->
1152 | @inst = new @cls
1153 | numberedParameters: true
1154 | numberedParametersPrefix: '&%'
1155 |
1156 | @inst.blocks = [
1157 | new squel.cls.WhereBlock({}),
1158 | ]
1159 |
1160 | test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> {
1161 | text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
1162 | }
1163 |
1164 | assert.same @inst.toParam(), { text: 'a = &%1 AND b in (&%2, &%3)', values: [1, 2, 3]}
1165 |
1166 |
1167 | 'cloning':
1168 | 'blocks get cloned properly': ->
1169 | blockCloneSpy = test.mocker.spy(squel.cls.StringBlock.prototype, 'clone')
1170 |
1171 | @inst.blocks = [
1172 | new squel.cls.StringBlock({}, 'TEST')
1173 | ]
1174 |
1175 | newinst = @inst.clone()
1176 | @inst.blocks[0].str = 'TEST2'
1177 |
1178 | assert.same 'TEST', newinst.blocks[0].toString()
1179 |
1180 | 'registerValueHandler':
1181 | 'beforEach': ->
1182 | @originalHandlers = [].concat(squel.cls.globalValueHandlers)
1183 | 'afterEach': ->
1184 | squel.cls.globalValueHandlers = @originalHandlers
1185 |
1186 | 'calls through to base class method': ->
1187 | baseBuilderSpy = test.mocker.spy(squel.cls.BaseBuilder.prototype, 'registerValueHandler')
1188 |
1189 | handler = -> 'test'
1190 | @inst.registerValueHandler(Date, handler)
1191 | @inst.registerValueHandler('number', handler)
1192 |
1193 | assert.ok baseBuilderSpy.calledTwice
1194 | assert.ok baseBuilderSpy.calledOn(@inst)
1195 |
1196 | 'returns instance for chainability': ->
1197 | handler = -> 'test'
1198 | assert.same @inst, @inst.registerValueHandler(Date, handler)
1199 |
1200 | 'calls through to blocks': ->
1201 | @inst.blocks = [
1202 | new squel.cls.StringBlock({}, ''),
1203 | ]
1204 |
1205 | baseBuilderSpy = test.mocker.spy(@inst.blocks[0], 'registerValueHandler')
1206 |
1207 | handler = -> 'test'
1208 | @inst.registerValueHandler(Date, handler)
1209 |
1210 | assert.ok baseBuilderSpy.calledOnce
1211 | assert.ok baseBuilderSpy.calledOn(@inst.blocks[0])
1212 |
1213 | 'get block':
1214 | 'valid': ->
1215 | block = new squel.cls.FunctionBlock()
1216 | @inst.blocks.push(block)
1217 | assert.same block, @inst.getBlock(squel.cls.FunctionBlock)
1218 | 'invalid': ->
1219 | assert.same undefined, @inst.getBlock(squel.cls.FunctionBlock)
1220 |
1221 |
1222 |
1223 |
1224 |
1225 | module?.exports[require('path').basename(__filename)] = test
1226 |
--------------------------------------------------------------------------------
/test/case.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 | test['Case expression builder base class'] =
33 | beforeEach: ->
34 | @func = squel.case
35 | @inst = @func()
36 | # manual return,
37 | # otherwise @inst will be treated a promise because it has a then() method
38 | return
39 |
40 | 'extends BaseBuilder': ->
41 | assert.ok (@inst instanceof squel.cls.BaseBuilder)
42 |
43 | 'toString() returns NULL': ->
44 | assert.same "NULL", @inst.toString()
45 |
46 | 'options':
47 | 'default options': ->
48 | assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
49 | 'custom options': ->
50 | e = @func({
51 | separator: ',asdf'
52 | })
53 |
54 | expected = _.extend({}, squel.cls.DefaultQueryBuilderOptions, {
55 | separator: ',asdf'
56 | })
57 |
58 | assert.same expected, e.options
59 |
60 | 'build expression':
61 | '>> when().then()':
62 | beforeEach: ->
63 | @inst.when('?', 'foo').then('bar')
64 | # manual return,
65 | # otherwise @inst will be treated a promise because it has a then() method
66 | return
67 |
68 | toString: ->
69 | assert.same @inst.toString(), 'CASE WHEN (\'foo\') THEN \'bar\' ELSE NULL END'
70 | toParam: ->
71 | assert.same @inst.toParam(), {
72 | text: 'CASE WHEN (?) THEN \'bar\' ELSE NULL END',
73 | values: ['foo']
74 | }
75 |
76 | '>> when().then().else()':
77 | beforeEach: ->
78 | @inst.when('?', 'foo').then('bar').else('foobar')
79 | # manual return,
80 | # otherwise @inst will be treated a promise because it has a then() method
81 | return
82 | toString: ->
83 | assert.same @inst.toString(), 'CASE WHEN (\'foo\') THEN \'bar\' ELSE \'foobar\' END'
84 | toParam: ->
85 | assert.same @inst.toParam(), {
86 | text: 'CASE WHEN (?) THEN \'bar\' ELSE \'foobar\' END',
87 | values: ['foo']
88 | }
89 |
90 | 'field case':
91 | beforeEach: ->
92 | @inst = @func('name').when('?', 'foo').then('bar')
93 | # manual return,
94 | # otherwise @inst will be treated a promise because it has a then() method
95 | return
96 | toString: ->
97 | assert.same @inst.toString(), 'CASE name WHEN (\'foo\') THEN \'bar\' ELSE NULL END'
98 | toParam: ->
99 | assert.same @inst.toParam(), {
100 | text: 'CASE name WHEN (?) THEN \'bar\' ELSE NULL END',
101 | values: ['foo']
102 | }
103 |
104 |
105 | module?.exports[require('path').basename(__filename)] = test
106 |
--------------------------------------------------------------------------------
/test/custom.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['Custom queries'] =
34 | 'custom query': ->
35 | class CommandBlock extends squel.cls.Block
36 | command: (command, arg) ->
37 | @_command = command
38 | @_arg = arg
39 | compress: (level) ->
40 | @command('compress', level)
41 | _toParamString: (options) ->
42 | totalStr = @_command.toUpperCase()
43 | totalValues = []
44 |
45 | if not options.buildParameterized
46 | totalStr += " #{@_arg}"
47 | else
48 | totalStr += " ?"
49 | totalValues.push(@_arg)
50 |
51 | {
52 | text: totalStr,
53 | values: totalValues,
54 | }
55 |
56 |
57 | class PragmaQuery extends squel.cls.QueryBuilder
58 | constructor: (options) ->
59 | blocks = [
60 | new squel.cls.StringBlock(options, 'PRAGMA'),
61 | new CommandBlock(options),
62 | ]
63 |
64 | super options, blocks
65 |
66 | # squel method
67 | squel.pragma = (options) -> new PragmaQuery(options)
68 |
69 | qry = squel.pragma().compress(9)
70 |
71 | assert.same qry.toString(), 'PRAGMA COMPRESS 9'
72 | assert.same qry.toParam() , {
73 | text: 'PRAGMA COMPRESS ?',
74 | values: [9],
75 | }
76 |
77 |
78 |
79 | module?.exports[require('path').basename(__filename)] = test
80 |
--------------------------------------------------------------------------------
/test/delete.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['DELETE builder'] =
34 | beforeEach: ->
35 | @func = squel.delete
36 | @inst = @func()
37 |
38 | 'instanceof QueryBuilder': ->
39 | assert.instanceOf @inst, squel.cls.QueryBuilder
40 |
41 | 'constructor':
42 | 'override options': ->
43 | @inst = squel.update
44 | usingValuePlaceholders: true
45 | dummy: true
46 |
47 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
48 | usingValuePlaceholders: true
49 | dummy: true
50 |
51 | for block in @inst.blocks
52 | assert.same _.pick(block.options, _.keys(expectedOptions)), expectedOptions
53 |
54 | 'override blocks': ->
55 | block = new squel.cls.StringBlock('SELECT')
56 | @inst = @func {}, [block]
57 | assert.same [block], @inst.blocks
58 |
59 | 'build query':
60 | 'no need to call from()': ->
61 | @inst.toString()
62 |
63 | '>> from(table)':
64 | beforeEach: -> @inst.from('table')
65 | toString: ->
66 | assert.same @inst.toString(), 'DELETE FROM table'
67 |
68 | '>> table(table2, t2)':
69 | beforeEach: -> @inst.from('table2', 't2')
70 | toString: ->
71 | assert.same @inst.toString(), 'DELETE FROM table2 `t2`'
72 |
73 | '>> where(a = 1)':
74 | beforeEach: -> @inst.where('a = 1')
75 | toString: ->
76 | assert.same @inst.toString(), 'DELETE FROM table2 `t2` WHERE (a = 1)'
77 |
78 | '>> join(other_table)':
79 | beforeEach: -> @inst.join('other_table', 'o', 'o.id = t2.id')
80 | toString: ->
81 | assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1)'
82 |
83 | '>> order(a, true)':
84 | beforeEach: -> @inst.order('a', true)
85 | toString: ->
86 | assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1) ORDER BY a ASC'
87 |
88 | '>> limit(2)':
89 | beforeEach: -> @inst.limit(2)
90 | toString: ->
91 | assert.same @inst.toString(), 'DELETE FROM table2 `t2` INNER JOIN other_table `o` ON (o.id = t2.id) WHERE (a = 1) ORDER BY a ASC LIMIT 2'
92 |
93 |
94 | '>> target(table1).from(table1).left_join(table2, null, "table1.a = table2.b")':
95 | beforeEach: ->
96 | @inst.target('table1').from('table1').left_join('table2', null, 'table1.a = table2.b').where('c = ?', 3)
97 | toString: ->
98 | assert.same @inst.toString(),
99 | 'DELETE table1 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
100 | toParam: ->
101 | assert.same @inst.toParam(),
102 | {
103 | text: 'DELETE table1 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
104 | values: [3]
105 | }
106 |
107 | '>> target(table2)':
108 | beforeEach: ->
109 | @inst.target('table2')
110 | toString: ->
111 | assert.same @inst.toString(),
112 | 'DELETE table1, table2 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
113 | toParam: ->
114 | assert.same @inst.toParam(),
115 | {
116 | text: 'DELETE table1, table2 FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
117 | values: [3]
118 | }
119 |
120 | '>> from(table1).left_join(table2, null, "table1.a = table2.b")':
121 | beforeEach: ->
122 | @inst.from('table1').left_join('table2', null, 'table1.a = table2.b').where('c = ?', 3)
123 | toString: ->
124 | assert.same @inst.toString(),
125 | 'DELETE FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = 3)'
126 | toParam: ->
127 | assert.same @inst.toParam(),
128 | {
129 | text: 'DELETE FROM table1 LEFT JOIN table2 ON (table1.a = table2.b) WHERE (c = ?)',
130 | values: [3]
131 | }
132 |
133 | 'cloning': ->
134 | newinst = @inst.from('students').limit(10).clone()
135 | newinst.limit(20)
136 |
137 | assert.same 'DELETE FROM students LIMIT 10', @inst.toString()
138 | assert.same 'DELETE FROM students LIMIT 20', newinst.toString()
139 |
140 |
141 |
142 | module?.exports[require('path').basename(__filename)] = test
143 |
--------------------------------------------------------------------------------
/test/expressions.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['Expression builder base class'] =
34 | beforeEach: ->
35 | @inst = squel.expr()
36 |
37 | 'extends BaseBuilder': ->
38 | assert.ok (@inst instanceof squel.cls.BaseBuilder)
39 |
40 | 'toString() returns empty': ->
41 | assert.same "", @inst.toString()
42 |
43 | 'options':
44 | 'default options': ->
45 | assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
46 | 'custom options': ->
47 | e = squel.expr({
48 | separator: ',asdf'
49 | })
50 |
51 | expected = _.extend({}, squel.cls.DefaultQueryBuilderOptions, {
52 | separator: ',asdf'
53 | })
54 |
55 | assert.same expected, e.options
56 |
57 |
58 | 'and()':
59 | 'without an argument throws an error': ->
60 | assert.throws (=> @inst.and()), 'expression must be a string or builder instance'
61 | 'with an array throws an error': ->
62 | assert.throws (=> @inst.and([1])), 'expression must be a string or builder instance'
63 | 'with an object throws an error': ->
64 | assert.throws (=> @inst.and(new Object)), 'expression must be a string or builder instance'
65 | 'with a function throws an error': ->
66 | assert.throws (=> @inst.and(-> 1)), 'expression must be a string or builder instance'
67 | 'with an Expression returns object instance': ->
68 | assert.same @inst, @inst.and(squel.expr())
69 | 'with a builder returns object instance': ->
70 | assert.same @inst, @inst.and(squel.str())
71 | 'with a string returns object instance': ->
72 | assert.same @inst, @inst.and('bla')
73 |
74 |
75 | 'or()':
76 | 'without an argument throws an error': ->
77 | assert.throws (=> @inst.or()), 'expression must be a string or builder instance'
78 | 'with an array throws an error': ->
79 | assert.throws (=> @inst.or([1])), 'expression must be a string or builder instance'
80 | 'with an object throws an error': ->
81 | assert.throws (=> @inst.or(new Object)), 'expression must be a string or builder instance'
82 | 'with a function throws an error': ->
83 | assert.throws (=> @inst.or(-> 1)), 'expression must be a string or builder instance'
84 | 'with an Expression returns object instance': ->
85 | assert.same @inst, @inst.or(squel.expr())
86 | 'with a builder returns object instance': ->
87 | assert.same @inst, @inst.or(squel.str())
88 | 'with a string returns object instance': ->
89 | assert.same @inst, @inst.or('bla')
90 |
91 |
92 | 'and("test = 3")':
93 | beforeEach: ->
94 | @inst.and("test = 3")
95 |
96 | '>> toString()': ->
97 | assert.same @inst.toString(), 'test = 3'
98 |
99 | '>> toParam()': ->
100 | assert.same @inst.toParam(), {
101 | text: 'test = 3',
102 | values: []
103 | }
104 |
105 | '>> and("flight = \'4\'")':
106 | beforeEach: ->
107 | @inst.and("flight = '4'")
108 |
109 | '>> toString()': ->
110 | assert.same @inst.toString(), "test = 3 AND flight = '4'"
111 |
112 | '>> toParam()': ->
113 | assert.same @inst.toParam(), {
114 | text: "test = 3 AND flight = '4'",
115 | values: []
116 | }
117 |
118 | '>> or("dummy IN (1,2,3)")':
119 | beforeEach: ->
120 | @inst.or("dummy IN (1,2,3)")
121 |
122 | '>> toString()': ->
123 | assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (1,2,3)"
124 |
125 | '>> toParam()': ->
126 | assert.same @inst.toParam(), {
127 | text: "test = 3 AND flight = '4' OR dummy IN (1,2,3)",
128 | values: [],
129 | }
130 |
131 |
132 | 'and("test = ?", null)':
133 | beforeEach: ->
134 | @inst.and("test = ?", null)
135 |
136 | '>> toString()': ->
137 | assert.same @inst.toString(), 'test = NULL'
138 |
139 | '>> toParam()': ->
140 | assert.same @inst.toParam(), {
141 | text: 'test = ?'
142 | values: [null]
143 | }
144 |
145 | 'and("test = ?", 3)':
146 | beforeEach: ->
147 | @inst.and("test = ?", 3)
148 |
149 | '>> toString()': ->
150 | assert.same @inst.toString(), 'test = 3'
151 |
152 | '>> toParam()': ->
153 | assert.same @inst.toParam(), {
154 | text: 'test = ?'
155 | values: [3]
156 | }
157 |
158 | '>> and("flight = ?", "4")':
159 | beforeEach: ->
160 | @inst.and("flight = ?", '4')
161 |
162 | '>> toString()': ->
163 | assert.same @inst.toString(), "test = 3 AND flight = '4'"
164 |
165 | '>> toParam()': ->
166 | assert.same @inst.toParam(), {
167 | text: "test = ? AND flight = ?"
168 | values: [3, '4']
169 | }
170 |
171 | '>> or("dummy IN ?", [false, 2, null, "str"])':
172 | beforeEach: ->
173 | @inst.or("dummy IN ?", [false,2,null,"str"])
174 |
175 | '>> toString()': ->
176 | assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (FALSE, 2, NULL, 'str')"
177 |
178 | '>> toParam()': ->
179 | assert.same @inst.toParam(), {
180 | text: "test = ? AND flight = ? OR dummy IN (?, ?, ?, ?)"
181 | values: [3, '4', false, 2, null, 'str']
182 | }
183 |
184 |
185 | 'or("test = 3")':
186 | beforeEach: ->
187 | @inst.or("test = 3")
188 |
189 | '>> toString()': ->
190 | assert.same @inst.toString(), 'test = 3'
191 |
192 | '>> toParam()': ->
193 | assert.same @inst.toParam(), {
194 | text: 'test = 3',
195 | values: [],
196 | }
197 |
198 | '>> or("flight = \'4\'")':
199 | beforeEach: ->
200 | @inst.or("flight = '4'")
201 |
202 | '>> toString()': ->
203 | assert.same @inst.toString(), "test = 3 OR flight = '4'"
204 |
205 | '>> toString()': ->
206 | assert.same @inst.toParam(), {
207 | text: "test = 3 OR flight = '4'",
208 | values: [],
209 | }
210 |
211 | '>> and("dummy IN (1,2,3)")':
212 | beforeEach: ->
213 | @inst.and("dummy IN (1,2,3)")
214 |
215 | '>> toString()': ->
216 | assert.same @inst.toString(), "test = 3 OR flight = '4' AND dummy IN (1,2,3)"
217 |
218 | '>> toParam()': ->
219 | assert.same @inst.toParam(), {
220 | text: "test = 3 OR flight = '4' AND dummy IN (1,2,3)",
221 | values: [],
222 | }
223 |
224 |
225 | 'or("test = ?", 3)':
226 | beforeEach: ->
227 | @inst.or("test = ?", 3)
228 |
229 | '>> toString()': ->
230 | assert.same @inst.toString(), 'test = 3'
231 |
232 | '>> toParam()': ->
233 | assert.same @inst.toParam(), {
234 | text: 'test = ?'
235 | values: [3]
236 | }
237 |
238 | '>> or("flight = ?", "4")':
239 | beforeEach: ->
240 | @inst.or("flight = ?", "4")
241 |
242 | '>> toString()': ->
243 | assert.same @inst.toString(), "test = 3 OR flight = '4'"
244 |
245 | '>> toParam()': ->
246 | assert.same @inst.toParam(), {
247 | text: "test = ? OR flight = ?"
248 | values: [3, '4']
249 | }
250 |
251 | '>> and("dummy IN ?", [false, 2, null, "str"])':
252 | beforeEach: ->
253 | @inst.and("dummy IN ?", [false, 2, null, "str"])
254 |
255 | '>> toString()': ->
256 | assert.same @inst.toString(), "test = 3 OR flight = '4' AND dummy IN (FALSE, 2, NULL, 'str')"
257 |
258 | '>> toParam()': ->
259 | assert.same @inst.toParam(), {
260 | text: "test = ? OR flight = ? AND dummy IN (?, ?, ?, ?)"
261 | values: [3, '4', false, 2, null, 'str']
262 | }
263 |
264 |
265 | 'or("test = ?", 4)':
266 | beforeEach: -> @inst.or("test = ?", 4)
267 |
268 | '>> and(expr().or("inner = ?", 1))':
269 | beforeEach: -> @inst.and( squel.expr().or('inner = ?', 1) )
270 |
271 | '>> toString()': ->
272 | assert.same @inst.toString(), "test = 4 AND (inner = 1)"
273 |
274 | '>> toParam()': ->
275 | assert.same @inst.toParam(), {
276 | text: "test = ? AND (inner = ?)"
277 | values: [4, 1]
278 | }
279 |
280 | '>> and(expr().or("inner = ?", 1).or(expr().and("another = ?", 34)))':
281 | beforeEach: ->
282 | @inst.and( squel.expr().or('inner = ?', 1).or(squel.expr().and("another = ?", 34)) )
283 |
284 | '>> toString()': ->
285 | assert.same @inst.toString(), "test = 4 AND (inner = 1 OR (another = 34))"
286 |
287 | '>> toParam()': ->
288 | assert.same @inst.toParam(), {
289 | text: "test = ? AND (inner = ? OR (another = ?))"
290 | values: [4, 1, 34]
291 | }
292 |
293 |
294 | 'custom parameter character: @@':
295 | beforeEach: ->
296 | @inst.options.parameterCharacter = '@@'
297 |
298 | 'and("test = @@", 3).and("flight = @@", "4").or("dummy IN @@", [false, 2, null, "str"])':
299 | beforeEach: ->
300 | @inst
301 | .and("test = @@", 3)
302 | .and("flight = @@", '4')
303 | .or("dummy IN @@", [false,2,null,"str"])
304 |
305 | '>> toString()': ->
306 | assert.same @inst.toString(), "test = 3 AND flight = '4' OR dummy IN (FALSE, 2, NULL, 'str')"
307 |
308 | '>> toParam()': ->
309 | assert.same @inst.toParam(), {
310 | text: "test = @@ AND flight = @@ OR dummy IN (@@, @@, @@, @@)"
311 | values: [3, '4', false, 2, null, 'str']
312 | }
313 |
314 |
315 | 'cloning': ->
316 | newinst = @inst.or("test = 4").or("inner = 1").or("inner = 2").clone()
317 | newinst.or('inner = 3')
318 |
319 | assert.same @inst.toString(), 'test = 4 OR inner = 1 OR inner = 2'
320 | assert.same newinst.toString(), 'test = 4 OR inner = 1 OR inner = 2 OR inner = 3'
321 |
322 |
323 | 'custom array prototype methods (Issue #210)': ->
324 | Array.prototype.last = () ->
325 | this[this.length - 1]
326 |
327 | @inst.or("foo = ?", "bar")
328 |
329 | delete Array.prototype.last
330 |
331 |
332 | 'any type of builder':
333 | beforeEach: ->
334 | @inst.or('b = ?', 5).or(squel.select().from('blah').where('a = ?', 9))
335 | toString: ->
336 | assert.same @inst.toString(), "b = 5 OR (SELECT * FROM blah WHERE (a = 9))"
337 | toParam: ->
338 | assert.same @inst.toParam(), {
339 | text: "b = ? OR (SELECT * FROM blah WHERE (a = ?))"
340 | values: [5, 9]
341 | }
342 |
343 | '#286 - nesting':
344 | beforeEach: ->
345 | @inst = squel.expr().and(squel.expr().and(squel.expr().and('A').and('B')).or(squel.expr().and('C').and('D'))).and('E')
346 | toString: ->
347 | assert.same @inst.toString(), "((A AND B) OR (C AND D)) AND E"
348 | toParam: ->
349 | assert.same @inst.toParam(), {
350 | text: "((A AND B) OR (C AND D)) AND E"
351 | values: []
352 | }
353 |
354 |
355 |
356 | module?.exports[require('path').basename(__filename)] = test
357 |
--------------------------------------------------------------------------------
/test/insert.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['INSERT builder'] =
34 | beforeEach: ->
35 | @func = squel.insert
36 | @inst = @func()
37 |
38 | 'instanceof QueryBuilder': ->
39 | assert.instanceOf @inst, squel.cls.QueryBuilder
40 |
41 | 'constructor':
42 | 'override options': ->
43 | @inst = squel.update
44 | usingValuePlaceholders: true
45 | dummy: true
46 |
47 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
48 | usingValuePlaceholders: true
49 | dummy: true
50 |
51 | for block in @inst.blocks
52 | assert.same _.pick(block.options, _.keys(expectedOptions)), expectedOptions
53 |
54 |
55 | 'override blocks': ->
56 | block = new squel.cls.StringBlock('SELECT')
57 | @inst = @func {}, [block]
58 | assert.same [block], @inst.blocks
59 |
60 |
61 | 'build query':
62 | 'need to call into() first': ->
63 | assert.throws (=> @inst.toString()), 'into() needs to be called'
64 |
65 | 'when set() not called': ->
66 | assert.same 'INSERT INTO table', @inst.into('table').toString()
67 |
68 | '>> into(table).set(field, null)':
69 | beforeEach: -> @inst.into('table').set('field', null)
70 | toString: ->
71 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (NULL)'
72 | toParam: ->
73 | assert.same @inst.toParam(), { text: 'INSERT INTO table (field) VALUES (?)', values: [null] }
74 |
75 | '>> into(table)':
76 | beforeEach: -> @inst.into('table')
77 |
78 | '>> set(field, 1)':
79 | beforeEach: -> @inst.set('field', 1)
80 | toString: ->
81 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1)'
82 |
83 | '>> set(field2, 1.2)':
84 | beforeEach: -> @inst.set('field2', 1.2)
85 | toString: ->
86 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 1.2)'
87 |
88 | '>> set(field2, "str")':
89 | beforeEach: -> @inst.set('field2', 'str')
90 | toString: ->
91 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, \'str\')'
92 | toParam: ->
93 | assert.same @inst.toParam(), {
94 | text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
95 | values: [ 1, 'str' ]
96 | }
97 |
98 | '>> set(field2, "str", { dontQuote: true } )':
99 | beforeEach: -> @inst.set('field2', 'str', dontQuote: true)
100 | toString: ->
101 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, str)'
102 | toParam: ->
103 | assert.same @inst.toParam(), {
104 | text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
105 | values: [ 1, 'str' ]
106 | }
107 |
108 | '>> set(field2, true)':
109 | beforeEach: -> @inst.set('field2', true)
110 | toString: ->
111 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, TRUE)'
112 |
113 | '>> set(field2, null)':
114 | beforeEach: -> @inst.set('field2', null)
115 | toString: ->
116 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, NULL)'
117 |
118 | '>> set(field, query builder)':
119 | beforeEach: ->
120 | @subQuery = squel.select().field('MAX(score)').from('scores')
121 | @inst.set( 'field', @subQuery )
122 | toString: ->
123 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((SELECT MAX(score) FROM scores))'
124 | toParam: ->
125 | parameterized = @inst.toParam()
126 | assert.same parameterized.text, 'INSERT INTO table (field) VALUES ((SELECT MAX(score) FROM scores))'
127 | assert.same parameterized.values, []
128 |
129 | '>> setFields({field2: \'value2\', field3: true })':
130 | beforeEach: -> @inst.setFields({field2: 'value2', field3: true })
131 | toString: ->
132 | assert.same @inst.toString(), 'INSERT INTO table (field, field2, field3) VALUES (1, \'value2\', TRUE)'
133 | toParam: ->
134 | parameterized = @inst.toParam()
135 | assert.same parameterized.text, 'INSERT INTO table (field, field2, field3) VALUES (?, ?, ?)'
136 | assert.same parameterized.values, [1,'value2',true]
137 |
138 | '>> setFields({field2: \'value2\', field: true })':
139 | beforeEach: -> @inst.setFields({field2: 'value2', field: true })
140 | toString: ->
141 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (TRUE, \'value2\')'
142 | toParam: ->
143 | parameterized = @inst.toParam()
144 | assert.same parameterized.text, 'INSERT INTO table (field, field2) VALUES (?, ?)'
145 | assert.same parameterized.values, [true, 'value2']
146 |
147 | '>> setFields(custom value type)':
148 | beforeEach: ->
149 | class MyClass
150 | @inst.registerValueHandler MyClass, -> 'abcd'
151 | @inst.setFields({ field: new MyClass() })
152 | toString: ->
153 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((abcd))'
154 | toParam: ->
155 | parameterized = @inst.toParam()
156 | assert.same parameterized.text, 'INSERT INTO table (field) VALUES (?)'
157 | assert.same parameterized.values, ['abcd']
158 |
159 | '>> setFieldsRows([{field: \'value2\', field2: true },{field: \'value3\', field2: 13 }]])':
160 | beforeEach: -> @inst.setFieldsRows([{field: 'value2', field2: true },{field: 'value3', field2: 13 }])
161 | toString: ->
162 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (\'value2\', TRUE), (\'value3\', 13)'
163 | toParam: ->
164 | parameterized = @inst.toParam()
165 | assert.same parameterized.text, 'INSERT INTO table (field, field2) VALUES (?, ?), (?, ?)'
166 | assert.same parameterized.values, ['value2',true, 'value3',13]
167 |
168 | 'Function values':
169 | beforeEach: -> @inst.set('field', squel.str('GETDATE(?, ?)', 2014, 'feb'))
170 | toString: ->
171 | assert.same 'INSERT INTO table (field) VALUES ((GETDATE(2014, \'feb\')))', @inst.toString()
172 | toParam: ->
173 | assert.same { text: 'INSERT INTO table (field) VALUES ((GETDATE(?, ?)))', values: [2014, 'feb'] }, @inst.toParam()
174 |
175 | '>> fromQuery([field1, field2], select query)':
176 | beforeEach: -> @inst.fromQuery(
177 | ['field1', 'field2'],
178 | squel.select().from('students').where('a = ?', 2)
179 | )
180 | toString: ->
181 | assert.same @inst.toString(), 'INSERT INTO table (field1, field2) (SELECT * FROM students WHERE (a = 2))'
182 | toParam: ->
183 | parameterized = @inst.toParam()
184 | assert.same parameterized.text, 'INSERT INTO table (field1, field2) (SELECT * FROM students WHERE (a = ?))'
185 | assert.same parameterized.values, [ 2 ]
186 |
187 | '>> setFieldsRows([{field1: 13, field2: \'value2\'},{field1: true, field3: \'value4\'}])': ->
188 | assert.throws (=> @inst.setFieldsRows([{field1: 13, field2: 'value2'},{field1: true, field3: 'value4'}]).toString()), 'All fields in subsequent rows must match the fields in the first row'
189 |
190 |
191 | 'dontQuote and replaceSingleQuotes set(field2, "ISNULL(\'str\', str)", { dontQuote: true })':
192 | beforeEach: ->
193 | @inst = squel.insert replaceSingleQuotes: true
194 | @inst.into('table').set('field', 1)
195 | @inst.set('field2', "ISNULL('str', str)", dontQuote: true)
196 | toString: ->
197 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, ISNULL(\'str\', str))'
198 | toParam: ->
199 | assert.same @inst.toParam(), {
200 | text: 'INSERT INTO table (field, field2) VALUES (?, ?)'
201 | values: [1, "ISNULL('str', str)"]
202 | }
203 |
204 | 'fix for #225 - autoquoting field names': ->
205 | @inst = squel.insert(autoQuoteFieldNames: true)
206 | .into('users')
207 | .set('active', 1)
208 | .set('regular', 0)
209 | .set('moderator',1)
210 |
211 | assert.same @inst.toParam(), {
212 | text: 'INSERT INTO users (`active`, `regular`, `moderator`) VALUES (?, ?, ?)',
213 | values: [1, 0, 1],
214 | }
215 |
216 | 'cloning': ->
217 | newinst = @inst.into('students').set('field', 1).clone()
218 | newinst.set('field', 2).set('field2', true)
219 |
220 | assert.same 'INSERT INTO students (field) VALUES (1)', @inst.toString()
221 | assert.same 'INSERT INTO students (field, field2) VALUES (2, TRUE)', newinst.toString()
222 |
223 |
224 |
225 | module?.exports[require('path').basename(__filename)] = test
226 |
--------------------------------------------------------------------------------
/test/mssql.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = undefined
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['MSSQL flavour'] =
34 | beforeEach: ->
35 | delete require.cache[require.resolve('../dist/squel')]
36 | squel = require "../dist/squel"
37 | squel = squel.useFlavour 'mssql'
38 |
39 | 'DATE Conversion':
40 | beforeEach: -> @inst = squel.insert()
41 |
42 | '>> into(table).set(field, new Date(2012-12-12T4:30:00Z))':
43 | beforeEach: -> @inst.into('table').set('field', new Date("2012-12-12T04:30:00Z"))
44 | toString: ->
45 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES ((\'2012-12-12 4:30:0\'))'
46 |
47 | 'SELECT builder':
48 | beforeEach: ->
49 | @sel = squel.select()
50 |
51 | '>> from(table).field(field).top(10)':
52 | beforeEach: -> @sel.from('table').field('field').top(10)
53 | toString: ->
54 | assert.same @sel.toString(), 'SELECT TOP (10) field FROM table'
55 |
56 | '>> from(table).field(field).limit(10)':
57 | beforeEach: -> @sel.from('table').field('field').limit(10)
58 | toString: ->
59 | assert.same @sel.toString(), 'SELECT TOP (10) field FROM table'
60 |
61 | '>> from(table).field(field).limit(10).offset(5)':
62 | beforeEach: -> @sel.from('table').field('field').limit(10).offset(5)
63 | toString: ->
64 | assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY'
65 |
66 | '>> from(table).field(field).top(10).offset(5)':
67 | beforeEach: -> @sel.from('table').field('field').top(10).offset(5)
68 | toString: ->
69 | assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY'
70 |
71 | '>> from(table).field(field).offset(5)':
72 | beforeEach: -> @sel.from('table').field('field').offset(5)
73 | toString: ->
74 | assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS'
75 |
76 | '>> from(table).field(field).offset(5).union(...)':
77 | beforeEach: -> @sel.from('table').field('field').offset(5).union(squel.select().from('table2').where('a = 2'))
78 | toString: ->
79 | assert.same @sel.toString(), 'SELECT field FROM table OFFSET 5 ROWS UNION (SELECT * FROM table2 WHERE (a = 2))'
80 |
81 | '>> check variables arent being shared':
82 | toString: ->
83 | assert.same squel.select().from('table').field('field').top(10).toString(), 'SELECT TOP (10) field FROM table'
84 | assert.same squel.select().from('table').field('field').toString(), 'SELECT field FROM table'
85 |
86 | 'INSERT builder':
87 | beforeEach: -> @inst = squel.insert()
88 |
89 | '>> into(table).set(field, 1).output(id)':
90 | beforeEach: -> @inst.into('table').output('id').set('field', 1)
91 | toString: ->
92 | assert.same @inst.toString(), 'INSERT INTO table (field) OUTPUT INSERTED.id VALUES (1)'
93 |
94 | 'UPDATE builder':
95 | beforeEach: -> @upt = squel.update()
96 |
97 | '>> table(table).set(field, 1).top(12)':
98 | beforeEach: -> @upt.table('table').set('field', 1).top(12)
99 | toString: ->
100 | assert.same @upt.toString(), 'UPDATE TOP (12) table SET field = 1'
101 |
102 | '>> table(table).set(field, 1).limit(12)':
103 | beforeEach: -> @upt.table('table').set('field', 1).limit(12)
104 | toString: ->
105 | assert.same @upt.toString(), 'UPDATE TOP (12) table SET field = 1'
106 |
107 | '>> table(table).set(field, 1).output(id)':
108 | beforeEach: -> @upt.table('table').output('id').set('field', 1)
109 | toString: ->
110 | assert.same @upt.toString(), 'UPDATE table SET field = 1 OUTPUT INSERTED.id'
111 |
112 | '>> table(table).set(field, 1).outputs(id AS ident, name AS naming)':
113 | beforeEach: -> @upt.table('table').outputs(
114 | id: 'ident'
115 | name: 'naming'
116 | ).set('field', 1)
117 | toString: ->
118 | assert.same @upt.toString(), 'UPDATE table SET field = 1 OUTPUT INSERTED.id AS ident, INSERTED.name AS naming'
119 |
120 | 'DELETE builder':
121 | beforeEach: -> @upt = squel.delete()
122 |
123 | '>> from(table)':
124 | beforeEach: -> @upt.from('table')
125 | toString: ->
126 | assert.same @upt.toString(), 'DELETE FROM table'
127 |
128 | '>> from(table).output(id)':
129 | beforeEach: -> @upt.from('table').output('id')
130 | toString: ->
131 | assert.same @upt.toString(), 'DELETE FROM table OUTPUT DELETED.id'
132 |
133 | '>> from(table).outputs(id AS ident, name AS naming).where("a = 1")':
134 | beforeEach: -> @upt.from('table').outputs(
135 | id: 'ident'
136 | name: 'naming'
137 | ).where('a = 1')
138 | toString: ->
139 | assert.same @upt.toString(), 'DELETE FROM table OUTPUT DELETED.id AS ident, DELETED.name AS naming WHERE (a = 1)'
140 |
141 | 'Default query builder options': ->
142 | assert.same {
143 | autoQuoteTableNames: false
144 | autoQuoteFieldNames: false
145 | autoQuoteAliasNames: false
146 | useAsForTableAliasNames: false
147 | nameQuoteCharacter: '`'
148 | tableAliasQuoteCharacter: '`'
149 | fieldAliasQuoteCharacter: '"'
150 | valueHandlers: []
151 | parameterCharacter: '?'
152 | numberedParameters: false
153 | numberedParametersPrefix: '@'
154 | numberedParametersStartAt: 1
155 | replaceSingleQuotes: true
156 | singleQuoteReplacement: '\'\''
157 | separator: ' '
158 | stringFormatter: null
159 | rawNesting: false
160 | }, squel.cls.DefaultQueryBuilderOptions
161 |
162 | module?.exports[require('path').basename(__filename)] = test
163 |
--------------------------------------------------------------------------------
/test/mysql.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = undefined
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 | test['MySQL flavour'] =
33 | beforeEach: ->
34 | delete require.cache[require.resolve('../dist/squel')]
35 | squel = require "../dist/squel"
36 | squel = squel.useFlavour 'mysql'
37 |
38 |
39 | 'MysqlOnDuplicateKeyUpdateBlock':
40 | beforeEach: ->
41 | @cls = squel.cls.MysqlOnDuplicateKeyUpdateBlock
42 | @inst = new @cls()
43 |
44 | 'instanceof of AbstractSetFieldBlock': ->
45 | assert.instanceOf @inst, squel.cls.AbstractSetFieldBlock
46 |
47 | 'onDupUpdate()':
48 | 'calls to _set()': ->
49 | spy = test.mocker.stub @inst, '_set'
50 |
51 | @inst.onDupUpdate 'f', 'v', dummy: true
52 |
53 | assert.ok spy.calledWithExactly('f', 'v', dummy: true)
54 |
55 |
56 | '_toParamString()':
57 | beforeEach: ->
58 | @inst.onDupUpdate('field1 = field1 + 1')
59 | @inst.onDupUpdate('field2', 'value2', {dummy: true})
60 | @inst.onDupUpdate('field3', 'value3')
61 |
62 | 'non-parameterized': ->
63 | assert.same @inst._toParamString(), {
64 | text: 'ON DUPLICATE KEY UPDATE field1 = field1 + 1, field2 = \'value2\', field3 = \'value3\''
65 | values: []
66 | }
67 | 'parameterized': ->
68 | assert.same @inst._toParamString(buildParameterized: true), {
69 | text: 'ON DUPLICATE KEY UPDATE field1 = field1 + 1, field2 = ?, field3 = ?'
70 | values: ['value2', 'value3']
71 | }
72 |
73 |
74 | 'INSERT builder':
75 | beforeEach: -> @inst = squel.insert()
76 |
77 | '>> into(table).set(field, 1).set(field1, 2).onDupUpdate(field, 5).onDupUpdate(field1, "str")':
78 | beforeEach: ->
79 | @inst
80 | .into('table')
81 | .set('field', 1)
82 | .set('field1', 2)
83 | .onDupUpdate('field', 5)
84 | .onDupUpdate('field1', 'str')
85 | toString: ->
86 | assert.same @inst.toString(), 'INSERT INTO table (field, field1) VALUES (1, 2) ON DUPLICATE KEY UPDATE field = 5, field1 = \'str\''
87 |
88 | toParam: ->
89 | assert.same @inst.toParam(), {
90 | text: 'INSERT INTO table (field, field1) VALUES (?, ?) ON DUPLICATE KEY UPDATE field = ?, field1 = ?'
91 | values: [1, 2, 5, 'str']
92 | }
93 |
94 | '>> into(table).set(field2, 3).onDupUpdate(field2, "str", { dontQuote: true })':
95 | beforeEach: ->
96 | @inst
97 | .into('table')
98 | .set('field2', 3)
99 | .onDupUpdate('field2', 'str', { dontQuote: true })
100 | toString: ->
101 | assert.same @inst.toString(), 'INSERT INTO table (field2) VALUES (3) ON DUPLICATE KEY UPDATE field2 = str'
102 | toParam: ->
103 | assert.same @inst.toParam(), {
104 | text: 'INSERT INTO table (field2) VALUES (?) ON DUPLICATE KEY UPDATE field2 = ?'
105 | values: [3, 'str']
106 | }
107 |
108 |
109 | 'REPLACE builder':
110 | beforeEach: -> @inst = squel.replace()
111 |
112 | '>> into(table).set(field, 1).set(field1, 2)':
113 | beforeEach: ->
114 | @inst
115 | .into('table')
116 | .set('field', 1)
117 | .set('field1', 2)
118 | toString: ->
119 | assert.same @inst.toString(), 'REPLACE INTO table (field, field1) VALUES (1, 2)'
120 |
121 | toParam: ->
122 | assert.same @inst.toParam(), {
123 | text: 'REPLACE INTO table (field, field1) VALUES (?, ?)'
124 | values: [1, 2]
125 | }
126 |
127 |
128 | module?.exports[require('path').basename(__filename)] = test
129 |
--------------------------------------------------------------------------------
/test/postgres.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = undefined
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 | test['Postgres flavour'] =
33 | beforeEach: ->
34 | delete require.cache[require.resolve('../dist/squel')]
35 | squel = require "../dist/squel"
36 | squel = squel.useFlavour 'postgres'
37 |
38 | 'INSERT builder':
39 | beforeEach: -> @inst = squel.insert()
40 |
41 | '>> into(table).set(field, 1).set(field,2).onConflict("field", {field2:2})':
42 | beforeEach: -> @inst.into('table').set('field', 1).set('field2', 2).onConflict('field', {"field2":2})
43 | toString: ->
44 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 2) ON CONFLICT (field) DO UPDATE SET field2 = 2'
45 |
46 | '>> into(table).set(field, 1).set(field,2).onConflict("field")':
47 | beforeEach: -> @inst.into('table').set('field', 1).set('field2', 2).onConflict('field')
48 | toString: ->
49 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 2) ON CONFLICT (field) DO NOTHING'
50 |
51 | '>> into(table).set(field, 1).set(field,2).onConflict(["field", "field2"], {field3:3})':
52 | beforeEach: -> @inst.into('table').set('field', 1).set('field2', 2).onConflict(['field', 'field2'], {field3: 3})
53 | toString: ->
54 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 2) ON CONFLICT (field, field2) DO UPDATE SET field3 = 3'
55 |
56 | '>> into(table).set(field, 1).set(field,2).onConflict(["field", "field2"])':
57 | beforeEach: -> @inst.into('table').set('field', 1).set('field2', 2).onConflict('field')
58 | toString: ->
59 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 2) ON CONFLICT (field) DO NOTHING'
60 |
61 | '>> into(table).set(field, 1).set(field,2).onConflict()':
62 | beforeEach: -> @inst.into('table').set('field', 1).set('field2', 2).onConflict()
63 | toString: ->
64 | assert.same @inst.toString(), 'INSERT INTO table (field, field2) VALUES (1, 2) ON CONFLICT DO NOTHING'
65 |
66 | '>> into(table).set(field, 1).returning("*")':
67 | beforeEach: -> @inst.into('table').set('field', 1).returning('*')
68 | toString: ->
69 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING *'
70 |
71 | '>> into(table).set(field, 1).returning("id")':
72 | beforeEach: -> @inst.into('table').set('field', 1).returning('id')
73 | toString: ->
74 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING id'
75 |
76 | '>> into(table).set(field, 1).returning("id").returning("id")':
77 | beforeEach: -> @inst.into('table').set('field', 1).returning('id').returning('id')
78 | toString: ->
79 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING id'
80 |
81 | '>> into(table).set(field, 1).returning("id").returning("name", "alias")':
82 | beforeEach: -> @inst.into('table').set('field', 1).returning('id').returning('name', 'alias')
83 | toString: ->
84 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING id, name AS alias'
85 |
86 | '>> into(table).set(field, 1).returning(squel.str("id < ?", 100), "under100")':
87 | beforeEach: -> @inst.into('table').set('field', 1).returning(squel.str('id < ?', 100), 'under100')
88 | toString: ->
89 | assert.same @inst.toString(), 'INSERT INTO table (field) VALUES (1) RETURNING (id < 100) AS under100'
90 | toParam: ->
91 | assert.same @inst.toParam(), {
92 | "text": 'INSERT INTO table (field) VALUES ($1) RETURNING (id < $2) AS under100',
93 | "values": [1, 100]
94 | }
95 |
96 |
97 | '>> into(table).set(field, 1).with(alias, table)':
98 | beforeEach: -> @inst.into('table').set('field', 1).with('alias', squel.select().from('table').where('field = ?', 2))
99 | toString: ->
100 | assert.same @inst.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) INSERT INTO table (field) VALUES (1)'
101 | toParam: ->
102 | assert.same @inst.toParam(), {
103 | "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) INSERT INTO table (field) VALUES ($2)',
104 | "values": [2, 1]
105 | }
106 |
107 | 'UPDATE builder':
108 | beforeEach: -> @upd = squel.update()
109 |
110 | '>> table(table).set(field, 1).returning("*")':
111 | beforeEach: -> @upd.table('table').set('field', 1).returning('*')
112 | toString: ->
113 | assert.same @upd.toString(), 'UPDATE table SET field = 1 RETURNING *'
114 |
115 | '>> table(table).set(field, 1).returning("field")':
116 | beforeEach: -> @upd.table('table').set('field', 1).returning('field')
117 | toString: ->
118 | assert.same @upd.toString(), 'UPDATE table SET field = 1 RETURNING field'
119 |
120 | '>> table(table).set(field, 1).returning("name", "alias")':
121 | beforeEach: -> @upd.table('table').set('field', 1).returning("name", "alias")
122 | toString: ->
123 | assert.same @upd.toString(), 'UPDATE table SET field = 1 RETURNING name AS alias'
124 |
125 | '>> table(table).set(field, 1).from(table2)':
126 | beforeEach: -> @upd.table('table').set('field', 1).from('table2')
127 | toString: ->
128 | assert.same @upd.toString(), 'UPDATE table SET field = 1 FROM table2'
129 |
130 | '>> table(table).set(field, 1).with(alias, table)':
131 | beforeEach: -> @upd.table('table').set('field', 1).with('alias', squel.select().from('table').where('field = ?', 2))
132 | toString: ->
133 | assert.same @upd.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) UPDATE table SET field = 1'
134 | toParam: ->
135 | assert.same @upd.toParam(), {
136 | "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) UPDATE table SET field = $2',
137 | "values": [2, 1]
138 | }
139 |
140 | 'DELETE builder':
141 | beforeEach: -> @del = squel.delete()
142 |
143 | '>> from(table).where(field = 1).returning("*")':
144 | beforeEach: -> @del.from('table').where('field = 1').returning('*')
145 | toString: ->
146 | assert.same @del.toString(), 'DELETE FROM table WHERE (field = 1) RETURNING *'
147 |
148 | '>> from(table).where(field = 1).returning("field")':
149 | beforeEach: -> @del.from('table').where('field = 1').returning('field')
150 | toString: ->
151 | assert.same @del.toString(), 'DELETE FROM table WHERE (field = 1) RETURNING field'
152 |
153 | '>> from(table).where(field = 1).returning("field", "f")':
154 | beforeEach: -> @del.from('table').where('field = 1').returning('field', 'f')
155 | toString: ->
156 | assert.same @del.toString(), 'DELETE FROM table WHERE (field = 1) RETURNING field AS f'
157 |
158 | '>> from(table).where(field = 1).with(alias, table)':
159 | beforeEach: -> @del.from('table').where('field = ?', 1).with('alias', squel.select().from('table').where('field = ?', 2))
160 | toString: ->
161 | assert.same @del.toString(), 'WITH alias AS (SELECT * FROM table WHERE (field = 2)) DELETE FROM table WHERE (field = 1)'
162 | toParam: ->
163 | assert.same @del.toParam(), {
164 | "text": 'WITH alias AS (SELECT * FROM table WHERE (field = $1)) DELETE FROM table WHERE (field = $2)',
165 | "values": [2, 1]
166 | }
167 |
168 | 'SELECT builder':
169 | beforeEach: ->
170 | @sel = squel.select()
171 | 'select':
172 | '>> from(table).where(field = 1)':
173 | beforeEach: ->
174 | @sel.field('field1').from('table1').where('field1 = 1')
175 | toString: ->
176 | assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 1)'
177 | toParam: ->
178 | assert.same @sel.toParam(), {
179 | "text": 'SELECT field1 FROM table1 WHERE (field1 = 1)'
180 | "values": []
181 | }
182 |
183 | '>> from(table).where(field = ?, 2)':
184 | beforeEach: ->
185 | @sel.field('field1').from('table1').where('field1 = ?', 2)
186 | toString: ->
187 | assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 2)'
188 | toParam: ->
189 | assert.same @sel.toParam(), {
190 | "text": 'SELECT field1 FROM table1 WHERE (field1 = $1)'
191 | "values": [2]
192 | }
193 |
194 | 'distinct queries':
195 | beforeEach: ->
196 | @sel.fields(['field1', 'field2']).from('table1')
197 |
198 | '>> from(table).distinct()':
199 | beforeEach: ->
200 | @sel.distinct()
201 | toString: ->
202 | assert.same @sel.toString(), 'SELECT DISTINCT field1, field2 FROM table1'
203 | toParam: ->
204 | assert.same @sel.toParam(), {
205 | 'text': 'SELECT DISTINCT field1, field2 FROM table1',
206 | 'values': []
207 | }
208 |
209 | '>> from(table).distinct(field1)':
210 | beforeEach: ->
211 | @sel.distinct('field1')
212 | toString: ->
213 | assert.same @sel.toString(), 'SELECT DISTINCT ON (field1) field1, field2 FROM table1'
214 | toParam: ->
215 | assert.same @sel.toParam(), {
216 | 'text': 'SELECT DISTINCT ON (field1) field1, field2 FROM table1',
217 | 'values': []
218 | }
219 |
220 | '>> from(table).distinct(field1, field2)':
221 | beforeEach: ->
222 | @sel.distinct('field1', 'field2')
223 | toString: ->
224 | assert.same @sel.toString(), 'SELECT DISTINCT ON (field1, field2) field1, field2 FROM table1'
225 | toParam: ->
226 | assert.same @sel.toParam(), {
227 | 'text': 'SELECT DISTINCT ON (field1, field2) field1, field2 FROM table1',
228 | 'values': []
229 | }
230 |
231 | 'cte queries':
232 | beforeEach: ->
233 | @sel = squel.select()
234 | @sel2 = squel.select()
235 | @sel3 = squel.select()
236 |
237 | '>> query1.with(alias, query2)':
238 | beforeEach: ->
239 | @sel.from('table1').where('field1 = ?', 1)
240 | @sel2.from('table2').where('field2 = ?', 2)
241 | @sel.with('someAlias', @sel2)
242 | toString: ->
243 | assert.same @sel.toString(), 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = 2)) SELECT * FROM table1 WHERE (field1 = 1)'
244 | toParam: ->
245 | assert.same @sel.toParam(), {
246 | "text": 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = $1)) SELECT * FROM table1 WHERE (field1 = $2)'
247 | "values": [2, 1]
248 | }
249 |
250 | '>> query1.with(alias1, query2).with(alias2, query2)':
251 | beforeEach: ->
252 | @sel.from('table1').where('field1 = ?', 1)
253 | @sel2.from('table2').where('field2 = ?', 2)
254 | @sel3.from('table3').where('field3 = ?', 3)
255 | @sel.with('someAlias', @sel2).with('anotherAlias', @sel3)
256 | toString: ->
257 | assert.same @sel.toString(), 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = 2)), anotherAlias AS (SELECT * FROM table3 WHERE (field3 = 3)) SELECT * FROM table1 WHERE (field1 = 1)'
258 | toParam: ->
259 | assert.same @sel.toParam(), {
260 | "text": 'WITH someAlias AS (SELECT * FROM table2 WHERE (field2 = $1)), anotherAlias AS (SELECT * FROM table3 WHERE (field3 = $2)) SELECT * FROM table1 WHERE (field1 = $3)'
261 | "values": [2, 3, 1]
262 | }
263 |
264 |
265 | 'union queries':
266 | beforeEach: ->
267 | @sel = squel.select()
268 | @sel2 = squel.select()
269 |
270 | '>> query1.union(query2)':
271 | beforeEach: ->
272 | @sel.field('field1').from('table1').where('field1 = ?', 3)
273 | @sel2.field('field1').from('table1').where('field1 < ?', 10)
274 | @sel.union(@sel2)
275 | toString: ->
276 | assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 3) UNION (SELECT field1 FROM table1 WHERE (field1 < 10))'
277 | toParam: ->
278 | assert.same @sel.toParam(), {
279 | "text": 'SELECT field1 FROM table1 WHERE (field1 = $1) UNION (SELECT field1 FROM table1 WHERE (field1 < $2))'
280 | "values": [
281 | 3
282 | 10
283 | ]
284 | }
285 |
286 | '>> query1.union_all(query2)':
287 | beforeEach: ->
288 | @sel.field('field1').from('table1').where('field1 = ?', 3)
289 | @sel2.field('field1').from('table1').where('field1 < ?', 10)
290 | @sel.union_all(@sel2)
291 | toString: ->
292 | assert.same @sel.toString(), 'SELECT field1 FROM table1 WHERE (field1 = 3) UNION ALL (SELECT field1 FROM table1 WHERE (field1 < 10))'
293 | toParam: ->
294 | assert.same @sel.toParam(), {
295 | "text": 'SELECT field1 FROM table1 WHERE (field1 = $1) UNION ALL (SELECT field1 FROM table1 WHERE (field1 < $2))'
296 | "values": [
297 | 3
298 | 10
299 | ]
300 | }
301 |
302 |
303 | 'Default query builder options': ->
304 | assert.same {
305 | replaceSingleQuotes: false
306 | singleQuoteReplacement: '\'\''
307 | autoQuoteTableNames: false
308 | autoQuoteFieldNames: false
309 | autoQuoteAliasNames: false
310 | useAsForTableAliasNames: true
311 | nameQuoteCharacter: '`'
312 | tableAliasQuoteCharacter: '`'
313 | fieldAliasQuoteCharacter: '"'
314 | valueHandlers: []
315 | parameterCharacter: '?'
316 | numberedParameters: true
317 | numberedParametersPrefix: '$'
318 | numberedParametersStartAt: 1
319 | separator: ' '
320 | stringFormatter: null
321 | rawNesting: false
322 | }, squel.cls.DefaultQueryBuilderOptions
323 |
324 |
325 | module?.exports[require('path').basename(__filename)] = test
326 |
--------------------------------------------------------------------------------
/test/select.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['SELECT builder'] =
34 | beforeEach: ->
35 | @func = squel.select
36 | @inst = @func()
37 |
38 | 'instanceof QueryBuilder': ->
39 | assert.instanceOf @inst, squel.cls.QueryBuilder
40 |
41 | 'constructor':
42 | 'override options': ->
43 | @inst = squel.select
44 | usingValuePlaceholders: true
45 | dummy: true
46 |
47 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
48 | usingValuePlaceholders: true
49 | dummy: true
50 |
51 | for block in @inst.blocks
52 | assert.same _.pick(block.options, _.keys(expectedOptions)), expectedOptions
53 |
54 | 'override blocks': ->
55 | block = new squel.cls.StringBlock('SELECT')
56 | @inst = @func {}, [block]
57 | assert.same [block], @inst.blocks
58 |
59 | 'build query':
60 | 'no need to call from() first': ->
61 | @inst.toString()
62 |
63 | '>> function(1)':
64 | beforeEach: -> @inst.function('1')
65 | toString: ->
66 | assert.same @inst.toString(), 'SELECT 1'
67 | toParam: ->
68 | assert.same @inst.toParam(), { text: 'SELECT 1', values: [] }
69 |
70 | '>> function(MAX(?,?), 3, 5)':
71 | beforeEach: -> @inst.function('MAX(?, ?)', 3, 5)
72 | toString: ->
73 | assert.same @inst.toString(), 'SELECT MAX(3, 5)'
74 | toParam: ->
75 | assert.same @inst.toParam(), { text: 'SELECT MAX(?, ?)', values: [3, 5] }
76 |
77 | '>> from(table).from(table2, alias2)':
78 | beforeEach: -> @inst.from('table').from('table2', 'alias2')
79 | toString: ->
80 | assert.same @inst.toString(), 'SELECT * FROM table, table2 `alias2`'
81 |
82 | '>> field(squel.select().field("MAX(score)").FROM("scores"), fa1)':
83 | beforeEach: -> @inst.field(squel.select().field("MAX(score)").from("scores"), 'fa1')
84 | toString: ->
85 | assert.same @inst.toString(), 'SELECT (SELECT MAX(score) FROM scores) AS "fa1" FROM table, table2 `alias2`'
86 |
87 | '>> field(squel.case().when(score > ?, 1).then(1), fa1)':
88 | beforeEach: -> @inst.field(squel.case().when("score > ?", 1).then(1), 'fa1')
89 | toString: ->
90 | assert.same @inst.toString(), 'SELECT CASE WHEN (score > 1) THEN 1 ELSE NULL END AS "fa1" FROM table, table2 `alias2`'
91 | toParam: ->
92 | assert.same @inst.toParam(), { text: 'SELECT CASE WHEN (score > ?) THEN 1 ELSE NULL END AS "fa1" FROM table, table2 `alias2`', values: [1] }
93 |
94 | '>> field( squel.str(SUM(?), squel.case().when(score > ?, 1).then(1) ), fa1)':
95 | beforeEach: -> @inst.field( squel.str('SUM(?)', squel.case().when("score > ?", 1).then(1)), 'fa1')
96 | toString: ->
97 | assert.same @inst.toString(), 'SELECT (SUM((CASE WHEN (score > 1) THEN 1 ELSE NULL END))) AS "fa1" FROM table, table2 `alias2`'
98 | toParam: ->
99 | assert.same @inst.toParam(), { text: 'SELECT (SUM(CASE WHEN (score > ?) THEN 1 ELSE NULL END)) AS "fa1" FROM table, table2 `alias2`', values: [1] }
100 |
101 | '>> field(field1, fa1) >> field(field2)':
102 | beforeEach: -> @inst.field('field1', 'fa1').field('field2')
103 | toString: ->
104 | assert.same @inst.toString(), 'SELECT field1 AS "fa1", field2 FROM table, table2 `alias2`'
105 |
106 | '>> distinct()':
107 | beforeEach: -> @inst.distinct()
108 | toString: ->
109 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2`'
110 |
111 | '>> group(field) >> group(field2)':
112 | beforeEach: -> @inst.group('field').group('field2')
113 | toString: ->
114 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2'
115 |
116 | '>> where(a = ?, squel.select().field("MAX(score)").from("scores"))':
117 | beforeEach: ->
118 | @subQuery = squel.select().field("MAX(score)").from("scores")
119 | @inst.where('a = ?', @subQuery)
120 | toString: ->
121 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT MAX(score) FROM scores)) GROUP BY field, field2'
122 | toParam: ->
123 | assert.same @inst.toParam(), {
124 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT MAX(score) FROM scores)) GROUP BY field, field2'
125 | values: []
126 | }
127 |
128 | '>> where(squel.expr().and(a = ?, 1).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
129 | beforeEach: -> @inst.where(squel.expr().and("a = ?", 1).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
130 | toString: ->
131 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = 1 AND (b = 2 OR c = 3)) GROUP BY field, field2'
132 | toParam: ->
133 | assert.same @inst.toParam(), {
134 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ? AND (b = ? OR c = ?)) GROUP BY field, field2'
135 | values: [1, 2, 3]
136 | }
137 |
138 | '>> where(squel.expr().and(a = ?, QueryBuilder).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
139 | beforeEach: ->
140 | subQuery = squel.select().field('field1').from('table1').where('field2 = ?', 10)
141 | @inst.where(squel.expr().and("a = ?", subQuery).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
142 | toString: ->
143 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT field1 FROM table1 WHERE (field2 = 10)) AND (b = 2 OR c = 3)) GROUP BY field, field2'
144 | toParam: ->
145 | assert.same @inst.toParam(), {
146 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = (SELECT field1 FROM table1 WHERE (field2 = ?)) AND (b = ? OR c = ?)) GROUP BY field, field2'
147 | values: [10, 2, 3]
148 | }
149 |
150 | '>> having(squel.expr().and(a = ?, QueryBuilder).and( expr().or(b = ?, 2).or(c = ?, 3) ))':
151 | beforeEach: ->
152 | subQuery = squel.select().field('field1').from('table1').having('field2 = ?', 10)
153 | @inst.having(squel.expr().and("a = ?", subQuery).and(squel.expr().or("b = ?", 2).or("c = ?", 3)))
154 | toString: ->
155 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2 HAVING (a = (SELECT field1 FROM table1 HAVING (field2 = 10)) AND (b = 2 OR c = 3))'
156 | toParam: ->
157 | assert.same @inst.toParam(), {
158 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` GROUP BY field, field2 HAVING (a = (SELECT field1 FROM table1 HAVING (field2 = ?)) AND (b = ? OR c = ?))'
159 | values: [10, 2, 3]
160 | }
161 |
162 | '>> where(a = ?, null)':
163 | beforeEach: -> @inst.where('a = ?', null)
164 | toString: ->
165 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = NULL) GROUP BY field, field2'
166 | toParam: ->
167 | assert.same @inst.toParam(), {
168 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ?) GROUP BY field, field2'
169 | values: [null]
170 | }
171 |
172 | '>> where(a = ?, 1)':
173 | beforeEach: -> @inst.where('a = ?', 1)
174 | toString: ->
175 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = 1) GROUP BY field, field2'
176 | toParam: ->
177 | assert.same @inst.toParam(), {
178 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` WHERE (a = ?) GROUP BY field, field2'
179 | values: [1]
180 | }
181 |
182 | '>> join(other_table)':
183 | beforeEach: -> @inst.join('other_table')
184 | toString: ->
185 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2'
186 |
187 | '>> order(a)':
188 | beforeEach: -> @inst.order('a')
189 | toString: ->
190 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
191 |
192 | '>> order(a, null)':
193 | beforeEach: -> @inst.order('a', null)
194 | toString: ->
195 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a'
196 |
197 | '>> order(a, \'asc nulls last\')':
198 | beforeEach: -> @inst.order('a', 'asc nulls last')
199 | toString: ->
200 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a asc nulls last'
201 |
202 | '>> order(a, true)':
203 | beforeEach: -> @inst.order('a', true)
204 | toString: ->
205 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
206 |
207 | '>> limit(2)':
208 | beforeEach: -> @inst.limit(2)
209 | toString: ->
210 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 2'
211 | toParam: ->
212 | assert.same @inst.toParam(), {
213 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY a ASC LIMIT ?',
214 | values: [1, 2]
215 | }
216 |
217 | '>> limit(0)':
218 | beforeEach: -> @inst.limit(0)
219 | toString: ->
220 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 0'
221 | toParam: ->
222 | assert.same @inst.toParam(), {
223 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY a ASC LIMIT ?',
224 | values: [1, 0]
225 | }
226 |
227 | '>> offset(3)':
228 | beforeEach: -> @inst.offset(3)
229 | toString: ->
230 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 2 OFFSET 3'
231 | toParam: ->
232 | assert.same @inst.toParam(), {
233 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY a ASC LIMIT ? OFFSET ?',
234 | values: [1, 2, 3]
235 | }
236 |
237 | '>> offset(0)':
238 | beforeEach: -> @inst.offset(0)
239 | toString: ->
240 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC LIMIT 2 OFFSET 0'
241 | toParam: ->
242 | assert.same @inst.toParam(), {
243 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY a ASC LIMIT ? OFFSET ?',
244 | values: [1, 2, 0]
245 | }
246 |
247 | '>> order(DIST(?,?), true, 2, 3)':
248 | beforeEach: -> @inst.order('DIST(?, ?)', true, 2, false)
249 | toString: ->
250 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY DIST(2, FALSE) ASC'
251 | toParam: ->
252 | assert.same @inst.toParam(), {
253 | text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = ?) GROUP BY field, field2 ORDER BY DIST(?, ?) ASC'
254 | values: [1, 2, false]
255 | }
256 |
257 | '>> order(a)':
258 | beforeEach: -> @inst.order('a')
259 | toString: ->
260 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY a ASC'
261 |
262 | '>> order(b, null)':
263 | beforeEach: -> @inst.order('b', null)
264 | toString: ->
265 | assert.same @inst.toString(), 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table WHERE (a = 1) GROUP BY field, field2 ORDER BY b'
266 |
267 | '>> join(other_table, condition = expr())':
268 | beforeEach: ->
269 | subQuery = squel.select().field('abc').from('table1').where('adf = ?', 'today1')
270 | subQuery2 = squel.select().field('xyz').from('table2').where('adf = ?', 'today2')
271 | expr = squel.expr().and('field1 = ?', subQuery)
272 | @inst.join('other_table', null, expr)
273 | @inst.where('def IN ?', subQuery2)
274 | toString: ->
275 | assert.same @inst.toString(), "SELECT DISTINCT field1 AS \"fa1\", field2 FROM table, table2 `alias2` INNER JOIN other_table ON (field1 = (SELECT abc FROM table1 WHERE (adf = 'today1'))) WHERE (a = 1) AND (def IN (SELECT xyz FROM table2 WHERE (adf = 'today2'))) GROUP BY field, field2"
276 | toParam: ->
277 | assert.same @inst.toParam(), { text: 'SELECT DISTINCT field1 AS "fa1", field2 FROM table, table2 `alias2` INNER JOIN other_table ON (field1 = (SELECT abc FROM table1 WHERE (adf = ?))) WHERE (a = ?) AND (def IN (SELECT xyz FROM table2 WHERE (adf = ?))) GROUP BY field, field2', values: ["today1",1,"today2"] }
278 |
279 |
280 | 'nested queries':
281 | 'basic': ->
282 | inner1 = squel.select().from('students')
283 | inner2 = squel.select().from('scores')
284 |
285 | @inst.from(inner1).from(inner2, 'scores')
286 |
287 | assert.same @inst.toString(), "SELECT * FROM (SELECT * FROM students), (SELECT * FROM scores) `scores`"
288 | 'deep nesting': ->
289 | inner1 = squel.select().from('students')
290 | inner2 = squel.select().from(inner1)
291 |
292 | @inst.from(inner2)
293 |
294 | assert.same @inst.toString(), "SELECT * FROM (SELECT * FROM (SELECT * FROM students))"
295 |
296 | 'nesting in JOINs': ->
297 | inner1 = squel.select().from('students')
298 | inner2 = squel.select().from(inner1)
299 |
300 | @inst.from('schools').join(inner2, 'meh', 'meh.ID = ID')
301 |
302 | assert.same @inst.toString(), "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students)) `meh` ON (meh.ID = ID)"
303 |
304 | 'nesting in JOINs with params': ->
305 | inner1 = squel.select().from('students').where('age = ?', 6)
306 | inner2 = squel.select().from(inner1)
307 |
308 | @inst.from('schools').where('school_type = ?', 'junior').join(inner2, 'meh', 'meh.ID = ID')
309 |
310 | assert.same @inst.toString(), "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = 6))) `meh` ON (meh.ID = ID) WHERE (school_type = 'junior')"
311 | assert.same @inst.toParam(), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = ?))) `meh` ON (meh.ID = ID) WHERE (school_type = ?)", "values": [6,'junior'] }
312 | assert.same @inst.toParam({ "numberedParameters": true}), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = $1))) `meh` ON (meh.ID = ID) WHERE (school_type = $2)", "values": [6,'junior'] }
313 |
314 | 'Complex table name, e.g. LATERAL (#230)':
315 | beforeEach: ->
316 | @inst = squel.select().from('foo').from(squel.str('LATERAL(?)', squel.select().from('bar').where('bar.id = ?', 2)), 'ss')
317 | 'toString': ->
318 | assert.same @inst.toString(), 'SELECT * FROM foo, (LATERAL((SELECT * FROM bar WHERE (bar.id = 2)))) `ss`',
319 | 'toParam': ->
320 | assert.same @inst.toParam(), {
321 | text: 'SELECT * FROM foo, (LATERAL((SELECT * FROM bar WHERE (bar.id = ?)))) `ss`'
322 | values: [2]
323 | }
324 |
325 | 'cloning':
326 | 'basic': ->
327 | newinst = @inst.from('students').limit(10).clone()
328 | newinst.limit(20)
329 |
330 | assert.same 'SELECT * FROM students LIMIT 10', @inst.toString()
331 | assert.same 'SELECT * FROM students LIMIT 20', newinst.toString()
332 |
333 | 'with expressions (ticket #120)': ->
334 | expr = squel.expr().and('a = 1')
335 | newinst = @inst.from('table').left_join('table_2', 't', expr)
336 | .clone()
337 | .where('c = 1')
338 |
339 | expr.and('b = 2')
340 |
341 | assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1 AND b = 2)', @inst.toString()
342 | assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1) WHERE (c = 1)', newinst.toString()
343 |
344 | 'with sub-queries (ticket #120)': ->
345 | newinst = @inst.from(squel.select().from('students')).limit(30)
346 | .clone()
347 | .where('c = 1')
348 | .limit(35)
349 |
350 | assert.same 'SELECT * FROM (SELECT * FROM students) LIMIT 30', @inst.toString()
351 | assert.same 'SELECT * FROM (SELECT * FROM students) WHERE (c = 1) LIMIT 35', newinst.toString()
352 |
353 | 'with complex expressions': ->
354 | expr = squel.expr().and(
355 | squel.expr().or('b = 2').or(
356 | squel.expr().and('c = 3').and('d = 4')
357 | )
358 | ).and('a = 1')
359 |
360 | newinst = @inst.from('table').left_join('table_2', 't', expr)
361 | .clone()
362 | .where('c = 1')
363 |
364 | expr.and('e = 5')
365 |
366 | assert.same @inst.toString(), 'SELECT * FROM table LEFT JOIN table_2 `t` ON ((b = 2 OR (c = 3 AND d = 4)) AND a = 1 AND e = 5)'
367 | assert.same newinst.toString(), 'SELECT * FROM table LEFT JOIN table_2 `t` ON ((b = 2 OR (c = 3 AND d = 4)) AND a = 1) WHERE (c = 1)'
368 |
369 |
370 |
371 | 'can specify block separator': ->
372 | assert.same( squel.select({separator: '\n'})
373 | .field('thing')
374 | .from('table')
375 | .toString(), """
376 | SELECT
377 | thing
378 | FROM table
379 | """
380 | )
381 |
382 | '#242 - auto-quote table names':
383 | beforeEach: ->
384 | @inst = squel
385 | .select({ autoQuoteTableNames: true })
386 | .field('name')
387 | .where('age > ?', 15)
388 |
389 | 'using string':
390 | beforeEach: ->
391 | @inst.from('students', 's')
392 |
393 | toString: ->
394 | assert.same @inst.toString(), """
395 | SELECT name FROM `students` `s` WHERE (age > 15)
396 | """
397 |
398 | toParam: ->
399 | assert.same @inst.toParam(), {
400 | "text": "SELECT name FROM `students` `s` WHERE (age > ?)"
401 | "values": [15]
402 | }
403 |
404 | 'using query builder':
405 | beforeEach: ->
406 | @inst.from(squel.select().from('students'), 's')
407 |
408 | toString: ->
409 | assert.same @inst.toString(), """
410 | SELECT name FROM (SELECT * FROM students) `s` WHERE (age > 15)
411 | """
412 |
413 | toParam: ->
414 | assert.same @inst.toParam(), {
415 | "text": "SELECT name FROM (SELECT * FROM students) `s` WHERE (age > ?)"
416 | "values": [15]
417 | }
418 |
419 |
420 | 'UNION JOINs':
421 | 'Two Queries NO Params':
422 | beforeEach: ->
423 | @qry1 = squel.select().field('name').from('students').where('age > 15')
424 | @qry2 = squel.select().field('name').from('students').where('age < 6')
425 | @qry1.union(@qry2)
426 |
427 | toString: ->
428 | assert.same @qry1.toString(), """
429 | SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))
430 | """
431 | toParam: ->
432 | assert.same @qry1.toParam(), {
433 | "text": "SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))"
434 | "values": [
435 | ]
436 | }
437 |
438 | 'Two Queries with Params':
439 | beforeEach: ->
440 | @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
441 | @qry2 = squel.select().field('name').from('students').where('age < ?', 6)
442 | @qry1.union(@qry2)
443 |
444 | toString: ->
445 | assert.same @qry1.toString(), """
446 | SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6))
447 | """
448 | toParam: ->
449 | assert.same @qry1.toParam(), {
450 | "text": "SELECT name FROM students WHERE (age > ?) UNION (SELECT name FROM students WHERE (age < ?))"
451 | "values": [
452 | 15
453 | 6
454 | ]
455 | }
456 |
457 | 'Three Queries':
458 | beforeEach: ->
459 | @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
460 | @qry2 = squel.select().field('name').from('students').where('age < 6')
461 | @qry3 = squel.select().field('name').from('students').where('age = ?', 8)
462 | @qry1.union(@qry2)
463 | @qry1.union(@qry3)
464 |
465 | toParam: ->
466 | assert.same @qry1.toParam(), {
467 | "text": "SELECT name FROM students WHERE (age > ?) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = ?))"
468 | "values": [
469 | 15
470 | 8
471 | ]
472 | }
473 | 'toParam(2)': ->
474 | assert.same @qry1.toParam({ "numberedParameters": true, "numberedParametersStartAt": 2}), {
475 | "text": "SELECT name FROM students WHERE (age > $2) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = $3))"
476 | "values": [
477 | 15
478 | 8
479 | ]
480 | }
481 |
482 | 'Multi-Parameter Query':
483 | beforeEach: ->
484 | @qry1 = squel.select().field('name').from('students').where('age > ?', 15)
485 | @qry2 = squel.select().field('name').from('students').where('age < ?', 6)
486 | @qry3 = squel.select().field('name').from('students').where('age = ?', 8)
487 | @qry4 = squel.select().field('name').from('students').where('age IN [?, ?]', 2, 10)
488 | @qry1.union(@qry2)
489 | @qry1.union(@qry3)
490 | @qry4.union_all(@qry1)
491 |
492 | toString: ->
493 | assert.same @qry4.toString(), """
494 | SELECT name FROM students WHERE (age IN [2, 10]) UNION ALL (SELECT name FROM students WHERE (age > 15) UNION (SELECT name FROM students WHERE (age < 6)) UNION (SELECT name FROM students WHERE (age = 8)))
495 | """
496 | toParam: ->
497 | assert.same @qry4.toParam({ "numberedParameters": true}), {
498 | "text": "SELECT name FROM students WHERE (age IN [$1, $2]) UNION ALL (SELECT name FROM students WHERE (age > $3) UNION (SELECT name FROM students WHERE (age < $4)) UNION (SELECT name FROM students WHERE (age = $5)))"
499 | "values": [
500 | 2
501 | 10
502 | 15
503 | 6
504 | 8
505 | ]
506 | }
507 |
508 | 'Where builder expression':
509 | beforeEach: ->
510 | @inst = squel.select().from('table').where('a = ?', 5)
511 | .where(squel.str('EXISTS(?)', squel.select().from('blah').where('b > ?', 6)))
512 | toString: ->
513 | assert.same @inst.toString(), """
514 | SELECT * FROM table WHERE (a = 5) AND (EXISTS((SELECT * FROM blah WHERE (b > 6))))
515 | """
516 | toParam: ->
517 | assert.same @inst.toParam(), {
518 | text: "SELECT * FROM table WHERE (a = ?) AND (EXISTS((SELECT * FROM blah WHERE (b > ?))))",
519 | values: [5, 6]
520 | }
521 |
522 | 'Join on builder expression':
523 | beforeEach: ->
524 | @inst = squel.select().from('table').join('table2', 't2',
525 | squel.str('EXISTS(?)', squel.select().from('blah').where('b > ?', 6))
526 | )
527 | toString: ->
528 | assert.same @inst.toString(), """
529 | SELECT * FROM table INNER JOIN table2 `t2` ON (EXISTS((SELECT * FROM blah WHERE (b > 6))))
530 | """
531 | toParam: ->
532 | assert.same @inst.toParam(), {
533 | text: "SELECT * FROM table INNER JOIN table2 `t2` ON (EXISTS((SELECT * FROM blah WHERE (b > ?))))",
534 | values: [6]
535 | }
536 |
537 | '#301 - FROM rstr() with nesting':
538 | beforeEach: ->
539 | @inst = squel.select().from(squel.rstr("generate_series(?,?,?)",1,10,2), "tblfn(odds)")
540 | toString: ->
541 | assert.same @inst.toString(), """
542 | SELECT * FROM generate_series(1,10,2) `tblfn(odds)`
543 | """
544 | toParam: ->
545 | assert.same @inst.toParam(), {
546 | text: "SELECT * FROM generate_series(?,?,?) `tblfn(odds)`",
547 | values:[1,10,2]
548 | }
549 |
550 |
551 | module?.exports[require('path').basename(__filename)] = test
552 |
--------------------------------------------------------------------------------
/test/testbase.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | sinon = require('sinon')
28 | chai = require("chai")
29 |
30 | assert = chai.assert
31 | expect = chai.expect
32 | should = chai.should()
33 |
34 |
35 | ###
36 | Assert that two items are the same.
37 |
38 | @param actual
39 | @param expected
40 | @param {String} [message] failure message
41 | ###
42 | assert.same = (actual, expected, message) ->
43 | assert.deepEqual actual, expected, message
44 |
45 |
46 |
47 |
48 | testCreator = ->
49 | test =
50 | mocker: null
51 | beforeEach: ->
52 | test.mocker = sinon.sandbox.create()
53 | afterEach: ->
54 | test.mocker.restore()
55 |
56 | test
57 |
58 |
59 | module?.exports =
60 | _: require('underscore')
61 | testCreator: testCreator
62 | assert: assert
63 | expect: expect
64 | should: should
65 |
--------------------------------------------------------------------------------
/test/update.test.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2014 Ramesh Nair (hiddentao.com)
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | ###
25 |
26 |
27 | squel = require "../dist/squel-basic"
28 | {_, testCreator, assert, expect, should} = require './testbase'
29 | test = testCreator()
30 |
31 |
32 |
33 | test['UPDATE builder'] =
34 | beforeEach: ->
35 | @func = squel.update
36 | @inst = @func()
37 |
38 | 'instanceof QueryBuilder': ->
39 | assert.instanceOf @inst, squel.cls.QueryBuilder
40 |
41 | 'constructor':
42 | 'override options': ->
43 | @inst = squel.update
44 | usingValuePlaceholders: true
45 | dummy: true
46 |
47 | expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
48 | usingValuePlaceholders: true
49 | dummy: true
50 |
51 | for block in @inst.blocks
52 | assert.same _.pick(block.options, _.keys(expectedOptions)), expectedOptions
53 |
54 | 'override blocks': ->
55 | block = new squel.cls.StringBlock('SELECT')
56 | @inst = @func {}, [block]
57 | assert.same [block], @inst.blocks
58 |
59 |
60 | 'build query':
61 | 'need to call set() first': ->
62 | @inst.table('table')
63 | assert.throws (=> @inst.toString()), 'set() needs to be called'
64 |
65 | '>> table(table, t1).set(field, 1)':
66 | beforeEach: -> @inst.table('table', 't1').set('field', 1)
67 | toString: ->
68 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1'
69 |
70 | '>> set(field2, 1.2)':
71 | beforeEach: -> @inst.set('field2', 1.2)
72 | toString: ->
73 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = 1.2'
74 |
75 | '>> set(field2, true)':
76 | beforeEach: -> @inst.set('field2', true)
77 | toString: ->
78 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = TRUE'
79 |
80 | '>> set(field2, "str")':
81 | beforeEach: -> @inst.set('field2', 'str')
82 | toString: ->
83 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = \'str\''
84 | toParam: ->
85 | assert.same @inst.toParam(), {
86 | text: 'UPDATE table `t1` SET field = ?, field2 = ?'
87 | values: [1, 'str']
88 | }
89 |
90 | '>> set(field2, "str", { dontQuote: true })':
91 | beforeEach: -> @inst.set('field2', 'str', dontQuote: true)
92 | toString: ->
93 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = str'
94 | toParam: ->
95 | assert.same @inst.toParam(), {
96 | text: 'UPDATE table `t1` SET field = ?, field2 = ?'
97 | values: [1, 'str']
98 | }
99 |
100 | '>> set(field, query builder)':
101 | beforeEach: ->
102 | @subQuery = squel.select().field('MAX(score)').from('scores')
103 | @inst.set( 'field', @subQuery )
104 | toString: ->
105 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = (SELECT MAX(score) FROM scores)'
106 | toParam: ->
107 | parameterized = @inst.toParam()
108 | assert.same parameterized.text, 'UPDATE table `t1` SET field = (SELECT MAX(score) FROM scores)'
109 | assert.same parameterized.values, []
110 |
111 | '>> set(custom value type)':
112 | beforeEach: ->
113 | class MyClass
114 | @inst.registerValueHandler MyClass, (a) -> 'abcd'
115 | @inst.set( 'field', new MyClass() )
116 | toString: ->
117 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = (abcd)'
118 | toParam: ->
119 | parameterized = @inst.toParam()
120 | assert.same parameterized.text, 'UPDATE table `t1` SET field = ?'
121 | assert.same parameterized.values, ['abcd']
122 |
123 | '>> setFields({field2: \'value2\', field3: true })':
124 | beforeEach: -> @inst.setFields({field2: 'value2', field3: true })
125 | toString: ->
126 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = \'value2\', field3 = TRUE'
127 | toParam: ->
128 | parameterized = @inst.toParam()
129 | assert.same parameterized.text, 'UPDATE table `t1` SET field = ?, field2 = ?, field3 = ?'
130 | assert.same parameterized.values, [1, 'value2', true]
131 |
132 | '>> setFields({field2: \'value2\', field: true })':
133 | beforeEach: -> @inst.setFields({field2: 'value2', field: true })
134 | toString: ->
135 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = TRUE, field2 = \'value2\''
136 |
137 | '>> set(field2, null)':
138 | beforeEach: -> @inst.set('field2', null)
139 | toString: ->
140 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = NULL'
141 | toParam: ->
142 | assert.same @inst.toParam(), { text: 'UPDATE table `t1` SET field = ?, field2 = ?', values: [1, null] }
143 |
144 | '>> table(table2)':
145 | beforeEach: -> @inst.table('table2')
146 | toString: ->
147 | assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL'
148 |
149 | '>> where(a = 1)':
150 | beforeEach: -> @inst.where('a = 1')
151 | toString: ->
152 | assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1)'
153 |
154 | '>> order(a, true)':
155 | beforeEach: -> @inst.order('a', true)
156 | toString: ->
157 | assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1) ORDER BY a ASC'
158 |
159 | '>> limit(2)':
160 | beforeEach: -> @inst.limit(2)
161 | toString: ->
162 | assert.same @inst.toString(), 'UPDATE table `t1`, table2 SET field = 1, field2 = NULL WHERE (a = 1) ORDER BY a ASC LIMIT 2'
163 |
164 | '>> table(table, t1).setFields({field1: 1, field2: \'value2\'})':
165 | beforeEach: -> @inst.table('table', 't1').setFields({field1: 1, field2: 'value2' })
166 | toString: ->
167 | assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1, field2 = \'value2\''
168 |
169 | '>> set(field1, 1.2)':
170 | beforeEach: -> @inst.set('field1', 1.2)
171 | toString: ->
172 | assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1.2, field2 = \'value2\''
173 |
174 | '>> setFields({field3: true, field4: \'value4\'})':
175 | beforeEach: -> @inst.setFields({field3: true, field4: 'value4'})
176 | toString: ->
177 | assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = 1, field2 = \'value2\', field3 = TRUE, field4 = \'value4\''
178 |
179 | '>> setFields({field1: true, field3: \'value3\'})':
180 | beforeEach: -> @inst.setFields({field1: true, field3: 'value3'})
181 | toString: ->
182 | assert.same @inst.toString(), 'UPDATE table `t1` SET field1 = TRUE, field2 = \'value2\', field3 = \'value3\''
183 |
184 | '>> table(table, t1).set("count = count + 1")':
185 | beforeEach: -> @inst.table('table', 't1').set('count = count + 1')
186 | toString: ->
187 | assert.same @inst.toString(), 'UPDATE table `t1` SET count = count + 1'
188 |
189 | 'str()':
190 | beforeEach: -> @inst.table('students').set('field', squel.str('GETDATE(?, ?)', 2014, '"feb"'))
191 | toString: ->
192 | assert.same 'UPDATE students SET field = (GETDATE(2014, \'"feb"\'))', @inst.toString()
193 | toParam: ->
194 | assert.same { text: 'UPDATE students SET field = (GETDATE(?, ?))', values: [2014, '"feb"'] }, @inst.toParam()
195 |
196 | 'string formatting':
197 | beforeEach: ->
198 | @inst.updateOptions {
199 | stringFormatter: (str) -> "N'#{str}'"
200 | }
201 | @inst.table('students').set('field', 'jack')
202 | toString: ->
203 | assert.same 'UPDATE students SET field = N\'jack\'', @inst.toString()
204 | toParam: ->
205 | assert.same { text: 'UPDATE students SET field = ?', values: ["jack"] }, @inst.toParam()
206 |
207 | 'fix for hiddentao/squel#63': ->
208 | newinst = @inst.table('students').set('field = field + 1')
209 | newinst.set('field2', 2).set('field3', true)
210 | assert.same { text: 'UPDATE students SET field = field + 1, field2 = ?, field3 = ?', values: [2, true] }, @inst.toParam()
211 |
212 | 'dontQuote and replaceSingleQuotes set(field2, "ISNULL(\'str\', str)", { dontQuote: true })':
213 | beforeEach: ->
214 | @inst = squel.update replaceSingleQuotes: true
215 | @inst.table('table', 't1').set('field', 1)
216 | @inst.set('field2', "ISNULL('str', str)", dontQuote: true)
217 | toString: ->
218 | assert.same @inst.toString(), 'UPDATE table `t1` SET field = 1, field2 = ISNULL(\'str\', str)'
219 | toParam: ->
220 | assert.same @inst.toParam(), {
221 | text: 'UPDATE table `t1` SET field = ?, field2 = ?'
222 | values: [1, "ISNULL('str', str)"]
223 | }
224 |
225 | 'fix for #223 - careful about array looping methods':
226 | beforeEach: ->
227 | Array::substr = () -> 1
228 | afterEach: ->
229 | delete Array::substr;
230 | check: ->
231 | @inst = squel.update()
232 | .table('users')
233 | .where('id = ?', 123)
234 | .set('active', 1)
235 | .set('regular', 0)
236 | .set('moderator',1)
237 |
238 | assert.same @inst.toParam(), {
239 | text: 'UPDATE users SET active = ?, regular = ?, moderator = ? WHERE (id = ?)',
240 | values: [1, 0, 1, 123],
241 | }
242 |
243 | 'fix for #225 - autoquoting field names': ->
244 | @inst = squel.update(autoQuoteFieldNames: true)
245 | .table('users')
246 | .where('id = ?', 123)
247 | .set('active', 1)
248 | .set('regular', 0)
249 | .set('moderator',1)
250 |
251 | assert.same @inst.toParam(), {
252 | text: 'UPDATE users SET `active` = ?, `regular` = ?, `moderator` = ? WHERE (id = ?)',
253 | values: [1, 0, 1, 123],
254 | }
255 |
256 | 'fix for #243 - ampersand in conditions':
257 | beforeEach: ->
258 | @inst = squel.update().table('a').set('a = a & ?', 2)
259 | toString: ->
260 | assert.same @inst.toString(), 'UPDATE a SET a = a & 2'
261 | toParam: ->
262 | assert.same @inst.toParam(), {
263 | text: 'UPDATE a SET a = a & ?',
264 | values: [2],
265 | }
266 |
267 | 'cloning': ->
268 | newinst = @inst.table('students').set('field', 1).clone()
269 | newinst.set('field', 2).set('field2', true)
270 |
271 | assert.same 'UPDATE students SET field = 1', @inst.toString()
272 | assert.same 'UPDATE students SET field = 2, field2 = TRUE', newinst.toString()
273 |
274 |
275 |
276 | module?.exports[require('path').basename(__filename)] = test
277 |
--------------------------------------------------------------------------------