backend/query/query_validator.cc (628 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/query/query_validator.h"
#include <optional>
#include <string>
#include <vector>
#include "zetasql/public/type.h"
#include "zetasql/resolved_ast/resolved_ast.h"
#include "zetasql/resolved_ast/resolved_node.h"
#include "zetasql/resolved_ast/resolved_node_kind.pb.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "backend/query/feature_filter/gsql_supported_functions.h"
#include "backend/query/feature_filter/sql_feature_filter.h"
#include "backend/query/query_context.h"
#include "backend/query/query_engine_options.h"
#include "backend/query/queryable_table.h"
#include "backend/schema/catalog/column.h"
#include "backend/schema/catalog/index.h"
#include "backend/schema/catalog/sequence.h"
#include "backend/schema/catalog/table.h"
#include "backend/transaction/read_write_transaction.h"
#include "common/constants.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 {
constexpr absl::string_view kSpannerQueryEngineHintPrefix = "spanner";
// Hints related to adaptive execution.
constexpr absl::string_view kHintEnableAdaptivePlans = "enable_adaptive_plans";
// Parameter sensitive plans hint
constexpr absl::string_view kHintParameterSensitive = "parameter_sensitive";
constexpr absl::string_view kHintParameterSensitiveAlways = "always";
constexpr absl::string_view kHintParameterSensitiveAuto = "auto";
constexpr absl::string_view kHintParameterSensitiveNever = "never";
// Indexes
constexpr absl::string_view kHintForceIndex = "force_index";
constexpr absl::string_view kHintBaseTable = "_base_table";
// Index strategy
constexpr absl::string_view kHintIndexStrategy = "index_strategy";
constexpr absl::string_view kHintIndexStrategyForceIndexUnion =
"force_index_union";
// Joins
constexpr absl::string_view kHintJoinForceOrder = "force_join_order";
constexpr absl::string_view kHintJoinMethod = "join_method";
constexpr absl::string_view kHintJoinTypeApply = "apply_join";
constexpr absl::string_view kHintJoinTypeHash = "hash_join";
constexpr absl::string_view kHintJoinTypeMerge = "merge_join";
constexpr absl::string_view kHintJoinTypePushBroadcastHashJoin =
"push_broadcast_hash_join";
constexpr absl::string_view kHintJoinBatch = "batch_mode";
// For join_method = hash_join
constexpr absl::string_view kHashJoinBuildSide = "hash_join_build_side";
constexpr absl::string_view kHashJoinBuildSideLeft = "build_left";
constexpr absl::string_view kHashJoinBuildSideRight = "build_right";
constexpr absl::string_view kHashJoinExecution = "hash_join_execution";
constexpr absl::string_view kHashJoinExecutionOnePass = "one_pass";
constexpr absl::string_view kHashJoinExecutionMultiPass = "multi_pass";
// Group by
constexpr absl::string_view kHintGroupMethod = "group_method";
constexpr absl::string_view kHintGroupMethodHash = "hash_group";
constexpr absl::string_view kHintGroupMethodStream = "stream_group";
// Specialized transformations
constexpr absl::string_view kHintConstantFolding = "constant_folding";
constexpr absl::string_view kHintTableScanGroupByScanOptimization =
"groupby_scan_optimization";
// Runtime hints
constexpr absl::string_view kUseAdditionalParallelism =
"use_additional_parallelism";
// Lock scanned ranges
constexpr absl::string_view kHintLockScannedRange = "lock_scanned_ranges";
constexpr absl::string_view kHintLockScannedRangeShared = "shared";
constexpr absl::string_view kHintLockScannedRangeExclusive = "exclusive";
constexpr absl::string_view kHintGroupTypeDeprecated = "group_type";
constexpr absl::string_view kHintJoinTypeDeprecated = "join_type";
constexpr absl::string_view kHintJoinTypeNestedLoopDeprecated = "loop_join";
// Emulator-specific hints that can be used to set QueryEngineOptions.
constexpr absl::string_view kEmulatorQueryEngineHintPrefix = "spanner_emulator";
constexpr absl::string_view kHintDisableQueryPartitionabilityCheck =
"disable_query_partitionability_check";
constexpr absl::string_view kHintDisableQueryNullFilteredIndexCheck =
"disable_query_null_filtered_index_check";
constexpr absl::string_view kHintDisableInline = "disable_inline";
constexpr absl::string_view kHintAllowSearchIndexesInTransaction =
"allow_search_indexes_in_transaction";
constexpr absl::string_view kRequireEnhanceQuery = "require_enhance_query";
constexpr absl::string_view kEnhanceQueryTimeoutMs = "enhance_query_timeout_ms";
constexpr absl::string_view kScanMethod = "scan_method";
constexpr absl::string_view kScanMethodBatch = "batch";
constexpr absl::string_view kScanMethodRow = "row";
absl::Status CollectHintsForNode(
const zetasql::ResolvedOption* hint,
absl::flat_hash_map<absl::string_view, zetasql::Value>* node_hint_map) {
ZETASQL_RET_CHECK_EQ(hint->value()->node_kind(), zetasql::RESOLVED_LITERAL);
const zetasql::Value& value =
hint->value()->GetAs<zetasql::ResolvedLiteral>()->value();
if (node_hint_map->contains(hint->name())) {
return error::MultipleValuesForSameHint(hint->name());
}
(*node_hint_map)[hint->name()] = value;
return absl::OkStatus();
}
} // namespace
bool IsSearchQueryAllowed(const QueryEngineOptions* options,
const QueryContext& context) {
bool allow_search_indexes_in_transaction =
options != nullptr && options->allow_search_indexes_in_transaction;
if (!allow_search_indexes_in_transaction) {
if (context.writer != nullptr &&
dynamic_cast<const ReadWriteTransaction*>(context.writer) != nullptr) {
// Not allowed in ReadWriteTransaction.
return false;
}
}
return true;
}
bool IsSelectForUpdateQuery(const zetasql::ResolvedNode& node) {
std::vector<const zetasql::ResolvedNode*> scan_nodes;
node.GetDescendantsSatisfying(
&zetasql::ResolvedNode::Is<zetasql::ResolvedLockMode>, &scan_nodes);
return !scan_nodes.empty();
}
absl::Status QueryValidator::ValidateHints(
const zetasql::ResolvedNode* node) {
std::vector<const zetasql::ResolvedNode*> child_nodes;
node->GetChildNodes(&child_nodes);
// Process the hints for each node, using maps to keep track of
// the hints for each node.
absl::flat_hash_map<absl::string_view, zetasql::Value> hint_map;
absl::flat_hash_map<absl::string_view, zetasql::Value> emulator_hint_map;
for (const zetasql::ResolvedNode* child_node : child_nodes) {
if (child_node->node_kind() == zetasql::RESOLVED_OPTION) {
const zetasql::ResolvedOption* hint =
child_node->GetAs<zetasql::ResolvedOption>();
if (absl::EqualsIgnoreCase(hint->qualifier(),
kSpannerQueryEngineHintPrefix) ||
hint->qualifier().empty()) {
ZETASQL_RETURN_IF_ERROR(CheckSpannerHintName(hint->name(), node->node_kind()));
ZETASQL_RETURN_IF_ERROR(CollectHintsForNode(hint, &hint_map));
} else if (absl::EqualsIgnoreCase(hint->qualifier(),
kEmulatorQueryEngineHintPrefix)) {
ZETASQL_RETURN_IF_ERROR(CheckEmulatorHintName(hint->name(), node->node_kind()));
ZETASQL_RETURN_IF_ERROR(CollectHintsForNode(hint, &emulator_hint_map));
} else {
// Ignore hints intended for other engines. Mark the value used so an
// 'Unimplemented' error is not raised.
hint->value()->MarkFieldsAccessed();
continue;
}
}
}
for (const auto& [hint_name, hint_value] : hint_map) {
ZETASQL_RETURN_IF_ERROR(
CheckHintValue(hint_name, hint_value, node->node_kind(), hint_map));
}
ZETASQL_RETURN_IF_ERROR(ExtractSpannerOptionsForNode(hint_map));
// Extract any Emulator-engine options from the hints for this node.
return ExtractEmulatorOptionsForNode(emulator_hint_map);
}
absl::Status QueryValidator::CheckSpannerHintName(
absl::string_view name, const zetasql::ResolvedNodeKind node_kind) const {
static const auto* supported_hints = new const absl::flat_hash_map<
zetasql::ResolvedNodeKind,
absl::flat_hash_set<absl::string_view, zetasql_base::StringViewCaseHash,
zetasql_base::StringViewCaseEqual>>{
{zetasql::RESOLVED_TABLE_SCAN,
{kHintForceIndex, kHintTableScanGroupByScanOptimization,
kHintIndexStrategy, kScanMethod}},
{zetasql::RESOLVED_JOIN_SCAN,
{kHintJoinTypeDeprecated, kHintJoinMethod, kHashJoinBuildSide,
kHintJoinForceOrder, kHashJoinExecution}},
{zetasql::RESOLVED_AGGREGATE_SCAN,
{kHintGroupTypeDeprecated, kHintGroupMethod}},
{zetasql::RESOLVED_ARRAY_SCAN,
{kHintJoinTypeDeprecated, kHintJoinMethod, kHashJoinBuildSide,
kHintJoinBatch, kHintJoinForceOrder}},
{zetasql::RESOLVED_QUERY_STMT,
{
kHintForceIndex,
kHintIndexStrategy,
kHintJoinTypeDeprecated,
kHintJoinMethod,
kHashJoinBuildSide,
kHintJoinForceOrder,
kHintConstantFolding,
kUseAdditionalParallelism,
kHintEnableAdaptivePlans,
kHintLockScannedRange,
kHintParameterSensitive,
kHashJoinExecution,
kHintAllowSearchIndexesInTransaction,
kRequireEnhanceQuery,
kEnhanceQueryTimeoutMs,
kScanMethod,
}},
{zetasql::RESOLVED_SUBQUERY_EXPR,
{kHintJoinTypeDeprecated, kHintJoinMethod, kHashJoinBuildSide,
kHintJoinBatch, kHintJoinForceOrder, kHashJoinExecution}},
{zetasql::RESOLVED_SET_OPERATION_SCAN,
{kHintJoinMethod, kHintJoinForceOrder}},
{zetasql::RESOLVED_FUNCTION_CALL, {kHintDisableInline}}};
const auto& iter = supported_hints->find(node_kind);
if (iter == supported_hints->end() || !iter->second.contains(name)) {
return error::InvalidHint(name);
}
return absl::OkStatus();
}
absl::Status QueryValidator::CheckEmulatorHintName(
absl::string_view name, const zetasql::ResolvedNodeKind node_kind) const {
static const auto* supported_hints = new const absl::flat_hash_map<
zetasql::ResolvedNodeKind,
absl::flat_hash_set<absl::string_view, zetasql_base::StringViewCaseHash,
zetasql_base::StringViewCaseEqual>>{
{zetasql::RESOLVED_TABLE_SCAN,
{kHintDisableQueryNullFilteredIndexCheck}},
{zetasql::RESOLVED_QUERY_STMT,
{kHintDisableQueryPartitionabilityCheck,
kHintDisableQueryNullFilteredIndexCheck}},
};
const auto& iter = supported_hints->find(node_kind);
if (iter == supported_hints->end() || !iter->second.contains(name)) {
return error::InvalidEmulatorHint(name);
}
return absl::OkStatus();
}
absl::Status QueryValidator::CheckHintValue(
absl::string_view name, const zetasql::Value& value,
const zetasql::ResolvedNodeKind node_kind,
const absl::flat_hash_map<absl::string_view, zetasql::Value>& hint_map) {
static const auto* supported_hint_types =
new absl::flat_hash_map<absl::string_view, const zetasql::Type*,
zetasql_base::StringViewCaseHash, zetasql_base::StringViewCaseEqual>{{
{kHintJoinTypeDeprecated, zetasql::types::StringType()},
{kHintParameterSensitive, zetasql::types::StringType()},
{kHintJoinMethod, zetasql::types::StringType()},
{kHashJoinBuildSide, zetasql::types::StringType()},
{kHashJoinExecution, zetasql::types::StringType()},
{kHintJoinBatch, zetasql::types::BoolType()},
{kHintJoinForceOrder, zetasql::types::BoolType()},
{kHintGroupTypeDeprecated, zetasql::types::StringType()},
{kHintGroupMethod, zetasql::types::StringType()},
{kHintForceIndex, zetasql::types::StringType()},
{kUseAdditionalParallelism, zetasql::types::BoolType()},
{kHintLockScannedRange, zetasql::types::StringType()},
{kHintConstantFolding, zetasql::types::BoolType()},
{kHintTableScanGroupByScanOptimization, zetasql::types::BoolType()},
{kHintEnableAdaptivePlans, zetasql::types::BoolType()},
{kHintDisableInline, zetasql::types::BoolType()},
{kHintIndexStrategy, zetasql::types::StringType()},
{kHintAllowSearchIndexesInTransaction, zetasql::types::BoolType()},
{kRequireEnhanceQuery, zetasql::types::BoolType()},
{kEnhanceQueryTimeoutMs, zetasql::types::Int64Type()},
{kScanMethod, zetasql::types::StringType()},
}};
const auto& iter = supported_hint_types->find(name);
ZETASQL_RET_CHECK(iter != supported_hint_types->cend());
if (!value.type()->Equals(iter->second)) {
return error::InvalidHintValue(name, value.DebugString());
}
if (absl::EqualsIgnoreCase(name, kHintForceIndex)) {
const std::string& index_name = value.string_value();
bool base_table_hint = absl::EqualsIgnoreCase(index_name, kHintBaseTable);
if (!base_table_hint) {
// Statement-level FORCE_INDEX hints can only be '_BASE_TABLE'.
if (node_kind == zetasql::RESOLVED_QUERY_STMT) {
return error::InvalidStatementHintValue(name, value.DebugString());
}
const std::vector<const Index*> indexes =
context_.schema->FindIndexesUnderName(index_name);
if (indexes.empty()) {
// We don't have the table name here. So this will not match prod error
// message.
return error::QueryHintIndexNotFound("", index_name);
}
for (const auto& index : indexes) {
indexes_used_.insert(index);
}
}
} else if (absl::EqualsIgnoreCase(name, kHintJoinMethod) ||
absl::EqualsIgnoreCase(name, kHintJoinTypeDeprecated)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kHintJoinTypeApply) ||
absl::EqualsIgnoreCase(string_value, kHintJoinTypeHash) ||
absl::EqualsIgnoreCase(string_value, kHintJoinTypeMerge) ||
absl::EqualsIgnoreCase(string_value,
kHintJoinTypePushBroadcastHashJoin) ||
absl::EqualsIgnoreCase(string_value,
kHintJoinTypeNestedLoopDeprecated))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHintParameterSensitive)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kHintParameterSensitiveAlways) ||
absl::EqualsIgnoreCase(string_value, kHintParameterSensitiveAuto) ||
absl::EqualsIgnoreCase(string_value, kHintParameterSensitiveNever))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHashJoinBuildSide)) {
bool is_hash_join = [&]() {
auto it = hint_map.find(kHintJoinMethod);
if (it != hint_map.end() &&
absl::EqualsIgnoreCase(it->second.string_value(),
kHintJoinTypeHash)) {
return true;
}
it = hint_map.find(kHintJoinTypeDeprecated);
if (it != hint_map.end() &&
absl::EqualsIgnoreCase(it->second.string_value(),
kHintJoinTypeHash)) {
return true;
}
return false;
}();
if (!is_hash_join) {
return error::InvalidHintForNode(kHashJoinBuildSide, "HASH joins");
}
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kHashJoinBuildSideLeft) ||
absl::EqualsIgnoreCase(string_value, kHashJoinBuildSideRight))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHashJoinExecution)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kHashJoinExecutionOnePass) ||
absl::EqualsIgnoreCase(string_value, kHashJoinExecutionMultiPass))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHintGroupMethod) ||
absl::EqualsIgnoreCase(name, kHintGroupTypeDeprecated)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kHintGroupMethodHash) ||
absl::EqualsIgnoreCase(string_value, kHintGroupMethodStream))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHintLockScannedRange)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value,
kHintLockScannedRangeExclusive) ||
absl::EqualsIgnoreCase(string_value, kHintLockScannedRangeShared))) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kHintIndexStrategy)) {
const std::string& string_value = value.string_value();
if (!absl::EqualsIgnoreCase(string_value,
kHintIndexStrategyForceIndexUnion)) {
return error::InvalidHintValue(name, value.DebugString());
}
} else if (absl::EqualsIgnoreCase(name, kScanMethod)) {
const std::string& string_value = value.string_value();
if (!(absl::EqualsIgnoreCase(string_value, kScanMethodBatch) ||
absl::EqualsIgnoreCase(string_value, kScanMethodRow))) {
return error::InvalidHintValue(name, value.DebugString());
}
}
return absl::OkStatus();
}
absl::Status QueryValidator::ExtractSpannerOptionsForNode(
const absl::flat_hash_map<absl::string_view, zetasql::Value>&
node_hint_map) {
// Only extract options if they are requested.
if (extracted_options_ == nullptr) {
return absl::OkStatus();
}
for (const auto& [hint_name, hint_value] : node_hint_map) {
if (absl::EqualsIgnoreCase(hint_name,
kHintAllowSearchIndexesInTransaction)) {
// We already checked the hint type in CheckHintValue.
extracted_options_->allow_search_indexes_in_transaction =
hint_value.bool_value();
}
if (absl::EqualsIgnoreCase(hint_name, kHintLockScannedRange)) {
// We already checked the hint type in CheckHintValue.
query_features_.has_lock_scanned_ranges = true;
}
}
return absl::OkStatus();
}
absl::Status QueryValidator::ExtractEmulatorOptionsForNode(
const absl::flat_hash_map<absl::string_view, zetasql::Value>&
node_hint_map) const {
// Only extract options if they are requested.
if (extracted_options_ == nullptr) return absl::OkStatus();
for (const auto& [hint_name, hint_value] : node_hint_map) {
if (absl::EqualsIgnoreCase(hint_name,
kHintDisableQueryPartitionabilityCheck)) {
if (!hint_value.type()->IsBool()) {
return error::InvalidEmulatorHintValue(hint_name,
hint_value.DebugString());
} else {
extracted_options_->disable_query_partitionability_check =
hint_value.bool_value();
}
}
if (absl::EqualsIgnoreCase(hint_name,
kHintDisableQueryNullFilteredIndexCheck)) {
if (!hint_value.type()->IsBool()) {
return error::InvalidEmulatorHintValue(hint_name,
hint_value.DebugString());
} else {
extracted_options_->disable_query_null_filtered_index_check =
hint_value.bool_value();
}
}
}
return absl::OkStatus();
}
absl::Status QueryValidator::CheckTableScanLockModeAllowed(
const QueryEngineOptions* options, const QueryContext& context) const {
// FOR UPDATE can't be run in a read-only transaction.
if (context.is_read_only_txn != std::nullopt &&
context.is_read_only_txn.value()) {
return error::ForUpdateUnsupportedInReadOnlyTransactions();
}
// FOR UPDATE can't be combined with lock_scanned_ranges hints.
if (query_features_.has_lock_scanned_ranges) {
return error::ForUpdateCannotCombineWithLockScannedRanges();
}
return absl::OkStatus();
}
namespace {
absl::Status CheckAllowedCasts(const zetasql::Type* from_type,
const zetasql::Type* to_type) {
if (to_type->IsArray() && from_type->IsArray() &&
!to_type->AsArray()->element_type()->Equals(
from_type->AsArray()->element_type())) {
return error::NoFeatureSupportDifferentTypeArrayCasts(
from_type->DebugString(), to_type->DebugString());
}
return absl::OkStatus();
}
} // namespace
absl::Status QueryValidator::DefaultVisit(const zetasql::ResolvedNode* node) {
ZETASQL_RETURN_IF_ERROR(ValidateHints(node));
return zetasql::ResolvedASTVisitor::DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedQueryStmt(
const zetasql::ResolvedQueryStmt* node) {
for (const auto& column : node->output_column_list()) {
if (column->column().type()->IsStruct())
return error::UnsupportedReturnStructAsColumn();
}
if (IsSelectForUpdateQuery(*node)) {
query_features_.has_for_update = true;
}
ZETASQL_RETURN_IF_ERROR(DefaultVisit(node));
if (IsSelectForUpdateQuery(*node)) {
ZETASQL_RETURN_IF_ERROR(
CheckTableScanLockModeAllowed(extracted_options_, context_));
}
return absl::OkStatus();
}
absl::Status QueryValidator::CheckSearchFunctionsAreAllowed(
const zetasql::ResolvedFunctionCall& function_call) {
static const auto* search_functions =
new const absl::flat_hash_set<absl::string_view, zetasql_base::StringViewCaseHash,
zetasql_base::StringViewCaseEqual>{
"search", "search_substring", "score", "snippet"};
const std::string name = function_call.function()->FullName(false);
if (search_functions->find(name) != search_functions->end()) {
if (in_partition_query_) {
// Not allowed in batch query.
return error::InvalidUseOfSearchRelatedFunctionWithReason(
"SQL Search functions are not supported for partitioned queries");
}
if (!IsSearchQueryAllowed(extracted_options_, context_)) {
return error::InvalidUseOfSearchRelatedFunctionWithReason(
"SQL Search functions are not supported for transactional "
"queries by default");
};
if (query_features_.has_for_update) {
return error::ForUpdateUnsupportedInSearchQueries();
}
}
return absl::OkStatus();
}
absl::Status QueryValidator::VisitResolvedLiteral(
const zetasql::ResolvedLiteral* node) {
if (node->type()->IsArray() &&
node->type()->AsArray()->element_type()->IsStruct() &&
node->value().is_empty_array()) {
return error::UnsupportedArrayConstructorSyntaxForEmptyStructArray();
}
return DefaultVisit(node);
}
bool QueryValidator::IsReadWriteOnlyFunction(absl::string_view name) const {
if (name == kGetNextSequenceValueFunctionName) {
return true;
}
return false;
}
absl::Status QueryValidator::VisitResolvedFunctionCall(
const zetasql::ResolvedFunctionCall* node) {
// Check if function is part of supported subset of ZetaSQL
if (node->function()->IsZetaSQLBuiltin()) {
if (!IsSupportedZetaSQLFunction(*node->function())) {
return error::UnsupportedFunction(node->function()->SQLName());
}
}
// Out of the supported subset of ZetaSQL, filter out functions that
// are unimplemented or may require additional validation of arguments.
ZETASQL_RETURN_IF_ERROR(FilterSafeModeFunction(*node));
ZETASQL_RETURN_IF_ERROR(
FilterResolvedFunction(language_options_, sql_features_, *node));
if (node->function()->FullName(/*include_group=*/false) == "cast") {
ZETASQL_RETURN_IF_ERROR(
CheckAllowedCasts(node->argument_list(0)->type(), node->type()));
}
ZETASQL_RETURN_IF_ERROR(CheckSearchFunctionsAreAllowed(*node));
if (IsSequenceFunction(node)) {
ZETASQL_RETURN_IF_ERROR(ValidateSequenceFunction(node));
}
if (!context_.allow_read_write_only_functions &&
IsReadWriteOnlyFunction(
node->function()->FullName(/*include_group=*/false))) {
return error::ReadOnlyTransactionDoesNotSupportReadWriteOnlyFunctions(
node->function()->FullName(/*include_group=*/false));
}
return DefaultVisit(node);
}
bool QueryValidator::IsSequenceFunction(
const zetasql::ResolvedFunctionCall* node) const {
return (node->function()->FullName(/*include_group=*/false) ==
kGetNextSequenceValueFunctionName ||
node->function()->FullName(/*include_group=*/false) ==
kGetInternalSequenceStateFunctionName);
}
absl::Status QueryValidator::ValidateSequenceFunction(
const zetasql::ResolvedFunctionCall* node) {
if (schema()->dialect() ==
database_api::DatabaseDialect::GOOGLE_STANDARD_SQL) {
if (node->generic_argument_list_size() != 1 ||
node->generic_argument_list(0)->sequence() == nullptr) {
return error::NoMatchingFunctionSignature(
node->function()->FullName(/*include_group=*/false), "SEQUENCE");
}
const std::string sequence_name =
node->generic_argument_list(0)->sequence()->sequence()->FullName();
const Sequence* current_sequence =
schema()->FindSequence(sequence_name,
/*exclude_internal=*/true);
if (current_sequence == nullptr) {
return error::SequenceNotFound(sequence_name);
}
dependent_sequences_.insert(current_sequence);
return absl::OkStatus();
}
// This is when the dialect is PostgreSQL.
if (node->argument_list_size() == 1 &&
node->argument_list(0)->node_kind() == zetasql::RESOLVED_LITERAL) {
const zetasql::Value& value =
node->argument_list(0)->GetAs<zetasql::ResolvedLiteral>()->value();
if (value.type()->IsString()) {
const Sequence* current_sequence =
schema()->FindSequence(value.string_value(),
/*exclude_internal=*/true);
if (current_sequence == nullptr) {
return error::SequenceNotFound(value.string_value());
}
dependent_sequences_.insert(current_sequence);
}
}
return absl::OkStatus();
}
absl::Status QueryValidator::VisitResolvedAggregateFunctionCall(
const zetasql::ResolvedAggregateFunctionCall* node) {
// Check if function is part of supported subset of ZetaSQL
if (node->function()->IsZetaSQLBuiltin()) {
if (!IsSupportedZetaSQLFunction(*node->function())) {
return error::UnsupportedFunction(node->function()->SQLName());
}
}
// Out of the supported subset of ZetaSQL, filter out functions that
// are unimplemented or may require additional validation of arguments.
ZETASQL_RETURN_IF_ERROR(FilterSafeModeFunction(*node));
ZETASQL_RETURN_IF_ERROR(FilterResolvedAggregateFunction(sql_features_, *node));
return DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedCast(
const zetasql::ResolvedCast* node) {
ZETASQL_RETURN_IF_ERROR(CheckAllowedCasts(node->expr()->type(), node->type()));
return DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedSampleScan(
const zetasql::ResolvedSampleScan* node) {
if (node->repeatable_argument()) {
return error::UnsupportedTablesampleRepeatable();
}
if (absl::EqualsIgnoreCase(node->method(), "system")) {
return error::UnsupportedTablesampleSystem();
}
return DefaultVisit(node);
}
absl::Status QueryValidator::CheckPendingCommitTimestampReads(
const zetasql::ResolvedTableScan* table_scan,
absl::Span<const zetasql::ResolvedStatement::ObjectAccess> access_list) {
ZETASQL_RET_CHECK(access_list.empty() ||
access_list.size() == table_scan->column_index_list_size());
// A commit timestamp tracker is not always present (e.g. read-only txns).
if (context_.commit_timestamp_tracker == nullptr) {
return absl::OkStatus();
}
// Any table in the user schema will be a QueryableTable. We use this property
// to skip table scans against system tables (e.g. information_schema.tables)
// since these tables do not have corresponding backend schema nodes.
//
// Skipping these tables is safe because they are not writable and do not
// contain commit timestamps (pending or otherwise).
if (!table_scan->table()->Is<QueryableTable>()) {
return absl::OkStatus();
}
const Table* table =
table_scan->table()->GetAs<QueryableTable>()->wrapped_table();
ZETASQL_RET_CHECK(table != nullptr);
std::vector<const Column*> columns;
for (int i = 0; i < table_scan->column_index_list_size(); ++i) {
// Ignore scan columns which are not read
if (i < access_list.size() &&
!(access_list[i] & zetasql::ResolvedStatement::READ)) {
continue;
}
int idx = table_scan->column_index_list(i);
std::string column_name = table_scan->table()->GetColumn(idx)->Name();
const Column* column = table->FindColumn(column_name);
ZETASQL_RET_CHECK(column != nullptr);
columns.push_back(column);
}
return context_.commit_timestamp_tracker->CheckRead(table, columns);
}
absl::Status QueryValidator::VisitResolvedInsertStmt(
const zetasql::ResolvedInsertStmt* node) {
if (node->table_scan() != nullptr) {
dml_table_scans_.insert(node->table_scan());
}
return DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedUpdateStmt(
const zetasql::ResolvedUpdateStmt* node) {
if (node->table_scan() != nullptr) {
ZETASQL_RETURN_IF_ERROR(CheckPendingCommitTimestampReads(
node->table_scan(), node->column_access_list()));
dml_table_scans_.insert(node->table_scan());
}
return DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedDeleteStmt(
const zetasql::ResolvedDeleteStmt* node) {
if (node->table_scan() != nullptr) {
ZETASQL_RETURN_IF_ERROR(CheckPendingCommitTimestampReads(
node->table_scan(), node->column_access_list()));
dml_table_scans_.insert(node->table_scan());
}
return DefaultVisit(node);
}
absl::Status QueryValidator::VisitResolvedTableScan(
const zetasql::ResolvedTableScan* node) {
// Skip table scans owned by DML statements. These have already been handled.
if (!dml_table_scans_.contains(node)) {
ZETASQL_RETURN_IF_ERROR(CheckPendingCommitTimestampReads(node));
}
return DefaultVisit(node);
}
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google