remodel-core

SQL Generation

Generate CREATE TABLE DDL for PostgreSQL, MySQL / MariaDB, and SQLite from a LogicalModel.

Once you have a LogicalModel, call to_sql to generate DDL:

let ddl = logical.to_sql(SqlDialect::Postgres);
println!("{ddl}");

SqlDialect

VariantTarget
SqlDialect::PostgresPostgreSQL 12+
SqlDialect::MySqlMySQL 8 / MariaDB 10+
SqlDialect::SqliteSQLite 3

Full example

Given the conceptual model from Getting Started:

Author (id PK, full_name) ──1..N── [wrote] ──0..N── Book (isbn PK, title, year)

PostgreSQL

CREATE TABLE "Author" (
  "id" INTEGER NOT NULL,
  "full_name" VARCHAR(120) NOT NULL,
  CONSTRAINT "pk_Author" PRIMARY KEY ("id")
);

CREATE TABLE "Book" (
  "isbn" VARCHAR(13) NOT NULL,
  "title" VARCHAR(255) NOT NULL,
  "year" INTEGER NOT NULL,
  "author_fk" INTEGER NOT NULL,
  CONSTRAINT "pk_Book" PRIMARY KEY ("isbn"),
  CONSTRAINT "fk_Book_Author" FOREIGN KEY ("author_fk")
    REFERENCES "Author" ("id")
    ON UPDATE NO ACTION ON DELETE NO ACTION
);

MySQL

CREATE TABLE `Author` (
  `id` INT NOT NULL,
  `full_name` VARCHAR(120) NOT NULL,
  CONSTRAINT `pk_Author` PRIMARY KEY (`id`)
);

CREATE TABLE `Book` (
  `isbn` VARCHAR(13) NOT NULL,
  `title` VARCHAR(255) NOT NULL,
  `year` INT NOT NULL,
  `author_fk` INT NOT NULL,
  CONSTRAINT `pk_Book` PRIMARY KEY (`isbn`),
  CONSTRAINT `fk_Book_Author` FOREIGN KEY (`author_fk`)
    REFERENCES `Author` (`id`)
    ON UPDATE NO ACTION ON DELETE NO ACTION
);

SQLite

CREATE TABLE "Author" (
  "id" INTEGER NOT NULL,
  "full_name" TEXT NOT NULL,
  PRIMARY KEY ("id")
);

CREATE TABLE "Book" (
  "isbn" TEXT NOT NULL,
  "title" TEXT NOT NULL,
  "year" INTEGER NOT NULL,
  "author_fk" INTEGER NOT NULL,
  PRIMARY KEY ("isbn"),
  FOREIGN KEY ("author_fk") REFERENCES "Author" ("id")
);

Identifier quoting

Each dialect uses its own quoting character:

DialectQuoteExample
PostgreSQL" (doubled for escaping)"my table"
MySQL` (doubled for escaping)`my table`
SQLite""my table"

Dialect differences

FeaturePostgreSQLMySQLSQLite
BooleanBOOLEANTINYINT(1)INTEGER
FloatDOUBLE PRECISIONDOUBLEREAL
TimestampTIMESTAMPDATETIMETEXT
UUIDUUIDCHAR(36)TEXT
BytesBYTEABLOBBLOB
Referential actionsAll 5All 5Subset — SET DEFAULT may silently no-op
Named PK constraintCONSTRAINT pk PRIMARY KEYsameInline PRIMARY KEY only

Implementing a custom dialect

The Dialect trait lets you plug in your own SQL renderer:

use remodel_core::sql::dialect::Dialect;
use remodel_core::models::types::DataType;
use remodel_core::models::logical::ReferentialAction;

struct OracleDialect;

impl Dialect for OracleDialect {
    fn render_type(&self, ty: &DataType) -> String {
        match ty {
            DataType::Integer  => "NUMBER(10)".into(),
            DataType::BigInt   => "NUMBER(19)".into(),
            DataType::Boolean  => "NUMBER(1)".into(),
            DataType::Uuid     => "RAW(16)".into(),
            DataType::Bytes    => "BLOB".into(),
            DataType::Text     => "CLOB".into(),
            DataType::Varchar(n) => format!("VARCHAR2({n})"),
            DataType::Custom(s)  => s.clone(),
            _ => "VARCHAR2(255)".into(), // fallback
        }
    }

    fn quote_ident(&self, ident: &str) -> String {
        format!("\"{}\"", ident.to_uppercase())
    }
}

You can then use the trait object directly with the DDL renderer (advanced use):

use remodel_core::sql::ddl::render_ddl;

let ddl = render_ddl(&logical, &OracleDialect);

On this page