Documentation
16. Errors & Limitations
Exception types, what triggers them, and known constraints you should be aware of.
Exception hierarchy
All RelPyDB exceptions inherit from RelPyError, so you can catch any library error with a single except RelPyError.
from relpy import (
RelPyError,
SchemaError,
TableNotFoundError,
ColumnNotFoundError,
ConstraintError,
QueryError,
QueryTypeError,
ViewError,
RowNotFoundError,
NoRowsFoundError,
MultipleRowsFoundError,
EncryptionError,
)
Error reference
| Exception | Triggered by |
|---|---|
TableNotFoundError | Querying or inserting into a table name that does not exist. |
ColumnNotFoundError | Referencing a column not defined in the schema. |
SchemaError | Calling add_column on a non-existent table, or defining an invalid schema. |
ConstraintError | Inserting a duplicate primary key, violating a NOT NULL rule, breaking a foreign key, or violating a unique index. |
QueryError | Using update() or delete() without allow_all=True when no where filter is given. |
QueryTypeError | Using Python and/or instead of &/| in a where condition. |
ViewError | Calling db.view() on a name that was never registered, or saving a database that has views. |
NoRowsFoundError | Calling .one() when the query returns zero rows. |
MultipleRowsFoundError | Calling .one() when the query returns more than one row. |
EncryptionError | Calling to_list(decrypt=True) without a key loaded, or loading a database encrypted with a different key. |
Common mistakes and fixes
Using Python and in a condition
❌ Wrong — raises QueryTypeError
db.query("users").where(
col("age") >= 18 and col("status") == "active"
)
✓ Correct
db.query("users").where(
(col("age") >= 18) &
(col("status") == "active")
)
Calling .one() on a query with multiple rows
❌ Wrong — raises MultipleRowsFoundError
user = db.query("users").one()
# Raises if there is more than one user
✓ Correct
user = (
db.query("users")
.where(col("id") == 1)
.one()
)
Deleting all rows without the safety flag
❌ Wrong — raises QueryError
db.delete("users")
✓ Correct
db.delete("users", allow_all=True)
Decrypting without loading a key
❌ Wrong — raises EncryptionError
db = RelPy.load("database.relpy.json")
db.to_list("users", decrypt=True)
# No key loaded!
✓ Correct
db = RelPy.load(
"database.relpy.json",
encryption_key=my_key,
)
db.to_list("users", decrypt=True)
Known limitations
- No concurrent writes. RelPyDB is a single-process in-memory object. It is not safe for multi-threaded mutation without external locking.
- Views are not persisted. Views contain Python callables and cannot be serialised. Re-register them after
RelPy.load(). - No subquery support. Nested queries are not supported in the current query builder. Use views or Python variables to chain results.
- Performance degrades on very large datasets. The library is optimised for clarity and correctness, not compiled-engine throughput. For >500 000 rows or heavy analytical queries, DuckDB or SQLite will be substantially faster.
- AutoNumber columns cannot be manually set. Passing an explicit value for an
AutoNumbercolumn ininsert()raises aConstraintError. - Primary keys are immutable.
update()ignores changes to primary key columns.
Catching errors gracefully
from relpy import RelPyError, ConstraintError, TableNotFoundError
try:
db.insert("orders", {"user_id": 9999, "amount": 50.0})
except ConstraintError as e:
print(f"FK violation: {e}")
except TableNotFoundError as e:
print(f"Table missing: {e}")
except RelPyError as e:
print(f"Other RelPyDB error: {e}")