backend/schema/updater/global_schema_names.cc (172 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/updater/global_schema_names.h" #include <new> #include <string> #include <vector> #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "backend/common/case.h" #include "backend/schema/catalog/schema.h" #include "common/errors.h" #include "common/limits.h" #include "farmhash.h" #include "zetasql/base/status_macros.h" namespace google { namespace spanner { namespace emulator { namespace backend { namespace { // Separator between components of generated names. constexpr absl::string_view kSeparator = "_"; // Formats the fingerprint hash value of a string. std::string MakeFingerprint(absl::string_view signature) { // Add a seed to help avoid generated name algorithm collisions. std::string text = absl::StrCat("2695C62E33338E4B", kSeparator, signature); return absl::StrFormat("%016X", farmhash::Fingerprint64(text)); } // Builds a name. Truncates the base component to ensure the final length of the // name does not exceed the schema name limit. std::string MakeName(absl::string_view base, absl::string_view suffix) { int length = base.length() + kSeparator.length() + suffix.length(); if (length > limits::kMaxSchemaIdentifierLength) { base.remove_suffix(length - limits::kMaxSchemaIdentifierLength); if (absl::EndsWith(base, kSeparator)) { base.remove_suffix(kSeparator.length()); } } return absl::StrCat(base, kSeparator, suffix); } std::string MakeBaseName(absl::string_view prefix, std::vector<absl::string_view> object_names) { std::vector<absl::string_view> to_join = {prefix}; for (const auto& object_name : object_names) { to_join.push_back(SDLObjectName::GetInSchemaName(object_name)); } return absl::StrJoin(to_join, kSeparator); } } // namespace absl::Status GlobalSchemaNames::AddName(absl::string_view type, const std::string& name) { if (!names_.insert(name).second) { return error::SchemaObjectAlreadyExists(type, name); } return absl::OkStatus(); } absl::StatusOr<std::string> GlobalSchemaNames::GenerateForeignKeyName( absl::string_view referencing_table_name, absl::string_view referenced_table_name) { ZETASQL_RET_CHECK(!referencing_table_name.empty()); ZETASQL_RET_CHECK(!referenced_table_name.empty()); std::string base = MakeBaseName("FK", {referencing_table_name, referenced_table_name}); return GenerateSequencedName("Foreign Key", base, MakeFingerprint(base)); } absl::StatusOr<std::string> GlobalSchemaNames::GenerateCheckConstraintName( absl::string_view table_name) { ZETASQL_RET_CHECK(!table_name.empty()); std::string base = MakeBaseName("CK", {table_name}); return GenerateSequencedName("Check Constraint", base, MakeFingerprint(base)); } std::string GlobalSchemaNames::GenerateSequencedName( absl::string_view type, absl::string_view base, absl::string_view fingerprint) { for (std::uint64_t sequence = 1; true; ++sequence) { std::string suffix = absl::StrCat(fingerprint, kSeparator, sequence); std::string name = MakeName(base, suffix); if (names_.insert(name).second) { ZETASQL_VLOG(1) << "Generated " << type << " name: " << name; return name; } } } absl::StatusOr<std::string> GlobalSchemaNames::GenerateManagedIndexName( absl::string_view table_name, const std::vector<std::string>& column_names, bool null_filtered, bool unique) { ZETASQL_RET_CHECK(!table_name.empty()); ZETASQL_RET_CHECK(!column_names.empty()); // Index column names. std::string columns = absl::StrJoin(column_names, kSeparator); // Base name = index prefix + table name + column names. std::string base = MakeBaseName("IDX", {table_name, columns}); // Index codes, possibly empty. std::string codes; if (unique) { // Single code of 'U' for unique indexes whether null-filtered or not. absl::StrAppend(&codes, "U"); } else if (null_filtered) { absl::StrAppend(&codes, "N"); } absl::string_view codes_separator = codes.empty() ? "" : kSeparator; // Signature = index prefix + table name + columns names + index codes. std::string signature = absl::StrCat(base, codes_separator, codes); // Fingerprint is based on the full, non-truncated signature of the index. std::string fingerprint = MakeFingerprint(signature); // Suffix = index codes + fingerprint. std::string suffix = absl::StrCat(codes, codes_separator, fingerprint); // Full name = truncated(index prefix + table name + column names) // + index codes + fingerprint. std::string name = MakeName(base, suffix); ZETASQL_VLOG(1) << "Generated managed index name: " << name; return name; } absl::Status GlobalSchemaNames::ValidateSchemaName(absl::string_view type, absl::string_view name) { ZETASQL_RET_CHECK(!name.empty()); const auto& [schema_part, name_part] = SDLObjectName::SplitSchemaName(name); if (!schema_part.empty()) { // Check is run for objects to ensure the named schema exists before making // it here, so minimal check will run on the schema_part. ZETASQL_RETURN_IF_ERROR(ValidateSchemaName(type, name_part)); if (!IsSDLTypeAllowedInNamedSchema(type)) { return error::SchemaObjectTypeUnsupportedInNamedSchema(type, name); } } if (name[0] == '_') { return error::InvalidSchemaName(type, name); } if (name.length() > limits::kMaxSchemaIdentifierLength) { return error::InvalidSchemaName(type, name); } return absl::OkStatus(); } absl::Status GlobalSchemaNames::ValidateNamedSchemaName( absl::string_view named_schema_name) { ZETASQL_RETURN_IF_ERROR(ValidateSchemaName("Schema", named_schema_name)); if (ReservedSchemaNames().contains(std::string(named_schema_name)) || // To avoid possible schema collision in the future, we block some prefix // below: // `spanner_{version}.{function}` could be used for built-in function // replacemet/deprecation purpose. absl::StartsWith(named_schema_name, "spanner_") // `pg_` prefix is used by couple postgresql system schemas, block whole // prefix for simplicity. || absl::StartsWith(named_schema_name, "pg_")) { return error::InvalidSchemaName("Schema", named_schema_name); } return absl::OkStatus(); }; absl::Status GlobalSchemaNames::ValidateConstraintName( absl::string_view table_name, absl::string_view constraint_type, absl::string_view constraint_name) { ZETASQL_RETURN_IF_ERROR(ValidateSchemaName(constraint_type, constraint_name)); for (absl::string_view reserved_prefix : {"PK_", "CK_IS_NOT_NULL_"}) { if (absl::StartsWithIgnoreCase(constraint_name, reserved_prefix)) { return error::InvalidConstraintName(constraint_type, constraint_name, reserved_prefix); } } return absl::OkStatus(); } const CaseInsensitiveStringSet& GlobalSchemaNames::ReservedSchemaNames() { // clang-format off // LINT.IfChange(reserved_schema_names) static const CaseInsensitiveStringSet* const kInstance = new CaseInsensitiveStringSet({ // Provided for in the sql standard, implemented by spanner. "INFORMATION_SCHEMA", // Provided for in the googlesql language as a function prefix, although not // a namespace there. "safe", // Provided for in googlesql as existing function namespaces. "aead", "kll_quantiles", "keys", "hll_count", "net", "ml", // The following are not reserved by googlesql, but provided by spanner. "SPANNER_PLACEMENT", "SPANNER_SYS", // Reserved for spanner, actually used in postgres syntax variant only. "pg_information_schema", "pg_catalog", "pg", "public", // Not provided by googlesql or spanner, but reserved because it exists in // the sql standard. "DEFINITION_SCHEMA", // `default` is used on UI and FGAC to refer our default schema. "default", // Used by PostgreSQL dialect as a prefix to refer spanner specific feature, // like spanner.commit_timestamp, spanner.{package_name}. "spanner" // We don't need to reserve "_mt" or any future system schema names starting // with "_" since users are already forbidden to create such system names. }); // LINT.ThenChange() // clang-format on return *kInstance; } bool GlobalSchemaNames::IsSDLTypeAllowedInNamedSchema(absl::string_view type) { return type == "Table" || type == "Synonym" || type == "View" || type == "Sequence" || type == "Index" || type == "Udf"; } } // namespace backend } // namespace emulator } // namespace spanner } // namespace google