reflex/docs/database/queries.md
2024-02-26 17:18:28 +01:00

187 lines
5.7 KiB
Markdown

# Queries
Queries are used to retrieve data from a database.
A query is a request for information from a database table or combination of
tables. A query can be used to retrieve data from a single table or multiple
tables. A query can also be used to insert, update, or delete data from a table.
## Session
To execute a query you must first create a `rx.session`. You can use the session
to query the database using SQLModel or SQLAlchemy syntax.
The `rx.session` statement will automatically close the session when the code
block is finished. **If `session.commit()` is not called, the changes will be
rolled back and not persisted to the database.** The code can also explicitly
rollback without closing the session via `session.rollback()`.
The following example shows how to create a session and query the database.
First we create a table called `User`.
```python
class User(rx.Model, table=True):
username: str
email: str
```
### Select
Then we create a session and query the User table.
```python
class QueryUser(rx.State):
name: str
users: list[User]
def get_users(self):
with rx.session() as session:
self.users = session.exec(
User.select.where(
User.username.contains(self.name)).all())
```
The `get_users` method will query the database for all users that contain the
value of the state var `name`.
On older python versions, the `.select` attribute on model objects does not work, but
you may use `sqlmodel.select(User)` instead.
### Insert
Similarly, the `session.add()` method to add a new record to the
database or persist an existing object.
```python
class AddUser(rx.State):
username: str
email: str
def add_user(self):
with rx.session() as session:
session.add(User(username=self.username, email=self.email))
session.commit()
```
### Update
To update the user, first query the database for the object, make the desired
modifications, `.add` the object to the session and finally call `.commit()`.
```python
class ChangeEmail(rx.State):
username: str
email: str
def modify_user(self):
with rx.session() as session:
user = session.exec(User.select.where(
(User.username == self.username).first()))
user.email = self.email
session.add(user)
session.commit()
```
### Delete
To delete a user, first query the database for the object, then call
`.delete()` on the session and finally call `.commit()`.
```python
class RemoveUser(rx.State):
username: str
def delete_user(self):
with rx.session() as session:
user = session.exec(User.select.where(
User.username == self.username).first())
session.delete(user)
session.commit()
```
## ORM Object Lifecycle
The objects returned by queries are bound to the session that created them, and cannot generally
be used outside that session. After adding or updating an object, not all fields are automatically
updated, so accessing certain attributes may trigger additional queries to refresh the object.
To avoid this, the `session.refresh()` method can be used to update the object explicitly and
ensure all fields are up to date before exiting the session.
```python
class AddUserForm(rx.State):
user: User | None = None
def add_user(self, form_data: dict[str, str]):
with rx.session() as session:
self.user = User(**form_data)
session.add(self.user)
session.commit()
session.refresh(self.user)
```
Now the `self.user` object will have a correct reference to the autogenerated
primary key, `id`, even though this was not provided when the object was created
from the form data.
If `self.user` needs to be modified or used in another query in a new session,
it must be added to the session. Adding an object to a session does not
necessarily create the object, but rather associates it with a session where it
may either be created or updated accordingly.
```python
class AddUserForm(rx.State):
...
def update_user(self, form_data: dict[str, str]):
if self.user is None:
return
with rx.session() as session:
self.user.set(**form_data)
session.add(self.user)
session.commit()
session.refresh(self.user)
```
If an ORM object will be referenced and accessed outside of a session, you
should call `.refresh()` on it to avoid stale object exceptions.
## Using SQL Directly
Avoiding SQL is one of the main benefits of using an ORM, but sometimes it is
necessary for particularly complex queries, or when using database-specific
features.
SQLModel exposes the `session.execute()` method that can be used to execute raw
SQL strings. If parameter binding is needed, the query may be wrapped in
[`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text),
which allows colon-prefix names to be used as placeholders.
```md alert
Never use string formatting to construct SQL queries, as this may lead to SQL injection vulnerabilities in the app.
```
```python
import sqlalchemy
import reflex as rx
class State(rx.State):
def insert_user_raw(self, username, email):
with rx.session() as session:
session.execute(
sqlalchemy.text(
"INSERT INTO user (username, email) "
"VALUES (:username, :email)"
),
\{"username": username, "email": email},
)
session.commit()
@rx.var
def raw_user_tuples(self) -> list[list]:
with rx.session() as session:
return [list(row) for row in session.execute("SELECT * FROM user").all()]
```