backend/schema/updater/schema_updater_tests/generated_column.cc (389 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 <memory>
#include <string>
#include <vector>
#include "google/spanner/admin/database/v1/common.pb.h"
#include "zetasql/public/type.pb.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/types/span.h"
#include "backend/schema/catalog/column.h"
#include "backend/schema/catalog/schema.h"
#include "backend/schema/catalog/table.h"
#include "backend/schema/updater/schema_updater_tests/base.h"
namespace google {
namespace spanner {
namespace emulator {
namespace backend {
namespace test {
using ::testing::HasSubstr;
using ::zetasql_base::testing::StatusIs;
// For the following tests, a custom PG DDL statement is required as translating
// expressions from GSQL to PG is not supported in tests.
using database_api::DatabaseDialect::GOOGLE_STANDARD_SQL;
using database_api::DatabaseDialect::POSTGRESQL;
TEST_P(SchemaUpdaterTest, GeneratedColumnBasic) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema,
CreateSchema({R"(
create table "T" (
"K" bigint primary key,
"V" varchar(10),
"G1" bigint not null generated always as ("K" + LENGTH("V")) stored
)
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V STRING(10),
G1 INT64 NOT NULL AS (k + LENGTH(v)) STORED,
) PRIMARY KEY (K)
)"}));
}
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("V");
ASSERT_NE(col, nullptr);
EXPECT_EQ(col->Name(), "V");
EXPECT_FALSE(col->is_generated());
EXPECT_FALSE(col->expression().has_value());
EXPECT_FALSE(col->original_expression().has_value());
col = table->FindColumn("G1");
ASSERT_NE(col, nullptr);
EXPECT_EQ(col->Name(), "G1");
EXPECT_EQ(col->GetType()->kind(), zetasql::TYPE_INT64);
EXPECT_FALSE(col->is_nullable());
EXPECT_TRUE(col->is_generated());
EXPECT_FALSE(col->has_default_value());
EXPECT_TRUE(col->expression().has_value());
if (GetParam() == POSTGRESQL) {
EXPECT_EQ(col->expression().value(), "K + (LENGTH(V))");
EXPECT_EQ(col->original_expression(), "(\"K\" + length(\"V\"))");
} else {
EXPECT_EQ(col->expression().value(), "(k + LENGTH(v))");
EXPECT_FALSE(col->original_expression().has_value());
}
auto get_column_names = [](absl::Span<const Column* const> columns,
std::vector<std::string>* column_names) {
column_names->clear();
column_names->reserve(columns.size());
for (const Column* col : columns) {
column_names->push_back(col->Name());
}
};
std::vector<std::string> dependent_column_names;
get_column_names(col->dependent_columns(), &dependent_column_names);
EXPECT_THAT(dependent_column_names,
testing::UnorderedElementsAreArray({"K", "V"}));
}
TEST_P(SchemaUpdaterTest, AlterTableReferenceAnotherGeneratedColumn) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
create table "T" (
"K" bigint primary key,
"V" varchar(10),
"G1" bigint not null generated always as ("K" + LENGTH("V")) stored
)
)",
R"(
alter table "T" add column "G2" bigint generated always as ("G1" + "G1") STORED
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("A generated column \"G2\" cannot reference "
"another generated column \"G1\".")));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V STRING(10),
G1 INT64 NOT NULL AS (k + LENGTH(v)) STORED,
) PRIMARY KEY (K)
)",
R"(
ALTER TABLE T ADD COLUMN G2 INT64 AS (G1 + G1) STORED
)"}));
auto get_column_names = [](absl::Span<const Column* const> columns,
std::vector<std::string>* column_names) {
column_names->clear();
column_names->reserve(columns.size());
for (const Column* col : columns) {
column_names->push_back(col->Name());
}
};
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("G2");
ASSERT_NE(col, nullptr);
EXPECT_EQ(col->Name(), "G2");
EXPECT_EQ(col->GetType()->kind(), zetasql::TYPE_INT64);
EXPECT_TRUE(col->is_nullable());
EXPECT_TRUE(col->is_generated());
EXPECT_FALSE(col->has_default_value());
EXPECT_TRUE(col->expression().has_value());
if (GetParam() == POSTGRESQL) {
EXPECT_EQ(col->expression().value(), "G1 + G1");
EXPECT_EQ(col->original_expression(), "(\"G1\" + \"G1\")");
} else {
EXPECT_EQ(col->expression().value(), "(G1 + G1)");
EXPECT_FALSE(col->original_expression().has_value());
}
std::vector<std::string> dependent_column_names;
get_column_names(col->dependent_columns(), &dependent_column_names);
EXPECT_THAT(dependent_column_names,
testing::UnorderedElementsAreArray({"G1"}));
}
}
TEST_P(SchemaUpdaterTest, CreateTableReferenceAnotherGeneratedColumn) {
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
CREATE TABLE T (
K bigint primary key,
V bigint,
G bigint generated always as (K + V) stored,
H bigint generated always as (G + 1) stored
)
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("A generated column \"h\" cannot reference "
"another generated column \"g\".")));
} else {
ZETASQL_EXPECT_OK(CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V INT64,
G INT64 AS (K + V) STORED,
H INT64 AS (G + 1) STORED,
) PRIMARY KEY (K)
)"}));
}
}
TEST_P(SchemaUpdaterTest, CannotCreateTableAddNonStoredGeneratedColumn) {
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
CREATE TABLE T (
K bigint primary key,
V bigint,
G bigint generated always as (K + V)
)
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
zetasql_base::testing::StatusIs(absl::StatusCode::kInvalidArgument));
} else {
ZETASQL_EXPECT_OK(CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V INT64,
G INT64 AS (K + V),
) PRIMARY KEY (K)
)"}));
}
}
TEST_P(SchemaUpdaterTest, CannotAlterTableAddNonStoredGeneratedColumn) {
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
CREATE TABLE T (
K bigint primary key,
V bigint
)
)",
R"(
ALTER TABLE T ADD COLUMN G bigint generated always as (K + V)
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
zetasql_base::testing::StatusIs(absl::StatusCode::kInvalidArgument));
} else {
ZETASQL_EXPECT_OK(CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V INT64,
) PRIMARY KEY (K)
)",
R"(
ALTER TABLE T ADD COLUMN G INT64 AS (K + V)
)"}));
}
}
TEST_P(SchemaUpdaterTest, CannotAlterTableAlterColumnToNonStoredGenerated) {
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
CREATE TABLE T (
K bigint primary key,
V bigint,
G bigint
)
)",
R"(
ALTER TABLE T ALTER COLUMN G bigint generated always as (K + V)
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
zetasql_base::testing::StatusIs(absl::StatusCode::kInvalidArgument));
} else {
EXPECT_THAT(
CreateSchema({R"(
CREATE TABLE T (
K INT64 NOT NULL,
V INT64,
G INT64,
) PRIMARY KEY (K)
)",
R"(
ALTER TABLE T ALTER COLUMN G INT64 AS (K + V)
)"}),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Cannot convert column `T.G` to a generated column.")));
}
}
std::vector<std::string> SchemaForCaseSensitivityTests(
database_api::DatabaseDialect dialect) {
if (dialect == POSTGRESQL) {
return {
R"sql(
CREATE TABLE T (
K bigint primary key,
V bigint
)
)sql",
};
}
return {
R"sql(
CREATE TABLE T (
K INT64 NOT NULL,
V INT64,
) PRIMARY KEY (K)
)sql",
};
}
TEST_P(SchemaUpdaterTest, StoredColumnExpressionIsCaseInsensitive) {
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema,
CreateSchema(SchemaForCaseSensitivityTests(POSTGRESQL),
/*proto_descriptor_bytes=*/"",
POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
ZETASQL_EXPECT_OK(UpdateSchema(schema.get(), {R"(
ALTER TABLE T ADD COLUMN G bigint generated always as (k + v) STORED
)"},
/*proto_descriptor_bytes=*/"",
POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(
auto schema,
CreateSchema(SchemaForCaseSensitivityTests(GOOGLE_STANDARD_SQL)));
ZETASQL_EXPECT_OK(UpdateSchema(schema.get(), {R"(
ALTER TABLE T ADD COLUMN G INT64 AS (k + v) STORED
)"}));
}
}
TEST_P(SchemaUpdaterTest, StoredColumnNameIsCaseSensitive) {
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema,
CreateSchema(SchemaForCaseSensitivityTests(POSTGRESQL),
/*proto_descriptor_bytes=*/"",
POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
ZETASQL_EXPECT_OK(UpdateSchema(schema.get(), {R"(
ALTER TABLE T ADD COLUMN G bigint generated always as (k + v) STORED
)"},
/*proto_descriptor_bytes=*/"",
POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
// TODO: find out why using T gets the "Table not found: T"
// error.
EXPECT_THAT(UpdateSchema(schema.get(), {R"(
ALTER TABLE t DROP COLUMN g
)"}),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("Column not found in table t: g")));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(
auto schema,
CreateSchema(SchemaForCaseSensitivityTests(GOOGLE_STANDARD_SQL)));
ZETASQL_EXPECT_OK(UpdateSchema(schema.get(), {R"(
ALTER TABLE T ADD COLUMN G INT64 AS (k + v) STORED
)"}));
EXPECT_THAT(UpdateSchema(schema.get(), {R"(
ALTER TABLE T DROP COLUMN g
)"}),
StatusIs(absl::StatusCode::kNotFound,
HasSubstr("Column not found in table T: g")));
}
}
TEST_P(SchemaUpdaterTest, SqlInlinedFunctionInGeneratedColumn) {
// A SQL-inlined function is a function whose implementation is a SQL string
// instead of an evaluator.
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK(CreateSchema({R"(
create table array_table (
id bigint primary key,
array_col varchar[]
)
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_col_not_applicable bool
generated always as (arrayoverlap(array_col, ARRAY['not_applicable'])) stored
)"},
/*proto_descriptor_bytes=*/"",
database_api::DatabaseDialect::POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK(CreateSchema({
R"(
CREATE TABLE array_table (
id INT64,
int_array ARRAY<INT64>,
string_array ARRAY<STRING(MAX)>
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS string_array_not_applicable BOOL
AS (ARRAY_INCLUDES(string_array, "not_applicable")) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_first_col INT64
AS (ARRAY_FIRST(int_array)) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_min_col INT64
AS (ARRAY_MIN(int_array)) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_max_col INT64
AS (ARRAY_MAX(int_array)) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_slice_col ARRAY<INT64>
AS (ARRAY_SLICE(int_array, 1, 2)) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_includes_any_col BOOL
AS (ARRAY_INCLUDES_ANY(int_array, [1, 2])) STORED
)",
R"(
ALTER TABLE array_table ADD COLUMN IF NOT EXISTS array_includes_all_col BOOL
AS (ARRAY_INCLUDES_ALL(int_array, [1, 2])) STORED
)",
}));
}
}
} // namespace test
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google