Skip to content

Commit

Permalink
Applying implicit prefix lengths for TEXT columns in secondary indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
fulghum committed Oct 8, 2024
1 parent baafd97 commit 7443a09
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 20 deletions.
4 changes: 4 additions & 0 deletions memory/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (idx *Index) PrefixLengths() []uint16 {
return idx.PrefixLens
}

func (idx *Index) SetPrefixLengths(prefixLengths []uint16) {
idx.PrefixLens = prefixLengths
}

func (idx *Index) IndexType() string {
if len(idx.DriverName) > 0 {
return idx.DriverName
Expand Down
6 changes: 3 additions & 3 deletions sql/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (ab *Builder) Build() *Analyzer {
Parallelism: ab.parallelism,
Coster: memo.NewDefaultCoster(),
ExecBuilder: rowexec.DefaultBuilder,
DatabaseType: sql.EngineType_MySql,
EngineType: sql.EngineType_MySql,
}
}

Expand All @@ -299,9 +299,9 @@ type Analyzer struct {
// EventScheduler is used to communiate with the event scheduler
// for any EVENT related statements. It can be nil if EventScheduler is not defined.
EventScheduler sql.EventScheduler
// DatabaseType indicates whether the analyzer's behavior is compatible with a MySQL
// EngineType indicates whether the analyzer's behavior is compatible with a MySQL
// database or a PostgreSQL database.
DatabaseType sql.EngineType
EngineType sql.EngineType
}

// NewDefault creates a default Analyzer instance with all default Rules and configuration.
Expand Down
5 changes: 3 additions & 2 deletions sql/analyzer/index_analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,9 @@ func (i *dummyIdx) Comment() string { return "" }
func (i *dummyIdx) IsGenerated() bool { return false }
func (i *dummyIdx) CanSupportOrderBy(sql.Expression) bool { return false }

func (i *dummyIdx) IndexType() string { return "BTREE" }
func (i *dummyIdx) PrefixLengths() []uint16 { return nil }
func (i *dummyIdx) IndexType() string { return "BTREE" }
func (i *dummyIdx) PrefixLengths() []uint16 { return nil }
func (i dummyIdx) SetPrefixLengths(uint16s []uint16) {}

func (i *dummyIdx) NewLookup(*sql.Context, ...sql.Range) (sql.IndexLookup, error) {
panic("not implemented")
Expand Down
52 changes: 49 additions & 3 deletions sql/analyzer/validate_create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import (

const MaxBytePrefix = 3072

// defaultPrefixLength is used for PostgreSQL compatibility. We don't support secondary indexes including address
// encoded types such as TEXT/BLOB, so we must apply an implicit prefix length.
const defaultPrefixLength = 200

// validateCreateTable validates various constraints about CREATE TABLE statements.
func validateCreateTable(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
ct, ok := n.(*plan.CreateTable)
Expand All @@ -44,7 +48,10 @@ func validateCreateTable(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.
if err != nil {
return nil, transform.SameTree, err
}
validatePrefixLengths := a.DatabaseType == sql.EngineType_MySql

// For MySQL compatibility, users must specify a prefix length for any TEXT or BLOB columns used in an index,
// otherwise we will apply an implicit prefix length.
validatePrefixLengths := a.EngineType == sql.EngineType_MySql
err = validateIndexes(ctx, sch, idxs, strictMySQLCompat, validatePrefixLengths)
if err != nil {
return nil, transform.SameTree, err
Expand Down Expand Up @@ -225,9 +232,11 @@ func resolveAlterColumn(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.S

sch = sch.Copy() // Make a copy of the original schema to deal with any references to the original table.
initialSch := sch

addedColumn := false
validatePrefixLengths := a.DatabaseType == sql.EngineType_MySql

// For MySQL compatibility, users must specify a prefix length for any TEXT or BLOB columns used in an index,
// otherwise we will apply an implicit prefix length.
validatePrefixLengths := a.EngineType == sql.EngineType_MySql

// Need a TransformUp here because multiple of these statement types can be nested under a Block node.
// It doesn't look it, but this is actually an iterative loop over all the independent clauses in an ALTER statement
Expand Down Expand Up @@ -520,6 +529,24 @@ func validateModifyColumn(ctx *sql.Context, initialSch sql.Schema, schema sql.Sc
}
}
}

// If we aren't validating user-specified prefix lengths, then apply an implicit prefix length for TEXT columns
if !validatePrefixLengths {
prefixLengths := make([]uint16, len(index.Expressions()))
for i, expr := range index.Expressions() {
col := plan.GetColumnFromIndexExpr(expr, tbl)
if !strings.EqualFold(col.Name, oldColName) {
continue
}
if types.IsJSON(newCol.Type) {
return nil, sql.ErrJSONIndex.New(col.Name)
}
if types.IsText(newCol.Type) {
prefixLengths[i] = defaultPrefixLength
}
}
index.SetPrefixLengths(prefixLengths)
}
}

return newSch, nil
Expand Down Expand Up @@ -906,6 +933,25 @@ func validateIndex(ctx *sql.Context, colMap map[string]*sql.Column, idxDef *sql.
}
}

// If we aren't validating user-specified prefix lengths, then we need to apply implicit prefix lengths for
// any TEXT columns.
if !validatePrefixLengths {
prefixLengths := make([]uint16, len(idxDef.Columns))
for i, idxCol := range idxDef.Columns {
schCol, exists := colMap[strings.ToLower(idxCol.Name)]
if !exists {
return sql.ErrKeyColumnDoesNotExist.New(idxCol.Name)
}
if types.IsJSON(schCol.Type) {
return sql.ErrJSONIndex.New(schCol.Name)
}
if types.IsText(schCol.Type) {
prefixLengths[i] = defaultPrefixLength
}
idxDef.Columns[i].Length = int64(prefixLengths[i])
}
}

if idxDef.IsSpatial() {
if len(idxDef.Columns) != 1 {
return sql.ErrTooManyKeyParts.New(1)
Expand Down
4 changes: 3 additions & 1 deletion sql/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ type Index interface {
// Verifying that the expression's children match the index columns are done separately.
CanSupportOrderBy(expr Expression) bool

// PrefixLengths returns the prefix lengths for each column in this index
// PrefixLengths returns the prefix lengths for each column in this index.
PrefixLengths() []uint16
// SetPrefixLengths sets the prefix lengths for each column in this index.
SetPrefixLengths([]uint16)
}

// ExtendedIndex is an extension of Index, that allows access to appended primary keys. MySQL internally represents an
Expand Down
2 changes: 2 additions & 0 deletions sql/index_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,6 @@ func (testIndex) PrefixLengths() []uint16 {
return nil
}

func (i testIndex) SetPrefixLengths(uint16s []uint16) {}

var _ sql.Index = testIndex{}
23 changes: 12 additions & 11 deletions sql/index_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,17 +443,18 @@ func (i dummyIdx) Expressions() []string {
}
return exprs
}
func (i dummyIdx) ID() string { return i.id }
func (i dummyIdx) Database() string { return i.database }
func (i dummyIdx) Table() string { return i.table }
func (i dummyIdx) Driver() string { return "dummy" }
func (i dummyIdx) IsUnique() bool { return false }
func (i dummyIdx) IsSpatial() bool { return false }
func (i dummyIdx) IsFullText() bool { return false }
func (i dummyIdx) Comment() string { return "" }
func (i dummyIdx) IsGenerated() bool { return false }
func (i dummyIdx) IndexType() string { return "BTREE" }
func (i dummyIdx) PrefixLengths() []uint16 { return nil }
func (i dummyIdx) ID() string { return i.id }
func (i dummyIdx) Database() string { return i.database }
func (i dummyIdx) Table() string { return i.table }
func (i dummyIdx) Driver() string { return "dummy" }
func (i dummyIdx) IsUnique() bool { return false }
func (i dummyIdx) IsSpatial() bool { return false }
func (i dummyIdx) IsFullText() bool { return false }
func (i dummyIdx) Comment() string { return "" }
func (i dummyIdx) IsGenerated() bool { return false }
func (i dummyIdx) IndexType() string { return "BTREE" }
func (i dummyIdx) PrefixLengths() []uint16 { return nil }
func (i dummyIdx) SetPrefixLengths(uint16s []uint16) {}

func (i dummyIdx) NewLookup(ctx *Context, ranges ...Range) (IndexLookup, error) {
panic("not implemented")
Expand Down
1 change: 1 addition & 0 deletions sql/memo/rel_props_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,5 +257,6 @@ func (i dummyIndex) ColumnExpressionTypes() []sql.ColumnExpressionType {
func (dummyIndex) PrefixLengths() []uint16 {
return nil
}
func (i dummyIndex) SetPrefixLengths(uint16s []uint16) {}

var _ sql.Index = dummyIndex{}

0 comments on commit 7443a09

Please sign in to comment.