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