Skip to content

Commit 6181247

Browse files
authored
feat: ORM-1110 externally managed tables - migrate dev (#5529)
This PR implements the functionality for filtering out tables marked as "externally" managed by the coming `tables.external` `prisma.config.ts` config option. It focuses on providing this functionality for the `migrate dev` command. The filtering happens during the actual diff computation. This is necessary because we still want to create "outgoing" foreign key constraints to external tables. For this we still need to know about them during diff creation. The goal is for the diff creation to just not create the SQL to create/update/delete external tables. ([also see slack about this](https://prisma-company.slack.com/archives/C0350LQSUF2/p1752229610438489)) Note: This PR comes with a minor change of behavior for schema creation. Previously we would issue `CREATE SCHEMA` statements for a schema if it just appears in the list of given schemas. Now that schema needs to have a non-external model or enum assigned to it that we would consider issuing a `CREATE SCHEMA` statement. This is to ensure that we do not try to create externally managed schemas. Implementation for `db push`, `db pull`, `migrate diff`, and the actual config attribute will follow in dedicated PRs.
1 parent 8047c96 commit 6181247

34 files changed

Lines changed: 880 additions & 74 deletions

File tree

query-engine/connector-test-kit-rs/qe-setup/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use driver_adapters::DriverAdapter;
1818
use enumflags2::BitFlags;
1919
use providers::Provider;
2020
use psl::{builtin_connectors::*, Datasource};
21-
use schema_core::schema_connector::{ConnectorResult, SchemaConnector, SchemaDialect};
21+
use schema_core::schema_connector::{ConnectorResult, SchemaConnector, SchemaDialect, SchemaFilter};
2222
use std::env;
2323

2424
#[derive(Debug, serde::Deserialize, PartialEq)]
@@ -151,7 +151,7 @@ pub async fn teardown(prisma_schema: &str, db_schemas: &[&str]) -> ConnectorResu
151151
pub(crate) async fn diff(schema: &str, dialect: &dyn SchemaDialect) -> ConnectorResult<String> {
152152
let from = dialect.empty_database_schema();
153153
let to = dialect.schema_from_datamodel(vec![("schema.prisma".to_string(), schema.into())])?;
154-
let migration = dialect.diff(from, to);
154+
let migration = dialect.diff(from, to, &SchemaFilter::default());
155155
dialect.render_script(&migration, &Default::default())
156156
}
157157

schema-engine/commands/src/commands/create_migration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ pub async fn create_migration(
3737

3838
let from = migration_schema_cache
3939
.get_or_insert(&input.migrations_list.migration_directories, || async {
40+
// We pass the namespaces here, because we want to describe all of the namespaces we know about from the "to" schema.
4041
let namespaces = dialect.extract_namespaces(&to);
41-
// We pass the namespaces here, because we want to describe all of these namespaces.
4242
connector.schema_from_migrations(&previous_migrations, namespaces).await
4343
})
4444
.await?;
4545

46-
let migration = dialect.diff(from, to);
46+
let migration = dialect.diff(from, to, &input.filters.into());
4747

4848
let extension = dialect.migration_file_extension().to_owned();
4949

schema-engine/commands/src/commands/dev_diagnostic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub async fn dev_diagnostic(
2424
let diagnose_input = DiagnoseMigrationHistoryInput {
2525
migrations_list: input.migrations_list,
2626
opt_in_to_shadow_database: true,
27+
schema_filter: input.schema_filter,
2728
};
2829

2930
let diagnose_migration_history_output = diagnose_migration_history(

schema-engine/commands/src/commands/diagnose_migration_history.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,15 @@ pub async fn diagnose_migration_history(
146146
})
147147
.await;
148148
let to = connector.schema_from_database(namespaces.clone()).await;
149-
let drift = match from.and_then(|from| to.map(|to| dialect.diff(from, to))).map(|mig| {
150-
if dialect.migration_is_empty(&mig) {
151-
None
152-
} else {
153-
Some(mig)
154-
}
155-
}) {
149+
let drift = match from
150+
.and_then(|from| to.map(|to| dialect.diff(from, to, &input.schema_filter.into())))
151+
.map(|mig| {
152+
if dialect.migration_is_empty(&mig) {
153+
None
154+
} else {
155+
Some(mig)
156+
}
157+
}) {
156158
Ok(Some(drift)) => Some(DriftDiagnostic::DriftDetected {
157159
summary: dialect.migration_summary(&drift),
158160
}),

schema-engine/commands/src/commands/diff.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use json_rpc::types::MigrationList;
1111
use psl::SourceFile;
1212
use quaint::connector::ExternalConnectorFactory;
1313
use schema_connector::{
14-
ConnectorError, DatabaseSchema, ExternalShadowDatabase, Namespaces, SchemaConnector, SchemaDialect,
14+
ConnectorError, DatabaseSchema, ExternalShadowDatabase, Namespaces, SchemaConnector, SchemaDialect, SchemaFilter,
1515
};
1616

1717
pub async fn diff(
@@ -49,7 +49,8 @@ pub async fn diff(
4949
let from = schema_from.unwrap_or_else(|| dialect.empty_database_schema());
5050
let to = schema_to.unwrap_or_else(|| dialect.empty_database_schema());
5151

52-
let migration = dialect.diff(from, to);
52+
// TODO:(schema-filter) get filter from params and prisma config
53+
let migration = dialect.diff(from, to, &SchemaFilter::default());
5354

5455
let mut stdout = if params.script {
5556
dialect.render_script(&migration, &Default::default())?

schema-engine/commands/src/commands/evaluate_data_loss.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub async fn evaluate_data_loss(
3131
})
3232
.await?;
3333

34-
let migration = dialect.diff(from, to);
34+
let migration = dialect.diff(from, to, &input.filters.into());
3535

3636
let migration_steps = dialect.migration_len(&migration) as u32;
3737
let diagnostics = connector.destructive_change_checker().check(&migration).await?;

schema-engine/commands/src/commands/schema_push.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{CoreResult, SchemaContainerExt, json_rpc::types::*, parse_schema_multi};
2-
use schema_connector::{ConnectorError, SchemaConnector};
2+
use schema_connector::{ConnectorError, SchemaConnector, SchemaFilter};
33
use tracing_futures::Instrument;
44

55
/// Command to bring the local database in sync with the prisma schema, without
@@ -31,7 +31,8 @@ pub async fn schema_push(input: SchemaPushInput, connector: &mut dyn SchemaConne
3131
.schema_from_database(namespaces)
3232
.instrument(tracing::info_span!("Calculate from database"))
3333
.await?;
34-
let database_migration = dialect.diff(from, to);
34+
// TODO:(schema-filter) get filter from prisma config
35+
let database_migration = dialect.diff(from, to, &SchemaFilter::default());
3536

3637
tracing::debug!(migration = dialect.migration_summary(&database_migration).as_str());
3738

schema-engine/connectors/mongodb-schema-connector/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl MongoDbSchemaConnector {
5454
}
5555

5656
impl SchemaDialect for MongoDbSchemaDialect {
57-
fn diff(&self, from: DatabaseSchema, to: DatabaseSchema) -> Migration {
57+
fn diff(&self, from: DatabaseSchema, to: DatabaseSchema, _filter: &SchemaFilter) -> Migration {
5858
let from: Box<MongoSchema> = from.downcast();
5959
let to: Box<MongoSchema> = to.downcast();
6060
Migration::new(differ::diff(from, to))

schema-engine/connectors/mongodb-schema-connector/tests/migrations/test_api.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use enumflags2::BitFlags;
33
use futures::TryStreamExt;
44
use mongodb_schema_connector::MongoDbSchemaConnector;
55
use psl::{parser_database::SourceFile, PreviewFeature};
6-
use schema_connector::{ConnectorParams, SchemaConnector};
6+
use schema_connector::{ConnectorParams, SchemaConnector, SchemaFilter};
77
use std::{
88
collections::BTreeMap,
99
fmt::Write as _,
@@ -184,7 +184,7 @@ pub(crate) fn test_scenario(scenario_name: &str) {
184184
let to = dialect
185185
.schema_from_datamodel(vec![("schema.prisma".to_string(), schema.clone())])
186186
.unwrap();
187-
let migration = dialect.diff(from, to);
187+
let migration = dialect.diff(from, to, &SchemaFilter::default());
188188

189189
connector.apply_migration(&migration).await.unwrap();
190190

@@ -222,7 +222,7 @@ Snapshot comparison failed. Run the test again with UPDATE_EXPECT=1 in the envir
222222
let to = dialect
223223
.schema_from_datamodel(vec![("schema.prisma".to_string(), schema.clone())])
224224
.unwrap();
225-
let migration = dialect.diff(from, to);
225+
let migration = dialect.diff(from, to, &SchemaFilter::default());
226226

227227
assert!(
228228
dialect.migration_is_empty(&migration),
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
/// Configuration of entities in the schema/database to be included or excluded from an operation.
22
#[derive(Debug, Default)]
33
pub struct SchemaFilter {
4-
/// Tables that shall be considered 'externally" managed. As per prisma.config.ts > tables.external.
4+
/// Tables that shall be considered "externally" managed. As per prisma.config.ts > tables.external.
5+
/// Prisma will not consider those tables during diffing operations, migration creation, or introspection.
6+
/// They are still available for querying at runtime.
57
pub external_tables: Vec<String>,
68
}
79

10+
impl SchemaFilter {
11+
/// Check if the given table name is in the list of external tables.
12+
/// `external_tables` can contain fully qualified table names with namespace
13+
/// (e.g. "auth.user") or just the table name.
14+
pub fn is_table_external(&self, namespace: Option<&str>, table_name: &str) -> bool {
15+
if let Some(namespace) = namespace {
16+
self.external_tables.contains(&format!("{namespace}.{table_name}"))
17+
|| self.external_tables.contains(&table_name.to_string())
18+
} else {
19+
self.external_tables.contains(&table_name.to_string())
20+
}
21+
}
22+
}
23+
824
impl From<json_rpc::types::SchemaFilter> for SchemaFilter {
925
fn from(filter: json_rpc::types::SchemaFilter) -> Self {
1026
Self {
1127
external_tables: filter.external_tables,
1228
}
1329
}
1430
}
31+
32+
impl From<Option<json_rpc::types::SchemaFilter>> for SchemaFilter {
33+
fn from(filter: Option<json_rpc::types::SchemaFilter>) -> Self {
34+
Self {
35+
external_tables: filter.map(|f| f.external_tables).unwrap_or_default(),
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)