### Copyright (c) 2014 Ramesh Nair (hiddentao.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### squel = require "../dist/squel-basic" {_, testCreator, assert, expect, should} = require './testbase' test = testCreator() test['Version number'] = assert.same squel.VERSION, require('../package.json').version test['Default flavour'] = assert.isNull squel.flavour test['internal methods'] = '_getObjectClassName': -> s = 'a string' b = new Object() c = new Error() d = 1 assert.same squel.cls._getObjectClassName(0), undefined assert.same squel.cls._getObjectClassName(true), 'Boolean' assert.same squel.cls._getObjectClassName(1.2), 'Number' assert.same squel.cls._getObjectClassName('a string'), 'String' assert.same squel.cls._getObjectClassName(new Object), 'Object' assert.same squel.cls._getObjectClassName(new Error), 'Error' test['Cloneable base class'] = '>> clone()': -> class Child extends squel.cls.Cloneable constructor: -> @a = 1 @b = 2.2 @c = true @d = 'str' @e = [1] @f = { a: 1 } child = new Child() copy = child.clone() assert.instanceOf copy, Child child.a = 2 child.b = 3.2 child.c = false child.d = 'str2' child.e.push(2) child.f.b = 1 assert.same copy.a, 1 assert.same copy.b, 2.2 assert.same copy.c, true assert.same copy.d, 'str' assert.same copy.e, [1] assert.same copy.f, { a: 1 } test['Default query builder options'] = 'default options': -> assert.same { autoQuoteTableNames: false autoQuoteFieldNames: false autoQuoteAliasNames: true useAsForTableAliasNames: false nameQuoteCharacter: '`' tableAliasQuoteCharacter: '`' fieldAliasQuoteCharacter: '"' valueHandlers: [] parameterCharacter: '?' numberedParameters: false numberedParametersPrefix: '$' numberedParametersStartAt: 1 replaceSingleQuotes: false singleQuoteReplacement: '\'\'' separator: ' ' stringFormatter: null }, squel.cls.DefaultQueryBuilderOptions test['Register global custom value handler'] = 'beforeEach': -> @originalHandlers = [].concat(squel.cls.globalValueHandlers) squel.cls.globalValueHandlers = [] 'afterEach': -> squel.cls.globalValueHandlers = @originalHandlers 'default': -> handler = -> 'test' squel.registerValueHandler(Date, handler) squel.registerValueHandler(Object, handler) squel.registerValueHandler('boolean', handler) assert.same 3, squel.cls.globalValueHandlers.length assert.same { type: Date, handler: handler }, squel.cls.globalValueHandlers[0] assert.same { type: Object, handler: handler }, squel.cls.globalValueHandlers[1] assert.same { type: 'boolean', handler: handler }, squel.cls.globalValueHandlers[2] 'type should be class constructor': -> assert.throws (-> squel.registerValueHandler 1, null), "type must be a class constructor or string" 'handler should be function': -> class MyClass assert.throws (-> squel.registerValueHandler MyClass, 1), 'handler must be a function' 'overrides existing handler': -> handler = -> 'test' handler2 = -> 'test2' squel.registerValueHandler(Date, handler) squel.registerValueHandler(Date, handler2) assert.same 1, squel.cls.globalValueHandlers.length assert.same { type: Date, handler: handler2 }, squel.cls.globalValueHandlers[0] test['str()'] = constructor: -> f = squel.str('GETDATE(?)', 12, 23) assert.ok (f instanceof squel.cls.FunctionBlock) assert.same 'GETDATE(?)', f._strings[0] assert.same [12, 23], f._values[0] 'custom value handler': beforeEach: -> @inst = squel.str('G(?,?)', 12, 23, 65) handlerConfig = _.find squel.cls.globalValueHandlers, (hc) -> hc.type is squel.cls.FunctionBlock @handler = handlerConfig.handler toString: -> assert.same @inst.toString(), @handler(@inst) toParam: -> assert.same @inst.toParam(), @handler(@inst, true) test['Load an SQL flavour'] = beforeEach: -> @flavoursBackup = squel.flavours squel.flavours = {} afterEach: -> squel.flavours = @flavoursBackup 'invalid flavour': -> assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test' 'flavour reference should be a function': -> squel.flavours['test'] = 'blah' assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test' 'flavour setup function gets executed': -> squel.flavours['test'] = test.mocker.spy() ret = squel.useFlavour 'test' assert.ok squel.flavours['test'].calledOnce assert.ok !!ret.select() 'can switch flavours': -> squel.flavours['test'] = test.mocker.spy( (s) -> s.cls.dummy = 1 ) squel.flavours['test2'] = test.mocker.spy( (s) -> s.cls.dummy2 = 2 ) ret = squel.useFlavour 'test' assert.same ret.cls.dummy, 1 ret = squel.useFlavour 'test2' assert.same ret.cls.dummy, undefined assert.same ret.cls.dummy2, 2 ret = squel.useFlavour() assert.same ret.cls.dummy, undefined assert.same ret.cls.dummy2, undefined 'can get current flavour': -> flavour = 'test' squel.flavours[flavour] = test.mocker.spy() ret = squel.useFlavour flavour assert.same ret.flavour, flavour test['Builder base class'] = beforeEach: -> @cls = squel.cls.BaseBuilder @inst = new @cls @originalHandlers = [].concat(squel.cls.globalValueHandlers) afterEach: -> squel.cls.globalValueHandlers = @originalHandlers 'instanceof Cloneable': -> assert.instanceOf @inst, squel.cls.Cloneable 'constructor': 'default options': -> assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options 'overridden options': -> @inst = new @cls dummy1: 'str' dummy2: 12.3 usingValuePlaceholders: true dummy3: true, globalValueHandlers: [1] expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions, dummy1: 'str' dummy2: 12.3 usingValuePlaceholders: true dummy3: true globalValueHandlers: [1] assert.same expectedOptions, @inst.options 'registerValueHandler': 'afterEach': -> squel.cls.globalValueHandlers = [] 'default': -> handler = -> 'test' @inst.registerValueHandler(Date, handler) @inst.registerValueHandler(Object, handler) @inst.registerValueHandler('number', handler) assert.same 3, @inst.options.valueHandlers.length assert.same { type: Date, handler: handler }, @inst.options.valueHandlers[0] assert.same { type: Object, handler: handler }, @inst.options.valueHandlers[1] assert.same { type: 'number', handler: handler }, @inst.options.valueHandlers[2] 'type should be class constructor': -> assert.throws (=> @inst.registerValueHandler 1, null), "type must be a class constructor or string" 'handler should be function': -> class MyClass assert.throws (=> @inst.registerValueHandler MyClass, 1), 'handler must be a function' 'returns instance for chainability': -> handler = -> 'test' assert.same @inst, @inst.registerValueHandler(Date, handler) 'overrides existing handler': -> handler = -> 'test' handler2 = -> 'test2' @inst.registerValueHandler(Date, handler) @inst.registerValueHandler(Date, handler2) assert.same 1, @inst.options.valueHandlers.length assert.same { type: Date, handler: handler2 }, @inst.options.valueHandlers[0] 'does not touch global value handlers list': -> oldGlobalHandlers = squel.cls.globalValueHandlers handler = -> 'test' @inst.registerValueHandler(Date, handler) assert.same oldGlobalHandlers, squel.cls.globalValueHandlers '_sanitizeExpression': 'if Expression': 'empty expression': -> e = squel.expr() assert.same e, @inst._sanitizeExpression(e) 'non-empty expression': -> e = squel.expr().and("s.name <> 'Fred'") assert.same e, @inst._sanitizeExpression(e) 'if string': -> s = 'BLA BLA' assert.same 'BLA BLA', @inst._sanitizeExpression(s) 'if neither Expression nor String': -> testFn = => @inst._sanitizeExpression(1) assert.throws testFn, 'expression must be a stringĀ or Expression instance' '_sanitizeName': beforeEach: -> test.mocker.spy @inst, '_sanitizeName' 'if string': -> assert.same 'bla', @inst._sanitizeName('bla') 'if boolean': -> assert.throws (=> @inst._sanitizeName(true, 'bla')), 'bla must be a string' 'if integer': -> assert.throws (=> @inst._sanitizeName(1)), 'undefined must be a string' 'if float': -> assert.throws (=> @inst._sanitizeName(1.2, 'meh')), 'meh must be a string' 'if array': -> assert.throws (=> @inst._sanitizeName([1], 'yes')), 'yes must be a string' 'if object': -> assert.throws (=> @inst._sanitizeName(new Object, 'yes')), 'yes must be a string' 'if null': -> assert.throws (=> @inst._sanitizeName(null, 'no')), 'no must be a string' 'if undefined': -> assert.throws (=> @inst._sanitizeName(undefined, 'no')), 'no must be a string' '_sanitizeField': 'default': -> test.mocker.spy @inst, '_sanitizeName' assert.same 'abc', @inst._sanitizeField('abc') assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field name' 'QueryBuilder': -> s = squel.select().from('scores').field('MAX(score)') assert.same s, @inst._sanitizeField(s) '_sanitizeBaseBuilder': 'is not base builder': -> assert.throws (=> @inst._sanitizeBaseBuilder(null)), 'must be a BaseBuilder instance' 'is a query builder': -> qry = squel.select() assert.same qry, @inst._sanitizeBaseBuilder(qry) '_sanitizeTable': 'default': -> test.mocker.spy @inst, '_sanitizeName' assert.same 'abc', @inst._sanitizeTable('abc') assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table' 'not a string': -> assert.throws (=> @inst._sanitizeTable(null)), 'table name must be a string or a query builder' 'query builder': -> select = squel.select() assert.same select, @inst._sanitizeTable(select, true) '_sanitizeFieldAlias': -> 'default': -> test.mocker.spy @inst, '_sanitizeName' @inst._sanitizeFieldAlias('abc') assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field alias' '_sanitizeTableAlias': -> 'default': -> test.mocker.spy @inst, '_sanitizeName' @inst._sanitizeTableAlias('abc') assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table alias' '_sanitizeLimitOffset': 'undefined': -> assert.throws (=> @inst._sanitizeLimitOffset()), 'limit/offset must be >= 0' 'null': -> assert.throws (=> @inst._sanitizeLimitOffset null), 'limit/offset must be >= 0' 'float': -> assert.same 1, @inst._sanitizeLimitOffset 1.2 'boolean': -> assert.throws (=> @inst._sanitizeLimitOffset false), 'limit/offset must be >= 0' 'string': -> assert.same 2, @inst._sanitizeLimitOffset '2' 'array': -> assert.same 3, @inst._sanitizeLimitOffset [3] 'object': -> assert.throws (=> @inst._sanitizeLimitOffset(new Object)), 'limit/offset must be >= 0' 'number >= 0': -> assert.same 0, @inst._sanitizeLimitOffset 0 assert.same 1, @inst._sanitizeLimitOffset 1 'number < 0': -> assert.throws (=> @inst._sanitizeLimitOffset(-1)), 'limit/offset must be >= 0' '_sanitizeValue': beforeEach: -> test.mocker.spy @inst, '_sanitizeValue' afterEach: -> squel.cls.globalValueHandlers = [] 'if string': -> assert.same 'bla', @inst._sanitizeValue('bla') 'if boolean': -> assert.same true, @inst._sanitizeValue(true) assert.same false, @inst._sanitizeValue(false) 'if integer': -> assert.same -1, @inst._sanitizeValue(-1) assert.same 0, @inst._sanitizeValue(0) assert.same 1, @inst._sanitizeValue(1) 'if float': -> assert.same -1.2, @inst._sanitizeValue(-1.2) assert.same 1.2, @inst._sanitizeValue(1.2) 'if array': -> assert.throws (=> @inst._sanitizeValue([1])), 'field value must be a string, number, boolean, null or one of the registered custom value types' 'if object': -> assert.throws (=> @inst._sanitizeValue(new Object)), 'field value must be a string, number, boolean, null or one of the registered custom value types' 'if null': -> assert.same null, @inst._sanitizeValue(null) 'if BaseBuilder': -> s = squel.select() assert.same s, @inst._sanitizeValue(s) 'if undefined': -> assert.throws (=> @inst._sanitizeValue(undefined)), 'field value must be a string, number, boolean, null or one of the registered custom value types' 'custom handlers': 'global': -> squel.registerValueHandler(Date, _.identity) date = new Date assert.same date, @inst._sanitizeValue(date) 'instance': -> @inst.registerValueHandler(Date, _.identity) date = new Date assert.same date, @inst._sanitizeValue(date) '_escapeValue': -> @inst.options.replaceSingleQuotes = false assert.same "te'st", @inst._escapeValue("te'st") @inst.options.replaceSingleQuotes = true assert.same "te''st", @inst._escapeValue("te'st") @inst.options.singleQuoteReplacement = '--' assert.same "te--st", @inst._escapeValue("te'st") '_formatTableName': 'default': -> assert.same 'abc', @inst._formatTableName('abc') 'auto quote names': beforeEach: -> @inst.options.autoQuoteTableNames = true 'default quote character': -> assert.same '`abc`', @inst._formatTableName('abc') 'custom quote character': -> @inst.options.nameQuoteCharacter = '|' assert.same '|abc|', @inst._formatTableName('abc') '_formatTableAlias': 'default': -> assert.same '`abc`', @inst._formatTableAlias('abc') 'custom quote character': -> @inst.options.tableAliasQuoteCharacter = '~' assert.same '~abc~', @inst._formatTableAlias('abc') 'auto quote alias names is OFF': -> @inst.options.autoQuoteAliasNames = false assert.same 'abc', @inst._formatTableAlias('abc') 'AS is turned ON': -> @inst.options.autoQuoteAliasNames = false @inst.options.useAsForTableAliasNames = true assert.same 'AS abc', @inst._formatTableAlias('abc') '_formatFieldAlias': default: -> assert.same '"abc"', @inst._formatFieldAlias('abc') 'custom quote character': -> @inst.options.fieldAliasQuoteCharacter = '~' assert.same '~abc~', @inst._formatFieldAlias('abc') 'auto quote alias names is OFF': -> @inst.options.autoQuoteAliasNames = false assert.same 'abc', @inst._formatFieldAlias('abc') '_formatFieldName': default: -> assert.same 'abc', @inst._formatFieldName('abc') 'auto quote names': beforeEach: -> @inst.options.autoQuoteFieldNames = true 'default quote character': -> assert.same '`abc`.`def`', @inst._formatFieldName('abc.def') 'do not quote *': -> assert.same '`abc`.*', @inst._formatFieldName('abc.*') 'custom quote character': -> @inst.options.nameQuoteCharacter = '|' assert.same '|abc|.|def|', @inst._formatFieldName('abc.def') 'ignore periods when quoting': -> assert.same '`abc.def`', @inst._formatFieldName('abc.def', ignorePeriodsForFieldNameQuotes: true) '_formatCustomValue': 'not a custom value type': -> assert.same null, @inst._formatCustomValue(null) assert.same 'abc', @inst._formatCustomValue('abc') assert.same 12, @inst._formatCustomValue(12) assert.same 1.2, @inst._formatCustomValue(1.2) assert.same true, @inst._formatCustomValue(true) assert.same false, @inst._formatCustomValue(false) 'custom value type': 'global': -> class MyClass myObj = new MyClass squel.registerValueHandler MyClass, () -> 3.14 squel.registerValueHandler 'boolean', (v) -> 'a' + v assert.same 3.14, @inst._formatCustomValue(myObj) assert.same 'atrue', @inst._formatCustomValue(true) 'instance': -> class MyClass myObj = new MyClass @inst.registerValueHandler MyClass, () -> 3.14 @inst.registerValueHandler 'number', (v) -> v + 'a' assert.same 3.14, @inst._formatCustomValue(myObj) assert.same '5.2a', @inst._formatCustomValue(5.2) 'instance handler takes precedence over global': -> @inst.registerValueHandler Date, (d) -> 'hello' squel.registerValueHandler Date, (d) -> 'goodbye' assert.same "hello", @inst._formatCustomValue(new Date) @inst = new @cls valueHandlers: [] assert.same "goodbye", @inst._formatCustomValue(new Date) 'whether to format for parameterized output': -> @inst.registerValueHandler Date, (d, asParam) -> return if asParam then 'foo' else 'bar' val = new Date() assert.same 'foo', @inst._formatCustomValue(val, true) assert.same 'bar', @inst._formatCustomValue(val) '_formatValueForParamArray': 'Query builder': -> s = squel.select().from('table') assert.same s, @inst._formatValueForParamArray(s) 'else calls _formatCustomValue': -> spy = test.mocker.stub @inst, '_formatCustomValue', (v, asParam) -> 'test' + (if asParam then 'foo' else 'bar') assert.same 'testfoo', @inst._formatValueForParamArray(null) assert.same 'testfoo', @inst._formatValueForParamArray('abc') assert.same 'testfoo', @inst._formatValueForParamArray(12) assert.same 'testfoo', @inst._formatValueForParamArray(1.2) assert.same 'testfoo', @inst._formatValueForParamArray(true) assert.same 'testfoo', @inst._formatValueForParamArray(false) assert.same 6, spy.callCount 'Array - recursively calls itself on each element': -> spy = test.mocker.spy @inst, '_formatValueForParamArray' v = [ squel.select().from('table'), 1.2 ] res = @inst._formatValueForParamArray(v) assert.same v, res assert.same 3, spy.callCount assert.ok spy.calledWith v[0] assert.ok spy.calledWith v[1] '_formatValueForQueryString': 'null': -> assert.same 'NULL', @inst._formatValueForQueryString(null) 'boolean': -> assert.same 'TRUE', @inst._formatValueForQueryString(true) assert.same 'FALSE', @inst._formatValueForQueryString(false) 'integer': -> assert.same 12, @inst._formatValueForQueryString(12) 'float': -> assert.same 1.2, @inst._formatValueForQueryString(1.2) 'string': 'have string formatter function': -> @inst.options.stringFormatter = (str) -> "N(#{str})" assert.same "N(test)", @inst._formatValueForQueryString('test') 'default': -> escapedValue = undefined test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str assert.same "'test'", @inst._formatValueForQueryString('test') assert.same "'test'", @inst._formatValueForQueryString('test') assert.ok @inst._escapeValue.calledWithExactly('test') escapedValue = 'blah' assert.same "'blah'", @inst._formatValueForQueryString('test') 'dont quote': -> escapedValue = undefined test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str assert.same "test", @inst._formatValueForQueryString('test', dontQuote: true ) assert.ok @inst._escapeValue.notCalled 'Array - recursively calls itself on each element': -> spy = test.mocker.spy @inst, '_formatValueForQueryString' expected = "('test', 123, TRUE, 1.2, NULL)" assert.same expected, @inst._formatValueForQueryString([ 'test', 123, true, 1.2, null ]) assert.same 6, spy.callCount assert.ok spy.calledWith 'test' assert.ok spy.calledWith 123 assert.ok spy.calledWith true assert.ok spy.calledWith 1.2 assert.ok spy.calledWith null 'BaseBuilder': -> spy = test.mocker.stub @inst, '_applyNestingFormatting', (v) => "{{#{v}}}" s = squel.select().from('table') assert.same '{{SELECT * FROM table}}', @inst._formatValueForQueryString(s) 'checks to see if it is custom value type first': -> test.mocker.stub @inst, '_formatCustomValue', (val, asParam) -> 12 + (if asParam then 25 else 65) test.mocker.stub @inst, '_applyNestingFormatting', (v) -> "{#{v}}" assert.same '{77}', @inst._formatValueForQueryString(123) '_applyNestingFormatting': default: -> assert.same '(77)', @inst._applyNestingFormatting('77') assert.same '((77)', @inst._applyNestingFormatting('(77') assert.same '(77))', @inst._applyNestingFormatting('77)') assert.same '(77)', @inst._applyNestingFormatting('(77)') 'no nesting': -> assert.same '77', @inst._applyNestingFormatting('77', false) '_buildString': 'empty': -> assert.same @inst._buildString('', []), { text: '', values: [], } 'no params': 'non-parameterized': -> assert.same @inst._buildString('abc = 3', []), { text: 'abc = 3', values: [] } 'parameterized': -> assert.same @inst._buildString('abc = 3', [], { buildParameterized: true }), { text: 'abc = 3', values: [] } 'non-array': 'non-parameterized': -> assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null]), { text: 'a = 2 \'abc\' FALSE NULL', values: [] } 'parameterized': -> assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null], { buildParameterized: true }), { text: 'a = ? ? ? ?', values: [2, 'abc', false, null] } 'array': -> 'non-parameterized': -> assert.same @inst._buildString('a = ?', [[1,2,3]]), { text: 'a = (1, 2, 3)', values: [], } 'parameterized': -> assert.same @inst._buildString('a = ?', [[1,2,3]], { buildParameterized: true }), { text: 'a = (?, ?, ?)', values: [1, 2, 3] } 'nested builder': -> beforeEach: @s = squel.select().from('master').where('b = ?', 5) 'non-parameterized': -> assert.same @inst._buildString('a = ?', [@s]), { text: 'a = (SELECT * FROM master WHERE (b = ?))', values: [5] } 'parameterized': -> assert.same @inst._buildString('a = ?', [@s], { buildParameterized: true }), { text: 'a = (SELECT * FROM master WHERE (b = ?))', values: [5] } 'return nested output': 'non-parameterized': -> assert.same @inst._buildString('a = ?', [3], { nested: true }), { text: '(a = 3)', values: [] } 'parameterized': -> assert.same @inst._buildString('a = ?', [3], { buildParameterized: true, nested: true }), { text: '(a = ?)', values: [3] } 'string formatting options': -> options = formattingOptions: dontQuote: true assert.same @inst._buildString('a = ?', ['NOW()'], options), { text: 'a = NOW()', values: [] } 'custom parameter character': -> beforeEach: -> @inst.options.parameterCharacter = '@@' 'non-parameterized': -> assert.same @inst._buildString('a = @@', [[1,2,3]]), { text: 'a = (1, 2, 3)', values: [], } 'parameterized': -> assert.same @inst._buildString('a = @@', [[1,2,3]]), { text: 'a = (@@, @@, @@)', values: [1,2,3], } '_buildManyStrings': 'empty': -> assert.same @inst._buildManyStrings([], []), { text: '', values: [], } 'simple': beforeEach: -> @strings = [ 'a = ?', 'b IN ? AND c = ?' ] @values = [ ['elephant'], [[1,2,3], 4] ] 'non-parameterized': -> assert.same @inst._buildManyStrings(@strings, @values), { text: 'a = \'elephant\' b IN (1, 2, 3) AND c = 4', values: [], } 'parameterized': -> assert.same @inst._buildManyStrings(@strings, @values, { buildParameterized: true }), { text: 'a = ? b IN (?, ?, ?) AND c = ?', values: ['elephant', 1, 2, 3, 4], } 'return nested': -> 'non-parameterized': -> assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { nested: true }), { text: '(a = 1 b = 2)', values: [], } 'parameterized': -> assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true, nested: true }), { text: '(a = ? b = ?)', values: [1, 2], } 'custom separator': -> 'non-parameterized': -> @inst.options.separator = '|' assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]]), { text: '(a = 1|b = 2)', values: [], } 'parameterized': -> assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true}), { text: '(a = ?|b = ?)', values: [1, 2], } 'toParam': -> spy = test.mocker.stub @inst, '_toParamString', -> { text: 'dummy' values: [1] } options = {test: 2} assert.same @inst.toParam(options), { text: 'dummy' values: [1] } spy.should.have.been.calledOnce assert.same spy.getCall(0).args[0].test, 2 assert.same spy.getCall(0).args[0].buildParameterized, true 'toString': -> spy = test.mocker.stub @inst, '_toParamString', -> { text: 'dummy' values: [1] } options = {test: 2} assert.same @inst.toString(options), 'dummy' spy.should.have.been.calledOnce assert.same spy.getCall(0).args[0], options test['QueryBuilder base class'] = beforeEach: -> @cls = squel.cls.QueryBuilder @inst = new @cls 'instanceof base builder': -> assert.instanceOf @inst, squel.cls.BaseBuilder 'constructor': 'default options': -> assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options 'overridden options': -> @inst = new @cls dummy1: 'str' dummy2: 12.3 usingValuePlaceholders: true dummy3: true expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions, dummy1: 'str' dummy2: 12.3 usingValuePlaceholders: true dummy3: true assert.same expectedOptions, @inst.options 'default blocks - none': -> assert.same [], @inst.blocks 'blocks passed in': 'exposes block methods': -> limitExposedMethodsSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'exposedMethods'); distinctExposedMethodsSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'exposedMethods'); limitSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'limit') distinctSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'distinct') blocks = [ new squel.cls.LimitBlock(), new squel.cls.DistinctBlock() ] @inst = new @cls({}, blocks) assert.ok limitExposedMethodsSpy.calledOnce assert.ok distinctExposedMethodsSpy.calledOnce assert.typeOf @inst.distinct, 'function' assert.typeOf @inst.limit, 'function' assert.same @inst, @inst.limit(2) assert.ok limitSpy.calledOnce assert.ok limitSpy.calledOn(blocks[0]) assert.same @inst, @inst.distinct() assert.ok distinctSpy.calledOnce assert.ok distinctSpy.calledOn(blocks[1]) 'cannot expose the same method twice': -> blocks = [ new squel.cls.DistinctBlock(), new squel.cls.DistinctBlock() ] try @inst = new @cls({}, blocks) throw new Error 'should not reach here' catch err assert.same 'Error: Builder already has a builder method called: distinct', err.toString() 'updateOptions()': 'updates query builder options': -> oldOptions = _.extend({}, @inst.options) @inst.updateOptions updated: false expected = _.extend oldOptions, updated: false assert.same expected, @inst.options 'updates building block options': -> @inst.blocks = [ new squel.cls.Block() ] oldOptions = _.extend({}, @inst.blocks[0].options) @inst.updateOptions updated: false expected = _.extend oldOptions, updated: false assert.same expected, @inst.blocks[0].options 'toString()': 'returns empty if no blocks': -> assert.same '', @inst.toString() 'skips empty block strings': -> @inst.blocks = [ new squel.cls.StringBlock({}, ''), ] assert.same '', @inst.toString() 'returns final query string': -> i = 1 toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', -> { text: "ret#{++i}" values: [] } @inst.blocks = [ new squel.cls.StringBlock({}, 'STR1'), new squel.cls.StringBlock({}, 'STR2'), new squel.cls.StringBlock({}, 'STR3') ] assert.same 'ret2 ret3 ret4', @inst.toString() assert.ok toStringSpy.calledThrice assert.ok toStringSpy.calledOn(@inst.blocks[0]) assert.ok toStringSpy.calledOn(@inst.blocks[1]) assert.ok toStringSpy.calledOn(@inst.blocks[2]) 'toParam()': 'returns empty if no blocks': -> assert.same { text: '', values: [] }, @inst.toParam() 'skips empty block strings': -> @inst.blocks = [ new squel.cls.StringBlock({}, ''), ] assert.same { text: '', values: [] }, @inst.toParam() 'returns final query string': -> @inst.blocks = [ new squel.cls.StringBlock({}, 'STR1'), new squel.cls.StringBlock({}, 'STR2'), new squel.cls.StringBlock({}, 'STR3') ] i = 1 toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', -> { text: "ret#{++i}" values: [] } assert.same { text: 'ret2 ret3 ret4', values: [] }, @inst.toParam() assert.ok toStringSpy.calledThrice assert.ok toStringSpy.calledOn(@inst.blocks[0]) assert.ok toStringSpy.calledOn(@inst.blocks[1]) assert.ok toStringSpy.calledOn(@inst.blocks[2]) 'returns query with unnumbered parameters': -> @inst.blocks = [ new squel.cls.WhereBlock({}), ] @inst.blocks[0]._toParamString = test.mocker.spy -> { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3] } assert.same { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]}, @inst.toParam() 'returns query with numbered parameters': -> @inst = new @cls numberedParameters: true @inst.blocks = [ new squel.cls.WhereBlock({}), ] test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3] } assert.same @inst.toParam(), { text: 'a = $1 AND b in ($2, $3)', values: [1, 2, 3]} 'returns query with numbered parameters and custom prefix': -> @inst = new @cls numberedParameters: true numberedParametersPrefix: '&%' @inst.blocks = [ new squel.cls.WhereBlock({}), ] test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3] } assert.same @inst.toParam(), { text: 'a = &%1 AND b in (&%2, &%3)', values: [1, 2, 3]} 'cloning': 'blocks get cloned properly': -> blockCloneSpy = test.mocker.spy(squel.cls.StringBlock.prototype, 'clone') @inst.blocks = [ new squel.cls.StringBlock({}, 'TEST') ] newinst = @inst.clone() @inst.blocks[0].str = 'TEST2' assert.same 'TEST', newinst.blocks[0].toString() 'registerValueHandler': 'beforEach': -> @originalHandlers = [].concat(squel.cls.globalValueHandlers) 'afterEach': -> squel.cls.globalValueHandlers = @originalHandlers 'calls through to base class method': -> baseBuilderSpy = test.mocker.spy(squel.cls.BaseBuilder.prototype, 'registerValueHandler') handler = -> 'test' @inst.registerValueHandler(Date, handler) @inst.registerValueHandler('number', handler) assert.ok baseBuilderSpy.calledTwice assert.ok baseBuilderSpy.calledOn(@inst) 'returns instance for chainability': -> handler = -> 'test' assert.same @inst, @inst.registerValueHandler(Date, handler) 'calls through to blocks': -> @inst.blocks = [ new squel.cls.StringBlock({}, ''), ] baseBuilderSpy = test.mocker.spy(@inst.blocks[0], 'registerValueHandler') handler = -> 'test' @inst.registerValueHandler(Date, handler) assert.ok baseBuilderSpy.calledOnce assert.ok baseBuilderSpy.calledOn(@inst.blocks[0]) 'get block': 'valid': -> block = new squel.cls.FunctionBlock() @inst.blocks.push(block) assert.same block, @inst.getBlock(squel.cls.FunctionBlock) 'invalid': -> assert.throws (-> @inst.getBlock(squel.cls.FunctionBlock) ) module?.exports[require('path').basename(__filename)] = test