Skip to content

Commit

Permalink
Introspection of PostgreSQL views (#3622)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julius de Bruijn authored Jan 27, 2023
1 parent 35b907e commit 61863c7
Show file tree
Hide file tree
Showing 86 changed files with 3,970 additions and 1,564 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tracing-futures = "0.2"
user-facing-errors = { workspace = true, features = ["sql"] }
enumflags2 = "0.7.1"
quaint.workspace = true
either = "1.8.0"

[dev-dependencies]
pretty_assertions = "1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
introspection_helpers::{is_new_migration_table, is_old_migration_table, is_prisma_join_table, is_relay_table},
introspection_map::RelationName,
pair::{EnumPair, ModelPair, Pair, RelationFieldDirection},
pair::{EnumPair, ModelPair, Pair, RelationFieldDirection, ViewPair},
warnings, EnumVariantName, IntrospectedName, ModelName,
};
use introspection_connector::{Version, Warning};
Expand Down Expand Up @@ -85,6 +85,23 @@ impl<'a> InputContext<'a> {
})
}

/// Iterate over the database views, combined together with a
/// possible existing view in the PSL.
pub(crate) fn view_pairs(self) -> impl Iterator<Item = ViewPair<'a>> {
// Right now all connectors introspect views for db reset.
// Filtering the ones with columns will not cause
// empty view blocks with these connectors.
//
// Removing the filter when all connectors are done.
self.schema
.view_walkers()
.filter(|v| !v.columns().len() > 0)
.map(move |next| {
let previous = self.existing_view(next.id);
Pair::new(self, previous, next)
})
}

/// Given a SQL enum from the database, this method returns the enum that matches it (by name)
/// in the Prisma schema.
pub(crate) fn existing_enum(self, id: sql::EnumId) -> Option<walkers::EnumWalker<'a>> {
Expand Down Expand Up @@ -151,15 +168,48 @@ impl<'a> InputContext<'a> {
.map(|id| self.previous_schema.db.walk(*id))
}

pub(crate) fn existing_scalar_field(self, id: sql::ColumnId) -> Option<walkers::ScalarFieldWalker<'a>> {
pub(crate) fn existing_view(self, id: sql::ViewId) -> Option<walkers::ModelWalker<'a>> {
self.introspection_map
.existing_views
.get(&id)
.map(|id| self.previous_schema.db.walk(*id))
}

pub(crate) fn existing_table_scalar_field(self, id: sql::TableColumnId) -> Option<walkers::ScalarFieldWalker<'a>> {
self.introspection_map
.existing_scalar_fields
.existing_model_scalar_fields
.get(&id)
.map(|(model_id, field_id)| self.previous_schema.db.walk(*model_id).scalar_field(*field_id))
}

pub(crate) fn column_prisma_name(self, id: sql::ColumnId) -> crate::IntrospectedName<'a> {
self.existing_scalar_field(id)
pub(crate) fn existing_view_scalar_field(self, id: sql::ViewColumnId) -> Option<walkers::ScalarFieldWalker<'a>> {
self.introspection_map
.existing_view_scalar_fields
.get(&id)
.map(|(model_id, field_id)| self.previous_schema.db.walk(*model_id).scalar_field(*field_id))
}

pub(crate) fn column_prisma_name(
self,
id: sql::Either<sql::TableColumnId, sql::ViewColumnId>,
) -> crate::IntrospectedName<'a> {
match id {
sql::Either::Left(id) => self.table_column_prisma_name(id),
sql::Either::Right(id) => self.view_column_prisma_name(id),
}
}

pub(crate) fn table_column_prisma_name(self, id: sql::TableColumnId) -> crate::IntrospectedName<'a> {
self.existing_table_scalar_field(id)
.map(|sf| IntrospectedName::FromPsl {
name: sf.name(),
mapped_name: sf.mapped_name(),
})
.unwrap_or_else(|| IntrospectedName::new_from_sql(self.schema.walk(id).name()))
}

pub(crate) fn view_column_prisma_name(self, id: sql::ViewColumnId) -> crate::IntrospectedName<'a> {
self.existing_view_scalar_field(id)
.map(|sf| IntrospectedName::FromPsl {
name: sf.name(),
mapped_name: sf.mapped_name(),
Expand All @@ -180,6 +230,19 @@ impl<'a> InputContext<'a> {
ModelName::new_from_sql(table.name(), table.namespace(), self)
}

// Use the existing view name when available.
pub(crate) fn view_prisma_name(self, id: sql::ViewId) -> crate::ModelName<'a> {
if let Some(view) = self.existing_view(id) {
return ModelName::FromPsl {
name: view.name(),
mapped_name: view.mapped_name(),
};
}

let view = self.schema.walk(id);
ModelName::new_from_sql(view.name(), view.namespace(), self)
}

pub(crate) fn name_is_unique(self, name: &'a str) -> bool {
let name = crate::sanitize_datamodel_names::sanitize_string(name);

Expand Down Expand Up @@ -273,6 +336,10 @@ impl<'a> InputContext<'a> {
self.introspection_map.missing_tables_for_previous_models.contains(id)
}

pub(crate) fn view_missing_for_model(self, id: &ast::ModelId) -> bool {
self.introspection_map.missing_views_for_previous_models.contains(id)
}

pub(crate) fn inline_relations_for_table(
self,
table_id_filter: sql::TableId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
mod relation_names;

use crate::{datamodel_calculator::InputContext, introspection_helpers as helpers, pair::RelationFieldDirection};
use psl::parser_database::{self, ast};
use psl::{
parser_database::{self, ast},
PreviewFeature,
};
use relation_names::RelationNames;
use sql_schema_describer as sql;
use std::{
Expand All @@ -20,8 +23,11 @@ pub(crate) use relation_names::RelationName;
pub(crate) struct IntrospectionMap<'a> {
pub(crate) existing_enums: HashMap<sql::EnumId, ast::EnumId>,
pub(crate) existing_models: HashMap<sql::TableId, ast::ModelId>,
pub(crate) existing_views: HashMap<sql::ViewId, ast::ModelId>,
pub(crate) missing_tables_for_previous_models: HashSet<ast::ModelId>,
pub(crate) existing_scalar_fields: HashMap<sql::ColumnId, (ast::ModelId, ast::FieldId)>,
pub(crate) missing_views_for_previous_models: HashSet<ast::ModelId>,
pub(crate) existing_model_scalar_fields: HashMap<sql::TableColumnId, (ast::ModelId, ast::FieldId)>,
pub(crate) existing_view_scalar_fields: HashMap<sql::ViewColumnId, (ast::ModelId, ast::FieldId)>,
pub(crate) existing_inline_relations: HashMap<sql::ForeignKeyId, parser_database::RelationId>,
pub(crate) existing_m2m_relations: HashMap<sql::TableId, parser_database::ManyToManyRelationId>,
pub(crate) relation_names: RelationNames<'a>,
Expand All @@ -37,6 +43,7 @@ impl<'a> IntrospectionMap<'a> {
let mut map = Default::default();

match_existing_models(sql_schema, prisma_schema, &mut map);
match_existing_views(sql_schema, prisma_schema, &mut map);
match_enums(sql_schema, prisma_schema, &mut map);
match_existing_scalar_fields(sql_schema, prisma_schema, &mut map);
match_existing_inline_relations(sql_schema, prisma_schema, &mut map);
Expand Down Expand Up @@ -81,6 +88,26 @@ fn populate_top_level_names<'a>(
let count = map.top_level_names.entry(name).or_default();
*count += 1;
}

// we do not want dupe warnings for models clashing with views,
// if not using the preview.
if prisma_schema
.configuration
.preview_features()
.contains(PreviewFeature::Views)
{
for view in sql_schema.view_walkers() {
let name = map
.existing_views
.get(&view.id)
.map(|id| prisma_schema.db.walk(*id))
.map(|m| Cow::Borrowed(m.name()))
.unwrap_or_else(|| crate::sanitize_datamodel_names::sanitize_string(view.name()));

let count = map.top_level_names.entry(name).or_default();
*count += 1;
}
}
}

/// Inlined relation fields (foreign key is defined in a model) are
Expand Down Expand Up @@ -133,7 +160,7 @@ fn position_m2m_relation_fields(sql_schema: &sql::SqlSchema, map: &mut Introspec
fn match_enums(sql_schema: &sql::SqlSchema, prisma_schema: &psl::ValidatedSchema, map: &mut IntrospectionMap) {
map.existing_enums = if prisma_schema.connector.is_provider("mysql") {
sql_schema
.walk_columns()
.walk_table_columns()
.filter_map(|col| col.column_type_family_as_enum().map(|enm| (col, enm)))
.filter_map(|(col, sql_enum)| {
prisma_schema
Expand Down Expand Up @@ -182,14 +209,30 @@ fn match_existing_models(schema: &sql::SqlSchema, prisma_schema: &psl::Validated
}
}

/// Finding views from the existing PSL definition, matching the
/// ones found in the database.
fn match_existing_views(sql_schema: &sql::SqlSchema, prisma_schema: &psl::ValidatedSchema, map: &mut IntrospectionMap) {
for view in prisma_schema.db.walk_views() {
match sql_schema.find_view(view.database_name(), view.schema_name()) {
Some(sql_id) => {
map.existing_views.insert(sql_id, view.id);
}

None => {
map.missing_views_for_previous_models.insert(view.id);
}
}
}
}

/// Finding scalar fields from the existing PSL definition, matching
/// the ones found in the database.
fn match_existing_scalar_fields(
sql_schema: &sql::SqlSchema,
prisma_schema: &psl::ValidatedSchema,
map: &mut IntrospectionMap,
) {
for col in sql_schema.walk_columns() {
for col in sql_schema.walk_table_columns() {
let ids = map.existing_models.get(&col.table().id).and_then(|model_id| {
let model = prisma_schema.db.walk(*model_id);

Expand All @@ -201,7 +244,25 @@ fn match_existing_scalar_fields(
});

if let Some((model, field)) = ids {
map.existing_scalar_fields.insert(col.id, (model.id, field.field_id()));
map.existing_model_scalar_fields
.insert(col.id, (model.id, field.field_id()));
}
}

for col in sql_schema.walk_view_columns() {
let ids = map.existing_views.get(&col.view().id).and_then(|view_id| {
let model = prisma_schema.db.walk(*view_id);

let field = model
.scalar_fields()
.find(|field| field.database_name() == col.name())?;

Some((model, field))
});

if let Some((model, field)) = ids {
map.existing_view_scalar_fields
.insert(col.id, (model.id, field.field_id()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fn inline_relation_name<'a>(
relation_name.push('_');
let mut cols = fk.constrained_columns().peekable();
while let Some(col) = cols.next() {
relation_name.push_str(input.column_prisma_name(col.id).prisma_name().as_ref());
relation_name.push_str(input.table_column_prisma_name(col.id).prisma_name().as_ref());
if cols.peek().is_some() {
relation_name.push('_');
}
Expand Down Expand Up @@ -238,7 +238,7 @@ fn inline_relation_ambiguousness(
let default_field_name = input.table_prisma_name(fk.referenced_table().id).prisma_name();
if fk
.constrained_columns()
.any(|col| default_field_name == input.column_prisma_name(col.id).prisma_name())
.any(|col| default_field_name == input.table_column_prisma_name(col.id).prisma_name())
{
ambiguous_relations.insert(tables);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod index_field;
mod model;
mod relation_field;
mod scalar_field;
mod view;

use crate::datamodel_calculator::InputContext;
pub(crate) use default::{DefaultKind, DefaultValuePair};
Expand All @@ -21,6 +22,7 @@ pub(crate) use index_field::{IndexFieldPair, IndexOps};
pub(crate) use model::ModelPair;
pub(crate) use relation_field::{RelationFieldDirection, RelationFieldPair};
pub(crate) use scalar_field::ScalarFieldPair;
pub(crate) use view::ViewPair;

/// Holds the introspected item from the database, and a possible
/// previous value from the PSL.
Expand All @@ -34,7 +36,7 @@ where
U: Copy,
{
/// The previous state, taken from the PSL.
previous: Option<T>,
previous: T,
/// The next state, taken from the database.
next: U,
/// The configuration object of the introspection.
Expand All @@ -46,7 +48,7 @@ where
T: Copy,
U: Copy,
{
pub(crate) fn new(context: InputContext<'a>, previous: Option<T>, next: U) -> Self {
pub(crate) fn new(context: InputContext<'a>, previous: T, next: U) -> Self {
Self {
context,
previous,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use either::Either;
use prisma_value::PrismaValue;
use psl::{
builtin_connectors::{MySqlType, PostgresType},
Expand All @@ -10,7 +11,7 @@ use std::{borrow::Cow, fmt};

use super::Pair;

pub(crate) type DefaultValuePair<'a> = Pair<'a, walkers::DefaultValueWalker<'a>, sql::ColumnWalker<'a>>;
pub(crate) type DefaultValuePair<'a> = Pair<'a, Option<walkers::DefaultValueWalker<'a>>, sql::ColumnWalker<'a>>;

pub(crate) enum DefaultKind<'a> {
Sequence(&'a sql::postgres::Sequence),
Expand Down Expand Up @@ -171,10 +172,11 @@ impl<'a> DefaultValuePair<'a> {
}

fn default_name(self) -> String {
ConstraintNames::default_name(
self.next.table().name(),
self.next.name(),
self.context.active_connector(),
)
let container_name = match self.next.refine() {
Either::Left(col) => col.table().name(),
Either::Right(col) => col.view().name(),
};

ConstraintNames::default_name(container_name, self.next.name(), self.context.active_connector())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ use std::borrow::Cow;

use super::Pair;

pub(crate) type EnumPair<'a> = Pair<'a, walkers::EnumWalker<'a>, sql::EnumWalker<'a>>;
pub(crate) type EnumVariantPair<'a> = Pair<'a, walkers::EnumValueWalker<'a>, sql::EnumVariantWalker<'a>>;
/// Pairing the PSL enums (previous) to database enums (next).
pub(crate) type EnumPair<'a> = Pair<'a, Option<walkers::EnumWalker<'a>>, sql::EnumWalker<'a>>;

/// Pairing the PSL enum values (previous) to database enums (next).
pub(crate) type EnumVariantPair<'a> = Pair<'a, Option<walkers::EnumValueWalker<'a>>, sql::EnumVariantWalker<'a>>;

impl<'a> EnumPair<'a> {
/// The documentation on top of the enum.
Expand Down
Loading

0 comments on commit 61863c7

Please sign in to comment.