backend/schema/updater/schema_updater_tests/udf.cc (870 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/catalog/udf.h"
#include <memory>
#include <vector>
#include "zetasql/public/json_value.h"
#include "zetasql/public/numeric_value.h"
#include "zetasql/public/types/type_factory.h"
#include "zetasql/public/value.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "zetasql/base/testing/status_matchers.h"
#include "tests/common/proto_matchers.h"
#include "absl/status/status.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "backend/schema/catalog/column.h"
#include "backend/schema/catalog/index.h"
#include "backend/schema/catalog/named_schema.h"
#include "backend/schema/catalog/schema.h"
#include "backend/schema/catalog/sequence.h"
#include "backend/schema/catalog/table.h"
#include "backend/schema/catalog/view.h"
#include "backend/schema/graph/schema_node.h"
#include "backend/schema/updater/schema_updater_tests/base.h"
#include "common/errors.h"
namespace google {
namespace spanner {
namespace emulator {
namespace backend {
namespace test {
// For the following tests, a custom PG DDL statement is required as translating
// expressions from GSQL to PG is not supported in tests.
using absl::StatusCode;
using database_api::DatabaseDialect::POSTGRESQL;
using zetasql::Value;
using ::testing::HasSubstr;
TEST_P(SchemaUpdaterTest, CreateUDF_Basic) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))"}));
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "x+1");
EXPECT_EQ(udf->determinism_level(), Udf::Determinism::DETERMINISTIC);
const absl::Span<const Udf* const> udfs = schema->udfs();
ASSERT_EQ(udfs.size(), 1);
EXPECT_EQ(udfs[0]->Name(), "udf_1");
EXPECT_EQ(udfs[0]->body(), "x+1");
EXPECT_EQ(udf->signature()->DebugString(), "(INT64 x) -> INT64");
ZETASQL_ASSERT_OK_AND_ASSIGN(schema,
UpdateSchema(schema.get(), {R"(DROP FUNCTION udf_1)"}));
udf = schema->FindUdf("udf_1");
ASSERT_EQ(udf, nullptr);
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema, UpdateSchema(schema.get(), {R"(DROP FUNCTION IF EXISTS udf_1)"}));
EXPECT_THAT(UpdateSchema(schema.get(), {R"(DROP FUNCTION udf_1)"}),
StatusIs(error::FunctionNotFound("udf_1")));
// Ensure the emulator can identify the determinism level of the UDF.
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
CreateSchema(
{R"(CREATE FUNCTION NOW(x INT64) RETURNS TIMESTAMP SQL SECURITY
INVOKER AS ((SELECT CURRENT_TIMESTAMP())))"}));
udf = schema->FindUdf("NOW");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "NOW");
EXPECT_EQ(udf->body(), "(SELECT CURRENT_TIMESTAMP())");
EXPECT_EQ(udf->determinism_level(),
Udf::Determinism::NOT_DETERMINISTIC_STABLE);
// Ensure the emulator can handle UDFs with default arguments.
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
CreateSchema(
{R"(CREATE FUNCTION test_udf(x INT64 DEFAULT 1) RETURNS INT64 SQL
SECURITY INVOKER AS (x+1))"}));
udf = schema->FindUdf("test_udf");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "test_udf");
EXPECT_EQ(udf->body(), "x+1");
EXPECT_EQ(udf->signature()->DebugString(), "(optional INT64 x) -> INT64");
EXPECT_EQ(udf->signature()->arguments()[0].GetDefault().value(),
zetasql::Value::Int64(1));
}
TEST_P(SchemaUpdaterTest, CreateUDF_ParameterizedTypes) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema({R"(CREATE FUNCTION func(a STRING(10)) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CONCAT(a, ' world')))"}),
::zetasql_base::testing::StatusIs(
StatusCode::kInvalidArgument,
HasSubstr(
"Parameterized types are not supported in function arguments")));
EXPECT_THAT(
CreateSchema({R"(CREATE FUNCTION func(a STRING) RETURNS STRING(10)
SQL SECURITY INVOKER AS (SUBSTR(a, 1, 10)))"}),
StatusIs(error::FunctionTypeMismatch("func", "STRING(10)", "STRING")));
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({R"(CREATE FUNCTION func(a STRING) RETURNS STRING
SQL SECURITY INVOKER AS (SUBSTR(a, 1, 10)))"}));
const Udf* udf = schema->FindUdf("func");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "func");
EXPECT_EQ(udf->body(), "SUBSTR(a, 1, 10)");
EXPECT_EQ(udf->determinism_level(), Udf::Determinism::DETERMINISTIC);
EXPECT_EQ(udf->signature()->DebugString(), "(STRING a) -> STRING");
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
CreateSchema({R"(CREATE FUNCTION func(a STRING) RETURNS ARRAY<STRING>
SQL SECURITY INVOKER AS ([a]))"}));
udf = schema->FindUdf("func");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "func");
EXPECT_EQ(udf->body(), "[a]");
EXPECT_EQ(udf->determinism_level(), Udf::Determinism::DETERMINISTIC);
EXPECT_EQ(udf->signature()->DebugString(), "(STRING a) -> ARRAY<STRING>");
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
CreateSchema({R"(CREATE FUNCTION func(a ARRAY<STRING>) RETURNS STRING
SQL SECURITY INVOKER AS (ARRAY_TO_STRING(a, ', ')))"}));
udf = schema->FindUdf("func");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "func");
EXPECT_EQ(udf->body(), "ARRAY_TO_STRING(a, ', ')");
EXPECT_EQ(udf->determinism_level(), Udf::Determinism::DETERMINISTIC);
EXPECT_EQ(udf->signature()->DebugString(), "(ARRAY<STRING> a) -> STRING");
}
TEST_P(SchemaUpdaterTest, CreateUDF_DuplicateName) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+2))"}),
StatusIs(error::SchemaObjectAlreadyExists("Function", "udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_FunctionTypeMismatch) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS STRING(MAX) SQL SECURITY INVOKER
AS (x+1))"}),
StatusIs(error::FunctionTypeMismatch("udf_1", "STRING(MAX)", "INT64")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_ReplaceBuiltInFunction) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema({R"(CREATE OR REPLACE FUNCTION abs(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (x+1))"}),
StatusIs(error::ReplacingBuiltInFunction("create or replace", "Function",
"abs")));
EXPECT_THAT(
CreateSchema(
{R"(CREATE VIEW abs SQL SECURITY INVOKER AS SELECT 1 AS col1)"}),
StatusIs(error::ReplacingBuiltInFunction("create", "View", "abs")));
// Can have the same name as a built-in function if in named schema.
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE SCHEMA my_schema)",
R"(CREATE FUNCTION my_schema.abs(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (x+1))"}));
const NamedSchema* my_schema = schema->FindNamedSchema("my_schema");
ASSERT_NE(my_schema, nullptr);
const Udf* udf = my_schema->FindUdf("my_schema.abs");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "my_schema.abs");
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidBodyAnalysis) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema({R"(CREATE FUNCTION func(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (x+func_2(x)))"}),
::zetasql_base::testing::StatusIs(
StatusCode::kInvalidArgument,
HasSubstr("Error parsing the definition of function `func`")));
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION func(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))"}));
const Udf* udf = schema->FindUdf("func");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "func");
EXPECT_THAT(UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION func(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (x+func_2(x)))"}),
::zetasql_base::testing::StatusIs(
StatusCode::kFailedPrecondition,
HasSubstr("Cannot replace FUNCTION `func` "
"because new definition is invalid")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_CreateOrReplace) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE OR REPLACE FUNCTION udf_1(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (x+1))"}));
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "x+1");
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (x+2))"}));
udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "x+2");
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithTableDepedency) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((SELECT MAX(t.col1) FROM t) + x))"}));
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "(SELECT MAX(t.col1) FROM t) + x");
EXPECT_EQ(udf->dependencies().size(), 2);
EXPECT_THAT(udf->dependencies(),
testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{t, t->FindColumn("col1")})));
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP TABLE t)"}),
StatusIs(error::InvalidDropDependentFunction("TABLE", "t", "udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithViewDepedency) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE VIEW v SQL SECURITY INVOKER AS SELECT MAX(t.col1) AS col1
FROM t)",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((SELECT v.col1 FROM v) + x))"}));
const View* v = schema->FindView("v");
ASSERT_NE(v, nullptr);
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "(SELECT v.col1 FROM v) + x");
EXPECT_EQ(udf->dependencies().size(), 1);
EXPECT_THAT(udf->dependencies(), testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{v})));
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP VIEW v)"}),
StatusIs(error::InvalidDropDependentFunction("VIEW", "v", "udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithIndexDependency) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE INDEX idx ON t(col2))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((SELECT MAX(t.col1) FROM t@{FORCE_INDEX=idx}) + x))"}));
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
const Index* idx = t->FindIndex("idx");
ASSERT_NE(idx, nullptr);
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "(SELECT MAX(t.col1) FROM t@{FORCE_INDEX=idx}) + x");
EXPECT_EQ(udf->dependencies().size(), 3);
EXPECT_THAT(
udf->dependencies(),
testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{t, t->FindColumn("col1"), idx})));
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP INDEX idx)"}),
StatusIs(error::InvalidDropDependentFunction("INDEX", "idx", "udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithSequenceDependency) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE SEQUENCE s OPTIONS(sequence_kind="bit_reversed_positive"))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((get_internal_sequence_state(SEQUENCE s)) + x))"}));
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
const Sequence* s = schema->FindSequence("s");
ASSERT_NE(s, nullptr);
const Udf* udf = schema->FindUdf("udf_1");
ASSERT_NE(udf, nullptr);
EXPECT_EQ(udf->Name(), "udf_1");
EXPECT_EQ(udf->body(), "(get_internal_sequence_state(SEQUENCE s)) + x");
EXPECT_EQ(udf->dependencies().size(), 1);
EXPECT_THAT(udf->dependencies(), testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{s})));
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP SEQUENCE s)"}),
StatusIs(error::InvalidDropDependentFunction("SEQUENCE", "s", "udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithVariousDefaultParamsAndLiterals) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(std::unique_ptr<const Schema> schema,
CreateSchema({R"(CREATE FUNCTION func_int64(
x INT64 DEFAULT 1
) RETURNS INT64 SQL SECURITY INVOKER AS (x + 1))"}));
const Udf* udf_int64 = schema->FindUdf("func_int64");
ASSERT_NE(udf_int64, nullptr);
EXPECT_EQ(udf_int64->Name(), "func_int64");
EXPECT_EQ(udf_int64->body(), "x + 1");
EXPECT_EQ(udf_int64->signature()->DebugString(),
"(optional INT64 x) -> INT64");
EXPECT_EQ(udf_int64->signature()->arguments()[0].GetDefault().value(),
Value::Int64(1));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_numeric(
x NUMERIC DEFAULT NUMERIC '123.456'
) RETURNS NUMERIC SQL SECURITY INVOKER AS (x * 2))"}));
const Udf* udf_numeric = schema->FindUdf("func_numeric");
ASSERT_NE(udf_numeric, nullptr);
EXPECT_EQ(udf_numeric->Name(), "func_numeric");
EXPECT_EQ(udf_numeric->body(), "x * 2");
EXPECT_EQ(udf_numeric->signature()->DebugString(),
"(optional NUMERIC x) -> NUMERIC");
EXPECT_EQ(udf_numeric->signature()->arguments()[0].GetDefault().value(),
Value::Numeric(
zetasql::NumericValue::FromStringStrict("123.456").value()));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_string(
x STRING DEFAULT 'default_string'
) RETURNS STRING SQL SECURITY INVOKER AS (CONCAT(x, '_suffix')))"}));
const Udf* udf_string = schema->FindUdf("func_string");
ASSERT_NE(udf_string, nullptr);
EXPECT_EQ(udf_string->Name(), "func_string");
EXPECT_EQ(udf_string->body(), "CONCAT(x, '_suffix')");
EXPECT_EQ(udf_string->signature()->DebugString(),
"(optional STRING x) -> STRING");
EXPECT_EQ(udf_string->signature()->arguments()[0].GetDefault().value(),
Value::String("default_string"));
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
CreateSchema(
{R"(CREATE FUNCTION func_bool(x BOOL DEFAULT TRUE) RETURNS BOOL SQL
SECURITY INVOKER AS (NOT x))"}));
const Udf* udf_bool = schema->FindUdf("func_bool");
ASSERT_NE(udf_bool, nullptr);
EXPECT_EQ(udf_bool->Name(), "func_bool");
EXPECT_EQ(udf_bool->body(), "NOT x");
EXPECT_EQ(udf_bool->signature()->DebugString(), "(optional BOOL x) -> BOOL");
EXPECT_EQ(udf_bool->signature()->arguments()[0].GetDefault().value(),
Value::Bool(true));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_timestamp(
x TIMESTAMP DEFAULT TIMESTAMP '2021-01-01 00:00:00+00'
) RETURNS TIMESTAMP SQL SECURITY INVOKER AS (x))"}));
const Udf* udf_timestamp = schema->FindUdf("func_timestamp");
ASSERT_NE(udf_timestamp, nullptr);
EXPECT_EQ(udf_timestamp->Name(), "func_timestamp");
EXPECT_EQ(udf_timestamp->body(), "x");
EXPECT_EQ(udf_timestamp->signature()->DebugString(),
"(optional TIMESTAMP x) -> TIMESTAMP");
EXPECT_EQ(udf_timestamp->signature()->arguments()[0].GetDefault().value(),
Value::Timestamp(absl::FromUnixSeconds(1609459200)));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_struct(
x STRUCT<field1 INT64> DEFAULT STRUCT<field1 INT64>(1)
) RETURNS INT64 SQL SECURITY INVOKER AS
(x.field1))"}));
const Udf* udf_struct = schema->FindUdf("func_struct");
ASSERT_NE(udf_struct, nullptr);
EXPECT_EQ(udf_struct->Name(), "func_struct");
EXPECT_EQ(udf_struct->body(), "x.field1");
EXPECT_EQ(udf_struct->signature()->DebugString(),
"(optional STRUCT<field1 INT64> x) -> INT64");
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_json(
x JSON DEFAULT JSON '{"key": "value"}'
) RETURNS JSON SQL SECURITY INVOKER AS (x))"}));
const Udf* udf_json = schema->FindUdf("func_json");
ASSERT_NE(udf_json, nullptr);
EXPECT_EQ(udf_json->Name(), "func_json");
EXPECT_EQ(udf_json->body(), "x");
EXPECT_EQ(udf_json->signature()->DebugString(), "(optional JSON x) -> JSON");
EXPECT_EQ(
udf_json->signature()->arguments()[0].GetDefault().value(),
Value::Json(zetasql::JSONValue::ParseJSONString("{\"key\": \"value\"}")
.value()));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_null(
x INT64 DEFAULT NULL
) RETURNS INT64 SQL SECURITY INVOKER AS (x))"}));
const Udf* udf_null = schema->FindUdf("func_null");
ASSERT_NE(udf_null, nullptr);
EXPECT_EQ(udf_null->Name(), "func_null");
EXPECT_EQ(udf_null->body(), "x");
EXPECT_EQ(udf_null->signature()->DebugString(),
"(optional INT64 x) -> INT64");
EXPECT_TRUE(
udf_null->signature()->arguments()[0].GetDefault().value().is_null());
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(CREATE FUNCTION func_array_int(
x ARRAY<INT64> DEFAULT [1, 2, 3]
) RETURNS INT64 SQL SECURITY INVOKER AS (ARRAY_LENGTH(x)))"}));
const Udf* udf_array_int = schema->FindUdf("func_array_int");
ASSERT_NE(udf_array_int, nullptr);
EXPECT_EQ(udf_array_int->Name(), "func_array_int");
EXPECT_EQ(udf_array_int->body(), "ARRAY_LENGTH(x)");
EXPECT_EQ(udf_array_int->signature()->DebugString(),
"(optional ARRAY<INT64> x) -> INT64");
EXPECT_EQ(udf_array_int->signature()->arguments()[0].GetDefault().value(),
Value::Array(zetasql::types::Int64ArrayType(),
{Value::Int64(1), Value::Int64(2), Value::Int64(3)}));
ZETASQL_ASSERT_OK_AND_ASSIGN(schema,
CreateSchema({R"(CREATE FUNCTION func_array_string(
x ARRAY<STRING> DEFAULT ['x', 'y', 'xy']
) RETURNS INT64 SQL SECURITY INVOKER AS (ARRAY_LENGTH(x)))"}));
const Udf* udf_array_string = schema->FindUdf("func_array_string");
ASSERT_NE(udf_array_string, nullptr);
EXPECT_EQ(udf_array_string->Name(), "func_array_string");
EXPECT_EQ(udf_array_string->body(), "ARRAY_LENGTH(x)");
EXPECT_EQ(udf_array_string->signature()->DebugString(),
"(optional ARRAY<STRING> x) -> INT64");
EXPECT_EQ(udf_array_string->signature()->arguments()[0].GetDefault().value(),
Value::Array(
zetasql::types::StringArrayType(),
{Value::String("x"), Value::String("y"), Value::String("xy")}));
EXPECT_THAT(
CreateSchema({R"(CREATE FUNCTION func_array_array_int(
x ARRAY<ARRAY<INT64>> DEFAULT [[1, 2], [3, 4]]
) RETURNS INT64 SQL SECURITY INVOKER AS (ARRAY_LENGTH(x)))"}),
zetasql_base::testing::StatusIs(
StatusCode::kInvalidArgument,
HasSubstr("Cannot construct array with element type ARRAY<INT64> "
"because nested arrays are not supported")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_ViewsAndFunctionsWithSameName) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema(
{R"(CREATE VIEW foo SQL SECURITY INVOKER AS SELECT 1 AS col1)",
R"(CREATE FUNCTION foo(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))"}),
StatusIs(error::SchemaObjectAlreadyExists("View", "foo")));
EXPECT_THAT(
CreateSchema({
R"(CREATE FUNCTION foo(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE VIEW foo SQL SECURITY INVOKER AS SELECT 1 AS col1)",
}),
StatusIs(error::SchemaObjectAlreadyExists("Function", "foo")));
EXPECT_THAT(
CreateSchema(
{R"(CREATE VIEW foo SQL SECURITY INVOKER AS SELECT 1 AS col1)",
R"(CREATE OR REPLACE FUNCTION foo(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (x+1))"}),
StatusIs(error::SchemaObjectAlreadyExists("View", "foo")));
EXPECT_THAT(
CreateSchema({
R"(CREATE FUNCTION foo(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE OR REPLACE VIEW foo SQL SECURITY INVOKER AS SELECT 1 AS col1)",
}),
StatusIs(error::SchemaObjectAlreadyExists("Function", "foo")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_UsingInUdf) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (2 + udf_1(x)))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->dependencies().size(), 0);
const Udf* udf_2 = schema->FindUdf("udf_2");
ASSERT_NE(udf_2, nullptr);
EXPECT_EQ(udf_2->dependencies().size(), 1);
EXPECT_THAT(udf_2->dependencies(),
testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{udf_1})));
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP FUNCTION udf_1)"}),
StatusIs(error::InvalidDropDependentFunction("UDF", "udf_1", "udf_2")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_UsingInTable) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER AS (x+1))",
R"(CREATE TABLE t1 (col1 INT64 AS (udf_1(10)), col2 INT64) PRIMARY KEY(col2))",
R"(CREATE TABLE t2 (col1 INT64, col2 INT64 DEFAULT (udf_1(10))) PRIMARY KEY(col1))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS INT64 SQL SECURITY INVOKER AS (x + 1))",
R"(CREATE TABLE t3 (col1 INT64, col2 INT64, CONSTRAINT c1 CHECK(udf_2(col2) > 0)) PRIMARY KEY(col1))"})); // Crash OK
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->dependencies().size(), 0);
const Table* t1 = schema->FindTable("t1");
ASSERT_NE(t1, nullptr);
EXPECT_THAT(
t1->FindColumn("col1")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
const Table* t2 = schema->FindTable("t2");
ASSERT_NE(t2, nullptr);
EXPECT_THAT(
t2->FindColumn("col2")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
const Udf* udf_2 = schema->FindUdf("udf_2");
ASSERT_NE(udf_2, nullptr);
EXPECT_EQ(udf_2->dependencies().size(), 0);
const Table* t3 = schema->FindTable("t3");
ASSERT_NE(t3, nullptr);
EXPECT_THAT(
t3->FindCheckConstraint("c1")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_2})));
EXPECT_THAT(
UpdateSchema(schema.get(),
{R"(ALTER TABLE t1 ALTER COLUMN col1 INT64 AS (1))"}),
StatusIs(error::CannotAlterGeneratedColumnExpression("t1", "col1")));
ZETASQL_ASSERT_OK_AND_ASSIGN(
schema,
UpdateSchema(schema.get(),
{R"(ALTER TABLE t2 ALTER COLUMN col2 SET DEFAULT (1))"}));
t2 = schema->FindTable("t2");
ASSERT_NE(t2, nullptr);
EXPECT_EQ(t2->FindColumn("col2")->udf_dependencies().size(), 0);
EXPECT_THAT(
UpdateSchema(schema.get(), {R"(DROP FUNCTION udf_1)"}),
StatusIs(error::InvalidDropDependentColumn("UDF", "udf_1", "t1.col1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_UsingInIndexes) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
// Indexes will not directly depend on UDFs; though, they may depend on UDFs
// when index expressions are supported.
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (CASE WHEN x > 10 THEN NULL ELSE x + 1 END))",
R"(CREATE TABLE t1 (col1 INT64, col2 INT64 AS (udf_1(col1)))
PRIMARY KEY (col1))",
R"(CREATE INDEX Idx1 ON t1(col2) WHERE col1 IS NOT NULL)"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Table* t1 = schema->FindTable("t1");
ASSERT_NE(t1, nullptr);
const Column* col1 = t1->FindColumn("col1");
ASSERT_NE(col1, nullptr);
const Column* col2 = t1->FindColumn("col2");
ASSERT_NE(col2, nullptr);
EXPECT_THAT(col2->udf_dependencies(), testing::UnorderedElementsAreArray(
(std::vector<const Udf*>{udf_1})));
const Index* idx1 = schema->FindIndex("Idx1");
ASSERT_NE(idx1, nullptr);
ASSERT_EQ(idx1->key_columns().size(), 1);
const KeyColumn* key_col = idx1->key_columns()[0];
ASSERT_NE(key_col, nullptr);
EXPECT_THAT(key_col->column(), SourceColumnIs(col2));
// Stored expressions in indexes are not supported; however, dependency
// checking should occur if this is supported in the future.
EXPECT_THAT(
UpdateSchema(schema.get(),
{R"(CREATE INDEX Idx2 ON t1 (col2) STORING (col2+1))"}),
::zetasql_base::testing::StatusIs(StatusCode::kInvalidArgument,
HasSubstr("Expecting ')' but found '+'")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_UsingInViews) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE VIEW v SQL SECURITY INVOKER AS SELECT t.col1, udf_1(t.col2)
AS col2 FROM t)"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_THAT(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_THAT(t->Name(), "t");
const View* v = schema->FindView("v");
ASSERT_NE(v, nullptr);
EXPECT_THAT(v->Name(), "v");
EXPECT_THAT(
v->dependencies(),
testing::UnorderedElementsAreArray((std::vector<const SchemaNode*>{
t, t->FindColumn("col2"), t->FindColumn("col1"), udf_1})));
EXPECT_THAT(UpdateSchema(schema.get(), {R"(DROP FUNCTION udf_1)"}),
StatusIs(error::InvalidDropDependentViews("UDF", "udf_1", "v")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_TransitiveFunctionDeterminism) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS TIMESTAMP SQL SECURITY INVOKER
AS ((SELECT CURRENT_TIMESTAMP())))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS STRING SQL SECURITY INVOKER
AS (CONCAT('Timestamp: ', CAST(udf_1(x) as STRING))))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
const Udf* udf_2 = schema->FindUdf("udf_2");
EXPECT_EQ(udf_1->determinism_level(),
Udf::Determinism::NOT_DETERMINISTIC_STABLE);
EXPECT_EQ(udf_2->determinism_level(),
Udf::Determinism::NOT_DETERMINISTIC_STABLE);
// Volatile functions should be transitive, but are not supported in the
// emulator.
EXPECT_THAT(
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (EXTRACT(YEAR from CURRENT_TIMESTAMP())))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((SELECT APPROX_DOT_PRODUCT([100, 10], [200, 6]) AS results) + udf_1(x)))"}),
StatusIs(error::FunctionTypeMismatch("udf_2", "INT64", "FLOAT64")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_CyclicDependencyOnViewFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY
INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE VIEW v SQL SECURITY INVOKER AS SELECT t.col1,
udf_1(t.col2)
AS col2 FROM t)"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_THAT(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_THAT(t->Name(), "t");
const View* v = schema->FindView("v");
ASSERT_NE(v, nullptr);
EXPECT_THAT(v->Name(), "v");
EXPECT_THAT(
v->dependencies(),
testing::UnorderedElementsAreArray((std::vector<const SchemaNode*>{
t, t->FindColumn("col2"), t->FindColumn("col1"), udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS ((SELECT v.col2 FROM v) + x))"}),
StatusIs(error::ViewReplaceRecursive("udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_CyclicDependencyOnUdfFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (x+1))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (udf_1(x) + 2))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_THAT(udf_1->Name(), "udf_1");
const Udf* udf_2 = schema->FindUdf("udf_2");
ASSERT_NE(udf_2, nullptr);
EXPECT_THAT(udf_2->Name(), "udf_2");
EXPECT_THAT(udf_2->dependencies(),
testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS (udf_2(x) + 1))"}),
StatusIs(error::ViewReplaceRecursive("udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_CyclicDependencyOnColumnExpressionFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64, col3 INT64 AS
(udf_1(col2))) PRIMARY KEY (col1))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_THAT(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_THAT(t->Name(), "t");
EXPECT_THAT(
t->FindColumn("col3")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x INT64) RETURNS INT64 SQL
SECURITY INVOKER AS ((SELECT MAX(t.col3) FROM t) + x))"}),
StatusIs(error::ViewReplaceRecursive("udf_1")));
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidNewSignatureOnUdfFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (x+1))",
R"(CREATE FUNCTION udf_2(x INT64) RETURNS INT64 SQL SECURITY
INVOKER AS (udf_1(x) + 2))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Udf* udf_2 = schema->FindUdf("udf_2");
ASSERT_NE(udf_2, nullptr);
EXPECT_EQ(udf_2->Name(), "udf_2");
EXPECT_THAT(udf_2->dependencies(),
testing::UnorderedElementsAreArray(
(std::vector<const SchemaNode*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX) SQL
SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END || 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidNewSignatureOnViewFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE VIEW v SQL SECURITY INVOKER AS SELECT t.col1,
(udf_1(t.col2) + 2) AS col2 FROM t)"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_EQ(t->Name(), "t");
const View* v = schema->FindView("v");
ASSERT_NE(v, nullptr);
EXPECT_EQ(v->Name(), "v");
EXPECT_THAT(
v->dependencies(),
testing::UnorderedElementsAreArray((std::vector<const SchemaNode*>{
t, t->FindColumn("col2"), t->FindColumn("col1"), udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END
|| 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidNewSignatureOnCheckConstraintFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64, CONSTRAINT c1
CHECK(udf_1(col2) > 0)) PRIMARY KEY(col1))"})); // Crash OK
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_EQ(t->Name(), "t");
EXPECT_THAT(
t->FindCheckConstraint("c1")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END
|| 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_P(SchemaUpdaterTest,
CreateUDF_InvalidNewSignatureOnColumnExpressionFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64, col3 INT64 AS
(udf_1(col2))) PRIMARY KEY (col1))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_EQ(t->Name(), "t");
EXPECT_THAT(
t->FindColumn("col3")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END
|| 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
// Changing generated column expression is not supported, so no need to handle
// this cyclic case.
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidNewSignatureOnDefaultValueFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema(
{R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64 DEFAULT (udf_1(10)))
PRIMARY KEY(col1))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
const Table* t = schema->FindTable("t");
ASSERT_NE(t, nullptr);
EXPECT_EQ(t->Name(), "t");
EXPECT_THAT(
t->FindColumn("col2")->udf_dependencies(),
testing::UnorderedElementsAreArray((std::vector<const Udf*>{udf_1})));
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END
|| 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_P(SchemaUpdaterTest, CreateUDF_InvalidNewSignatureOnSequenceFails) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
ZETASQL_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<const Schema> schema,
CreateSchema({
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64
SQL SECURITY INVOKER AS (x+1))",
R"(CREATE TABLE t (col1 INT64, col2 INT64 DEFAULT (udf_1(10)))
PRIMARY KEY(col1))"}));
const Udf* udf_1 = schema->FindUdf("udf_1");
ASSERT_NE(udf_1, nullptr);
EXPECT_EQ(udf_1->Name(), "udf_1");
EXPECT_THAT(
UpdateSchema(
schema.get(),
{R"(CREATE OR REPLACE FUNCTION udf_1(x BOOL) RETURNS STRING(MAX)
SQL SECURITY INVOKER AS (CASE WHEN x THEN 'true' ELSE 'false' END
|| 'c'))"}),
zetasql_base::testing::StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_P(SchemaUpdaterTest, CreateUDF_WithForUpdateInvalid) {
if (GetParam() == POSTGRESQL) GTEST_SKIP();
EXPECT_THAT(
CreateSchema(
{R"(CREATE TABLE t (col1 INT64, col2 INT64) PRIMARY KEY (col1))",
R"(CREATE FUNCTION udf_1(x INT64) RETURNS INT64 SQL SECURITY INVOKER
AS ((SELECT MAX(t.col1) FROM t FOR UPDATE) + x))"}),
::zetasql_base::testing::StatusIs(
absl::StatusCode::kInvalidArgument,
testing::HasSubstr("Unexpected lock mode in function body query")));
}
} // namespace test
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google