Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/tests-php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ jobs:
# Prepare our composer cache directory
# ------------------------------------------------------------------------------
- name: Get Composer Cache Directory
id: get-composer-cache-dir
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v2
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
with:
path: ${{ steps.get-composer-cache-dir.outputs.dir }}
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -874,12 +874,74 @@ DB::table('table_name')

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

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.

#### Basic delete with WHERE

```php
DB::table('posts')
->where('post_author', 1)
->delete();
```

#### Delete with LIMIT

Limit the number of rows to delete:

```php
// Delete only the first 10 draft posts
DB::table('posts')
->where('post_status', 'draft')
->limit(10)
->delete();
```

#### Delete with ORDER BY and LIMIT

Control which rows are deleted when using LIMIT:

```php
// Delete the 100 oldest posts in trash
DB::table('posts')
->where('post_status', 'trash')
->orderBy('post_date', 'ASC')
->limit(100)
->delete();
```

#### Delete with LIKE patterns

Use pattern matching to delete rows:

```php
// Delete all posts with titles starting with "Draft:"
DB::table('posts')
->whereLike('post_title', 'Draft:%')
->delete();
```

#### Delete with complex WHERE conditions

Combine multiple WHERE clauses for precise deletion:

```php
// Delete auto-draft pages with IDs between 1 and 1000
DB::table('posts')
->where('post_type', 'page')
->where('post_status', 'auto-draft')
->whereBetween('ID', 1, 1000)
->delete();

// Delete posts using whereIn
DB::table('posts')
->whereIn('ID', [5, 10, 15, 20])
->delete();
```

**Important restrictions:**
- 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()`.
- JOINs are not supported in DELETE statements with this implementation.


### Get

Expand Down
52 changes: 45 additions & 7 deletions src/DB/QueryBuilder/Concerns/CRUD.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,56 @@ public function upsert( $data, $match = [], $format = null ) {
}

/**
* Delete rows from the database.
*
* 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.
*
* Supports:
* - WHERE clauses (including whereLike, whereIn, whereBetween, etc.)
* - ORDER BY for controlling which rows are deleted first
* - LIMIT to restrict the number of rows deleted
* - Complex WHERE conditions (AND, OR, nested queries)
*
* Usage examples:
* ```php
* // Simple delete with WHERE
* DB::table('posts')->where('post_status', 'draft')->delete();
*
* // Delete with LIMIT (delete only 10 rows)
* DB::table('posts')->where('post_type', 'temp')->limit(10)->delete();
*
* // Delete oldest posts first using ORDER BY and LIMIT
* DB::table('posts')
* ->where('post_status', 'trash')
* ->orderBy('post_date', 'ASC')
* ->limit(100)
* ->delete();
*
* // Delete with LIKE pattern
* DB::table('posts')->whereLike('post_title', 'Draft:%')->delete();
*
* // Delete with multiple conditions
* DB::table('posts')
* ->where('post_type', 'page')
* ->where('post_status', 'auto-draft')
* ->whereBetween('ID', 1, 1000)
* ->delete();
* ```
*
* Restrictions:
* - 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 with delete().
* - JOINs are not supported in DELETE statements with this implementation
*
* @since 1.0.0
*
* @return false|int
* @return false|int Number of rows deleted, or false on error.
*
* @see https://developer.wordpress.org/reference/classes/wpdb/delete/
* @see QueryBuilder::deleteSQL() for the SQL generation logic
*/
public function delete() {
return DB::delete(
$this->getTable(),
$this->getWhere(),
null
);
return DB::query( $this->deleteSQL() );
}

/**
Expand Down
33 changes: 32 additions & 1 deletion src/DB/QueryBuilder/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,38 @@ public function getSQL() {
$this->getUnionSQL()
);

// Trim double spaces added by DB::prepare
return $this->buildSQL($sql);
}

/**
* Generate the SQL for a DELETE query. Only the FROM, WHERE, ORDER BY, and LIMIT clauses are included.
* RETURNING is not supported.
* Note that aliases are supported only on MySQL >= 8.0.24 and MariaDB >= 11.6.
*
* @see https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/changing-deleting-data/delete
* @see https://dev.mysql.com/doc/refman/8.4/en/delete.html
*
* @return string DELETE query.
*/
public function deleteSQL() {
$sql = array_merge(
$this->getFromSQL(),
$this->getWhereSQL(),
$this->getOrderBySQL(),
$this->getLimitSQL()
);

return 'DELETE ' . $this->buildSQL($sql);
}

/**
* Build the SQL query from the given parts.
*
* @param array $sql The SQL query parts.
*
* @return string SQL query.
*/
private function buildSQL( $sql ) {
return str_replace(
[ ' ', ' ' ],
' ',
Expand Down
Loading