Queries¶
Ferro provides a fluent, type-safe API for constructing and executing database queries. All queries are constructed in Python and executed by the high-performance Rust engine.
Fetching Data¶
Queries are typically started using the select() or where() methods on a Model class.
Basic Filtering¶
Use standard Python comparison operators on Model fields to create filter conditions.
# Select all active users
users = await User.where(User.is_active == True).all()
# Select users with age >= 18
adults = await User.where(User.age >= 18).all()
Chaining¶
Methods can be chained to build complex queries incrementally.
results = await Product.select() \
.where(Product.category == "Electronics") \
.order_by(Product.price, "desc") \
.limit(10) \
.offset(5) \
.all()
Logical Operators¶
Use bitwise operators for complex logical conditions. Note that parentheses are required around each condition.
- AND:
& - OR:
|
# (age > 21 AND status == 'active') OR role == 'admin'
query = User.where(
((User.age > 21) & (User.status == "active")) | (User.role == "admin")
)
Terminal Operations¶
These methods execute the query and return a result.
| Method | Return Type | Description |
|---|---|---|
.all() |
list[Model] |
Executes the query and returns all matching records. |
.first() |
Model \| None |
Returns the first matching record or None. |
.count() |
int |
Returns the total number of matching records. |
.exists() |
bool |
Returns True if at least one matching record exists. |
Mutations¶
Ferro supports both instance-level and batch mutation operations.
Creating Records¶
# Single record
user = await User.create(username="alice", email="alice@example.com")
# Bulk creation (highly efficient)
users = [User(username=f"user_{i}") for i in range(100)]
await User.bulk_create(users)
Updating Records¶
Batch updates can be performed directly on a query without loading instances into memory.
# Update all products in a category
await Product.where(Product.category == "Old").update(status="archived")
Deleting Records¶
# Delete specific instance
await user.delete()
# Batch deletion
await User.where(User.is_active == False).delete()
SQL Injection Protection¶
All values passed to the fluent API (via .where(), .update(), etc.) are automatically parameterized by the Rust engine. Raw user input is never concatenated into SQL strings, ensuring built-in protection against SQL injection attacks.