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 implements IWriteQueryBuilder {
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    public function __construct( IDatabase $db ) {
43        $this->db = $db;
44    }
45
46    /**
47     * @inheritDoc
48     */
49    public function connection( IDatabase $db ): DeleteQueryBuilder {
50        if ( $this->db->getType() !== $db->getType() ) {
51            throw new InvalidArgumentException(
52                __METHOD__ . ' cannot switch to a database of a different type.'
53            );
54        }
55        $this->db = $db;
56        return $this;
57    }
58
59    /**
60     * @inheritDoc
61     */
62    public function queryInfo( $info ): DeleteQueryBuilder {
63        if ( isset( $info['table'] ) ) {
64            $this->table( $info['table'] );
65        }
66        if ( isset( $info['conds'] ) ) {
67            $this->where( $info['conds'] );
68        }
69        if ( isset( $info['caller'] ) ) {
70            $this->caller( $info['caller'] );
71        }
72        return $this;
73    }
74
75    /**
76     * Manually set the table name to be passed to IDatabase::delete()
77     *
78     * @param string $table The unqualified name of a table
79     * @param-taint $table exec_sql
80     * @return $this
81     */
82    public function table( string $table ): DeleteQueryBuilder {
83        $this->table = $table;
84        return $this;
85    }
86
87    /**
88     * Set table for the query. Alias for table().
89     *
90     * @param string $table The unqualified name of a table
91     * @param-taint $table exec_sql
92     * @return $this
93     */
94    public function deleteFrom( string $table ): DeleteQueryBuilder {
95        return $this->table( $table );
96    }
97
98    /**
99     * Set table for the query. Alias for table().
100     *
101     * @param string $table The unqualified name of a table
102     * @param-taint $table exec_sql
103     * @return $this
104     */
105    public function delete( string $table ): DeleteQueryBuilder {
106        return $this->table( $table );
107    }
108
109    /**
110     * Add conditions to the query. The supplied conditions will be appended
111     * to the existing conditions, separated by AND.
112     *
113     * @phpcs:ignore Generic.Files.LineLength
114     * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar|Blob>|RawSQLValue|Blob>|array<int,string|IExpression> $conds
115     *
116     * May be either a string containing a single condition, or an array of
117     * conditions. If an array is given, the conditions constructed from each
118     * element are combined with AND.
119     *
120     * Array elements may take one of two forms:
121     *
122     * - Elements with a numeric key are interpreted as raw SQL fragments.
123     * - Elements with a string key are interpreted as equality conditions,
124     *   where the key is the field name.
125     *   - If the value of such an array element is a scalar (such as a
126     *     string), it will be treated as data and thus quoted appropriately.
127     *     If it is null, an IS NULL clause will be added.
128     *   - If the value is an array, an IN (...) clause will be constructed
129     *     from its non-null elements, and an IS NULL clause will be added
130     *     if null is present, such that the field may match any of the
131     *     elements in the array. The non-null elements will be quoted.
132     *
133     * Note that expressions are often DBMS-dependent in their syntax.
134     * DBMS-independent wrappers are provided for constructing several types of
135     * expression commonly used in condition queries. See:
136     * - {@link ISQLPlatform::buildLike()}
137     * - {@link ISQLPlatform::conditional()}
138     *
139     * Untrusted user input is safe in the values of string keys, however untrusted
140     * input must not be used in the array key names or in the values of numeric keys.
141     * Escaping of untrusted input used in values of numeric keys should be done via
142     * {@link IDatabase::addQuotes()}.
143     *
144     * @param-taint $conds exec_sql_numkey
145     * @return $this
146     */
147    public function where( $conds ): DeleteQueryBuilder {
148        if ( is_array( $conds ) ) {
149            foreach ( $conds as $key => $cond ) {
150                if ( is_int( $key ) ) {
151                    $this->conds[] = $cond;
152                } elseif ( isset( $this->conds[$key] ) ) {
153                    // @phan-suppress-previous-line PhanTypeMismatchDimFetch
154                    // T288882
155                    $this->conds[] = $this->db->makeList(
156                        [ $key => $cond ], IDatabase::LIST_AND );
157                } else {
158                    $this->conds[$key] = $cond;
159                }
160            }
161        } else {
162            $this->conds[] = $conds;
163        }
164        return $this;
165    }
166
167    /**
168     * Add conditions to the query. Alias for where().
169     *
170     * @phpcs:ignore Generic.Files.LineLength
171     * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar|Blob>|RawSQLValue|Blob>|array<int,string|IExpression> $conds
172     * @param-taint $conds exec_sql_numkey
173     * @return $this
174     */
175    public function andWhere( $conds ): DeleteQueryBuilder {
176        return $this->where( $conds );
177    }
178
179    /**
180     * Add conditions to the query. Alias for where().
181     *
182     * @phpcs:ignore Generic.Files.LineLength
183     * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar|Blob>|RawSQLValue|Blob>|array<int,string|IExpression> $conds
184     * @param-taint $conds exec_sql_numkey
185     * @return $this
186     */
187    public function conds( $conds ): DeleteQueryBuilder {
188        return $this->where( $conds );
189    }
190
191    /**
192     * @inheritDoc
193     */
194    public function caller( $fname ): DeleteQueryBuilder {
195        $this->caller = $fname;
196        return $this;
197    }
198
199    /**
200     * @inheritDoc
201     */
202    public function execute(): void {
203        if ( !$this->conds ) {
204            throw new UnexpectedValueException(
205                __METHOD__ . ' expects at least one condition to be set' );
206        }
207        if ( $this->table === '' ) {
208            throw new UnexpectedValueException(
209                __METHOD__ . ' expects table not to be empty' );
210        }
211        $this->db->delete( $this->table, $this->conds, $this->caller );
212    }
213
214    /**
215     * @inheritDoc
216     */
217    public function getQueryInfo(): array {
218        $info = [
219            'table' => $this->table,
220            'conds' => $this->conds,
221        ];
222        if ( $this->caller !== __CLASS__ ) {
223            $info['caller'] = $this->caller;
224        }
225        return $info;
226    }
227}