Skip to content

Commit

Permalink
sql: add sequence option info for identity columns under information_…
Browse files Browse the repository at this point in the history
…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 #82064

Release note (sql): add sequence option info for identity columns under
information_schema
  • Loading branch information
ZhouXing19 committed Jul 18, 2022
1 parent e67e47f commit 385307a
Show file tree
Hide file tree
Showing 9 changed files with 574 additions and 275 deletions.
1 change: 1 addition & 0 deletions pkg/sql/catalog/schemaexpr/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ go_library(
"hash_sharded_compute_expr.go",
"partial_index.go",
"select_name_resolution.go",
"sequence_options.go",
"unique_contraint.go",
],
importpath = "github.com/cockroachdb/cockroach/pkg/sql/catalog/schemaexpr",
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/catalog/schemaexpr/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
312 changes: 312 additions & 0 deletions pkg/sql/catalog/schemaexpr/sequence_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
package schemaexpr

import (
"math"

"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/parser"
"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/errors"
)

// ParseSequenceOpts is to transform the sequence options saved the
// descriptor to a descpb.TableDescriptor_SequenceOpts.
// Note that this function is used to acquire the sequence option for the
// information schema table, so it doesn't parse for the sequence owner info.
func ParseSequenceOpts(s string) (*descpb.TableDescriptor_SequenceOpts, error) {
stmt, err := parser.ParseOne("CREATE SEQUENCE fake_seq " + s)
if err != nil {
return nil, errors.Wrap(err, "cannot parse sequence option")
}

createSeqNode, ok := stmt.AST.(*tree.CreateSequence)
if !ok {
return nil, errors.New("cannot convert parsed result to tree.CreateSequence")
}

opts := &descpb.TableDescriptor_SequenceOpts{
Increment: 1,
}
if err := AssignSequenceOptions(
opts,
createSeqNode.Options,
true, /* setDefaults */
nil, /* existingType */
); err != nil {
return nil, err
}

return opts, nil
}

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
}
12 changes: 10 additions & 2 deletions pkg/sql/catalog/table_elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,17 @@ 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
// IDENTITY` sequence option if it exists, and possible error.
// If the column is not an identity column, return nil for both sequence option
// and the error.
// Note it doesn't return the sequence owner info.
GetGeneratedAsIdentitySequenceOption() (*descpb.TableDescriptor_SequenceOpts, error)
}

// ConstraintToUpdate is an interface around a constraint mutation.
Expand Down
27 changes: 24 additions & 3 deletions pkg/sql/catalog/tabledesc/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/schemaexpr"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
Expand Down Expand Up @@ -233,15 +234,35 @@ 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, and possible error.
// If the column is not an identity column, return nil for both sequence option
// and the error.
// Note it doesn't return the sequence owner info.
func (w column) GetGeneratedAsIdentitySequenceOption() (
*descpb.TableDescriptor_SequenceOpts,
error,
) {
if !w.HasGeneratedAsIdentitySequenceOption() {
return nil, nil
}
seqOpts, err := schemaexpr.ParseSequenceOpts(*w.desc.GeneratedAsIdentitySequenceOption)
if err != nil {
return nil, err
}
return seqOpts, nil
}

// HasGeneratedAsIdentitySequenceOption returns true if there is a
// customized sequence option when this column is created as a
// `GENERATED AS IDENTITY` column.
Expand Down
Loading

0 comments on commit 385307a

Please sign in to comment.