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