Skip to content

Commit

Permalink
New unified internal table names format: part 2, generating new names (
Browse files Browse the repository at this point in the history
…#15178)

Signed-off-by: Shlomi Noach <[email protected]>
  • Loading branch information
shlomi-noach authored Feb 15, 2024
1 parent af1d6d6 commit 09c3d56
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 91 deletions.
6 changes: 3 additions & 3 deletions go/test/endtoend/tabletmanager/tablegc/tablegc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,9 @@ func TestPopulateTable(t *testing.T) {

func generateRenameStatement(newFormat bool, fromTableName string, state schema.TableGCState, tm time.Time) (statement string, toTableName string, err error) {
if newFormat {
return schema.GenerateRenameStatementNewFormat(fromTableName, state, tm)
return schema.GenerateRenameStatement(fromTableName, state, tm)
}
return schema.GenerateRenameStatement(fromTableName, state, tm)
return schema.GenerateRenameStatementOldFormat(fromTableName, state, tm)
}

func TestHold(t *testing.T) {
Expand Down Expand Up @@ -449,7 +449,7 @@ func TestPurge(t *testing.T) {

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

_, err = primaryTablet.VttabletProcess.QueryTablet(query, keyspaceName, true)
Expand Down
56 changes: 55 additions & 1 deletion go/vt/schema/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package schema

import (
"fmt"
"regexp"
"strings"
"time"
Expand All @@ -32,8 +33,23 @@ const (
InternalTableNameExpression string = `^_vt_([a-zA-Z0-9]{3})_([0-f]{32})_([0-9]{14})_$`
)

type InternalTableHint string

const (
InternalTableUnknownHint InternalTableHint = "nil"
InternalTableGCHoldHint InternalTableHint = "hld"
InternalTableGCPurgeHint InternalTableHint = "prg"
InternalTableGCEvacHint InternalTableHint = "evc"
InternalTableGCDropHint InternalTableHint = "drp"
InternalTableVreplicationHint InternalTableHint = "vrp"
)

func (h InternalTableHint) String() string {
return string(h)
}

var (
// internalTableNameRegexp parses new intrnal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
// internalTableNameRegexp parses new internal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
internalTableNameRegexp = regexp.MustCompile(InternalTableNameExpression)
)

Expand Down Expand Up @@ -65,6 +81,44 @@ func ToReadableTimestamp(t time.Time) string {
return t.Format(readableTimeFormat)
}

// ReadableTimestamp returns the current timestamp, in seconds resolution, that is human readable
func ReadableTimestamp() string {
return ToReadableTimestamp(time.Now())
}

func condenseUUID(uuid string) string {
uuid = strings.ReplaceAll(uuid, "-", "")
uuid = strings.ReplaceAll(uuid, "_", "")
return uuid
}

// isCondensedUUID answers 'true' when the given string is a condensed UUID, e.g.:
// a0638f6bec7b11ea9bf8000d3a9b8a9a
func isCondensedUUID(uuid string) bool {
return condensedUUIDRegexp.MatchString(uuid)
}

// generateGCTableName creates an internal table name, based on desired hint and time, and with optional preset UUID.
// If uuid is given, then it must be in condensed-UUID format. If empty, the function auto-generates a UUID.
func GenerateInternalTableName(hint string, uuid string, t time.Time) (tableName string, err error) {
if len(hint) != 3 {
return "", fmt.Errorf("Invalid hint: %s, expected 3 characters", hint)
}
if uuid == "" {
uuid, err = CreateUUIDWithDelimiter("")
} else {
uuid = condenseUUID(uuid)
}
if err != nil {
return "", err
}
if !isCondensedUUID(uuid) {
return "", fmt.Errorf("Invalid UUID: %s, expected condensed 32 hexadecimals", uuid)
}
timestamp := ToReadableTimestamp(t)
return fmt.Sprintf("_vt_%s_%s_%s_", hint, uuid, timestamp), nil
}

// IsInternalOperationTableName answers 'true' when the given table name stands for an internal Vitess
// table used for operations such as:
// - Online DDL (gh-ost, pt-online-schema-change)
Expand Down
54 changes: 53 additions & 1 deletion go/vt/schema/name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNameIsGCTableName(t *testing.T) {
Expand Down Expand Up @@ -165,10 +166,61 @@ func TestAnalyzeInternalTableName(t *testing.T) {
assert.Equal(t, ts.isInternal, isInternal)
if ts.isInternal {
assert.NoError(t, err)
assert.True(t, IsGCUUID(uuid))
assert.True(t, isCondensedUUID(uuid))
assert.Equal(t, ts.hint, hint)
assert.Equal(t, ts.t, tm)
}
})
}
}

func TestToReadableTimestamp(t *testing.T) {
ti, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
assert.NoError(t, err)

readableTimestamp := ToReadableTimestamp(ti)
assert.Equal(t, "20150225110639", readableTimestamp)
}

func TestGenerateInternalTableName(t *testing.T) {
ti, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
assert.NoError(t, err)

{
uuid := "6ace8bcef73211ea87e9f875a4d24e90"
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
require.NoError(t, err)
assert.Equal(t, "_vt_prg_6ace8bcef73211ea87e9f875a4d24e90_20150225110639_", tableName)
assert.True(t, IsInternalOperationTableName(tableName))
}
{
uuid := "4e5dcf80_354b_11eb_82cd_f875a4d24e90"
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
require.NoError(t, err)
assert.Equal(t, "_vt_prg_4e5dcf80354b11eb82cdf875a4d24e90_20150225110639_", tableName)
assert.True(t, IsInternalOperationTableName(tableName))
}
{
uuid := "4e5dcf80-354b-11eb-82cd-f875a4d24e90"
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
require.NoError(t, err)
assert.Equal(t, "_vt_prg_4e5dcf80354b11eb82cdf875a4d24e90_20150225110639_", tableName)
assert.True(t, IsInternalOperationTableName(tableName))
}
{
uuid := ""
tableName, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
require.NoError(t, err)
assert.True(t, IsInternalOperationTableName(tableName))
}
{
uuid := "4e5dcf80_354b_11eb_82cd_f875a4d24e90_00001111"
_, err := GenerateInternalTableName(InternalTableGCPurgeHint.String(), uuid, ti)
require.ErrorContains(t, err, "Invalid UUID")
}
{
uuid := "6ace8bcef73211ea87e9f875a4d24e90"
_, err := GenerateInternalTableName("abcdefg", uuid, ti)
require.ErrorContains(t, err, "Invalid hint")
}
}
2 changes: 1 addition & 1 deletion go/vt/schema/online_ddl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestGetGCUUID(t *testing.T) {
onlineDDL, err := NewOnlineDDL("ks", "tbl", "alter table t drop column c", NewDDLStrategySetting(DDLStrategyDirect, ""), "", "", parser)
assert.NoError(t, err)
gcUUID := onlineDDL.GetGCUUID()
assert.True(t, IsGCUUID(gcUUID))
assert.True(t, isCondensedUUID(gcUUID))
uuids[gcUUID] = true
}
assert.Equal(t, count, len(uuids))
Expand Down
82 changes: 35 additions & 47 deletions go/vt/schema/tablegc.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,85 +44,73 @@ const (
TableDroppedGCState TableGCState = ""
)

func (s TableGCState) TableHint() InternalTableHint {
if hint, ok := gcStatesTableHints[s]; ok {
return hint
}
return InternalTableUnknownHint
}

const (
GCTableNameExpression string = `^_vt_(HOLD|PURGE|EVAC|DROP)_([0-f]{32})_([0-9]{14})$`
// NewGCTableNameExpression parses new intrnal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
NewGCTableNameExpression string = `^_vt_(hld|prg|evc|drp)_([0-f]{32})_([0-9]{14})_$`
OldGCTableNameExpression string = `^_vt_(HOLD|PURGE|EVAC|DROP)_([0-f]{32})_([0-9]{14})$`
// GCTableNameExpression parses new internal table name format, e.g. _vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_
GCTableNameExpression string = `^_vt_(hld|prg|evc|drp)_([0-f]{32})_([0-9]{14})_$`
)

var (
gcUUIDRegexp = regexp.MustCompile(`^[0-f]{32}$`)
gcTableNameRegexp = regexp.MustCompile(GCTableNameExpression)

gcStates = map[string]TableGCState{
string(HoldTableGCState): HoldTableGCState,
"hld": HoldTableGCState,
string(PurgeTableGCState): PurgeTableGCState,
"prg": PurgeTableGCState,
string(EvacTableGCState): EvacTableGCState,
"evc": EvacTableGCState,
string(DropTableGCState): DropTableGCState,
"drp": DropTableGCState,
}
condensedUUIDRegexp = regexp.MustCompile(`^[0-f]{32}$`)
oldGCTableNameRegexp = regexp.MustCompile(OldGCTableNameExpression)

gcStates = map[string]TableGCState{}
gcStatesTableHints = map[TableGCState]InternalTableHint{}
)

// IsGCUUID answers 'true' when the given string is an GC UUID, e.g.:
// a0638f6bec7b11ea9bf8000d3a9b8a9a
func IsGCUUID(uuid string) bool {
return gcUUIDRegexp.MatchString(uuid)
func init() {
gcStatesTableHints[HoldTableGCState] = InternalTableGCHoldHint
gcStatesTableHints[PurgeTableGCState] = InternalTableGCPurgeHint
gcStatesTableHints[EvacTableGCState] = InternalTableGCEvacHint
gcStatesTableHints[DropTableGCState] = InternalTableGCDropHint
for _, gcState := range []TableGCState{HoldTableGCState, PurgeTableGCState, EvacTableGCState, DropTableGCState} {
gcStates[string(gcState)] = gcState
gcStates[gcState.TableHint().String()] = gcState
}
}

// generateGCTableName creates a GC table name, based on desired state and time, and with optional preset UUID.
// If uuid is given, then it must be in GC-UUID format. If empty, the function auto-generates a UUID.
func generateGCTableName(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
func generateGCTableNameOldFormat(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
if uuid == "" {
uuid, err = CreateUUIDWithDelimiter("")
}
if err != nil {
return "", err
}
if !IsGCUUID(uuid) {
if !isCondensedUUID(uuid) {
return "", fmt.Errorf("Not a valid GC UUID format: %s", uuid)
}
timestamp := ToReadableTimestamp(t)
return fmt.Sprintf("_vt_%s_%s_%s", state, uuid, timestamp), nil
}

// generateGCTableNameNewFormat creates a GC table name, based on desired state and time, and with optional preset UUID.
// generateGCTableName creates a GC table name, based on desired state and time, and with optional preset UUID.
// If uuid is given, then it must be in GC-UUID format. If empty, the function auto-generates a UUID.
func generateGCTableNameNewFormat(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
if uuid == "" {
uuid, err = CreateUUIDWithDelimiter("")
}
if err != nil {
return "", err
}
if !IsGCUUID(uuid) {
return "", fmt.Errorf("Not a valid GC UUID format: %s", uuid)
}
timestamp := ToReadableTimestamp(t)
var hint string
func generateGCTableName(state TableGCState, uuid string, t time.Time) (tableName string, err error) {
for k, v := range gcStates {
if v != state {
continue
}
if len(k) == 3 && k != string(state) { // the "new" format
hint = k
return GenerateInternalTableName(k, uuid, t)
}
}
return fmt.Sprintf("_vt_%s_%s_%s_", hint, uuid, timestamp), nil
return "", fmt.Errorf("Unknown GC state: %v", state)
}

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

// GenerateGCTableNameNewFormat creates a GC table name, based on desired state and time, and with random UUID
func GenerateGCTableNameNewFormat(state TableGCState, t time.Time) (tableName string, err error) {
return generateGCTableNameNewFormat(state, "", t)
}

// AnalyzeGCTableName analyzes a given table name to see if it's a GC table, and if so, parse out
// its state, uuid, and timestamp
func AnalyzeGCTableName(tableName string) (isGCTable bool, state TableGCState, uuid string, t time.Time, err error) {
Expand All @@ -134,7 +122,7 @@ func AnalyzeGCTableName(tableName string) (isGCTable bool, state TableGCState, u
}
// Try old naming formats. These names will not be generated in v20.
// TODO(shlomi): the code below should be remvoed in v21
submatch := gcTableNameRegexp.FindStringSubmatch(tableName)
submatch := oldGCTableNameRegexp.FindStringSubmatch(tableName)
if len(submatch) == 0 {
return false, state, uuid, t, nil
}
Expand Down Expand Up @@ -165,8 +153,8 @@ func GenerateRenameStatementWithUUID(fromTableName string, state TableGCState, u
}

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

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

// ParseGCLifecycle parses a comma separated list of gc states and returns a map of indicated states
Expand Down
29 changes: 23 additions & 6 deletions go/vt/schema/tablegc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,37 @@ import (
"github.com/stretchr/testify/require"
)

func TestGCStates(t *testing.T) {
// These are all hard coded
require.Equal(t, HoldTableGCState, gcStates["hld"])
require.Equal(t, HoldTableGCState, gcStates["HOLD"])
require.Equal(t, PurgeTableGCState, gcStates["prg"])
require.Equal(t, PurgeTableGCState, gcStates["PURGE"])
require.Equal(t, EvacTableGCState, gcStates["evc"])
require.Equal(t, EvacTableGCState, gcStates["EVAC"])
require.Equal(t, DropTableGCState, gcStates["drp"])
require.Equal(t, DropTableGCState, gcStates["DROP"])
_, ok := gcStates["purge"]
require.False(t, ok)
_, ok = gcStates["vrp"]
require.False(t, ok)
require.Equal(t, 2*4, len(gcStates)) // 4 states, 2 forms each
}

func TestIsGCTableName(t *testing.T) {
tm := time.Now()
states := []TableGCState{HoldTableGCState, PurgeTableGCState, EvacTableGCState, DropTableGCState}
for _, state := range states {
for i := 0; i < 10; i++ {
tableName, err := generateGCTableName(state, "", tm)
tableName, err := generateGCTableNameOldFormat(state, "", tm)
assert.NoError(t, err)
assert.True(t, IsGCTableName(tableName))
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)

tableName, err = generateGCTableNameNewFormat(state, "6ace8bcef73211ea87e9f875a4d24e90", tm)
tableName, err = generateGCTableName(state, "6ace8bcef73211ea87e9f875a4d24e90", tm)
assert.NoError(t, err)
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)

tableName, err = GenerateGCTableNameNewFormat(state, tm)
tableName, err = GenerateGCTableName(state, tm)
assert.NoError(t, err)
assert.Truef(t, IsGCTableName(tableName), "table name: %s", tableName)
}
Expand Down Expand Up @@ -77,7 +94,7 @@ func TestIsGCTableName(t *testing.T) {
t.Run("explicit regexp", func(t *testing.T) {
// NewGCTableNameExpression regexp is used externally by vreplication. Its a redundant form of
// InternalTableNameExpression, but is nonetheless required. We verify it works correctly
re := regexp.MustCompile(NewGCTableNameExpression)
re := regexp.MustCompile(GCTableNameExpression)
t.Run("accept", func(t *testing.T) {
names := []string{
"_vt_hld_6ace8bcef73211ea87e9f875a4d24e90_20200915120410_",
Expand Down Expand Up @@ -173,7 +190,7 @@ func TestAnalyzeGCTableName(t *testing.T) {
assert.Equal(t, ts.isGC, isGC)
if ts.isGC {
assert.NoError(t, err)
assert.True(t, IsGCUUID(uuid))
assert.True(t, isCondensedUUID(uuid))
assert.Equal(t, ts.state, state)
assert.Equal(t, ts.t, tm)
}
Expand Down
Loading

0 comments on commit 09c3d56

Please sign in to comment.