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 {
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     * Change the IDatabase object the query builder is bound to. The specified
54     * IDatabase will subsequently be used to execute the query.
55     *
56     * @param IDatabase $db
57     * @return $this
58     */
59    public function connection( IDatabase $db ) {
60        if ( $this->db->getType() !== $db->getType() ) {
61            throw new InvalidArgumentException(
62                __METHOD__ . ' cannot switch to a database of a different type.'
63            );
64        }
65        $this->db = $db;
66        return $this;
67    }
68
69    /**
70     * Set the query parameters to the given values, appending to the values
71     * which were already set. This can be used to interface with legacy code.
72     * If a key is omitted, the previous value will be retained.
73     *
74     * The parameters must be formatted as required by Database::replace.
75     *
76     * @param array $info Associative array of query info, with keys:
77     *   - table: The table name to be passed to Database::replace()
78     *   - rows: The rows to be inserted
79     *   - options: The query options
80     *   - caller: The caller signature
81     *
82     * @return $this
83     */
84    public function queryInfo( $info ) {
85        if ( isset( $info['table'] ) ) {
86            $this->table( $info['table'] );
87        }
88        if ( isset( $info['rows'] ) ) {
89            $this->rows( $info['rows'] );
90        }
91        if ( isset( $info['uniqueIndexFields'] ) ) {
92            $this->uniqueIndexFields( (array)$info['uniqueIndexFields'] );
93        }
94        if ( isset( $info['caller'] ) ) {
95            $this->caller( $info['caller'] );
96        }
97        return $this;
98    }
99
100    /**
101     * Manually set the table name to be passed to IDatabase::replace()
102     *
103     * @param string $table The unqualified name of a table
104     * @param-taint $table exec_sql
105     * @return $this
106     */
107    public function table( $table ) {
108        $this->table = $table;
109        return $this;
110    }
111
112    /**
113     * Set table for the query. Alias for table().
114     *
115     * @param string $table The unqualified name of a table
116     * @param-taint $table exec_sql
117     * @return $this
118     */
119    public function replaceInto( string $table ) {
120        return $this->table( $table );
121    }
122
123    /**
124     * Add rows to be inserted.
125     *
126     * @param list<array> $rows
127     *   $rows should be an integer-keyed list of such string-keyed maps, defining a list of new rows.
128     *   The keys in each map must be identical to each other and in the same order.
129     *   The rows must not collide with each other.
130     *
131     * @return $this
132     */
133    public function rows( array $rows ) {
134        $this->rows = array_merge( $this->rows, $rows );
135        return $this;
136    }
137
138    /**
139     * Add one row to be inserted.
140     *
141     * @param array $row
142     *   $row must be a string-keyed map of (column name => value) defining a new row. Values are
143     *   treated as literals and quoted appropriately; null is interpreted as NULL.
144     *
145     * @return $this
146     */
147    public function row( array $row ) {
148        $this->rows[] = $row;
149        return $this;
150    }
151
152    /**
153     * Set the unique index fields
154     *
155     * @param string|string[] $uniqueIndexFields
156     * @return $this
157     */
158    public function uniqueIndexFields( $uniqueIndexFields ) {
159        if ( is_string( $uniqueIndexFields ) ) {
160            $uniqueIndexFields = [ $uniqueIndexFields ];
161        }
162        $this->uniqueIndexFields = $uniqueIndexFields;
163        return $this;
164    }
165
166    /**
167     * Set the method name to be included in an SQL comment.
168     *
169     * @param string $fname
170     * @param-taint $fname exec_sql
171     * @return $this
172     */
173    public function caller( $fname ) {
174        $this->caller = $fname;
175        return $this;
176    }
177
178    /**
179     * Run the constructed REPLACE query and return the result.
180     */
181    public function execute() {
182        if ( !$this->rows ) {
183            throw new UnexpectedValueException(
184                __METHOD__ . ' can\'t have empty $rows value' );
185        }
186        if ( $this->table === '' ) {
187            throw new UnexpectedValueException(
188                __METHOD__ . ' expects table not to be empty' );
189        }
190        if ( !$this->uniqueIndexFields ) {
191            throw new UnexpectedValueException(
192                __METHOD__ . ' expects unique key to be provided' );
193        }
194        $this->db->replace( $this->table, [ $this->uniqueIndexFields ], $this->rows, $this->caller );
195    }
196
197    /**
198     * Get an associative array describing the query in terms of its raw parameters to
199     * Database::replace(). This can be used to interface with legacy code.
200     *
201     * @return array The query info array, with keys:
202     *   - table: The table name
203     *   - rows: The rows array
204     *   - options: The query options
205     *   - caller: The caller signature
206     */
207    public function getQueryInfo() {
208        $info = [
209            'table' => $this->table,
210            'rows' => $this->rows,
211            'uniqueIndexFields' => $this->uniqueIndexFields,
212        ];
213        if ( $this->caller !== __CLASS__ ) {
214            $info['caller'] = $this->caller;
215        }
216        return $info;
217    }
218}