#database-table #orm #generator #codegen

codegenta

基于 rustorm ORM 从数据库表生成模型代码的工具

8 个版本

使用旧的 Rust 2015

0.0.8 2016年1月27日
0.0.7 2015年12月27日
0.0.5 2015年11月24日
0.0.3 2015年10月21日
0.0.1 2015年7月9日

#29 in #database-table

24 每月下载量

MIT 协议

260KB
1.5K SLoC

Codegenta

Latest Version Build Status Coverage Status MIT licensed

是一个代码生成工具,旨在与 rustorm 一起使用,以便根据数据库表元数据信息积极创建 Rust 结构体。

为什么?

有无数种方式使用各种库在生态系统中表示数据库表。可能最常见的方式(没有太多开销/学习曲线)是使用数据库 GUI 工具,如 PgAdmin。

这样做的方式是,您创建数据库表,然后在您的代码中创建相应的结构体。这是一个迭代过程,其中您将根据您开发应用程序添加/删除/编辑表定义。通常,很容易出现不一致。

示例

术语和表

  • Product 表存储了我们示例应用程序中的产品列表。
  • Users 是一个用户表(复数是因为用户在大多数数据库中都是一个保留关键字)。
  • Category - 产品、项目等的分类。
  • Photos - 如果产品/项目列表有图片,会看起来更加吸引人。
  • Reviews - 对特定产品的用户评论。
  • ProductAvailability - 当特定产品可用时,确定卖家的营业时间。在产品上市前宣布产品。这更适用于服务。

要查看的代码

  • 查看 examples/generate_model_code.rs。它生成的源代码与您可能为项目编写的模型代码相当。

  • 生成的模型代码位于 ./gen 文件夹



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
)

生成的模型代码



#[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, 标记列 product 为 NOT NULL 因此它始终具有值。

  • pub name:Option<String> 是一个可选值,因为我们没有指定它为非空。同样,descrption 等也是如此。

  • pub owner: Option<Users>, 根据 foreign key 约束,codegenta 足够智能,可以识别出产品有一个基于 owner_idowner,它引用 Users 表。Codegenta 然后添加一个可选字段 owner:Option,您可以在您的应用程序中使用它来存储有关特定产品卖家的额外信息。

  • pub currency: Option<Currency>currency_id指定了产品使用哪种货币。您可以在控制器代码中将使用的货币添加到产品中,以包含更多有关所使用货币的详细信息,而无需创建额外的容器结构体。

更多高级功能

查看项目中提供的示例所使用的表模式。

  • pub category: Vec<Category>, - codegenta同样能够智能地识别产品表通过product_category连接表与Category表相关联,这提供了一个产品与类别之间的1:M关系,因为产品可以有多个类别。

  • pubphoto: Vec<Photo>,

  • pubreview: Vec<Review>,

同样适用于Photo,通过product_photo连接表和带有product_review连接的Review表。

  • pub availability: Option<Box<ProductAvailability>>,- 产品可用性是codegenta的另一个独特功能,它确定ProductAvailability表只是产品表的扩展表,具有1:1关系,因为每个产品只能有一个产品可用性。

使用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());
}

看看那些漂亮的生成的SQL

路线图

  • 支持相反的方向吗?
  • 用户编写模型并在数据库中创建相应的表

依赖项

~9.5MB
~185K SLoC