packages/ztd-query-core/tests/Fake/FakeCopySupport.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Fake;
6
7use ZtdQuery\Platform\CopySupport;
8use ZtdQuery\Platform\CopyTarget;
9use ZtdQuery\Schema\TableDefinition;
10
11/**
12 * A COPY implementation writing the plainest form of every part of it.
13 *
14 * COPY is PostgreSQL's, and how a relation, a column list and a row of values
15 * are written is its business. What every implementation has to do is the
16 * same: say which table is meant, which columns, how a row is written out and
17 * how one is read back. That is what this shows.
18 */
19final class FakeCopySupport implements CopySupport
20{
21 /**
22 * Answers the table a relation names, without its schema.
23 *
24 * @param string $relation Relation as the statement named it
25 *
26 * @return string The table name
27 */
28 public function tableName(string $relation): string
29 {
30 $parts = explode('.', trim($relation, '"'));
31
32 return $parts[count($parts) - 1];
33 }
34
35 /**
36 * Answers what a COPY statement is written against.
37 *
38 * @param string $relation Relation as the statement named it
39 * @param string|null $fields Column list as the statement wrote it, or null for every column
40 * @param TableDefinition $definition What the table declares
41 *
42 * @return CopyTarget The relation and the columns
43 */
44 public function target(string $relation, ?string $fields, TableDefinition $definition): CopyTarget
45 {
46 $columns = $fields === null
47 ? $definition->columns
48 : array_map(trim(...), explode(',', $fields));
49
50 return new CopyTarget(
51 explode('.', trim($relation, '"')),
52 $columns === [] ? ['*'] : $columns,
53 );
54 }
55
56 /**
57 * Writes the SELECT that reads the rows a COPY OUT would have written.
58 *
59 * @param CopyTarget $target What the statement is written against
60 *
61 * @return string The statement
62 */
63 public function selectSql(CopyTarget $target): string
64 {
65 return 'SELECT ' . implode(', ', $target->columns) . ' FROM ' . $target->tableName();
66 }
67
68 /**
69 * Writes the INSERT that would have written the rows a COPY IN carries.
70 *
71 * @param CopyTarget $target What the statement is written against
72 * @param int $rowCount How many rows are being written
73 * @param bool $overrideSystemValue Whether a column the database numbers is being written anyway
74 *
75 * @return string The statement
76 */
77 public function insertSql(CopyTarget $target, int $rowCount, bool $overrideSystemValue): string
78 {
79 $placeholders = '(' . implode(', ', array_fill(0, count($target->columns), '?')) . ')';
80
81 return 'INSERT INTO ' . $target->tableName()
82 . ' (' . implode(', ', $target->columns) . ')'
83 . ($overrideSystemValue ? ' OVERRIDING SYSTEM VALUE' : '')
84 . ' VALUES ' . implode(', ', array_fill(0, max($rowCount, 1), $placeholders));
85 }
86
87 /**
88 * Writes one row the way COPY carries it.
89 *
90 * @param list<mixed> $values Values of the row, in column order
91 * @param string $separator What goes between two values
92 * @param string $nullAs What a null is written as
93 *
94 * @return string The row
95 */
96 public function encodeRow(array $values, string $separator, string $nullAs): string
97 {
98 $written = [];
99 foreach ($values as $value) {
100 $written[] = $value === null ? $nullAs : (is_scalar($value) ? (string) $value : $nullAs);
101 }
102
103 return implode($separator, $written);
104 }
105
106 /**
107 * Reads one row the way COPY carries it.
108 *
109 * @param string $row Row as it was written
110 * @param string $separator What goes between two values
111 * @param string $nullAs What a null is written as
112 *
113 * @return list<string|null> The values, in column order
114 */
115 public function decodeRow(string $row, string $separator, string $nullAs): array
116 {
117 $values = [];
118 foreach (explode($separator === '' ? "\t" : $separator, $row) as $written) {
119 $values[] = $written === $nullAs ? null : $written;
120 }
121
122 return $values;
123 }
124
125 /**
126 * Reports whether a statement is a COPY at all.
127 *
128 * @param string $sql Statement as it was written
129 *
130 * @return bool True when it is
131 */
132 public function isCopyStatement(string $sql): bool
133 {
134 return preg_match('/^\s*COPY\b/i', $sql) === 1;
135 }
136}
137