-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New unified internal table names format: part 2, generating new names #15178
Changes from 6 commits
078bb0f
f78e48f
56db8cf
4ca4459
34438a0
f720936
ec2b976
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import ( | |
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNameIsGCTableName(t *testing.T) { | ||
|
@@ -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, readableTimestamp, "20150225110639") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be more meaningful if we didn't use a string literal here but instead used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point was to validate that a transformation of |
||
} | ||
|
||
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") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,85 +44,76 @@ | |
TableDroppedGCState TableGCState = "" | ||
) | ||
|
||
func (s TableGCState) TableHint() InternalTableHint { | ||
switch s { | ||
case HoldTableGCState: | ||
return InternalTableGCHoldHint | ||
case PurgeTableGCState: | ||
return InternalTableGCPurgeHint | ||
case EvacTableGCState: | ||
return InternalTableGCEvacHint | ||
case DropTableGCState: | ||
return InternalTableGCDropHint | ||
default: | ||
return InternalTableUnknownHint | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason not to make a map of these states like protobuf does? Not a big deal, but the use of Internal (capital I) and Hint here are not obvious to the average reader (me). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
} | ||
|
||
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{} | ||
) | ||
|
||
// 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() { | ||
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) { | ||
|
@@ -134,7 +125,7 @@ | |
} | ||
// 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 | ||
} | ||
|
@@ -165,8 +156,8 @@ | |
} | ||
|
||
// 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 | ||
} | ||
|
@@ -179,8 +170,8 @@ | |
} | ||
|
||
// 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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to use vterrors here so that we have codes? IIRC you added some error handling not too long ago that relied on error codes. Error messages also aren't supposed to be capitalized (for wrapping).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://go.dev/wiki/CodeReviewComments#error-strings