ORM

Objective: Create a PostgreSQL database that preserves state of a stock. Insert a few items. Then select some of them and present the results.

Python

ORM in use: sqlalchemy. Migration tool: alembic.

# DB in a container:
# docker run --name productsdb -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres:15.3

import sqlalchemy as db
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.orm import MappedAsDataclass, Session

class Base(MappedAsDataclass, DeclarativeBase):
#    metadata = db.MetaData()
    pass

class StockItem(Base):
    __tablename__ = "stock"
    name: Mapped[str] = mapped_column(primary_key=True)
    vendor: Mapped[str]
    quantity: Mapped[int]

engine = db.create_engine("postgresql+psycopg2://postgres:pass@127.0.0.1/postgres",)
#Base.metadata.create_all(engine)

with Session(engine) as session:
    session.add_all([
        StockItem(name="Toothbrush", vendor="Limo", quantity=1497),
        StockItem(name="Comb", vendor="Takoon", quantity=210),
        StockItem(name="Towel", vendor="Beana", quantity=362),
        ])

    session.commit()    

    query = db.select(StockItem).where(StockItem.quantity >= 300)
    for user in session.scalars(query):
        print(user)


Restore commented lines to Initialize database.

Rust

ORM in use: diesel. Diesel comes with built-in migration tool.

// models.rs
use crate::schema::stock;
use diesel::prelude::*;

#[derive(Debug)]
#[derive(Insertable)]
#[derive(Queryable, Selectable)]
#[diesel(table_name = stock)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct StockItem {
    pub name: String,
    pub vendor: String,
    pub quantity: i32,
}
// schema.rs
// @generated automatically by Diesel CLI.

diesel::table! {
    stock (name) {
        name -> Varchar,
        vendor -> Varchar,
        quantity -> Int4,
    }
}
// main.rs
mod models;
mod schema;

use diesel::pg::PgConnection;
use diesel::prelude::*;
use self::schema::stock::dsl::*;
use self::models::*;

pub fn establish_connection() -> PgConnection {
     let database_url = "postgres://postgres:pass@127.0.0.1/postgres";
     PgConnection::establish(&database_url)
         .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

fn main() {
    let connection = &mut establish_connection();
    
    let new_item1 = StockItem { name: "Toothbrush".to_string(), vendor: "Limo".to_string(), quantity: 1497 };
    let new_item2 = StockItem { name: "Comb".to_string(), vendor: "Takoon".to_string(), quantity: 210 };
    let new_item3 = StockItem { name: "Towel".to_string(), vendor: "Beana".to_string(), quantity: 362 };
    
    diesel::insert_into(self::schema::stock::table)
        .values([new_item1, new_item2, new_item3])
        .returning(StockItem::as_returning())
        .get_results(connection)
        .expect("Error saving new stock item");
    
    let results = stock
        .filter(quantity.ge(300))
        .select(StockItem::as_select())
        .load(connection)
        .expect("Error loading stock items");

    for item in results {
        println!("{:?}", item);
    }
}

Crystal

ORM in use: granite. Migration tool: micrate. These are part of amber framework.

require "granite/adapter/pg"

Granite::Connections << Granite::Adapter::Pg.new(name: "testdb", url: "postgres://postgres:pass@127.0.0.1/postgres")

class Stock < Granite::Base
  connection testdb
  table stock

  column name : String, primary: true, auto: false
  column vendor : String
  column quantity : Int32
end

Stock.new(name: "Toothbrush", vendor: "Limo", quantity: 1497).save
Stock.new(name: "Comb", vendor: "Takoon", quantity: 210).save
Stock.new(name: "Towel", vendor: "Beana", quantity: 362).save

Stock.where(:quantity, :gteq, 300).select.each do |item|
  # puts item.name # accessing fields also possible
  puts item.to_json
end
  • Initialize DB by calling: shards run micrate -- up
  • Uninitialize DB by calling: shards run micrate -- down