Skip to content

Commit

Permalink
Rest of the test, ME/IE + PSL lower
Browse files Browse the repository at this point in the history
  • Loading branch information
Julius de Bruijn committed Aug 16, 2021
1 parent 191e6de commit cbee5e3
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 27 deletions.
Expand Up @@ -5,6 +5,7 @@ use introspection_engine_tests::test_api::TestApi;
use introspection_engine_tests::test_api::*;

use introspection_engine_tests::TestResult;
use quaint::prelude::Queryable;
use test_macros::test_connector;

#[test_connector(preview_features("NamedConstraints"), tags(Mssql, Postgres))]
Expand Down Expand Up @@ -359,3 +360,45 @@ async fn introspecting_custom_fk_names_does_not_return_them_on_sqlite(api: &Test

Ok(())
}

#[test_connector(preview_features("NamedConstraints"), tags(Mssql))]
async fn introspecting_custom_default_names_should_output_to_dml(api: &TestApi) -> TestResult {
let create_table = format!(
"CREATE TABLE [{}].[custom_defaults_test] (id INT CONSTRAINT pk_meow PRIMARY KEY, data NVARCHAR(255) CONSTRAINT meow DEFAULT 'foo')",
api.schema_name()
);

api.database().raw_cmd(&create_table).await?;

let dm = r#"
model custom_defaults_test {
id Int @id(map: "pk_meow")
data String? @default("foo", map: "meow") @db.NVarChar(255)
}
"#;

api.assert_eq_datamodels(&dm, &api.introspect().await?);

Ok(())
}

#[test_connector(preview_features("NamedConstraints"), tags(Mssql))]
async fn introspecting_default_default_names_should_not_output_to_dml(api: &TestApi) -> TestResult {
let create_table = format!(
"CREATE TABLE [{}].[custom_defaults_test] (id INT CONSTRAINT pk_meow PRIMARY KEY, data NVARCHAR(255) CONSTRAINT custom_defaults_test_data_df DEFAULT 'foo')",
api.schema_name()
);

api.database().raw_cmd(&create_table).await?;

let dm = indoc! {r#"
model custom_defaults_test {
id Int @id(map: "pk_meow")
data String? @default("foo") @db.NVarChar(255)
}
"#};

api.assert_eq_datamodels(&dm, &api.introspect().await?);

Ok(())
}
5 changes: 5 additions & 0 deletions libs/datamodel/connectors/dml/src/default_value.rs
Expand Up @@ -117,6 +117,11 @@ impl DefaultValue {
_ => None,
}
}

/// The default value constraint name.
pub fn db_name(&self) -> Option<&str> {
self.db_name.as_ref().map(|s| s.as_str())
}
}

#[derive(Clone)]
Expand Down
7 changes: 6 additions & 1 deletion libs/datamodel/connectors/dml/src/field.rs
Expand Up @@ -431,6 +431,7 @@ impl ScalarField {
is_ignored: false,
}
}

/// Creates a new field with the given name and type, marked as generated and optional.
pub fn new_generated(name: &str, field_type: FieldType) -> ScalarField {
let mut field = Self::new(name, FieldArity::Optional, field_type);
Expand Down Expand Up @@ -461,9 +462,13 @@ impl ScalarField {
}

pub fn is_auto_increment(&self) -> bool {
let kind = self.default_value.as_ref().map(|val| &val.kind);
let kind = self.default_value().map(|val| &val.kind);
matches!(kind, Some(DefaultKind::Expression(ref expr)) if expr == &ValueGenerator::new_autoincrement())
}

pub fn default_value(&self) -> Option<&DefaultValue> {
self.default_value.as_ref()
}
}

impl WithName for ScalarField {
Expand Down
4 changes: 4 additions & 0 deletions libs/datamodel/core/src/ast/attribute.rs
Expand Up @@ -23,6 +23,10 @@ impl Attribute {
pub fn is_index(&self) -> bool {
matches!(self.name.name.as_str(), "index" | "unique")
}

pub fn is_id(&self) -> bool {
matches!(self.name.name.as_str(), "id")
}
}

impl WithIdentifier for Attribute {
Expand Down
10 changes: 10 additions & 0 deletions libs/datamodel/core/src/ast/model.rs
Expand Up @@ -52,6 +52,16 @@ impl Model {
pub(crate) fn find_field_bang(&self, name: &str) -> &Field {
self.find_field(name).unwrap()
}

pub(crate) fn id_attribute(&self) -> &Attribute {
let from_model = self.attributes().iter().find(|attr| attr.is_id());

let mut from_field = self
.iter_fields()
.flat_map(|(_, field)| field.attributes().iter().find(|attr| attr.is_id()));

from_model.or_else(|| from_field.next()).unwrap()
}
}

impl WithIdentifier for Model {
Expand Down
68 changes: 60 additions & 8 deletions libs/datamodel/core/src/transform/ast_to_dml/validate.rs
@@ -1,16 +1,19 @@
#![allow(clippy::suspicious_operation_groupings)] // clippy is wrong there
use crate::common::constraint_names::ConstraintNames;
use crate::PreviewFeature::NamedConstraints;

mod names;

use crate::{
ast::{self, Span},
common::preview_features::PreviewFeature,
common::{constraint_names::ConstraintNames, preview_features::PreviewFeature},
configuration,
diagnostics::{DatamodelError, Diagnostics},
dml,
PreviewFeature::NamedConstraints,
};
use ::dml::{datamodel::Datamodel, field::RelationField, model::Model, traits::WithName};
use datamodel_connector::ConnectorCapability;
use enumflags2::BitFlags;
use names::NamesValidator;
use std::collections::HashSet;

/// Helper for validating a datamodel.
Expand Down Expand Up @@ -39,7 +42,7 @@ impl<'a> Validator<'a> {
}
}

pub(crate) fn validate(&self, ast: &ast::SchemaAst, schema: &mut dml::Datamodel, diagnostics: &mut Diagnostics) {
pub(crate) fn validate(&self, ast: &ast::SchemaAst, schema: &dml::Datamodel, diagnostics: &mut Diagnostics) {
for model in schema.models() {
let ast_model = ast.find_model(&model.name).expect(STATE_ERROR);

Expand Down Expand Up @@ -107,17 +110,51 @@ impl<'a> Validator<'a> {
}
}

pub fn post_standardisation_validate(
pub(crate) fn post_standardisation_validate(
&self,
ast_schema: &ast::SchemaAst,
schema: &mut dml::Datamodel,
schema: &dml::Datamodel,
diagnostics: &mut Diagnostics,
) {
let constraint_names = NamesValidator::new(&schema, self.preview_features, self.source);

for model in schema.models() {
let ast_model = ast_schema.find_model(&model.name).expect(STATE_ERROR);

if let Some(pk) = &model.primary_key {
if let Some(name) = &pk.db_name {
// Only for SQL Server for now...
if constraint_names.is_duplicate(name) {
let span = ast_model.id_attribute().span;
let message = "Given constraint name is already in use in the data model.";
let error = DatamodelError::new_attribute_validation_error(message, "default", span);

diagnostics.push_error(error);
}
}
}

// TODO: Extend this check for other constraints. Now only used
// for SQL Server default constraint names.
for field in model.fields().filter(|f| f.is_scalar_field()) {
if let Some(name) = field.default_value().and_then(|d| d.db_name()) {
let ast_field = ast_model.find_field_bang(field.name());

if constraint_names.is_duplicate(name) {
let message = "Given constraint name is already in use in the data model.";
let span = ast_field.span_for_argument("default", "map");
let error = DatamodelError::new_attribute_validation_error(message, "default", span);

diagnostics.push_error(error);
}
}
}

let mut new_errors = self.validate_relation_arguments_bla(
schema,
ast_schema.find_model(&model.name).expect(STATE_ERROR),
model,
&constraint_names,
);

diagnostics.append(&mut new_errors);
Expand Down Expand Up @@ -544,11 +581,12 @@ impl<'a> Validator<'a> {
}
}

fn validate_relation_arguments_bla(
fn validate_relation_arguments_bla<'dml>(
&self,
datamodel: &dml::Datamodel,
datamodel: &'dml dml::Datamodel,
ast_model: &ast::Model,
model: &dml::Model,
constraint_names: &NamesValidator<'dml>,
) -> Diagnostics {
let mut errors = Diagnostics::new();

Expand All @@ -563,6 +601,20 @@ impl<'a> Validator<'a> {
let rel_info = &field.relation_info;
let related_model = datamodel.find_model(&rel_info.to).expect(STATE_ERROR);

if let Some(name) = field.relation_info.fk_name.as_ref() {
// Only for SQL Server for now...
if constraint_names.is_duplicate(name) {
let span = ast_field
.map(|f| f.span_for_argument("relation", "map"))
.unwrap_or_else(ast::Span::empty);

let message = "Given constraint name is already in use in the data model.";
let error = DatamodelError::new_attribute_validation_error(message, RELATION_ATTRIBUTE_NAME, span);

errors.push_error(error);
}
}

if let Some((_rel_field_idx, related_field)) = datamodel.find_related_field(field) {
let related_field_rel_info = &related_field.relation_info;

Expand Down
Expand Up @@ -80,7 +80,7 @@ impl<'a, 'b> ValidationPipeline<'a> {

// Phase 6: Post Standardisation Validation
self.validator
.post_standardisation_validate(ast_schema, &mut schema, &mut diagnostics);
.post_standardisation_validate(ast_schema, &schema, &mut diagnostics);

diagnostics.to_result()?;

Expand Down
42 changes: 31 additions & 11 deletions libs/datamodel/core/src/transform/dml_to_ast/lower_field.rs
Expand Up @@ -7,6 +7,8 @@ use crate::{
ast::{self, Attribute, Span},
dml, Datasource, Field, Ignorable,
};
use ::dml::traits::WithName;
use datamodel_connector::{Connector, EmptyDatamodelConnector};
use prisma_value::PrismaValue;

impl<'a> LowerDmlToAst<'a> {
Expand Down Expand Up @@ -109,13 +111,27 @@ impl<'a> LowerDmlToAst<'a> {

// @default
if let Some(default_value) = field.default_value() {
attributes.push(ast::Attribute::new(
"default",
vec![ast::Argument::new(
"",
LowerDmlToAst::<'a>::lower_default_value(default_value.clone()),
)],
));
let mut args = vec![ast::Argument::new(
"",
LowerDmlToAst::<'a>::lower_default_value(default_value.clone()),
)];

if self.preview_features.contains(PreviewFeature::NamedConstraints) {
let connector = self
.datasource
.map(|source| source.active_connector.as_ref())
.unwrap_or(&EmptyDatamodelConnector as &dyn Connector);

let prisma_default = ConstraintNames::default_name(model.name(), field.name(), connector);

if let Some(name) = default_value.db_name() {
if name != &prisma_default {
args.push(ast::Argument::new("map", Self::lower_string(name)))
}
}
}

attributes.push(ast::Attribute::new("default", args));
}

// @updatedAt
Expand Down Expand Up @@ -224,19 +240,23 @@ impl<'a> LowerDmlToAst<'a> {
}
}

pub fn lower_string(s: impl ToString) -> ast::Expression {
ast::Expression::StringValue(s.to_string(), ast::Span::empty())
}

pub fn lower_prisma_value(pv: &PrismaValue) -> ast::Expression {
match pv {
PrismaValue::Boolean(true) => ast::Expression::BooleanValue(String::from("true"), ast::Span::empty()),
PrismaValue::Boolean(false) => ast::Expression::BooleanValue(String::from("false"), ast::Span::empty()),
PrismaValue::String(value) => ast::Expression::StringValue(value.clone(), ast::Span::empty()),
PrismaValue::String(value) => Self::lower_string(value),
PrismaValue::Enum(value) => ast::Expression::ConstantValue(value.clone(), ast::Span::empty()),
PrismaValue::DateTime(value) => ast::Expression::StringValue(value.to_rfc3339(), ast::Span::empty()),
PrismaValue::DateTime(value) => Self::lower_string(value),
PrismaValue::Float(value) => ast::Expression::NumericValue(value.to_string(), ast::Span::empty()),
PrismaValue::Int(value) => ast::Expression::NumericValue(value.to_string(), ast::Span::empty()),
PrismaValue::BigInt(value) => ast::Expression::NumericValue(value.to_string(), ast::Span::empty()),
PrismaValue::Null => ast::Expression::ConstantValue("null".to_string(), ast::Span::empty()),
PrismaValue::Uuid(val) => ast::Expression::StringValue(val.to_string(), ast::Span::empty()),
PrismaValue::Json(val) => ast::Expression::StringValue(val.to_string(), ast::Span::empty()),
PrismaValue::Uuid(val) => Self::lower_string(val),
PrismaValue::Json(val) => Self::lower_string(val),
PrismaValue::List(vec) => ast::Expression::Array(
vec.iter()
.map(|pv| LowerDmlToAst::<'a>::lower_prisma_value(pv))
Expand Down
45 changes: 39 additions & 6 deletions libs/datamodel/core/tests/attributes/default_negative.rs
Expand Up @@ -427,7 +427,18 @@ fn named_default_constraints_cannot_have_duplicate_names() {
let error = datamodel::parse_schema(dml).map(drop).unwrap_err();

let expectation = expect![[r#"
TODO: Talk with Matthias on Monday!
error: Error parsing attribute "@default": Given constraint name is already in use in the data model.
--> schema.prisma:13
 | 
12 |  id Int @id @default(autoincrement())
13 |  a String @default("asdf", map: "reserved")
 | 
error: Error parsing attribute "@default": Given constraint name is already in use in the data model.
--> schema.prisma:18
 | 
17 |  id Int @id @default(autoincrement())
18 |  b String @default("asdf", map: "reserved")
 | 
"#]];

expectation.assert_eq(&error)
Expand Down Expand Up @@ -459,7 +470,18 @@ fn named_default_constraints_cannot_clash_with_pk_names() {
let error = datamodel::parse_schema(dml).map(drop).unwrap_err();

let expectation = expect![[r#"
TODO: Talk with Matthias on Monday!
error: Error parsing attribute "@default": Given constraint name is already in use in the data model.
--> schema.prisma:13
 | 
12 |  id Int @id @default(autoincrement())
13 |  a String @default("asdf", map: "reserved")
 | 
error: Error parsing attribute "@default": Given constraint name is already in use in the data model.
--> schema.prisma:17
 | 
16 | model B {
17 |  id Int @id(map: "reserved") @default(autoincrement())
 | 
"#]];

expectation.assert_eq(&error)
Expand All @@ -480,21 +502,32 @@ fn named_default_constraints_cannot_clash_with_fk_names() {
model A {
id Int @id @default(autoincrement())
a String @default("asdf", map: "reserved")
b B @relation(fields: [bId], references: [id], map: "name")
a String @default("asdf", map: "reserved")
b B @relation(fields: [bId], references: [id], map: "reserved")
bId Int
}
model B {
id Int @id(map: "reserved") @default(autoincrement())
id Int @id @default(autoincrement())
as A[]
}
"#};

let error = datamodel::parse_schema(dml).map(drop).unwrap_err();

let expectation = expect![[r#"
TODO: Talk with Matthias on Monday!
error: Error parsing attribute "@default": Given constraint name is already in use in the data model.
--> schema.prisma:13
 | 
12 |  id Int @id @default(autoincrement())
13 |  a String @default("asdf", map: "reserved")
 | 
error: Error parsing attribute "@relation": Given constraint name is already in use in the data model.
--> schema.prisma:14
 | 
13 |  a String @default("asdf", map: "reserved")
14 |  b B @relation(fields: [bId], references: [id], map: "reserved")
 | 
"#]];

expectation.assert_eq(&error)
Expand Down

0 comments on commit cbee5e3

Please sign in to comment.