Skip to content

Commit f92d6ae

Browse files
authored
Refactor CRUD::delete method to support advanced DELETE queries (#25)
The current implementation relies on `wpdb::delete`, which supports only simple `where x = y` conditions. It's not only incomplete but also dangerous because `Where::$comparisonOperator` and `Where::$logicalOperator` are just ignored, so the result is unpredictable. The following change provides a way to use existing QueryBuilder functionality to generate DELETE SQL. The documentation describes some restrictions.
1 parent 043ab89 commit f92d6ae

File tree

5 files changed

+376
-13
lines changed

5 files changed

+376
-13
lines changed

.github/workflows/tests-php.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ jobs:
2828
# Prepare our composer cache directory
2929
# ------------------------------------------------------------------------------
3030
- name: Get Composer Cache Directory
31-
id: get-composer-cache-dir
32-
run: |
33-
echo "::set-output name=dir::$(composer config cache-files-dir)"
34-
- uses: actions/cache@v2
3531
id: composer-cache
32+
run: |
33+
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
34+
- uses: actions/cache@v4
3635
with:
37-
path: ${{ steps.get-composer-cache-dir.outputs.dir }}
36+
path: ${{ steps.composer-cache.outputs.dir }}
3837
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
3938
restore-keys: |
4039
${{ runner.os }}-composer-

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,12 +874,74 @@ DB::table('table_name')
874874

875875
The `QueryBuilder::delete` method may be used to delete records from the table.
876876

877+
Unlike WordPress's `$wpdb->delete()` method, this implementation generates and executes a DELETE SQL statement directly, which allows for advanced features like ORDER BY and LIMIT.
878+
879+
#### Basic delete with WHERE
880+
877881
```php
878882
DB::table('posts')
879883
->where('post_author', 1)
880884
->delete();
881885
```
882886

887+
#### Delete with LIMIT
888+
889+
Limit the number of rows to delete:
890+
891+
```php
892+
// Delete only the first 10 draft posts
893+
DB::table('posts')
894+
->where('post_status', 'draft')
895+
->limit(10)
896+
->delete();
897+
```
898+
899+
#### Delete with ORDER BY and LIMIT
900+
901+
Control which rows are deleted when using LIMIT:
902+
903+
```php
904+
// Delete the 100 oldest posts in trash
905+
DB::table('posts')
906+
->where('post_status', 'trash')
907+
->orderBy('post_date', 'ASC')
908+
->limit(100)
909+
->delete();
910+
```
911+
912+
#### Delete with LIKE patterns
913+
914+
Use pattern matching to delete rows:
915+
916+
```php
917+
// Delete all posts with titles starting with "Draft:"
918+
DB::table('posts')
919+
->whereLike('post_title', 'Draft:%')
920+
->delete();
921+
```
922+
923+
#### Delete with complex WHERE conditions
924+
925+
Combine multiple WHERE clauses for precise deletion:
926+
927+
```php
928+
// Delete auto-draft pages with IDs between 1 and 1000
929+
DB::table('posts')
930+
->where('post_type', 'page')
931+
->where('post_status', 'auto-draft')
932+
->whereBetween('ID', 1, 1000)
933+
->delete();
934+
935+
// Delete posts using whereIn
936+
DB::table('posts')
937+
->whereIn('ID', [5, 10, 15, 20])
938+
->delete();
939+
```
940+
941+
**Important restrictions:**
942+
- Table aliases in the FROM clause may not be supported on older database versions (MySQL < 8.0.24, MariaDB < 11.6). Avoid using table aliases when calling `delete()`.
943+
- JOINs are not supported in DELETE statements with this implementation.
944+
883945

884946
### Get
885947

src/DB/QueryBuilder/Concerns/CRUD.php

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,56 @@ public function upsert( $data, $match = [], $format = null ) {
7575
}
7676

7777
/**
78+
* Delete rows from the database.
79+
*
80+
* Unlike WordPress's $wpdb->delete() method, this implementation generates and executes
81+
* a DELETE SQL statement directly, which allows for advanced features like ORDER BY and LIMIT.
82+
*
83+
* Supports:
84+
* - WHERE clauses (including whereLike, whereIn, whereBetween, etc.)
85+
* - ORDER BY for controlling which rows are deleted first
86+
* - LIMIT to restrict the number of rows deleted
87+
* - Complex WHERE conditions (AND, OR, nested queries)
88+
*
89+
* Usage examples:
90+
* ```php
91+
* // Simple delete with WHERE
92+
* DB::table('posts')->where('post_status', 'draft')->delete();
93+
*
94+
* // Delete with LIMIT (delete only 10 rows)
95+
* DB::table('posts')->where('post_type', 'temp')->limit(10)->delete();
96+
*
97+
* // Delete oldest posts first using ORDER BY and LIMIT
98+
* DB::table('posts')
99+
* ->where('post_status', 'trash')
100+
* ->orderBy('post_date', 'ASC')
101+
* ->limit(100)
102+
* ->delete();
103+
*
104+
* // Delete with LIKE pattern
105+
* DB::table('posts')->whereLike('post_title', 'Draft:%')->delete();
106+
*
107+
* // Delete with multiple conditions
108+
* DB::table('posts')
109+
* ->where('post_type', 'page')
110+
* ->where('post_status', 'auto-draft')
111+
* ->whereBetween('ID', 1, 1000)
112+
* ->delete();
113+
* ```
114+
*
115+
* Restrictions:
116+
* - Table aliases in the FROM clause may not be supported on older database versions
117+
* (MySQL < 8.0.24, MariaDB < 11.6). Avoid using table aliases with delete().
118+
* - JOINs are not supported in DELETE statements with this implementation
119+
*
78120
* @since 1.0.0
79121
*
80-
* @return false|int
122+
* @return false|int Number of rows deleted, or false on error.
81123
*
82-
* @see https://developer.wordpress.org/reference/classes/wpdb/delete/
124+
* @see QueryBuilder::deleteSQL() for the SQL generation logic
83125
*/
84126
public function delete() {
85-
return DB::delete(
86-
$this->getTable(),
87-
$this->getWhere(),
88-
null
89-
);
127+
return DB::query( $this->deleteSQL() );
90128
}
91129

92130
/**

src/DB/QueryBuilder/QueryBuilder.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,38 @@ public function getSQL() {
5353
$this->getUnionSQL()
5454
);
5555

56-
// Trim double spaces added by DB::prepare
56+
return $this->buildSQL($sql);
57+
}
58+
59+
/**
60+
* Generate the SQL for a DELETE query. Only the FROM, WHERE, ORDER BY, and LIMIT clauses are included.
61+
* RETURNING is not supported.
62+
* Note that aliases are supported only on MySQL >= 8.0.24 and MariaDB >= 11.6.
63+
*
64+
* @see https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/changing-deleting-data/delete
65+
* @see https://dev.mysql.com/doc/refman/8.4/en/delete.html
66+
*
67+
* @return string DELETE query.
68+
*/
69+
public function deleteSQL() {
70+
$sql = array_merge(
71+
$this->getFromSQL(),
72+
$this->getWhereSQL(),
73+
$this->getOrderBySQL(),
74+
$this->getLimitSQL()
75+
);
76+
77+
return 'DELETE ' . $this->buildSQL($sql);
78+
}
79+
80+
/**
81+
* Build the SQL query from the given parts.
82+
*
83+
* @param array $sql The SQL query parts.
84+
*
85+
* @return string SQL query.
86+
*/
87+
private function buildSQL( $sql ) {
5788
return str_replace(
5889
[ ' ', ' ' ],
5990
' ',

0 commit comments

Comments
 (0)