Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify Prisma Version #726

Merged
merged 20 commits into from May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions introspection-engine/connectors/introspection-connector/src/lib.rs
Expand Up @@ -25,12 +25,22 @@ pub struct DatabaseMetadata {
pub size_in_bytes: usize,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub enum Version {
NonPrisma,
Prisma1,
Prisma11,
Prisma2,
}
do4gr marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug)]
pub struct IntrospectionResult {
/// Datamodel
pub datamodel: Datamodel,
/// warnings
pub warnings: Vec<Warning>,
/// version
pub version: Version,
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down
@@ -1,28 +1,33 @@
use crate::commenting_out_guardrails::commenting_out_guardrails;
use crate::misc_helpers::*;
use crate::sanitize_datamodel_names::sanitize_datamodel_names;
use crate::version_checker::VersionChecker;
use crate::SqlIntrospectionResult;
use datamodel::{dml, Datamodel, FieldType, Model};
use introspection_connector::IntrospectionResult;
use quaint::connector::SqlFamily;
use sql_schema_describer::*;
use tracing::debug;

/// Calculate a data model from a database schema.
pub fn calculate_model(schema: &SqlSchema) -> SqlIntrospectionResult<IntrospectionResult> {
pub fn calculate_datamodel(schema: &SqlSchema, family: &SqlFamily) -> SqlIntrospectionResult<IntrospectionResult> {
debug!("Calculating data model.");

let mut version_check = VersionChecker::new(family.clone(), schema);

let mut data_model = Datamodel::new();
for table in schema
.tables
.iter()
.filter(|table| !is_migration_table(&table))
.filter(|table| !is_prisma_1_point_1_join_table(&table))
.filter(|table| !is_prisma_1_point_1_or_2_join_table(&table))
.filter(|table| !is_prisma_1_point_0_join_table(&table))
{
debug!("Calculating model: {}", table.name);
let mut model = Model::new(table.name.clone(), None);

for column in &table.columns {
version_check.uses_non_prisma_type(&column.tpe);
let field = calculate_scalar_field(&table, &column);
model.add_field(field);
}
Expand All @@ -36,6 +41,8 @@ pub fn calculate_model(schema: &SqlSchema) -> SqlIntrospectionResult<Introspecti
.iter()
.any(|c| matches!(model_copy.find_field(c).unwrap().field_type, FieldType::Unsupported(_)))
}) {
version_check.has_inline_relations(table);
version_check.uses_on_delete(foreign_key, table);
model.add_field(calculate_relation_field(schema, table, foreign_key));
}

Expand All @@ -51,6 +58,8 @@ pub fn calculate_model(schema: &SqlSchema) -> SqlIntrospectionResult<Introspecti
model.id_fields = table.primary_key_columns();
}

version_check.always_has_created_at_updated_at(table, &model);

data_model.add_model(model);
}

Expand Down Expand Up @@ -91,7 +100,7 @@ pub fn calculate_model(schema: &SqlSchema) -> SqlIntrospectionResult<Introspecti
for table in schema
.tables
.iter()
.filter(|table| is_prisma_1_point_1_join_table(&table) || is_prisma_1_point_0_join_table(&table))
.filter(|table| is_prisma_1_point_1_or_2_join_table(&table) || is_prisma_1_point_0_join_table(&table))
{
if let (Some(f), Some(s)) = (table.foreign_keys.get(0), table.foreign_keys.get(1)) {
let is_self_relation = f.referenced_table == s.referenced_table;
Expand Down Expand Up @@ -122,6 +131,7 @@ pub fn calculate_model(schema: &SqlSchema) -> SqlIntrospectionResult<Introspecti

Ok(IntrospectionResult {
datamodel: data_model,
version: version_check.version(&warnings),
warnings,
})
}
Expand Down
Expand Up @@ -4,6 +4,7 @@ mod error;
mod misc_helpers;
mod sanitize_datamodel_names;
mod schema_describer_loading;
mod version_checker;

use introspection_connector::{
ConnectorError, ConnectorResult, DatabaseMetadata, IntrospectionConnector, IntrospectionResult,
Expand Down Expand Up @@ -85,7 +86,9 @@ impl IntrospectionConnector for SqlIntrospectionConnector {
let sql_schema = self.catch(self.describe()).await?;
tracing::debug!("SQL Schema Describer is done: {:?}", sql_schema);

let introspection_result = calculate_datamodel::calculate_model(&sql_schema).unwrap();
let family = self.connection_info.sql_family();

let introspection_result = calculate_datamodel::calculate_datamodel(&sql_schema, &family).unwrap();
tracing::debug!("Calculating datamodel is done: {:?}", sql_schema);
Ok(introspection_result)
}
Expand Down
Expand Up @@ -13,7 +13,20 @@ pub fn is_migration_table(table: &Table) -> bool {
table.name == "_Migration"
}

pub(crate) fn is_prisma_1_point_1_join_table(table: &Table) -> bool {
pub(crate) fn is_relay_table(table: &Table) -> bool {
table.name == "_RelayId"
&& table.columns[0].name == "id"
&& table.columns[1].name.to_lowercase() == "stablemodelidentifier"
}
do4gr marked this conversation as resolved.
Show resolved Hide resolved

pub(crate) fn is_prisma_1_or_11_list_table(table: &Table) -> bool {
table.columns.len() == 3
&& table.columns[0].name.to_lowercase() == "nodeid"
&& table.columns[1].name == "position"
&& table.columns[2].name == "value"
}

pub(crate) fn is_prisma_1_point_1_or_2_join_table(table: &Table) -> bool {
table.columns.len() == 2 && table.indices.len() >= 2 && common_prisma_m_to_n_relation_conditions(table)
}

Expand Down
@@ -0,0 +1,169 @@
use crate::misc_helpers::{
is_migration_table, is_prisma_1_or_11_list_table, is_prisma_1_point_0_join_table,
is_prisma_1_point_1_or_2_join_table, is_relay_table,
};
use datamodel::Model;
use introspection_connector::{Version, Warning};
use quaint::connector::SqlFamily;
use sql_schema_describer::{ColumnType, ForeignKey, ForeignKeyAction, SqlSchema, Table};

pub struct VersionChecker {
sql_family: SqlFamily,
migration_table: bool,
has_prisma_1_join_table: bool,
has_prisma_1_1_or_2_join_table: bool,
uses_on_delete: bool,
always_has_created_at_updated_at: bool,
uses_non_prisma_types: bool,
has_inline_relations: bool,
}

const SQLITE_TYPES: [(&'static str, &'static str); 5] = [
("BOOLEAN", "BOOLEAN"),
("DATE", "DATE"),
("REAL", "REAL"),
("INTEGER", "INTEGER"),
("TEXT", "TEXT"),
];

const POSTGRES_TYPES: [(&'static str, &'static str); 5] = [
("boolean", "bool"),
("timestamp without time zone", "timestamp"),
("numeric", "numeric"),
("integer", "int4"),
("text", "text"),
];
const MYSQL_TYPES: [(&'static str, &'static str); 13] = [
("tinyint", "tinyint(1)"),
("datetime", "datetime(3)"),
("decimal", "decimal(65,30)"),
("int", "int"),
("int", "int(11)"),
("varchar", "varchar(191)"),
("char", "char(25)"),
("char", "char(36)"),
("varchar", "varchar(25)"),
("varchar", "varchar(36)"),
("text", "text"),
("mediumtext", "mediumtext"),
("int", "int(4)"),
];

impl VersionChecker {
pub fn new(sql_family: SqlFamily, schema: &SqlSchema) -> VersionChecker {
VersionChecker {
sql_family,
migration_table: schema.tables.iter().any(|table| is_migration_table(&table)),
has_prisma_1_join_table: schema.tables.iter().any(|table| is_prisma_1_point_0_join_table(&table)),
has_prisma_1_1_or_2_join_table: schema
.tables
.iter()
.any(|table| is_prisma_1_point_1_or_2_join_table(&table)),
uses_on_delete: false,
always_has_created_at_updated_at: true,
uses_non_prisma_types: false,
has_inline_relations: false,
}
}

pub fn uses_non_prisma_type(&mut self, tpe: &ColumnType) {
match (&tpe.data_type, &tpe.full_data_type, self.sql_family) {
(dt, fdt, SqlFamily::Postgres) if !POSTGRES_TYPES.contains(&(dt, fdt)) => self.uses_non_prisma_types = true,
(dt, fdt, SqlFamily::Mysql) if !MYSQL_TYPES.contains(&(dt, fdt)) => self.uses_non_prisma_types = true,
(dt, fdt, SqlFamily::Sqlite) if !SQLITE_TYPES.contains(&(dt, fdt)) => self.uses_non_prisma_types = true,
_ => (),
};
}

pub fn has_inline_relations(&mut self, table: &Table) {
if !is_prisma_1_or_11_list_table(table) {
self.has_inline_relations = true;
}
}

pub fn uses_on_delete(&mut self, fk: &ForeignKey, table: &Table) {
if !(fk.on_delete_action == ForeignKeyAction::NoAction || fk.on_delete_action == ForeignKeyAction::SetNull) {
if !is_prisma_1_or_11_list_table(table) && fk.on_delete_action != ForeignKeyAction::Cascade {
self.uses_on_delete = true
}
}
}

pub fn always_has_created_at_updated_at(&mut self, table: &Table, model: &Model) {
if !is_prisma_1_or_11_list_table(table) && !is_relay_table(table) && !model.has_created_at_and_updated_at() {
self.always_has_created_at_updated_at = false
}
}

pub fn version(&self, warnings: &Vec<Warning>) -> Version {
match self.sql_family {
SqlFamily::Sqlite
if self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& warnings.is_empty() =>
{
Version::Prisma2
}
SqlFamily::Sqlite => Version::NonPrisma,
SqlFamily::Mysql
if self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& warnings.is_empty() =>
{
Version::Prisma2
}
SqlFamily::Mysql
if !self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& self.always_has_created_at_updated_at
&& !self.has_prisma_1_1_or_2_join_table
&& !self.has_inline_relations
&& warnings.is_empty() =>
{
Version::Prisma1
}
SqlFamily::Mysql
if !self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& !self.has_prisma_1_join_table
&& warnings.is_empty() =>
{
Version::Prisma11
}
SqlFamily::Mysql => Version::NonPrisma,
SqlFamily::Postgres
if self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& warnings.is_empty() =>
{
Version::Prisma2
}
SqlFamily::Postgres
if !self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& self.always_has_created_at_updated_at
&& !self.has_prisma_1_join_table
&& !self.has_inline_relations
&& warnings.is_empty() =>
{
Version::Prisma1
}
SqlFamily::Postgres
if !self.migration_table
&& !self.uses_on_delete
&& !self.uses_non_prisma_types
&& !self.has_prisma_1_1_or_2_join_table
&& warnings.is_empty() =>
{
Version::Prisma11
}
SqlFamily::Postgres => Version::NonPrisma,
}
}
}