from typing import Any, Optional, Union
from ..expressions import ComplexCondition, Condition, Raw
from .base import Query
from .mixins import WhereClauseMixin
[docs]
class DeleteQuery(WhereClauseMixin, Query):
__slots__ = (
"_table",
"_wheres",
"_limit",
"_order_bys",
"_joins",
"_dialect",
"_allow_all_rows",
)
def __init__(self, table: str, dialect=None):
super().__init__(dialect)
self._table = table
self._wheres: list[tuple[str, str, Any]] = []
self._limit: Optional[int] = None
self._order_bys: list[str] = []
self._joins: list[tuple[str, str, Optional[str]]] = [] # (type, table, on)
self._allow_all_rows: bool = False
[docs]
def join(
self, table: str, on: Optional[str] = None, join_type: str = "INNER"
) -> "DeleteQuery":
"""Add a JOIN clause (MySQL multi-table DELETE)."""
self._joins.append((join_type, table, on))
return self
[docs]
def left_join(self, table: str, on: Optional[str] = None) -> "DeleteQuery":
"""Add a LEFT JOIN clause."""
return self.join(table, on, join_type="LEFT")
[docs]
def where(
self,
column: Union[str, Raw, Condition, ComplexCondition],
value: Any = None,
operator: str = "=",
) -> "DeleteQuery":
connector, sql, params = self._build_where_clause(column, value, operator)
self._wheres.append((connector, sql, params))
return self
[docs]
def limit(self, limit: int) -> "DeleteQuery":
self._limit = limit
return self
[docs]
def order_by(self, *columns: str) -> "DeleteQuery":
for col in columns:
direction = "ASC"
if col.startswith("-"):
direction = "DESC"
col = col[1:]
self._order_bys.append(f"{self._dialect.quote(col)} {direction}")
return self
[docs]
def allow_all_rows(self) -> "DeleteQuery":
"""Allow DELETE without WHERE clause (deletes all rows).
This is a safety feature to prevent accidental mass deletions.
You must call this method explicitly if you want to delete all rows.
Returns:
Self for method chaining
Example:
>>> Q.delete_from("temp_table").allow_all_rows().build()
"""
self._allow_all_rows = True
return self
[docs]
def build(self) -> tuple[str, tuple[Any, ...]]:
if not self._table:
raise ValueError("No table specified")
# Safety check: DELETE without WHERE
if not self._wheres and not self._allow_all_rows:
raise ValueError(
"DELETE without WHERE clause would affect all rows. "
"If this is intentional, call .allow_all_rows() first."
)
parts: list[str] = []
params: list[Any] = []
# DELETE FROM
parts.append("DELETE FROM ")
parts.append(self._dialect.quote(self._table))
# JOINs (for multi-table DELETE)
if self._joins:
for type_, table, on in self._joins:
parts.append(f" {type_} JOIN {table}")
if on:
parts.append(f" ON {on}")
# WHERE
if self._wheres:
parts.append(" WHERE ")
for i, (connector, sql, p) in enumerate(self._wheres):
if i > 0:
parts.append(f" {connector} ")
parts.append(sql)
params.extend(p)
# ORDER BY
if self._order_bys:
parts.append(" ORDER BY ")
parts.append(", ".join(self._order_bys))
# LIMIT
if self._limit:
parts.append(f" LIMIT {self._limit}")
return "".join(parts), tuple(params)