backend/schema/updater/schema_updater_tests/table.cc (1,533 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/table.h" #include <algorithm> #include <iterator> #include <memory> #include <string> #include <utility> #include <vector> #include "google/spanner/admin/database/v1/common.pb.h" #include "zetasql/public/types/type_factory.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/statusor.h" #include "backend/schema/catalog/schema.h" #include "backend/schema/updater/schema_updater_tests/base.h" #include "common/errors.h" #include "common/feature_flags.h" #include "tests/common/scoped_feature_flags_setter.h" #include "third_party/spanner_pg/datatypes/extended/spanner_extended_type.h" namespace google { namespace spanner { namespace emulator { namespace backend { namespace test { namespace types = zetasql::types; namespace { using database_api::DatabaseDialect::POSTGRESQL; TEST_P(SchemaUpdaterTest, CreateTable_SingleKey) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 STRING(MAX) ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 2); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::StringType())); EXPECT_THAT(col2, testing::Not(IsKeyColumnOf(t, "ASC"))); } TEST_P(SchemaUpdaterTest, CreateTable_SingleKeyInline) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64 PRIMARY KEY, col2 STRING(MAX) ))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 2); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::StringType())); EXPECT_THAT(col2, testing::Not(IsKeyColumnOf(t, "ASC"))); } TEST_P(SchemaUpdaterTest, CreateTable_MultiKey) { std::unique_ptr<const Schema> schema; // Changing the ordering for key columns is unsupported in PG. if (GetParam() == POSTGRESQL) { ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T ( col1 bigint, col2 varchar, PRIMARY KEY(col1, col2) ))"}, /*proto_descriptor_bytes=*/"", /*dialect=*/POSTGRESQL, /*use_gsql_to_pg_translation=*/false)); } else { ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 STRING(MAX) ) PRIMARY KEY(col1 DESC, col2))"})); } const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 2); EXPECT_EQ(t->primary_key().size(), 2); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); if (GetParam() == POSTGRESQL) { EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); } else { EXPECT_THAT(col1, IsKeyColumnOf(t, "DESC")); } auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::StringType())); EXPECT_THAT(col2, IsKeyColumnOf(t, "ASC")); } TEST_P(SchemaUpdaterTest, CreateTable_NoKey) { // Empty key columns are unsupported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64 ) PRIMARY KEY())"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 1); EXPECT_EQ(t->primary_key().size(), 0); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, testing::Not(IsKeyColumnOf(t, "ASC"))); } TEST_P(SchemaUpdaterTest, CreateTable_NoColumns) { // Empty key columns are unsupported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( ) PRIMARY KEY())"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 0); EXPECT_EQ(t->primary_key().size(), 0); } TEST_P(SchemaUpdaterTest, CreateTable_ColumnLength) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); // Empty key columns are unsupported in PG. ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 BYTES(10) ) PRIMARY KEY())"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::BytesType())); EXPECT_EQ(col1->declared_max_length(), 10); ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 BYTES(MAX) ) PRIMARY KEY())"})); t = schema->FindTable("T"); col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::BytesType())); EXPECT_FALSE(col1->declared_max_length().has_value()); } TEST_P(SchemaUpdaterTest, CreateTable_AllowCommitTimestamp) { std::unique_ptr<const Schema> schema; if (GetParam() == POSTGRESQL) { ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 bigint PRIMARY KEY, col2 spanner.commit_timestamp ))"}, /*proto_descriptor_bytes=*/"", /*dialect=*/POSTGRESQL, /*use_gsql_to_pg_translation=*/false)); } else { ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 TIMESTAMP OPTIONS( allow_commit_timestamp = true ) ) PRIMARY KEY(col1))"})); } const Table* t = schema->FindTable("T"); auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::TimestampType())); EXPECT_TRUE(col2->allows_commit_timestamp()); // The following tests are skipped because column options are unsupported in // PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 TIMESTAMP OPTIONS( allow_commit_timestamp = false ) ) PRIMARY KEY(col1))"})); t = schema->FindTable("T"); col2 = t->columns()[1]; EXPECT_FALSE(col2->allows_commit_timestamp()); ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 TIMESTAMP OPTIONS( allow_commit_timestamp = true, allow_commit_timestamp = null ) ) PRIMARY KEY(col1))"})); t = schema->FindTable("T"); col2 = t->columns()[1]; EXPECT_FALSE(col2->allows_commit_timestamp()); } TEST_P(SchemaUpdaterTest, CreateTable_InvalidColumnLength) { // Empty key columns are unsupported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); EXPECT_THAT(CreateSchema({R"( CREATE TABLE T( col1 BYTES(1000000000) ) PRIMARY KEY())"}), StatusIs(error::InvalidColumnLength( "T.col1", 1000000000, 1, limits::kMaxBytesColumnLength))); } TEST_P(SchemaUpdaterTest, CreateTable_DuplicateKeys) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); EXPECT_THAT(CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 BYTES(10) ) PRIMARY KEY(col1,col1))"}), StatusIs(error::MultipleRefsToKeyColumn("Table", "T", "col1"))); } TEST_P(SchemaUpdaterTest, CreateTable_DuplicateColumns) { EXPECT_THAT(CreateSchema({R"( CREATE TABLE T( col1 INT64, col1 STRING(MAX) ) PRIMARY KEY(col1,col1))"}), StatusIs(error::DuplicateColumnName("T.col1"))); } TEST_P(SchemaUpdaterTest, CreateTable_ColumnNullability) { // Empty key columns are unsupported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64 NOT NULL, col2 INT64 ) PRIMARY KEY())"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 2); auto col1 = t->columns()[0]; EXPECT_FALSE(col1->is_nullable()); auto col2 = t->columns()[0]; EXPECT_FALSE(col2->is_nullable()); } TEST_P(SchemaUpdaterTest, CreateTable_ColumnNotFound) { EXPECT_THAT(CreateSchema({R"( CREATE TABLE T( col1 INT64 ) PRIMARY KEY(col2))"}), StatusIs(error::NonExistentKeyColumn("Table", "T", "col2"))); } TEST_P(SchemaUpdaterTest, CreateTable_AlreadyExists) { EXPECT_THAT(CreateSchema({ R"( CREATE TABLE T( col1 INT64 ) PRIMARY KEY(col1) )", R"( DROP TABLE T )", R"( CREATE TABLE T( col1 INT64 ) PRIMARY KEY(col1) )", R"( CREATE TABLE T( col2 INT64 ) PRIMARY KEY(col2) )"}), StatusIs(error::SchemaObjectAlreadyExists("Table", "T"))); } TEST_P(SchemaUpdaterTest, CreateTable_Interleave) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )", R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"})); const Table* p = schema->FindTable("Parent"); EXPECT_NE(p, nullptr); const Table* c = schema->FindTable("Child"); EXPECT_NE(c, nullptr); EXPECT_THAT(c, IsInterleavedIn(p, Table::OnDeleteAction::kCascade)); } TEST_P(SchemaUpdaterTest, CreateTable_InterleaveMismatch) { // Changing the ordering for key columns is unsupported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1))"})); EXPECT_THAT( UpdateSchema(schema.get(), {R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1 DESC, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::IncorrectParentKeyOrder("Table", "Child", "k1", "ASC"))); EXPECT_THAT(UpdateSchema(schema.get(), {R"( CREATE TABLE Child ( k1 INT64, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::IncorrectParentKeyNullability( "Table", "Child", "k1", "not null", "nullable"))); EXPECT_THAT(UpdateSchema(schema.get(), {R"( CREATE TABLE Child ( k1 STRING(10), k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::IncorrectParentKeyType("Table", "Child", "k1", "STRING", "INT64"))); } TEST_P(SchemaUpdaterTest, CreateTable_InterleaveDepth) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T0 ( k0 INT64, ) PRIMARY KEY (k0) )"})); std::vector<std::string> statements; std::vector<int> key_idx; for (int i = 0; i < limits::kMaxInterleavingDepth; ++i) { key_idx.push_back(i); std::vector<std::string> key_names; std::transform(key_idx.begin(), key_idx.end(), std::back_inserter(key_names), [](int k) { return absl::StrCat("k", k); }); std::vector<std::string> key_defs; std::transform(key_idx.begin(), key_idx.end(), std::back_inserter(key_defs), [](int k) { return absl::StrCat("k", k, " INT64"); }); std::string create_table = absl::Substitute( "CREATE TABLE T$0 ( $1 ) PRIMARY KEY($2), INTERLEAVE IN PARENT T$3", i + 1, absl::StrJoin(key_defs, ","), absl::StrJoin(key_names, ","), i); statements.emplace_back(std::move(create_table)); } EXPECT_THAT(UpdateSchema(schema.get(), statements), StatusIs(error::DeepNesting("Table", "T7", limits::kMaxInterleavingDepth))); } TEST_P(SchemaUpdaterTest, CreateTable_ParentNotFound) { EXPECT_THAT(CreateSchema({ R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )", R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `NoParent` ON DELETE CASCADE )"}), StatusIs(error::TableNotFound("NoParent"))); } TEST_P(SchemaUpdaterTest, CreateTable_ChildTableMissingPrimaryKey) { EXPECT_THAT( CreateSchema({ R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2) )", R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, k3 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k3), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::MustReferenceParentKeyColumn("Table", "Child", "k2"))); } TEST_P(SchemaUpdaterTest, CreateTable_ChildTableCaseSensitiveMissingPrimaryKey) { EXPECT_THAT( CreateSchema({ R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )", R"( CREATE TABLE Child ( K1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (K1, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::MustReferenceParentKeyColumn("Table", "Child", "k1"))); } TEST_P(SchemaUpdaterTest, CreateTable_ChildTablePrimaryKeyInWrongOrder) { EXPECT_THAT( CreateSchema({ R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )", R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k2, k1), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"}), StatusIs(error::IncorrectParentKeyPosition("Table", "Child", "k1", 1))); } TEST_P(SchemaUpdaterTest, CreateTable_CreateChildTable) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE `Parent` ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )", R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `Parent` ON DELETE CASCADE )"})); const Table* parent_table = schema->FindTable("Parent"); EXPECT_NE(parent_table, nullptr); const Table* child_table = schema->FindTable("Child"); EXPECT_NE(child_table, nullptr); } TEST_P(SchemaUpdaterTest, CreateTable_WithSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 STRING(MAX), SYNONYM(S), ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); const Table* s = schema->FindTable("S"); EXPECT_NE(s, nullptr); EXPECT_EQ(t, s); const Table* t1 = schema->FindTableUsingSynonym("T"); EXPECT_EQ(t1, nullptr); const Table* s1 = schema->FindTableUsingSynonym("S"); EXPECT_NE(s1, nullptr); t1 = schema->FindTableCaseSensitive("T"); EXPECT_NE(t1, nullptr); t1 = schema->FindTableCaseSensitive("t"); EXPECT_EQ(t1, nullptr); s1 = schema->FindTableUsingSynonymCaseSensitive("S"); EXPECT_NE(s1, nullptr); s1 = schema->FindTableUsingSynonymCaseSensitive("s"); EXPECT_EQ(s1, nullptr); } TEST_P(SchemaUpdaterTest, CreateTable_TableNameConflictsWithSynonym) { EXPECT_THAT(CreateSchema({ R"( CREATE TABLE T( col1 INT64, SYNONYM(S), ) PRIMARY KEY(col1) )", R"( CREATE TABLE S( col1 INT64 ) PRIMARY KEY(col1) )"}), StatusIs(error::SchemaObjectAlreadyExists("Table", "S"))); } TEST_P(SchemaUpdaterTest, CreateTable_SynonymConflictsWithTableName) { EXPECT_THAT(CreateSchema({ R"( CREATE TABLE T( col1 INT64, ) PRIMARY KEY(col1) )", R"( CREATE TABLE T2( col1 INT64, SYNONYM(T), ) PRIMARY KEY(col1) )"}), StatusIs(error::SchemaObjectAlreadyExists("Table", "T"))); } TEST_P(SchemaUpdaterTest, CreateTable_SynonymConflictsWithSynonym) { EXPECT_THAT(CreateSchema({ R"( CREATE TABLE T1( col1 INT64, SYNONYM(S), ) PRIMARY KEY(col1) )", R"( CREATE TABLE T2( col1 INT64, SYNONYM(S), ) PRIMARY KEY(col1) )"}), StatusIs(error::SchemaObjectAlreadyExists("Table", "S"))); } TEST_P(SchemaUpdaterTest, AlterTable_Rename) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, ) PRIMARY KEY (k1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T RENAME TO S )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_EQ(t_new, nullptr); const Table* s_new = new_schema->FindTable("S"); EXPECT_NE(s_new, nullptr); } TEST_P(SchemaUpdaterTest, AlterTable_RenameWithSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, ) PRIMARY KEY (k1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T RENAME TO S, ADD SYNONYM T )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_NE(t_new, nullptr); const Table* s_new = new_schema->FindTable("S"); EXPECT_NE(s_new, nullptr); // The two table objects should be the same. EXPECT_EQ(t_new, s_new); } TEST_P(SchemaUpdaterTest, AlterTable_RenameWithDependencies) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 INT64, ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k1 INT64, c1 INT64, ) PRIMARY KEY (k1), INTERLEAVE IN PARENT T )", R"( CREATE INDEX Idx1 ON T(c1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T RENAME TO S )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_EQ(t_new, nullptr); const Table* s_new = new_schema->FindTable("S"); EXPECT_NE(s_new, nullptr); // Child table must be interleaved in the new parent table name. const Table* t_child = new_schema->FindTable("T2"); EXPECT_NE(t_child, nullptr); EXPECT_EQ(t_child->parent()->Name(), "S"); // Index must be indexing the new base table name. auto idx = new_schema->FindIndex("Idx1"); EXPECT_NE(idx, nullptr); EXPECT_EQ(idx->indexed_table()->Name(), "S"); } TEST_P(SchemaUpdaterTest, AlterTable_AddColumn) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 INT64, ) PRIMARY KEY (k1) )"})); const Table* t_old = schema->FindTable("T"); EXPECT_EQ(t_old->FindColumn("c2"), nullptr); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD COLUMN c2 BYTES(100) )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_NE(t_old, t_new); auto c2 = t_new->FindColumn("c2"); EXPECT_NE(c2, nullptr); EXPECT_THAT(c2, ColumnIs("c2", types::BytesType())); EXPECT_TRUE(c2->is_nullable()); EXPECT_EQ(c2->declared_max_length(), 100); } TEST_P(SchemaUpdaterTest, AlterTableAddColumnIfNotExists) { // IF NOT EXISTS isn't yet supported on the PG side of the emulator if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 INT64, ) PRIMARY KEY (k1) )"})); const Table* t_old = schema->FindTable("T"); EXPECT_EQ(t_old->FindColumn("c2"), nullptr); // Add a column, make sure it goes in right. ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD COLUMN c2 BYTES(100) )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_NE(t_old, t_new); auto c2 = t_new->FindColumn("c2"); EXPECT_NE(c2, nullptr); EXPECT_THAT(c2, ColumnIs("c2", types::BytesType())); EXPECT_TRUE(c2->is_nullable()); EXPECT_EQ(c2->declared_max_length(), 100); // Add the same column again and make sure we didn't change anything. ZETASQL_ASSERT_OK(UpdateSchema(new_schema.get(), {R"( ALTER TABLE T ADD COLUMN IF NOT EXISTS c2 INT64 )"})); t_new = new_schema->FindTable("T"); EXPECT_NE(t_old, t_new); c2 = t_new->FindColumn("c2"); EXPECT_NE(c2, nullptr); EXPECT_THAT(c2, ColumnIs("c2", types::BytesType())); EXPECT_TRUE(c2->is_nullable()); EXPECT_EQ(c2->declared_max_length(), 100); } TEST_P(SchemaUpdaterTest, AlterTable_AddColumnAlreadyExists) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 INT64, ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD COLUMN c1 BYTES(100) )"}), StatusIs(error::DuplicateColumnName("T.c1"))); } TEST_P(SchemaUpdaterTest, AlterColumn_ChangeColumnType_StaticCheckValid) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 STRING(100), ) PRIMARY KEY (k1) )"})); const Table* t = schema->FindTable("T"); auto c1 = t->FindColumn("c1"); EXPECT_THAT(c1, ColumnIs("c1", types::StringType())); EXPECT_EQ(c1->declared_max_length(), 100); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 BYTES(400) )"})); t = new_schema->FindTable("T"); c1 = t->FindColumn("c1"); EXPECT_THAT(c1, ColumnIs("c1", types::BytesType())); EXPECT_EQ(c1->declared_max_length(), 400); } TEST_P(SchemaUpdaterTest, AlterColumn_ChangeColumnType_Invalid) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 STRING(100), ) PRIMARY KEY (k1) )"})); const Table* t = schema->FindTable("T"); auto c1 = t->FindColumn("c1"); EXPECT_THAT(c1, ColumnIs("c1", types::StringType())); EXPECT_EQ(c1->declared_max_length(), 100); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 INT64 )"}), StatusIs(error::CannotChangeColumnType("c1", "STRING", "INT64"))); } TEST_P(SchemaUpdaterTest, AlterColumn_ChangeNonArrayToArray) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 STRING(100), ) PRIMARY KEY (k1) )"})); EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 ARRAY<STRING(MAX)> )"}), StatusIs(error::CannotChangeColumnType("c1", "STRING", "ARRAY<STRING>"))); EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 ARRAY<BYTES(MAX)> )"}), StatusIs(error::CannotChangeColumnType("c1", "STRING", "ARRAY<BYTES>"))); } TEST_P(SchemaUpdaterTest, AlterColumn_NotNullToNullable) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T ( k1 INT64 NOT NULL, c1 STRING(MAX), c2 INT64 NOT NULL, ) PRIMARY KEY (k1) )"})); const Table* t = schema->FindTable("T"); auto c2 = t->FindColumn("c2"); EXPECT_FALSE(c2->is_nullable()); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c2 INT64 )"})); t = new_schema->FindTable("T"); c2 = t->FindColumn("c2"); EXPECT_TRUE(c2->is_nullable()); } TEST_P(SchemaUpdaterTest, AlterColumn_ChangeIndexedColumnType) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T ( k1 INT64 NOT NULL, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE INDEX Idx1 ON T(c1) )"})); const Table* t = schema->FindTable("T"); auto c1 = t->FindColumn("c1"); EXPECT_THAT(c1, ColumnIs("c1", types::StringType())); auto idx = schema->FindIndex("Idx1"); auto c1_idx = idx->key_columns()[0]; EXPECT_THAT(c1_idx->column(), SourceColumnIs(c1)); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 BYTES(40) )"})); const Table* t_new = new_schema->FindTable("T"); auto c1_new = t_new->FindColumn("c1"); EXPECT_THAT(c1_new, ColumnIs("c1", types::BytesType())); auto idx_new = new_schema->FindIndex("Idx1"); auto c1_idx_new = idx_new->key_columns()[0]; EXPECT_THAT(c1_idx_new->column(), SourceColumnIs(c1_new)); } TEST_P(SchemaUpdaterTest, AlterColumn_ChangeIndexedColumnNullability) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T ( k1 INT64, c1 STRING(10) NOT NULL, c2 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE INDEX Idx1 ON T(c1) STORING(c2) )", R"( CREATE NULL_FILTERED INDEX Idx2 ON T(c2) STORING(c1) )"})); // Changing the nullability of indexed columns for non-null-filtered indexes // is not allowed. EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 STRING(10) )"}), StatusIs(error::ChangingNullConstraintOnIndexedColumn("c1", "Idx1"))); // Changing nullability of stored columns and indexed columns // in null filtered indexes is allowed ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c2 BYTES(40) )"})); auto c2 = new_schema->FindTable("T")->FindColumn("c2"); EXPECT_THAT(c2, ColumnIs("c2", types::BytesType())); EXPECT_EQ(c2->declared_max_length(), 40); auto idx1 = new_schema->FindIndex("Idx1"); auto idx2 = new_schema->FindIndex("Idx2"); EXPECT_THAT(idx1->stored_columns()[0], SourceColumnIs(c2)); EXPECT_THAT(idx2->key_columns()[0]->column(), SourceColumnIs(c2)); } TEST_P(SchemaUpdaterTest, AlterColumn_KeyColumnType) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 STRING(100) NOT NULL, ) PRIMARY KEY (k1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN k1 BYTES(MAX) NOT NULL )"})); const Table* t = new_schema->FindTable("T"); auto c1 = t->FindColumn("k1"); EXPECT_THAT(c1, ColumnIs("k1", types::BytesType())); EXPECT_FALSE(c1->declared_max_length().has_value()); } TEST_P(SchemaUpdaterTest, AlterColumn_KeyColumnNullability) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64 NOT NULL, ) PRIMARY KEY (k1) )"})); EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN k1 INT64 )"}), StatusIs(error::CannotChangeKeyColumn("T.k1", "from NOT NULL to NULL"))); } TEST_P(SchemaUpdaterTest, AlterTable_UnsetAllowCommitTimestamp) { // Assigning column options is not supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 TIMESTAMP OPTIONS ( allow_commit_timestamp=true ), ) PRIMARY KEY (k1) )"})); auto c1 = schema->FindTable("T")->FindColumn("c1"); EXPECT_TRUE(c1->allows_commit_timestamp()); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN c1 SET OPTIONS ( allow_commit_timestamp = false ) )"})); auto c1_new = new_schema->FindTable("T")->FindColumn("c1"); EXPECT_FALSE(c1_new->allows_commit_timestamp()); } TEST_P(SchemaUpdaterTest, AlterTable_DropColumn) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 STRING(MAX), ) PRIMARY KEY (k1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP COLUMN c1 )"})); const Table* t = new_schema->FindTable("T"); auto c1 = t->FindColumn("c1"); EXPECT_EQ(c1, nullptr); } TEST_P(SchemaUpdaterTest, AlterTable_InvalidDropKeyColumn) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, c1 STRING(MAX), ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP COLUMN k1 )"}), StatusIs(error::InvalidDropKeyColumn("k1", "T"))); } TEST_P(SchemaUpdaterTest, AlterTable_InvalidDropIndexedColumn) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T ( k1 INT64, c1 STRING(10) NOT NULL, c2 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE INDEX Idx1 ON T(c1) STORING(c2) )"})); EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP COLUMN c1 )"}), StatusIs(error::InvalidDropColumnWithDependency("c1", "T", "Idx1"))); EXPECT_THAT( UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP COLUMN c2 )"}), StatusIs(error::InvalidDropColumnWithDependency("c2", "T", "Idx1"))); } TEST_P(SchemaUpdaterTest, AlterTable_ChangeOnDelete) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k1 INT64, c1 STRING(10), c2 BOOL, ) PRIMARY KEY (k1, c1), INTERLEAVE IN PARENT T1 )"})); const Table* t2 = schema->FindTable("T2"); EXPECT_NE(t2, nullptr); EXPECT_EQ(t2->on_delete_action(), Table::OnDeleteAction::kNoAction); // Change from NO ACTION to CASCADE. ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T2 SET ON DELETE CASCADE )"})); t2 = new_schema->FindTable("T2"); EXPECT_NE(t2, nullptr); EXPECT_EQ(t2->on_delete_action(), Table::OnDeleteAction::kCascade); // Change from CASCADE to NO ACTION ZETASQL_ASSERT_OK_AND_ASSIGN(new_schema, UpdateSchema(new_schema.get(), {R"( ALTER TABLE T2 SET ON DELETE NO ACTION )"})); t2 = new_schema->FindTable("T2"); EXPECT_NE(t2, nullptr); EXPECT_EQ(t2->on_delete_action(), Table::OnDeleteAction::kNoAction); } TEST_P(SchemaUpdaterTest, AlterTable_AddSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, ) PRIMARY KEY (k1) )"})); const Table* t_old = schema->FindTable("T"); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD SYNONYM S )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_NE(t_old, t_new); const Table* s_new = new_schema->FindTable("S"); EXPECT_EQ(t_new, s_new); } TEST_P(SchemaUpdaterTest, AlterTable_CannotAddDuplicateSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, ) PRIMARY KEY (k1) )", R"( CREATE TABLE S ( k1 INT64, ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD SYNONYM S )"}), StatusIs(error::SchemaObjectAlreadyExists("Table", "S"))); } TEST_P(SchemaUpdaterTest, AlterTable_CannotAddTwoSynonyms) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, SYNONYM(S), ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T ADD SYNONYM S2 )"}), StatusIs(error::SynonymAlreadyExists("S", "T"))); } TEST_P(SchemaUpdaterTest, AlterTable_DropSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, SYNONYM(S), ) PRIMARY KEY (k1) )"})); const Table* t_old = schema->FindTable("T"); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP SYNONYM S )"})); const Table* t_new = new_schema->FindTable("T"); EXPECT_NE(t_old, t_new); const Table* s_new = new_schema->FindTable("S"); EXPECT_EQ(s_new, nullptr); // S is now available for reuse. ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema1, UpdateSchema(new_schema.get(), {R"( CREATE TABLE S ( k1 INT64, ) PRIMARY KEY (k1) )"})); s_new = new_schema1->FindTable("S"); EXPECT_NE(s_new, nullptr); } TEST_P(SchemaUpdaterTest, AlterTable_CannotDropNonExistentSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP SYNONYM S )"}), StatusIs(error::SynonymDoesNotExist("S", "T"))); } TEST_P(SchemaUpdaterTest, AlterTable_CannotDropInvalidSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 INT64, SYNONYM(S), ) PRIMARY KEY (k1) )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP SYNONYM S2 )"}), StatusIs(error::SynonymDoesNotExist("S2", "T"))); } TEST_P(SchemaUpdaterTest, DropTableNonexistentIfExists) { // IF NOT EXISTS isn't yet supported on the PG side of the emulator if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k2 INT64, c2 STRING(MAX), ) PRIMARY KEY (k2) )"})); const Table* t1 = schema->FindTable("T1"); EXPECT_NE(t1, nullptr); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T1 )"})); // Dropped table and its dependent nodes like columns, key columns etc. // are deleted. t1 = new_schema->FindTable("T1"); EXPECT_EQ(t1, nullptr); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema2, UpdateSchema(schema.get(), {R"( DROP TABLE IF EXISTS T1 )"})); // Make sure it's still gone t1 = new_schema2->FindTable("T1"); EXPECT_EQ(t1, nullptr); // Make sure the other table is still there. const Table* t2 = new_schema2->FindTable("T2"); EXPECT_NE(t2, nullptr); } TEST_P(SchemaUpdaterTest, DropTableIfExists) { // IF NOT EXISTS isn't yet supported on the PG side of the emulator if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k2 INT64, c2 STRING(MAX), ) PRIMARY KEY (k2) )"})); const Table* t1 = schema->FindTable("T1"); EXPECT_NE(t1, nullptr); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE IF EXISTS T1 )"})); // Dropped table and its dependent nodes like columns, key columns etc. // are deleted. t1 = new_schema->FindTable("T1"); EXPECT_EQ(t1, nullptr); } TEST_P(SchemaUpdaterTest, DropTable) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k2 INT64, c2 STRING(MAX), ) PRIMARY KEY (k2) )"})); const Table* t1 = schema->FindTable("T1"); EXPECT_NE(t1, nullptr); EXPECT_EQ(schema->GetSchemaGraph()->GetSchemaNodes().size(), 8); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T1 )"})); // Dropped table and its dependent nodes like columns, key columns etc. // are deleted. t1 = new_schema->FindTable("T1"); EXPECT_EQ(t1, nullptr); EXPECT_EQ(new_schema->GetSchemaGraph()->GetSchemaNodes().size(), 4); // The other table is still there. const Table* t2 = new_schema->FindTable("T2"); EXPECT_NE(t2, nullptr); } TEST_P(SchemaUpdaterTest, DropTable_CanDropChildTable) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k1 INT64, k2 INT64, c2 STRING(MAX), ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT T1 )"})); // Cannot drop parent table. EXPECT_THAT(UpdateSchema(schema.get(), {R"( DROP TABLE T1 )"}), StatusIs(error::DropTableWithInterleavedTables("T1", "T2"))); // Can drop child table. ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T2 )"})); const Table* t1 = new_schema->FindTable("T1"); EXPECT_EQ(t1->children().size(), 0); EXPECT_EQ(new_schema->FindTable("T2"), nullptr); } TEST_P(SchemaUpdaterTest, DropTable_CanDropChildAndParentTogether) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k1 INT64, k2 INT64, c2 STRING(MAX), ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT T1 )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T2)", R"( DROP TABLE T1)"})); EXPECT_TRUE(new_schema->tables().empty()); } TEST_P(SchemaUpdaterTest, DropTable_Recreate) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T1 )"})); EXPECT_TRUE(new_schema->tables().empty()); ZETASQL_ASSERT_OK_AND_ASSIGN(new_schema, UpdateSchema(new_schema.get(), {R"( CREATE TABLE T1 ( k1 INT64, c1 STRING(10), ) PRIMARY KEY (k1) )"})); const Table* t1 = new_schema->FindTable("T1"); EXPECT_NE(t1, nullptr); } TEST_P(SchemaUpdaterTest, DropTableWithSynonym) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T1 ( k1 INT64, SYNONYM(S1) ) PRIMARY KEY (k1) )"})); const Table* t1 = schema->FindTable("T1"); EXPECT_NE(t1, nullptr); const Table* s1 = schema->FindTable("S1"); EXPECT_NE(s1, nullptr); EXPECT_EQ(t1, s1); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( DROP TABLE T1 )"})); // Dropped table and its dependent nodes like columns, key columns etc. // are deleted. t1 = new_schema->FindTable("T1"); EXPECT_EQ(t1, nullptr); s1 = new_schema->FindTable("S1"); EXPECT_EQ(s1, nullptr); } TEST_P(SchemaUpdaterTest, ChangeKeyColumn) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({ R"( CREATE TABLE T1 ( k1 STRING(30), k2 INT64 ) PRIMARY KEY (k1) )", R"( CREATE TABLE T2 ( k1 STRING(30), k2 INT64, c1 BYTES(MAX) ) PRIMARY KEY (k1,k2), INTERLEAVE IN PARENT T1 )"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T2 ALTER COLUMN k1 STRING(30) )"}), StatusIs(error::AlteringParentColumn("T2.k1"))); } TEST_P(SchemaUpdaterTest, CreateTable_NumericColumns) { if (GetParam() == POSTGRESQL) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 PG.NUMERIC, col3 ARRAY<PG.NUMERIC> ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 3); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_TRUE(col2->GetType()->IsExtendedType()); EXPECT_EQ(static_cast<const postgres_translator::spangres::datatypes:: SpannerExtendedType*>(col2->GetType()) ->code(), google::spanner::v1::TypeAnnotationCode::PG_NUMERIC); auto col3 = t->columns()[2]; EXPECT_TRUE(col3->GetType()->IsArray()); EXPECT_TRUE(col3->GetType()->AsArray()->element_type()->IsExtendedType()); EXPECT_EQ(static_cast<const postgres_translator::spangres::datatypes:: SpannerExtendedType*>( col3->GetType()->AsArray()->element_type()) ->code(), google::spanner::v1::TypeAnnotationCode::PG_NUMERIC); } else { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 NUMERIC, col3 ARRAY<NUMERIC> ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 3); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::NumericType())); auto col3 = t->columns()[2]; EXPECT_THAT(col3, ColumnIs("col3", types::NumericArrayType())); } } TEST_P(SchemaUpdaterTest, CreateTable_NumericAsPK) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); EmulatorFeatureFlags::Flags flags; emulator::test::ScopedEmulatorFeatureFlagsSetter setter(flags); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T ( k1 NUMERIC ) PRIMARY KEY (k1) )"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 1); EXPECT_EQ(t->primary_key().size(), 1); EXPECT_THAT(t->columns()[0], ColumnIs("k1", types::NumericType())); } TEST_P(SchemaUpdaterTest, CreateTable_JsonColumns) { std::unique_ptr<const Schema> schema; if (GetParam() == POSTGRESQL) { ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 PG.JSONB, col3 ARRAY<PG.JSONB> ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 3); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_TRUE(col2->GetType()->IsExtendedType()); EXPECT_EQ(static_cast<const postgres_translator::spangres::datatypes:: SpannerExtendedType*>(col2->GetType()) ->code(), google::spanner::v1::TypeAnnotationCode::PG_JSONB); auto col3 = t->columns()[2]; EXPECT_TRUE(col3->GetType()->IsArray()); EXPECT_TRUE(col3->GetType()->AsArray()->element_type()->IsExtendedType()); EXPECT_EQ(static_cast<const postgres_translator::spangres::datatypes:: SpannerExtendedType*>( col3->GetType()->AsArray()->element_type()) ->code(), google::spanner::v1::TypeAnnotationCode::PG_JSONB); } else { EmulatorFeatureFlags::Flags flags; emulator::test::ScopedEmulatorFeatureFlagsSetter setter(flags); ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 JSON, col3 ARRAY<JSON> ) PRIMARY KEY(col1))"})); const Table* t = schema->FindTable("T"); EXPECT_NE(t, nullptr); EXPECT_EQ(t->columns().size(), 3); EXPECT_EQ(t->primary_key().size(), 1); auto col1 = t->columns()[0]; EXPECT_THAT(col1, ColumnIs("col1", types::Int64Type())); EXPECT_THAT(col1, IsKeyColumnOf(t, "ASC")); auto col2 = t->columns()[1]; EXPECT_THAT(col2, ColumnIs("col2", types::JsonType())); auto col3 = t->columns()[2]; EXPECT_THAT(col3, ColumnIs("col3", types::JsonArrayType())); } } TEST_P(SchemaUpdaterTest, CreateTable_JsonAsPK) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); EmulatorFeatureFlags::Flags flags; emulator::test::ScopedEmulatorFeatureFlagsSetter setter(flags); EXPECT_THAT(CreateSchema({R"( CREATE TABLE T ( k1 JSON ) PRIMARY KEY (k1) )"}), StatusIs(error::InvalidPrimaryKeyColumnType("T.k1", "JSON"))); } std::vector<std::string> SchemaForCaseSensitivityTests() { return { R"sql( CREATE TABLE T ( k1 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1) )sql", R"sql( CREATE TABLE T1 ( a INT64 NOT NULL, ) PRIMARY KEY (a) )sql", }; } TEST_P(SchemaUpdaterTest, PrimaryKeyIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( CREATE TABLE T2 ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, ) PRIMARY KEY (K1) )"}), StatusIs(error::NonExistentKeyColumn("Table", "T2", "K1"))); } TEST_P(SchemaUpdaterTest, TableNameIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( CREATE TABLE t ( k1 INT64 NOT NULL, ) PRIMARY KEY (K1) )"}), StatusIs(error::SchemaObjectAlreadyExists("T", "t"))); } TEST_P(SchemaUpdaterTest, InterleaveTableNameIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( CREATE TABLE Child ( k1 INT64 NOT NULL, k2 INT64 NOT NULL, c1 STRING(MAX) ) PRIMARY KEY (k1, k2), INTERLEAVE IN PARENT `t` ON DELETE CASCADE )"}), StatusIs(error::TableNotFound("t"))); } TEST_P(SchemaUpdaterTest, AlterTableNameIsCaseSensitive) { // Only BYTES(MAX) is supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE t ADD COLUMN k2 BYTES(100) )"}), StatusIs(error::TableNotFound("t"))); } TEST_P(SchemaUpdaterTest, AlterTableSetOptionsIsCaseSensitive) { // Assigning column options is not supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE t ALTER COLUMN c1 SET OPTIONS ( allow_commit_timestamp = false ) )"}), StatusIs(error::TableNotFound("t"))); } TEST_P(SchemaUpdaterTest, DropTableIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( DROP TABLE t)"}), StatusIs(error::TableNotFound("t"))); } TEST_P(SchemaUpdaterTest, AlterColumnIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T ALTER COLUMN K2 INT64 DEFAULT(0) )"}), StatusIs(error::ColumnNotFound("T", "K2"))); } TEST_P(SchemaUpdaterTest, DropColumnIsCaseSensitive) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema(SchemaForCaseSensitivityTests())); EXPECT_THAT(UpdateSchema(schema.get(), {R"( ALTER TABLE T DROP COLUMN K2 )"}), StatusIs(error::ColumnNotFound("T", "K2"))); } TEST_P(SchemaUpdaterTest, CreateTableIfNotExists) { // IF NOT EXISTS isn't yet supported on the PG side of the emulator if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"( CREATE TABLE T( col1 INT64, col2 STRING(MAX) ) PRIMARY KEY(col1 DESC, col2))"})); ZETASQL_ASSERT_OK_AND_ASSIGN(auto new_schema, UpdateSchema(schema.get(), {R"( CREATE TABLE IF NOT EXISTS T( col1 STRING(MAX), col3 INT64 ) PRIMARY KEY(col3))"})); const Table* t = new_schema->FindTable("T"); // If the new table was *not* created (it shouldn't be) then this will be // null. ASSERT_EQ(t->FindColumn("col3"), nullptr); // If the new table wasn't created (it shouldn't be) then this *won't* be // null. ASSERT_NE(t->FindColumn("col2"), nullptr); } TEST_P(SchemaUpdaterTest, CreateTableWithVectorLength) { ZETASQL_EXPECT_OK(CreateSchema({R"( CREATE TABLE T ( k1 INT64, a1 ARRAY<FLOAT64>(vector_length=>123), a2 ARRAY<FLOAT32>(vector_length=>123), ) PRIMARY KEY (k1) )"})); } TEST_P(SchemaUpdaterTest, CreateTableWithVectorLengthOnInvalidType) { EXPECT_THAT(CreateSchema({R"sql( CREATE TABLE T ( k1 INT64, a1 ARRAY<INT64>(vector_length=>123), ) PRIMARY KEY (k1) )sql"}), StatusIs(error::InvalidTypeForVectorLength("T.a1"))); } TEST_P(SchemaUpdaterTest, AlterTableAlterColumnAddVectorLength) { // ALTER TABLE ALTER COLUMN to add vector length param is not supported in PG. if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"sql( CREATE TABLE T ( k1 INT64, a1 ARRAY<FLOAT64>(vector_length=>123), a2 ARRAY<FLOAT32>, ) PRIMARY KEY (k1) )sql"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"sql( ALTER TABLE T ALTER COLUMN a2 ARRAY<FLOAT64>(vector_length=>10) )sql"}), StatusIs(error::CannotAlterColumnToAddVectorLength("a2"))); } TEST_P(SchemaUpdaterTest, AlterTableAlterColumnRemoveVectorLength) { ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"sql( CREATE TABLE T ( k1 INT64, a1 ARRAY<FLOAT64>(vector_length=>2), a2 ARRAY<FLOAT32>, ) PRIMARY KEY (k1) )sql"})); EXPECT_THAT( UpdateSchema(schema.get(), {R"sql( ALTER TABLE T ALTER COLUMN a1 ARRAY<FLOAT64>)sql"}), StatusIs(error::CannotAlterColumnToRemoveVectorLength("a1"))); } TEST_P(SchemaUpdaterTest, CreateTableWithVectorLengthOnGeneratedOrDefaultColumn) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); EXPECT_THAT( CreateSchema({R"sql( CREATE TABLE T ( k1 INT64, Arr1 ARRAY<FLOAT64>(vector_length=>3) DEFAULT ([1.1,2.2,3.3,4.4]) ) PRIMARY KEY (k1) )sql"}), StatusIs(error::VectorLengthOnGeneratedOrDefaultColumn("T.Arr1"))); EXPECT_THAT( CreateSchema({R"sql( CREATE TABLE T ( k1 INT64, Arr1 ARRAY<FLOAT64> DEFAULT ([1.0]), Arr2 ARRAY<FLOAT64>(vector_length=>3) AS (Arr1) ) PRIMARY KEY (k1) )sql"}), StatusIs(error::VectorLengthOnGeneratedOrDefaultColumn("T.Arr2"))); } TEST_P(SchemaUpdaterTest, CreateTable_Tokenlist_Column_Not_Hidden) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); EXPECT_THAT(CreateSchema({R"sql( CREATE TABLE T( col1 INT64, col_token TOKENLIST AS (TOKEN(col1)) STORED, ) PRIMARY KEY(col1))sql"}), StatusIs(error::NonHiddenTokenlistColumn("T", "col_token"))); } TEST_P(SchemaUpdaterTest, AlterTable_Add_Non_Hidden_Tokenlist_Column) { if (GetParam() == POSTGRESQL) GTEST_SKIP(); ZETASQL_ASSERT_OK_AND_ASSIGN(auto schema, CreateSchema({R"sql( CREATE TABLE T( col1 INT64 ) PRIMARY KEY(col1))sql"})); EXPECT_THAT(UpdateSchema(schema.get(), {R"sql( ALTER TABLE T ADD COLUMN col_token TOKENLIST AS (TOKEN(col1)) STORED )sql"}), StatusIs(error::NonHiddenTokenlistColumn("T", "col_token"))); } TEST_P(SchemaUpdaterTest, CreateTable_Interval_Column_Fail) { // INTERVAL is query only type and is not supported as a column type. std::string sql = R"sql( CREATE TABLE T( col1 INT64, col_interval INTERVAL ) PRIMARY KEY(col1))sql"; absl::StatusOr<std::unique_ptr<const Schema>> status = CreateSchema({sql}); EXPECT_FALSE(status.ok()); } TEST_P(SchemaUpdaterTest, CreateTable_IntervalArray_Column_Fail) { // INTERVAL is query only type and is not supported as a column type. std::string sql; if (GetParam() == POSTGRESQL) { sql = R"sql( CREATE TABLE T( col1 INT64, col_interval INTERVAL[] ) PRIMARY KEY(col1))sql"; } else { sql = R"sql( CREATE TABLE T( col1 INT64, col_interval ARRAY<INTERVAL> ) PRIMARY KEY(col1))sql"; } absl::StatusOr<std::unique_ptr<const Schema>> status = CreateSchema({sql}); EXPECT_FALSE(status.ok()); } TEST_P(SchemaUpdaterTest, CreateTable_IntervalStruct_Column_Fail) { // INTERVAL is query only type and is not supported as a column type. if (GetParam() == POSTGRESQL) { GTEST_SKIP(); } std::string sql = R"sql( CREATE TABLE T( col1 INT64, col_interval STRUCT<interval_val INTERVAL> ) PRIMARY KEY(col1))sql"; absl::StatusOr<std::unique_ptr<const Schema>> status = CreateSchema({sql}); EXPECT_FALSE(status.ok()); } } // namespace } // namespace test } // namespace backend } // namespace emulator } // namespace spanner } // namespace google