backend/transaction/foreign_key_restrictions.cc (111 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/transaction/foreign_key_restrictions.h"
#include <set>
#include <string>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "backend/actions/ops.h"
#include "backend/datamodel/key.h"
#include "backend/datamodel/key_range.h"
#include "backend/schema/catalog/column.h"
#include "backend/schema/catalog/foreign_key.h"
#include "backend/schema/catalog/schema.h"
#include "common/errors.h"
namespace google {
namespace spanner {
namespace emulator {
namespace backend {
bool MutationContainReferencedColumn(
const std::vector<const Column*>& columns,
const absl::flat_hash_set<const Column*>& referenced_columns) {
bool has_referenced_columns = false;
for (const auto& column : columns) {
if (referenced_columns.contains(column)) {
has_referenced_columns = true;
break;
}
}
return has_referenced_columns;
}
absl::Status ForeignKeyRestrictions::ValidateReferencedDeleteMods(
const std::string& table_name, std::vector<KeyRange>& key_ranges) {
std::set<Key>& inserted_keys = inserted_keys_by_referenced_table_[table_name];
std::vector<KeyRange>& deleted_keys =
deleted_key_ranges_by_referenced_table_[table_name];
for (const auto& key_range : key_ranges) {
for (const Key& key : inserted_keys) {
if (key_range.Contains(key)) {
return error::ForeignKeyReferencedRestrictionInTransaction(
table_name, key_range.DebugString());
}
}
deleted_keys.push_back(key_range);
}
return absl::OkStatus();
}
absl::Status ForeignKeyRestrictions::ValidateReferencedMods(
const std::vector<WriteOp>& write_ops, const std::string& table_name,
const Schema* schema) {
if (!referenced_columns_by_referenced_table_.contains(table_name)) {
const Table* table = schema->FindTable(table_name);
for (auto fk : table->referencing_foreign_keys()) {
if (fk->on_delete_action() == ForeignKey::Action::kCascade) {
for (const auto& column : fk->referenced_columns()) {
referenced_columns_by_referenced_table_[table_name].insert(column);
}
}
}
}
std::set<Key>& inserted_keys = inserted_keys_by_referenced_table_[table_name];
std::vector<KeyRange>& deleted_keys =
deleted_key_ranges_by_referenced_table_[table_name];
for (const auto& write_op : write_ops) {
if (std::holds_alternative<DeleteOp>(write_op)) {
const DeleteOp delete_op = std::get<DeleteOp>(write_op);
if (inserted_keys.find(delete_op.key) != inserted_keys.end()) {
return error::ForeignKeyReferencedRestrictionInTransaction(
table_name, delete_op.key.DebugString());
} else {
deleted_keys.push_back(KeyRange(EndpointType::kClosed, delete_op.key,
EndpointType::kClosed, delete_op.key));
}
} else if (std::holds_alternative<InsertOp>(write_op)) {
const InsertOp insert_op = std::get<InsertOp>(write_op);
if (!MutationContainReferencedColumn(
insert_op.columns,
referenced_columns_by_referenced_table_[table_name])) {
continue;
}
for (const auto& key_range : deleted_keys) {
if (key_range.Contains(insert_op.key)) {
return error::ForeignKeyReferencedRestrictionInTransaction(
table_name, insert_op.key.DebugString());
}
}
inserted_keys.insert(insert_op.key);
} else if (std::holds_alternative<UpdateOp>(write_op)) {
const UpdateOp update_op = std::get<UpdateOp>(write_op);
if (!MutationContainReferencedColumn(
update_op.columns,
referenced_columns_by_referenced_table_[table_name])) {
continue;
}
for (const auto& key_range : deleted_keys) {
if (key_range.Contains(update_op.key)) {
return error::ForeignKeyReferencedRestrictionInTransaction(
table_name, update_op.key.DebugString());
}
}
inserted_keys.insert(update_op.key);
} else {
return absl::InvalidArgumentError("Unsupported write op type.");
}
}
return absl::OkStatus();
}
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google