#database-table #orm #generator #codegen

codegenta

A model code generator from database table using rustorm ORM

8 releases

Uses old Rust 2015

0.0.8 Jan 27, 2016
0.0.7 Dec 27, 2015
0.0.5 Nov 24, 2015
0.0.3 Oct 21, 2015
0.0.1 Jul 9, 2015

#1816 in Database interfaces

Download history 93/week @ 2024-07-22

93 downloads per month

MIT license

260KB
1.5K SLoC

Codegenta

Latest Version Build Status Coverage Status MIT licensed

is code generation tool designed to work along side with rustorm to eagerly create Rust structs based on the database tables meta data information.

Why?

There are a lot of ways to express database tables using a multitude of libraries around the ecosystem. Arguably the most prevalent way(without too much of overhead/learning curve) is by using database GUI tools such as PgAdmin.

The way to do these is, you create database tables, you then create the corresponding struct in your code. This is an iterative process wherein you will add/remove/edit the table definition as you develop you application. Often times, it's easy to make inconsistencies.

Example

Terminology and tables

  • Product table holds the list of products in our example app.
  • Users is a user table (plural due to user being a reserved keyword in most databases).
  • Category - categories of products, items etc.
  • Photos - product/item listing would be much more pleasing if it has pictures.
  • Reviews - a review of users for a certain product.
  • ProductAvailability - determines office time of the seller when a certain product is availability. Anouncement of product before it is available. This is much more applicable to services.

Code to look at

  • Take a look at examples/generate_model_code.rs. This generates a source code that is comparible to what you could have written as a model code of your project.

  • The source code generated is located at ./gen folder



CREATE TABLE bazaar.product
(
  product_id uuid NOT NULL DEFAULT uuid_generate_v4(),
  name character varying,
  description character varying,
  price numeric,
  currency_id uuid,
  unit character varying,
  barcode character varying,
  owner_id uuid,
  currency_id uuid,
  CONSTRAINT product_pkey PRIMARY KEY (product_id),
  CONSTRAINT product_currency_id_fkey FOREIGN KEY (currency_id)
      REFERENCES payment.currency (currency_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
  CONSTRAINT product_user_id_fkey FOREIGN KEY (owner_id)
      REFERENCES bazaar.users (user_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED
)

The generated model code



#[derive(RustcDecodable, RustcEncodable)]
#[derive(Debug, Clone)]
pub struct Product {
    pub product_id:Uuid,
    pub name:Option<String>,
    pub description:Option<String>,
    pub barcode:Option<String>,
    pub currency_id:Option<Uuid>,
    pub owner_id:Option<Uuid>,
    pub price:Option<f64>,
    pub unit:Option<String>,

    pub owner: Option<Users>,
    pub currency: Option<Currency>,
    pub availability: Option<Box<ProductAvailability>>,
    pub category: Vec<Category>,
    pub photo: Vec<Photo>,
    pub review: Vec<Review>,
}

  • pub product_id:Uuid, marked the column product as NOT NULL therefore it will always have value.

  • pub name:Option<String> is an option since, we did not specify that this non nullable. Same as descrption and etc.

  • pub owner: Option<Users>, base on the foreign key constraint, codegenta is smart enough to recognize that a product has an owner based on the owner_id which references Users table. Codegenta then add an optional field owner:Option, which you can then later use in you app to hold an additional info about the seller of a certain product.

  • pub currency: Option<Currency>, currency_id specifies which currency a product is using. You can put the used currency in the product in your controller code to include a more detailed information about the currency used without having make additional container structs.

More advance features

Take a look at the table schema used in these examples provided by the project.

  • pub category: Vec<Category>, - the codegenta is also smart to recognize that product table is referred by Category table with a linker table product_category, which provides a 1:M relationship between product and category, since products can have multiple categories

  • pub photo: Vec<Photo>,

  • pub review: Vec<Review>,

Same applies for Photo, with linker table product_photo and Review table with linker product_review

  • pub availability: Option<Box<ProductAvailability>>, - product availability is another unique feature of codegenta that determines that ProductAvailability table is just an extension table of product and has a 1:1 relationship, since each product can only have 1 product availability.

A complex query when using codegenta

link


extern crate rustorm;
extern crate uuid;
extern crate chrono;
extern crate rustc_serialize;

use rustorm::query::Query;
use rustorm::query::{Filter,Equality};
use rustorm::dao::{Dao,IsDao};
use gen::bazaar::Product;
use gen::bazaar::product;
use gen::bazaar::Photo;
use gen::bazaar::photo;
use gen::bazaar::Review;
use gen::bazaar::review;
use gen::bazaar::Category;
use gen::bazaar::category;
use gen::bazaar::product_category;
use gen::bazaar::ProductCategory;
use gen::bazaar::product_photo;
use gen::bazaar::ProductPhoto;
use gen::bazaar::ProductAvailability;
use gen::bazaar::product_availability;

use rustorm::table::IsTable;
use rustorm::pool::ManagedPool;

mod gen;

fn main(){
    let mut pool = ManagedPool::init("postgres://postgres:p0stgr3s@localhost/bazaar_v6",1);
    let db = pool.connect().unwrap();
    
    let mut query = Query::select_all();
    
    query.from(&Product::table())
        .left_join(&ProductCategory::table(),
            product_category::product_id, product::product_id)
         .left_join(&Category::table(),
            category::category_id, product_category::category_id)
        .left_join(&ProductPhoto::table(),
            product::product_id, product_photo::product_id)
        .left_join(&Photo::table(), 
            product_photo::photo_id, photo::photo_id)
        .filter(product::name, Equality::EQ, &"GTX660 Ti videocard")
        .filter(category::name, Equality::EQ, &"Electronic")
        .group_by(vec![category::name])
        .having("count(*)", Equality::GT, &1)
        .asc(product::name)
        .desc(product::created)
        ;
    let frag = query.build(db.as_ref());
    
    let expected = "
   SELECT *
     FROM bazaar.product
          LEFT OUTER JOIN bazaar.product_category 
          ON product_category.product_id = product.product_id 
          LEFT OUTER JOIN bazaar.category 
          ON category.category_id = product_category.category_id 
          LEFT OUTER JOIN bazaar.product_photo 
          ON product.product_id = product_photo.product_id 
          LEFT OUTER JOIN bazaar.photo 
          ON product_photo.photo_id = photo.photo_id 
    WHERE product.name = $1 
      AND category.name = $2 
 GROUP BY category.name 
   HAVING count(*) > $3 
 ORDER BY product.name ASC, product.created DESC
    ".to_string();
    println!("actual:   {{{}}} [{}]", frag.sql, frag.sql.len());
    println!("expected: {{{}}} [{}]", expected, expected.len());
    assert!(frag.sql.trim() == expected.trim());
}

Look at those pretty generated SQL's

Roadmap

  • Support for the other way around?
  • The user writes the model and create a corresponding table in the database

Dependencies

~10MB
~185K SLoC