diff --git a/docs/generated/sql/bnf/index_def.bnf b/docs/generated/sql/bnf/index_def.bnf index de6fef0a828a..9d09bd9a46d4 100644 --- a/docs/generated/sql/bnf/index_def.bnf +++ b/docs/generated/sql/bnf/index_def.bnf @@ -1,12 +1,20 @@ index_def ::= - 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'COVERING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'USING' 'HASH' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible @@ -15,5 +23,5 @@ index_def ::= | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'STORING' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' 'INCLUDE' '(' name_list ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INVERTED' 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'INVERTED' 'INDEX' name '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INVERTED' 'INDEX' '(' index_elem ( ( ',' index_elem ) )* ')' ( 'PARTITION' ( 'ALL' | ) 'BY' partition_by_inner | ) opt_with_storage_parameter_list opt_where_clause opt_index_visible diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 200677b447cc..b0376cb95b04 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -1127,6 +1127,7 @@ unreserved_keyword ::= | 'INCREMENT' | 'INCREMENTAL' | 'INCREMENTAL_LOCATION' + | 'INDEX' | 'INDEXES' | 'INHERITS' | 'INJECT' @@ -1194,6 +1195,7 @@ unreserved_keyword ::= | 'NEXT' | 'NO' | 'NORMAL' + | 'NOTHING' | 'NO_INDEX_JOIN' | 'NO_ZIGZAG_JOIN' | 'NO_FULL_SCAN' @@ -2615,6 +2617,8 @@ type_func_name_crdb_extra_keyword ::= cockroachdb_extra_reserved_keyword ::= 'INDEX' + | 'INDEX' + | 'INDEX' | 'NOTHING' type_func_name_keyword ::= @@ -3338,6 +3342,7 @@ interval_qualifier ::= func_name ::= type_function_name | prefixed_column_path + | 'INDEX' single_sort_clause ::= 'ORDER' 'BY' sortby @@ -3729,9 +3734,11 @@ storage_parameter_key ::= | 'SCONST' index_def ::= - 'INDEX' opt_index_name '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + 'INDEX' '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INDEX' name '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible | 'UNIQUE' 'INDEX' opt_index_name '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible - | 'INVERTED' 'INDEX' opt_name '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INVERTED' 'INDEX' '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + | 'INVERTED' 'INDEX' name '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible like_table_option_list ::= ( ) ( ( 'INCLUDING' like_table_option | 'EXCLUDING' like_table_option ) )* diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index 0c84eda1b93f..00f13e28533c 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -621,6 +621,7 @@ ALL_TESTS = [ "//pkg/workload/cli:cli_test", "//pkg/workload/faker:faker_test", "//pkg/workload/histogram:histogram_test", + "//pkg/workload/kv:kv_test", "//pkg/workload/movr:movr_test", "//pkg/workload/rand:rand_test", "//pkg/workload/tpcc:tpcc_test", @@ -2114,6 +2115,7 @@ GO_TARGETS = [ "//pkg/workload/indexes:indexes", "//pkg/workload/jsonload:jsonload", "//pkg/workload/kv:kv", + "//pkg/workload/kv:kv_test", "//pkg/workload/ledger:ledger", "//pkg/workload/movr:movr", "//pkg/workload/movr:movr_test", diff --git a/pkg/ccl/backupccl/backupinfo/BUILD.bazel b/pkg/ccl/backupccl/backupinfo/BUILD.bazel index 2bf6b1cb2bd6..42c7fe30b84c 100644 --- a/pkg/ccl/backupccl/backupinfo/BUILD.bazel +++ b/pkg/ccl/backupccl/backupinfo/BUILD.bazel @@ -50,6 +50,7 @@ go_library( "//pkg/util/timeutil", "//pkg/util/tracing", "@com_github_cockroachdb_errors//:errors", + "@com_github_klauspost_compress//gzip", ], ) diff --git a/pkg/ccl/backupccl/backupinfo/manifest_handling.go b/pkg/ccl/backupccl/backupinfo/manifest_handling.go index dc2cd72b627d..2d6d7c2f2505 100644 --- a/pkg/ccl/backupccl/backupinfo/manifest_handling.go +++ b/pkg/ccl/backupccl/backupinfo/manifest_handling.go @@ -10,7 +10,6 @@ package backupinfo import ( "bytes" - "compress/gzip" "context" "crypto/sha256" "encoding/hex" @@ -55,6 +54,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/tracing" "github.com/cockroachdb/errors" + gzip "github.com/klauspost/compress/gzip" ) // Files that may appear in a backup directory. @@ -189,7 +189,10 @@ func DecompressData(ctx context.Context, mem *mon.BoundAccount, descBytes []byte if err != nil { return nil, err } - defer r.Close() + defer func() { + // Swallow any errors, this is only a read operation. + _ = r.Close() + }() return mon.ReadAll(ctx, ioctx.ReaderAdapter(r), mem) } diff --git a/pkg/cmd/docgen/diagrams.go b/pkg/cmd/docgen/diagrams.go index 4c91cefd862b..0e2a76b10cd3 100644 --- a/pkg/cmd/docgen/diagrams.go +++ b/pkg/cmd/docgen/diagrams.go @@ -315,8 +315,12 @@ func runParse( return nil, errors.Wrap(err, "inline") } b, err := g.ExtractProduction(topStmt, descend, nosplit, match, exclude) + b = bytes.Replace(b, []byte("NOTHING_AFTER_RETURNING"), []byte("NOTHING"), -1) b = bytes.Replace(b, []byte("'IDENT'"), []byte("'identifier'"), -1) b = bytes.Replace(b, []byte("_LA"), []byte(""), -1) + b = bytes.Replace(b, []byte("INDEX_BEFORE_PAREN"), []byte("INDEX"), -1) + b = bytes.Replace(b, []byte("INDEX_BEFORE_NAME_THEN_PAREN"), []byte("INDEX"), -1) + b = bytes.Replace(b, []byte("INDEX_AFTER_ORDER_BY_BEFORE_AT"), []byte("INDEX"), -1) return b, err } diff --git a/pkg/sql/parser/lexer.go b/pkg/sql/parser/lexer.go index 714597588510..682901591fa2 100644 --- a/pkg/sql/parser/lexer.go +++ b/pkg/sql/parser/lexer.go @@ -80,6 +80,131 @@ func (l *lexer) Lex(lval *sqlSymType) int { *lval = l.tokens[l.lastPos] switch lval.id { + case NOTHING: + // Introducing the "RETURNING NOTHING" syntax in CockroachDB + // was a terrible idea, given that it is not even used any more! + // We should really deprecate it and remove this special case. + if l.lastPos > 0 && l.tokens[l.lastPos-1].id == RETURNING { + lval.id = NOTHING_AFTER_RETURNING + } + case INDEX: + // The following complex logic is a consternation, really. + // + // It flows from a profoundly mistaken decision to allow the INDEX + // keyword inside the column definition list of CREATE, a place + // where PostgreSQL did not allow it, for a very good reason: + // applications legitimately want to name columns with the name + // "index". + // + // After this mistaken decision was first made, the INDEX keyword + // was also allowed in CockroachDB in another place where it is + // partially ambiguous with other identifiers: ORDER BY + // (`ORDER BY INDEX foo@bar`, ambiguous with `ORDER BY index`). + // + // Sadly it took a very long time before we realized this mistake, + // and by that time these uses of INDEX have become legitimate + // CockroachDB features. + // + // We are thus left with the need to disambiguate between: + // + // CREATE TABLE t(index a) -- column name "index", column type "a" + // CREATE TABLE t(index (a)) -- keyword INDEX, column name "a" + // CREATE TABLE t(index a (b)) -- keyword INDEX, index name "a", column name "b" + // + // Thankfully, a coldef for a column named "index" and an index + // specification differ unambiguously, *given sufficient + // lookaheaed*: an index specification always has an open '(' + // after INDEX, with or without an identifier in-between. A column + // definition never has this. + // + // Likewise, between: + // + // ORDER BY index + // ORDER BY index a@idx + // ORDER BY index a.b@idx + // ORDER BY index a.b.c@idx + // + // We can unambiguously distinguish by the presence of the '@' sign + // with a maximum of 6 token lookahead. + // + var pprevID, prevID int32 + if l.lastPos > 0 { + prevID = l.tokens[l.lastPos-1].id + } + if l.lastPos > 1 { + pprevID = l.tokens[l.lastPos-2].id + } + var nextID, secondID int32 + if l.lastPos+1 < len(l.tokens) { + nextID = l.tokens[l.lastPos+1].id + } + if l.lastPos+2 < len(l.tokens) { + secondID = l.tokens[l.lastPos+2].id + } + afterCommaOrParen := prevID == ',' || prevID == '(' + afterCommaOrOPTIONS := prevID == ',' || prevID == OPTIONS + afterCommaOrParenThenINVERTED := prevID == INVERTED && (pprevID == ',' || pprevID == '(') + followedByParen := nextID == '(' + followedByNonPunctThenParen := nextID > 255 /* non-punctuation */ && secondID == '(' + if // + // CREATE ... (INDEX ( + // CREATE ... (x INT, y INT, INDEX ( + (afterCommaOrParen && followedByParen) || + // SCRUB ... WITH OPTIONS INDEX (... + // SCRUB ... WITH OPTIONS a, INDEX (... + (afterCommaOrOPTIONS && followedByParen) || + // CREATE ... (INVERTED INDEX ( + // CREATE ... (x INT, y INT, INVERTED INDEX ( + (afterCommaOrParenThenINVERTED && followedByParen) { + lval.id = INDEX_BEFORE_PAREN + break + } + if // + // CREATE ... (INDEX abc ( + // CREATE ... (x INT, y INT, INDEX abc ( + (afterCommaOrParen && followedByNonPunctThenParen) || + // CREATE ... (INVERTED INDEX abc ( + // CREATE ... (x INT, y INT, INVERTED INDEX abc ( + (afterCommaOrParenThenINVERTED && followedByNonPunctThenParen) { + lval.id = INDEX_BEFORE_NAME_THEN_PAREN + break + } + // The rules above all require that the INDEX keyword be + // followed ultimately by an open parenthesis, with no '@' + // in-between. The rule below is strictly exclusive with this + // situation. + afterCommaOrOrderBy := prevID == ',' || (prevID == BY && pprevID == ORDER) + if afterCommaOrOrderBy { + // SORT BY INDEX @ + // SORT BY a, b, INDEX @ + atSignAfterObjectName := false + // An object name has one of the following forms: + // name + // name.name + // name.name.name + // So it is between 1 and 5 tokens in length. + for i := l.lastPos + 1; i < len(l.tokens) && i < l.lastPos+7; i++ { + curToken := l.tokens[i].id + // An object name can only contain keyword/identifiers, and + // the punctuation '.'. + if curToken < 255 /* not ident/keyword */ && curToken != '.' && curToken != '@' { + // Definitely not object name. + break + } + if curToken == '@' { + if i == l.lastPos+1 { + /* The '@' cannot follow the INDEX keyword directly. */ + break + } + atSignAfterObjectName = true + break + } + } + if atSignAfterObjectName { + lval.id = INDEX_AFTER_ORDER_BY_BEFORE_AT + } + } + case NOT, WITH, AS, GENERATED, NULLS, RESET, ROLE, USER, ON, TENANT, SET: nextToken := sqlSymType{} if l.lastPos+1 < len(l.tokens) { diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 420ee4e38183..1b23cd8f9f14 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -888,6 +888,7 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %token INCLUDING INCREMENT INCREMENTAL INCREMENTAL_LOCATION %token INET INET_CONTAINED_BY_OR_EQUALS %token INET_CONTAINS_OR_EQUALS INDEX INDEXES INHERITS INJECT INITIALLY +%token INDEX_BEFORE_PAREN INDEX_BEFORE_NAME_THEN_PAREN INDEX_AFTER_ORDER_BY_BEFORE_AT %token INNER INOUT INPUT INSENSITIVE INSERT INT INTEGER %token INTERSECT INTERVAL INTO INTO_DB INVERTED INVOKER IS ISERROR ISNULL ISOLATION @@ -907,7 +908,9 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %token NAN NAME NAMES NATURAL NEVER NEW_DB_NAME NEW_KMS NEXT NO NOCANCELQUERY NOCONTROLCHANGEFEED %token NOCONTROLJOB NOCREATEDB NOCREATELOGIN NOCREATEROLE NOLOGIN NOMODIFYCLUSTERSETTING -%token NOSQLLOGIN NO_INDEX_JOIN NO_ZIGZAG_JOIN NO_FULL_SCAN NONE NONVOTERS NORMAL NOT NOTHING NOTNULL +%token NOSQLLOGIN NO_INDEX_JOIN NO_ZIGZAG_JOIN NO_FULL_SCAN NONE NONVOTERS NORMAL NOT +%token NOTHING NOTHING_AFTER_RETURNING +%token NOTNULL %token NOVIEWACTIVITY NOVIEWACTIVITYREDACTED NOVIEWCLUSTERSETTING NOWAIT NULL NULLIF NULLS NUMERIC %token OF OFF OFFSET OID OIDS OIDVECTOR OLD_KMS ON ONLY OPT OPTION OPTIONS OR @@ -5889,7 +5892,7 @@ scrub_option: { $$.val = &tree.ScrubOptionIndex{} } -| INDEX '(' name_list ')' +| INDEX_BEFORE_PAREN '(' name_list ')' { $$.val = &tree.ScrubOptionIndex{IndexNames: $3.nameList()} } @@ -8724,7 +8727,20 @@ generated_by_default_as: GENERATED_BY_DEFAULT BY DEFAULT AS {} index_def: - INDEX opt_index_name '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + INDEX_BEFORE_PAREN '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + { + $$.val = &tree.IndexTableDef{ + Name: "", + Columns: $3.idxElems(), + Sharded: $5.shardedIndexDef(), + Storing: $6.nameList(), + PartitionByIndex: $7.partitionByIndex(), + StorageParams: $8.storageParams(), + Predicate: $9.expr(), + NotVisible: $10.bool(), + } + } +| INDEX_BEFORE_NAME_THEN_PAREN name '(' index_params ')' opt_hash_sharded opt_storing opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible { $$.val = &tree.IndexTableDef{ Name: tree.Name($2), @@ -8752,7 +8768,19 @@ index_def: }, } } -| INVERTED INDEX opt_name '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible +| INVERTED INDEX_BEFORE_PAREN '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible + { + $$.val = &tree.IndexTableDef{ + Name: "", + Columns: $4.idxElems(), + Inverted: true, + PartitionByIndex: $6.partitionByIndex(), + StorageParams: $7.storageParams(), + Predicate: $8.expr(), + NotVisible: $9.bool(), + } + } +| INVERTED INDEX_BEFORE_NAME_THEN_PAREN name '(' index_params ')' opt_partition_by_index opt_with_storage_parameter_list opt_where_clause opt_index_visible { $$.val = &tree.IndexTableDef{ Name: tree.Name($3), @@ -10909,7 +10937,7 @@ returning_clause: ret := tree.ReturningExprs($2.selExprs()) $$.val = &ret } -| RETURNING NOTHING +| RETURNING NOTHING_AFTER_RETURNING { $$.val = tree.ReturningNothingClause } @@ -11472,7 +11500,7 @@ sortby: name := $3.unresolvedObjectName().ToTableName() $$.val = &tree.Order{OrderType: tree.OrderByIndex, Direction: $4.dir(), Table: name} } -| INDEX table_name '@' index_name opt_asc_desc +| INDEX_AFTER_ORDER_BY_BEFORE_AT table_name '@' index_name opt_asc_desc { name := $2.unresolvedObjectName().ToTableName() $$.val = &tree.Order{ @@ -15059,6 +15087,10 @@ func_name: $$.val = &tree.UnresolvedName{NumParts:1, Parts: tree.NameParts{$1}} } | prefixed_column_path +| INDEX_BEFORE_PAREN + { + $$.val = &tree.UnresolvedName{NumParts:1, Parts: tree.NameParts{$1}} + } // func_name_no_crdb_extra is the same rule as func_name, but does not // contain some CRDB specific keywords like FAMILY. @@ -15332,6 +15364,7 @@ unreserved_keyword: | INCREMENT | INCREMENTAL | INCREMENTAL_LOCATION +| INDEX | INDEXES | INHERITS | INJECT @@ -15399,6 +15432,7 @@ unreserved_keyword: | NEXT | NO | NORMAL +| NOTHING | NO_INDEX_JOIN | NO_ZIGZAG_JOIN | NO_FULL_SCAN @@ -15854,7 +15888,9 @@ reserved_keyword: // Adding keywords here creates non-resolvable incompatibilities with // postgres clients. cockroachdb_extra_reserved_keyword: - INDEX -| NOTHING + INDEX_BEFORE_NAME_THEN_PAREN +| INDEX_BEFORE_PAREN +| INDEX_AFTER_ORDER_BY_BEFORE_AT +| NOTHING_AFTER_RETURNING %% diff --git a/pkg/sql/parser/testdata/create_table b/pkg/sql/parser/testdata/create_table index dfdc684fdd7c..b4e131d9027c 100644 --- a/pkg/sql/parser/testdata/create_table +++ b/pkg/sql/parser/testdata/create_table @@ -14,22 +14,6 @@ CREATE TABLE "family" (x INT8) -- fully parenthesized CREATE TABLE "family" (x INT8) -- literals removed CREATE TABLE _ (_ INT8) -- identifiers removed -parse -CREATE TABLE INDEX (x INT) ----- -CREATE TABLE "index" (x INT8) -- normalized! -CREATE TABLE "index" (x INT8) -- fully parenthesized -CREATE TABLE "index" (x INT8) -- literals removed -CREATE TABLE _ (_ INT8) -- identifiers removed - -parse -CREATE TABLE NOTHING (x INT) ----- -CREATE TABLE "nothing" (x INT8) -- normalized! -CREATE TABLE "nothing" (x INT8) -- fully parenthesized -CREATE TABLE "nothing" (x INT8) -- literals removed -CREATE TABLE _ (_ INT8) -- identifiers removed - parse CREATE TABLE MINVALUE (x INT) ---- diff --git a/pkg/sql/parser/testdata/reserved_keywords b/pkg/sql/parser/testdata/reserved_keywords new file mode 100644 index 000000000000..8587cd583ae6 --- /dev/null +++ b/pkg/sql/parser/testdata/reserved_keywords @@ -0,0 +1,122 @@ +parse +CREATE TABLE nothing (nothing INT) +---- +CREATE TABLE nothing (nothing INT8) -- normalized! +CREATE TABLE nothing (nothing INT8) -- fully parenthesized +CREATE TABLE nothing (nothing INT8) -- literals removed +CREATE TABLE _ (_ INT8) -- identifiers removed + +parse +SELECT nothing(nothing + 1) AS nothing +---- +SELECT nothing(nothing + 1) AS nothing +SELECT (nothing(((nothing) + (1)))) AS nothing -- fully parenthesized +SELECT nothing(nothing + _) AS nothing -- literals removed +SELECT nothing(_ + 1) AS _ -- identifiers removed + +parse +SET a = nothing +---- +SET a = nothing +SET a = (nothing) -- fully parenthesized +SET a = nothing -- literals removed +SET a = _ -- identifiers removed + +parse +CREATE TABLE index (index INT) +---- +CREATE TABLE index (index INT8) -- normalized! +CREATE TABLE index (index INT8) -- fully parenthesized +CREATE TABLE index (index INT8) -- literals removed +CREATE TABLE _ (_ INT8) -- identifiers removed + +parse +CREATE TABLE index (a INT CONSTRAINT index NOT NULL FAMILY index, index INT) +---- +CREATE TABLE index (a INT8 CONSTRAINT index NOT NULL FAMILY index, index INT8) -- normalized! +CREATE TABLE index (a INT8 CONSTRAINT index NOT NULL FAMILY index, index INT8) -- fully parenthesized +CREATE TABLE index (a INT8 CONSTRAINT index NOT NULL FAMILY index, index INT8) -- literals removed +CREATE TABLE _ (_ INT8 CONSTRAINT _ NOT NULL FAMILY _, _ INT8) -- identifiers removed + +parse +CREATE TABLE index (FAMILY index(index), INDEX (index), INDEX index(index)) +---- +CREATE TABLE index (FAMILY index (index), INDEX (index), INDEX index (index)) -- normalized! +CREATE TABLE index (FAMILY index (index), INDEX (index), INDEX index (index)) -- fully parenthesized +CREATE TABLE index (FAMILY index (index), INDEX (index), INDEX index (index)) -- literals removed +CREATE TABLE _ (FAMILY _ (_), INDEX (_), INDEX _ (_)) -- identifiers removed + +parse +CREATE TYPE index AS ENUM ('a') +---- +CREATE TYPE index AS ENUM ('a') +CREATE TYPE index AS ENUM ('a') -- fully parenthesized +CREATE TYPE index AS ENUM ('a') -- literals removed +CREATE TYPE _ AS ENUM (_) -- identifiers removed + +parse +CREATE TABLE index (a mytype, index index, b myothertype) +---- +CREATE TABLE index (a mytype, index index, b myothertype) +CREATE TABLE index (a mytype, index index, b myothertype) -- fully parenthesized +CREATE TABLE index (a mytype, index index, b myothertype) -- literals removed +CREATE TABLE _ (_ mytype, _ index, _ myothertype) -- identifiers removed + +parse +CREATE TABLE index (index, index) AS TABLE index +---- +CREATE TABLE index (index, index) AS TABLE index +CREATE TABLE index (index, index) AS TABLE index -- fully parenthesized +CREATE TABLE index (index, index) AS TABLE index -- literals removed +CREATE TABLE _ (_, _) AS TABLE _ -- identifiers removed + +parse +SELECT index, index AS index, index.index FROM index index, index AS index, index index ORDER BY index, index +---- +SELECT index, index AS index, index.index FROM index AS index, index AS index, index AS index ORDER BY index, index -- normalized! +SELECT (index), (index) AS index, (index.index) FROM index AS index, index AS index, index AS index ORDER BY (index), (index) -- fully parenthesized +SELECT index, index AS index, index.index FROM index AS index, index AS index, index AS index ORDER BY index, index -- literals removed +SELECT _, _ AS _, _._ FROM _ AS _, _ AS _, _ AS _ ORDER BY _, _ -- identifiers removed + +parse +SELECT index::index, index->'index', EXTRACT(seconds FROM index) +---- +SELECT index::index, index->'index', extract('seconds', index) -- normalized! +SELECT ((index)::index), ((index)->('index')), (extract(('seconds'), (index))) -- fully parenthesized +SELECT index::index, index->'_', extract('_', index) -- literals removed +SELECT _::_, _->'index', extract('seconds', _) -- identifiers removed + +# The following exercises ", index(" in a scalar position. This requires +# special handling in func_expr. +parse +SELECT index(index), index(index), index.index(index) +---- +SELECT index(index), index(index), index.index(index) +SELECT (index((index))), (index((index))), (index.index((index))) -- fully parenthesized +SELECT index(index), index(index), index.index(index) -- literals removed +SELECT index(_), index(_), index.index(_) -- identifiers removed + +# Note: this is also an error in PostgreSQL. +error +SELECT index index FROM a +---- +at or near "index": syntax error +DETAIL: source SQL: +SELECT index index FROM a + ^ + +parse +INSERT INTO t VALUES (1) RETURNING index, index +---- +INSERT INTO t VALUES (1) RETURNING index, index +INSERT INTO t VALUES ((1)) RETURNING (index), (index) -- fully parenthesized +INSERT INTO t VALUES (_) RETURNING index, index -- literals removed +INSERT INTO _ VALUES (1) RETURNING _, _ -- identifiers removed + +parse +SET a = index +---- +SET a = index +SET a = (index) -- fully parenthesized +SET a = index -- literals removed +SET a = _ -- identifiers removed diff --git a/pkg/sql/parser/testdata/select_clauses b/pkg/sql/parser/testdata/select_clauses index d4ad9a58f3f8..787eea3adf91 100644 --- a/pkg/sql/parser/testdata/select_clauses +++ b/pkg/sql/parser/testdata/select_clauses @@ -1546,6 +1546,14 @@ SELECT (a) FROM t ORDER BY INDEX t@foo -- fully parenthesized SELECT a FROM t ORDER BY INDEX t@foo -- literals removed SELECT _ FROM _ ORDER BY INDEX _@_ -- identifiers removed +parse +SELECT a FROM t ORDER BY INDEX t.t.t@foo +---- +SELECT a FROM t ORDER BY INDEX t.t.t@foo +SELECT (a) FROM t ORDER BY INDEX t.t.t@foo -- fully parenthesized +SELECT a FROM t ORDER BY INDEX t.t.t@foo -- literals removed +SELECT _ FROM _ ORDER BY INDEX _._._@_ -- identifiers removed + parse SELECT a FROM t ORDER BY INDEX t@foo ASC ---- diff --git a/pkg/sql/parser/testdata/set b/pkg/sql/parser/testdata/set index 000c973e9461..2265ba939722 100644 --- a/pkg/sql/parser/testdata/set +++ b/pkg/sql/parser/testdata/set @@ -14,23 +14,6 @@ EXPLAIN SET a = (3) -- fully parenthesized EXPLAIN SET a = _ -- literals removed EXPLAIN SET a = 3 -- identifiers removed -parse -SET a = INDEX ----- -SET a = "index" -- normalized! -SET a = ("index") -- fully parenthesized -SET a = "index" -- literals removed -SET a = _ -- identifiers removed - -parse -SET a = NOTHING ----- -SET a = "nothing" -- normalized! -SET a = ("nothing") -- fully parenthesized -SET a = "nothing" -- literals removed -SET a = _ -- identifiers removed - - parse SET a = 3, 4 ---- diff --git a/pkg/sql/ttl/ttljob/ttljob_query_builder_test.go b/pkg/sql/ttl/ttljob/ttljob_query_builder_test.go index bbb3707d61b4..f1683c085108 100644 --- a/pkg/sql/ttl/ttljob/ttljob_query_builder_test.go +++ b/pkg/sql/ttl/ttljob/ttljob_query_builder_test.go @@ -481,9 +481,9 @@ func TestMakeColumnNamesSQL(t *testing.T) { expected string }{ {[]string{"a"}, "a"}, - {[]string{"index"}, `"index"`}, + {[]string{"index"}, `index`}, {[]string{"a", "b"}, "a, b"}, - {[]string{"escape-me", "index", "c"}, `"escape-me", "index", c`}, + {[]string{"escape-me", "index", "c"}, `"escape-me", index, c`}, } for _, tc := range testCases { diff --git a/pkg/workload/kv/BUILD.bazel b/pkg/workload/kv/BUILD.bazel index 246d3f7aeacb..8a85834b96db 100644 --- a/pkg/workload/kv/BUILD.bazel +++ b/pkg/workload/kv/BUILD.bazel @@ -1,5 +1,5 @@ load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data") -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "kv", @@ -21,4 +21,12 @@ go_library( ], ) +go_test( + name = "kv_test", + srcs = ["kv_test.go"], + args = ["-test.timeout=295s"], + embed = [":kv"], + deps = ["@com_github_stretchr_testify//require"], +) + get_x_data(name = "get_x_data") diff --git a/pkg/workload/kv/kv.go b/pkg/workload/kv/kv.go index c0ef34d2eb04..503285afedd2 100644 --- a/pkg/workload/kv/kv.go +++ b/pkg/workload/kv/kv.go @@ -174,8 +174,17 @@ ALTER TABLE kv ADD COLUMN e enum_type NOT NULL AS ('v') STORED;`) return errors.Errorf("Value of 'max-block-bytes' (%d) must be greater than or equal to value of 'min-block-bytes' (%d)", w.maxBlockSizeBytes, w.minBlockSizeBytes) } - if w.sequential && w.splits > 0 { - return errors.New("'sequential' and 'splits' cannot both be enabled") + if w.splits < 0 { + return errors.Errorf("Value of `--splits` (%d) must not be negative", + w.splits) + } + if w.cycleLength < 1 { + return errors.Errorf("Value of `--cycle-length` (%d) must be greater than 0", + w.cycleLength) + } + if w.cycleLength <= int64(w.splits) { + return errors.Errorf("Value of `--splits` (%d) must be less than the value of `--cycle-length` (%d)", + w.splits, w.cycleLength) } if w.sequential && w.zipfian { return errors.New("'sequential' and 'zipfian' cannot both be enabled") @@ -194,20 +203,49 @@ ALTER TABLE kv ADD COLUMN e enum_type NOT NULL AS ('v') STORED;`) } } +func (w *kv) keyRange() (int64, int64) { + rangeMin := int64(0) + rangeMax := w.cycleLength + if w.sequential { + // Sequential can generate keys in the range [0, cycleLength) + } else if w.zipfian { + // Zipfian can generate keys in the range [0, MaxInt64) + rangeMax = math.MaxInt64 + } else { + // Hash can generate keys in the range [MinInt64, MaxInt64) + rangeMax = math.MaxInt64 + rangeMin = math.MinInt64 + } + return rangeMin, rangeMax +} + +// splitFinder returns the ith split point, given the key access distribution +// and number of splits. +func (w *kv) splitFinder(i int) int { + splits := int64(w.splits) + if splits < 0 || (splits >= w.cycleLength && w.sequential) { + panic(fmt.Sprintf("programming error: splits (%d) cannot be less than 0, "+ + "greater than or equal to the cycle-length (%d) with sequential", + splits, w.cycleLength, + )) + } + rangeMin, rangeMax := w.keyRange() + + stride := rangeMax/(splits+1) - rangeMin/(splits+1) + splitPoint := int(rangeMin + int64(i+1)*stride) + return splitPoint +} + // Tables implements the Generator interface. func (w *kv) Tables() []workload.Table { - table := workload.Table{ - Name: `kv`, - // TODO(dan): Support initializing kv with data. - Splits: workload.Tuples( - w.splits, - func(splitIdx int) []interface{} { - stride := (float64(w.cycleLength) - float64(math.MinInt64)) / float64(w.splits+1) - splitPoint := int(math.MinInt64 + float64(splitIdx+1)*stride) - return []interface{}{splitPoint} - }, - ), - } + table := workload.Table{Name: `kv`} + table.Splits = workload.Tuples( + w.splits, + func(splitIdx int) []interface{} { + return []interface{}{w.splitFinder(splitIdx)} + }, + ) + if w.shards > 0 { schema := shardedKvSchema if w.secondaryIndex { diff --git a/pkg/workload/kv/kv_test.go b/pkg/workload/kv/kv_test.go new file mode 100644 index 000000000000..0bbbde6fa4a3 --- /dev/null +++ b/pkg/workload/kv/kv_test.go @@ -0,0 +1,85 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package kv + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSplitFinder(t *testing.T) { + mx := math.MaxInt64 + mn := math.MinInt64 + + testCases := []struct { + desc string + config *kv + expected []int + expectPanic bool + }{ + { + desc: "hash [min,max], 5 splits", + config: &kv{splits: 5, cycleLength: 1 /* irrelevant for hash */}, + // NB: We perform integer division to determine the split points, + // -2 to account for the error on this case. + expected: []int{2*(mn/3) - 2, (mn / 3) - 2, -2, (mx / 3) - 2, 2*(mx/3) - 2}, + }, + { + desc: "hash [min,max], 1 splits", + config: &kv{splits: 1}, + expected: []int{-1}, + }, + { + desc: "sequential [0, 600), 5 splits", + config: &kv{splits: 5, sequential: true, cycleLength: 600}, + expected: []int{100, 200, 300, 400, 500}, + }, + { + desc: "sequential [0, 5), 4 splits", + config: &kv{splits: 4, sequential: true, cycleLength: 5}, + expected: []int{1, 2, 3, 4}, + }, + { + desc: "zipfian [0, max), 5 splits", + config: &kv{splits: 5, zipfian: true, cycleLength: 1 /* irrelevant for zipf */}, + expected: []int{mx / 6, 2 * (mx / 6), 3 * (mx / 6), 4 * (mx / 6), 5 * (mx / 6)}, + }, + { + desc: "invalid: splits >= cycle-length when sequential", + config: &kv{splits: 5, cycleLength: 5, sequential: true}, + expectPanic: true, + }, + { + desc: "invalid: splits < 0", + config: &kv{splits: -1}, + expectPanic: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + splits := tc.config.splits + + if tc.expectPanic { + require.Panics(t, func() { tc.config.splitFinder(0) }) + return + } + + results := make([]int, splits) + for i := range results { + results[i] = tc.config.splitFinder(i) + } + require.Equal(t, tc.expected, results) + }) + } +}