mz_deploy/project/compiler/object_validation/schema_constraints.rs
1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Schema-level constraint validation.
11//!
12//! Validates structural constraints across objects within a schema, such as
13//! ensuring storage and computation objects are not mixed in the same schema.
14
15use crate::project::ast::Statement;
16use crate::project::error::{ValidationError, ValidationErrorKind};
17use crate::project::ir::compiled::DatabaseObject;
18use std::path::PathBuf;
19
20/// Validates that a schema doesn't mix storage objects with computation objects.
21///
22/// This validation prevents accidentally recreating tables or sinks when recreating views,
23/// which would cause data loss. Storage and computation objects should be in separate schemas.
24///
25/// # Object Groups
26///
27/// - **Storage objects**: Tables, Sinks (can coexist in same schema)
28/// - **Computation objects**: Views, Materialized Views (can coexist in same schema)
29/// - These two groups CANNOT mix in the same schema
30///
31/// # Validation Rules
32///
33/// Valid combinations within a schema:
34/// - Tables only
35/// - Tables + Sinks
36/// - Sinks only
37/// - Views only
38/// - Views + Materialized Views
39/// - Materialized Views only
40///
41/// Invalid combinations:
42/// - Tables + Views
43/// - Tables + Materialized Views
44/// - Sinks + Views
45/// - Sinks + Materialized Views
46/// - Tables + Sinks + Views
47/// - (any mix of storage and computation)
48///
49/// # Arguments
50///
51/// * `schema_name` - The name of the schema being validated
52/// * `objects` - All database objects in the schema
53/// * `errors` - Vector to collect validation errors
54pub(super) fn validate_no_storage_and_computation_in_schema(
55 schema_name: &str,
56 objects: &[DatabaseObject],
57 errors: &mut Vec<ValidationError>,
58) {
59 let mut has_storage = false;
60 let mut has_computation = false;
61 let mut storage_names = Vec::new();
62 let mut computation_names = Vec::new();
63
64 for obj in objects {
65 match &obj.stmt {
66 Statement::CreateTable(_)
67 | Statement::CreateTableFromSource(_)
68 | Statement::CreateSource(_)
69 | Statement::CreateSink(_)
70 | Statement::CreateSecret(_)
71 | Statement::CreateConnection(_) => {
72 has_storage = true;
73 let ident = obj.stmt.ident();
74 storage_names.push(ident.object.to_string());
75 }
76 Statement::CreateView(_) | Statement::CreateMaterializedView(_) => {
77 has_computation = true;
78 let ident = obj.stmt.ident();
79 computation_names.push(ident.object.to_string());
80 }
81 }
82 }
83
84 if has_storage && has_computation {
85 errors.push(ValidationError::with_file(
86 ValidationErrorKind::StorageAndComputationObjectsInSameSchema {
87 schema_name: schema_name.to_string(),
88 storage_objects: storage_names,
89 computation_objects: computation_names,
90 },
91 PathBuf::from(schema_name),
92 ));
93 }
94}