├── .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 | [![Build Status](https://secure.travis-ci.org/hiddentao/squel.svg?branch=master)](http://travis-ci.org/hiddentao/squel) 6 | [![CDNJS](https://img.shields.io/cdnjs/v/squel.svg)](https://cdnjs.com/libraries/squel) 7 | [![NPM module](https://badge.fury.io/js/squel.svg)](https://badge.fury.io/js/squel) 8 | [![NPM downloads](https://img.shields.io/npm/dm/squel.svg?maxAge=2592000)](https://www.npmjs.com/package/squel) 9 | [![Join the chat at https://discord.gg/PBAR2Bz](https://img.shields.io/badge/discord-join%20chat-738bd7.svg)](https://discord.gg/PBAR2Bz) 10 | [![Follow on Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow&maxAge=2592000)](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 | --------------------------------------------------------------------------------