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