backend/schema/updater/schema_updater_tests/identity_column.cc (476 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 <optional>
#include "google/spanner/admin/database/v1/common.pb.h"
#include "zetasql/public/options.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 "backend/schema/catalog/column.h"
#include "backend/schema/catalog/schema.h"
#include "backend/schema/catalog/sequence.h"
#include "backend/schema/updater/schema_updater_tests/base.h"
namespace google {
namespace spanner {
namespace emulator {
namespace backend {
namespace test {
using database_api::DatabaseDialect::POSTGRESQL;
using ::testing::HasSubstr;
using ::zetasql_base::testing::StatusIs;
using IdentityColumnTest = SchemaUpdaterTest;
INSTANTIATE_TEST_SUITE_P(
SchemaUpdaterPerDialectTests, IdentityColumnTest,
testing::Values(database_api::DatabaseDialect::GOOGLE_STANDARD_SQL,
database_api::DatabaseDialect::POSTGRESQL),
[](const testing::TestParamInfo<IdentityColumnTest::ParamType>& info) {
return database_api::DatabaseDialect_Name(info.param);
});
TEST_P(IdentityColumnTest, Basic) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE START COUNTER WITH 10 SKIP RANGE 100, 1000),
non_key_col INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE START COUNTER WITH 20 SKIP RANGE 200, 2000),
value INT64,
) PRIMARY KEY(id)
)"}));
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("id");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->has_default_value());
EXPECT_TRUE(col->is_identity_column());
ASSERT_TRUE(col->expression().has_value());
if (GetParam() == POSTGRESQL) {
EXPECT_EQ(col->expression().value(),
"(GET_NEXT_SEQUENCE_VALUE(\"_identity_seq_T__id\"))");
} else {
EXPECT_EQ(col->expression().value(),
"(GET_NEXT_SEQUENCE_VALUE(SEQUENCE _identity_seq_T__id))");
}
ASSERT_EQ(col->sequences_used().size(), 1);
const Sequence* seq =
static_cast<const Sequence*>(col->sequences_used().at(0));
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->Name(), "_identity_seq_T__id");
EXPECT_EQ(seq->sequence_kind_name(), "BIT_REVERSED_POSITIVE");
EXPECT_EQ(seq->start_with_counter(), 10);
EXPECT_EQ(seq->skip_range_min(), 100);
EXPECT_EQ(seq->skip_range_max(), 1000);
col = table->FindColumn("non_key_col");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->has_default_value());
EXPECT_TRUE(col->is_identity_column());
ASSERT_TRUE(col->expression().has_value());
if (GetParam() == POSTGRESQL) {
EXPECT_EQ(col->expression().value(),
"(GET_NEXT_SEQUENCE_VALUE(\"_identity_seq_T__non_key_col\"))");
} else {
EXPECT_EQ(
col->expression().value(),
"(GET_NEXT_SEQUENCE_VALUE(SEQUENCE _identity_seq_T__non_key_col))");
}
ASSERT_EQ(col->sequences_used().size(), 1);
seq = static_cast<const Sequence*>(col->sequences_used().at(0));
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->Name(), "_identity_seq_T__non_key_col");
EXPECT_EQ(seq->sequence_kind_name(), "BIT_REVERSED_POSITIVE");
EXPECT_EQ(seq->start_with_counter(), 20);
EXPECT_EQ(seq->skip_range_min(), 200);
EXPECT_EQ(seq->skip_range_max(), 2000);
}
TEST_P(IdentityColumnTest, NamedSchema) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE SCHEMA sch
)",
R"(
CREATE TABLE sch.T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
non_key_col INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)"}));
const Table* table = schema->FindTable("sch.T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("id");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->is_identity_column());
col = table->FindColumn("non_key_col");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->is_identity_column());
}
TEST_P(IdentityColumnTest, DropDefault) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ALTER COLUMN id DROP DEFAULT
)"}));
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("id");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->has_default_value());
EXPECT_TRUE(col->is_identity_column());
ASSERT_EQ(col->sequences_used().size(), 1);
const Sequence* seq =
static_cast<const Sequence*>(col->sequences_used().at(0));
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->Name(), "_identity_seq_T__id");
}
TEST_P(IdentityColumnTest, DropTable) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({
R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
non_key_col INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
DROP TABLE T
)"}));
const Table* table = schema->FindTable("T");
ASSERT_EQ(table, nullptr);
const Sequence* seq = schema->FindSequence("_identity_seq_T__non_key_col");
EXPECT_EQ(seq, nullptr);
// Test to recreate the same table again.
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, UpdateSchema(schema.get(), {
R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
non_key_col INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)"}));
table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
seq = schema->FindSequence("_identity_seq_T__non_key_col");
EXPECT_NE(seq, nullptr);
}
TEST_P(IdentityColumnTest, DropIdentityColumn) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
non_key_col INT64 GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T DROP COLUMN non_key_col
)"}));
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("non_key_col");
ASSERT_EQ(col, nullptr);
const Sequence* seq = schema->FindSequence("_identity_seq_T__non_key_col");
EXPECT_EQ(seq, nullptr);
}
TEST_P(IdentityColumnTest, AddColumn) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ADD COLUMN non_key_col INT64
GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)
)"}));
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("non_key_col");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->has_default_value());
EXPECT_TRUE(col->is_identity_column());
ASSERT_EQ(col->sequences_used().size(), 1);
const Sequence* seq =
static_cast<const Sequence*>(col->sequences_used().at(0));
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->Name(), "_identity_seq_T__non_key_col");
}
TEST_P(IdentityColumnTest, AlterEntireColumn) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
// ALTER COLUMN dosn't support altering an entire column to identity column.
// See: ALTER COLUMN in
// https://cloud.google.com/spanner/docs/reference/postgresql/data-definition-language#alter_table
GTEST_SKIP();
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ALTER COLUMN id INT64
GENERATED BY DEFAULT AS IDENTITY (
BIT_REVERSED_POSITIVE START COUNTER WITH 1000)
)"}));
}
const Sequence* seq = schema->FindSequence("_identity_seq_T__id");
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->start_with_counter(), 1000);
}
TEST_P(IdentityColumnTest, AlterRestartCounter) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ALTER COLUMN id ALTER IDENTITY RESTART COUNTER WITH 1000
)"}));
const Sequence* seq = schema->FindSequence("_identity_seq_T__id");
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->start_with_counter(), 1000);
}
TEST_P(IdentityColumnTest, AlterSkipRange) {
std::unique_ptr<const Schema> schema;
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ALTER COLUMN id ALTER IDENTITY SET SKIP RANGE 2000, 3000
)"}));
const Sequence* seq = schema->FindSequence("_identity_seq_T__id");
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->skip_range_min(), 2000);
EXPECT_EQ(seq->skip_range_max(), 3000);
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, UpdateSchema(schema.get(), {R"(
ALTER TABLE T ALTER COLUMN id ALTER IDENTITY SET NO SKIP RANGE
)"}));
seq = schema->FindSequence("_identity_seq_T__id");
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->skip_range_min(), std::nullopt);
EXPECT_EQ(seq->skip_range_max(), std::nullopt);
}
TEST_P(IdentityColumnTest, SetDefaultSequenceKindFromNullToValue) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema,
CreateSchema({R"(
ALTER DATABASE db SET spanner.default_sequence_kind = DEFAULT
)",
R"(
ALTER DATABASE db
SET spanner.default_sequence_kind = 'bit_reversed_positive'
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
ALTER DATABASE db SET OPTIONS (default_sequence_kind = NULL)
)",
R"(
ALTER DATABASE db SET OPTIONS (
default_sequence_kind = 'bit_reversed_positive')
)"}));
}
}
TEST_P(IdentityColumnTest, SetDefaultSequenceKindFromValueToNull) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(CreateSchema({R"(
ALTER DATABASE db
SET spanner.default_sequence_kind = 'bit_reversed_positive'
)",
R"(
ALTER DATABASE db SET spanner.default_sequence_kind = DEFAULT
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("The default_sequence_kind option may not "
"be unset once set.")));
} else {
EXPECT_THAT(CreateSchema({R"(
ALTER DATABASE db SET OPTIONS (
default_sequence_kind = 'bit_reversed_positive')
)",
R"(
ALTER DATABASE db SET OPTIONS (default_sequence_kind = NULL)
)"}),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("The default_sequence_kind option may not "
"be unset once set.")));
}
}
TEST_P(IdentityColumnTest, UnsupportedDefaultSequenceKindValue) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(
CreateSchema({R"(
ALTER DATABASE db
SET spanner.default_sequence_kind = 1
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("Incorrect datatype specified for option.")));
} else {
EXPECT_THAT(
CreateSchema({R"(
ALTER DATABASE db SET OPTIONS (default_sequence_kind = 1)
)"}),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Unexpected value for option: default_sequence_kind. "
"Supported option values are strings and NULL.")));
}
}
TEST_P(IdentityColumnTest, UseDefaultSequenceKindOptionFailed) {
std::unique_ptr<const Schema> schema;
EXPECT_THAT(
CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY,
value INT64,
) PRIMARY KEY(id)
)"}),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("The sequence kind of an identity column id is not "
"specified. Please specify the sequence kind explicitly "
"or set the database option `default_sequence_kind`.")));
}
TEST_P(IdentityColumnTest, UseDefaultSequenceKindOptionSuccess) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema,
CreateSchema({R"(
ALTER DATABASE db
SET spanner.default_sequence_kind = 'bit_reversed_positive'
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
ALTER DATABASE db SET OPTIONS (
default_sequence_kind = 'bit_reversed_positive')
)"}));
}
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, UpdateSchema(schema.get(), {R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY,
value INT64,
) PRIMARY KEY(id)
)"}));
const Sequence* seq = schema->FindSequence("_identity_seq_T__id");
ASSERT_NE(seq, nullptr);
EXPECT_EQ(seq->sequence_kind_name(), "BIT_REVERSED_POSITIVE");
}
TEST_P(IdentityColumnTest, AlterNonIdentityColumn) {
std::unique_ptr<const Schema> schema;
EXPECT_THAT(
CreateSchema({R"(
CREATE TABLE T (
id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
value INT64,
) PRIMARY KEY(id)
)",
R"(
ALTER TABLE T ALTER COLUMN value ALTER IDENTITY SET NO SKIP RANGE
)"}),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Column is not an identity column in table T: value")));
}
TEST_P(IdentityColumnTest, SerialAutoIncrement) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema(
{
R"(
ALTER DATABASE db
SET spanner.default_sequence_kind = 'bit_reversed_positive'
)",
R"(
CREATE TABLE T (
id serial,
value bigint,
PRIMARY KEY(id)
)
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false));
} else {
ZETASQL_ASSERT_OK_AND_ASSIGN(schema, CreateSchema({R"(
ALTER DATABASE db SET OPTIONS (
default_sequence_kind = 'bit_reversed_positive')
)",
R"(
CREATE TABLE T (
id INT64 AUTO_INCREMENT,
value INT64,
) PRIMARY KEY(id)
)"}));
}
const Table* table = schema->FindTable("T");
ASSERT_NE(table, nullptr);
const Column* col = table->FindColumn("id");
ASSERT_NE(col, nullptr);
EXPECT_TRUE(col->is_identity_column());
if (GetParam() == POSTGRESQL) {
EXPECT_EQ(col->is_nullable(), false);
} else {
EXPECT_EQ(col->is_nullable(), true);
}
}
TEST_P(IdentityColumnTest, SerialAutoIncrementWithoutSettingDatabaseOption) {
std::unique_ptr<const Schema> schema;
if (GetParam() == POSTGRESQL) {
EXPECT_THAT(
CreateSchema({R"(
CREATE TABLE T (
id serial,
value bigint,
PRIMARY KEY(id)
)
)"},
/*proto_descriptor_bytes=*/"",
/*dialect=*/POSTGRESQL,
/*use_gsql_to_pg_translation=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("The sequence kind of an identity column id is not "
"specified.")));
} else {
EXPECT_THAT(CreateSchema({R"(
CREATE TABLE T (
id INT64 AUTO_INCREMENT,
value INT64,
) PRIMARY KEY(id)
)"}),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("The sequence kind of an identity column id "
"is not specified.")));
}
}
} // namespace test
} // namespace backend
} // namespace emulator
} // namespace spanner
} // namespace google