Skip to content

Commit f79e51f

Browse files
authored
feat: ORM-1263 external enums (#5559)
Add support for externally managed enums analog to external tables. Effectively only really required on Postgres as enums are dedicated database entities there.
1 parent f2cca53 commit f79e51f

8 files changed

Lines changed: 122 additions & 20 deletions

File tree

schema-engine/connectors/schema-connector/src/filter.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ pub struct SchemaFilter {
99
/// Prisma will not consider those tables during diffing operations, migration creation, or introspection.
1010
/// They are still available for querying at runtime.
1111
pub external_tables: Vec<String>,
12+
/// Enums that shall be considered "externally" managed. As per prisma.config.ts > enums.external.
13+
/// Prisma will not consider those enums during diffing operations, migration creation, or introspection.
14+
/// They are still available for querying at runtime.
15+
pub external_enums: Vec<String>,
1216
}
1317

1418
impl SchemaFilter {
15-
/// Validate that the schema filter contains correctly qualified table names.
19+
/// Validate that the schema filter contains correctly qualified table and enum names.
1620
pub fn validate(&self, namespaces: Option<&Namespaces>) -> ConnectorResult<()> {
1721
let has_explicit_namespaces = namespaces.is_some();
1822

@@ -24,6 +28,14 @@ impl SchemaFilter {
2428
}
2529
}
2630

31+
for enum_name in self.external_enums.iter() {
32+
if has_explicit_namespaces && !enum_name.contains(".") {
33+
return Err(ConnectorError::user_facing(MissingNamespaceInExternalTables));
34+
} else if !has_explicit_namespaces && enum_name.contains(".") {
35+
return Err(ConnectorError::user_facing(UnexpectedNamespaceInExternalTables));
36+
}
37+
}
38+
2739
Ok(())
2840
}
2941
}
@@ -32,14 +44,17 @@ impl From<json_rpc::types::SchemaFilter> for SchemaFilter {
3244
fn from(filter: json_rpc::types::SchemaFilter) -> Self {
3345
Self {
3446
external_tables: filter.external_tables,
47+
external_enums: filter.external_enums,
3548
}
3649
}
3750
}
3851

3952
impl From<Option<json_rpc::types::SchemaFilter>> for SchemaFilter {
4053
fn from(filter: Option<json_rpc::types::SchemaFilter>) -> Self {
54+
let filter = filter.unwrap_or_default();
4155
Self {
42-
external_tables: filter.map(|f| f.external_tables).unwrap_or_default(),
56+
external_tables: filter.external_tables,
57+
external_enums: filter.external_enums,
4358
}
4459
}
4560
}

schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/differ_database.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,25 @@ impl<'a> DifferDatabase<'a> {
353353
all_tables_are_external && !has_enums
354354
}
355355

356+
fn is_enum_external(&self, enum_walker: &EnumWalker<'_>) -> bool {
357+
self.flavour
358+
.contains_table(&self.filter.external_enums, enum_walker.namespace(), enum_walker.name())
359+
}
360+
356361
fn previous_enums(&self) -> impl Iterator<Item = EnumWalker<'a>> {
357-
self.schemas.previous.describer_schema.enum_walkers()
362+
self.schemas
363+
.previous
364+
.describer_schema
365+
.enum_walkers()
366+
.filter(|e| !self.is_enum_external(e))
358367
}
359368

360369
fn next_enums(&self) -> impl Iterator<Item = EnumWalker<'a>> {
361-
self.schemas.next.describer_schema.enum_walkers()
370+
self.schemas
371+
.next
372+
.describer_schema
373+
.enum_walkers()
374+
.filter(|e| !self.is_enum_external(e))
362375
}
363376

364377
fn previous_extensions(&self) -> impl Iterator<Item = ExtensionWalker<'a>> {

schema-engine/json-rpc-api/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ pub struct SchemasWithConfigDir {
9696
pub struct SchemaFilter {
9797
/// Tables that shall be considered 'externally" managed. As per prisma.config.ts > tables.external.
9898
pub external_tables: Vec<String>,
99+
/// Enums that shall be considered "externally" managed. As per prisma.config.ts > enums.external.
100+
pub external_enums: Vec<String>,
99101
}
100102

101103
/// The path to a live database taken as input. For flexibility, this can be Prisma schemas as strings, or only the

schema-engine/sql-migration-tests/tests/migrations/diff.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ fn with_schema_filters(mut api: TestApi) {
720720
to: DiffTarget::Url(UrlContainer { url: second_url }),
721721
filters: Some(SchemaFilter {
722722
external_tables: vec!["external_table".to_string()],
723+
external_enums: vec![],
723724
}),
724725
};
725726

@@ -769,6 +770,7 @@ fn with_invalid_schema_filters(mut api: TestApi) {
769770
to: DiffTarget::Url(UrlContainer { url: second_url }),
770771
filters: Some(SchemaFilter {
771772
external_tables: vec!["public.external_table".to_string()],
773+
external_enums: vec![],
772774
}),
773775
};
774776

schema-engine/sql-migration-tests/tests/migrations/migration_persistence_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ fn starting_a_migration_on_db_with_existing_external_table_does_not_errors(api:
204204
None,
205205
SchemaFilter {
206206
external_tables: vec!["cats".to_string()],
207+
external_enums: vec![],
207208
},
208209
));
209210

schema-engine/sql-migration-tests/tests/migrations/schema_filter.rs

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ fn schema_filter_migration_adding_external_table(api: TestApi) {
1616

1717
let filter = SchemaFilter {
1818
external_tables: vec!["ExternalTable".to_string()],
19+
external_enums: vec![],
1920
};
2021
api.create_migration_with_filter("custom", &schema, &dir, filter, "")
2122
.send_sync()
2223
.assert_migration_directories_count(0);
24+
// Table is external => migrations should not touch it => created no migration => external table should not be there
25+
api.assert_schema().assert_has_no_table("ExternalTable");
2326
}
2427

2528
#[test_connector]
@@ -32,20 +35,21 @@ fn schema_filter_migration_removing_external_table(mut api: TestApi) {
3235
}
3336
"#,
3437
);
35-
36-
let dir = api.create_migrations_directory();
37-
3838
// No filter applied here to actually create the external tables first
39-
api.create_migration("create", &schema_1, &dir).send_sync();
39+
api.schema_push(schema_1).send();
4040

41+
let dir = api.create_migrations_directory();
4142
let schema_2 = api.datamodel_with_provider("");
4243

4344
let filter = SchemaFilter {
4445
external_tables: vec!["ExternalTable".to_string()],
46+
external_enums: vec![],
4547
};
4648
api.create_migration_with_filter("remove", &schema_2, &dir, filter, "")
4749
.send_sync()
48-
.assert_migration_directories_count(1);
50+
.assert_migration_directories_count(0);
51+
// Table is external => migrations should not touch it => created no migration => external table should still be there
52+
api.assert_schema().assert_has_table("ExternalTable");
4953
}
5054

5155
#[test_connector]
@@ -58,20 +62,17 @@ fn schema_filter_migration_removing_external_table_with_contents(mut api: TestAp
5862
}
5963
"#,
6064
);
61-
62-
let dir = api.create_migrations_directory();
63-
6465
// No filter applied here to actually create the external tables first
65-
api.create_migration("create", &schema_1, &dir).send_sync();
66-
api.apply_migrations(&dir).send_sync();
67-
66+
api.schema_push(schema_1).send();
6867
api.insert("Cat").value("id", 1).value("name", "Felix").result_raw();
6968
api.insert("Cat").value("id", 2).value("name", "Norbert").result_raw();
7069

70+
let dir = api.create_migrations_directory();
7171
let schema_2 = api.datamodel_with_provider("");
7272

7373
let filter = SchemaFilter {
7474
external_tables: vec!["Cat".to_string()],
75+
external_enums: vec![],
7576
};
7677
api.evaluate_data_loss_with_filter(&dir, schema_2.clone(), filter)
7778
.send()
@@ -87,12 +88,10 @@ fn schema_filter_migration_modifying_external_table(mut api: TestApi) {
8788
}
8889
"#,
8990
);
90-
91-
let dir = api.create_migrations_directory();
92-
9391
// No filter applied here to actually create the external tables first
94-
api.create_migration("create", &schema_1, &dir).send_sync();
92+
api.schema_push(schema_1).send();
9593

94+
let dir = api.create_migrations_directory();
9695
let schema_2 = api.datamodel_with_provider(
9796
r#"
9897
model ExternalTable {
@@ -104,10 +103,69 @@ fn schema_filter_migration_modifying_external_table(mut api: TestApi) {
104103

105104
let filter = SchemaFilter {
106105
external_tables: vec!["ExternalTable".to_string()],
106+
external_enums: vec![],
107107
};
108108
api.create_migration_with_filter("modify", &schema_2, &dir, filter, "")
109109
.send_sync()
110-
.assert_migration_directories_count(1);
110+
.assert_migration_directories_count(0);
111+
// Table is external => migrations should not touch it => created no migration => external table should still be in the old state
112+
api.assert_schema().assert_table("ExternalTable", |table_assertions| {
113+
table_assertions.assert_column_count(1)
114+
});
115+
}
116+
117+
#[test_connector(tags(Postgres), exclude(CockroachDb))]
118+
fn schema_filter_migration_adding_external_enum(api: TestApi) {
119+
let schema = api.datamodel_with_provider(
120+
r#"
121+
enum ExternalEnum {
122+
ONE
123+
TWO
124+
}
125+
"#,
126+
);
127+
128+
let dir = api.create_migrations_directory();
129+
130+
let filter = SchemaFilter {
131+
external_tables: vec![],
132+
external_enums: vec!["ExternalEnum".to_string()],
133+
};
134+
api.create_migration_with_filter("custom", &schema, &dir, filter, "")
135+
.send_sync()
136+
.assert_migration_directories_count(0);
137+
// Enum is external => migrations should not touch it => created no migration => external enum should not be there
138+
api.assert_schema().assert_has_no_enum("ExternalEnum");
139+
}
140+
141+
#[test_connector(tags(Postgres), exclude(CockroachDb))]
142+
fn schema_filter_migration_removing_external_enum(mut api: TestApi) {
143+
let schema_1 = api.datamodel_with_provider(
144+
r#"
145+
enum ExternalEnum {
146+
ONE
147+
TWO
148+
}
149+
"#,
150+
);
151+
// Create the external enum in the database
152+
api.schema_push(schema_1).send();
153+
154+
let dir = api.create_migrations_directory();
155+
156+
let schema_2 = api.datamodel_with_provider("");
157+
158+
let filter = SchemaFilter {
159+
external_tables: vec![],
160+
external_enums: vec!["ExternalEnum".to_string()],
161+
};
162+
api.create_migration_with_filter("remove", &schema_2, &dir, filter, "")
163+
.send_sync()
164+
.assert_migration_directories_count(0);
165+
// Enum is external => migrations should not touch it => created no migration => external enum should still be there
166+
api.assert_schema().assert_enum("ExternalEnum", |enum_assertions| {
167+
enum_assertions.assert_values(&["ONE", "TWO"])
168+
});
111169
}
112170

113171
#[test_connector(exclude(CockroachDb, Vitess))]
@@ -152,6 +210,7 @@ fn schema_filter_migration_adding_external_tables_incl_relations(api: TestApi) {
152210

153211
let filter = SchemaFilter {
154212
external_tables: vec!["ExternalTableA".to_string(), "ExternalTableB".to_string()],
213+
external_enums: vec![],
155214
};
156215
api.create_migration_with_filter("custom", &schema, &dir, filter, "")
157216
.send_sync()
@@ -283,6 +342,7 @@ fn schema_filter_migration_removing_external_tables_incl_relations(mut api: Test
283342

284343
let filter = SchemaFilter {
285344
external_tables: vec!["ExternalTableA".to_string(), "ExternalTableB".to_string()],
345+
external_enums: vec![],
286346
};
287347
api.create_migration_with_filter("remove", &schema_2, &dir, filter, "")
288348
.send_sync()
@@ -436,6 +496,7 @@ fn schema_filter_migration_modifying_external_tables_incl_relations(mut api: Tes
436496

437497
let filter = SchemaFilter {
438498
external_tables: vec!["ExternalTableA".to_string(), "ExternalTableB".to_string()],
499+
external_enums: vec![],
439500
};
440501
api.create_migration_with_filter("modify", &schema_2, &dir, filter, "")
441502
.send_sync()
@@ -542,6 +603,7 @@ fn schema_filter_leveraging_init_script(api: TestApi) {
542603

543604
let filter = SchemaFilter {
544605
external_tables: vec!["external".to_string()],
606+
external_enums: vec![],
545607
};
546608
api.create_migration_with_filter("custom", &schema, &dir, filter, init_script)
547609
.send_sync()
@@ -652,6 +714,7 @@ fn schema_filter_migration_multi_schema_requires_namespaced_table_names(api: Tes
652714

653715
let filter = SchemaFilter {
654716
external_tables: vec!["two.ExternalTable".to_string()],
717+
external_enums: vec![],
655718
};
656719
api.create_migration_with_filter("custom", &schema, &dir, filter, "")
657720
.send_sync()
@@ -731,6 +794,7 @@ fn schema_filter_migration_multi_schema_without_namespaced_table_names(api: Test
731794

732795
let filter = SchemaFilter {
733796
external_tables: vec!["ExternalTable".to_string()],
797+
external_enums: vec![],
734798
};
735799
let err = api
736800
.create_migration_with_filter("custom", &schema, &dir, filter, "")
@@ -763,6 +827,7 @@ fn schema_filter_migration_with_namespaced_table_names_and_no_explicit_schemas_l
763827

764828
let filter = SchemaFilter {
765829
external_tables: vec!["public.ExternalTable".to_string()],
830+
external_enums: vec![],
766831
};
767832
let err = api
768833
.create_migration_with_filter("custom", &schema, &dir, filter, "")
@@ -785,6 +850,7 @@ fn schema_filter_migration_dev_diagnostic_drift_detection(api: TestApi) {
785850

786851
let filter = SchemaFilter {
787852
external_tables: vec!["external_table".to_string()],
853+
external_enums: vec![],
788854
};
789855
// Table exists in DB and is missing in the schema but is marked as external => not a drift.
790856
api.dev_diagnostic_with_filter(&dir, filter)

schema-engine/sql-migration-tests/tests/migrations/soft_resets.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ fn soft_resets_does_not_drop_external_tables(mut api: TestApi) {
273273
.soft(true)
274274
.filter(SchemaFilter {
275275
external_tables: vec!["external_table".to_string()],
276+
external_enums: vec![],
276277
})
277278
.send_sync(None);
278279

schema-engine/sql-migration-tests/tests/schema_push/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ fn schema_push_with_schema_filters(api: TestApi) {
473473
dm,
474474
Some(SchemaFilter {
475475
external_tables: vec!["ExternalTable".to_string()],
476+
external_enums: vec![],
476477
}),
477478
)
478479
.send()
@@ -501,6 +502,7 @@ fn schema_push_with_invalid_schema_filters(api: TestApi) {
501502
dm,
502503
Some(SchemaFilter {
503504
external_tables: vec!["public.ExternalTable".to_string()],
505+
external_enums: vec![],
504506
}),
505507
)
506508
.send_unwrap_err();

0 commit comments

Comments
 (0)