Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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