CaseStatementExpressionTest.php 72 KB

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