Skip to content

Commit e82432e

Browse files
committed
update pg example
1 parent 43fc45a commit e82432e

File tree

4 files changed

+135
-91
lines changed

4 files changed

+135
-91
lines changed

example-postgres/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dotenv = "0.15"
1212
chrono = "0.4"
1313
env_logger = "0.11.5"
1414
log = "0.4"
15+
futures-util = "0.3"
1516

1617
[dependencies.sqlx]
1718
version = "0.8"

example-postgres/migrations/20240908062042_schema.sql

+17-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@ CREATE TYPE user_role AS ENUM ('user', 'admin');
22
CREATE TYPE account_type AS ENUM ('legacy', 'normal');
33
CREATE TYPE user_group AS ENUM ('local', 'global', 'other');
44

5+
CREATE TYPE color AS
6+
(
7+
red INT,
8+
green int,
9+
blue int
10+
);
11+
512
CREATE TABLE users
613
(
7-
id SERIAL PRIMARY KEY,
8-
first_name VARCHAR(128) NOT NULL,
9-
last_name VARCHAR(128) NOT NULL,
10-
email VARCHAR(128) NOT NULL UNIQUE,
11-
role user_role NOT NULL,
12-
type account_type,
13-
"group" user_group NOT NULL DEFAULT 'local',
14-
disabled TEXT,
15-
last_login TIMESTAMP DEFAULT NULL
14+
id SERIAL PRIMARY KEY,
15+
first_name VARCHAR(128) NOT NULL,
16+
last_name VARCHAR(128) NOT NULL,
17+
email VARCHAR(128) NOT NULL UNIQUE,
18+
role user_role NOT NULL,
19+
type account_type,
20+
"group" user_group NOT NULL DEFAULT 'local',
21+
disabled TEXT,
22+
favourite_color color DEFAULT NULL,
23+
last_login TIMESTAMP DEFAULT NULL
1624
);
1725

1826
CREATE TABLE test

example-postgres/src/main.rs

+114-79
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,47 @@
11
use chrono::{NaiveDateTime, Utc};
2-
use log::LevelFilter;
2+
use futures_util::TryStreamExt;
3+
use log::{info, LevelFilter};
34
use ormx::{Delete, Insert, Table};
45
use sqlx::PgPool;
56

67
mod query2;
78

8-
#[tokio::main]
9-
async fn main() -> anyhow::Result<()> {
10-
dotenv::dotenv().ok();
11-
env_logger::builder()
12-
.filter_level(LevelFilter::Debug)
13-
.init();
14-
15-
let db = PgPool::connect(&dotenv::var("DATABASE_URL")?).await?;
16-
17-
log::info!("insert a few new rows into the database");
18-
let mut new = InsertUser {
19-
user_id: 1,
20-
first_name: "Moritz".to_owned(),
21-
last_name: "Bischof".to_owned(),
22-
email: "[email protected]".to_owned(),
23-
disabled: None,
24-
role: Role::User,
25-
ty: Some(AccountType::Normal),
26-
}
27-
.insert(&db)
28-
.await?;
29-
InsertUser {
30-
user_id: 2,
31-
first_name: "Dylan".to_owned(),
32-
last_name: "Thomas".to_owned(),
33-
email: "[email protected]".to_owned(),
34-
disabled: Some("email not verified".to_owned()),
35-
role: Role::Admin,
36-
ty: None,
37-
}
38-
.insert(&db)
39-
.await?;
40-
41-
log::info!("update a single field");
42-
new.set_last_login(&db, Some(Utc::now().naive_utc()))
43-
.await?;
44-
45-
log::info!("update all fields at once");
46-
new.email = "asdf".to_owned();
47-
new.update(&db).await?;
48-
49-
log::info!("apply a patch to the user");
50-
new.patch(
51-
&db,
52-
UpdateUser {
53-
first_name: "NewFirstName".to_owned(),
54-
last_name: "NewLastName".to_owned(),
55-
disabled: Some("Reason".to_owned()),
56-
role: Role::Admin,
57-
},
58-
)
59-
.await?;
60-
61-
log::info!("reload the user, in case it has been modified");
62-
new.email.clear();
63-
new.reload(&db).await?;
64-
65-
log::info!("use the improved query macro for searching users");
66-
let search_result = query2::query_users(&db, Some("NewFirstName"), None).await?;
67-
log::info!("search result: {:?}", search_result);
68-
69-
log::info!("load all users in the order specified by the 'order_by' attribute");
70-
let all = User::all_paginated(&db, 0, 100).await?;
71-
log::info!("all users: {all:?}");
72-
73-
log::info!("delete the user from the database");
74-
new.delete(&db).await?;
75-
76-
Ok(())
77-
}
78-
799
#[derive(Debug, ormx::Table)]
8010
#[ormx(table = "users", id = user_id, insertable, deletable, order_by = "email ASC")]
8111
struct User {
82-
// map this field to the column "id"
83-
#[ormx(column = "id")]
84-
#[ormx(get_one = get_by_user_id)]
12+
// `#[ormx(default)]` indicates that the database generates a value for us.
13+
// `#[ormx(get_one = ..)]` generates `User::get_by_user_id(db, id: i32) -> Result<User>` for us
14+
#[ormx(column = "id", default, get_one = get_by_user_id)] // map this field to the column "id"
8515
user_id: i32,
16+
17+
// just some normal, 'NOT NULL' columns
8618
first_name: String,
8719
last_name: String,
88-
// generate `User::by_email(&str) -> Result<Option<Self>>`
20+
disabled: Option<String>,
21+
22+
// generates `User::by_email(&str) -> Result<Option<Self>>`
23+
// unlike `#[ormx(get_one = .. )]`, `by_email` will return `None` instead of an error if no record is found.
8924
#[ormx(get_optional(&str))]
9025
email: String,
26+
27+
// custom types need to be annotated with `#[ormx(custom_type)]`
9128
#[ormx(custom_type)]
9229
role: Role,
30+
31+
// they can, of course, also be nullable
9332
#[ormx(column = "type", custom_type)]
9433
ty: Option<AccountType>,
34+
35+
// the database can also provide a default value for them.
36+
// `#[ormx(set)]` generates `User::set_group(&mut self, g: UserGroup) -> Result` for us
9537
#[ormx(custom_type, default, set)]
9638
group: UserGroup,
97-
disabled: Option<String>,
98-
// don't include this field into `InsertUser` since it has a default value
99-
// generate `User::set_last_login(Option<NaiveDateTime>) -> Result<()>`
39+
40+
// besides enums, composite/record types are also supported
41+
#[ormx(custom_type, default)]
42+
favourite_color: Option<Color>,
43+
44+
// generates `User::set_last_login(&mut self, Option<NaiveDateTime>) -> Result`
10045
#[ormx(default, set)]
10146
last_login: Option<NaiveDateTime>,
10247
}
@@ -112,6 +57,8 @@ struct UpdateUser {
11257
role: Role,
11358
}
11459

60+
// these are all enums, created using `CREATE TYPE .. AS ENUM (..);`
61+
11562
#[derive(Debug, Copy, Clone, sqlx::Type)]
11663
#[sqlx(type_name = "user_role")]
11764
#[sqlx(rename_all = "lowercase")]
@@ -137,10 +84,98 @@ enum UserGroup {
13784
Other,
13885
}
13986

87+
// PostgreSQL also supports composite/record types
88+
89+
#[derive(Debug, Copy, Clone, sqlx::Type)]
90+
#[sqlx(type_name = "color")]
91+
struct Color {
92+
red: i32,
93+
green: i32,
94+
blue: i32,
95+
}
96+
14097
#[derive(Debug, ormx::Table)]
14198
#[ormx(table = "test", id = id, insertable)]
14299
struct Test {
143100
id: i32,
144101
#[ormx(by_ref)]
145102
rows: Vec<String>,
146103
}
104+
105+
#[tokio::main]
106+
async fn main() -> anyhow::Result<()> {
107+
dotenv::dotenv().ok();
108+
env_logger::builder().filter_level(LevelFilter::Info).init();
109+
110+
let pool = PgPool::connect(&dotenv::var("DATABASE_URL")?).await?;
111+
let mut tx = pool.begin().await?;
112+
113+
114+
info!("insert a new row into the database..");
115+
let mut new = InsertUser {
116+
first_name: "Moritz".to_owned(),
117+
last_name: "Bischof".to_owned(),
118+
email: "[email protected]".to_owned(),
119+
disabled: None,
120+
role: Role::User,
121+
ty: Some(AccountType::Normal),
122+
}
123+
.insert(&mut *tx)
124+
.await?;
125+
info!("after inserting a row, ormx loads the database-generated columns for us, including the ID ({})", new.user_id);
126+
127+
128+
info!("update a single field at a time, each in its own query..");
129+
new.set_last_login(&mut *tx, Some(Utc::now().naive_utc()))
130+
.await?;
131+
new.set_group(&mut *tx, UserGroup::Global).await?;
132+
133+
134+
info!("update all fields at once..");
135+
new.email = "asdf".to_owned();
136+
new.favourite_color = Some(Color {
137+
red: 255,
138+
green: 0,
139+
blue: 0,
140+
});
141+
new.update(&mut *tx).await?;
142+
143+
144+
info!("apply a patch to the user..");
145+
new.patch(
146+
&mut *tx,
147+
UpdateUser {
148+
first_name: "NewFirstName".to_owned(),
149+
last_name: "NewLastName".to_owned(),
150+
disabled: Some("Reason".to_owned()),
151+
role: Role::Admin,
152+
},
153+
)
154+
.await?;
155+
156+
157+
info!("reload the user, in case it has been modified..");
158+
new.email.clear();
159+
new.reload(&mut *tx).await?;
160+
161+
162+
info!("use the improved query macro for searching users..");
163+
let search_result = query2::query_users(&mut *tx, Some("NewFirstName"), None).await?;
164+
info!("found {} matching users", search_result.len());
165+
166+
167+
info!("load all users in the order specified by the 'order_by' attribute..");
168+
User::stream_all_paginated(&mut *tx, 0, 100)
169+
.try_for_each(|u| async move {
170+
info!("- user_id = {}", u.user_id);
171+
Ok(())
172+
})
173+
.await?;
174+
175+
176+
info!("delete the user from the database..");
177+
new.delete(&mut *tx).await?;
178+
179+
180+
Ok(())
181+
}

example-postgres/src/query2.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use sqlx::PgPool;
1+
use sqlx::{Executor, Postgres};
22

33
use crate::User;
44

55
pub(crate) async fn query_users(
6-
db: &PgPool,
6+
db: impl Executor<'_, Database = Postgres>,
77
filter: Option<&str>,
88
limit: Option<usize>,
99
) -> anyhow::Result<Vec<User>> {
1010
let result = ormx::conditional_query_as!(
1111
User,
1212
r#"SELECT
13-
id AS user_id, first_name, last_name, email, disabled,
13+
id AS user_id, first_name, last_name, email, disabled, favourite_color as "favourite_color: _",
1414
role AS "role: _", "group" AS "group: _", type as "ty: _", last_login"#
1515
"FROM users"
1616
Some(f) = filter => {

0 commit comments

Comments
 (0)