Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReplaceQueryBuilder
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 11
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 connection
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 queryInfo
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 table
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 replaceInto
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rows
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 row
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 uniqueIndexFields
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 caller
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6use UnexpectedValueException;
7
8/**
9 * Build REPLACE queries with a fluent interface.
10 *
11 * Each query builder object must be used for a single database query only,
12 * and not be reused afterwards. To run multiple similar queries, you can
13 * create a query builder to set up most of your query, which you can use
14 * as a "template" to clone. You can then modify the cloned object for
15 * each individual query.
16 *
17 * @since 1.41
18 * @stable to extend
19 * @ingroup Database
20 */
21class ReplaceQueryBuilder implements IWriteQueryBuilder {
22    /**
23     * @var string The table name to be passed to IDatabase::replace()
24     */
25    private $table = '';
26
27    /**
28     * @var list<array> The rows to be passed to IDatabase::replace()
29     */
30    private $rows = [];
31
32    /**
33     * @var string The caller (function name) to be passed to IDatabase::replace()
34     */
35    private $caller = __CLASS__;
36
37    /**
38     * @var string[] The unique keys to be passed to IDatabase::replace()
39     */
40    private $uniqueIndexFields = [];
41
42    protected IDatabase $db;
43
44    /**
45     * Only for use in subclasses. To create a ReplaceQueryBuilder instance,
46     * use `$db->newReplaceQueryBuilder()` instead.
47     */
48    public function __construct( IDatabase $db ) {
49        $this->db = $db;
50    }
51
52    /**
53     * @inheritDoc
54     */
55    public function connection( IDatabase $db ) {
56        if ( $this->db->getType() !== $db->getType() ) {
57            throw new InvalidArgumentException(
58                __METHOD__ . ' cannot switch to a database of a different type.'
59            );
60        }
61        $this->db = $db;
62        return $this;
63    }
64
65    /**
66     * @inheritDoc
67     */
68    public function queryInfo( $info ) {
69        if ( isset( $info['table'] ) ) {
70            $this->table( $info['table'] );
71        }
72        if ( isset( $info['rows'] ) ) {
73            $this->rows( $info['rows'] );
74        }
75        if ( isset( $info['uniqueIndexFields'] ) ) {
76            $this->uniqueIndexFields( (array)$info['uniqueIndexFields'] );
77        }
78        if ( isset( $info['caller'] ) ) {
79            $this->caller( $info['caller'] );
80        }
81        return $this;
82    }
83
84    /**
85     * Manually set the table name to be passed to IDatabase::replace()
86     *
87     * @param string $table The unqualified name of a table
88     * @param-taint $table exec_sql
89     * @return $this
90     */
91    public function table( $table ) {
92        $this->table = $table;
93        return $this;
94    }
95
96    /**
97     * Set table for the query. Alias for table().
98     *
99     * @param string $table The unqualified name of a table
100     * @param-taint $table exec_sql
101     * @return $this
102     */
103    public function replaceInto( string $table ) {
104        return $this->table( $table );
105    }
106
107    /**
108     * Add rows to be inserted.
109     *
110     * @param list<array> $rows
111     *   $rows should be an integer-keyed list of such string-keyed maps, defining a list of new rows.
112     *   The keys in each map must be identical to each other and in the same order.
113     *   The rows must not collide with each other.
114     *
115     * @return $this
116     */
117    public function rows( array $rows ) {
118        $this->rows = array_merge( $this->rows, $rows );
119        return $this;
120    }
121
122    /**
123     * Add one row to be inserted.
124     *
125     * @param array $row
126     *   $row must be a string-keyed map of (column name => value) defining a new row. Values are
127     *   treated as literals and quoted appropriately; null is interpreted as NULL.
128     *
129     * @return $this
130     */
131    public function row( array $row ) {
132        $this->rows[] = $row;
133        return $this;
134    }
135
136    /**
137     * Set the unique index fields
138     *
139     * @param string|string[] $uniqueIndexFields
140     * @return $this
141     */
142    public function uniqueIndexFields( $uniqueIndexFields ) {
143        if ( is_string( $uniqueIndexFields ) ) {
144            $uniqueIndexFields = [ $uniqueIndexFields ];
145        }
146        $this->uniqueIndexFields = $uniqueIndexFields;
147        return $this;
148    }
149
150    /**
151     * @inheritDoc
152     */
153    public function caller( $fname ) {
154        $this->caller = $fname;
155        return $this;
156    }
157
158    /**
159     * @inheritDoc
160     */
161    public function execute(): void {
162        if ( !$this->rows ) {
163            throw new UnexpectedValueException(
164                __METHOD__ . ' can\'t have empty $rows value' );
165        }
166        if ( $this->table === '' ) {
167            throw new UnexpectedValueException(
168                __METHOD__ . ' expects table not to be empty' );
169        }
170        if ( !$this->uniqueIndexFields ) {
171            throw new UnexpectedValueException(
172                __METHOD__ . ' expects unique key to be provided' );
173        }
174        $this->db->replace( $this->table, [ $this->uniqueIndexFields ], $this->rows, $this->caller );
175    }
176
177    /**
178     * @inheritDoc
179     */
180    public function getQueryInfo() {
181        $info = [
182            'table' => $this->table,
183            'rows' => $this->rows,
184            'uniqueIndexFields' => $this->uniqueIndexFields,
185        ];
186        if ( $this->caller !== __CLASS__ ) {
187            $info['caller'] = $this->caller;
188        }
189        return $info;
190    }
191}