-
Notifications
You must be signed in to change notification settings - Fork 5.9k
/
generated_column.go
155 lines (144 loc) · 4.77 KB
/
generated_column.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2017 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package ddl
import (
"github.com/juju/errors"
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/table"
)
// columnGenerationInDDL is a struct for validating generated columns in DDL.
type columnGenerationInDDL struct {
position int
generated bool
dependences map[string]struct{}
}
// verifyColumnGeneration is for CREATE TABLE, because we need verify all columns in the table.
func verifyColumnGeneration(colName2Generation map[string]columnGenerationInDDL, colName string) error {
attribute := colName2Generation[colName]
if attribute.generated {
for depCol := range attribute.dependences {
if attr, ok := colName2Generation[depCol]; ok {
if attr.generated && attribute.position <= attr.position {
// A generated column definition can refer to other
// generated columns occurring earilier in the table.
err := errGeneratedColumnNonPrior.GenByArgs()
return errors.Trace(err)
}
} else {
err := errBadField.GenByArgs(depCol, "generated column function")
return errors.Trace(err)
}
}
}
return nil
}
// columnNamesCover checks whether dependColNames is covered by normalColNames or not.
// it's only for alter table add column because before alter, we can make sure that all
// columns in table are verified already.
func columnNamesCover(normalColNames map[string]struct{}, dependColNames map[string]struct{}) error {
for name := range dependColNames {
if _, ok := normalColNames[name]; !ok {
return errBadField.GenByArgs(name, "generated column function")
}
}
return nil
}
// findDependedColumnNames returns a set of string, which indicates
// the names of the columns that are depended by colDef.
func findDependedColumnNames(colDef *ast.ColumnDef) (generated bool, colsMap map[string]struct{}) {
colsMap = make(map[string]struct{})
for _, option := range colDef.Options {
if option.Tp == ast.ColumnOptionGenerated {
generated = true
colNames := findColumnNamesInExpr(option.Expr)
for _, depCol := range colNames {
colsMap[depCol.Name.L] = struct{}{}
}
break
}
}
return
}
// findColumnNamesInExpr returns a slice of ast.ColumnName which is referred in expr.
func findColumnNamesInExpr(expr ast.ExprNode) []*ast.ColumnName {
var c generatedColumnChecker
expr.Accept(&c)
return c.cols
}
type generatedColumnChecker struct {
cols []*ast.ColumnName
}
func (c *generatedColumnChecker) Enter(inNode ast.Node) (outNode ast.Node, skipChildren bool) {
return inNode, false
}
func (c *generatedColumnChecker) Leave(inNode ast.Node) (node ast.Node, ok bool) {
switch x := inNode.(type) {
case *ast.ColumnName:
c.cols = append(c.cols, x)
}
return inNode, true
}
// checkModifyGeneratedColumn checks the modification between
// old and new is valid or not by such rules:
// 1. the modification can't change stored status;
// 2. if the new is generated, check its refer rules.
func checkModifyGeneratedColumn(originCols []*table.Column, oldCol, newCol *table.Column) error {
// rule 1.
var stored = [2]bool{false, false}
var cols = [2]*table.Column{oldCol, newCol}
for i, col := range cols {
if !col.IsGenerated() || col.GeneratedStored {
stored[i] = true
}
}
if stored[0] != stored[1] {
return errUnsupportedOnGeneratedColumn.GenByArgs("Changing the STORED status")
}
// rule 2.
var colName2Generation = make(map[string]columnGenerationInDDL, len(originCols))
for i, column := range originCols {
// We can compare the pointers simply.
if column == oldCol {
colName2Generation[newCol.Name.L] = columnGenerationInDDL{
position: i,
generated: newCol.IsGenerated(),
dependences: newCol.Dependences,
}
} else if !column.IsGenerated() {
colName2Generation[column.Name.L] = columnGenerationInDDL{
position: i,
generated: false,
}
} else {
colName2Generation[column.Name.L] = columnGenerationInDDL{
position: i,
generated: true,
dependences: column.Dependences,
}
}
}
// We always need test all columns, even if it's not changed
// because other can depend on it so its name can't be changed.
for _, column := range originCols {
var colName string
if column == oldCol {
colName = newCol.Name.L
} else {
colName = column.Name.L
}
if err := verifyColumnGeneration(colName2Generation, colName); err != nil {
return errors.Trace(err)
}
}
return nil
}