packages/ztd-query-core/tests/Unit/Shadow/Mutation/UpsertUpdateTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Mutation;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Exception\UnsupportedSqlException;
11use ZtdQuery\Shadow\Mutation\Upsert\UpsertColumn;
12use ZtdQuery\Shadow\Mutation\Upsert\UpsertComparison;
13use ZtdQuery\Shadow\Mutation\Upsert\UpsertNumber;
14use ZtdQuery\Shadow\Mutation\Upsert\UpsertOperator;
15use ZtdQuery\Shadow\Mutation\Upsert\UpsertTruth;
16use ZtdQuery\Shadow\Mutation\UpsertColumnSource;
17use ZtdQuery\Shadow\Mutation\UpsertExpression;
18use ZtdQuery\Shadow\Mutation\UpsertExpressionKind;
19use ZtdQuery\Shadow\Mutation\UpsertMutationRow;
20use ZtdQuery\Shadow\Mutation\UpsertUpdate;
21
22#[CoversClass(UpsertUpdate::class)]
23#[UsesClass(UpsertMutationRow::class)]
24#[UsesClass(UpsertExpression::class)]
25#[UsesClass(UpsertColumn::class)]
26#[UsesClass(UpsertOperator::class)]
27#[UsesClass(UpsertTruth::class)]
28#[UsesClass(UnsupportedSqlException::class)]
29#[UsesClass(UpsertComparison::class)]
30#[UsesClass(UpsertNumber::class)]
31#[UsesClass(\ZtdQuery\Shadow\Mutation\Upsert\UpsertLiteral::class)]
32final class UpsertUpdateTest extends TestCase
33{
34 public function testIncomingRowLeavesARowAloneWhereTheDatabaseWorkedNothingOut(): void
35 {
36 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], null, null, false);
37
38 self::assertSame(['id' => 1, 'a' => 2], $update->incomingRow(['id' => 1, 'a' => 2]));
39 }
40
41 public function testIncomingRowTakesOffWhatTheDatabaseWorkedOut(): void
42 {
43 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], null, null, true);
44 $codec = new UpsertMutationRow();
45
46 $row = ['id' => 1, $codec->valueColumn(0) => 9, $codec->predicateColumn() => 1];
47
48 self::assertSame(['id' => 1], $update->incomingRow($row));
49 }
50
51 public function testAppliesToEveryConflictWhereTheStatementWroteNoCondition(): void
52 {
53 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], null, null, false);
54
55 self::assertTrue($update->applies([], ['id' => 1], ['id' => 1], false));
56 }
57
58 public function testAppliesReadsTheConditionHereWhereTheDatabaseWorkedNothingOut(): void
59 {
60 $never = UpsertExpression::binary(
61 UpsertExpressionKind::Equal,
62 UpsertExpression::literal(1),
63 UpsertExpression::literal(2),
64 );
65 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], null, $never, false);
66
67 self::assertFalse($update->applies([], ['id' => 1], ['id' => 1], false));
68 }
69
70 public function testAppliesTakesTheVerdictTheDatabaseWorkedOut(): void
71 {
72 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], 'a > 0', null, true);
73 $codec = new UpsertMutationRow();
74
75 self::assertSame(
76 [true, false],
77 [
78 $update->applies([$codec->predicateColumn() => 1], ['id' => 1], ['id' => 1], false),
79 $update->applies([$codec->predicateColumn() => 0], ['id' => 1], ['id' => 1], false),
80 ],
81 );
82 }
83
84 public function testAppliesRefusesAConditionTheDatabaseCouldNotWorkOutForARowAlreadyChanged(): void
85 {
86 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], 'a > 0', null, true);
87
88 $this->expectException(UnsupportedSqlException::class);
89
90 $update->applies([], ['id' => 1], ['id' => 1], true);
91 }
92
93 public function testAppliesRefusesAConditionTheDatabaseDidNotWorkOutAtAll(): void
94 {
95 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], 'a > 0', null, true);
96
97 $this->expectException(UnsupportedSqlException::class);
98
99 $update->applies([], ['id' => 1], ['id' => 1], false);
100 }
101
102 public function testOfCarriesEverythingButTheConflictColumnsWhereNoColumnIsNamed(): void
103 {
104 $update = new UpsertUpdate('t', ['id'], [], [], [], null, null, false);
105
106 self::assertSame(
107 ['id' => 1, 'a' => 2],
108 $update->of([], ['id' => 1, 'a' => 0], ['id' => 9, 'a' => 2], false),
109 );
110 }
111
112 public function testOfAssignsEachColumnTheStatementNames(): void
113 {
114 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], null, null, false);
115
116 self::assertSame(['id' => 1, 'a' => 2], $update->of([], ['id' => 1, 'a' => 0], ['id' => 1, 'a' => 2], false));
117 }
118
119 public function testWithColumnReadsTheExpressionAgainstTheRowAsTheStatementLeftIt(): void
120 {
121 $doubled = UpsertExpression::binary(
122 UpsertExpressionKind::Add,
123 UpsertExpression::column(UpsertColumnSource::Existing, 'a'),
124 UpsertExpression::literal(1),
125 );
126 $update = new UpsertUpdate('t', ['id'], ['a'], ['a' => $doubled], [], null, null, false);
127
128 self::assertSame(['a' => 3], $update->withColumn(['a' => 2], 'a', 0, [], [], false));
129 }
130
131 public function testWithColumnTakesWhatTheDatabaseWorkedOut(): void
132 {
133 $update = new UpsertUpdate('t', ['id'], ['a'], [], ['a' => 'a + 1'], null, null, true);
134 $codec = new UpsertMutationRow();
135
136 self::assertSame(
137 ['a' => 7],
138 $update->withColumn(['a' => 2], 'a', 0, [$codec->valueColumn(0) => 7], [], false),
139 );
140 }
141
142 public function testWithColumnRefusesAnAssignmentTheDatabaseCouldNotWorkOut(): void
143 {
144 $update = new UpsertUpdate('t', ['id'], ['a'], [], ['a' => 'a + 1'], null, null, true);
145
146 $this->expectException(UnsupportedSqlException::class);
147
148 $update->withColumn(['a' => 2], 'a', 0, [], [], true);
149 }
150
151 public function testPredicateReadRefusesAConditionZtdCouldNotRead(): void
152 {
153 $update = new UpsertUpdate('t', ['id'], ['a'], [], [], 'a > 0', null, true);
154
155 $this->expectException(UnsupportedSqlException::class);
156 $this->expectExceptionMessage('why');
157
158 $update->predicateRead('why');
159 }
160
161 public function testValueReadAnswersTheAssignmentZtdRead(): void
162 {
163 $one = UpsertExpression::literal(1);
164 $update = new UpsertUpdate('t', ['id'], ['a'], ['a' => $one], [], null, null, true);
165
166 self::assertSame($one, $update->valueRead('a', 'why'));
167 }
168}
169