Skip to content

Commit 09c3d56

Browse files
authored
New unified internal table names format: part 2, generating new names (#15178)
Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>
1 parent af1d6d6 commit 09c3d56

File tree

10 files changed

+177
-91
lines changed

10 files changed

+177
-91
lines changed

go/test/endtoend/tabletmanager/tablegc/tablegc_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,9 @@ func TestPopulateTable(t *testing.T) {
321321

322322
func generateRenameStatement(newFormat bool, fromTableName string, state schema.TableGCState, tm time.Time) (statement string, toTableName string, err error) {
323323
if newFormat {
324-
return schema.GenerateRenameStatementNewFormat(fromTableName, state, tm)
324+
return schema.GenerateRenameStatement(fromTableName, state, tm)
325325
}
326-
return schema.GenerateRenameStatement(fromTableName, state, tm)
326+
return schema.GenerateRenameStatementOldFormat(fromTableName, state, tm)
327327
}
328328

329329
func TestHold(t *testing.T) {
@@ -449,7 +449,7 @@ func TestPurge(t *testing.T) {
449449

450450
func TestPurgeView(t *testing.T) {
451451
populateTable(t)
452-
query, tableName, err := schema.GenerateRenameStatement("v1", schema.PurgeTableGCState, time.Now().UTC().Add(tableTransitionExpiration))
452+
query, tableName, err := generateRenameStatement(true, "v1", schema.PurgeTableGCState, time.Now().UTC().Add(tableTransitionExpiration))
453453
require.NoError(t, err)
454454

455455
_, err = primaryTablet.VttabletProcess.QueryTablet(query, keyspaceName, true)

go/vt/schema/name.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package schema
1818

1919
import (
20+
"fmt"
2021
"regexp"
2122
"strings"
2223
"time"
@@ -32,8 +33,23 @@ const (
3233
InternalTableNameExpression string = `^_vt_([a-zA-Z0-9]{3})_([0-f]{32})_([0-9]{14})_$`
3334
)
3435

36+
type InternalTableHint string
37+
38+
const (
39+
InternalTableUnknownHint InternalTableHint = "nil"
40+
InternalTableGCHoldHint InternalTableHint = "hld"
41+
InternalTableGCPurgeHint InternalTableHint = "prg"
42+
InternalTableGCEvacHint InternalTableHint = "evc"
43+
InternalTableGCDropHint InternalTableHint = "drp"
44+
InternalTableVreplicationHint InternalTableHint = "vrp"
45+
)
46+
47+
func (h InternalTableHint) String() string {
48+
return string(h)
49+
}
50+
3551
var (
36-
// internalTableNameRegexp parses new intrnal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
52+
// internalTableNameRegexp parses new internal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
3753
internalTableNameRegexp = regexp.MustCompile(InternalTableNameExpression)
3854
)
3955

@@ -65,6 +81,44 @@ func ToReadableTimestamp(t time.Time) string {
6581
return t.Format(readableTimeFormat)
6682
}
6783

84+
// ReadableTimestamp returns the current timestamp, in seconds resolution, that is human readable
85+
func ReadableTimestamp() string {
86+
return ToReadableTimestamp(time.Now())
87+
}
88+
89+
func condenseUUID(uuid string) string {
90+
uuid = strings.ReplaceAll(uuid, "-", "")
91+
uuid = strings.ReplaceAll(uuid, "_", "")
92+
return uuid
93+
}
94+
95+
// isCondensedUUID answers 'true' when the given string is a condensed UUID, e.g.:
96+
// a0638f6bec7b11ea9bf8000d3a9b8a9a
97+
func isCondensedUUID(uuid string) bool {
98+
return condensedUUIDRegexp.MatchString(uuid)
99+
}
100+
101+
// generateGCTableName creates an internal table name, based on desired hint and time, and with optional preset UUID.
102+
// If uuid is given, then it must be in condensed-UUID format. If empty, the function auto-generates a UUID.
103+
func GenerateInternalTableName(hint string, uuid string, t time.Time) (tableName string, err error) {
104+
if len(hint) != 3 {
105+
return "", fmt.Errorf("Invalid hint: %s, expected 3 characters", hint)
106+
}
107+
if uuid == "" {
108+
uuid, err = CreateUUIDWithDelimiter("")
109+
} else {
110+
uuid = condenseUUID(uuid)
111+
}
112+
if err != nil {
113+
return "", err
114+
}
115+
if !isCondensedUUID(uuid) {
116+
return "", fmt.Errorf("Invalid UUID: %s, expected condensed 32 hexadecimals", uuid)
117+
}
118+
timestamp := ToReadableTimestamp(t)
119+
return fmt.Sprintf("_vt_%s_%s_%s_", hint, uuid, timestamp), nil
120+
}
121+
68122
// IsInternalOperationTableName answers 'true' when the given table name stands for an internal Vitess
69123
// table used for operations such as:
70124
// - Online DDL (gh-ost, pt-online-schema-change)

go/vt/schema/name_test.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"time"
2222

2323
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
2425
)
2526

2627
func TestNameIsGCTableName(t *testing.T) {
@@ -165,10 +166,61 @@ func TestAnalyzeInternalTableName(t *testing.T) {
165166
assert.Equal(t, ts.isInternal, isInternal)
166167
if ts.isInternal {
167168
assert.NoError(t, err)
168-
assert.True(t, IsGCUUID(uuid))
169+
assert.True(t, isCondensedUUID(uuid))
169170
assert.Equal(t, ts.hint, hint)
170171
assert.Equal(t, ts.t, tm)
171172
}
172173
})
173174
}
174175
}
176+
177+
func TestToReadableTimestamp(t *testing.T) {
178+
ti, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
179+
assert.NoError(t, err)
180+
181+
readableTimestamp := ToReadableTimestamp(ti)
182+
assert.Equal(t, "20150225110639", readableTimestamp)
183+
}
184+
185+
func TestGenerateInternalTableName(t *testing.T) {
186+
ti, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
187+
assert.NoError(t, err)
188+
189+
{
190+
uuid := "6ace8bcef73211ea87e9f875a4d24e90"
191+
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
192+
require.NoError(t, err)
193+
assert.Equal(t, "_vt_prg_6ace8bcef73211ea87e9f875a4d24e90_20150225110639_", tableName)
194+
assert.True(t, IsInternalOperationTableName(tableName))
195+
}
196+
{
197+
uuid := "4e5dcf80_354b_11eb_82cd_f875a4d24e90"
198+
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
199+
require.NoError(t, err)
200+
assert.Equal(t, "_vt_prg_4e5dcf80354b11eb82cdf875a4d24e90_20150225110639_", tableName)
201+
assert.True(t, IsInternalOperationTableName(tableName))
202+
}
203+
{
204+
uuid := "4e5dcf80-354b-11eb-82cd-f875a4d24e90"
205+
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
206+
require.NoError(t, err)
207+
assert.Equal(t, "_vt_prg_4e5dcf80354b11eb82cdf875a4d24e90_20150225110639_", tableName)
208+
assert.True(t, IsInternalOperationTableName(tableName))
209+
}
210+
{
211+
uuid := ""
212+
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
213+
require.NoError(t, err)
214+
assert.True(t, IsInternalOperationTableName(tableName))
215+
}
216+
{
217+
uuid := "4e5dcf80_354b_11eb_82cd_f875a4d24e90_00001111"
218+
_, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
219+
require.ErrorContains(t, err, "Invalid UUID")
220+
}
221+
{
222+
uuid := "6ace8bcef73211ea87e9f875a4d24e90"
223+
_, err := GenerateInternalTableName("abcdefg", uuid, ti)
224+
require.ErrorContains(t, err, "Invalid hint")
225+
}
226+
}

go/vt/schema/online_ddl_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestGetGCUUID(t *testing.T) {
5959
onlineDDL, err := NewOnlineDDL("ks", "tbl", "alter table t drop column c", NewDDLStrategySetting(DDLStrategyDirect, ""), "", "", parser)
6060
assert.NoError(t, err)
6161
gcUUID := onlineDDL.GetGCUUID()
62-
assert.True(t, IsGCUUID(gcUUID))
62+
assert.True(t, isCondensedUUID(gcUUID))
6363
uuids[gcUUID] = true
6464
}
6565
assert.Equal(t, count, len(uuids))

go/vt/schema/tablegc.go

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -44,85 +44,73 @@ const (
4444
TableDroppedGCState TableGCState = ""
4545
)
4646

47+
func (s TableGCState) TableHint() InternalTableHint {
48+
if hint, ok := gcStatesTableHints[s]; ok {
49+
return hint
50+
}
51+
return InternalTableUnknownHint
52+
}
53+
4754
const (
48-
GCTableNameExpression string = `^_vt_(HOLD|PURGE|EVAC|DROP)_([0-f]{32})_([0-9]{14})$`
49-
// NewGCTableNameExpression parses new intrnal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
50-
NewGCTableNameExpression string = `^_vt_(hld|prg|evc|drp)_([0-f]{32})_([0-9]{14})_$`
55+
OldGCTableNameExpression string = `^_vt_(HOLD|PURGE|EVAC|DROP)_([0-f]{32})_([0-9]{14})$`
56+
// GCTableNameExpression parses new internal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
57+
GCTableNameExpression string = `^_vt_(hld|prg|evc|drp)_([0-f]{32})_([0-9]{14})_$`
5158
)
5259

5360
var (
54-
gcUUIDRegexp = regexp.MustCompile(`^[0-f]{32}$`)
55-
gcTableNameRegexp = regexp.MustCompile(GCTableNameExpression)
56-
57-
gcStates = map[string]TableGCState{
58-
string(HoldTableGCState): HoldTableGCState,
59-
"hld": HoldTableGCState,
60-
string(PurgeTableGCState): PurgeTableGCState,
61-
"prg": PurgeTableGCState,
62-
string(EvacTableGCState): EvacTableGCState,
63-
"evc": EvacTableGCState,
64-
string(DropTableGCState): DropTableGCState,
65-
"drp": DropTableGCState,
66-
}
61+
condensedUUIDRegexp = regexp.MustCompile(`^[0-f]{32}$`)
62+
oldGCTableNameRegexp = regexp.MustCompile(OldGCTableNameExpression)
63+
64+
gcStates = map[string]TableGCState{}
65+
gcStatesTableHints = map[TableGCState]InternalTableHint{}
6766
)
6867

69-
// IsGCUUID answers 'true' when the given string is an GC UUID, e.g.:
70-
// a0638f6bec7b11ea9bf8000d3a9b8a9a
71-
func IsGCUUID(uuid string) bool {
72-
return gcUUIDRegexp.MatchString(uuid)
68+
func init() {
69+
gcStatesTableHints[HoldTableGCState] = InternalTableGCHoldHint
70+
gcStatesTableHints[PurgeTableGCState] = InternalTableGCPurgeHint
71+
gcStatesTableHints[EvacTableGCState] = InternalTableGCEvacHint
72+
gcStatesTableHints[DropTableGCState] = InternalTableGCDropHint
73+
for _, gcState := range []TableGCState{HoldTableGCState, PurgeTableGCState, EvacTableGCState, DropTableGCState} {
74+
gcStates[string(gcState)] = gcState
75+
gcStates[gcState.TableHint().String()] = gcState
76+
}
7377
}
7478

7579
// generateGCTableName creates a GC table name, based on desired state and time, and with optional preset UUID.
7680
// If uuid is given, then it must be in GC-UUID format. If empty, the function auto-generates a UUID.
77-
func generateGCTableName(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
81+
func generateGCTableNameOldFormat(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
7882
if uuid == "" {
7983
uuid, err = CreateUUIDWithDelimiter("")
8084
}
8185
if err != nil {
8286
return "", err
8387
}
84-
if !IsGCUUID(uuid) {
88+
if !isCondensedUUID(uuid) {
8589
return "", fmt.Errorf("Not a valid GC UUID format: %s", uuid)
8690
}
8791
timestamp := ToReadableTimestamp(t)
8892
return fmt.Sprintf("_vt_%s_%s_%s", state, uuid, timestamp), nil
8993
}
9094

91-
// generateGCTableNameNewFormat creates a GC table name, based on desired state and time, and with optional preset UUID.
95+
// generateGCTableName creates a GC table name, based on desired state and time, and with optional preset UUID.
9296
// If uuid is given, then it must be in GC-UUID format. If empty, the function auto-generates a UUID.
93-
func generateGCTableNameNewFormat(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
94-
if uuid == "" {
95-
uuid, err = CreateUUIDWithDelimiter("")
96-
}
97-
if err != nil {
98-
return "", err
99-
}
100-
if !IsGCUUID(uuid) {
101-
return "", fmt.Errorf("Not a valid GC UUID format: %s", uuid)
102-
}
103-
timestamp := ToReadableTimestamp(t)
104-
var hint string
97+
func generateGCTableName(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
10598
for k, v := range gcStates {
10699
if v != state {
107100
continue
108101
}
109102
if len(k) == 3 && k != string(state) { // the "new" format
110-
hint = k
103+
return GenerateInternalTableName(k, uuid, t)
111104
}
112105
}
113-
return fmt.Sprintf("_vt_%s_%s_%s_", hint, uuid, timestamp), nil
106+
return "", fmt.Errorf("Unknown GC state: %v", state)
114107
}
115108

116109
// GenerateGCTableName creates a GC table name, based on desired state and time, and with random UUID
117110
func GenerateGCTableName(state TableGCState, t time.Time) (tableName string, err error) {
118111
return generateGCTableName(state, "", t)
119112
}
120113

121-
// GenerateGCTableNameNewFormat creates a GC table name, based on desired state and time, and with random UUID
122-
func GenerateGCTableNameNewFormat(state TableGCState, t time.Time) (tableName string, err error) {
123-
return generateGCTableNameNewFormat(state, "", t)
124-
}
125-
126114
// AnalyzeGCTableName analyzes a given table name to see if it's a GC table, and if so, parse out
127115
// its state, uuid, and timestamp
128116
func AnalyzeGCTableName(tableName string) (isGCTable bool, state TableGCState, uuid string, t time.Time, err error) {
@@ -134,7 +122,7 @@ func AnalyzeGCTableName(tableName string) (isGCTable bool, state TableGCState, u
134122
}
135123
// Try old naming formats. These names will not be generated in v20.
136124
// TODO(shlomi): the code below should be remvoed in v21
137-
submatch := gcTableNameRegexp.FindStringSubmatch(tableName)
125+
submatch := oldGCTableNameRegexp.FindStringSubmatch(tableName)
138126
if len(submatch) == 0 {
139127
return false, state, uuid, t, nil
140128
}
@@ -165,8 +153,8 @@ func GenerateRenameStatementWithUUID(fromTableName string, state TableGCState, u
165153
}
166154

167155
// GenerateRenameStatementWithUUIDNewFormat generates a "RENAME TABLE" statement, where a table is renamed to a GC table, with preset UUID
168-
func GenerateRenameStatementWithUUIDNewFormat(fromTableName string, state TableGCState, uuid string, t time.Time) (statement string, toTableName string, err error) {
169-
toTableName, err = generateGCTableNameNewFormat(state, uuid, t)
156+
func generateRenameStatementWithUUIDOldFormat(fromTableName string, state TableGCState, uuid string, t time.Time) (statement string, toTableName string, err error) {
157+
toTableName, err = generateGCTableNameOldFormat(state, uuid, t)
170158
if err != nil {
171159
return "", "", err
172160
}
@@ -179,8 +167,8 @@ func GenerateRenameStatement(fromTableName string, state TableGCState, t time.Ti
179167
}
180168

181169
// GenerateRenameStatement generates a "RENAME TABLE" statement, where a table is renamed to a GC table.
182-
func GenerateRenameStatementNewFormat(fromTableName string, state TableGCState, t time.Time) (statement string, toTableName string, err error) {
183-
return GenerateRenameStatementWithUUIDNewFormat(fromTableName, state, "", t)
170+
func GenerateRenameStatementOldFormat(fromTableName string, state TableGCState, t time.Time) (statement string, toTableName string, err error) {
171+
return generateRenameStatementWithUUIDOldFormat(fromTableName, state, "", t)
184172
}
185173

186174
// ParseGCLifecycle parses a comma separated list of gc states and returns a map of indicated states

go/vt/schema/tablegc_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,37 @@ import (
2525
"github.com/stretchr/testify/require"
2626
)
2727

28+
func TestGCStates(t *testing.T) {
29+
// These are all hard coded
30+
require.Equal(t, HoldTableGCState, gcStates["hld"])
31+
require.Equal(t, HoldTableGCState, gcStates["HOLD"])
32+
require.Equal(t, PurgeTableGCState, gcStates["prg"])
33+
require.Equal(t, PurgeTableGCState, gcStates["PURGE"])
34+
require.Equal(t, EvacTableGCState, gcStates["evc"])
35+
require.Equal(t, EvacTableGCState, gcStates["EVAC"])
36+
require.Equal(t, DropTableGCState, gcStates["drp"])
37+
require.Equal(t, DropTableGCState, gcStates["DROP"])
38+
_, ok := gcStates["purge"]
39+
require.False(t, ok)
40+
_, ok = gcStates["vrp"]
41+
require.False(t, ok)
42+
require.Equal(t, 2*4, len(gcStates)) // 4 states, 2 forms each
43+
}
44+
2845
func TestIsGCTableName(t *testing.T) {
2946
tm := time.Now()
3047
states := []TableGCState{HoldTableGCState, PurgeTableGCState, EvacTableGCState, DropTableGCState}
3148
for _, state := range states {
3249
for i := 0; i < 10; i++ {
33-
tableName, err := generateGCTableName(state, "", tm)
50+
tableName, err := generateGCTableNameOldFormat(state, "", tm)
3451
assert.NoError(t, err)
35-
assert.True(t, IsGCTableName(tableName))
52+
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)
3653

37-
tableName, err = generateGCTableNameNewFormat(state, "6ace8bcef73211ea87e9f875a4d24e90", tm)
54+
tableName, err = generateGCTableName(state, "6ace8bcef73211ea87e9f875a4d24e90", tm)
3855
assert.NoError(t, err)
3956
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)
4057

41-
tableName, err = GenerateGCTableNameNewFormat(state, tm)
58+
tableName, err = GenerateGCTableName(state, tm)
4259
assert.NoError(t, err)
4360
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)
4461
}
@@ -77,7 +94,7 @@ func TestIsGCTableName(t *testing.T) {
7794
t.Run("explicit regexp", func(t *testing.T) {
7895
// NewGCTableNameExpression regexp is used externally by vreplication. Its a redundant form of
7996
// InternalTableNameExpression, but is nonetheless required. We verify it works correctly
80-
re := regexp.MustCompile(NewGCTableNameExpression)
97+
re := regexp.MustCompile(GCTableNameExpression)
8198
t.Run("accept", func(t *testing.T) {
8299
names := []string{
83100
"_vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_",
@@ -173,7 +190,7 @@ func TestAnalyzeGCTableName(t *testing.T) {
173190
assert.Equal(t, ts.isGC, isGC)
174191
if ts.isGC {
175192
assert.NoError(t, err)
176-
assert.True(t, IsGCUUID(uuid))
193+
assert.True(t, isCondensedUUID(uuid))
177194
assert.Equal(t, ts.state, state)
178195
assert.Equal(t, ts.t, tm)
179196
}

0 commit comments

Comments
 (0)