backend/schema/updater/schema_validation_context.h (123 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.
//
#ifndef THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_CATALOG_SCHEMA_VALIDATION_CONTEXT_H_
#define THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_CATALOG_SCHEMA_VALIDATION_CONTEXT_H_
#include <memory>
#include <vector>
#include "zetasql/public/types/type_factory.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/time/time.h"
#include "backend/query/analyzer_options.h"
#include "backend/schema/catalog/proto_bundle.h"
#include "backend/schema/graph/schema_graph.h"
#include "backend/schema/graph/schema_node.h"
#include "backend/storage/storage.h"
#include "absl/status/status.h"
#include "zetasql/base/status_macros.h"
namespace google {
namespace spanner {
namespace emulator {
namespace backend {
class Schema;
class GlobalSchemaNames;
// A class used to collect and execute verification/backfill actions resulting
// from a schema change. A `SchemaChangeAction` object can be constructed and
// added to this context during processing/validation of schema changes that
// requires verifying that the data in the database conforms to an
// existing/newly added constraint/invariant or backfilling values in the
// database.
// Note: Currently SchemaChangeActions that modify the state of a database
// cannot be rolled back. Consequently, action execution should be atomic, i.e.
// the action should either apply all the effects entailed by the schema change
// or leave the datatabse unchanged.
class SchemaValidationContext {
public:
// The actual function doing the verification to which `context_` is passed.
using SchemaChangeAction =
std::function<absl::Status(const SchemaValidationContext*)>;
// A callback used to return an unowned instance of a Schema.
using SchemaConstructorCb = std::function<const Schema*(const SchemaGraph*)>;
// Test-only constructor.
// TODO : Split out into a separate Schema update validation
// context that is used by SchemaUpdater and can therefore be mocked
// separately.
SchemaValidationContext() = default;
SchemaValidationContext(Storage* storage, GlobalSchemaNames* global_names,
zetasql::TypeFactory* type_factory,
absl::Time pending_commit_timestamp,
DatabaseDialect dialect)
: storage_(storage),
global_names_(global_names),
type_factory_(type_factory),
pending_commit_timestamp_(pending_commit_timestamp),
dialect_(dialect) {}
~SchemaValidationContext() = default;
// TODO : Split out into a separate StatementValidationContext.
// Interface accessed by validators to enqueue schema
// change actions.
// -----------------------------------------------------
// Adds a SchemaChangeAction to this validation context.
void AddAction(SchemaChangeAction action_fn) {
actions_.emplace_back(std::move(action_fn));
}
// Interface used by a SchemaChangeAction to access the
// database
// --------------------------------------------------
// Provides SchemaVerifiers running under this validation context
// access to the database storage to perform reads.
Storage* storage() const { return storage_; }
// Access to the current schema's set of global names.
GlobalSchemaNames* global_names() const { return global_names_; }
// Access to the current database's type factory.
zetasql::TypeFactory* type_factory() const { return type_factory_; }
// Returns the pending commit timestamp.
absl::Time pending_commit_timestamp() const {
return pending_commit_timestamp_;
}
// Returns the Schema snapshot for the old/unmodified schema.
const Schema* old_schema() const { return old_schema_snapshot_; }
// Returns the Schema snapshot for the updated validated schema. This is only
// valid to call during the verification and backfill phase. Attempts to call
// this during the validation phase will result in undefined behavior. Callers
// wishing to access a temporary, unvalidated snapshot of the new schema
// during the validation phase should call `tmp_new_schema()` instead.
const Schema* validated_new_schema() const { return new_schema_snapshot_; }
// Returns the temporary schema snapshot of the new schema during the
// validation phase and is invalid to call during the verification and
// backfill phases. Callers should not hold on to any references to the
// returned schema or its nodes.
const Schema* tmp_new_schema() const { return tmp_new_schema_; }
bool is_postgresql_dialect() const {
return dialect_ == DatabaseDialect::POSTGRESQL;
}
// Interface accessed by SchemaUpdater to execute queued
// actions.
// -----------------------------------------------------
void SetOldSchemaSnapshot(const Schema* old_schema) {
old_schema_snapshot_ = old_schema;
}
void SetValidatedNewSchemaSnapshot(const Schema* new_schema) {
new_schema_snapshot_ = new_schema;
}
void SetTempNewSchemaSnapshotConstructor(
SchemaConstructorCb schema_constructor) {
tmp_new_schema_cb_ = std::move(schema_constructor);
}
// Runs all SchemaVerifiers added to this validation context.
absl::Status RunSchemaChangeActions() const {
for (auto& action : actions_) {
ZETASQL_RETURN_IF_ERROR(action(this));
}
return absl::OkStatus();
}
// Returns the number of pending schema change actions.
int num_actions() const { return actions_.size(); }
// Returns true if 'node' is a node that was modified using a DDL
// statement/operation as a part of the schema change associated
// with this SchemaValidationContext.
bool IsModifiedNode(const SchemaNode* node) const {
return edited_nodes_->contains(node);
}
// Returns the pointer to the first node added with a given name
// (case-insensitive) using the node's SchemaNameInfo. Returns nullptr if not
// found. Also returns nullptr if nodes with that name were found but were the
// wrong type.
template <typename T>
const T* FindAddedNode(absl::string_view name) const {
for (const auto& node : *added_nodes_) {
auto info = node->GetSchemaNameInfo().value_or(SchemaNameInfo{});
if (info.name == name) {
const T* candidate = node.get()->As<T>();
if (info.global || candidate != nullptr) {
return candidate;
}
}
}
return nullptr;
}
// This method is used to set the recently parsed Proto Bundle to schema
// validation context. This will be used during the validation of columns.
void set_proto_bundle(std::shared_ptr<const ProtoBundle> proto_bundle) {
proto_bundle_ = proto_bundle;
}
const ProtoBundle* proto_bundle() const { return proto_bundle_.get(); }
private:
friend class SchemaGraphEditor;
// Saves a pointer to the 'edited_nodes' maintained by the 'SchemaGraphEditor'
// to allow nodes to check through the SchemaValidationContext if they were
// modified by user action or just cloned during the processing of a schema
// update.
void set_edited_nodes(
const absl::flat_hash_set<const SchemaNode*>* edited_nodes) {
edited_nodes_ = edited_nodes;
}
const absl::flat_hash_set<const SchemaNode*>* edited_nodes_ = nullptr;
// Saves a pointer to the 'added_nodes' maintained by the 'SchemaGraphEditor'
// to allow nodes to check through the SchemaValidationContext if they were
// added by user action.
void set_added_nodes(
const std::vector<std::unique_ptr<const SchemaNode>>* added_nodes) {
added_nodes_ = added_nodes;
}
// Called by SchemaGraphEditor to expose a temporary `schema_graph` via
// the `Schema` interface to Validate/ValidateUpdate() functions during the
// validation phase.
void MakeNewTempSchemaSnapshot(const SchemaGraph* schema_graph) {
if (tmp_new_schema_cb_ == nullptr) return;
tmp_new_schema_ = tmp_new_schema_cb_(schema_graph);
}
void ClearNewTempSchemaSnapshot() { tmp_new_schema_ = nullptr; }
const std::vector<std::unique_ptr<const SchemaNode>>* added_nodes_ = nullptr;
// Used to read data from the database for verifiers. Not owned.
Storage* storage_;
// Global names for schema objects.
GlobalSchemaNames* global_names_;
// Type factory used for all ZetaSQL operations on the database.
zetasql::TypeFactory* type_factory_;
// Planned commit time for the schema change.
absl::Time pending_commit_timestamp_;
// The database dialect determines how the OIDs are validated.
DatabaseDialect dialect_;
// The list of pending schema change actions (verifications/backfills) to run.
std::vector<SchemaChangeAction> actions_;
// The old schema.
const Schema* old_schema_snapshot_ = nullptr;
// The new schema.
const Schema* new_schema_snapshot_ = nullptr;
// Callback to construct a temporary instance of the new Schema.
SchemaConstructorCb tmp_new_schema_cb_ = nullptr;
// Holds an instance of the temporary new schema during the validation phase.
// This instance is not owned by SchemaValidationContext but is guaranteed
// to be alive during the validation phase.
const Schema* tmp_new_schema_ = nullptr;
// Proto bundle for schema validation.
// Use this only during schema validation when the schema isn't created. Once
// the validation is completed, a schema object is created and has the proto
// bundle which can be accessed by schema->proto_bundle()
std::shared_ptr<const ProtoBundle> proto_bundle_;
};
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google
#endif // THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_CATALOG_SCHEMA_VALIDATION_CONTEXT_H_