Skip to content

Commit

Permalink
Merge pull request prisma#4832 from prisma/feature/rust-introspection…
Browse files Browse the repository at this point in the history
…-model

Implement generating of data models in Rust
  • Loading branch information
aknuds1 authored Aug 23, 2019
2 parents 545e484 + 42ea842 commit a8ac3b5
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 2 deletions.
12 changes: 12 additions & 0 deletions server/prisma-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/prisma-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"libs/datamodel",
"libs/prisma-inflector",
"libs/database-introspection",
"libs/introspection-command",
"libs/logger",
]

Expand Down
23 changes: 23 additions & 0 deletions server/prisma-rs/libs/database-introspection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use failure::Fail;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt;

pub mod mysql;
pub mod postgres;
Expand Down Expand Up @@ -142,7 +143,9 @@ impl Table {
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum IndexType {
/// Unique type.
Unique,
/// Normal type.
Normal,
}

Expand Down Expand Up @@ -257,6 +260,26 @@ pub enum ColumnTypeFamily {
TransactionId,
}

impl fmt::Display for ColumnTypeFamily {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str = match self {
Self::Int => "int",
Self::Float => "float",
Self::Boolean => "boolean",
Self::String => "string",
Self::DateTime => "dateTime",
Self::Binary => "binary",
Self::Json => "json",
Self::Uuid => "uuid",
Self::Geometric => "geometric",
Self::LogSequenceNumber => "logSequenceNumber",
Self::TextSearch => "textSearch",
Self::TransactionId => "transactionId",
};
write!(f, "{}", str)
}
}

/// A column's arity.
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn setup() {
return;
}

let log_level = match std::env::var("RUST_LOG")
let log_level = match std::env::var("TEST_LOG")
.unwrap_or("warn".to_string())
.to_lowercase()
.as_ref()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn setup() {
return;
}

let log_level = match std::env::var("RUST_LOG")
let log_level = match std::env::var("TEST_LOG")
.unwrap_or("warn".to_string())
.to_lowercase()
.as_ref()
Expand Down
15 changes: 15 additions & 0 deletions server/prisma-rs/libs/introspection-command/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "introspection-command"
version = "0.1.0"
authors = ["Arve Knudsen <arve.knudsen@gmail.com>"]
edition = "2018"

[dependencies]
database-introspection = { path = "../../libs/database-introspection" }
failure = "0.1"
datamodel = { path = "../../libs/datamodel" }
log = "0.4"

[dev-dependencies]
fern = "0.5"
pretty_assertions = "0.6"
10 changes: 10 additions & 0 deletions server/prisma-rs/libs/introspection-command/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Prisma Data Model v2 Generator from Introspection Results

Language: Rust

Build System: Cargo

## Overview

This package is responsible for generating a Prisma data model from a set of introspection
results, as obtained via the database-introspection package.
54 changes: 54 additions & 0 deletions server/prisma-rs/libs/introspection-command/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![warn(missing_docs)]
//! Logic for generating Prisma data models from database introspection.
use database_introspection::{ColumnArity, ColumnTypeFamily, DatabaseSchema};
use datamodel::{common::PrismaType, Datamodel, Field, FieldArity, FieldType, Model};
use failure::Error;
use log::debug;

/// The result type.
pub type Result<T> = core::result::Result<T, Error>;

/// Calculate a data model from a database schema.
pub fn calculate_model(schema: &DatabaseSchema) -> Result<Datamodel> {
debug!("Calculating data model");

let mut data_model = Datamodel::new();
for table in schema.tables.iter() {
let mut model = Model::new(&table.name);
for column in table.columns.iter() {
debug!("Handling column {:?}", column);
let field_type = match column.tpe.family {
ColumnTypeFamily::Boolean => FieldType::Base(PrismaType::Boolean),
ColumnTypeFamily::DateTime => FieldType::Base(PrismaType::DateTime),
ColumnTypeFamily::Float => FieldType::Base(PrismaType::Float),
ColumnTypeFamily::Int => FieldType::Base(PrismaType::Int),
ColumnTypeFamily::String => FieldType::Base(PrismaType::String),
// XXX: We made a conscious decision to punt on mapping of ColumnTypeFamily
// variants that don't yet have corresponding PrismaType variants
_ => FieldType::Base(PrismaType::String),
};
let arity = match column.arity {
ColumnArity::Required => FieldArity::Required,
ColumnArity::Nullable => FieldArity::Optional,
ColumnArity::List => FieldArity::List,
};
let field = Field {
name: column.name.clone(),
arity,
field_type,
database_name: None,
default_value: None,
is_unique: false,
id_info: None,
scalar_list_strategy: None,
documentation: None,
is_generated: false,
is_updated_at: false,
};
model.add_field(field);
}
data_model.add_model(model);
}
Ok(data_model)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use database_introspection::*;
use datamodel::{common::PrismaType, Datamodel, Field, FieldArity, FieldType, Model};
use introspection_command::calculate_model;
use log::LevelFilter;
use pretty_assertions::assert_eq;
use std::sync::atomic::{AtomicBool, Ordering};

static IS_SETUP: AtomicBool = AtomicBool::new(false);

fn setup() {
let is_setup = IS_SETUP.load(Ordering::Relaxed);
if is_setup {
return;
}

let log_level = match std::env::var("TEST_LOG")
.unwrap_or("warn".to_string())
.to_lowercase()
.as_ref()
{
"trace" => LevelFilter::Trace,
"debug" => LevelFilter::Debug,
"info" => LevelFilter::Info,
"warn" => LevelFilter::Warn,
"error" => LevelFilter::Error,
_ => LevelFilter::Warn,
};
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!("[{}][{}] {}", record.target(), record.level(), message))
})
.level(log_level)
.chain(std::io::stdout())
.apply()
.expect("fern configuration");

IS_SETUP.store(true, Ordering::Relaxed);
}

#[test]
fn a_data_model_can_be_generated_from_a_schema() {
setup();

let col_types = vec![
ColumnTypeFamily::Int,
ColumnTypeFamily::Float,
ColumnTypeFamily::Boolean,
ColumnTypeFamily::String,
ColumnTypeFamily::DateTime,
ColumnTypeFamily::Binary,
ColumnTypeFamily::Json,
ColumnTypeFamily::Uuid,
ColumnTypeFamily::Geometric,
ColumnTypeFamily::LogSequenceNumber,
ColumnTypeFamily::TextSearch,
ColumnTypeFamily::TransactionId,
];

let ref_data_model = Datamodel {
models: vec![Model {
database_name: None,
name: "Table1".to_string(),
documentation: None,
is_embedded: false,
is_generated: false,
fields: col_types
.iter()
.map(|col_type| {
let field_type = match col_type {
ColumnTypeFamily::Boolean => FieldType::Base(PrismaType::Boolean),
ColumnTypeFamily::DateTime => FieldType::Base(PrismaType::DateTime),
ColumnTypeFamily::Float => FieldType::Base(PrismaType::Float),
ColumnTypeFamily::Int => FieldType::Base(PrismaType::Int),
ColumnTypeFamily::String => FieldType::Base(PrismaType::String),
// XXX: We made a conscious decision to punt on mapping of ColumnTypeFamily
// variants that don't yet have corresponding PrismaType variants
_ => FieldType::Base(PrismaType::String),
};
Field {
name: col_type.to_string(),
arity: FieldArity::Optional,
field_type,
database_name: None,
default_value: None,
is_unique: false,
id_info: None,
scalar_list_strategy: None,
documentation: None,
is_generated: false,
is_updated_at: false,
}
})
.collect(),
}],
enums: vec![],
};

let schema = DatabaseSchema {
tables: vec![Table {
name: "Table1".to_string(),
columns: col_types
.iter()
.map(|family| Column {
name: family.to_string(),
tpe: ColumnType {
raw: "raw".to_string(),
family: family.to_owned(),
},
arity: ColumnArity::Nullable,
default: None,
auto_increment: false,
})
.collect(),
indices: vec![],
primary_key: Some(PrimaryKey {
columns: vec!["primary_col".to_string()],
}),
foreign_keys: vec![],
}],
enums: vec![],
sequences: vec![],
};
let data_model = calculate_model(&schema).expect("calculate data model");

assert_eq!(data_model, ref_data_model);
}

0 comments on commit a8ac3b5

Please sign in to comment.