Skip to content

Commit

Permalink
feat(dmmf): add full index information (#4949)
Browse files Browse the repository at this point in the history
Add complete index information to the `datamodel` section of the DMMF.
  • Loading branch information
aqrln committed Jul 11, 2024
1 parent 5954db1 commit 9618390
Show file tree
Hide file tree
Showing 22 changed files with 7,727 additions and 131 deletions.
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": [
{
"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

0 comments on commit 9618390

Please sign in to comment.