CaseStatementExpressionTest.php 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 4.3.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Database\Expression;
  17. use Cake\Chronos\Chronos;
  18. use Cake\Chronos\ChronosDate;
  19. use Cake\Database\Expression\CaseStatementExpression;
  20. use Cake\Database\Expression\ComparisonExpression;
  21. use Cake\Database\Expression\FunctionExpression;
  22. use Cake\Database\Expression\IdentifierExpression;
  23. use Cake\Database\Expression\QueryExpression;
  24. use Cake\Database\Expression\WhenThenExpression;
  25. use Cake\Database\TypeFactory;
  26. use Cake\Database\TypeMap;
  27. use Cake\Database\ValueBinder;
  28. use Cake\Datasource\ConnectionManager;
  29. use Cake\I18n\Date;
  30. use Cake\I18n\DateTime;
  31. use Cake\Test\test_app\TestApp\Database\Expression\CustomWhenThenExpression;
  32. use Cake\Test\test_app\TestApp\Stub\CaseStatementExpressionStub;
  33. use Cake\Test\test_app\TestApp\Stub\WhenThenExpressionStub;
  34. use Cake\TestSuite\TestCase;
  35. use InvalidArgumentException;
  36. use LogicException;
  37. use stdClass;
  38. use TestApp\Database\Type\CustomExpressionType;
  39. use TestApp\View\Object\TestObjectWithToString;
  40. use TypeError;
  41. class CaseStatementExpressionTest extends TestCase
  42. {
  43. // region Type handling
  44. public function testExpressionTypeCastingSimpleCase(): void
  45. {
  46. TypeFactory::map('custom', CustomExpressionType::class);
  47. $expression = (new CaseStatementExpression(1, 'custom'))
  48. ->when(1, 'custom')
  49. ->then(2, 'custom')
  50. ->else(3, 'custom');
  51. $valueBinder = new ValueBinder();
  52. $sql = $expression->sql($valueBinder);
  53. $this->assertSame(
  54. 'CASE CUSTOM(:param0) WHEN CUSTOM(:param1) THEN CUSTOM(:param2) ELSE CUSTOM(:param3) END',
  55. $sql
  56. );
  57. }
  58. public function testExpressionTypeCastingNullValues(): void
  59. {
  60. TypeFactory::map('custom', CustomExpressionType::class);
  61. $expression = (new CaseStatementExpression(null, 'custom'))
  62. ->when(1, 'custom')
  63. ->then(null, 'custom')
  64. ->else(null, 'custom');
  65. $valueBinder = new ValueBinder();
  66. $sql = $expression->sql($valueBinder);
  67. $this->assertSame(
  68. 'CASE CUSTOM(:param0) WHEN CUSTOM(:param1) THEN CUSTOM(:param2) ELSE CUSTOM(:param3) END',
  69. $sql
  70. );
  71. }
  72. public function testExpressionTypeCastingSearchedCase(): void
  73. {
  74. TypeFactory::map('custom', CustomExpressionType::class);
  75. $expression = (new CaseStatementExpression())
  76. ->when(['Table.column' => true], ['Table.column' => 'custom'])
  77. ->then(1, 'custom')
  78. ->else(2, 'custom');
  79. $valueBinder = new ValueBinder();
  80. $sql = $expression->sql($valueBinder);
  81. $this->assertSame(
  82. 'CASE WHEN Table.column = (CUSTOM(:param0)) THEN CUSTOM(:param1) ELSE CUSTOM(:param2) END',
  83. $sql
  84. );
  85. }
  86. public function testGetReturnType(): void
  87. {
  88. // all provided `then` and `else` types are the same, return
  89. // type can be inferred
  90. $expression = (new CaseStatementExpression())
  91. ->when(['Table.column_a' => true])
  92. ->then(1, 'integer')
  93. ->when(['Table.column_b' => true])
  94. ->then(2, 'integer')
  95. ->else(3, 'integer');
  96. $this->assertSame('integer', $expression->getReturnType());
  97. // all provided `then` an `else` types are the same, one `then`
  98. // type is `null`, return type can be inferred
  99. $expression = (new CaseStatementExpression())
  100. ->when(['Table.column_a' => true])
  101. ->then(1)
  102. ->when(['Table.column_b' => true])
  103. ->then(2, 'integer')
  104. ->else(3, 'integer');
  105. $this->assertSame('integer', $expression->getReturnType());
  106. // all `then` types are null, an `else` type was provided,
  107. // return type can be inferred
  108. $expression = (new CaseStatementExpression())
  109. ->when(['Table.column_a' => true])
  110. ->then(1)
  111. ->when(['Table.column_b' => true])
  112. ->then(2)
  113. ->else(3, 'integer');
  114. $this->assertSame('integer', $expression->getReturnType());
  115. // all provided `then` types are the same, the `else` type is
  116. // `null`, return type can be inferred
  117. $expression = (new CaseStatementExpression())
  118. ->when(['Table.column_a' => true])
  119. ->then(1, 'integer')
  120. ->when(['Table.column_b' => true])
  121. ->then(2, 'integer')
  122. ->else(3);
  123. $this->assertSame('integer', $expression->getReturnType());
  124. // no `then` or `else` types were provided, they are all `null`,
  125. // and will be derived from the passed value, return type can be
  126. // inferred
  127. $expression = (new CaseStatementExpression())
  128. ->when(['Table.column_a' => true])
  129. ->then(1)
  130. ->when(['Table.column_b' => true])
  131. ->then(2)
  132. ->else(3);
  133. $this->assertSame('integer', $expression->getReturnType());
  134. // all `then` and `else` point to columns of the same type,
  135. // return type can be inferred
  136. $typeMap = new TypeMap([
  137. 'Table.column_a' => 'boolean',
  138. 'Table.column_b' => 'boolean',
  139. 'Table.column_c' => 'boolean',
  140. ]);
  141. $expression = (new CaseStatementExpression())
  142. ->setTypeMap($typeMap)
  143. ->when(['Table.column_a' => true])
  144. ->then(new IdentifierExpression('Table.column_a'))
  145. ->when(['Table.column_b' => true])
  146. ->then(new IdentifierExpression('Table.column_b'))
  147. ->else(new IdentifierExpression('Table.column_c'));
  148. $this->assertSame('boolean', $expression->getReturnType());
  149. // all `then` and `else` use the same custom type, return type
  150. // can be inferred
  151. $expression = (new CaseStatementExpression())
  152. ->when(['Table.column_a' => true])
  153. ->then(1, 'custom')
  154. ->when(['Table.column_b' => true])
  155. ->then(2, 'custom')
  156. ->else(3, 'custom');
  157. $this->assertSame('custom', $expression->getReturnType());
  158. // all `then` and `else` types were provided, but an explicit
  159. // return type was set, return type will be overwritten
  160. $expression = (new CaseStatementExpression())
  161. ->when(['Table.column_a' => true])
  162. ->then(1, 'integer')
  163. ->when(['Table.column_b' => true])
  164. ->then(2, 'integer')
  165. ->else(3, 'integer')
  166. ->setReturnType('string');
  167. $this->assertSame('string', $expression->getReturnType());
  168. // all `then` and `else` types are different, return type
  169. // cannot be inferred
  170. $expression = (new CaseStatementExpression())
  171. ->when(['Table.column_a' => true])
  172. ->then(true)
  173. ->when(['Table.column_b' => true])
  174. ->then(1)
  175. ->else(null);
  176. $this->assertSame('string', $expression->getReturnType());
  177. }
  178. public function testSetReturnType(): void
  179. {
  180. $expression = (new CaseStatementExpression())->else('1');
  181. $this->assertSame('string', $expression->getReturnType());
  182. $expression->setReturnType('float');
  183. $this->assertSame('float', $expression->getReturnType());
  184. }
  185. public static function valueTypeInferenceDataProvider(): array
  186. {
  187. return [
  188. // Values that should have their type inferred because
  189. // they will be bound by the case expression.
  190. ['1', 'string'],
  191. [1, 'integer'],
  192. [1.0, 'float'],
  193. [true, 'boolean'],
  194. [ChronosDate::now(), 'date'],
  195. [Chronos::now(), 'datetime'],
  196. // Values that should not have a type inferred, either
  197. // because they are not bound by the case expression,
  198. // and/or because their type is obtained differently
  199. // (for example from a type map).
  200. [new IdentifierExpression('Table.column'), null],
  201. [new FunctionExpression('SUM', ['Table.column' => 'literal'], [], 'integer'), null],
  202. [new stdClass(), null],
  203. [null, null],
  204. ];
  205. }
  206. /**
  207. * @dataProvider valueTypeInferenceDataProvider
  208. * @param mixed $value The value from which to infer the type.
  209. * @param string|null $type The expected type.
  210. */
  211. public function testInferValueType($value, ?string $type): void
  212. {
  213. $expression = new CaseStatementExpressionStub();
  214. $this->assertNull($expression->getValueType());
  215. $expression = (new CaseStatementExpressionStub($value))
  216. ->setTypeMap(new TypeMap(['Table.column' => 'boolean']))
  217. ->when(1)
  218. ->then(2);
  219. $this->assertSame($type, $expression->getValueType());
  220. }
  221. public static function whenTypeInferenceDataProvider(): array
  222. {
  223. return [
  224. // Values that should have their type inferred because
  225. // they will be bound by the case expression.
  226. ['1', 'string'],
  227. [1, 'integer'],
  228. [1.0, 'float'],
  229. [true, 'boolean'],
  230. [ChronosDate::now(), 'date'],
  231. [Chronos::now(), 'datetime'],
  232. // Values that should not have a type inferred, either
  233. // because they are not bound by the case expression,
  234. // and/or because their type is obtained differently
  235. // (for example from a type map).
  236. [new IdentifierExpression('Table.column'), null],
  237. [new FunctionExpression('SUM', ['Table.column' => 'literal'], [], 'integer'), null],
  238. [['Table.column' => true], null],
  239. [new stdClass(), null],
  240. ];
  241. }
  242. /**
  243. * @dataProvider whenTypeInferenceDataProvider
  244. * @param mixed $value The value from which to infer the type.
  245. * @param string|null $type The expected type.
  246. */
  247. public function testInferWhenType($value, ?string $type): void
  248. {
  249. $expression = (new CaseStatementExpressionStub())
  250. ->setTypeMap(new TypeMap(['Table.column' => 'boolean']));
  251. $expression->when(new WhenThenExpressionStub($expression->getTypeMap()));
  252. $this->assertNull($expression->clause('when')[0]->getWhenType());
  253. $expression->clause('when')[0]
  254. ->when($value)
  255. ->then(1);
  256. $this->assertSame($type, $expression->clause('when')[0]->getWhenType());
  257. }
  258. public static function resultTypeInferenceDataProvider(): array
  259. {
  260. return [
  261. // Unless a result type has been set manually, values
  262. // should have their type inferred when possible.
  263. ['1', 'string'],
  264. [1, 'integer'],
  265. [1.0, 'float'],
  266. [true, 'boolean'],
  267. [ChronosDate::now(), 'date'],
  268. [Chronos::now(), 'datetime'],
  269. [new IdentifierExpression('Table.column'), 'boolean'],
  270. [new FunctionExpression('SUM', ['Table.column' => 'literal'], [], 'integer'), 'integer'],
  271. [new stdClass(), null],
  272. [null, null],
  273. ];
  274. }
  275. /**
  276. * @dataProvider resultTypeInferenceDataProvider
  277. * @param mixed $value The value from which to infer the type.
  278. * @param string|null $type The expected type.
  279. */
  280. public function testInferResultType($value, ?string $type): void
  281. {
  282. $expression = (new CaseStatementExpressionStub())
  283. ->setTypeMap(new TypeMap(['Table.column' => 'boolean']))
  284. ->when(function (WhenThenExpression $whenThen) {
  285. return $whenThen;
  286. });
  287. $this->assertNull($expression->clause('when')[0]->getResultType());
  288. $expression->clause('when')[0]
  289. ->when(['Table.column' => true])
  290. ->then($value);
  291. $this->assertSame($type, $expression->clause('when')[0]->getResultType());
  292. }
  293. /**
  294. * @dataProvider resultTypeInferenceDataProvider
  295. * @param mixed $value The value from which to infer the type.
  296. * @param string|null $type The expected type.
  297. */
  298. public function testInferElseType($value, ?string $type): void
  299. {
  300. $expression = new CaseStatementExpressionStub();
  301. $this->assertNull($expression->getElseType());
  302. $expression = (new CaseStatementExpressionStub())
  303. ->setTypeMap(new TypeMap(['Table.column' => 'boolean']));
  304. $this->assertNull($expression->getElseType());
  305. $expression->else($value);
  306. $this->assertSame($type, $expression->getElseType());
  307. }
  308. public function testWhenArrayValueInheritTypeMap(): void
  309. {
  310. $typeMap = new TypeMap([
  311. 'Table.column_a' => 'boolean',
  312. 'Table.column_b' => 'string',
  313. ]);
  314. $expression = (new CaseStatementExpression())
  315. ->setTypeMap($typeMap)
  316. ->when(['Table.column_a' => true])
  317. ->then(1)
  318. ->when(['Table.column_b' => 'foo'])
  319. ->then(2)
  320. ->else(3);
  321. $valueBinder = new ValueBinder();
  322. $sql = $expression->sql($valueBinder);
  323. $this->assertSame(
  324. 'CASE WHEN Table.column_a = :c0 THEN :c1 WHEN Table.column_b = :c2 THEN :c3 ELSE :c4 END',
  325. $sql
  326. );
  327. $this->assertSame(
  328. [
  329. ':c0' => [
  330. 'value' => true,
  331. 'type' => 'boolean',
  332. 'placeholder' => 'c0',
  333. ],
  334. ':c1' => [
  335. 'value' => 1,
  336. 'type' => 'integer',
  337. 'placeholder' => 'c1',
  338. ],
  339. ':c2' => [
  340. 'value' => 'foo',
  341. 'type' => 'string',
  342. 'placeholder' => 'c2',
  343. ],
  344. ':c3' => [
  345. 'value' => 2,
  346. 'type' => 'integer',
  347. 'placeholder' => 'c3',
  348. ],
  349. ':c4' => [
  350. 'value' => 3,
  351. 'type' => 'integer',
  352. 'placeholder' => 'c4',
  353. ],
  354. ],
  355. $valueBinder->bindings()
  356. );
  357. }
  358. public function testWhenArrayValueWithExplicitTypes(): void
  359. {
  360. $typeMap = new TypeMap([
  361. 'Table.column_a' => 'boolean',
  362. 'Table.column_b' => 'string',
  363. ]);
  364. $expression = (new CaseStatementExpression())
  365. ->setTypeMap($typeMap)
  366. ->when(['Table.column_a' => 123], ['Table.column_a' => 'integer'])
  367. ->then(1)
  368. ->when(['Table.column_b' => 'foo'])
  369. ->then(2)
  370. ->else(3);
  371. $valueBinder = new ValueBinder();
  372. $sql = $expression->sql($valueBinder);
  373. $this->assertSame(
  374. 'CASE WHEN Table.column_a = :c0 THEN :c1 WHEN Table.column_b = :c2 THEN :c3 ELSE :c4 END',
  375. $sql
  376. );
  377. $this->assertSame(
  378. [
  379. ':c0' => [
  380. 'value' => 123,
  381. 'type' => 'integer',
  382. 'placeholder' => 'c0',
  383. ],
  384. ':c1' => [
  385. 'value' => 1,
  386. 'type' => 'integer',
  387. 'placeholder' => 'c1',
  388. ],
  389. ':c2' => [
  390. 'value' => 'foo',
  391. 'type' => 'string',
  392. 'placeholder' => 'c2',
  393. ],
  394. ':c3' => [
  395. 'value' => 2,
  396. 'type' => 'integer',
  397. 'placeholder' => 'c3',
  398. ],
  399. ':c4' => [
  400. 'value' => 3,
  401. 'type' => 'integer',
  402. 'placeholder' => 'c4',
  403. ],
  404. ],
  405. $valueBinder->bindings()
  406. );
  407. }
  408. public function testWhenCallableArrayValueInheritTypeMap(): void
  409. {
  410. $typeMap = new TypeMap([
  411. 'Table.column_a' => 'boolean',
  412. 'Table.column_b' => 'string',
  413. ]);
  414. $expression = (new CaseStatementExpression())
  415. ->setTypeMap($typeMap)
  416. ->when(function (WhenThenExpression $whenThen) {
  417. return $whenThen
  418. ->when(['Table.column_a' => true])
  419. ->then(1);
  420. })
  421. ->when(function (WhenThenExpression $whenThen) {
  422. return $whenThen
  423. ->when(['Table.column_b' => 'foo'])
  424. ->then(2);
  425. })
  426. ->else(3);
  427. $valueBinder = new ValueBinder();
  428. $sql = $expression->sql($valueBinder);
  429. $this->assertSame(
  430. 'CASE WHEN Table.column_a = :c0 THEN :c1 WHEN Table.column_b = :c2 THEN :c3 ELSE :c4 END',
  431. $sql
  432. );
  433. $this->assertSame(
  434. [
  435. ':c0' => [
  436. 'value' => true,
  437. 'type' => 'boolean',
  438. 'placeholder' => 'c0',
  439. ],
  440. ':c1' => [
  441. 'value' => 1,
  442. 'type' => 'integer',
  443. 'placeholder' => 'c1',
  444. ],
  445. ':c2' => [
  446. 'value' => 'foo',
  447. 'type' => 'string',
  448. 'placeholder' => 'c2',
  449. ],
  450. ':c3' => [
  451. 'value' => 2,
  452. 'type' => 'integer',
  453. 'placeholder' => 'c3',
  454. ],
  455. ':c4' => [
  456. 'value' => 3,
  457. 'type' => 'integer',
  458. 'placeholder' => 'c4',
  459. ],
  460. ],
  461. $valueBinder->bindings()
  462. );
  463. }
  464. public function testWhenCallableArrayValueWithExplicitTypes(): void
  465. {
  466. $typeMap = new TypeMap([
  467. 'Table.column_a' => 'boolean',
  468. 'Table.column_b' => 'string',
  469. ]);
  470. $expression = (new CaseStatementExpression())
  471. ->setTypeMap($typeMap)
  472. ->when(function (WhenThenExpression $whenThen) {
  473. return $whenThen
  474. ->when(['Table.column_a' => 123], ['Table.column_a' => 'integer'])
  475. ->then(1);
  476. })
  477. ->when(function (WhenThenExpression $whenThen) {
  478. return $whenThen
  479. ->when(['Table.column_b' => 'foo'])
  480. ->then(2);
  481. })
  482. ->else(3);
  483. $valueBinder = new ValueBinder();
  484. $sql = $expression->sql($valueBinder);
  485. $this->assertSame(
  486. 'CASE WHEN Table.column_a = :c0 THEN :c1 WHEN Table.column_b = :c2 THEN :c3 ELSE :c4 END',
  487. $sql
  488. );
  489. $this->assertSame(
  490. [
  491. ':c0' => [
  492. 'value' => 123,
  493. 'type' => 'integer',
  494. 'placeholder' => 'c0',
  495. ],
  496. ':c1' => [
  497. 'value' => 1,
  498. 'type' => 'integer',
  499. 'placeholder' => 'c1',
  500. ],
  501. ':c2' => [
  502. 'value' => 'foo',
  503. 'type' => 'string',
  504. 'placeholder' => 'c2',
  505. ],
  506. ':c3' => [
  507. 'value' => 2,
  508. 'type' => 'integer',
  509. 'placeholder' => 'c3',
  510. ],
  511. ':c4' => [
  512. 'value' => 3,
  513. 'type' => 'integer',
  514. 'placeholder' => 'c4',
  515. ],
  516. ],
  517. $valueBinder->bindings()
  518. );
  519. }
  520. public function testWhenArrayValueRequiresArrayTypeValue(): void
  521. {
  522. $this->expectException(InvalidArgumentException::class);
  523. $this->expectExceptionMessage(
  524. 'When using an array for the `$when` argument, the `$type` ' .
  525. 'argument must be an array too, `string` given.'
  526. );
  527. (new CaseStatementExpression())
  528. ->when(['Table.column' => 123], 'integer')
  529. ->then(1);
  530. }
  531. public function testWhenNonArrayValueRequiresStringTypeValue(): void
  532. {
  533. $this->expectException(InvalidArgumentException::class);
  534. $this->expectExceptionMessage(
  535. 'When using a non-array value for the `$when` argument, ' .
  536. 'the `$type` argument must be a string, `array` given.'
  537. );
  538. (new CaseStatementExpression())
  539. ->when(123, ['Table.column' => 'integer'])
  540. ->then(1);
  541. }
  542. public function testInternalTypeMapChangesAreNonPersistent(): void
  543. {
  544. $typeMap = new TypeMap([
  545. 'Table.column' => 'integer',
  546. ]);
  547. $expression = (new CaseStatementExpression())
  548. ->setTypeMap($typeMap)
  549. ->when(['Table.column' => 123])
  550. ->then(1)
  551. ->when(['Table.column' => 'foo'], ['Table.column' => 'string'])
  552. ->then('bar')
  553. ->when(['Table.column' => 456])
  554. ->then(2);
  555. $valueBinder = new ValueBinder();
  556. $expression->sql($valueBinder);
  557. $this->assertSame(
  558. [
  559. ':c0' => [
  560. 'value' => 123,
  561. 'type' => 'integer',
  562. 'placeholder' => 'c0',
  563. ],
  564. ':c1' => [
  565. 'value' => 1,
  566. 'type' => 'integer',
  567. 'placeholder' => 'c1',
  568. ],
  569. ':c2' => [
  570. 'value' => 'foo',
  571. 'type' => 'string',
  572. 'placeholder' => 'c2',
  573. ],
  574. ':c3' => [
  575. 'value' => 'bar',
  576. 'type' => 'string',
  577. 'placeholder' => 'c3',
  578. ],
  579. ':c4' => [
  580. 'value' => 456,
  581. 'type' => 'integer',
  582. 'placeholder' => 'c4',
  583. ],
  584. ':c5' => [
  585. 'value' => 2,
  586. 'type' => 'integer',
  587. 'placeholder' => 'c5',
  588. ],
  589. ],
  590. $valueBinder->bindings()
  591. );
  592. $this->assertSame($typeMap, $expression->getTypeMap());
  593. }
  594. // endregion
  595. // region SQL injections
  596. public function testSqlInjectionViaTypedCaseValueIsNotPossible(): void
  597. {
  598. $expression = (new CaseStatementExpression('1 THEN 1 END; DELETE * FROM foo; --', 'integer'))
  599. ->when(1)
  600. ->then(2);
  601. $valueBinder = new ValueBinder();
  602. $sql = $expression->sql($valueBinder);
  603. $this->assertSame(
  604. 'CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END',
  605. $sql
  606. );
  607. $this->assertSame(
  608. [
  609. ':c0' => [
  610. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  611. 'type' => 'integer',
  612. 'placeholder' => 'c0',
  613. ],
  614. ':c1' => [
  615. 'value' => 1,
  616. 'type' => 'integer',
  617. 'placeholder' => 'c1',
  618. ],
  619. ':c2' => [
  620. 'value' => 2,
  621. 'type' => 'integer',
  622. 'placeholder' => 'c2',
  623. ],
  624. ],
  625. $valueBinder->bindings()
  626. );
  627. }
  628. public function testSqlInjectionViaUntypedCaseValueIsNotPossible(): void
  629. {
  630. $expression = (new CaseStatementExpression('1 THEN 1 END; DELETE * FROM foo; --'))
  631. ->when(1)
  632. ->then(2);
  633. $valueBinder = new ValueBinder();
  634. $sql = $expression->sql($valueBinder);
  635. $this->assertSame(
  636. 'CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END',
  637. $sql
  638. );
  639. $this->assertSame(
  640. [
  641. ':c0' => [
  642. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  643. 'type' => 'string',
  644. 'placeholder' => 'c0',
  645. ],
  646. ':c1' => [
  647. 'value' => 1,
  648. 'type' => 'integer',
  649. 'placeholder' => 'c1',
  650. ],
  651. ':c2' => [
  652. 'value' => 2,
  653. 'type' => 'integer',
  654. 'placeholder' => 'c2',
  655. ],
  656. ],
  657. $valueBinder->bindings()
  658. );
  659. }
  660. public function testSqlInjectionViaTypedWhenValueIsNotPossible(): void
  661. {
  662. $expression = (new CaseStatementExpression())
  663. ->when('1 THEN 1 END; DELETE * FROM foo; --', 'integer')
  664. ->then(1);
  665. $valueBinder = new ValueBinder();
  666. $sql = $expression->sql($valueBinder);
  667. $this->assertSame(
  668. 'CASE WHEN :c0 THEN :c1 ELSE NULL END',
  669. $sql
  670. );
  671. $this->assertSame(
  672. [
  673. ':c0' => [
  674. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  675. 'type' => 'integer',
  676. 'placeholder' => 'c0',
  677. ],
  678. ':c1' => [
  679. 'value' => 1,
  680. 'type' => 'integer',
  681. 'placeholder' => 'c1',
  682. ],
  683. ],
  684. $valueBinder->bindings()
  685. );
  686. }
  687. public function testSqlInjectionViaTypedWhenArrayValueIsNotPossible(): void
  688. {
  689. $this->expectException(InvalidArgumentException::class);
  690. $this->expectExceptionMessage(
  691. 'When using an array for the `$when` argument, the `$type` ' .
  692. 'argument must be an array too, `string` given.'
  693. );
  694. (new CaseStatementExpression())
  695. ->when(['1 THEN 1 END; DELETE * FROM foo; --' => '123'], 'integer')
  696. ->then(1);
  697. }
  698. public function testSqlInjectionViaUntypedWhenValueIsNotPossible()
  699. {
  700. $expression = (new CaseStatementExpression())
  701. ->when('1 THEN 1 END; DELETE * FROM foo; --')
  702. ->then(1);
  703. $valueBinder = new ValueBinder();
  704. $sql = $expression->sql($valueBinder);
  705. $this->assertSame(
  706. 'CASE WHEN :c0 THEN :c1 ELSE NULL END',
  707. $sql
  708. );
  709. $this->assertSame(
  710. [
  711. ':c0' => [
  712. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  713. 'type' => 'string',
  714. 'placeholder' => 'c0',
  715. ],
  716. ':c1' => [
  717. 'value' => 1,
  718. 'type' => 'integer',
  719. 'placeholder' => 'c1',
  720. ],
  721. ],
  722. $valueBinder->bindings()
  723. );
  724. }
  725. public function testSqlInjectionViaTypedThenValueIsNotPossible(): void
  726. {
  727. $expression = (new CaseStatementExpression(1))
  728. ->when(2)
  729. ->then('1 THEN 1 END; DELETE * FROM foo; --', 'integer');
  730. $valueBinder = new ValueBinder();
  731. $sql = $expression->sql($valueBinder);
  732. $this->assertSame(
  733. 'CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END',
  734. $sql
  735. );
  736. $this->assertSame(
  737. [
  738. ':c0' => [
  739. 'value' => 1,
  740. 'type' => 'integer',
  741. 'placeholder' => 'c0',
  742. ],
  743. ':c1' => [
  744. 'value' => 2,
  745. 'type' => 'integer',
  746. 'placeholder' => 'c1',
  747. ],
  748. ':c2' => [
  749. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  750. 'type' => 'integer',
  751. 'placeholder' => 'c2',
  752. ],
  753. ],
  754. $valueBinder->bindings()
  755. );
  756. }
  757. public function testSqlInjectionViaUntypedThenValueIsNotPossible(): void
  758. {
  759. $expression = (new CaseStatementExpression(1))
  760. ->when(2)
  761. ->then('1 THEN 1 END; DELETE * FROM foo; --');
  762. $valueBinder = new ValueBinder();
  763. $sql = $expression->sql($valueBinder);
  764. $this->assertSame(
  765. 'CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END',
  766. $sql
  767. );
  768. $this->assertSame(
  769. [
  770. ':c0' => [
  771. 'value' => 1,
  772. 'type' => 'integer',
  773. 'placeholder' => 'c0',
  774. ],
  775. ':c1' => [
  776. 'value' => 2,
  777. 'type' => 'integer',
  778. 'placeholder' => 'c1',
  779. ],
  780. ':c2' => [
  781. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  782. 'type' => 'string',
  783. 'placeholder' => 'c2',
  784. ],
  785. ],
  786. $valueBinder->bindings()
  787. );
  788. }
  789. public function testSqlInjectionViaTypedElseValueIsNotPossible(): void
  790. {
  791. $expression = (new CaseStatementExpression(1))
  792. ->when(2)
  793. ->then(3)
  794. ->else('1 THEN 1 END; DELETE * FROM foo; --', 'integer');
  795. $valueBinder = new ValueBinder();
  796. $sql = $expression->sql($valueBinder);
  797. $this->assertSame(
  798. 'CASE :c0 WHEN :c1 THEN :c2 ELSE :c3 END',
  799. $sql
  800. );
  801. $this->assertSame(
  802. [
  803. ':c0' => [
  804. 'value' => 1,
  805. 'type' => 'integer',
  806. 'placeholder' => 'c0',
  807. ],
  808. ':c1' => [
  809. 'value' => 2,
  810. 'type' => 'integer',
  811. 'placeholder' => 'c1',
  812. ],
  813. ':c2' => [
  814. 'value' => 3,
  815. 'type' => 'integer',
  816. 'placeholder' => 'c2',
  817. ],
  818. ':c3' => [
  819. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  820. 'type' => 'integer',
  821. 'placeholder' => 'c3',
  822. ],
  823. ],
  824. $valueBinder->bindings()
  825. );
  826. }
  827. public function testSqlInjectionViaUntypedElseValueIsNotPossible(): void
  828. {
  829. $expression = (new CaseStatementExpression(1))
  830. ->when(2)
  831. ->then(3)
  832. ->else('1 THEN 1 END; DELETE * FROM foo; --');
  833. $valueBinder = new ValueBinder();
  834. $sql = $expression->sql($valueBinder);
  835. $this->assertSame(
  836. 'CASE :c0 WHEN :c1 THEN :c2 ELSE :c3 END',
  837. $sql
  838. );
  839. $this->assertSame(
  840. [
  841. ':c0' => [
  842. 'value' => 1,
  843. 'type' => 'integer',
  844. 'placeholder' => 'c0',
  845. ],
  846. ':c1' => [
  847. 'value' => 2,
  848. 'type' => 'integer',
  849. 'placeholder' => 'c1',
  850. ],
  851. ':c2' => [
  852. 'value' => 3,
  853. 'type' => 'integer',
  854. 'placeholder' => 'c2',
  855. ],
  856. ':c3' => [
  857. 'value' => '1 THEN 1 END; DELETE * FROM foo; --',
  858. 'type' => 'string',
  859. 'placeholder' => 'c3',
  860. ],
  861. ],
  862. $valueBinder->bindings()
  863. );
  864. }
  865. // endregion
  866. // region Getters
  867. public function testGetInvalidCaseExpressionClause()
  868. {
  869. $this->expectException(InvalidArgumentException::class);
  870. $this->expectExceptionMessage(
  871. 'The `$clause` argument must be one of `value`, `when`, `else`, the given value `invalid` is invalid.'
  872. );
  873. (new CaseStatementExpression())->clause('invalid');
  874. }
  875. public function testGetInvalidWhenThenExpressionClause()
  876. {
  877. $this->expectException(InvalidArgumentException::class);
  878. $this->expectExceptionMessage(
  879. 'The `$clause` argument must be one of `when`, `then`, the given value `invalid` is invalid.'
  880. );
  881. (new WhenThenExpression())->clause('invalid');
  882. }
  883. public function testGetValueClause(): void
  884. {
  885. $expression = new CaseStatementExpression();
  886. $this->assertNull($expression->clause('value'));
  887. $expression = (new CaseStatementExpression(1))
  888. ->when(1)
  889. ->then(2);
  890. $this->assertSame(1, $expression->clause('value'));
  891. }
  892. public function testGetWhenClause(): void
  893. {
  894. $when = ['Table.column' => true];
  895. $expression = new CaseStatementExpression();
  896. $this->assertSame([], $expression->clause('when'));
  897. $expression
  898. ->when($when)
  899. ->then(1);
  900. $this->assertCount(1, $expression->clause('when'));
  901. $this->assertInstanceOf(WhenThenExpression::class, $expression->clause('when')[0]);
  902. }
  903. public function testWhenArrayValueGetWhenClause(): void
  904. {
  905. $when = ['Table.column' => true];
  906. $expression = new CaseStatementExpression();
  907. $this->assertSame([], $expression->clause('when'));
  908. $expression
  909. ->when($when)
  910. ->then(1);
  911. $this->assertEquals(
  912. new QueryExpression($when),
  913. $expression->clause('when')[0]->clause('when')
  914. );
  915. }
  916. public function testWhenNonArrayValueGetWhenClause(): void
  917. {
  918. $expression = new CaseStatementExpression();
  919. $this->assertSame([], $expression->clause('when'));
  920. $expression
  921. ->when(1)
  922. ->then(2);
  923. $this->assertSame(1, $expression->clause('when')[0]->clause('when'));
  924. }
  925. public function testWhenGetThenClause(): void
  926. {
  927. $expression = (new CaseStatementExpression())
  928. ->when(function (WhenThenExpression $whenThen) {
  929. return $whenThen;
  930. });
  931. $this->assertNull($expression->clause('when')[0]->clause('then'));
  932. $expression->clause('when')[0]->then(1);
  933. $this->assertSame(1, $expression->clause('when')[0]->clause('then'));
  934. }
  935. public function testGetElseClause(): void
  936. {
  937. $expression = new CaseStatementExpression();
  938. $this->assertNull($expression->clause('else'));
  939. $expression
  940. ->when(['Table.column' => true])
  941. ->then(1)
  942. ->else(2);
  943. $this->assertSame(2, $expression->clause('else'));
  944. }
  945. // endregion
  946. // region Order based syntax
  947. public function testWhenThenElse(): void
  948. {
  949. $expression = (new CaseStatementExpression())
  950. ->when(['Table.column_a' => true, 'Table.column_b IS' => null])
  951. ->then(1)
  952. ->when(['Table.column_c' => true, 'Table.column_d IS NOT' => null])
  953. ->then(2)
  954. ->else(3);
  955. $valueBinder = new ValueBinder();
  956. $sql = $expression->sql($valueBinder);
  957. $this->assertSame(
  958. 'CASE ' .
  959. 'WHEN (Table.column_a = :c0 AND (Table.column_b) IS NULL) THEN :c1 ' .
  960. 'WHEN (Table.column_c = :c2 AND (Table.column_d) IS NOT NULL) THEN :c3 ' .
  961. 'ELSE :c4 ' .
  962. 'END',
  963. $sql
  964. );
  965. }
  966. public function testWhenBeforeClosingThenFails(): void
  967. {
  968. $this->expectException(LogicException::class);
  969. $this->expectExceptionMessage('Cannot call `when()` between `when()` and `then()`.');
  970. (new CaseStatementExpression())
  971. ->when(['Table.column_a' => true])
  972. ->when(['Table.column_b' => true]);
  973. }
  974. public function testElseBeforeClosingThenFails(): void
  975. {
  976. $this->expectException(LogicException::class);
  977. $this->expectExceptionMessage('Cannot call `else()` between `when()` and `then()`.');
  978. (new CaseStatementExpression())
  979. ->when(['Table.column' => true])
  980. ->else(1);
  981. }
  982. public function testThenBeforeOpeningWhenFails(): void
  983. {
  984. $this->expectException(LogicException::class);
  985. $this->expectExceptionMessage('Cannot call `then()` before `when()`.');
  986. (new CaseStatementExpression())
  987. ->then(1);
  988. }
  989. // endregion
  990. // region Callable syntax
  991. public function testWhenCallables(): void
  992. {
  993. $expression = (new CaseStatementExpression())
  994. ->when(function (WhenThenExpression $whenThen) {
  995. return $whenThen
  996. ->when([
  997. 'Table.column_a' => true,
  998. 'Table.column_b IS' => null,
  999. ])
  1000. ->then(1);
  1001. })
  1002. ->when(function (WhenThenExpression $whenThen) {
  1003. return $whenThen
  1004. ->when([
  1005. 'Table.column_c' => true,
  1006. 'Table.column_d IS NOT' => null,
  1007. ])
  1008. ->then(2);
  1009. })
  1010. ->else(3);
  1011. $valueBinder = new ValueBinder();
  1012. $sql = $expression->sql($valueBinder);
  1013. $this->assertSame(
  1014. 'CASE ' .
  1015. 'WHEN (Table.column_a = :c0 AND (Table.column_b) IS NULL) THEN :c1 ' .
  1016. 'WHEN (Table.column_c = :c2 AND (Table.column_d) IS NOT NULL) THEN :c3 ' .
  1017. 'ELSE :c4 ' .
  1018. 'END',
  1019. $sql
  1020. );
  1021. }
  1022. public function testWhenCallablesWithCustomWhenThenExpressions(): void
  1023. {
  1024. $expression = (new CaseStatementExpression())
  1025. ->when(function () {
  1026. return (new CustomWhenThenExpression())
  1027. ->when([
  1028. 'Table.column_a' => true,
  1029. 'Table.column_b IS' => null,
  1030. ])
  1031. ->then(1);
  1032. })
  1033. ->when(function () {
  1034. return (new CustomWhenThenExpression())
  1035. ->when([
  1036. 'Table.column_c' => true,
  1037. 'Table.column_d IS NOT' => null,
  1038. ])
  1039. ->then(2);
  1040. })
  1041. ->else(3);
  1042. $valueBinder = new ValueBinder();
  1043. $sql = $expression->sql($valueBinder);
  1044. $this->assertSame(
  1045. 'CASE ' .
  1046. 'WHEN (Table.column_a = :c0 AND (Table.column_b) IS NULL) THEN :c1 ' .
  1047. 'WHEN (Table.column_c = :c2 AND (Table.column_d) IS NOT NULL) THEN :c3 ' .
  1048. 'ELSE :c4 ' .
  1049. 'END',
  1050. $sql
  1051. );
  1052. }
  1053. public function testWhenCallablesWithInvalidReturnTypeFails(): void
  1054. {
  1055. $this->expectException(LogicException::class);
  1056. $this->expectExceptionMessage(
  1057. '`when()` callables must return an instance of ' .
  1058. '`\Cake\Database\Expression\WhenThenExpression`, `null` given.'
  1059. );
  1060. $this->deprecated(function () {
  1061. (new CaseStatementExpression())
  1062. ->when(function () {
  1063. return null;
  1064. });
  1065. });
  1066. }
  1067. // endregion
  1068. // region Self-contained values
  1069. public function testSelfContainedWhenThenExpressions(): void
  1070. {
  1071. $expression = (new CaseStatementExpression())
  1072. ->when(
  1073. (new WhenThenExpression())
  1074. ->when([
  1075. 'Table.column_a' => true,
  1076. 'Table.column_b IS' => null,
  1077. ])
  1078. ->then(1)
  1079. )
  1080. ->when(
  1081. (new WhenThenExpression())
  1082. ->when([
  1083. 'Table.column_c' => true,
  1084. 'Table.column_d IS NOT' => null,
  1085. ])
  1086. ->then(2)
  1087. )
  1088. ->else(3);
  1089. $valueBinder = new ValueBinder();
  1090. $sql = $expression->sql($valueBinder);
  1091. $this->assertSame(
  1092. 'CASE ' .
  1093. 'WHEN (Table.column_a = :c0 AND (Table.column_b) IS NULL) THEN :c1 ' .
  1094. 'WHEN (Table.column_c = :c2 AND (Table.column_d) IS NOT NULL) THEN :c3 ' .
  1095. 'ELSE :c4 ' .
  1096. 'END',
  1097. $sql
  1098. );
  1099. }
  1100. public function testSelfContainedCustomWhenThenExpressions(): void
  1101. {
  1102. $expression = (new CaseStatementExpression())
  1103. ->when(
  1104. (new CustomWhenThenExpression())
  1105. ->when([
  1106. 'Table.column_a' => true,
  1107. 'Table.column_b IS' => null,
  1108. ])
  1109. ->then(1)
  1110. )
  1111. ->when(
  1112. (new CustomWhenThenExpression())
  1113. ->when([
  1114. 'Table.column_c' => true,
  1115. 'Table.column_d IS NOT' => null,
  1116. ])
  1117. ->then(2)
  1118. )
  1119. ->else(3);
  1120. $valueBinder = new ValueBinder();
  1121. $sql = $expression->sql($valueBinder);
  1122. $this->assertSame(
  1123. 'CASE ' .
  1124. 'WHEN (Table.column_a = :c0 AND (Table.column_b) IS NULL) THEN :c1 ' .
  1125. 'WHEN (Table.column_c = :c2 AND (Table.column_d) IS NOT NULL) THEN :c3 ' .
  1126. 'ELSE :c4 ' .
  1127. 'END',
  1128. $sql
  1129. );
  1130. }
  1131. // endregion
  1132. // region Incomplete states
  1133. public function testCompilingEmptyCaseExpressionFails(): void
  1134. {
  1135. $this->expectException(LogicException::class);
  1136. $this->expectExceptionMessage('Case expression must have at least one when statement.');
  1137. $this->deprecated(function () {
  1138. (new CaseStatementExpression())->sql(new ValueBinder());
  1139. });
  1140. }
  1141. public function testCompilingNonClosedWhenFails(): void
  1142. {
  1143. $this->expectException(LogicException::class);
  1144. $this->expectExceptionMessage('Case expression has incomplete when clause. Missing `then()` after `when()`.');
  1145. $this->deprecated(function () {
  1146. (new CaseStatementExpression())
  1147. ->when(['Table.column' => true])
  1148. ->sql(new ValueBinder());
  1149. });
  1150. }
  1151. public function testCompilingWhenThenExpressionWithMissingWhenFails(): void
  1152. {
  1153. $this->expectException(LogicException::class);
  1154. $this->expectExceptionMessage('Case expression has incomplete when clause. Missing `when()`.');
  1155. $this->deprecated(function () {
  1156. (new CaseStatementExpression())
  1157. ->when(function (WhenThenExpression $whenThen) {
  1158. return $whenThen->then(1);
  1159. })
  1160. ->sql(new ValueBinder());
  1161. });
  1162. }
  1163. public function testCompilingWhenThenExpressionWithMissingThenFails(): void
  1164. {
  1165. $this->expectException(LogicException::class);
  1166. $this->expectExceptionMessage('Case expression has incomplete when clause. Missing `then()` after `when()`.');
  1167. $this->deprecated(function () {
  1168. (new CaseStatementExpression())
  1169. ->when(function (WhenThenExpression $whenThen) {
  1170. return $whenThen->when(1);
  1171. })
  1172. ->sql(new ValueBinder());
  1173. });
  1174. }
  1175. // endregion
  1176. // region Valid values
  1177. public static function validCaseValuesDataProvider(): array
  1178. {
  1179. return [
  1180. [null, 'NULL', null],
  1181. ['0', null, 'string'],
  1182. [0, null, 'integer'],
  1183. [0.0, null, 'float'],
  1184. ['foo', null, 'string'],
  1185. [true, null, 'boolean'],
  1186. [Date::now(), null, 'date'],
  1187. [ChronosDate::now(), null, 'date'],
  1188. [DateTime::now(), null, 'datetime'],
  1189. [Chronos::now(), null, 'datetime'],
  1190. [new IdentifierExpression('Table.column'), 'Table.column', null],
  1191. [new QueryExpression('Table.column'), 'Table.column', null],
  1192. [ConnectionManager::get('test')->selectQuery('a'), '(SELECT a)', null],
  1193. [new TestObjectWithToString(), null, 'string'],
  1194. [new stdClass(), null, null],
  1195. ];
  1196. }
  1197. /**
  1198. * @dataProvider validCaseValuesDataProvider
  1199. * @param mixed $value The case value.
  1200. * @param string|null $sqlValue The expected SQL string value.
  1201. * @param string|null $type The expected bound type.
  1202. */
  1203. public function testValidCaseValue($value, ?string $sqlValue, ?string $type): void
  1204. {
  1205. $expression = (new CaseStatementExpression($value))
  1206. ->when(1)
  1207. ->then(2);
  1208. $valueBinder = new ValueBinder();
  1209. $sql = $expression->sql($valueBinder);
  1210. if ($sqlValue) {
  1211. $this->assertEqualsSql(
  1212. "CASE $sqlValue WHEN :c0 THEN :c1 ELSE NULL END",
  1213. $sql
  1214. );
  1215. $this->assertSame(
  1216. [
  1217. ':c0' => [
  1218. 'value' => 1,
  1219. 'type' => 'integer',
  1220. 'placeholder' => 'c0',
  1221. ],
  1222. ':c1' => [
  1223. 'value' => 2,
  1224. 'type' => 'integer',
  1225. 'placeholder' => 'c1',
  1226. ],
  1227. ],
  1228. $valueBinder->bindings()
  1229. );
  1230. } else {
  1231. $this->assertEqualsSql(
  1232. 'CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END',
  1233. $sql
  1234. );
  1235. $this->assertSame(
  1236. [
  1237. ':c0' => [
  1238. 'value' => $value,
  1239. 'type' => $type,
  1240. 'placeholder' => 'c0',
  1241. ],
  1242. ':c1' => [
  1243. 'value' => 1,
  1244. 'type' => 'integer',
  1245. 'placeholder' => 'c1',
  1246. ],
  1247. ':c2' => [
  1248. 'value' => 2,
  1249. 'type' => 'integer',
  1250. 'placeholder' => 'c2',
  1251. ],
  1252. ],
  1253. $valueBinder->bindings()
  1254. );
  1255. }
  1256. }
  1257. public static function validWhenValuesSimpleCaseDataProvider(): array
  1258. {
  1259. return [
  1260. ['0', null, 'string'],
  1261. [0, null, 'integer'],
  1262. [0.0, null, 'float'],
  1263. ['foo', null, 'string'],
  1264. [true, null, 'boolean'],
  1265. [new stdClass(), null, null],
  1266. [new TestObjectWithToString(), null, 'string'],
  1267. [Date::now(), null, 'date'],
  1268. [ChronosDate::now(), null, 'date'],
  1269. [DateTime::now(), null, 'datetime'],
  1270. [Chronos::now(), null, 'datetime'],
  1271. [
  1272. new IdentifierExpression('Table.column'),
  1273. 'CASE :c0 WHEN Table.column THEN :c1 ELSE NULL END',
  1274. [
  1275. ':c0' => [
  1276. 'value' => true,
  1277. 'type' => 'boolean',
  1278. 'placeholder' => 'c0',
  1279. ],
  1280. ':c1' => [
  1281. 'value' => 2,
  1282. 'type' => 'integer',
  1283. 'placeholder' => 'c1',
  1284. ],
  1285. ],
  1286. ],
  1287. [
  1288. new QueryExpression('Table.column'),
  1289. 'CASE :c0 WHEN Table.column THEN :c1 ELSE NULL END',
  1290. [
  1291. ':c0' => [
  1292. 'value' => true,
  1293. 'type' => 'boolean',
  1294. 'placeholder' => 'c0',
  1295. ],
  1296. ':c1' => [
  1297. 'value' => 2,
  1298. 'type' => 'integer',
  1299. 'placeholder' => 'c1',
  1300. ],
  1301. ],
  1302. ],
  1303. [
  1304. ConnectionManager::get('test')->selectQuery('a'),
  1305. 'CASE :c0 WHEN (SELECT a) THEN :c1 ELSE NULL END',
  1306. [
  1307. ':c0' => [
  1308. 'value' => true,
  1309. 'type' => 'boolean',
  1310. 'placeholder' => 'c0',
  1311. ],
  1312. ':c1' => [
  1313. 'value' => 2,
  1314. 'type' => 'integer',
  1315. 'placeholder' => 'c1',
  1316. ],
  1317. ],
  1318. ],
  1319. [
  1320. [
  1321. 'Table.column_a' => 1,
  1322. 'Table.column_b' => 'foo',
  1323. ],
  1324. 'CASE :c0 WHEN (Table.column_a = :c1 AND Table.column_b = :c2) THEN :c3 ELSE NULL END',
  1325. [
  1326. ':c0' => [
  1327. 'value' => true,
  1328. 'type' => 'boolean',
  1329. 'placeholder' => 'c0',
  1330. ],
  1331. ':c1' => [
  1332. 'value' => 1,
  1333. 'type' => 'integer',
  1334. 'placeholder' => 'c1',
  1335. ],
  1336. ':c2' => [
  1337. 'value' => 'foo',
  1338. 'type' => 'string',
  1339. 'placeholder' => 'c2',
  1340. ],
  1341. ':c3' => [
  1342. 'value' => 2,
  1343. 'type' => 'integer',
  1344. 'placeholder' => 'c3',
  1345. ],
  1346. ],
  1347. ],
  1348. ];
  1349. }
  1350. /**
  1351. * @dataProvider validWhenValuesSimpleCaseDataProvider
  1352. * @param mixed $value The when value.
  1353. * @param string|null $expectedSql The expected SQL string.
  1354. * @param array|string|null $typeOrBindings The expected bound type(s).
  1355. */
  1356. public function testValidWhenValueSimpleCase($value, ?string $expectedSql, $typeOrBindings = null): void
  1357. {
  1358. $typeMap = new TypeMap([
  1359. 'Table.column_a' => 'integer',
  1360. 'Table.column_b' => 'string',
  1361. ]);
  1362. $expression = (new CaseStatementExpression(true))
  1363. ->setTypeMap($typeMap)
  1364. ->when($value)
  1365. ->then(2);
  1366. $valueBinder = new ValueBinder();
  1367. $sql = $expression->sql($valueBinder);
  1368. if ($expectedSql) {
  1369. $this->assertEqualsSql($expectedSql, $sql);
  1370. $this->assertSame($typeOrBindings, $valueBinder->bindings());
  1371. } else {
  1372. $this->assertEqualsSql('CASE :c0 WHEN :c1 THEN :c2 ELSE NULL END', $sql);
  1373. $this->assertSame(
  1374. [
  1375. ':c0' => [
  1376. 'value' => true,
  1377. 'type' => 'boolean',
  1378. 'placeholder' => 'c0',
  1379. ],
  1380. ':c1' => [
  1381. 'value' => $value,
  1382. 'type' => $typeOrBindings,
  1383. 'placeholder' => 'c1',
  1384. ],
  1385. ':c2' => [
  1386. 'value' => 2,
  1387. 'type' => 'integer',
  1388. 'placeholder' => 'c2',
  1389. ],
  1390. ],
  1391. $valueBinder->bindings()
  1392. );
  1393. }
  1394. }
  1395. public static function validWhenValuesSearchedCaseDataProvider(): array
  1396. {
  1397. return [
  1398. ['0', null, 'string'],
  1399. [0, null, 'integer'],
  1400. [0.0, null, 'float'],
  1401. ['foo', null, 'string'],
  1402. [true, null, 'boolean'],
  1403. [new stdClass(), null, null],
  1404. [new TestObjectWithToString(), null, 'string'],
  1405. [Date::now(), null, 'date'],
  1406. [ChronosDate::now(), null, 'date'],
  1407. [DateTime::now(), null, 'datetime'],
  1408. [Chronos::now(), null, 'datetime'],
  1409. [
  1410. new IdentifierExpression('Table.column'),
  1411. 'CASE WHEN Table.column THEN :c0 ELSE NULL END',
  1412. [
  1413. ':c0' => [
  1414. 'value' => 2,
  1415. 'type' => 'integer',
  1416. 'placeholder' => 'c0',
  1417. ],
  1418. ],
  1419. ],
  1420. [
  1421. new QueryExpression('Table.column'),
  1422. 'CASE WHEN Table.column THEN :c0 ELSE NULL END',
  1423. [
  1424. ':c0' => [
  1425. 'value' => 2,
  1426. 'type' => 'integer',
  1427. 'placeholder' => 'c0',
  1428. ],
  1429. ],
  1430. ],
  1431. [
  1432. ConnectionManager::get('test')->selectQuery('a'),
  1433. 'CASE WHEN (SELECT a) THEN :c0 ELSE NULL END',
  1434. [
  1435. ':c0' => [
  1436. 'value' => 2,
  1437. 'type' => 'integer',
  1438. 'placeholder' => 'c0',
  1439. ],
  1440. ],
  1441. ],
  1442. [
  1443. [
  1444. 'Table.column_a' => 1,
  1445. 'Table.column_b' => 'foo',
  1446. ],
  1447. 'CASE WHEN (Table.column_a = :c0 AND Table.column_b = :c1) THEN :c2 ELSE NULL END',
  1448. [
  1449. ':c0' => [
  1450. 'value' => 1,
  1451. 'type' => 'integer',
  1452. 'placeholder' => 'c0',
  1453. ],
  1454. ':c1' => [
  1455. 'value' => 'foo',
  1456. 'type' => 'string',
  1457. 'placeholder' => 'c1',
  1458. ],
  1459. ':c2' => [
  1460. 'value' => 2,
  1461. 'type' => 'integer',
  1462. 'placeholder' => 'c2',
  1463. ],
  1464. ],
  1465. ],
  1466. ];
  1467. }
  1468. /**
  1469. * @dataProvider validWhenValuesSearchedCaseDataProvider
  1470. * @param mixed $value The when value.
  1471. * @param string|null $expectedSql The expected SQL string.
  1472. * @param array|string|null $typeOrBindings The expected bound type(s).
  1473. */
  1474. public function testValidWhenValueSearchedCase($value, ?string $expectedSql, $typeOrBindings = null): void
  1475. {
  1476. $typeMap = new TypeMap([
  1477. 'Table.column_a' => 'integer',
  1478. 'Table.column_b' => 'string',
  1479. ]);
  1480. $expression = (new CaseStatementExpression())
  1481. ->setTypeMap($typeMap)
  1482. ->when($value)
  1483. ->then(2);
  1484. $valueBinder = new ValueBinder();
  1485. $sql = $expression->sql($valueBinder);
  1486. if ($expectedSql) {
  1487. $this->assertEqualsSql($expectedSql, $sql);
  1488. $this->assertSame($typeOrBindings, $valueBinder->bindings());
  1489. } else {
  1490. $this->assertEqualsSql('CASE WHEN :c0 THEN :c1 ELSE NULL END', $sql);
  1491. $this->assertSame(
  1492. [
  1493. ':c0' => [
  1494. 'value' => $value,
  1495. 'type' => $typeOrBindings,
  1496. 'placeholder' => 'c0',
  1497. ],
  1498. ':c1' => [
  1499. 'value' => 2,
  1500. 'type' => 'integer',
  1501. 'placeholder' => 'c1',
  1502. ],
  1503. ],
  1504. $valueBinder->bindings()
  1505. );
  1506. }
  1507. }
  1508. public static function validThenValuesDataProvider(): array
  1509. {
  1510. return [
  1511. [null, 'NULL', null],
  1512. ['0', null, 'string'],
  1513. [0, null, 'integer'],
  1514. [0.0, null, 'float'],
  1515. ['foo', null, 'string'],
  1516. [true, null, 'boolean'],
  1517. [Date::now(), null, 'date'],
  1518. [ChronosDate::now(), null, 'date'],
  1519. [DateTime::now(), null, 'datetime'],
  1520. [Chronos::now(), null, 'datetime'],
  1521. [new IdentifierExpression('Table.column'), 'Table.column', null],
  1522. [new QueryExpression('Table.column'), 'Table.column', null],
  1523. [ConnectionManager::get('test')->selectQuery('a'), '(SELECT a)', null],
  1524. [new TestObjectWithToString(), null, 'string'],
  1525. [new stdClass(), null, null],
  1526. ];
  1527. }
  1528. /**
  1529. * @dataProvider validThenValuesDataProvider
  1530. * @param mixed $value The then value.
  1531. * @param string|null $sqlValue The expected SQL string value.
  1532. * @param string|null $type The expected bound type.
  1533. */
  1534. public function testValidThenValue($value, ?string $sqlValue, ?string $type): void
  1535. {
  1536. $expression = (new CaseStatementExpression())
  1537. ->when(1)
  1538. ->then($value);
  1539. $valueBinder = new ValueBinder();
  1540. $sql = $expression->sql($valueBinder);
  1541. if ($sqlValue) {
  1542. $this->assertEqualsSql(
  1543. "CASE WHEN :c0 THEN $sqlValue ELSE NULL END",
  1544. $sql
  1545. );
  1546. $this->assertSame(
  1547. [
  1548. ':c0' => [
  1549. 'value' => 1,
  1550. 'type' => 'integer',
  1551. 'placeholder' => 'c0',
  1552. ],
  1553. ],
  1554. $valueBinder->bindings()
  1555. );
  1556. } else {
  1557. $this->assertEqualsSql(
  1558. 'CASE WHEN :c0 THEN :c1 ELSE NULL END',
  1559. $sql
  1560. );
  1561. $this->assertSame(
  1562. [
  1563. ':c0' => [
  1564. 'value' => 1,
  1565. 'type' => 'integer',
  1566. 'placeholder' => 'c0',
  1567. ],
  1568. ':c1' => [
  1569. 'value' => $value,
  1570. 'type' => $type,
  1571. 'placeholder' => 'c1',
  1572. ],
  1573. ],
  1574. $valueBinder->bindings()
  1575. );
  1576. }
  1577. }
  1578. public static function validElseValuesDataProvider(): array
  1579. {
  1580. return [
  1581. [null, 'NULL', null],
  1582. ['0', null, 'string'],
  1583. [0, null, 'integer'],
  1584. [0.0, null, 'float'],
  1585. ['foo', null, 'string'],
  1586. [true, null, 'boolean'],
  1587. [Date::now(), null, 'date'],
  1588. [ChronosDate::now(), null, 'date'],
  1589. [DateTime::now(), null, 'datetime'],
  1590. [Chronos::now(), null, 'datetime'],
  1591. [new IdentifierExpression('Table.column'), 'Table.column', null],
  1592. [new QueryExpression('Table.column'), 'Table.column', null],
  1593. [ConnectionManager::get('test')->selectQuery('a'), '(SELECT a)', null],
  1594. [new TestObjectWithToString(), null, 'string'],
  1595. [new stdClass(), null, null],
  1596. ];
  1597. }
  1598. /**
  1599. * @dataProvider validElseValuesDataProvider
  1600. * @param mixed $value The else value.
  1601. * @param string|null $sqlValue The expected SQL string value.
  1602. * @param string|null $type The expected bound type.
  1603. */
  1604. public function testValidElseValue($value, ?string $sqlValue, ?string $type): void
  1605. {
  1606. $expression = (new CaseStatementExpression())
  1607. ->when(1)
  1608. ->then(2)
  1609. ->else($value);
  1610. $valueBinder = new ValueBinder();
  1611. $sql = $expression->sql($valueBinder);
  1612. if ($sqlValue) {
  1613. $this->assertEqualsSql(
  1614. "CASE WHEN :c0 THEN :c1 ELSE $sqlValue END",
  1615. $sql
  1616. );
  1617. $this->assertSame(
  1618. [
  1619. ':c0' => [
  1620. 'value' => 1,
  1621. 'type' => 'integer',
  1622. 'placeholder' => 'c0',
  1623. ],
  1624. ':c1' => [
  1625. 'value' => 2,
  1626. 'type' => 'integer',
  1627. 'placeholder' => 'c1',
  1628. ],
  1629. ],
  1630. $valueBinder->bindings()
  1631. );
  1632. } else {
  1633. $this->assertEqualsSql(
  1634. 'CASE WHEN :c0 THEN :c1 ELSE :c2 END',
  1635. $sql
  1636. );
  1637. $this->assertSame(
  1638. [
  1639. ':c0' => [
  1640. 'value' => 1,
  1641. 'type' => 'integer',
  1642. 'placeholder' => 'c0',
  1643. ],
  1644. ':c1' => [
  1645. 'value' => 2,
  1646. 'type' => 'integer',
  1647. 'placeholder' => 'c1',
  1648. ],
  1649. ':c2' => [
  1650. 'value' => $value,
  1651. 'type' => $type,
  1652. 'placeholder' => 'c2',
  1653. ],
  1654. ],
  1655. $valueBinder->bindings()
  1656. );
  1657. }
  1658. }
  1659. // endregion
  1660. // region Invalid values
  1661. public static function invalidCaseValuesDataProvider(): array
  1662. {
  1663. $res = fopen('data:text/plain,123', 'rb');
  1664. fclose($res);
  1665. return [
  1666. [[], 'array'],
  1667. [
  1668. function () {
  1669. },
  1670. 'Closure',
  1671. ],
  1672. [$res, 'resource (closed)'],
  1673. ];
  1674. }
  1675. /**
  1676. * @dataProvider invalidCaseValuesDataProvider
  1677. * @param mixed $value The case value.
  1678. * @param string $typeName The expected error type name.
  1679. */
  1680. public function testInvalidCaseValue($value, string $typeName): void
  1681. {
  1682. $this->expectException(InvalidArgumentException::class);
  1683. $this->expectExceptionMessage(
  1684. 'The `$value` argument must be either `null`, a scalar value, an object, ' .
  1685. "or an instance of `\\Cake\\Database\\ExpressionInterface`, `$typeName` given."
  1686. );
  1687. new CaseStatementExpression($value);
  1688. }
  1689. public function testInvalidWhenValue(): void
  1690. {
  1691. $this->expectException(InvalidArgumentException::class);
  1692. $this->expectExceptionMessage(
  1693. 'The `$when` argument must be a non-empty array'
  1694. );
  1695. (new CaseStatementExpression())
  1696. ->when([])
  1697. ->then(1);
  1698. }
  1699. public static function invalidThenValueDataProvider(): array
  1700. {
  1701. $res = fopen('data:text/plain,123', 'rb');
  1702. fclose($res);
  1703. return [
  1704. [[], 'array'],
  1705. [
  1706. function () {
  1707. },
  1708. 'Closure',
  1709. ],
  1710. [$res, 'resource (closed)'],
  1711. ];
  1712. }
  1713. /**
  1714. * @dataProvider invalidThenValueDataProvider
  1715. * @param mixed $value The then value.
  1716. * @param string $typeName The expected error type name.
  1717. */
  1718. public function testInvalidThenValue($value, string $typeName): void
  1719. {
  1720. $this->expectException(InvalidArgumentException::class);
  1721. $this->expectExceptionMessage(
  1722. 'The `$result` argument must be either `null`, a scalar value, an object, ' .
  1723. "or an instance of `\\Cake\\Database\\ExpressionInterface`, `$typeName` given."
  1724. );
  1725. (new CaseStatementExpression())
  1726. ->when(1)
  1727. ->then($value);
  1728. }
  1729. public static function invalidThenTypeDataProvider(): array
  1730. {
  1731. $res = fopen('data:text/plain,123', 'rb');
  1732. fclose($res);
  1733. return [
  1734. [1],
  1735. [1.0],
  1736. [new stdClass()],
  1737. [
  1738. function () {
  1739. },
  1740. ],
  1741. [$res, 'resource (closed)'],
  1742. ];
  1743. }
  1744. /**
  1745. * @dataProvider invalidThenTypeDataProvider
  1746. * @param mixed $type The then type.
  1747. */
  1748. public function testInvalidThenType($type): void
  1749. {
  1750. $this->expectException(TypeError::class);
  1751. (new CaseStatementExpression())
  1752. ->when(1)
  1753. ->then(1, $type);
  1754. }
  1755. public static function invalidElseValueDataProvider(): array
  1756. {
  1757. $res = fopen('data:text/plain,123', 'rb');
  1758. fclose($res);
  1759. return [
  1760. [[], 'array'],
  1761. [
  1762. function () {
  1763. },
  1764. 'Closure',
  1765. ],
  1766. [$res, 'resource (closed)'],
  1767. ];
  1768. }
  1769. /**
  1770. * @dataProvider invalidElseValueDataProvider
  1771. * @param mixed $value The else value.
  1772. * @param string $typeName The expected error type name.
  1773. */
  1774. public function testInvalidElseValue($value, string $typeName): void
  1775. {
  1776. $this->expectException(InvalidArgumentException::class);
  1777. $this->expectExceptionMessage(
  1778. 'The `$result` argument must be either `null`, a scalar value, an object, ' .
  1779. "or an instance of `\\Cake\\Database\\ExpressionInterface`, `$typeName` given."
  1780. );
  1781. (new CaseStatementExpression())
  1782. ->when(1)
  1783. ->then(1)
  1784. ->else($value);
  1785. }
  1786. public static function invalidElseTypeDataProvider(): array
  1787. {
  1788. $res = fopen('data:text/plain,123', 'rb');
  1789. fclose($res);
  1790. return [
  1791. [1],
  1792. [1.0],
  1793. [new stdClass()],
  1794. [
  1795. function () {
  1796. },
  1797. 'Closure',
  1798. ],
  1799. [$res, 'resource (closed)'],
  1800. ];
  1801. }
  1802. /**
  1803. * @dataProvider invalidElseTypeDataProvider
  1804. * @param mixed $type The else type.
  1805. */
  1806. public function testInvalidElseType($type): void
  1807. {
  1808. $this->expectException(TypeError::class);
  1809. (new CaseStatementExpression())
  1810. ->when(1)
  1811. ->then(1)
  1812. ->else(1, $type);
  1813. }
  1814. // endregion
  1815. // region Traversal
  1816. public function testTraverse(): void
  1817. {
  1818. $value = new IdentifierExpression('Table.column');
  1819. $conditionsA = ['Table.column_a' => true, 'Table.column_b IS' => null];
  1820. $resultA = new QueryExpression('1');
  1821. $conditionsB = ['Table.column_c' => true, 'Table.column_d IS NOT' => null];
  1822. $resultB = new QueryExpression('2');
  1823. $else = new QueryExpression('3');
  1824. $expression = (new CaseStatementExpression($value))
  1825. ->when($conditionsA)
  1826. ->then($resultA)
  1827. ->when($conditionsB)
  1828. ->then($resultB)
  1829. ->else($else);
  1830. $expressions = [];
  1831. $expression->traverse(function ($expression) use (&$expressions) {
  1832. $expressions[] = $expression;
  1833. });
  1834. $this->assertCount(14, $expressions);
  1835. $this->assertInstanceOf(IdentifierExpression::class, $expressions[0]);
  1836. $this->assertSame($value, $expressions[0]);
  1837. $this->assertInstanceOf(WhenThenExpression::class, $expressions[1]);
  1838. $this->assertEquals(new QueryExpression($conditionsA), $expressions[2]);
  1839. $this->assertEquals(new ComparisonExpression('Table.column_a', true), $expressions[3]);
  1840. $this->assertSame($resultA, $expressions[6]);
  1841. $this->assertInstanceOf(WhenThenExpression::class, $expressions[7]);
  1842. $this->assertEquals(new QueryExpression($conditionsB), $expressions[8]);
  1843. $this->assertEquals(new ComparisonExpression('Table.column_c', true), $expressions[9]);
  1844. $this->assertSame($resultB, $expressions[12]);
  1845. $this->assertSame($else, $expressions[13]);
  1846. }
  1847. public function testTraverseBeforeClosingThenFails(): void
  1848. {
  1849. $this->expectException(LogicException::class);
  1850. $this->expectExceptionMessage('Case expression has incomplete when clause. Missing `then()` after `when()`.');
  1851. $this->deprecated(function () {
  1852. $expression = (new CaseStatementExpression())
  1853. ->when(['Table.column' => true]);
  1854. $expression->traverse(
  1855. function () {
  1856. }
  1857. );
  1858. });
  1859. }
  1860. // endregion
  1861. // region Cloning
  1862. public function testClone(): void
  1863. {
  1864. $value = new IdentifierExpression('Table.column');
  1865. $conditionsA = ['Table.column_a' => true, 'Table.column_b IS' => null];
  1866. $resultA = new QueryExpression('1');
  1867. $conditionsB = ['Table.column_c' => true, 'Table.column_d IS NOT' => null];
  1868. $resultB = new QueryExpression('2');
  1869. $else = new QueryExpression('3');
  1870. $expression = (new CaseStatementExpression($value))
  1871. ->when($conditionsA)
  1872. ->then($resultA)
  1873. ->when($conditionsB)
  1874. ->then($resultB)
  1875. ->else($else);
  1876. $clone = clone $expression;
  1877. $this->assertEquals($clone, $expression);
  1878. $this->assertNotSame($clone, $expression);
  1879. }
  1880. public function testCloneBeforeClosingThenFails(): void
  1881. {
  1882. $this->expectException(LogicException::class);
  1883. $this->expectExceptionMessage('Case expression has incomplete when clause. Missing `then()` after `when()`.');
  1884. $this->deprecated(function () {
  1885. $expression = (new CaseStatementExpression())
  1886. ->when(['Table.column' => true]);
  1887. clone $expression;
  1888. });
  1889. }
  1890. // endregion
  1891. }