Skip to main content

postgresql_example/
postgresql_example.rs

1// TORM PostgreSQL Example
2// PostgreSQL support is implemented via the native wire protocol:
3//   ✅ Connection configuration and creation (real TCP connection)
4//   ✅ Authentication: cleartext / MD5 / SCRAM-SHA-256
5//   ✅ Simple query protocol + parameterized queries (Parse/Bind/Execute)
6//   ✅ Result decoding (RowDescription/DataRow), transactions
7//
8// Prerequisites: a running PostgreSQL server, e.g.
9//   psql -h localhost -p 5432 -U odoo -c "CREATE DATABASE tormdb;"
10
11use torm::*;
12
13const PG_HOST: &str = "localhost";
14const PG_PORT: u16 = 5432;
15const PG_DB: &str = "tormdb";
16const PG_USER: &str = "odoo";
17const PG_PASS: &str = "odoo";
18
19#[tokio::main]
20async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
21    println!("🐘 TORM - Tokio ORM PostgreSQL Demo\n");
22
23    // 1. Database connection example
24    println!("📡 PostgreSQL connection example");
25    println!("================================");
26    demonstrate_database_connection().await?;
27    println!();
28
29    // 2. GORM-style model CRUD
30    println!("🗄️  GORM-style model CRUD");
31    println!("================================");
32    demonstrate_crud().await?;
33    println!();
34
35    // 3. Transactions
36    println!("🔒 PostgreSQL transactions");
37    println!("================================");
38    demonstrate_transactions().await?;
39    println!();
40
41    // 4. Simplified UUID generation
42    println!("🔑 Simplified UUID generation");
43    println!("================================");
44    demonstrate_simplified_uuid()?;
45    println!();
46
47    // 5. Simplified error handling
48    println!("⚠️  Simplified error handling");
49    println!("================================");
50    demonstrate_simplified_error()?;
51    println!();
52
53    // 6. Simplified LRU cache
54    println!("💾 Simplified LRU cache");
55    println!("================================");
56    demonstrate_simplified_cache()?;
57    println!();
58
59    // 7. Simple connection pool
60    println!("🏊 Simple connection pool");
61    println!("================================");
62    demonstrate_connection_pool().await?;
63    println!();
64
65    // 8. Query builder example
66    println!("🔨 Query builder example");
67    println!("================================");
68    demonstrate_query_builder();
69    println!();
70
71    println!("🎉 All demos completed successfully!");
72    println!();
73
74    Ok(())
75}
76
77async fn demonstrate_database_connection() -> std::result::Result<(), Box<dyn std::error::Error>> {
78    println!("PostgreSQL connection (native wire protocol):");
79    let dsn = Dsn::new(DBDriver::PostgreSQL, PG_DB)
80        .with_host(PG_HOST)
81        .with_port(PG_PORT)
82        .with_username(PG_USER)
83        .with_password(PG_PASS);
84    println!("  DSN: {}", dsn.build());
85
86    // Test actual PostgreSQL connection
87    let db = Database::postgresql(PG_HOST, PG_PORT, PG_DB, PG_USER, PG_PASS).await?;
88    println!("  ✅ Connected to PostgreSQL (native wire protocol)");
89    println!("  DB type: {:?}", db.db_type());
90    println!("  Connected: {}", db.is_connected());
91    db.ping().await?;
92    println!("  ✅ Ping successful");
93    db.close().await?;
94
95    println!();
96    println!("Connection via ConnectionFactory:");
97    let config = ConnectionConfig::postgresql(PG_HOST, PG_PORT, PG_DB, PG_USER, PG_PASS)
98        .with_timeout(std::time::Duration::from_secs(30))
99        .with_max_connections(10);
100    let conn = ConnectionFactory::create_connection(config.clone()).await?;
101    println!("  ✅ Connection created (factory)");
102    println!("  DB type: {:?}", conn.db_type());
103    conn.close().await?;
104
105    println!();
106    println!("Direct PostgresConnection:");
107    let pg = PostgresConnection::new(&config).await?;
108    println!("  ✅ PostgresConnection created");
109    println!("  Connected: {}", pg.is_connected());
110    pg.close().await?;
111
112    Ok(())
113}
114
115async fn demonstrate_crud() -> std::result::Result<(), Box<dyn std::error::Error>> {
116    println!("GORM-style model CRUD (create / first / find / update / delete):");
117    let db = Database::postgresql(PG_HOST, PG_PORT, PG_DB, PG_USER, PG_PASS).await?;
118
119    // Schema setup (GORM AutoMigrate equivalent — drop + recreate for a clean demo)
120    db.execute("DROP TABLE IF EXISTS users", &[]).await?;
121    db.execute(
122        "CREATE TABLE users (
123            id TEXT PRIMARY KEY,
124            name TEXT NOT NULL,
125            email TEXT,
126            age INTEGER,
127            status TEXT,
128            created_at TIMESTAMPTZ,
129            updated_at TIMESTAMPTZ
130        )",
131        &[],
132    )
133    .await?;
134    println!("  ✅ Schema ready (users table recreated)");
135
136    // Create — GORM `db.Create(&user)` equivalent
137    let mut alice = User::new("Alice", "alice@example.com").with_age(25);
138    db.create(&mut alice).await?;
139    println!(
140        "  ✅ Created: id={} name={} (created_at: {:?})",
141        alice.id,
142        alice.name,
143        alice.created_at()
144    );
145
146    let mut bob = User::new("Bob", "bob@example.com").with_age(30);
147    db.create(&mut bob).await?;
148    println!("  ✅ Created: id={} name={}", bob.id, bob.name);
149
150    // First — GORM `db.First(&user, id)` equivalent
151    let found: Option<User> = db.first(&alice.id).await?;
152    match &found {
153        Some(u) => println!(
154            "  ✅ First: id={} name={} age={:?} status={}",
155            u.id, u.name, u.age, u.status
156        ),
157        None => println!("  ❌ First: not found"),
158    }
159
160    // Find all — GORM `db.Find(&users)` equivalent
161    let users: Vec<User> = db.all().await?;
162    println!("  ✅ Find: {} user(s)", users.len());
163    for u in &users {
164        println!(
165            "    - {} <{}> age={:?} status={}",
166            u.name, u.email, u.age, u.status
167        );
168    }
169
170    // Update — GORM `db.Model(&user).Update("age", 29)` equivalent
171    db.update(&mut alice, &[("age", 29)]).await?;
172    let updated: User = db.first(&alice.id).await?.expect("alice exists");
173    println!("  ✅ Updated: {} age now {:?}", updated.name, updated.age);
174
175    // Delete — GORM `db.Delete(&user)` equivalent
176    db.delete(&mut alice).await?;
177    let gone: Option<User> = db.first(&alice.id).await?;
178    println!(
179        "  ✅ Deleted: {} exists after delete = {}",
180        alice.name,
181        gone.is_some()
182    );
183
184    db.close().await?;
185    println!("  ✅ Database closed");
186
187    Ok(())
188}
189
190async fn demonstrate_transactions() -> std::result::Result<(), Box<dyn std::error::Error>> {
191    println!("PostgreSQL transactions (BEGIN / COMMIT / ROLLBACK):");
192    let db = Database::postgresql(PG_HOST, PG_PORT, PG_DB, PG_USER, PG_PASS).await?;
193
194    // Commit
195    let mut tx = db.begin_transaction().await?;
196    tx.execute(
197        "INSERT INTO users (id, name, age) VALUES ($1, $2, $3)",
198        &[
199            SimpleUuid::new_v4().to_string().into(),
200            "Dave".into(),
201            40.into(),
202        ],
203    )
204    .await?;
205    tx.execute(
206        "INSERT INTO users (id, name, age) VALUES ($1, $2, $3)",
207        &[
208            SimpleUuid::new_v4().to_string().into(),
209            "Frank".into(),
210            35.into(),
211        ],
212    )
213    .await?;
214    tx.commit().await?;
215    println!("  ✅ Transaction committed (2 users)");
216
217    // Rollback
218    let mut tx = db.begin_transaction().await?;
219    tx.execute(
220        "INSERT INTO users (id, name, age) VALUES ($1, $2, $3)",
221        &[
222            SimpleUuid::new_v4().to_string().into(),
223            "Eve".into(),
224            45.into(),
225        ],
226    )
227    .await?;
228    tx.rollback().await?;
229    println!("  ✅ Transaction rolled back (Eve not saved)");
230
231    let result = db.query("SELECT COUNT(*) AS count FROM users", &[]).await?;
232    println!("  ✅ Final user count: {:?}", result.rows[0].get("count"));
233
234    db.close().await?;
235    Ok(())
236}
237
238fn demonstrate_simplified_uuid() -> std::result::Result<(), Box<dyn std::error::Error>> {
239    println!("Generating UUIDs:");
240
241    // Generate multiple UUIDs
242    let uuid1 = SimpleUuid::new_v4();
243    let uuid2 = SimpleUuid::new_v4();
244    let uuid3 = SimpleUuid::new_v4();
245
246    println!("  UUID 1: {}", uuid1);
247    println!("  UUID 2: {}", uuid2);
248    println!("  UUID 3: {}", uuid3);
249    println!(
250        "  All unique: {}",
251        uuid1 != uuid2 && uuid2 != uuid3 && uuid1 != uuid3
252    );
253
254    println!();
255    println!("ID Generator:");
256    let generator = IdGenerator::new();
257    let id1 = generator.generate();
258    let id2 = generator.generate();
259
260    println!("  ID 1: {}", id1);
261    println!("  ID 2: {}", id2);
262    println!(
263        "  ID 3: {}",
264        generator.with_prefix("user_").generate()
265    );
266
267    println!();
268    println!("Simple IDs:");
269    let simple_gen = IdGenerator::new().with_simple_id();
270    println!("  Simple ID: {}", simple_gen.generate());
271    println!(
272        "  Prefixed: {}",
273        simple_gen.with_prefix("order_").generate()
274    );
275
276    Ok(())
277}
278
279fn demonstrate_simplified_error() -> std::result::Result<(), Box<dyn std::error::Error>> {
280    println!("Error handling without thiserror:");
281
282    // Create different error types
283    let not_found = SimpleError::NotFound;
284    println!("  NotFound: {}", not_found);
285
286    let custom = SimpleError::custom("Something went wrong");
287    println!("  Custom: {}", custom);
288
289    let invalid_query = SimpleError::invalid_query("Invalid WHERE clause");
290    println!("  InvalidQuery: {}", invalid_query);
291
292    let connection_error = SimpleError::connection_error("Could not connect to database");
293    println!("  ConnectionError: {}", connection_error);
294
295    println!();
296    println!("Using SimpleResult:");
297    let success: SimpleResult<i32> = Ok(42);
298    println!("  Success: {:?}", success);
299
300    let failure: SimpleResult<i32> = Err(SimpleError::NotFound);
301    println!("  Failure: {:?}", failure);
302
303    Ok(())
304}
305
306fn demonstrate_simplified_cache() -> std::result::Result<(), Box<dyn std::error::Error>> {
307    println!("LRU cache:");
308
309    let mut cache: SimpleLruCache<&str, &str> = SimpleLruCache::new(3);
310
311    // Add items
312    cache.put("key1", "value1");
313    cache.put("key2", "value2");
314    cache.put("key3", "value3");
315
316    println!("  Initial size: {}", cache.len());
317    println!("  key1: {:?}", cache.get(&"key1"));
318    println!("  key2: {:?}", cache.get(&"key2"));
319    println!("  key3: {:?}", cache.get(&"key3"));
320
321    // Test LRU eviction
322    println!();
323    println!("  Adding key4 (should evict oldest):");
324    cache.put("key4", "value4");
325    println!("  key1: {:?}", cache.get(&"key1")); // Should be None
326    println!("  key4: {:?}", cache.get(&"key4")); // Should be Some
327
328    // Test capacity
329    println!();
330    println!("  Current capacity: {}", cache.capacity());
331    println!("  Current size: {}", cache.len());
332
333    // Resize
334    cache.resize(2);
335    println!("  After resize to 2:");
336    println!("  New size: {}", cache.len());
337
338    // Cleanup
339    cache.clear();
340    println!("  After clear: {}", cache.is_empty());
341
342    Ok(())
343}
344
345async fn demonstrate_connection_pool() -> std::result::Result<(), Box<dyn std::error::Error>> {
346    println!("Simple connection pool implementation:");
347
348    // Create a pool with pre-created connections
349    let connections = vec![1, 2, 3, 4, 5];
350    let pool = SimplePool::new(connections);
351    let status = pool.status();
352
353    println!("  Total connections: {}", status.total_connections);
354    println!("  Idle connections: {}", status.idle_connections);
355    println!("  Active connections: {}", status.active_connections);
356    println!(
357        "  Utilization: {:.1}%",
358        status.utilization_rate() * 100.0
359    );
360
361    // Test getting a connection
362    println!();
363    println!("  Getting a connection from pool:");
364    match pool.get().await {
365        Ok(conn) => {
366            println!("    Got: {}", conn);
367            let status = pool.status();
368            println!(
369                "    After get - idle: {}, active: {}",
370                status.idle_connections, status.active_connections
371            );
372            pool.put(conn);
373            let status = pool.status();
374            println!(
375                "    After put - idle: {}, active: {}",
376                status.idle_connections, status.active_connections
377            );
378        }
379        Err(e) => println!("    Error: {}", e),
380    }
381
382    println!();
383    println!("Pool features:");
384    println!("  • Connection reuse");
385    println!("  • Timeout handling");
386    println!("  • No external deadpool dependency");
387
388    Ok(())
389}
390
391fn demonstrate_query_builder() {
392    println!("Query builder examples:");
393    println!("  (generates SQL text; note PostgreSQL executes with $1/$2-style parameters, see CRUD above)");
394
395    // Basic query
396    let (sql, bindings) = QueryBuilder::new(User::table_name())
397        .where_eq("email", "john@example.com")
398        .limit(1)
399        .build();
400    println!("  Basic query:");
401    println!("    SQL: {}", sql);
402    println!("    Bindings: {:?}", bindings);
403
404    // Complex query
405    let (sql, bindings) = QueryBuilder::new(User::table_name())
406        .where_eq("status", "active")
407        .where_gt("age", 18)
408        .where_like("name", "John%")
409        .order_by("created_at", "DESC")
410        .limit(10)
411        .build();
412    println!();
413    println!("  Complex query:");
414    println!("    SQL: {}", sql);
415    println!("    Bindings: {:?}", bindings);
416
417    // IN query
418    let (sql, bindings) = QueryBuilder::new(User::table_name())
419        .where_in("id", vec![1, 2, 3])
420        .build();
421    println!();
422    println!("  IN query:");
423    println!("    SQL: {}", sql);
424    println!("    Bindings: {:?}", bindings);
425
426    // BETWEEN query
427    let (sql, bindings) = QueryBuilder::new(User::table_name())
428        .where_between("age", 18, 65)
429        .build();
430    println!();
431    println!("  BETWEEN query:");
432    println!("    SQL: {}", sql);
433    println!("    Bindings: {:?}", bindings);
434}
435
436// User model with simplified UUID
437#[derive(Debug, Clone, Model)]
438#[model(table_name = "users")]
439pub struct User {
440    pub id: String,
441    pub name: String,
442    pub email: String,
443    pub age: Option<i32>,
444    pub status: String,
445    pub timestamps: torm::orm::model::Timestamps,
446}
447
448impl User {
449    pub fn new(name: &str, email: &str) -> Self {
450        let generator = IdGenerator::new().with_prefix("user_");
451        Self {
452            id: generator.generate(),
453            name: name.to_string(),
454            email: email.to_string(),
455            age: None,
456            status: "active".to_string(),
457            timestamps: torm::orm::model::Timestamps::new(),
458        }
459    }
460
461    pub fn with_age(mut self, age: i32) -> Self {
462        self.age = Some(age);
463        self
464    }
465
466    pub fn with_status(mut self, status: &str) -> Self {
467        self.status = status.to_string();
468        self
469    }
470}
471