Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.74% covered (warning)
71.74%
33 / 46
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteQueryBuilder
71.74% covered (warning)
71.74%
33 / 46
58.33% covered (warning)
58.33%
7 / 12
34.94
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 connection
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 queryInfo
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 table
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteFrom
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 where
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
 andWhere
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 conds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 caller
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
4.68
 getQueryInfo
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6use UnexpectedValueException;
7
8/**
9 * A query builder for DELETE queries with a fluent interface.
10 *
11 * Any particular query builder object should only be used for a single database query,
12 * and not be reused afterwards. However, to run multiple similar queries,
13 * you can create a “template” query builder to set up most of the query,
14 * and then clone the object (and potentially modify the clone) for each individual query.
15 *
16 * @stable to extend
17 * @since 1.41
18 * @ingroup Database
19 */
20class DeleteQueryBuilder {
21    /**
22     * The table name to be passed to IDatabase::delete()
23     */
24    private string $table = '';
25
26    /**
27     * The conditions to be passed to IDatabase::delete()
28     */
29    private array $conds = [];
30
31    /**
32     * The caller (function name) to be passed to IDatabase::delete()
33     */
34    private string $caller = __CLASS__;
35
36    protected IDatabase $db;
37
38    /**
39     * Only for use in subclasses and Database::newDeleteQueryBuilder.
40     * To create a DeleteQueryBuilder instance, use `$db->newDeleteQueryBuilder()` instead.
41     *
42     * @param IDatabase $db
43     */
44    public function __construct( IDatabase $db ) {
45        $this->db = $db;
46    }
47
48    /**
49     * Change the IDatabase object the query builder is bound to. The specified
50     * IDatabase will subsequently be used to execute the query.
51     *
52     * @param IDatabase $db
53     * @return $this
54     */
55    public function connection( IDatabase $db ): DeleteQueryBuilder {
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     * Set the query parameters to the given values, appending to the values
67     * which were already set. This can be used to interface with legacy code.
68     * If a key is omitted, the previous value will be retained.
69     *
70     * The parameters must be formatted as required by Database::delete.
71     *
72     * @param array $info Associative array of query info, with keys:
73     *   - table: The table name to be passed to IDatabase::delete()
74     *   - conds: The conditions
75     *   - caller: The caller signature
76     *
77     * @return $this
78     */
79    public function queryInfo( array $info ): DeleteQueryBuilder {
80        if ( isset( $info['table'] ) ) {
81            $this->table( $info['table'] );
82        }
83        if ( isset( $info['conds'] ) ) {
84            $this->where( $info['conds'] );
85        }
86        if ( isset( $info['caller'] ) ) {
87            $this->caller( $info['caller'] );
88        }
89        return $this;
90    }
91
92    /**
93     * Manually set the table name to be passed to IDatabase::delete()
94     *
95     * @param string $table The table name
96     * @param-taint $table exec_sql
97     * @return $this
98     */
99    public function table( string $table ): DeleteQueryBuilder {
100        $this->table = $table;
101        return $this;
102    }
103
104    /**
105     * Set table for the query. Alias for table().
106     *
107     * @param string $table The table name
108     * @param-taint $table exec_sql
109     * @return $this
110     */
111    public function deleteFrom( string $table ): DeleteQueryBuilder {
112        return $this->table( $table );
113    }
114
115    /**
116     * Set table for the query. Alias for table().
117     *
118     * @param string $table The table name
119     * @param-taint $table exec_sql
120     * @return $this
121     */
122    public function delete( string $table ): DeleteQueryBuilder {
123        return $this->table( $table );
124    }
125
126    /**
127     * Add conditions to the query. The supplied conditions will be appended
128     * to the existing conditions, separated by AND.
129     *
130     * @param string|array|IExpression $conds
131     * @param-taint $conds exec_sql_numkey
132     *
133     * May be either a string containing a single condition, or an array of
134     * conditions. If an array is given, the conditions constructed from each
135     * element are combined with AND.
136     *
137     * Array elements may take one of two forms:
138     *
139     *   - Elements with a numeric key are interpreted as raw SQL fragments.
140     *   - Elements with a string key are interpreted as equality conditions,
141     *     where the key is the field name.
142     *     - If the value of such an array element is a scalar (such as a
143     *       string), it will be treated as data and thus quoted appropriately.
144     *       If it is null, an IS NULL clause will be added.
145     *     - If the value is an array, an IN (...) clause will be constructed
146     *       from its non-null elements, and an IS NULL clause will be added
147     *       if null is present, such that the field may match any of the
148     *       elements in the array. The non-null elements will be quoted.
149     *
150     * Note that expressions are often DBMS-dependent in their syntax.
151     * DBMS-independent wrappers are provided for constructing several types of
152     * expression commonly used in condition queries. See:
153     *    - IDatabase::buildLike()
154     *    - IDatabase::conditional()
155     *
156     * Untrusted user input is safe in the values of string keys, however untrusted
157     * input must not be used in the array key names or in the values of numeric keys.
158     * Escaping of untrusted input used in values of numeric keys should be done via
159     * IDatabase::addQuotes()
160     *
161     * @return $this
162     */
163    public function where( $conds ): DeleteQueryBuilder {
164        if ( is_array( $conds ) ) {
165            foreach ( $conds as $key => $cond ) {
166                if ( is_int( $key ) ) {
167                    $this->conds[] = $cond;
168                } elseif ( isset( $this->conds[$key] ) ) {
169                    // @phan-suppress-previous-line PhanTypeMismatchDimFetch
170                    // T288882
171                    $this->conds[] = $this->db->makeList(
172                        [ $key => $cond ], IDatabase::LIST_AND );
173                } else {
174                    $this->conds[$key] = $cond;
175                }
176            }
177        } else {
178            $this->conds[] = $conds;
179        }
180        return $this;
181    }
182
183    /**
184     * Add conditions to the query. Alias for where().
185     *
186     * @param string|array|IExpression $conds
187     * @param-taint $conds exec_sql_numkey
188     * @return $this
189     */
190    public function andWhere( $conds ): DeleteQueryBuilder {
191        return $this->where( $conds );
192    }
193
194    /**
195     * Add conditions to the query. Alias for where().
196     *
197     * @param string|array|IExpression $conds
198     * @param-taint $conds exec_sql_numkey
199     * @return $this
200     */
201    public function conds( $conds ): DeleteQueryBuilder {
202        return $this->where( $conds );
203    }
204
205    /**
206     * Set the method name to be included in an SQL comment.
207     *
208     * @param string $fname
209     * @param-taint $fname exec_sql
210     * @return $this
211     */
212    public function caller( string $fname ): DeleteQueryBuilder {
213        $this->caller = $fname;
214        return $this;
215    }
216
217    /**
218     * Run the constructed DELETE query.
219     */
220    public function execute(): void {
221        if ( !$this->conds ) {
222            throw new UnexpectedValueException(
223                __METHOD__ . ' expects at least one condition to be set' );
224        }
225        if ( $this->table === '' ) {
226            throw new UnexpectedValueException(
227                __METHOD__ . ' expects table not to be empty' );
228        }
229        $this->db->delete( $this->table, $this->conds, $this->caller );
230    }
231
232    /**
233     * Get an associative array describing the query in terms of its raw parameters to
234     * IDatabase::delete(). This can be used to interface with legacy code.
235     *
236     * @return array The query info array, with keys:
237     *   - table: The table name
238     *   - conds: The conditions
239     *   - caller: The caller signature
240     */
241    public function getQueryInfo(): array {
242        $info = [
243            'table' => $this->table,
244            'conds' => $this->conds,
245        ];
246        if ( $this->caller !== __CLASS__ ) {
247            $info['caller'] = $this->caller;
248        }
249        return $info;
250    }
251}