diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cdd519f..28de8afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## v3.4.13 (unreleased) - - Nothing changed yet. + - Do not allow Cassandra reserved words to be used as field names etc. ## v3.4.12 (2019-12-06) - Improve some CLI error messages diff --git a/names.go b/names.go index 360492d7..b7c43226 100644 --- a/names.go +++ b/names.go @@ -28,7 +28,8 @@ import ( ) // This module does some sanity checking of names used in DOSA. A name must have a leading letter, -// followed by a string of letters and digits. A name can have up to 32 chars. +// followed by a string of letters and digits. A name can have up to 32 chars. Reserved words are +// not allowed as a name. // // A special kind of name is the name-prefix. A name-prefix has the same restrictions as a name, // except that a name-prefix can also contain the "." character. @@ -40,10 +41,34 @@ const ( var ( namePrefixRegex = regexp.MustCompile("^[a-z_][a-z0-9_.]{0,31}$") nameRegex = regexp.MustCompile("^[a-z_][a-z0-9_]{0,31}$") + + // Reserved words + reserved map[string]struct{} ) -// DosaNamingRule DOSA Name Rule -const DosaNamingRule = "DOSA valid names must start with a letter or underscore, and may contain letters, digits, and underscores, and must not be longer than 32 characters." +func init() { + // Cassandra's reserved words + cassandraRsvd := []string{"add", "aggregate", "all", "allow", "alter", "and", "any", "apply", "as", + "asc", "ascii", "authorize", "batch", "begin", "bigint", "blob", "boolean", "by", "clustering", + "columnfamily", "compact", "consistency", "count", "counter", "create", "custom", "decimal", + "delete", "desc", "distinct", "double", "drop", "each_quorum", "entries", "exists", "filtering", + "float", "from", "frozen", "full", "grant", "if", "in", "index", "inet", "infinity", "insert", + "int", "into", "key", "keyspace", "keyspaces", "level", "limit", "list", "local_one", + "local_quorum", "map", "materialized", "modify", "nan", "norecursive", "nosuperuser", "not", + "of", "on", "one", "order", "partition", "password", "per", "permission", "permissions", + "primary", "quorum", "rename", "revoke", "schema", "select", "set", "static", "storage", + "superuser", "table", "text", "time", "timestamp", "timeuuid", "three", "to", "token", + "truncate", "ttl", "tuple", "two", "type", "unlogged", "update", "use", "user", "users", + "using", "uuid", "values", "varchar", "varint", "view", "where", "with", "writetime"} + reserved = make(map[string]struct{}) + for _, n := range cassandraRsvd { + reserved[n] = struct{}{} + } +} + +// DosaNamingRule is the error message for invalid names. +const DosaNamingRule = "DOSA valid names must start with a letter or underscore, and may contain letters, " + + "digits, and underscores, and must not be longer than 32 characters." // IsValidNamePrefix checks if a name prefix is valid. func IsValidNamePrefix(namePrefix string) error { @@ -59,7 +84,10 @@ func IsValidName(name string) error { if !nameRegex.MatchString(name) { return errors.Errorf("invalid name '%s': %s", name, DosaNamingRule) } - return nil + if _, ok := reserved[name]; !ok { + return nil + } + return errors.Errorf("%s is a reserved word", name) } // NormalizeName normalizes a name to a canonical representation. diff --git a/names_test.go b/names_test.go index 95be5a6b..634e4744 100644 --- a/names_test.go +++ b/names_test.go @@ -29,58 +29,66 @@ import ( func TestIsValidName(t *testing.T) { dataProvider := []struct { - arg string - allowed bool + arg string + err string }{ { - arg: "has_underscore", - allowed: true, + arg: "has_underscore", }, { - arg: "mixeDCase", - allowed: false, + arg: "mixeDCase", + err: "invalid name", }, { - arg: "md5", - allowed: true, + arg: "md5", }, { - arg: "_name", - allowed: true, + arg: "_name", }, { - arg: "_alreadynormalized9", - allowed: true, + arg: "_alreadynormalized9", }, { - arg: "123numberprefix", - allowed: false, + arg: "123numberprefix", + err: "invalid name", }, { - arg: "", - allowed: false, + arg: "", + err: "invalid name", }, { - arg: "longname012345678901234567890123456789", - allowed: false, + arg: "longname012345678901234567890123456789", + err: "invalid name", }, { - arg: "世界", - allowed: false, + arg: "世界", + err: "invalid name", }, { - arg: "an apple", - allowed: false, + arg: "an apple", + err: "invalid name", + }, + { + arg: "token", + err: "reserved word", + }, + { + arg: "keyspaces", + err: "reserved word", + }, + { + arg: "schema", + err: "reserved word", }, } for _, testData := range dataProvider { err := IsValidName(testData.arg) - if testData.allowed { - assert.NoError(t, err, fmt.Sprintf("got error while expecting no error for %s", testData.arg)) + if testData.err == "" { + assert.NoError(t, err, testData.arg) } else { - assert.Error(t, err, fmt.Sprintf("expect error but got no error for %s", testData.arg)) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Error(t, err, testData.arg) + assert.Contains(t, err.Error(), testData.err) } } } @@ -123,12 +131,10 @@ func TestNormalizeName(t *testing.T) { for _, testData := range dataProvider { name, err := NormalizeName(testData.arg) if testData.allowed { - assert.NoError(t, err, fmt.Sprintf("got error while expecting no error for %s", testData.arg)) - assert.Equal(t, testData.expected, name, - fmt.Sprintf("unexpected normalized name for %s", testData.arg)) + assert.NoError(t, err, testData.arg) + assert.Equal(t, testData.expected, name, testData.arg) } else { - assert.Error(t, err, fmt.Sprintf("expect error but got no error for %s", testData.arg)) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Error(t, err, testData.arg) } } } @@ -142,19 +148,19 @@ func TestIsValidNamePrefix(t *testing.T) { err = IsValidNamePrefix("") assert.Error(t, err) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Contains(t, err.Error(), "invalid name") err = IsValidNamePrefix("service.an entity") assert.Error(t, err) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Contains(t, err.Error(), "invalid name") err = IsValidNamePrefix("germanRush.über") assert.Error(t, err) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Contains(t, err.Error(), "invalid name") err = IsValidNamePrefix("this.prefix.has.more.than.thrity.two.characters.in.it") assert.Error(t, err) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Contains(t, err.Error(), "invalid name") } func TestNormalizeNamePrefix(t *testing.T) { @@ -205,7 +211,7 @@ func TestNormalizeNamePrefix(t *testing.T) { name, err := NormalizeNamePrefix(tc.arg) if tc.bogus { assert.Error(t, err) - assert.Contains(t, err.Error(), DosaNamingRule) + assert.Contains(t, err.Error(), "invalid name") } else { assert.NoError(t, err) assert.Equal(t, tc.expected, name)