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

feat(dmmf): add full index information #4949

Merged
merged 10 commits into from
Jul 11, 2024
Merged
47 changes: 45 additions & 2 deletions prisma-fmt/src/get_datamodel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ mod tests {
}

model Post {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
Expand Down Expand Up @@ -190,7 +190,50 @@ mod tests {
"isGenerated": false
}
],
"types": []
"types": [],
"indexes": [
aqrln marked this conversation as resolved.
Show resolved Hide resolved
{
"model": "User",
"type": "id",
"isDefinedOnField": true,
"fields": [
{
"name": "id"
}
]
},
{
"model": "User",
"type": "unique",
"isDefinedOnField": true,
"fields": [
{
"name": "email"
}
]
},
{
"model": "Post",
"type": "id",
"isDefinedOnField": true,
"fields": [
{
"name": "id"
}
]
},
{
"model": "Post",
"type": "normal",
"isDefinedOnField": false,
"dbName": "idx_post_on_title",
"fields": [
{
"name": "title"
}
]
}
]
}"#]];

let response = get_datamodel(
Expand Down
6,548 changes: 6,536 additions & 12 deletions prisma-fmt/src/get_dmmf.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions query-engine/dmmf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
bigdecimal = "0.3.0"
itertools.workspace = true
psl.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand Down
90 changes: 75 additions & 15 deletions query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::serialization_ast::datamodel_ast::{
Datamodel, Enum, EnumValue, Field, Function, Model, PrimaryKey, UniqueIndex,
use crate::serialization_ast::{
datamodel_ast::{Datamodel, Enum, EnumValue, Field, Function, Model, PrimaryKey, UniqueIndex},
Index, IndexField, IndexType,
};
use bigdecimal::ToPrimitive;
use itertools::{Either, Itertools};
use psl::{
parser_database::{walkers, ScalarFieldType},
schema_ast::ast::WithDocumentation,
Expand All @@ -13,6 +15,7 @@ pub(crate) fn schema_to_dmmf(schema: &psl::ValidatedSchema) -> Datamodel {
models: Vec::with_capacity(schema.db.models_count()),
enums: Vec::with_capacity(schema.db.enums_count()),
types: Vec::new(),
indexes: Vec::new(),
};

for enum_model in schema.db.walk_enums() {
Expand All @@ -26,6 +29,7 @@ pub(crate) fn schema_to_dmmf(schema: &psl::ValidatedSchema) -> Datamodel {
.chain(schema.db.walk_views().filter(|view| !view.is_ignored()))
{
datamodel.models.push(model_to_dmmf(model));
datamodel.indexes.extend(model_indexes_to_dmmf(model));
}

for ct in schema.db.walk_composite_types() {
Expand Down Expand Up @@ -238,6 +242,56 @@ fn relation_field_to_dmmf(field: walkers::RelationFieldWalker<'_>) -> Field {
}
}

fn model_indexes_to_dmmf(model: walkers::ModelWalker<'_>) -> impl Iterator<Item = Index> + '_ {
model
.primary_key()
.into_iter()
.map(move |pk| Index {
model: model.name().to_owned(),
r#type: IndexType::Id,
is_defined_on_field: pk.is_defined_on_field(),
name: pk.name().map(ToOwned::to_owned),
db_name: pk.mapped_name().map(ToOwned::to_owned),
algorithm: None,
clustered: pk.clustered(),
fields: pk
.scalar_field_attributes()
.map(scalar_field_attribute_to_dmmf)
.collect(),
})
.chain(model.indexes().map(move |index| {
Index {
model: model.name().to_owned(),
r#type: index.index_type().into(),
is_defined_on_field: index.is_defined_on_field(),
name: index.name().map(ToOwned::to_owned),
db_name: index.mapped_name().map(ToOwned::to_owned),
algorithm: index.algorithm().map(|alg| alg.to_string()),
clustered: index.clustered(),
fields: index
.scalar_field_attributes()
.map(scalar_field_attribute_to_dmmf)
.collect(),
}
}))
}

fn scalar_field_attribute_to_dmmf(sfa: walkers::ScalarFieldAttributeWalker<'_>) -> IndexField {
IndexField {
name: sfa
.as_path_to_indexed_field()
.into_iter()
.map(|(field_name, _)| field_name.to_owned())
.join("."),
sort_order: sfa.sort_order().map(Into::into),
length: sfa.length(),
operator_class: sfa.operator_class().map(|oc| match oc.get() {
Either::Left(oc) => oc.to_string(),
Either::Right(oc) => oc.to_owned(),
}),
}
}

fn default_value_to_serde(dv: &DefaultKind) -> serde_json::Value {
match dv {
DefaultKind::Single(value) => prisma_value_to_serde(&value.clone()),
Expand Down Expand Up @@ -296,27 +350,30 @@ mod tests {
serde_json::to_string_pretty(&dmmf).expect("Failed to render JSON")
}

const SAMPLES_FOLDER_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/test_files");

#[test]
fn test_dmmf_rendering() {
let test_cases = vec![
"general",
"functions",
"source",
"source_with_comments",
"source_with_generator",
"without_relation_name",
"ignore",
"views",
];
let test_cases = fs::read_dir(SAMPLES_FOLDER_PATH)
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.filter(|name| name.ends_with(".prisma"))
.map(|name| name.trim_end_matches(".prisma").to_owned());

for test_case in test_cases {
println!("TESTING: {test_case}");

let datamodel_string = load_from_file(format!("{test_case}.prisma").as_str());
let dmmf_string = render_to_dmmf(&datamodel_string);

if std::env::var("UPDATE_EXPECT") == Ok("1".into()) {
save_to_file(&format!("{test_case}.json"), &dmmf_string);
}

let expected_json = load_from_file(format!("{test_case}.json").as_str());

println!("{dmmf_string}");
assert_eq_json(&dmmf_string, &expected_json, test_case);
assert_eq_json(&dmmf_string, &expected_json, &test_case);
}
}

Expand All @@ -329,7 +386,10 @@ mod tests {
}

fn load_from_file(file: &str) -> String {
let samples_folder_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test_files");
fs::read_to_string(format!("{samples_folder_path}/{file}")).unwrap()
fs::read_to_string(format!("{SAMPLES_FOLDER_PATH}/{file}")).unwrap()
}

fn save_to_file(file: &str, content: &str) {
fs::write(format!("{SAMPLES_FOLDER_PATH}/{file}"), content).unwrap();
}
}
64 changes: 64 additions & 0 deletions query-engine/dmmf/src/serialization_ast/datamodel_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub struct Datamodel {
pub enums: Vec<Enum>,
pub models: Vec<Model>,
pub types: Vec<Model>, // composite types
pub indexes: Vec<Index>,
}

#[derive(Debug, serde::Serialize)]
Expand Down Expand Up @@ -107,3 +108,66 @@ pub struct EnumValue {
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation: Option<String>,
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Index {
pub model: String,
pub r#type: IndexType,
pub is_defined_on_field: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub db_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clustered: Option<bool>,
pub fields: Vec<IndexField>,
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IndexField {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_order: Option<SortOrder>,
#[serde(skip_serializing_if = "Option::is_none")]
pub length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operator_class: Option<String>,
}

macro_rules! from {
( $from:path => $to:ident { $( $variant:ident ),+ } ) => {
impl From<$from> for $to {
fn from(value: $from) -> Self {
match value {
$( <$from>::$variant => <$to>::$variant ),+
}
}
}
};
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum IndexType {
Id,
Normal,
Unique,
Fulltext,
}

// `Id` doesn't exist in `psl::parser_database::IndexType` as primary keys are not represented as
// such on that level, so we only generate the From impl for the other three variants.
from!(psl::parser_database::IndexType => IndexType { Normal, Unique, Fulltext });

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum SortOrder {
Asc,
Desc,
}

from!(psl::parser_database::SortOrder => SortOrder { Asc, Desc });
Binary file not shown.
36 changes: 29 additions & 7 deletions query-engine/dmmf/test_files/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"isUnique": false,
"isId": true,
"isReadOnly": false,
"type": "Int",
"hasDefaultValue": false,
"type": "Int",
"isGenerated": false,
"isUpdatedAt": false
},
Expand All @@ -26,8 +26,8 @@
"isUnique": false,
"isId": false,
"isReadOnly": false,
"type": "DateTime",
"hasDefaultValue": true,
"type": "DateTime",
"default": {
"name": "now",
"args": []
Expand All @@ -43,8 +43,8 @@
"isUnique": true,
"isId": false,
"isReadOnly": false,
"type": "String",
"hasDefaultValue": true,
"type": "String",
"default": {
"name": "cuid",
"args": []
Expand All @@ -53,11 +53,33 @@
"isUpdatedAt": false
}
],
"isGenerated": false,
"primaryKey": null,
"uniqueFields": [],
"uniqueIndexes": []
"uniqueIndexes": [],
"isGenerated": false
}
],
"types": []
}
"types": [],
"indexes": [
{
"model": "User",
"type": "id",
"isDefinedOnField": true,
"fields": [
{
"name": "id"
}
]
},
{
"model": "User",
"type": "unique",
"isDefinedOnField": true,
"fields": [
{
"name": "someId"
}
]
}
]
}
Loading
Loading