baseclasses.test.coffee 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147
  1. ###
  2. Copyright (c) 2014 Ramesh Nair (hiddentao.com)
  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. The above copyright notice and this permission notice shall be
  12. included in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  15. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  17. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  20. OTHER DEALINGS IN THE SOFTWARE.
  21. ###
  22. squel = require "../dist/squel-basic"
  23. {_, testCreator, assert, expect, should} = require './testbase'
  24. test = testCreator()
  25. test['Version number'] =
  26. assert.same squel.VERSION, require('../package.json').version
  27. test['Default flavour'] =
  28. assert.isNull squel.flavour
  29. test['internal methods'] =
  30. '_getObjectClassName': ->
  31. s = 'a string'
  32. b = new Object()
  33. c = new Error()
  34. d = 1
  35. assert.same squel.cls._getObjectClassName(0), undefined
  36. assert.same squel.cls._getObjectClassName(true), 'Boolean'
  37. assert.same squel.cls._getObjectClassName(1.2), 'Number'
  38. assert.same squel.cls._getObjectClassName('a string'), 'String'
  39. assert.same squel.cls._getObjectClassName(new Object), 'Object'
  40. assert.same squel.cls._getObjectClassName(new Error), 'Error'
  41. test['Cloneable base class'] =
  42. '>> clone()': ->
  43. class Child extends squel.cls.Cloneable
  44. constructor: ->
  45. @a = 1
  46. @b = 2.2
  47. @c = true
  48. @d = 'str'
  49. @e = [1]
  50. @f = { a: 1 }
  51. child = new Child()
  52. copy = child.clone()
  53. assert.instanceOf copy, Child
  54. child.a = 2
  55. child.b = 3.2
  56. child.c = false
  57. child.d = 'str2'
  58. child.e.push(2)
  59. child.f.b = 1
  60. assert.same copy.a, 1
  61. assert.same copy.b, 2.2
  62. assert.same copy.c, true
  63. assert.same copy.d, 'str'
  64. assert.same copy.e, [1]
  65. assert.same copy.f, { a: 1 }
  66. test['Default query builder options'] =
  67. 'default options': ->
  68. assert.same {
  69. autoQuoteTableNames: false
  70. autoQuoteFieldNames: false
  71. autoQuoteAliasNames: true
  72. useAsForTableAliasNames: false
  73. nameQuoteCharacter: '`'
  74. tableAliasQuoteCharacter: '`'
  75. fieldAliasQuoteCharacter: '"'
  76. valueHandlers: []
  77. parameterCharacter: '?'
  78. numberedParameters: false
  79. numberedParametersPrefix: '$'
  80. numberedParametersStartAt: 1
  81. replaceSingleQuotes: false
  82. singleQuoteReplacement: '\'\''
  83. separator: ' '
  84. stringFormatter: null
  85. }, squel.cls.DefaultQueryBuilderOptions
  86. test['Register global custom value handler'] =
  87. 'beforeEach': ->
  88. @originalHandlers = [].concat(squel.cls.globalValueHandlers)
  89. squel.cls.globalValueHandlers = []
  90. 'afterEach': ->
  91. squel.cls.globalValueHandlers = @originalHandlers
  92. 'default': ->
  93. handler = -> 'test'
  94. squel.registerValueHandler(Date, handler)
  95. squel.registerValueHandler(Object, handler)
  96. squel.registerValueHandler('boolean', handler)
  97. assert.same 3, squel.cls.globalValueHandlers.length
  98. assert.same { type: Date, handler: handler }, squel.cls.globalValueHandlers[0]
  99. assert.same { type: Object, handler: handler }, squel.cls.globalValueHandlers[1]
  100. assert.same { type: 'boolean', handler: handler }, squel.cls.globalValueHandlers[2]
  101. 'type should be class constructor': ->
  102. assert.throws (-> squel.registerValueHandler 1, null), "type must be a class constructor or string"
  103. 'handler should be function': ->
  104. class MyClass
  105. assert.throws (-> squel.registerValueHandler MyClass, 1), 'handler must be a function'
  106. 'overrides existing handler': ->
  107. handler = -> 'test'
  108. handler2 = -> 'test2'
  109. squel.registerValueHandler(Date, handler)
  110. squel.registerValueHandler(Date, handler2)
  111. assert.same 1, squel.cls.globalValueHandlers.length
  112. assert.same { type: Date, handler: handler2 }, squel.cls.globalValueHandlers[0]
  113. test['str()'] =
  114. constructor: ->
  115. f = squel.str('GETDATE(?)', 12, 23)
  116. assert.ok (f instanceof squel.cls.FunctionBlock)
  117. assert.same 'GETDATE(?)', f._strings[0]
  118. assert.same [12, 23], f._values[0]
  119. 'custom value handler':
  120. beforeEach: ->
  121. @inst = squel.str('G(?,?)', 12, 23, 65)
  122. handlerConfig = _.find squel.cls.globalValueHandlers, (hc) ->
  123. hc.type is squel.cls.FunctionBlock
  124. @handler = handlerConfig.handler
  125. toString: ->
  126. assert.same @inst.toString(), @handler(@inst)
  127. toParam: ->
  128. assert.same @inst.toParam(), @handler(@inst, true)
  129. test['Load an SQL flavour'] =
  130. beforeEach: ->
  131. @flavoursBackup = squel.flavours
  132. squel.flavours = {}
  133. afterEach: ->
  134. squel.flavours = @flavoursBackup
  135. 'invalid flavour': ->
  136. assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
  137. 'flavour reference should be a function': ->
  138. squel.flavours['test'] = 'blah'
  139. assert.throws (-> squel.useFlavour 'test'), 'Flavour not available: test'
  140. 'flavour setup function gets executed': ->
  141. squel.flavours['test'] = test.mocker.spy()
  142. ret = squel.useFlavour 'test'
  143. assert.ok squel.flavours['test'].calledOnce
  144. assert.ok !!ret.select()
  145. 'can switch flavours': ->
  146. squel.flavours['test'] = test.mocker.spy( (s) ->
  147. s.cls.dummy = 1
  148. )
  149. squel.flavours['test2'] = test.mocker.spy( (s) ->
  150. s.cls.dummy2 = 2
  151. )
  152. ret = squel.useFlavour 'test'
  153. assert.same ret.cls.dummy, 1
  154. ret = squel.useFlavour 'test2'
  155. assert.same ret.cls.dummy, undefined
  156. assert.same ret.cls.dummy2, 2
  157. ret = squel.useFlavour()
  158. assert.same ret.cls.dummy, undefined
  159. assert.same ret.cls.dummy2, undefined
  160. 'can get current flavour': ->
  161. flavour = 'test'
  162. squel.flavours[flavour] = test.mocker.spy()
  163. ret = squel.useFlavour flavour
  164. assert.same ret.flavour, flavour
  165. test['Builder base class'] =
  166. beforeEach: ->
  167. @cls = squel.cls.BaseBuilder
  168. @inst = new @cls
  169. @originalHandlers = [].concat(squel.cls.globalValueHandlers)
  170. afterEach: ->
  171. squel.cls.globalValueHandlers = @originalHandlers
  172. 'instanceof Cloneable': ->
  173. assert.instanceOf @inst, squel.cls.Cloneable
  174. 'constructor':
  175. 'default options': ->
  176. assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
  177. 'overridden options': ->
  178. @inst = new @cls
  179. dummy1: 'str'
  180. dummy2: 12.3
  181. usingValuePlaceholders: true
  182. dummy3: true,
  183. globalValueHandlers: [1]
  184. expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
  185. dummy1: 'str'
  186. dummy2: 12.3
  187. usingValuePlaceholders: true
  188. dummy3: true
  189. globalValueHandlers: [1]
  190. assert.same expectedOptions, @inst.options
  191. 'registerValueHandler':
  192. 'afterEach': ->
  193. squel.cls.globalValueHandlers = []
  194. 'default': ->
  195. handler = -> 'test'
  196. @inst.registerValueHandler(Date, handler)
  197. @inst.registerValueHandler(Object, handler)
  198. @inst.registerValueHandler('number', handler)
  199. assert.same 3, @inst.options.valueHandlers.length
  200. assert.same { type: Date, handler: handler }, @inst.options.valueHandlers[0]
  201. assert.same { type: Object, handler: handler }, @inst.options.valueHandlers[1]
  202. assert.same { type: 'number', handler: handler }, @inst.options.valueHandlers[2]
  203. 'type should be class constructor': ->
  204. assert.throws (=> @inst.registerValueHandler 1, null), "type must be a class constructor or string"
  205. 'handler should be function': ->
  206. class MyClass
  207. assert.throws (=> @inst.registerValueHandler MyClass, 1), 'handler must be a function'
  208. 'returns instance for chainability': ->
  209. handler = -> 'test'
  210. assert.same @inst, @inst.registerValueHandler(Date, handler)
  211. 'overrides existing handler': ->
  212. handler = -> 'test'
  213. handler2 = -> 'test2'
  214. @inst.registerValueHandler(Date, handler)
  215. @inst.registerValueHandler(Date, handler2)
  216. assert.same 1, @inst.options.valueHandlers.length
  217. assert.same { type: Date, handler: handler2 }, @inst.options.valueHandlers[0]
  218. 'does not touch global value handlers list': ->
  219. oldGlobalHandlers = squel.cls.globalValueHandlers
  220. handler = -> 'test'
  221. @inst.registerValueHandler(Date, handler)
  222. assert.same oldGlobalHandlers, squel.cls.globalValueHandlers
  223. '_sanitizeExpression':
  224. 'if Expression':
  225. 'empty expression': ->
  226. e = squel.expr()
  227. assert.same e, @inst._sanitizeExpression(e)
  228. 'non-empty expression': ->
  229. e = squel.expr().and("s.name <> 'Fred'")
  230. assert.same e, @inst._sanitizeExpression(e)
  231. 'if string': ->
  232. s = 'BLA BLA'
  233. assert.same 'BLA BLA', @inst._sanitizeExpression(s)
  234. 'if neither Expression nor String': ->
  235. testFn = => @inst._sanitizeExpression(1)
  236. assert.throws testFn, 'expression must be a string or Expression instance'
  237. '_sanitizeName':
  238. beforeEach: ->
  239. test.mocker.spy @inst, '_sanitizeName'
  240. 'if string': ->
  241. assert.same 'bla', @inst._sanitizeName('bla')
  242. 'if boolean': ->
  243. assert.throws (=> @inst._sanitizeName(true, 'bla')), 'bla must be a string'
  244. 'if integer': ->
  245. assert.throws (=> @inst._sanitizeName(1)), 'undefined must be a string'
  246. 'if float': ->
  247. assert.throws (=> @inst._sanitizeName(1.2, 'meh')), 'meh must be a string'
  248. 'if array': ->
  249. assert.throws (=> @inst._sanitizeName([1], 'yes')), 'yes must be a string'
  250. 'if object': ->
  251. assert.throws (=> @inst._sanitizeName(new Object, 'yes')), 'yes must be a string'
  252. 'if null': ->
  253. assert.throws (=> @inst._sanitizeName(null, 'no')), 'no must be a string'
  254. 'if undefined': ->
  255. assert.throws (=> @inst._sanitizeName(undefined, 'no')), 'no must be a string'
  256. '_sanitizeField':
  257. 'default': ->
  258. test.mocker.spy @inst, '_sanitizeName'
  259. assert.same 'abc', @inst._sanitizeField('abc')
  260. assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field name'
  261. 'QueryBuilder': ->
  262. s = squel.select().from('scores').field('MAX(score)')
  263. assert.same s, @inst._sanitizeField(s)
  264. '_sanitizeBaseBuilder':
  265. 'is not base builder': ->
  266. assert.throws (=> @inst._sanitizeBaseBuilder(null)), 'must be a BaseBuilder instance'
  267. 'is a query builder': ->
  268. qry = squel.select()
  269. assert.same qry, @inst._sanitizeBaseBuilder(qry)
  270. '_sanitizeTable':
  271. 'default': ->
  272. test.mocker.spy @inst, '_sanitizeName'
  273. assert.same 'abc', @inst._sanitizeTable('abc')
  274. assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table'
  275. 'not a string': ->
  276. assert.throws (=> @inst._sanitizeTable(null)), 'table name must be a string or a query builder'
  277. 'query builder': ->
  278. select = squel.select()
  279. assert.same select, @inst._sanitizeTable(select, true)
  280. '_sanitizeFieldAlias': ->
  281. 'default': ->
  282. test.mocker.spy @inst, '_sanitizeName'
  283. @inst._sanitizeFieldAlias('abc')
  284. assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'field alias'
  285. '_sanitizeTableAlias': ->
  286. 'default': ->
  287. test.mocker.spy @inst, '_sanitizeName'
  288. @inst._sanitizeTableAlias('abc')
  289. assert.ok @inst._sanitizeName.calledWithExactly 'abc', 'table alias'
  290. '_sanitizeLimitOffset':
  291. 'undefined': ->
  292. assert.throws (=> @inst._sanitizeLimitOffset()), 'limit/offset must be >= 0'
  293. 'null': ->
  294. assert.throws (=> @inst._sanitizeLimitOffset null), 'limit/offset must be >= 0'
  295. 'float': ->
  296. assert.same 1, @inst._sanitizeLimitOffset 1.2
  297. 'boolean': ->
  298. assert.throws (=> @inst._sanitizeLimitOffset false), 'limit/offset must be >= 0'
  299. 'string': ->
  300. assert.same 2, @inst._sanitizeLimitOffset '2'
  301. 'array': ->
  302. assert.same 3, @inst._sanitizeLimitOffset [3]
  303. 'object': ->
  304. assert.throws (=> @inst._sanitizeLimitOffset(new Object)), 'limit/offset must be >= 0'
  305. 'number >= 0': ->
  306. assert.same 0, @inst._sanitizeLimitOffset 0
  307. assert.same 1, @inst._sanitizeLimitOffset 1
  308. 'number < 0': ->
  309. assert.throws (=> @inst._sanitizeLimitOffset(-1)), 'limit/offset must be >= 0'
  310. '_sanitizeValue':
  311. beforeEach: ->
  312. test.mocker.spy @inst, '_sanitizeValue'
  313. afterEach: ->
  314. squel.cls.globalValueHandlers = []
  315. 'if string': ->
  316. assert.same 'bla', @inst._sanitizeValue('bla')
  317. 'if boolean': ->
  318. assert.same true, @inst._sanitizeValue(true)
  319. assert.same false, @inst._sanitizeValue(false)
  320. 'if integer': ->
  321. assert.same -1, @inst._sanitizeValue(-1)
  322. assert.same 0, @inst._sanitizeValue(0)
  323. assert.same 1, @inst._sanitizeValue(1)
  324. 'if float': ->
  325. assert.same -1.2, @inst._sanitizeValue(-1.2)
  326. assert.same 1.2, @inst._sanitizeValue(1.2)
  327. 'if array': ->
  328. assert.throws (=> @inst._sanitizeValue([1])), 'field value must be a string, number, boolean, null or one of the registered custom value types'
  329. 'if object': ->
  330. assert.throws (=> @inst._sanitizeValue(new Object)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
  331. 'if null': ->
  332. assert.same null, @inst._sanitizeValue(null)
  333. 'if BaseBuilder': ->
  334. s = squel.select()
  335. assert.same s, @inst._sanitizeValue(s)
  336. 'if undefined': ->
  337. assert.throws (=> @inst._sanitizeValue(undefined)), 'field value must be a string, number, boolean, null or one of the registered custom value types'
  338. 'custom handlers':
  339. 'global': ->
  340. squel.registerValueHandler(Date, _.identity)
  341. date = new Date
  342. assert.same date, @inst._sanitizeValue(date)
  343. 'instance': ->
  344. @inst.registerValueHandler(Date, _.identity)
  345. date = new Date
  346. assert.same date, @inst._sanitizeValue(date)
  347. '_escapeValue': ->
  348. @inst.options.replaceSingleQuotes = false
  349. assert.same "te'st", @inst._escapeValue("te'st")
  350. @inst.options.replaceSingleQuotes = true
  351. assert.same "te''st", @inst._escapeValue("te'st")
  352. @inst.options.singleQuoteReplacement = '--'
  353. assert.same "te--st", @inst._escapeValue("te'st")
  354. '_formatTableName':
  355. 'default': ->
  356. assert.same 'abc', @inst._formatTableName('abc')
  357. 'auto quote names':
  358. beforeEach: ->
  359. @inst.options.autoQuoteTableNames = true
  360. 'default quote character': ->
  361. assert.same '`abc`', @inst._formatTableName('abc')
  362. 'custom quote character': ->
  363. @inst.options.nameQuoteCharacter = '|'
  364. assert.same '|abc|', @inst._formatTableName('abc')
  365. '_formatTableAlias':
  366. 'default': ->
  367. assert.same '`abc`', @inst._formatTableAlias('abc')
  368. 'custom quote character': ->
  369. @inst.options.tableAliasQuoteCharacter = '~'
  370. assert.same '~abc~', @inst._formatTableAlias('abc')
  371. 'auto quote alias names is OFF': ->
  372. @inst.options.autoQuoteAliasNames = false
  373. assert.same 'abc', @inst._formatTableAlias('abc')
  374. 'AS is turned ON': ->
  375. @inst.options.autoQuoteAliasNames = false
  376. @inst.options.useAsForTableAliasNames = true
  377. assert.same 'AS abc', @inst._formatTableAlias('abc')
  378. '_formatFieldAlias':
  379. default: ->
  380. assert.same '"abc"', @inst._formatFieldAlias('abc')
  381. 'custom quote character': ->
  382. @inst.options.fieldAliasQuoteCharacter = '~'
  383. assert.same '~abc~', @inst._formatFieldAlias('abc')
  384. 'auto quote alias names is OFF': ->
  385. @inst.options.autoQuoteAliasNames = false
  386. assert.same 'abc', @inst._formatFieldAlias('abc')
  387. '_formatFieldName':
  388. default: ->
  389. assert.same 'abc', @inst._formatFieldName('abc')
  390. 'auto quote names':
  391. beforeEach: ->
  392. @inst.options.autoQuoteFieldNames = true
  393. 'default quote character': ->
  394. assert.same '`abc`.`def`', @inst._formatFieldName('abc.def')
  395. 'do not quote *': ->
  396. assert.same '`abc`.*', @inst._formatFieldName('abc.*')
  397. 'custom quote character': ->
  398. @inst.options.nameQuoteCharacter = '|'
  399. assert.same '|abc|.|def|', @inst._formatFieldName('abc.def')
  400. 'ignore periods when quoting': ->
  401. assert.same '`abc.def`', @inst._formatFieldName('abc.def', ignorePeriodsForFieldNameQuotes: true)
  402. '_formatCustomValue':
  403. 'not a custom value type': ->
  404. assert.same null, @inst._formatCustomValue(null)
  405. assert.same 'abc', @inst._formatCustomValue('abc')
  406. assert.same 12, @inst._formatCustomValue(12)
  407. assert.same 1.2, @inst._formatCustomValue(1.2)
  408. assert.same true, @inst._formatCustomValue(true)
  409. assert.same false, @inst._formatCustomValue(false)
  410. 'custom value type':
  411. 'global': ->
  412. class MyClass
  413. myObj = new MyClass
  414. squel.registerValueHandler MyClass, () -> 3.14
  415. squel.registerValueHandler 'boolean', (v) -> 'a' + v
  416. assert.same 3.14, @inst._formatCustomValue(myObj)
  417. assert.same 'atrue', @inst._formatCustomValue(true)
  418. 'instance': ->
  419. class MyClass
  420. myObj = new MyClass
  421. @inst.registerValueHandler MyClass, () -> 3.14
  422. @inst.registerValueHandler 'number', (v) -> v + 'a'
  423. assert.same 3.14, @inst._formatCustomValue(myObj)
  424. assert.same '5.2a', @inst._formatCustomValue(5.2)
  425. 'instance handler takes precedence over global': ->
  426. @inst.registerValueHandler Date, (d) -> 'hello'
  427. squel.registerValueHandler Date, (d) -> 'goodbye'
  428. assert.same "hello", @inst._formatCustomValue(new Date)
  429. @inst = new @cls
  430. valueHandlers: []
  431. assert.same "goodbye", @inst._formatCustomValue(new Date)
  432. 'whether to format for parameterized output': ->
  433. @inst.registerValueHandler Date, (d, asParam) ->
  434. return if asParam then 'foo' else 'bar'
  435. val = new Date()
  436. assert.same 'foo', @inst._formatCustomValue(val, true)
  437. assert.same 'bar', @inst._formatCustomValue(val)
  438. '_formatValueForParamArray':
  439. 'Query builder': ->
  440. s = squel.select().from('table')
  441. assert.same s, @inst._formatValueForParamArray(s)
  442. 'else calls _formatCustomValue': ->
  443. spy = test.mocker.stub @inst, '_formatCustomValue', (v, asParam) ->
  444. 'test' + (if asParam then 'foo' else 'bar')
  445. assert.same 'testfoo', @inst._formatValueForParamArray(null)
  446. assert.same 'testfoo', @inst._formatValueForParamArray('abc')
  447. assert.same 'testfoo', @inst._formatValueForParamArray(12)
  448. assert.same 'testfoo', @inst._formatValueForParamArray(1.2)
  449. assert.same 'testfoo', @inst._formatValueForParamArray(true)
  450. assert.same 'testfoo', @inst._formatValueForParamArray(false)
  451. assert.same 6, spy.callCount
  452. 'Array - recursively calls itself on each element': ->
  453. spy = test.mocker.spy @inst, '_formatValueForParamArray'
  454. v = [ squel.select().from('table'), 1.2 ]
  455. res = @inst._formatValueForParamArray(v)
  456. assert.same v, res
  457. assert.same 3, spy.callCount
  458. assert.ok spy.calledWith v[0]
  459. assert.ok spy.calledWith v[1]
  460. '_formatValueForQueryString':
  461. 'null': ->
  462. assert.same 'NULL', @inst._formatValueForQueryString(null)
  463. 'boolean': ->
  464. assert.same 'TRUE', @inst._formatValueForQueryString(true)
  465. assert.same 'FALSE', @inst._formatValueForQueryString(false)
  466. 'integer': ->
  467. assert.same 12, @inst._formatValueForQueryString(12)
  468. 'float': ->
  469. assert.same 1.2, @inst._formatValueForQueryString(1.2)
  470. 'string':
  471. 'have string formatter function': ->
  472. @inst.options.stringFormatter = (str) -> "N(#{str})"
  473. assert.same "N(test)", @inst._formatValueForQueryString('test')
  474. 'default': ->
  475. escapedValue = undefined
  476. test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
  477. assert.same "'test'", @inst._formatValueForQueryString('test')
  478. assert.same "'test'", @inst._formatValueForQueryString('test')
  479. assert.ok @inst._escapeValue.calledWithExactly('test')
  480. escapedValue = 'blah'
  481. assert.same "'blah'", @inst._formatValueForQueryString('test')
  482. 'dont quote': ->
  483. escapedValue = undefined
  484. test.mocker.stub @inst, '_escapeValue', (str) -> escapedValue or str
  485. assert.same "test", @inst._formatValueForQueryString('test', dontQuote: true )
  486. assert.ok @inst._escapeValue.notCalled
  487. 'Array - recursively calls itself on each element': ->
  488. spy = test.mocker.spy @inst, '_formatValueForQueryString'
  489. expected = "('test', 123, TRUE, 1.2, NULL)"
  490. assert.same expected, @inst._formatValueForQueryString([ 'test', 123, true, 1.2, null ])
  491. assert.same 6, spy.callCount
  492. assert.ok spy.calledWith 'test'
  493. assert.ok spy.calledWith 123
  494. assert.ok spy.calledWith true
  495. assert.ok spy.calledWith 1.2
  496. assert.ok spy.calledWith null
  497. 'BaseBuilder': ->
  498. spy = test.mocker.stub @inst, '_applyNestingFormatting', (v) => "{{#{v}}}"
  499. s = squel.select().from('table')
  500. assert.same '{{SELECT * FROM table}}', @inst._formatValueForQueryString(s)
  501. 'checks to see if it is custom value type first': ->
  502. test.mocker.stub @inst, '_formatCustomValue', (val, asParam) ->
  503. 12 + (if asParam then 25 else 65)
  504. test.mocker.stub @inst, '_applyNestingFormatting', (v) -> "{#{v}}"
  505. assert.same '{77}', @inst._formatValueForQueryString(123)
  506. '_applyNestingFormatting':
  507. default: ->
  508. assert.same '(77)', @inst._applyNestingFormatting('77')
  509. assert.same '((77)', @inst._applyNestingFormatting('(77')
  510. assert.same '(77))', @inst._applyNestingFormatting('77)')
  511. assert.same '(77)', @inst._applyNestingFormatting('(77)')
  512. 'no nesting': ->
  513. assert.same '77', @inst._applyNestingFormatting('77', false)
  514. '_buildString':
  515. 'empty': ->
  516. assert.same @inst._buildString('', []), {
  517. text: '',
  518. values: [],
  519. }
  520. 'no params':
  521. 'non-parameterized': ->
  522. assert.same @inst._buildString('abc = 3', []), {
  523. text: 'abc = 3',
  524. values: []
  525. }
  526. 'parameterized': ->
  527. assert.same @inst._buildString('abc = 3', [], { buildParameterized: true }), {
  528. text: 'abc = 3',
  529. values: []
  530. }
  531. 'non-array':
  532. 'non-parameterized': ->
  533. assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null]), {
  534. text: 'a = 2 \'abc\' FALSE NULL',
  535. values: []
  536. }
  537. 'parameterized': ->
  538. assert.same @inst._buildString('a = ? ? ? ?', [2, 'abc', false, null], { buildParameterized: true }), {
  539. text: 'a = ? ? ? ?',
  540. values: [2, 'abc', false, null]
  541. }
  542. 'array': ->
  543. 'non-parameterized': ->
  544. assert.same @inst._buildString('a = ?', [[1,2,3]]), {
  545. text: 'a = (1, 2, 3)',
  546. values: [],
  547. }
  548. 'parameterized': ->
  549. assert.same @inst._buildString('a = ?', [[1,2,3]], { buildParameterized: true }), {
  550. text: 'a = (?, ?, ?)',
  551. values: [1, 2, 3]
  552. }
  553. 'nested builder': ->
  554. beforeEach:
  555. @s = squel.select().from('master').where('b = ?', 5)
  556. 'non-parameterized': ->
  557. assert.same @inst._buildString('a = ?', [@s]), {
  558. text: 'a = (SELECT * FROM master WHERE (b = ?))',
  559. values: [5]
  560. }
  561. 'parameterized': ->
  562. assert.same @inst._buildString('a = ?', [@s], { buildParameterized: true }), {
  563. text: 'a = (SELECT * FROM master WHERE (b = ?))',
  564. values: [5]
  565. }
  566. 'return nested output':
  567. 'non-parameterized': ->
  568. assert.same @inst._buildString('a = ?', [3], { nested: true }), {
  569. text: '(a = 3)',
  570. values: []
  571. }
  572. 'parameterized': ->
  573. assert.same @inst._buildString('a = ?', [3], { buildParameterized: true, nested: true }), {
  574. text: '(a = ?)',
  575. values: [3]
  576. }
  577. 'string formatting options': ->
  578. options =
  579. formattingOptions:
  580. dontQuote: true
  581. assert.same @inst._buildString('a = ?', ['NOW()'], options), {
  582. text: 'a = NOW()',
  583. values: []
  584. }
  585. 'custom parameter character': ->
  586. beforeEach: ->
  587. @inst.options.parameterCharacter = '@@'
  588. 'non-parameterized': ->
  589. assert.same @inst._buildString('a = @@', [[1,2,3]]), {
  590. text: 'a = (1, 2, 3)',
  591. values: [],
  592. }
  593. 'parameterized': ->
  594. assert.same @inst._buildString('a = @@', [[1,2,3]]), {
  595. text: 'a = (@@, @@, @@)',
  596. values: [1,2,3],
  597. }
  598. '_buildManyStrings':
  599. 'empty': ->
  600. assert.same @inst._buildManyStrings([], []), {
  601. text: '',
  602. values: [],
  603. }
  604. 'simple':
  605. beforeEach: ->
  606. @strings = [
  607. 'a = ?',
  608. 'b IN ? AND c = ?'
  609. ]
  610. @values = [
  611. ['elephant'],
  612. [[1,2,3], 4]
  613. ]
  614. 'non-parameterized': ->
  615. assert.same @inst._buildManyStrings(@strings, @values), {
  616. text: 'a = \'elephant\' b IN (1, 2, 3) AND c = 4',
  617. values: [],
  618. }
  619. 'parameterized': ->
  620. assert.same @inst._buildManyStrings(@strings, @values, { buildParameterized: true }), {
  621. text: 'a = ? b IN (?, ?, ?) AND c = ?',
  622. values: ['elephant', 1, 2, 3, 4],
  623. }
  624. 'return nested': ->
  625. 'non-parameterized': ->
  626. assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { nested: true }), {
  627. text: '(a = 1 b = 2)',
  628. values: [],
  629. }
  630. 'parameterized': ->
  631. assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true, nested: true }), {
  632. text: '(a = ? b = ?)',
  633. values: [1, 2],
  634. }
  635. 'custom separator': ->
  636. 'non-parameterized': ->
  637. @inst.options.separator = '|'
  638. assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]]), {
  639. text: '(a = 1|b = 2)',
  640. values: [],
  641. }
  642. 'parameterized': ->
  643. assert.same @inst._buildManyStrings(['a = ?', 'b = ?'], [[1], [2]], { buildParameterized: true}), {
  644. text: '(a = ?|b = ?)',
  645. values: [1, 2],
  646. }
  647. 'toParam': ->
  648. spy = test.mocker.stub @inst, '_toParamString', ->
  649. {
  650. text: 'dummy'
  651. values: [1]
  652. }
  653. options = {test: 2}
  654. assert.same @inst.toParam(options), {
  655. text: 'dummy'
  656. values: [1]
  657. }
  658. spy.should.have.been.calledOnce
  659. assert.same spy.getCall(0).args[0].test, 2
  660. assert.same spy.getCall(0).args[0].buildParameterized, true
  661. 'toString': ->
  662. spy = test.mocker.stub @inst, '_toParamString', ->
  663. {
  664. text: 'dummy'
  665. values: [1]
  666. }
  667. options = {test: 2}
  668. assert.same @inst.toString(options), 'dummy'
  669. spy.should.have.been.calledOnce
  670. assert.same spy.getCall(0).args[0], options
  671. test['QueryBuilder base class'] =
  672. beforeEach: ->
  673. @cls = squel.cls.QueryBuilder
  674. @inst = new @cls
  675. 'instanceof base builder': ->
  676. assert.instanceOf @inst, squel.cls.BaseBuilder
  677. 'constructor':
  678. 'default options': ->
  679. assert.same squel.cls.DefaultQueryBuilderOptions, @inst.options
  680. 'overridden options': ->
  681. @inst = new @cls
  682. dummy1: 'str'
  683. dummy2: 12.3
  684. usingValuePlaceholders: true
  685. dummy3: true
  686. expectedOptions = _.extend {}, squel.cls.DefaultQueryBuilderOptions,
  687. dummy1: 'str'
  688. dummy2: 12.3
  689. usingValuePlaceholders: true
  690. dummy3: true
  691. assert.same expectedOptions, @inst.options
  692. 'default blocks - none': ->
  693. assert.same [], @inst.blocks
  694. 'blocks passed in':
  695. 'exposes block methods': ->
  696. limitExposedMethodsSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'exposedMethods');
  697. distinctExposedMethodsSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'exposedMethods');
  698. limitSpy = test.mocker.spy(squel.cls.LimitBlock.prototype, 'limit')
  699. distinctSpy = test.mocker.spy(squel.cls.DistinctBlock.prototype, 'distinct')
  700. blocks = [
  701. new squel.cls.LimitBlock(),
  702. new squel.cls.DistinctBlock()
  703. ]
  704. @inst = new @cls({}, blocks)
  705. assert.ok limitExposedMethodsSpy.calledOnce
  706. assert.ok distinctExposedMethodsSpy.calledOnce
  707. assert.typeOf @inst.distinct, 'function'
  708. assert.typeOf @inst.limit, 'function'
  709. assert.same @inst, @inst.limit(2)
  710. assert.ok limitSpy.calledOnce
  711. assert.ok limitSpy.calledOn(blocks[0])
  712. assert.same @inst, @inst.distinct()
  713. assert.ok distinctSpy.calledOnce
  714. assert.ok distinctSpy.calledOn(blocks[1])
  715. 'cannot expose the same method twice': ->
  716. blocks = [
  717. new squel.cls.DistinctBlock(),
  718. new squel.cls.DistinctBlock()
  719. ]
  720. try
  721. @inst = new @cls({}, blocks)
  722. throw new Error 'should not reach here'
  723. catch err
  724. assert.same 'Error: Builder already has a builder method called: distinct', err.toString()
  725. 'updateOptions()':
  726. 'updates query builder options': ->
  727. oldOptions = _.extend({}, @inst.options)
  728. @inst.updateOptions
  729. updated: false
  730. expected = _.extend oldOptions,
  731. updated: false
  732. assert.same expected, @inst.options
  733. 'updates building block options': ->
  734. @inst.blocks = [
  735. new squel.cls.Block()
  736. ]
  737. oldOptions = _.extend({}, @inst.blocks[0].options)
  738. @inst.updateOptions
  739. updated: false
  740. expected = _.extend oldOptions,
  741. updated: false
  742. assert.same expected, @inst.blocks[0].options
  743. 'toString()':
  744. 'returns empty if no blocks': ->
  745. assert.same '', @inst.toString()
  746. 'skips empty block strings': ->
  747. @inst.blocks = [
  748. new squel.cls.StringBlock({}, ''),
  749. ]
  750. assert.same '', @inst.toString()
  751. 'returns final query string': ->
  752. i = 1
  753. toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', ->
  754. {
  755. text: "ret#{++i}"
  756. values: []
  757. }
  758. @inst.blocks = [
  759. new squel.cls.StringBlock({}, 'STR1'),
  760. new squel.cls.StringBlock({}, 'STR2'),
  761. new squel.cls.StringBlock({}, 'STR3')
  762. ]
  763. assert.same 'ret2 ret3 ret4', @inst.toString()
  764. assert.ok toStringSpy.calledThrice
  765. assert.ok toStringSpy.calledOn(@inst.blocks[0])
  766. assert.ok toStringSpy.calledOn(@inst.blocks[1])
  767. assert.ok toStringSpy.calledOn(@inst.blocks[2])
  768. 'toParam()':
  769. 'returns empty if no blocks': ->
  770. assert.same { text: '', values: [] }, @inst.toParam()
  771. 'skips empty block strings': ->
  772. @inst.blocks = [
  773. new squel.cls.StringBlock({}, ''),
  774. ]
  775. assert.same { text: '', values: [] }, @inst.toParam()
  776. 'returns final query string': ->
  777. @inst.blocks = [
  778. new squel.cls.StringBlock({}, 'STR1'),
  779. new squel.cls.StringBlock({}, 'STR2'),
  780. new squel.cls.StringBlock({}, 'STR3')
  781. ]
  782. i = 1
  783. toStringSpy = test.mocker.stub squel.cls.StringBlock.prototype, '_toParamString', ->
  784. {
  785. text: "ret#{++i}"
  786. values: []
  787. }
  788. assert.same { text: 'ret2 ret3 ret4', values: [] }, @inst.toParam()
  789. assert.ok toStringSpy.calledThrice
  790. assert.ok toStringSpy.calledOn(@inst.blocks[0])
  791. assert.ok toStringSpy.calledOn(@inst.blocks[1])
  792. assert.ok toStringSpy.calledOn(@inst.blocks[2])
  793. 'returns query with unnumbered parameters': ->
  794. @inst.blocks = [
  795. new squel.cls.WhereBlock({}),
  796. ]
  797. @inst.blocks[0]._toParamString = test.mocker.spy -> {
  798. text: 'a = ? AND b in (?, ?)',
  799. values: [1, 2, 3]
  800. }
  801. assert.same { text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]}, @inst.toParam()
  802. 'returns query with numbered parameters': ->
  803. @inst = new @cls
  804. numberedParameters: true
  805. @inst.blocks = [
  806. new squel.cls.WhereBlock({}),
  807. ]
  808. test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> {
  809. text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
  810. }
  811. assert.same @inst.toParam(), { text: 'a = $1 AND b in ($2, $3)', values: [1, 2, 3]}
  812. 'returns query with numbered parameters and custom prefix': ->
  813. @inst = new @cls
  814. numberedParameters: true
  815. numberedParametersPrefix: '&%'
  816. @inst.blocks = [
  817. new squel.cls.WhereBlock({}),
  818. ]
  819. test.mocker.stub squel.cls.WhereBlock.prototype, '_toParamString', -> {
  820. text: 'a = ? AND b in (?, ?)', values: [1, 2, 3]
  821. }
  822. assert.same @inst.toParam(), { text: 'a = &%1 AND b in (&%2, &%3)', values: [1, 2, 3]}
  823. 'cloning':
  824. 'blocks get cloned properly': ->
  825. blockCloneSpy = test.mocker.spy(squel.cls.StringBlock.prototype, 'clone')
  826. @inst.blocks = [
  827. new squel.cls.StringBlock({}, 'TEST')
  828. ]
  829. newinst = @inst.clone()
  830. @inst.blocks[0].str = 'TEST2'
  831. assert.same 'TEST', newinst.blocks[0].toString()
  832. 'registerValueHandler':
  833. 'beforEach': ->
  834. @originalHandlers = [].concat(squel.cls.globalValueHandlers)
  835. 'afterEach': ->
  836. squel.cls.globalValueHandlers = @originalHandlers
  837. 'calls through to base class method': ->
  838. baseBuilderSpy = test.mocker.spy(squel.cls.BaseBuilder.prototype, 'registerValueHandler')
  839. handler = -> 'test'
  840. @inst.registerValueHandler(Date, handler)
  841. @inst.registerValueHandler('number', handler)
  842. assert.ok baseBuilderSpy.calledTwice
  843. assert.ok baseBuilderSpy.calledOn(@inst)
  844. 'returns instance for chainability': ->
  845. handler = -> 'test'
  846. assert.same @inst, @inst.registerValueHandler(Date, handler)
  847. 'calls through to blocks': ->
  848. @inst.blocks = [
  849. new squel.cls.StringBlock({}, ''),
  850. ]
  851. baseBuilderSpy = test.mocker.spy(@inst.blocks[0], 'registerValueHandler')
  852. handler = -> 'test'
  853. @inst.registerValueHandler(Date, handler)
  854. assert.ok baseBuilderSpy.calledOnce
  855. assert.ok baseBuilderSpy.calledOn(@inst.blocks[0])
  856. 'get block':
  857. 'valid': ->
  858. block = new squel.cls.FunctionBlock()
  859. @inst.blocks.push(block)
  860. assert.same block, @inst.getBlock(squel.cls.FunctionBlock)
  861. 'invalid': ->
  862. assert.throws (-> @inst.getBlock(squel.cls.FunctionBlock) )
  863. module?.exports[require('path').basename(__filename)] = test