Skip to content

Commit

Permalink
datamodel: Simplify reformatter
Browse files Browse the repository at this point in the history
- Lift without validating: we don't return errors, so we just have to
  make sure we can produce DML.
  - In the future, we want to push DML out of the picture and
    progressively move the relation fixing logic to code actions. In the
    meantime, the relation fixing could be done more safely and easily
    without lifting with the parser db.
  - Consequence: we can remove conditional validation 🎉
- Do not assume the reformatter can return errors. It can't, and it's
  not its role.
  • Loading branch information
tomhoule committed Jan 11, 2022
1 parent 154f494 commit 6572bbe
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 128 deletions.
150 changes: 58 additions & 92 deletions libs/datamodel/core/src/ast/reformat/reformatter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::helpers::*;
use crate::{Datasource, ValidatedDatamodel, ValidatedMissingFields};
use crate::{Datamodel, Datasource};
use enumflags2::BitFlags;
use pest::{iterators::Pair, Parser};
use schema_ast::{
Expand All @@ -10,33 +10,23 @@ use schema_ast::{

pub struct Reformatter<'a> {
input: &'a str,
missing_fields: Result<ValidatedMissingFields, crate::diagnostics::Diagnostics>,
missing_field_attributes: Result<Vec<MissingFieldAttribute>, crate::diagnostics::Diagnostics>,
missing_relation_attribute_args: Result<Vec<MissingRelationAttributeArg>, crate::diagnostics::Diagnostics>,
missing_fields: Vec<MissingField>,
missing_field_attributes: Vec<MissingFieldAttribute>,
missing_relation_attribute_args: Vec<MissingRelationAttributeArg>,
}

impl<'a> Reformatter<'a> {
pub fn new(input: &'a str) -> Self {
match (
crate::parse_schema_ast(input),
crate::parse_datamodel_for_formatter(input),
) {
(Ok(schema_ast), Ok(validated_datamodel)) => {
let datasource = crate::parse_configuration(input)
.ok()
.and_then(|mut config| config.subject.datasources.pop());

let missing_fields =
Self::find_all_missing_fields(&schema_ast, &validated_datamodel, datasource.as_ref());

let info = crate::parse_schema_ast(input)
.and_then(|ast| crate::parse_datamodel_for_formatter(&ast).map(|datamodel| (ast, datamodel)));
match info {
Ok((schema_ast, (datamodel, mut datasources))) => {
let datasource = datasources.pop();
let missing_fields = Self::find_all_missing_fields(&schema_ast, &datamodel, datasource.as_ref());
let missing_field_attributes =
Self::find_all_missing_attributes(&schema_ast, &validated_datamodel, datasource.as_ref());

let missing_relation_attribute_args = Self::find_all_missing_relation_attribute_args(
&schema_ast,
&validated_datamodel,
datasource.as_ref(),
);
Self::find_all_missing_attributes(&schema_ast, &datamodel, datasource.as_ref());
let missing_relation_attribute_args =
Self::find_all_missing_relation_attribute_args(&schema_ast, &datamodel, datasource.as_ref());

Reformatter {
input,
Expand All @@ -45,40 +35,30 @@ impl<'a> Reformatter<'a> {
missing_relation_attribute_args,
}
}
(Err(diagnostics), _) => Reformatter {
input,
missing_field_attributes: Err(diagnostics.clone()),
missing_relation_attribute_args: Err(diagnostics.clone()),
missing_fields: Err(diagnostics),
},

(Ok(_), Err(diagnostics)) => Reformatter {
_ => Reformatter {
input,
missing_field_attributes: Err(diagnostics.clone()),
missing_relation_attribute_args: Err(diagnostics.clone()),
missing_fields: Err(diagnostics),
missing_field_attributes: Vec::new(),
missing_relation_attribute_args: Vec::new(),
missing_fields: Vec::new(),
},
}
}

// this finds all auto generated fields, that are added during auto generation AND are missing from the original input.
fn find_all_missing_fields(
schema_ast: &SchemaAst,
validated_datamodel: &ValidatedDatamodel,
datamodel: &Datamodel,
datasource: Option<&Datasource>,
) -> Result<ValidatedMissingFields, crate::diagnostics::Diagnostics> {
let mut diagnostics = crate::diagnostics::Diagnostics::new();
) -> Vec<MissingField> {
let lowerer = crate::transform::dml_to_ast::LowerDmlToAst::new(datasource, BitFlags::empty());
let mut result = Vec::new();

diagnostics.append_warning_vec(validated_datamodel.warnings.clone());

for model in validated_datamodel.subject.models() {
for model in datamodel.models() {
let ast_model = schema_ast.find_model(&model.name).unwrap();

for field in model.fields() {
if !ast_model.fields.iter().any(|f| f.name.name == field.name()) {
let ast_field = lowerer.lower_field(model, field, &validated_datamodel.subject);
let ast_field = lowerer.lower_field(model, field, datamodel);

result.push(MissingField {
model: model.name.clone(),
Expand All @@ -88,27 +68,21 @@ impl<'a> Reformatter<'a> {
}
}

Ok(ValidatedMissingFields {
subject: result,
warnings: diagnostics.warnings().to_owned(),
})
result
}

fn find_all_missing_attributes(
schema_ast: &SchemaAst,
validated_datamodel: &ValidatedDatamodel,
datamodel: &Datamodel,
datasource: Option<&Datasource>,
) -> Result<Vec<MissingFieldAttribute>, crate::diagnostics::Diagnostics> {
let mut diagnostics = crate::diagnostics::Diagnostics::new();

diagnostics.append_warning_vec(validated_datamodel.warnings.clone());
) -> Vec<MissingFieldAttribute> {
let lowerer = crate::transform::dml_to_ast::LowerDmlToAst::new(datasource, BitFlags::empty());

let mut missing_field_attributes = Vec::new();
for model in validated_datamodel.subject.models() {
for model in datamodel.models() {
let ast_model = schema_ast.find_model(&model.name).unwrap();
for field in model.fields() {
let new_ast_field = lowerer.lower_field(model, field, &validated_datamodel.subject);
let new_ast_field = lowerer.lower_field(model, field, datamodel);

if let Some(original_field) = ast_model.fields.iter().find(|f| f.name.name == field.name()) {
for attribute in new_ast_field.attributes {
Expand All @@ -127,24 +101,22 @@ impl<'a> Reformatter<'a> {
}
}
}
Ok(missing_field_attributes)

missing_field_attributes
}

fn find_all_missing_relation_attribute_args(
schema_ast: &SchemaAst,
validated_datamodel: &ValidatedDatamodel,
datamodel: &Datamodel,
datasource: Option<&Datasource>,
) -> Result<Vec<MissingRelationAttributeArg>, crate::diagnostics::Diagnostics> {
let mut diagnostics = crate::diagnostics::Diagnostics::new();

diagnostics.append_warning_vec(validated_datamodel.warnings.clone());
) -> Vec<MissingRelationAttributeArg> {
let lowerer = crate::transform::dml_to_ast::LowerDmlToAst::new(datasource, BitFlags::empty());

let mut missing_relation_attribute_args = Vec::new();
for model in validated_datamodel.subject.models() {
for model in datamodel.models() {
let ast_model = schema_ast.find_model(&model.name).unwrap();
for field in model.fields() {
let new_ast_field = lowerer.lower_field(model, field, &validated_datamodel.subject);
let new_ast_field = lowerer.lower_field(model, field, datamodel);

if let Some(original_field) = ast_model.fields.iter().find(|f| f.name.name == field.name()) {
for attribute in new_ast_field.attributes.iter().filter(|a| a.name.name == "relation") {
Expand Down Expand Up @@ -174,7 +146,7 @@ impl<'a> Reformatter<'a> {
}
}
}
Ok(missing_relation_attribute_args)
missing_relation_attribute_args
}

pub fn reformat_to(&self, output: &mut dyn std::io::Write, ident_width: usize) {
Expand All @@ -187,7 +159,10 @@ impl<'a> Reformatter<'a> {
}

fn reformat_internal(&self, ident_width: usize) -> String {
let mut ast = PrismaDatamodelParser::parse(Rule::schema, self.input).unwrap(); // TODO: Handle error.
let mut ast = match PrismaDatamodelParser::parse(Rule::schema, self.input) {
Ok(ast) => ast,
Err(_) => return self.input.to_owned(),
};
let mut target_string = String::with_capacity(self.input.len());
let mut renderer = Renderer::new(&mut target_string, ident_width);
self.reformat_top(&mut renderer, &ast.next().unwrap());
Expand Down Expand Up @@ -332,12 +307,9 @@ impl<'a> Reformatter<'a> {
}
}),
&(|table, _, model_name| {
// TODO: what is the right thing to do on error?
if let Ok(missing_fields) = self.missing_fields.as_ref() {
for missing_back_relation_field in missing_fields.subject.iter() {
if missing_back_relation_field.model.as_str() == model_name {
Renderer::render_field(table, &missing_back_relation_field.field, false);
}
for missing_back_relation_field in &self.missing_fields {
if missing_back_relation_field.model.as_str() == model_name {
Renderer::render_field(table, &missing_back_relation_field.field, false);
}
}
}),
Expand Down Expand Up @@ -556,23 +528,19 @@ impl<'a> Reformatter<'a> {
Rule::field_type => {
target.write(&Self::reformat_field_type(&current));
}
//todo special case field attribute to pass model and field name and probably attribute name and down to the args
Rule::attribute => {
if let Ok(missing_relation_attribute_args) = self.missing_relation_attribute_args.as_ref() {
let missing_relation_args: Vec<&MissingRelationAttributeArg> = missing_relation_attribute_args
.iter()
.filter(|arg| arg.model == model_name && arg.field == *field_name)
.collect();

Self::reformat_attribute(
&mut target.column_locked_writer_for(2),
&current,
"@",
missing_relation_args,
)
} else {
Self::reformat_attribute(&mut target.column_locked_writer_for(2), &current, "@", vec![])
}
let missing_relation_args: Vec<&MissingRelationAttributeArg> = self
.missing_relation_attribute_args
.iter()
.filter(|arg| arg.model == model_name && arg.field == *field_name)
.collect();

Self::reformat_attribute(
&mut target.column_locked_writer_for(2),
&current,
"@",
missing_relation_args,
)
}
// This is a comment at the end of a field.
Rule::doc_comment | Rule::comment => target.append_suffix_to_current_row(current.as_str()),
Expand All @@ -583,14 +551,12 @@ impl<'a> Reformatter<'a> {
}
}

if let Ok(missing_field_attributes) = self.missing_field_attributes.as_ref() {
for missing_field_attribute in missing_field_attributes.iter() {
if missing_field_attribute.field == field_name && missing_field_attribute.model.as_str() == model_name {
Renderer::render_field_attribute(
&mut target.column_locked_writer_for(2),
&missing_field_attribute.attribute,
)
}
for missing_field_attribute in &self.missing_field_attributes {
if missing_field_attribute.field == field_name && missing_field_attribute.model.as_str() == model_name {
Renderer::render_field_attribute(
&mut target.column_locked_writer_for(2),
&missing_field_attribute.attribute,
)
}
}

Expand Down
28 changes: 16 additions & 12 deletions libs/datamodel/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ pub use parser_database::is_reserved_type_name;
pub use schema_ast;

use crate::{ast::SchemaAst, common::preview_features::PreviewFeature};
use ast::reformat::MissingField;
use diagnostics::{Diagnostics, Validated};
use enumflags2::BitFlags;
use transform::{
Expand All @@ -101,34 +100,40 @@ use transform::{

pub type ValidatedDatamodel = Validated<Datamodel>;
pub type ValidatedConfiguration = Validated<Configuration>;
pub type ValidatedMissingFields = Validated<Vec<MissingField>>;

/// Parse and validate the whole schema
pub fn parse_schema(schema_str: &str) -> Result<(Configuration, Datamodel), String> {
parse_datamodel_internal(schema_str, false)
parse_datamodel_internal(schema_str)
.map_err(|err| err.to_pretty_string("schema.prisma", schema_str))
.map(|v| v.subject)
}

/// Parses and validates a datamodel string, using core attributes only.
pub fn parse_datamodel(datamodel_string: &str) -> Result<ValidatedDatamodel, diagnostics::Diagnostics> {
parse_datamodel_internal(datamodel_string, false).map(|validated| Validated {
parse_datamodel_internal(datamodel_string).map(|validated| Validated {
subject: validated.subject.1,
warnings: validated.warnings,
})
}

pub fn parse_datamodel_for_formatter(datamodel_string: &str) -> Result<ValidatedDatamodel, diagnostics::Diagnostics> {
parse_datamodel_internal(datamodel_string, true).map(|validated| Validated {
subject: validated.subject.1,
warnings: validated.warnings,
})
fn parse_datamodel_for_formatter(ast: &SchemaAst) -> Result<(Datamodel, Vec<Datasource>), Diagnostics> {
let mut diagnostics = diagnostics::Diagnostics::new();
let datasources = load_sources(ast, Default::default(), &mut diagnostics);
let (db, diagnostics) = parser_database::ParserDatabase::new(ast, diagnostics);
diagnostics.to_result()?;
let (connector, referential_integrity) = datasources
.get(0)
.map(|ds| (ds.active_connector, ds.referential_integrity()))
.unwrap_or((&datamodel_connector::EmptyDatamodelConnector, Default::default()));

let datamodel = transform::ast_to_dml::LiftAstToDml::new(&db, connector, referential_integrity).lift();
Ok((datamodel, datasources))
}

/// Parses and validates a datamodel string, using core attributes only.
/// In case of an error, a pretty, colorful string is returned.
pub fn parse_datamodel_or_pretty_error(datamodel_string: &str, file_name: &str) -> Result<ValidatedDatamodel, String> {
parse_datamodel_internal(datamodel_string, false)
parse_datamodel_internal(datamodel_string)
.map_err(|err| err.to_pretty_string(file_name, datamodel_string))
.map(|validated| Validated {
subject: validated.subject.1,
Expand All @@ -138,7 +143,6 @@ pub fn parse_datamodel_or_pretty_error(datamodel_string: &str, file_name: &str)

fn parse_datamodel_internal(
datamodel_string: &str,
transform: bool,
) -> Result<Validated<(Configuration, Datamodel)>, diagnostics::Diagnostics> {
let mut diagnostics = diagnostics::Diagnostics::new();
let ast = ast::parse_schema(datamodel_string, &mut diagnostics);
Expand All @@ -149,7 +153,7 @@ fn parse_datamodel_internal(

diagnostics.to_result()?;

let out = validate(&ast, &datasources, preview_features, diagnostics, transform);
let out = validate(&ast, &datasources, preview_features, diagnostics);

if !out.diagnostics.errors().is_empty() {
return Err(out.diagnostics);
Expand Down
2 changes: 0 additions & 2 deletions libs/datamodel/core/src/transform/ast_to_dml/lift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,6 @@ impl<'a> LiftAstToDml<'a> {
model.indices = walker
.indexes()
.map(|idx| {
assert!(idx.fields().len() != 0);

let fields = idx
.scalar_field_attributes()
.map(|field| IndexField {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ pub(crate) fn validate<'ast>(
sources: &[configuration::Datasource],
preview_features: BitFlags<PreviewFeature>,
diagnostics: Diagnostics,
relation_transformation_enabled: bool,
) -> ValidateOutput<'ast> {
let source = sources.first();
let connector = source.map(|s| s.active_connector).unwrap_or(&EmptyDatamodelConnector);
Expand Down Expand Up @@ -57,7 +56,7 @@ pub(crate) fn validate<'ast>(
diagnostics: &mut output.diagnostics,
};

validations::validate(&mut context, relation_transformation_enabled);
validations::validate(&mut context);

output
}
Loading

0 comments on commit 6572bbe

Please sign in to comment.