Skip to content

Commit 4f2fa63

Browse files
authored
Merge pull request #61 from seapagan/add_delete_method_to_query
2 parents b82b33c + bad8fe3 commit 4f2fa63

File tree

8 files changed

+442
-14
lines changed

8 files changed

+442
-14
lines changed

.github/workflows/testing.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
python-version: ["3.9", "3.10", "3.11", "3.12"]
16+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1717

1818
steps:
1919
- uses: actions/checkout@v4
@@ -29,8 +29,7 @@ jobs:
2929

3030
- name: Run tests
3131
# For example, using `pytest`
32-
run:
33-
uv run --all-extras -p ${{ matrix.python-version }} pytest tests
32+
run: uv run --all-extras -p ${{ matrix.python-version }} pytest tests
3433
--cov-report=xml
3534

3635
- name: Run codacy-coverage-reporter

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,11 @@ for user in results:
131131
new_user.age = 31
132132
db.update(new_user)
133133

134-
# Delete a record
134+
# Delete a record by primary key
135135
db.delete(User, new_user.pk)
136+
137+
# Delete all records returned from a query:
138+
delete_count = db.select(User).filter(age__gt=30).delete()
136139
```
137140

138141
See the [Usage](https://sqliter.grantramsay.dev/usage) section of the documentation

TODO.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ Items marked with :fire: are high priority.
77
- add an 'execute' method to the main class to allow executing arbitrary SQL
88
queries which can be chained to the 'find_first' etc methods or just used
99
directly.
10-
- add a `delete` method to the QueryBuilder class to allow deleting
11-
single/multiple records from the database based on the query. This is in
12-
addition to the `delete` method in the main class which deletes a single
13-
record based on the primary key.
1410
- add a `rollback` method to the main class to allow manual rollbacks.
1511
- :fire: allow adding foreign keys and relationships to each table.
1612
- add a migration system to allow updating the database schema without losing

docs/guide/data-operations.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,48 @@ timestamp in UTC by default.
111111
112112
## Deleting Records
113113

114-
To delete a record from the database, you need to pass the model class and the
115-
primary key value of the record you want to delete:
114+
SQLiter provides two ways to delete records:
115+
116+
### Single Record Deletion
117+
118+
To delete a single record from the database by its primary key, use the `delete()` method directly on the database instance:
116119

117120
```python
118121
db.delete(User, user.pk)
119122
```
120123

124+
> [!IMPORTANT]
125+
>
126+
> The single record deletion method will raise:
127+
>
128+
> - `RecordNotFoundError` if the record with the specified primary key is not found
129+
> - `RecordDeletionError` if there's an error during the deletion process
130+
131+
### Query-Based Deletion
132+
133+
You can also use a query to delete records that match specific criteria. The `delete()` method will delete all records returned by the query and return an integer with the count of records deleted:
134+
135+
```python
136+
# Delete all users over 30
137+
deleted_count = db.select(User).filter(age__gt=30).delete()
138+
139+
# Delete inactive users in a specific age range
140+
deleted_count = db.select(User).filter(
141+
age__gte=25,
142+
age__lt=40,
143+
status="inactive"
144+
).delete()
145+
146+
# Delete all records from a table
147+
deleted_count = db.select(User).delete()
148+
```
149+
150+
> [!NOTE]
151+
>
152+
> The query-based delete operation ignores any `limit()`, `offset()`, or `order()`
153+
> clauses that might be in the query chain. It will always delete ALL records
154+
> that match the filter conditions.
155+
121156
## Commit your changes
122157

123158
By default, SQLiter will automatically commit changes to the database after each

docs/guide/guide.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# SQLiter Overview
32

43
SQLiter is a lightweight Python library designed to simplify database operations
@@ -122,13 +121,22 @@ db.update(user)
122121

123122
## Deleting Records
124123

125-
Deleting records is simple as well. You just need to pass the Model that defines
126-
your table and the primary key value of the record you want to delete:
124+
SQLiter provides two ways to delete records:
125+
126+
### Single Record Deletion
127+
128+
To delete a single record by its primary key:
127129

128130
```python
129131
db.delete(User, 1)
130132
```
131133

134+
> [!IMPORTANT]
135+
>
136+
> The single record deletion method will raise:
137+
> - `RecordNotFoundError` if the record with the specified primary key is not found
138+
> - `RecordDeletionError` if there's an error during the deletion process
139+
132140
> [!NOTE]
133141
>
134142
> You can get the primary key value from the record or model instance itself,
@@ -139,6 +147,28 @@ db.delete(User, 1)
139147
> db.delete(User, new_record.pk)
140148
> ```
141149
150+
### Query-Based Deletion
151+
152+
You can also delete multiple records that match specific criteria using a query. The `delete()` method will delete all records that match the query and return the number of records deleted:
153+
154+
```python
155+
# Delete all users over 30
156+
deleted_count = db.select(User).filter(age__gt=30).delete()
157+
158+
# Delete inactive users in a specific age range
159+
deleted_count = db.select(User).filter(
160+
age__gte=25,
161+
age__lt=40,
162+
status="inactive"
163+
).delete()
164+
```
165+
166+
> [!NOTE]
167+
>
168+
> The query-based delete operation ignores any `limit()`, `offset()`, or `order()`
169+
> clauses that might be in the query chain. It will always delete ALL records
170+
> that match the filter conditions.
171+
142172
## Advanced Query Features
143173

144174
### Ordering

docs/quickstart.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ results = db.select(User).filter(name="John Doe").fetch_one()
3737

3838
print("Updated age:", results.age)
3939

40-
# Delete a record
40+
# Delete a record by primary key
4141
db.delete(User, new_user.pk)
42+
43+
# Delete all records returned from a query:
44+
delete_count = db.select(User).filter(age__gt=30).delete()
4245
```
4346

4447
See the [Guide](guide/guide.md) for more detailed information on how to use `SQLiter`.

sqliter/query/query.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
InvalidFilterError,
2929
InvalidOffsetError,
3030
InvalidOrderError,
31+
RecordDeletionError,
3132
RecordFetchError,
3233
)
3334

@@ -728,3 +729,34 @@ def exists(self) -> bool:
728729
True if at least one result exists, False otherwise.
729730
"""
730731
return self.count() > 0
732+
733+
def delete(self) -> int:
734+
"""Delete records that match the current query conditions.
735+
736+
Returns:
737+
The number of records deleted.
738+
739+
Raises:
740+
RecordDeletionError: If there's an error deleting the records.
741+
"""
742+
sql = f'DELETE FROM "{self.table_name}"' # noqa: S608 # nosec
743+
744+
# Build the WHERE clause with special handling for None (NULL in SQL)
745+
values, where_clause = self._parse_filter()
746+
747+
if self.filters:
748+
sql += f" WHERE {where_clause}"
749+
750+
# Print the raw SQL and values if debug is enabled
751+
if self.db.debug:
752+
self.db._log_sql(sql, values) # noqa: SLF001
753+
754+
try:
755+
with self.db.connect() as conn:
756+
cursor = conn.cursor()
757+
cursor.execute(sql, values)
758+
deleted_count = cursor.rowcount
759+
self.db._maybe_commit() # noqa: SLF001
760+
return deleted_count
761+
except sqlite3.Error as exc:
762+
raise RecordDeletionError(self.table_name) from exc

0 commit comments

Comments
 (0)