backend/schema/validators/view_validator.cc (147 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "backend/schema/validators/view_validator.h" #include <string> #include <vector> #include "zetasql/public/options.pb.h" #include "zetasql/public/type.h" #include "zetasql/public/types/type_factory.h" #include "absl/container/flat_hash_set.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "backend/schema/catalog/column.h" #include "backend/schema/catalog/foreign_key.h" #include "backend/schema/catalog/schema.h" #include "backend/schema/catalog/udf.h" #include "backend/schema/catalog/view.h" #include "backend/schema/graph/schema_node.h" #include "backend/schema/updater/global_schema_names.h" #include "backend/schema/updater/sql_expression_validators.h" #include "common/errors.h" #include "zetasql/base/ret_check.h" #include "zetasql/base/status_macros.h" namespace google { namespace spanner { namespace emulator { namespace backend { namespace { absl::Status ValidateViewSignatureChange(absl::string_view modify_action, absl::string_view dependency_name, const View* dependent_view, const Schema* temp_new_schema, zetasql::TypeFactory* type_factory) { // Re-analyze the dependent view based on the new definition of the dependency // in the temporary new schema. std::vector<View::Column> new_columns; absl::flat_hash_set<const SchemaNode*> unused_new_deps; auto status = AnalyzeViewDefinition( dependent_view->Name(), dependent_view->body(), temp_new_schema, type_factory, &new_columns, &unused_new_deps); if (!status.ok()) { return error::DependentViewBecomesInvalid(modify_action, dependency_name, dependent_view->Name(), status.message()); } // The number of columns of the dependent view should not change after // re-analysis. ZETASQL_RET_CHECK_EQ(new_columns.size(), dependent_view->columns().size()); auto original_columns = dependent_view->columns(); for (int i = 0; i < new_columns.size(); i++) { if (!absl::EqualsIgnoreCase(original_columns[i].name, new_columns[i].name)) { return error::DependentViewColumnRename( modify_action, dependency_name, dependent_view->Name(), original_columns[i].name, new_columns[i].name); } if (original_columns[i].type != new_columns[i].type) { return error::DependentViewColumnRetype( modify_action, dependency_name, dependent_view->Name(), original_columns[i].type->TypeName(zetasql::PRODUCT_EXTERNAL, /*use_external_float32=*/true), new_columns[i].type->TypeName(zetasql::PRODUCT_EXTERNAL, /*use_external_float32=*/true)); } } return absl::OkStatus(); } } // namespace absl::Status ViewValidator::Validate(const View* view, SchemaValidationContext* context) { ZETASQL_RET_CHECK(!view->name_.empty()); if (context->is_postgresql_dialect()) { ZETASQL_RET_CHECK(view->postgresql_oid().has_value()); } else { ZETASQL_RET_CHECK(!view->postgresql_oid().has_value()); } for (const SchemaNode* dependency : view->dependencies()) { ZETASQL_RET_CHECK(!dependency->is_deleted()); } return absl::OkStatus(); } absl::Status ViewValidator::ValidateUpdate(const View* view, const View* old_view, SchemaValidationContext* context) { // During a REPLACE, the view name's case can change. if (context->IsModifiedNode(view)) { ZETASQL_RET_CHECK(absl::EqualsIgnoreCase(view->Name(), old_view->Name())); } else { ZETASQL_RET_CHECK_EQ(view->Name(), old_view->Name()); } if (view->is_deleted()) { context->global_names()->RemoveName(view->Name()); return absl::OkStatus(); } if (context->is_postgresql_dialect()) { ZETASQL_RET_CHECK(view->postgresql_oid().has_value()); ZETASQL_RET_CHECK(old_view->postgresql_oid().has_value()); ZETASQL_RET_CHECK_EQ(view->postgresql_oid().value(), old_view->postgresql_oid().value()); } else { ZETASQL_RET_CHECK(!view->postgresql_oid().has_value()); ZETASQL_RET_CHECK(!old_view->postgresql_oid().has_value()); } for (const SchemaNode* dependency : view->dependencies()) { // Cannot drop dependencies of the view. if (dependency->is_deleted()) { // If the deleted dependency is a column that was marked as deleted // as a result of a table drop, then we use the table to report the error // message. if (auto dep_column = dependency->As<const Column>(); dep_column != nullptr) { auto dep_table = dep_column->table(); if (dep_table->is_deleted()) { dependency = dep_table; } } const auto& dep_info = dependency->GetSchemaNameInfo(); std::string dependency_type = (dep_info->global ? absl::AsciiStrToUpper(dep_info->kind) : absl::AsciiStrToLower(dep_info->kind)); return error::InvalidDropDependentViews(dependency_type, dep_info->name, view->Name()); } // If a dependency was updated during the schema change then we need to // re-analyze *this. if (context->IsModifiedNode(dependency)) { const auto& dep_info = dependency->GetSchemaNameInfo(); std::string dependency_type = (dep_info->global ? absl::AsciiStrToUpper(dep_info->kind) : absl::AsciiStrToLower(dep_info->kind)); std::string modify_action = absl::StrCat("alter ", dependency_type); std::string dependency_name; if (auto dep_view = dependency->As<const View>(); dep_view != nullptr) { // TODO : In case of dependency change due to alteration // of a dependency view, the original name (and not the changed name) // should be reported in the error message. dependency_name = dep_view->Name(); } if (auto dep_table = dependency->As<const Table>(); dep_table != nullptr) { dependency_name = dep_table->Name(); } if (auto dep_column = dependency->As<const Column>(); dep_column != nullptr) { dependency_name = dep_column->FullName(); } if (auto dep_udf = dependency->As<const Udf>(); dep_udf != nullptr) { dependency_name = dep_udf->Name(); } // No need to check modifications on index dependencies as indexes // cannot currently be altered. ZETASQL_RETURN_IF_ERROR(ValidateViewSignatureChange( modify_action, dependency_name, view, context->tmp_new_schema(), context->type_factory())); } } return absl::OkStatus(); } } // namespace backend } // namespace emulator } // namespace spanner } // namespace google