From f3c4e6fc1adf7355b3e48d74b1735f86d16dda54 Mon Sep 17 00:00:00 2001 From: Jane Xing Date: Wed, 6 Jul 2022 17:19:13 -0400 Subject: [PATCH] sql: add sequence option info for identity columns under information_schema Previously, for a column created with the `GENERATED ... AS IDENTITY (seq_options)` syntax, the info for the sequence option is not saved in the information schema. This commit is to fix it. We parse the sequence options saved as a string in the descriptor, so that it's much easier to extract specific option such as sequence's start value or increment size. To make sure that we get the same sequence option to generate the sequence, we reuse `assignSequenceOptions()` by breaking it into several helper functions. fixes https://github.com/cockroachdb/cockroach/issues/82064 Release note (sql): add sequence option info for identity columns under information_schema --- pkg/sql/catalog/schemaexpr/column.go | 2 +- pkg/sql/catalog/table_elements.go | 7 +- pkg/sql/catalog/tabledesc/column.go | 410 +++++++++++++++++- pkg/sql/information_schema.go | 21 +- .../testdata/logic_test/information_schema | 169 ++++++++ pkg/sql/schemachanger/scdecomp/decomp.go | 2 +- pkg/sql/sequence.go | 293 ++----------- pkg/sql/types/types.go | 14 + 8 files changed, 645 insertions(+), 273 deletions(-) diff --git a/pkg/sql/catalog/schemaexpr/column.go b/pkg/sql/catalog/schemaexpr/column.go index fdbce0c8a763..7fa7061a93aa 100644 --- a/pkg/sql/catalog/schemaexpr/column.go +++ b/pkg/sql/catalog/schemaexpr/column.go @@ -107,7 +107,7 @@ func FormatColumnForDisplay( f.WriteString(" GENERATED BY DEFAULT AS IDENTITY") } if col.HasGeneratedAsIdentitySequenceOption() { - seqOpt := col.GetGeneratedAsIdentitySequenceOption() + seqOpt := col.GetGeneratedAsIdentitySequenceOptionStr() s := formatGeneratedAsIdentitySequenceOption(seqOpt) f.WriteString(s) } diff --git a/pkg/sql/catalog/table_elements.go b/pkg/sql/catalog/table_elements.go index 2856549c6662..23039119db7a 100644 --- a/pkg/sql/catalog/table_elements.go +++ b/pkg/sql/catalog/table_elements.go @@ -367,9 +367,14 @@ type Column interface { // `GENERATED AS IDENTITY` column. HasGeneratedAsIdentitySequenceOption() bool + // GetGeneratedAsIdentitySequenceOptionStr returns the string representation + // of the column's `GENERATED AS IDENTITY` sequence option if it exists, empty + // string otherwise. + GetGeneratedAsIdentitySequenceOptionStr() string + // GetGeneratedAsIdentitySequenceOption returns the column's `GENERATED AS // IDENTITY` sequence option if it exists, empty string otherwise. - GetGeneratedAsIdentitySequenceOption() string + GetGeneratedAsIdentitySequenceOption() *descpb.TableDescriptor_SequenceOpts } // ConstraintToUpdate is an interface around a constraint mutation. diff --git a/pkg/sql/catalog/tabledesc/column.go b/pkg/sql/catalog/tabledesc/column.go index df8a8dad0464..2fac107934de 100644 --- a/pkg/sql/catalog/tabledesc/column.go +++ b/pkg/sql/catalog/tabledesc/column.go @@ -11,6 +11,8 @@ package tabledesc import ( + "math" + "strconv" "strings" "github.com/cockroachdb/cockroach/pkg/sql/catalog" @@ -19,7 +21,9 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/types" + "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" "github.com/cockroachdb/cockroach/pkg/util/protoutil" + "github.com/cockroachdb/errors" ) var _ catalog.Column = (*column)(nil) @@ -233,15 +237,145 @@ func (w column) GetGeneratedAsIdentityType() catpb.GeneratedAsIdentityType { return w.desc.GeneratedAsIdentityType } -// GetGeneratedAsIdentitySequenceOption returns the column's `GENERATED AS -// IDENTITY` sequence option if it exists, empty string otherwise. -func (w column) GetGeneratedAsIdentitySequenceOption() string { +// GetGeneratedAsIdentitySequenceOptionStr returns the string representation +// of the column's `GENERATED AS IDENTITY` sequence option if it exists, empty +// string otherwise. +func (w column) GetGeneratedAsIdentitySequenceOptionStr() string { if !w.HasGeneratedAsIdentitySequenceOption() { return "" } return strings.TrimSpace(*w.desc.GeneratedAsIdentitySequenceOption) } +// GetGeneratedAsIdentitySequenceOption returns the column's `GENERATED AS +// IDENTITY` sequence option if it exists, empty string otherwise. +func (w column) GetGeneratedAsIdentitySequenceOption() *descpb.TableDescriptor_SequenceOpts { + if !w.HasGeneratedAsIdentitySequenceOption() { + return nil + } + seqOpts, err := transformStringToSeqOption(*w.desc.GeneratedAsIdentitySequenceOption) + if err != nil { + return nil + } + return seqOpts +} + +// transformStringToSeqOption is to transform the sequence options saved the +// descriptor to a descpb.TableDescriptor_SequenceOpts. +// We don't do extra check for the values following the keywords, since they +// should have been checked via sql.go. +func transformStringToSeqOption(s string) (*descpb.TableDescriptor_SequenceOpts, error) { + var res []tree.SequenceOption + arr := strings.Fields(strings.ToUpper(s)) + i := 0 + for i < len(arr) { + switch arr[i] { + case tree.SeqOptAs: + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptAs, + AsIntegerType: types.ConvertStrToIntType(arr[i+1]), + }) + i += 2 + case tree.SeqOptCache: + cacheSize, _ := strconv.ParseInt(arr[i+1], 10, 64) + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptCache, + IntVal: &cacheSize, + }) + i += 2 + case tree.SeqOptIncrement: + if arr[i+1] == "BY" { + i++ + } + incrementSize, _ := strconv.ParseInt(arr[i+1], 10, 64) + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptIncrement, + IntVal: &incrementSize, + }) + i += 2 + case tree.SeqOptMinValue: + minVal, _ := strconv.ParseInt(arr[i+1], 10, 64) + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptMinValue, + IntVal: &minVal, + }) + i += 2 + case tree.SeqOptMaxValue: + maxVal, _ := strconv.ParseInt(arr[i+1], 10, 64) + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptMaxValue, + IntVal: &maxVal, + }) + i += 2 + case tree.SeqOptStart: + if arr[i+1] == "WITH" { + i++ + } + startVal, _ := strconv.ParseInt(arr[i+1], 10, 64) + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptStart, + IntVal: &startVal, + }) + i += 2 + case tree.SeqOptRestart: + if arr[i+1] == "WITH" { + i++ + } + restartVal, err := strconv.ParseInt(arr[i+1], 10, 64) + if err != nil { + // This means it uses the single RESTART keyword, without any int value + // following. + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptStart, + }) + i++ + continue + } + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptStart, + IntVal: &restartVal, + }) + i += 2 + case tree.SeqOptVirtual: + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptVirtual, + }) + i++ + case "NO": + switch arr[i+1] { + case tree.SeqOptCycle: + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptNoCycle, + }) + case tree.SeqOptMinValue: + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptMinValue, + }) + case tree.SeqOptMaxValue: + res = append(res, tree.SequenceOption{ + Name: tree.SeqOptMaxValue, + }) + } + i += 2 + default: + i++ + } + } + opts := &descpb.TableDescriptor_SequenceOpts{ + Increment: 1, + } + if err := AssignSequenceOptions( + opts, + res, + true, /* setDefaults */ + nil, /* existingType */ + ); err != nil { + return nil, err + } + + return opts, nil +} + // HasGeneratedAsIdentitySequenceOption returns true if there is a // customized sequence option when this column is created as a // `GENERATED AS IDENTITY` column. @@ -447,3 +581,273 @@ func lazyAllocAppendColumn(slice *[]catalog.Column, col catalog.Column, cap int) } *slice = append(*slice, col) } + +func getSequenceIntegerBounds( + integerType *types.T, +) (lowerIntBound int64, upperIntBound int64, err error) { + switch integerType { + case types.Int2: + return math.MinInt16, math.MaxInt16, nil + case types.Int4: + return math.MinInt32, math.MaxInt32, nil + case types.Int: + return math.MinInt64, math.MaxInt64, nil + } + + return 0, 0, errors.AssertionFailedf( + "CREATE SEQUENCE option AS received type %s, must be integer", + integerType, + ) +} + +func setSequenceIntegerBounds( + opts *descpb.TableDescriptor_SequenceOpts, + integerType *types.T, + isAscending bool, + setMinValue bool, + setMaxValue bool, +) error { + var minValue int64 = math.MinInt64 + var maxValue int64 = math.MaxInt64 + + if isAscending { + minValue = 1 + + switch integerType { + case types.Int2: + maxValue = math.MaxInt16 + case types.Int4: + maxValue = math.MaxInt32 + case types.Int: + // Do nothing, it's the default. + default: + return errors.AssertionFailedf( + "CREATE SEQUENCE option AS received type %s, must be integer", + integerType, + ) + } + } else { + maxValue = -1 + switch integerType { + case types.Int2: + minValue = math.MinInt16 + case types.Int4: + minValue = math.MinInt32 + case types.Int: + // Do nothing, it's the default. + default: + return errors.AssertionFailedf( + "CREATE SEQUENCE option AS received type %s, must be integer", + integerType, + ) + } + } + if setMinValue { + opts.MinValue = minValue + } + if setMaxValue { + opts.MaxValue = maxValue + } + return nil +} + +// AssignSequenceOptions moves options from the AST node to the sequence options descriptor, +// starting with defaults and overriding them with user-provided options. +func AssignSequenceOptions( + opts *descpb.TableDescriptor_SequenceOpts, + optsNode tree.SequenceOptions, + setDefaults bool, + existingType *types.T, +) error { + wasAscending := opts.Increment > 0 + + // Set the default integer type of a sequence. + var integerType = types.Int + // All other defaults are dependent on the value of increment + // and the AS integerType. (i.e. whether the sequence is ascending + // or descending, bigint vs. smallint) + for _, option := range optsNode { + if option.Name == tree.SeqOptIncrement { + opts.Increment = *option.IntVal + } else if option.Name == tree.SeqOptAs { + integerType = option.AsIntegerType + opts.AsIntegerType = integerType.SQLString() + } + } + if opts.Increment == 0 { + return errors.New("INCREMENT must not be zero") + } + isAscending := opts.Increment > 0 + + // Set increment-dependent defaults. + if setDefaults { + if isAscending { + opts.MinValue = 1 + opts.MaxValue = math.MaxInt64 + opts.Start = opts.MinValue + } else { + opts.MinValue = math.MinInt64 + opts.MaxValue = -1 + opts.Start = opts.MaxValue + } + opts.CacheSize = 1 + } + + lowerIntBound, upperIntBound, err := getSequenceIntegerBounds(integerType) + if err != nil { + return err + } + + // Set default MINVALUE and MAXVALUE if AS option value for integer type is specified. + if opts.AsIntegerType != "" { + // We change MINVALUE and MAXVALUE if it is the originally set to the default during ALTER. + setMinValue := setDefaults + setMaxValue := setDefaults + if !setDefaults && existingType != nil { + existingLowerIntBound, existingUpperIntBound, err := getSequenceIntegerBounds(existingType) + if err != nil { + return err + } + if (wasAscending && opts.MinValue == 1) || (!wasAscending && opts.MinValue == existingLowerIntBound) { + setMinValue = true + } + if (wasAscending && opts.MaxValue == existingUpperIntBound) || (!wasAscending && opts.MaxValue == -1) { + setMaxValue = true + } + } + + if err := setSequenceIntegerBounds( + opts, + integerType, + isAscending, + setMinValue, + setMaxValue, + ); err != nil { + return err + } + } + + // Fill in all other options. + var restartVal *int64 + optionsSeen := map[string]bool{} + for _, option := range optsNode { + // Error on duplicate options. + _, seenBefore := optionsSeen[option.Name] + if seenBefore { + return errors.New("conflicting or redundant options") + } + optionsSeen[option.Name] = true + + switch option.Name { + case tree.SeqOptCycle: + return unimplemented.NewWithIssue(20961, + "CYCLE option is not supported") + case tree.SeqOptNoCycle: + // Do nothing; this is the default. + case tree.SeqOptCache: + if v := *option.IntVal; v >= 1 { + opts.CacheSize = v + } else { + return errors.Newf( + "CACHE (%d) must be greater than zero", v) + } + case tree.SeqOptIncrement: + // Do nothing; this has already been set. + case tree.SeqOptMinValue: + // A value of nil represents the user explicitly saying `NO MINVALUE`. + if option.IntVal != nil { + opts.MinValue = *option.IntVal + } + case tree.SeqOptMaxValue: + // A value of nil represents the user explicitly saying `NO MAXVALUE`. + if option.IntVal != nil { + opts.MaxValue = *option.IntVal + } + case tree.SeqOptStart: + opts.Start = *option.IntVal + case tree.SeqOptRestart: + // The RESTART option does not get saved, but still gets validated below. + restartVal = option.IntVal + case tree.SeqOptVirtual: + opts.Virtual = true + } + } + + if setDefaults || (wasAscending && opts.Start == 1) || (!wasAscending && opts.Start == -1) { + // If start option not specified, set it to MinValue (for ascending sequences) + // or MaxValue (for descending sequences). + // We only do this if we're setting it for the first time, or the sequence was + // ALTERed with the default original values. + if _, startSeen := optionsSeen[tree.SeqOptStart]; !startSeen { + if opts.Increment > 0 { + opts.Start = opts.MinValue + } else { + opts.Start = opts.MaxValue + } + } + } + + if opts.MinValue < lowerIntBound { + return errors.Newf( + "MINVALUE (%d) must be greater than (%d) for type %s", + opts.MinValue, + lowerIntBound, + integerType.SQLString(), + ) + } + if opts.MaxValue < lowerIntBound { + return errors.Newf( + "MAXVALUE (%d) must be greater than (%d) for type %s", + opts.MaxValue, + lowerIntBound, + integerType.SQLString(), + ) + } + if opts.MinValue > upperIntBound { + return errors.Newf( + "MINVALUE (%d) must be less than (%d) for type %s", + opts.MinValue, + upperIntBound, + integerType.SQLString(), + ) + } + if opts.MaxValue > upperIntBound { + return errors.Newf( + "MAXVALUE (%d) must be less than (%d) for type %s", + opts.MaxValue, + upperIntBound, + integerType.SQLString(), + ) + } + if opts.Start > opts.MaxValue { + return errors.Newf( + "START value (%d) cannot be greater than MAXVALUE (%d)", + opts.Start, + opts.MaxValue, + ) + } + if opts.Start < opts.MinValue { + return errors.Newf( + "START value (%d) cannot be less than MINVALUE (%d)", + opts.Start, + opts.MinValue, + ) + } + if restartVal != nil { + if *restartVal > opts.MaxValue { + return errors.Newf( + "RESTART value (%d) cannot be greater than MAXVALUE (%d)", + *restartVal, + opts.MaxValue, + ) + } + if *restartVal < opts.MinValue { + return errors.Newf( + "RESTART value (%d) cannot be less than MINVALUE (%d)", + *restartVal, + opts.MinValue, + ) + } + } + return nil +} diff --git a/pkg/sql/information_schema.go b/pkg/sql/information_schema.go index bcc532a71381..e7503aa041ef 100644 --- a/pkg/sql/information_schema.go +++ b/pkg/sql/information_schema.go @@ -478,6 +478,15 @@ https://www.postgresql.org/docs/9.5/infoschema-columns.html`, udtSchema = tree.NewDString(typeMetaName.Schema) } + var identityStart, identityIncrement, identityMax, identityMin tree.DString + generatedAsIdentitySeqOpt := column.GetGeneratedAsIdentitySequenceOption() + if generatedAsIdentitySeqOpt != nil { + identityStart = *tree.NewDString(strconv.FormatInt(generatedAsIdentitySeqOpt.Start, 10)) + identityIncrement = *tree.NewDString(strconv.FormatInt(generatedAsIdentitySeqOpt.Increment, 10)) + identityMax = *tree.NewDString(strconv.FormatInt(generatedAsIdentitySeqOpt.MaxValue, 10)) + identityMin = *tree.NewDString(strconv.FormatInt(generatedAsIdentitySeqOpt.MinValue, 10)) + } + err := addRow( dbNameStr, // table_catalog scNameStr, // table_schema @@ -516,12 +525,12 @@ https://www.postgresql.org/docs/9.5/infoschema-columns.html`, tree.DNull, // is_self_referencing yesOrNoDatum(column.IsGeneratedAsIdentity()), // is_identity colGeneratedAsIdentity, // identity_generation - // TODO(janexing): parse the GeneratedAsIdentitySequenceOption to - // fill out these "identity_x" columns. - tree.DNull, // identity_start - tree.DNull, // identity_increment - tree.DNull, // identity_maximum - tree.DNull, // identity_minimum + &identityStart, // identity_start + &identityIncrement, // identity_increment + &identityMax, // identity_maximum + &identityMin, // identity_minimum + // TODO(janexing): we don't support CYCLE syntax for sequences yet. + // https://github.com/cockroachdb/cockroach/issues/20961 tree.DNull, // identity_cycle yesOrNoDatum(column.IsComputed()), // is_generated colComputed, // generation_expression diff --git a/pkg/sql/logictest/testdata/logic_test/information_schema b/pkg/sql/logictest/testdata/logic_test/information_schema index 234310929302..2e037c8b8c7b 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -4813,3 +4813,172 @@ SELECT column_name FROM information_schema.columns WHERE table_name = 't70505' k a b + + +subtest identity_columns + +statement ok +CREATE TABLE t ( + id1 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 10), + id2 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (START 10), + id3 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE 5), + id4 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT 5), + id5 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (START 2 INCREMENT 5), + id6 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY -1 START -5), + id7 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE 5 MAXVALUE 10), + id8 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MAXVALUE 10 START WITH 9), + id9 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY -1 MINVALUE -10 START WITH -10), + id10 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 9223372036854775807), + id11 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE -9223372036854775808 START WITH -9223372036854775808 INCREMENT -1), + id12 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (VIRTUAL), + id13 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (CACHE 10 INCREMENT 1), + id14 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT 5), + id15 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MAXVALUE 4 START WITH 2 CACHE 5 INCREMENT BY 2), + id16 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE -4 START WITH -2 CACHE 5 INCREMENT BY -2), + id17 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE -2 MAXVALUE 2 START WITH 2 CACHE 5 INCREMENT BY -2), + id18 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS int2 START WITH -4 INCREMENT BY -3), + id19 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS integer), + id20 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS int8), + id21 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS smallint), + id22 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS integer START WITH 2 INCREMENT BY 1 MINVALUE 0 MAXVALUE 234567 CACHE 1), + id23 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS smallint START WITH -4 INCREMENT BY -3), + id24 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (AS integer START WITH 1 INCREMENT BY 1 MAXVALUE 9001 CACHE 1), + id25 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 3 MINVALUE 6 MAXVALUE 10), + id26 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (MINVALUE -2 MAXVALUE 2 START WITH 1 CACHE 5 INCREMENT BY -2), + id27 INT8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 3 MINVALUE 6 MAXVALUE 12) +) + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id1'; +---- +10 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id2'; +---- +10 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id3'; +---- +5 1 9223372036854775807 5 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id4'; +---- +1 5 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id5'; +---- +2 5 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id6'; +---- +-5 -1 -1 -9223372036854775808 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id7'; +---- +5 1 10 5 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id8'; +---- +9 1 10 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id9'; +---- +-10 -1 -1 -10 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id10'; +---- +9223372036854775807 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id11'; +---- +-9223372036854775808 -1 -1 -9223372036854775808 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id12'; +---- +1 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id13'; +---- +1 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id14'; +---- +1 5 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id15'; +---- +2 2 4 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id16'; +---- +-2 -2 -1 -4 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id17'; +---- +2 -2 2 -2 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id18'; +---- +-4 -3 -1 -32768 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id19'; +---- +1 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id20'; +---- +1 1 9223372036854775807 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id21'; +---- +1 1 32767 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id22'; +---- +2 1 234567 0 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id23'; +---- +-4 -3 -1 -32768 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id24'; +---- +1 1 9001 1 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id25'; +---- +6 3 10 6 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id26'; +---- +1 -2 2 -2 NULL + +query TTTTT +select identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle from information_schema.columns where table_name = 't' and column_name='id27'; +---- +6 3 12 6 NULL diff --git a/pkg/sql/schemachanger/scdecomp/decomp.go b/pkg/sql/schemachanger/scdecomp/decomp.go index 8706af026acf..d21862bd736a 100644 --- a/pkg/sql/schemachanger/scdecomp/decomp.go +++ b/pkg/sql/schemachanger/scdecomp/decomp.go @@ -365,7 +365,7 @@ func (w *walkCtx) walkColumn(tbl catalog.TableDescriptor, col catalog.Column) { IsHidden: col.IsHidden(), IsInaccessible: col.IsInaccessible(), GeneratedAsIdentityType: col.GetGeneratedAsIdentityType(), - GeneratedAsIdentitySequenceOption: col.GetGeneratedAsIdentitySequenceOption(), + GeneratedAsIdentitySequenceOption: col.GetGeneratedAsIdentitySequenceOptionStr(), PgAttributeNum: col.GetPGAttributeNum(), IsSystemColumn: col.IsSystemColumn(), } diff --git a/pkg/sql/sequence.go b/pkg/sql/sequence.go index 30db52e91c5e..b3c6814042a4 100644 --- a/pkg/sql/sequence.go +++ b/pkg/sql/sequence.go @@ -13,7 +13,6 @@ package sql import ( "context" "fmt" - "math" "github.com/cockroachdb/cockroach/pkg/keys" "github.com/cockroachdb/cockroach/pkg/kv" @@ -33,7 +32,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqlerrors" "github.com/cockroachdb/cockroach/pkg/sql/types" - "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/errors" ) @@ -390,162 +388,14 @@ func readOnlyError(s string) error { "cannot execute %s in a read-only transaction", s) } -func getSequenceIntegerBounds( - integerType *types.T, -) (lowerIntBound int64, upperIntBound int64, err error) { - switch integerType { - case types.Int2: - return math.MinInt16, math.MaxInt16, nil - case types.Int4: - return math.MinInt32, math.MaxInt32, nil - case types.Int: - return math.MinInt64, math.MaxInt64, nil - } - - return 0, 0, pgerror.Newf( - pgcode.InvalidParameterValue, - "CREATE SEQUENCE option AS received type %s, must be integer", - integerType, - ) -} - -func setSequenceIntegerBounds( - opts *descpb.TableDescriptor_SequenceOpts, - integerType *types.T, - isAscending bool, - setMinValue bool, - setMaxValue bool, -) error { - var minValue int64 = math.MinInt64 - var maxValue int64 = math.MaxInt64 - - if isAscending { - minValue = 1 - - switch integerType { - case types.Int2: - maxValue = math.MaxInt16 - case types.Int4: - maxValue = math.MaxInt32 - case types.Int: - // Do nothing, it's the default. - default: - return pgerror.Newf( - pgcode.InvalidParameterValue, - "CREATE SEQUENCE option AS received type %s, must be integer", - integerType, - ) - } - } else { - maxValue = -1 - switch integerType { - case types.Int2: - minValue = math.MinInt16 - case types.Int4: - minValue = math.MinInt32 - case types.Int: - // Do nothing, it's the default. - default: - return pgerror.Newf( - pgcode.InvalidParameterValue, - "CREATE SEQUENCE option AS received type %s, must be integer", - integerType, - ) - } - } - if setMinValue { - opts.MinValue = minValue - } - if setMaxValue { - opts.MaxValue = maxValue - } - return nil -} - -// assignSequenceOptions moves options from the AST node to the sequence options descriptor, -// starting with defaults and overriding them with user-provided options. -func assignSequenceOptions( +func assignSequenceOwner( ctx context.Context, p *planner, opts *descpb.TableDescriptor_SequenceOpts, optsNode tree.SequenceOptions, - setDefaults bool, sequenceID descpb.ID, sequenceParentID descpb.ID, - existingType *types.T, ) error { - wasAscending := opts.Increment > 0 - - // Set the default integer type of a sequence. - var integerType = types.Int - // All other defaults are dependent on the value of increment - // and the AS integerType. (i.e. whether the sequence is ascending - // or descending, bigint vs. smallint) - for _, option := range optsNode { - if option.Name == tree.SeqOptIncrement { - opts.Increment = *option.IntVal - } else if option.Name == tree.SeqOptAs { - integerType = option.AsIntegerType - opts.AsIntegerType = integerType.SQLString() - } - } - if opts.Increment == 0 { - return pgerror.New( - pgcode.InvalidParameterValue, "INCREMENT must not be zero") - } - isAscending := opts.Increment > 0 - - // Set increment-dependent defaults. - if setDefaults { - if isAscending { - opts.MinValue = 1 - opts.MaxValue = math.MaxInt64 - opts.Start = opts.MinValue - } else { - opts.MinValue = math.MinInt64 - opts.MaxValue = -1 - opts.Start = opts.MaxValue - } - // No Caching - opts.CacheSize = 1 - } - - lowerIntBound, upperIntBound, err := getSequenceIntegerBounds(integerType) - if err != nil { - return err - } - - // Set default MINVALUE and MAXVALUE if AS option value for integer type is specified. - if opts.AsIntegerType != "" { - // We change MINVALUE and MAXVALUE if it is the originally set to the default during ALTER. - setMinValue := setDefaults - setMaxValue := setDefaults - if !setDefaults && existingType != nil { - existingLowerIntBound, existingUpperIntBound, err := getSequenceIntegerBounds(existingType) - if err != nil { - return err - } - if (wasAscending && opts.MinValue == 1) || (!wasAscending && opts.MinValue == existingLowerIntBound) { - setMinValue = true - } - if (wasAscending && opts.MaxValue == existingUpperIntBound) || (!wasAscending && opts.MaxValue == -1) { - setMaxValue = true - } - } - - if err := setSequenceIntegerBounds( - opts, - integerType, - isAscending, - setMinValue, - setMaxValue, - ); err != nil { - return err - } - } - - // Fill in all other options. - var restartVal *int64 optionsSeen := map[string]bool{} for _, option := range optsNode { // Error on duplicate options. @@ -554,39 +404,7 @@ func assignSequenceOptions( return pgerror.New(pgcode.Syntax, "conflicting or redundant options") } optionsSeen[option.Name] = true - switch option.Name { - case tree.SeqOptCycle: - return unimplemented.NewWithIssue(20961, - "CYCLE option is not supported") - case tree.SeqOptNoCycle: - // Do nothing; this is the default. - case tree.SeqOptCache: - if v := *option.IntVal; v >= 1 { - opts.CacheSize = v - } else { - return pgerror.Newf(pgcode.InvalidParameterValue, - "CACHE (%d) must be greater than zero", v) - } - case tree.SeqOptIncrement: - // Do nothing; this has already been set. - case tree.SeqOptMinValue: - // A value of nil represents the user explicitly saying `NO MINVALUE`. - if option.IntVal != nil { - opts.MinValue = *option.IntVal - } - case tree.SeqOptMaxValue: - // A value of nil represents the user explicitly saying `NO MAXVALUE`. - if option.IntVal != nil { - opts.MaxValue = *option.IntVal - } - case tree.SeqOptStart: - opts.Start = *option.IntVal - case tree.SeqOptRestart: - // The RESTART option does not get saved, but still gets validated below. - restartVal = option.IntVal - case tree.SeqOptVirtual: - opts.Virtual = true case tree.SeqOptOwnedBy: if p == nil { return pgerror.Newf(pgcode.Internal, @@ -629,90 +447,43 @@ func assignSequenceOptions( } } } + return nil +} - if setDefaults || (wasAscending && opts.Start == 1) || (!wasAscending && opts.Start == -1) { - // If start option not specified, set it to MinValue (for ascending sequences) - // or MaxValue (for descending sequences). - // We only do this if we're setting it for the first time, or the sequence was - // ALTERed with the default original values. - if _, startSeen := optionsSeen[tree.SeqOptStart]; !startSeen { - if opts.Increment > 0 { - opts.Start = opts.MinValue - } else { - opts.Start = opts.MaxValue - } +// checkDupSeqOption check if there is any duplicate sequence option. +func checkDupSeqOption(optsNode tree.SequenceOptions) error { + optionsSeen := map[string]bool{} + for _, option := range optsNode { + // Error on duplicate options. + _, seenBefore := optionsSeen[option.Name] + if seenBefore { + return pgerror.New(pgcode.Syntax, "conflicting or redundant options") } + optionsSeen[option.Name] = true } + return nil +} - if opts.MinValue < lowerIntBound { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "MINVALUE (%d) must be greater than (%d) for type %s", - opts.MinValue, - lowerIntBound, - integerType.SQLString(), - ) - } - if opts.MaxValue < lowerIntBound { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "MAXVALUE (%d) must be greater than (%d) for type %s", - opts.MaxValue, - lowerIntBound, - integerType.SQLString(), - ) - } - if opts.MinValue > upperIntBound { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "MINVALUE (%d) must be less than (%d) for type %s", - opts.MinValue, - upperIntBound, - integerType.SQLString(), - ) - } - if opts.MaxValue > upperIntBound { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "MAXVALUE (%d) must be less than (%d) for type %s", - opts.MaxValue, - upperIntBound, - integerType.SQLString(), - ) - } - if opts.Start > opts.MaxValue { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "START value (%d) cannot be greater than MAXVALUE (%d)", - opts.Start, - opts.MaxValue, - ) +// assignSequenceOptions moves options from the AST node to the sequence options descriptor, +// starting with defaults and overriding them with user-provided options. +func assignSequenceOptions( + ctx context.Context, + p *planner, + opts *descpb.TableDescriptor_SequenceOpts, + optsNode tree.SequenceOptions, + setDefaults bool, + sequenceID descpb.ID, + sequenceParentID descpb.ID, + existingType *types.T, +) error { + if err := checkDupSeqOption(optsNode); err != nil { + return err } - if opts.Start < opts.MinValue { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "START value (%d) cannot be less than MINVALUE (%d)", - opts.Start, - opts.MinValue, - ) + if err := tabledesc.AssignSequenceOptions(opts, optsNode, setDefaults, existingType); err != nil { + return pgerror.Wrap(err, pgcode.InvalidParameterValue, "") } - if restartVal != nil { - if *restartVal > opts.MaxValue { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "RESTART value (%d) cannot be greater than MAXVALUE (%d)", - *restartVal, - opts.MaxValue, - ) - } - if *restartVal < opts.MinValue { - return pgerror.Newf( - pgcode.InvalidParameterValue, - "RESTART value (%d) cannot be less than MINVALUE (%d)", - *restartVal, - opts.MinValue, - ) - } + if err := assignSequenceOwner(ctx, p, opts, optsNode, sequenceID, sequenceParentID); err != nil { + return pgerror.Wrap(err, pgcode.InvalidParameterValue, "") } return nil } diff --git a/pkg/sql/types/types.go b/pkg/sql/types/types.go index 5558659ae4f4..4e6a0bca0832 100644 --- a/pkg/sql/types/types.go +++ b/pkg/sql/types/types.go @@ -2784,6 +2784,20 @@ var unreservedTypeTokens = map[string]*T{ "uuid": Uuid, } +// ConvertStrToIntType converts a string to an integer type. +func ConvertStrToIntType(s string) *T { + switch strings.ToLower(s) { + case "int2", "smallint": + return Int2 + case "int4", "integer": + return Int4 + case "int8", "bigint", "int64": + return Int + default: + panic(fmt.Sprintf("unknown string %s for conversion to integet type", s)) + } +} + // The following map must include all types predefined in PostgreSQL // that are also not yet defined in CockroachDB and link them to // github issues. It is also possible, but not necessary, to include