diff --git a/docs/generated/sql/bnf/drop_stmt.bnf b/docs/generated/sql/bnf/drop_stmt.bnf index 1ad02e13dcb7..3b87e9d1dd04 100644 --- a/docs/generated/sql/bnf/drop_stmt.bnf +++ b/docs/generated/sql/bnf/drop_stmt.bnf @@ -6,3 +6,4 @@ drop_stmt ::= | drop_sequence_stmt | drop_type_stmt | drop_role_stmt + | drop_schedule_stmt diff --git a/docs/generated/sql/bnf/pause_job.bnf b/docs/generated/sql/bnf/pause_job.bnf index a51ed54cb5e6..6b9fbb521b1f 100644 --- a/docs/generated/sql/bnf/pause_job.bnf +++ b/docs/generated/sql/bnf/pause_job.bnf @@ -1,3 +1,3 @@ -pause_stmt ::= +pause_jobs_stmt ::= 'PAUSE' 'JOB' job_id | 'PAUSE' 'JOBS' select_stmt diff --git a/docs/generated/sql/bnf/pause_schedule.bnf b/docs/generated/sql/bnf/pause_schedule.bnf new file mode 100644 index 000000000000..0e4152dbbed5 --- /dev/null +++ b/docs/generated/sql/bnf/pause_schedule.bnf @@ -0,0 +1,4 @@ +pause_schedules_stmt ::= + 'PAUSE' 'SCHEDULE' schedule_id + | 'PAUSE' 'SCHEDULES' select_stmt + | with_clause 'PAUSE' 'SCHEDULES' diff --git a/docs/generated/sql/bnf/resume_job.bnf b/docs/generated/sql/bnf/resume_job.bnf index 8d4cd5eba911..c7ef323f9f21 100644 --- a/docs/generated/sql/bnf/resume_job.bnf +++ b/docs/generated/sql/bnf/resume_job.bnf @@ -1,3 +1,3 @@ -resume_stmt ::= +resume_jobs_stmt ::= 'RESUME' 'JOB' job_id | 'RESUME' 'JOBS' select_stmt diff --git a/docs/generated/sql/bnf/resume_schedule.bnf b/docs/generated/sql/bnf/resume_schedule.bnf new file mode 100644 index 000000000000..c06451783a89 --- /dev/null +++ b/docs/generated/sql/bnf/resume_schedule.bnf @@ -0,0 +1,3 @@ +resume_schedules_stmt ::= + 'RESUME' 'SCHEDULE' schedule_id + | 'RESUME' 'SCHEDULES' select_stmt diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 9591a4f184b8..cda7dca4a98e 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -123,6 +123,7 @@ delete_stmt ::= drop_stmt ::= drop_ddl_stmt | drop_role_stmt + | drop_schedule_stmt explain_stmt ::= 'EXPLAIN' preparable_stmt @@ -145,8 +146,8 @@ insert_stmt ::= | opt_with_clause 'INSERT' 'INTO' insert_target insert_rest on_conflict returning_clause pause_stmt ::= - 'PAUSE' 'JOB' a_expr - | 'PAUSE' 'JOBS' select_stmt + pause_jobs_stmt + | pause_schedules_stmt reset_stmt ::= reset_session_stmt @@ -157,8 +158,8 @@ restore_stmt ::= | 'RESTORE' targets 'FROM' partitioned_backup_list opt_as_of_clause opt_with_options resume_stmt ::= - 'RESUME' 'JOB' a_expr - | 'RESUME' 'JOBS' select_stmt + resume_jobs_stmt + | resume_schedules_stmt export_stmt ::= 'EXPORT' 'INTO' import_format string_or_placeholder opt_with_options 'FROM' select_stmt @@ -409,6 +410,10 @@ drop_role_stmt ::= 'DROP' role_or_group_or_user string_or_placeholder_list | 'DROP' role_or_group_or_user 'IF' 'EXISTS' string_or_placeholder_list +drop_schedule_stmt ::= + 'DROP' 'SCHEDULE' a_expr + | 'DROP' 'SCHEDULES' select_stmt + explain_option_list ::= ( explain_option_name ) ( ( ',' explain_option_name ) )* @@ -441,8 +446,14 @@ on_conflict ::= 'ON' 'CONFLICT' opt_conf_expr 'DO' 'UPDATE' 'SET' set_clause_list opt_where_clause | 'ON' 'CONFLICT' opt_conf_expr 'DO' 'NOTHING' -a_expr ::= - ( c_expr | '+' a_expr | '-' a_expr | '~' a_expr | 'SQRT' a_expr | 'CBRT' a_expr | 'NOT' a_expr | 'NOT' a_expr | 'DEFAULT' ) ( ( 'TYPECAST' cast_target | 'TYPEANNOTATE' typename | 'COLLATE' collation_name | 'AT' 'TIME' 'ZONE' a_expr | '+' a_expr | '-' a_expr | '*' a_expr | '/' a_expr | 'FLOORDIV' a_expr | '%' a_expr | '^' a_expr | '#' a_expr | '&' a_expr | '|' a_expr | '<' a_expr | '>' a_expr | '?' a_expr | 'JSON_SOME_EXISTS' a_expr | 'JSON_ALL_EXISTS' a_expr | 'CONTAINS' a_expr | 'CONTAINED_BY' a_expr | '=' a_expr | 'CONCAT' a_expr | 'LSHIFT' a_expr | 'RSHIFT' a_expr | 'FETCHVAL' a_expr | 'FETCHTEXT' a_expr | 'FETCHVAL_PATH' a_expr | 'FETCHTEXT_PATH' a_expr | 'REMOVE_PATH' a_expr | 'INET_CONTAINED_BY_OR_EQUALS' a_expr | 'AND_AND' a_expr | 'INET_CONTAINS_OR_EQUALS' a_expr | 'LESS_EQUALS' a_expr | 'GREATER_EQUALS' a_expr | 'NOT_EQUALS' a_expr | 'AND' a_expr | 'OR' a_expr | 'LIKE' a_expr | 'LIKE' a_expr 'ESCAPE' a_expr | 'NOT' 'LIKE' a_expr | 'NOT' 'LIKE' a_expr 'ESCAPE' a_expr | 'ILIKE' a_expr | 'ILIKE' a_expr 'ESCAPE' a_expr | 'NOT' 'ILIKE' a_expr | 'NOT' 'ILIKE' a_expr 'ESCAPE' a_expr | 'SIMILAR' 'TO' a_expr | 'SIMILAR' 'TO' a_expr 'ESCAPE' a_expr | 'NOT' 'SIMILAR' 'TO' a_expr | 'NOT' 'SIMILAR' 'TO' a_expr 'ESCAPE' a_expr | '~' a_expr | 'NOT_REGMATCH' a_expr | 'REGIMATCH' a_expr | 'NOT_REGIMATCH' a_expr | 'IS' 'NAN' | 'IS' 'NOT' 'NAN' | 'IS' 'NULL' | 'ISNULL' | 'IS' 'NOT' 'NULL' | 'NOTNULL' | 'IS' 'TRUE' | 'IS' 'NOT' 'TRUE' | 'IS' 'FALSE' | 'IS' 'NOT' 'FALSE' | 'IS' 'UNKNOWN' | 'IS' 'NOT' 'UNKNOWN' | 'IS' 'DISTINCT' 'FROM' a_expr | 'IS' 'NOT' 'DISTINCT' 'FROM' a_expr | 'IS' 'OF' '(' type_list ')' | 'IS' 'NOT' 'OF' '(' type_list ')' | 'BETWEEN' opt_asymmetric b_expr 'AND' a_expr | 'NOT' 'BETWEEN' opt_asymmetric b_expr 'AND' a_expr | 'BETWEEN' 'SYMMETRIC' b_expr 'AND' a_expr | 'NOT' 'BETWEEN' 'SYMMETRIC' b_expr 'AND' a_expr | 'IN' in_expr | 'NOT' 'IN' in_expr | subquery_op sub_type a_expr ) )* +pause_jobs_stmt ::= + 'PAUSE' 'JOB' a_expr + | 'PAUSE' 'JOBS' select_stmt + +pause_schedules_stmt ::= + 'PAUSE' 'SCHEDULE' a_expr + | 'PAUSE' 'SCHEDULES' select_stmt + | with_clause 'PAUSE' 'SCHEDULES' reset_session_stmt ::= 'RESET' session_var @@ -454,6 +465,14 @@ reset_csetting_stmt ::= partitioned_backup_list ::= ( partitioned_backup ) ( ( ',' partitioned_backup ) )* +resume_jobs_stmt ::= + 'RESUME' 'JOB' a_expr + | 'RESUME' 'JOBS' select_stmt + +resume_schedules_stmt ::= + 'RESUME' 'SCHEDULE' a_expr + | 'RESUME' 'SCHEDULES' select_stmt + scrub_table_stmt ::= 'EXPERIMENTAL' 'SCRUB' 'TABLE' table_name opt_as_of_clause opt_scrub_options_clause @@ -833,6 +852,7 @@ unreserved_keyword ::= | 'ROWS' | 'RULE' | 'SCHEDULE' + | 'SCHEDULES' | 'SETTING' | 'SETTINGS' | 'STATUS' @@ -1052,6 +1072,9 @@ as_of_clause ::= backup_options_list ::= ( backup_options ) ( ( ',' backup_options ) )* +a_expr ::= + ( c_expr | '+' a_expr | '-' a_expr | '~' a_expr | 'SQRT' a_expr | 'CBRT' a_expr | 'NOT' a_expr | 'NOT' a_expr | 'DEFAULT' ) ( ( 'TYPECAST' cast_target | 'TYPEANNOTATE' typename | 'COLLATE' collation_name | 'AT' 'TIME' 'ZONE' a_expr | '+' a_expr | '-' a_expr | '*' a_expr | '/' a_expr | 'FLOORDIV' a_expr | '%' a_expr | '^' a_expr | '#' a_expr | '&' a_expr | '|' a_expr | '<' a_expr | '>' a_expr | '?' a_expr | 'JSON_SOME_EXISTS' a_expr | 'JSON_ALL_EXISTS' a_expr | 'CONTAINS' a_expr | 'CONTAINED_BY' a_expr | '=' a_expr | 'CONCAT' a_expr | 'LSHIFT' a_expr | 'RSHIFT' a_expr | 'FETCHVAL' a_expr | 'FETCHTEXT' a_expr | 'FETCHVAL_PATH' a_expr | 'FETCHTEXT_PATH' a_expr | 'REMOVE_PATH' a_expr | 'INET_CONTAINED_BY_OR_EQUALS' a_expr | 'AND_AND' a_expr | 'INET_CONTAINS_OR_EQUALS' a_expr | 'LESS_EQUALS' a_expr | 'GREATER_EQUALS' a_expr | 'NOT_EQUALS' a_expr | 'AND' a_expr | 'OR' a_expr | 'LIKE' a_expr | 'LIKE' a_expr 'ESCAPE' a_expr | 'NOT' 'LIKE' a_expr | 'NOT' 'LIKE' a_expr 'ESCAPE' a_expr | 'ILIKE' a_expr | 'ILIKE' a_expr 'ESCAPE' a_expr | 'NOT' 'ILIKE' a_expr | 'NOT' 'ILIKE' a_expr 'ESCAPE' a_expr | 'SIMILAR' 'TO' a_expr | 'SIMILAR' 'TO' a_expr 'ESCAPE' a_expr | 'NOT' 'SIMILAR' 'TO' a_expr | 'NOT' 'SIMILAR' 'TO' a_expr 'ESCAPE' a_expr | '~' a_expr | 'NOT_REGMATCH' a_expr | 'REGIMATCH' a_expr | 'NOT_REGIMATCH' a_expr | 'IS' 'NAN' | 'IS' 'NOT' 'NAN' | 'IS' 'NULL' | 'ISNULL' | 'IS' 'NOT' 'NULL' | 'NOTNULL' | 'IS' 'TRUE' | 'IS' 'NOT' 'TRUE' | 'IS' 'FALSE' | 'IS' 'NOT' 'FALSE' | 'IS' 'UNKNOWN' | 'IS' 'NOT' 'UNKNOWN' | 'IS' 'DISTINCT' 'FROM' a_expr | 'IS' 'NOT' 'DISTINCT' 'FROM' a_expr | 'IS' 'OF' '(' type_list ')' | 'IS' 'NOT' 'OF' '(' type_list ')' | 'BETWEEN' opt_asymmetric b_expr 'AND' a_expr | 'NOT' 'BETWEEN' opt_asymmetric b_expr 'AND' a_expr | 'BETWEEN' 'SYMMETRIC' b_expr 'AND' a_expr | 'NOT' 'BETWEEN' 'SYMMETRIC' b_expr 'AND' a_expr | 'IN' in_expr | 'NOT' 'IN' in_expr | subquery_op sub_type a_expr ) )* + create_changefeed_stmt ::= 'CREATE' 'CHANGEFEED' 'FOR' changefeed_targets opt_changefeed_sink opt_with_options @@ -1188,45 +1211,6 @@ opt_conf_expr ::= '(' name_list ')' | -c_expr ::= - d_expr - | d_expr array_subscripts - | case_expr - | 'EXISTS' select_with_parens - -cast_target ::= - typename - -typename ::= - simple_typename opt_array_bounds - | simple_typename 'ARRAY' - -collation_name ::= - unrestricted_name - -opt_asymmetric ::= - 'ASYMMETRIC' - | - -b_expr ::= - ( c_expr | '+' b_expr | '-' b_expr | '~' b_expr ) ( ( 'TYPECAST' cast_target | 'TYPEANNOTATE' typename | '+' b_expr | '-' b_expr | '*' b_expr | '/' b_expr | 'FLOORDIV' b_expr | '%' b_expr | '^' b_expr | '#' b_expr | '&' b_expr | '|' b_expr | '<' b_expr | '>' b_expr | '=' b_expr | 'CONCAT' b_expr | 'LSHIFT' b_expr | 'RSHIFT' b_expr | 'LESS_EQUALS' b_expr | 'GREATER_EQUALS' b_expr | 'NOT_EQUALS' b_expr | 'IS' 'DISTINCT' 'FROM' b_expr | 'IS' 'NOT' 'DISTINCT' 'FROM' b_expr | 'IS' 'OF' '(' type_list ')' | 'IS' 'NOT' 'OF' '(' type_list ')' ) )* - -in_expr ::= - select_with_parens - | expr_tuple1_ambiguous - -subquery_op ::= - math_op - | 'LIKE' - | 'NOT' 'LIKE' - | 'ILIKE' - | 'NOT' 'ILIKE' - -sub_type ::= - 'ANY' - | 'SOME' - | 'ALL' - session_var ::= 'identifier' | 'ALL' @@ -1350,6 +1334,10 @@ unrestricted_name ::= | type_func_name_keyword | reserved_keyword +typename ::= + simple_typename opt_array_bounds + | simple_typename 'ARRAY' + transaction_mode ::= transaction_user_priority | transaction_read_mode @@ -1454,6 +1442,41 @@ backup_options ::= | 'REVISION_HISTORY' | 'DETACHED' +c_expr ::= + d_expr + | d_expr array_subscripts + | case_expr + | 'EXISTS' select_with_parens + +cast_target ::= + typename + +collation_name ::= + unrestricted_name + +opt_asymmetric ::= + 'ASYMMETRIC' + | + +b_expr ::= + ( c_expr | '+' b_expr | '-' b_expr | '~' b_expr ) ( ( 'TYPECAST' cast_target | 'TYPEANNOTATE' typename | '+' b_expr | '-' b_expr | '*' b_expr | '/' b_expr | 'FLOORDIV' b_expr | '%' b_expr | '^' b_expr | '#' b_expr | '&' b_expr | '|' b_expr | '<' b_expr | '>' b_expr | '=' b_expr | 'CONCAT' b_expr | 'LSHIFT' b_expr | 'RSHIFT' b_expr | 'LESS_EQUALS' b_expr | 'GREATER_EQUALS' b_expr | 'NOT_EQUALS' b_expr | 'IS' 'DISTINCT' 'FROM' b_expr | 'IS' 'NOT' 'DISTINCT' 'FROM' b_expr | 'IS' 'OF' '(' type_list ')' | 'IS' 'NOT' 'OF' '(' type_list ')' ) )* + +in_expr ::= + select_with_parens + | expr_tuple1_ambiguous + +subquery_op ::= + math_op + | 'LIKE' + | 'NOT' 'LIKE' + | 'ILIKE' + | 'NOT' 'ILIKE' + +sub_type ::= + 'ANY' + | 'SOME' + | 'ALL' + changefeed_targets ::= single_table_pattern_list | 'TABLE' single_table_pattern_list @@ -1616,72 +1639,6 @@ like_table_option_list ::= column_name ::= name -d_expr ::= - 'ICONST' - | 'FCONST' - | 'SCONST' - | 'BCONST' - | 'BITCONST' - | typed_literal - | interval_value - | 'TRUE' - | 'FALSE' - | 'NULL' - | column_path_with_star - | '@' iconst64 - | 'PLACEHOLDER' - | '(' a_expr ')' '.' '*' - | '(' a_expr ')' '.' unrestricted_name - | '(' a_expr ')' '.' '@' 'ICONST' - | '(' a_expr ')' - | func_expr - | select_with_parens - | labeled_row - | 'ARRAY' select_with_parens - | 'ARRAY' row - | 'ARRAY' array_expr - -array_subscripts ::= - ( array_subscript ) ( ( array_subscript ) )* - -case_expr ::= - 'CASE' case_arg when_clause_list case_default 'END' - -simple_typename ::= - general_type_name - | '@' iconst32 - | complex_type_name - | const_typename - | bit_with_length - | character_with_length - | interval_type - -opt_array_bounds ::= - '[' ']' - | - -expr_tuple1_ambiguous ::= - '(' ')' - | '(' tuple1_ambiguous_values ')' - -math_op ::= - '+' - | '-' - | '*' - | '/' - | 'FLOORDIV' - | '%' - | '&' - | '|' - | '^' - | '#' - | '<' - | '>' - | '=' - | 'LESS_EQUALS' - | 'GREATER_EQUALS' - | 'NOT_EQUALS' - attrs ::= ( '.' unrestricted_name ) ( ( '.' unrestricted_name ) )* @@ -1834,6 +1791,19 @@ reserved_keyword ::= | 'WITH' | cockroachdb_extra_reserved_keyword +simple_typename ::= + general_type_name + | '@' iconst32 + | complex_type_name + | const_typename + | bit_with_length + | character_with_length + | interval_type + +opt_array_bounds ::= + '[' ']' + | + transaction_user_priority ::= 'PRIORITY' user_priority @@ -1862,6 +1832,59 @@ role_option ::= | password_clause | valid_until_clause +d_expr ::= + 'ICONST' + | 'FCONST' + | 'SCONST' + | 'BCONST' + | 'BITCONST' + | typed_literal + | interval_value + | 'TRUE' + | 'FALSE' + | 'NULL' + | column_path_with_star + | '@' iconst64 + | 'PLACEHOLDER' + | '(' a_expr ')' '.' '*' + | '(' a_expr ')' '.' unrestricted_name + | '(' a_expr ')' '.' '@' 'ICONST' + | '(' a_expr ')' + | func_expr + | select_with_parens + | labeled_row + | 'ARRAY' select_with_parens + | 'ARRAY' row + | 'ARRAY' array_expr + +array_subscripts ::= + ( array_subscript ) ( ( array_subscript ) )* + +case_expr ::= + 'CASE' case_arg when_clause_list case_default 'END' + +expr_tuple1_ambiguous ::= + '(' ')' + | '(' tuple1_ambiguous_values ')' + +math_op ::= + '+' + | '-' + | '*' + | '/' + | 'FLOORDIV' + | '%' + | '&' + | '|' + | '^' + | '#' + | '<' + | '>' + | '=' + | 'LESS_EQUALS' + | 'GREATER_EQUALS' + | 'NOT_EQUALS' + single_table_pattern_list ::= ( table_name ) ( ( ',' table_name ) )* @@ -1937,103 +1960,24 @@ like_table_option ::= | 'INDEXES' | 'ALL' -typed_literal ::= - func_name_no_crdb_extra 'SCONST' - | const_typename 'SCONST' +scrub_option ::= + 'INDEX' 'ALL' + | 'INDEX' '(' name_list ')' + | 'CONSTRAINT' 'ALL' + | 'CONSTRAINT' '(' name_list ')' + | 'PHYSICAL' -interval_value ::= - 'INTERVAL' 'SCONST' opt_interval_qualifier - | 'INTERVAL' '(' iconst32 ')' 'SCONST' +opt_all_clause ::= + 'ALL' + | -column_path_with_star ::= - column_path - | db_object_name_component '.' unrestricted_name '.' unrestricted_name '.' '*' - | db_object_name_component '.' unrestricted_name '.' '*' - | db_object_name_component '.' '*' +from_clause ::= + 'FROM' from_list opt_as_of_clause + | -func_expr ::= - func_application within_group_clause filter_clause over_clause - | func_expr_common_subexpr - -labeled_row ::= - row - | '(' row 'AS' name_list ')' - -row ::= - 'ROW' '(' opt_expr_list ')' - | expr_tuple_unambiguous - -array_expr ::= - '[' opt_expr_list ']' - | '[' array_expr_list ']' - -array_subscript ::= - '[' a_expr ']' - | '[' opt_slice_bound ':' opt_slice_bound ']' - -case_arg ::= - a_expr - | - -when_clause_list ::= - ( when_clause ) ( ( when_clause ) )* - -case_default ::= - 'ELSE' a_expr - | - -general_type_name ::= - type_function_name_no_crdb_extra - -iconst32 ::= - 'ICONST' - -complex_type_name ::= - general_type_name '.' unrestricted_name - | general_type_name '.' unrestricted_name '.' unrestricted_name - -const_typename ::= - numeric - | bit_without_length - | character_without_length - | const_datetime - | const_geo - -bit_with_length ::= - 'BIT' opt_varying '(' iconst32 ')' - | 'VARBIT' '(' iconst32 ')' - -character_with_length ::= - character_base '(' iconst32 ')' - -interval_type ::= - 'INTERVAL' - | 'INTERVAL' interval_qualifier - | 'INTERVAL' '(' iconst32 ')' - -tuple1_ambiguous_values ::= - a_expr - | a_expr ',' - | a_expr ',' expr_list - -scrub_option ::= - 'INDEX' 'ALL' - | 'INDEX' '(' name_list ')' - | 'CONSTRAINT' 'ALL' - | 'CONSTRAINT' '(' name_list ')' - | 'PHYSICAL' - -opt_all_clause ::= - 'ALL' - | - -from_clause ::= - 'FROM' from_list opt_as_of_clause - | - -group_clause ::= - 'GROUP' 'BY' expr_list - | +group_clause ::= + 'GROUP' 'BY' expr_list + | having_clause ::= 'HAVING' a_expr @@ -2112,6 +2056,35 @@ type_func_name_no_crdb_extra_keyword ::= | 'RIGHT' | 'SIMILAR' +general_type_name ::= + type_function_name_no_crdb_extra + +iconst32 ::= + 'ICONST' + +complex_type_name ::= + general_type_name '.' unrestricted_name + | general_type_name '.' unrestricted_name '.' unrestricted_name + +const_typename ::= + numeric + | bit_without_length + | character_without_length + | const_datetime + | const_geo + +bit_with_length ::= + 'BIT' opt_varying '(' iconst32 ')' + | 'VARBIT' '(' iconst32 ')' + +character_with_length ::= + character_base '(' iconst32 ')' + +interval_type ::= + 'INTERVAL' + | 'INTERVAL' interval_qualifier + | 'INTERVAL' '(' iconst32 ')' + user_priority ::= 'LOW' | 'NORMAL' @@ -2167,6 +2140,56 @@ valid_until_clause ::= 'VALID' 'UNTIL' string_or_placeholder | 'VALID' 'UNTIL' 'NULL' +typed_literal ::= + func_name_no_crdb_extra 'SCONST' + | const_typename 'SCONST' + +interval_value ::= + 'INTERVAL' 'SCONST' opt_interval_qualifier + | 'INTERVAL' '(' iconst32 ')' 'SCONST' + +column_path_with_star ::= + column_path + | db_object_name_component '.' unrestricted_name '.' unrestricted_name '.' '*' + | db_object_name_component '.' unrestricted_name '.' '*' + | db_object_name_component '.' '*' + +func_expr ::= + func_application within_group_clause filter_clause over_clause + | func_expr_common_subexpr + +labeled_row ::= + row + | '(' row 'AS' name_list ')' + +row ::= + 'ROW' '(' opt_expr_list ')' + | expr_tuple_unambiguous + +array_expr ::= + '[' opt_expr_list ']' + | '[' array_expr_list ']' + +array_subscript ::= + '[' a_expr ']' + | '[' opt_slice_bound ':' opt_slice_bound ']' + +case_arg ::= + a_expr + | + +when_clause_list ::= + ( when_clause ) ( ( when_clause ) )* + +case_default ::= + 'ELSE' a_expr + | + +tuple1_ambiguous_values ::= + a_expr + | a_expr ',' + | a_expr ',' expr_list + opt_asc_desc ::= 'ASC' | 'DESC' @@ -2218,76 +2241,44 @@ reference_actions ::= | reference_on_delete reference_on_update | -func_name_no_crdb_extra ::= - type_function_name_no_crdb_extra - | prefixed_column_path - -opt_interval_qualifier ::= - interval_qualifier - | - -func_application ::= - func_name '(' ')' - | func_name '(' expr_list opt_sort_clause ')' - | func_name '(' 'ALL' expr_list opt_sort_clause ')' - | func_name '(' 'DISTINCT' expr_list ')' - | func_name '(' '*' ')' - -within_group_clause ::= - 'WITHIN' 'GROUP' '(' single_sort_clause ')' - | +window_definition_list ::= + ( window_definition ) ( ( ',' window_definition ) )* -filter_clause ::= - 'FILTER' '(' 'WHERE' a_expr ')' - | +for_locking_strength ::= + 'FOR' 'UPDATE' + | 'FOR' 'NO' 'KEY' 'UPDATE' + | 'FOR' 'SHARE' + | 'FOR' 'KEY' 'SHARE' -over_clause ::= - 'OVER' window_specification - | 'OVER' window_name - | +opt_locked_rels ::= + 'OF' table_name_list -func_expr_common_subexpr ::= - 'COLLATION' 'FOR' '(' a_expr ')' - | 'CURRENT_DATE' - | 'CURRENT_SCHEMA' - | 'CURRENT_CATALOG' - | 'CURRENT_TIMESTAMP' - | 'CURRENT_TIME' - | 'LOCALTIMESTAMP' - | 'LOCALTIME' - | 'CURRENT_USER' - | 'CURRENT_ROLE' - | 'SESSION_USER' - | 'USER' - | 'CAST' '(' a_expr 'AS' cast_target ')' - | 'ANNOTATE_TYPE' '(' a_expr ',' typename ')' - | 'IF' '(' a_expr ',' a_expr ',' a_expr ')' - | 'IFERROR' '(' a_expr ',' a_expr ',' a_expr ')' - | 'IFERROR' '(' a_expr ',' a_expr ')' - | 'ISERROR' '(' a_expr ')' - | 'ISERROR' '(' a_expr ',' a_expr ')' - | 'NULLIF' '(' a_expr ',' a_expr ')' - | 'IFNULL' '(' a_expr ',' a_expr ')' - | 'COALESCE' '(' expr_list ')' - | special_function +opt_nowait_or_skip ::= + 'SKIP' 'LOCKED' + | 'NOWAIT' -opt_expr_list ::= - expr_list +opt_join_hint ::= + 'HASH' + | 'MERGE' + | 'LOOKUP' | -expr_tuple_unambiguous ::= - '(' ')' - | '(' tuple1_unambiguous_values ')' +join_type ::= + 'FULL' join_outer + | 'LEFT' join_outer + | 'RIGHT' join_outer + | 'INNER' -array_expr_list ::= - ( array_expr ) ( ( ',' array_expr ) )* +join_qual ::= + 'USING' '(' name_list ')' + | 'ON' a_expr -opt_slice_bound ::= - a_expr - | +func_expr_windowless ::= + func_application + | func_expr_common_subexpr -when_clause ::= - 'WHEN' a_expr 'THEN' a_expr +rowsfrom_list ::= + ( rowsfrom_item ) ( ( ',' rowsfrom_item ) )* type_function_name_no_crdb_extra ::= 'identifier' @@ -2359,45 +2350,6 @@ interval_qualifier ::= | 'HOUR' 'TO' interval_second | 'MINUTE' 'TO' interval_second -window_definition_list ::= - ( window_definition ) ( ( ',' window_definition ) )* - -for_locking_strength ::= - 'FOR' 'UPDATE' - | 'FOR' 'NO' 'KEY' 'UPDATE' - | 'FOR' 'SHARE' - | 'FOR' 'KEY' 'SHARE' - -opt_locked_rels ::= - 'OF' table_name_list - -opt_nowait_or_skip ::= - 'SKIP' 'LOCKED' - | 'NOWAIT' - -opt_join_hint ::= - 'HASH' - | 'MERGE' - | 'LOOKUP' - | - -join_type ::= - 'FULL' join_outer - | 'LEFT' join_outer - | 'RIGHT' join_outer - | 'INNER' - -join_qual ::= - 'USING' '(' name_list ')' - | 'ON' a_expr - -func_expr_windowless ::= - func_application - | func_expr_common_subexpr - -rowsfrom_list ::= - ( rowsfrom_item ) ( ( ',' rowsfrom_item ) )* - opt_column ::= 'COLUMN' | @@ -2429,6 +2381,77 @@ audit_mode ::= signed_iconst64 ::= signed_iconst +func_name_no_crdb_extra ::= + type_function_name_no_crdb_extra + | prefixed_column_path + +opt_interval_qualifier ::= + interval_qualifier + | + +func_application ::= + func_name '(' ')' + | func_name '(' expr_list opt_sort_clause ')' + | func_name '(' 'ALL' expr_list opt_sort_clause ')' + | func_name '(' 'DISTINCT' expr_list ')' + | func_name '(' '*' ')' + +within_group_clause ::= + 'WITHIN' 'GROUP' '(' single_sort_clause ')' + | + +filter_clause ::= + 'FILTER' '(' 'WHERE' a_expr ')' + | + +over_clause ::= + 'OVER' window_specification + | 'OVER' window_name + | + +func_expr_common_subexpr ::= + 'COLLATION' 'FOR' '(' a_expr ')' + | 'CURRENT_DATE' + | 'CURRENT_SCHEMA' + | 'CURRENT_CATALOG' + | 'CURRENT_TIMESTAMP' + | 'CURRENT_TIME' + | 'LOCALTIMESTAMP' + | 'LOCALTIME' + | 'CURRENT_USER' + | 'CURRENT_ROLE' + | 'SESSION_USER' + | 'USER' + | 'CAST' '(' a_expr 'AS' cast_target ')' + | 'ANNOTATE_TYPE' '(' a_expr ',' typename ')' + | 'IF' '(' a_expr ',' a_expr ',' a_expr ')' + | 'IFERROR' '(' a_expr ',' a_expr ',' a_expr ')' + | 'IFERROR' '(' a_expr ',' a_expr ')' + | 'ISERROR' '(' a_expr ')' + | 'ISERROR' '(' a_expr ',' a_expr ')' + | 'NULLIF' '(' a_expr ',' a_expr ')' + | 'IFNULL' '(' a_expr ',' a_expr ')' + | 'COALESCE' '(' expr_list ')' + | special_function + +opt_expr_list ::= + expr_list + | + +expr_tuple_unambiguous ::= + '(' ')' + | '(' tuple1_unambiguous_values ')' + +array_expr_list ::= + ( array_expr ) ( ( ',' array_expr ) )* + +opt_slice_bound ::= + a_expr + | + +when_clause ::= + 'WHEN' a_expr 'THEN' a_expr + list_partition ::= partition 'VALUES' 'IN' '(' expr_list ')' opt_partition_by @@ -2462,47 +2485,15 @@ reference_on_update ::= reference_on_delete ::= 'ON' 'DELETE' reference_action -func_name ::= - type_function_name - | prefixed_column_path - -single_sort_clause ::= - 'ORDER' 'BY' sortby - | 'ORDER' 'BY' sortby ',' sortby_list - -window_specification ::= - '(' opt_existing_window_name opt_partition_clause opt_sort_clause opt_frame_clause ')' - -window_name ::= - name +window_definition ::= + window_name 'AS' window_specification -special_function ::= - 'CURRENT_DATE' '(' ')' - | 'CURRENT_SCHEMA' '(' ')' - | 'CURRENT_TIMESTAMP' '(' ')' - | 'CURRENT_TIMESTAMP' '(' a_expr ')' - | 'CURRENT_TIME' '(' ')' - | 'CURRENT_TIME' '(' a_expr ')' - | 'LOCALTIMESTAMP' '(' ')' - | 'LOCALTIMESTAMP' '(' a_expr ')' - | 'LOCALTIME' '(' ')' - | 'LOCALTIME' '(' a_expr ')' - | 'CURRENT_USER' '(' ')' - | 'EXTRACT' '(' extract_list ')' - | 'EXTRACT_DURATION' '(' extract_list ')' - | 'OVERLAY' '(' overlay_list ')' - | 'POSITION' '(' position_list ')' - | 'SUBSTRING' '(' substr_list ')' - | 'TRIM' '(' 'BOTH' trim_list ')' - | 'TRIM' '(' 'LEADING' trim_list ')' - | 'TRIM' '(' 'TRAILING' trim_list ')' - | 'TRIM' '(' trim_list ')' - | 'GREATEST' '(' expr_list ')' - | 'LEAST' '(' expr_list ')' +join_outer ::= + 'OUTER' + | -tuple1_unambiguous_values ::= - a_expr ',' - | a_expr ',' expr_list +rowsfrom_item ::= + func_expr_windowless opt_float ::= '(' 'ICONST' ')' @@ -2540,15 +2531,47 @@ interval_second ::= 'SECOND' | 'SECOND' '(' iconst32 ')' -window_definition ::= - window_name 'AS' window_specification +func_name ::= + type_function_name + | prefixed_column_path -join_outer ::= - 'OUTER' - | +single_sort_clause ::= + 'ORDER' 'BY' sortby + | 'ORDER' 'BY' sortby ',' sortby_list -rowsfrom_item ::= - func_expr_windowless +window_specification ::= + '(' opt_existing_window_name opt_partition_clause opt_sort_clause opt_frame_clause ')' + +window_name ::= + name + +special_function ::= + 'CURRENT_DATE' '(' ')' + | 'CURRENT_SCHEMA' '(' ')' + | 'CURRENT_TIMESTAMP' '(' ')' + | 'CURRENT_TIMESTAMP' '(' a_expr ')' + | 'CURRENT_TIME' '(' ')' + | 'CURRENT_TIME' '(' a_expr ')' + | 'LOCALTIMESTAMP' '(' ')' + | 'LOCALTIMESTAMP' '(' a_expr ')' + | 'LOCALTIME' '(' ')' + | 'LOCALTIME' '(' a_expr ')' + | 'CURRENT_USER' '(' ')' + | 'EXTRACT' '(' extract_list ')' + | 'EXTRACT_DURATION' '(' extract_list ')' + | 'OVERLAY' '(' overlay_list ')' + | 'POSITION' '(' position_list ')' + | 'SUBSTRING' '(' substr_list ')' + | 'TRIM' '(' 'BOTH' trim_list ')' + | 'TRIM' '(' 'LEADING' trim_list ')' + | 'TRIM' '(' 'TRAILING' trim_list ')' + | 'TRIM' '(' trim_list ')' + | 'GREATEST' '(' expr_list ')' + | 'LEAST' '(' expr_list ')' + +tuple1_unambiguous_values ::= + a_expr ',' + | a_expr ',' expr_list create_as_col_qualification_elem ::= 'PRIMARY' 'KEY' diff --git a/pkg/cmd/docgen/diagrams.go b/pkg/cmd/docgen/diagrams.go index c3a217a4fae6..e12284e5de4b 100644 --- a/pkg/cmd/docgen/diagrams.go +++ b/pkg/cmd/docgen/diagrams.go @@ -883,10 +883,16 @@ var specs = []stmtSpec{ }, { name: "pause_job", - stmt: "pause_stmt", + stmt: "pause_jobs_stmt", replace: map[string]string{"a_expr": "job_id"}, unlink: []string{"job_id"}, }, + { + name: "pause_schedule", + stmt: "pause_schedules_stmt", + replace: map[string]string{"a_expr": "schedule_id"}, + unlink: []string{"schedule_id"}, + }, { name: "primary_key_column_level", stmt: "stmt_block", @@ -956,10 +962,16 @@ var specs = []stmtSpec{ }, { name: "resume_job", - stmt: "resume_stmt", + stmt: "resume_jobs_stmt", replace: map[string]string{"a_expr": "job_id"}, unlink: []string{"job_id"}, }, + { + name: "resume_schedule", + stmt: "resume_schedules_stmt", + replace: map[string]string{"a_expr": "schedule_id"}, + unlink: []string{"schedule_id"}, + }, { name: "revoke_privileges", stmt: "revoke_stmt", diff --git a/pkg/jobs/job_scheduler_test.go b/pkg/jobs/job_scheduler_test.go index fe8814fe99b4..9743dcbf7d08 100644 --- a/pkg/jobs/job_scheduler_test.go +++ b/pkg/jobs/job_scheduler_test.go @@ -22,9 +22,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/jobs/jobspb" "github.com/cockroachdb/cockroach/pkg/kv" "github.com/cockroachdb/cockroach/pkg/scheduledjobs" - "github.com/cockroachdb/cockroach/pkg/security" "github.com/cockroachdb/cockroach/pkg/settings" - "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" "github.com/cockroachdb/cockroach/pkg/sql/sqlutil" "github.com/cockroachdb/cockroach/pkg/testutils" "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" @@ -39,19 +37,6 @@ import ( "github.com/stretchr/testify/require" ) -func addFakeJob(t *testing.T, h *testHelper, id int64, status Status, txn *kv.Txn) { - payload := []byte("fake payload") - n, err := h.cfg.InternalExecutor.ExecEx(context.Background(), "fake-job", txn, - sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, - fmt.Sprintf( - "INSERT INTO %s (created_by_type, created_by_id, status, payload) VALUES ($1, $2, $3, $4)", - h.env.SystemJobsTableName()), - CreatedByScheduledJobs, id, status, payload, - ) - require.NoError(t, err) - require.Equal(t, 1, n) -} - func TestJobSchedulerReschedulesRunning(t *testing.T) { defer leaktest.AfterTest(t)() defer log.Scope(t).Close(t) @@ -78,7 +63,7 @@ func TestJobSchedulerReschedulesRunning(t *testing.T) { // non terminal states. for _, status := range []Status{ StatusRunning, StatusFailed, StatusCanceled, StatusSucceeded, StatusPaused} { - addFakeJob(t, h, j.ScheduleID(), status, txn) + _ = addFakeJob(t, h, j.ScheduleID(), status, txn) } return nil })) @@ -133,7 +118,7 @@ func TestJobSchedulerExecutesAfterTerminal(t *testing.T) { // Let's add few fake runs for this schedule which are in every // terminal state. for _, status := range []Status{StatusFailed, StatusCanceled, StatusSucceeded} { - addFakeJob(t, h, j.ScheduleID(), status, txn) + _ = addFakeJob(t, h, j.ScheduleID(), status, txn) } return nil })) diff --git a/pkg/jobs/schedule_control_test.go b/pkg/jobs/schedule_control_test.go new file mode 100644 index 000000000000..7dd703647753 --- /dev/null +++ b/pkg/jobs/schedule_control_test.go @@ -0,0 +1,115 @@ +// Copyright 2020 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 jobs + +import ( + "context" + "fmt" + "testing" + + "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "github.com/stretchr/testify/require" +) + +func TestScheduleControl(t *testing.T) { + defer leaktest.AfterTest(t)() + th, cleanup := newTestHelper(t) + defer cleanup() + + // Inject our test environment into schedule control execution via testing knobs. + th.cfg.TestingKnobs.(*TestingKnobs).JobSchedulerEnv = th.env + + t.Run("non-existent", func(t *testing.T) { + for _, command := range []string{ + "PAUSE SCHEDULE 123", + "PAUSE SCHEDULES SELECT 123", + "RESUME SCHEDULE 123", + "RESUME SCHEDULES SELECT schedule_id FROM system.scheduled_jobs", + "DROP SCHEDULE 123", + "DROP SCHEDULES SELECT schedule_id FROM system.scheduled_jobs", + } { + t.Run(command, func(t *testing.T) { + th.sqlDB.ExecRowsAffected(t, 0, command) + }) + } + }) + + ctx := context.Background() + + var recurringNever string + + makeSchedule := func(name string, cron string) int64 { + schedule := th.newScheduledJob(t, name, "sql") + if cron != "" { + require.NoError(t, schedule.SetSchedule(cron)) + } + require.NoError(t, schedule.Create(ctx, th.cfg.InternalExecutor, nil)) + return schedule.ScheduleID() + } + + t.Run("pause-one-schedule", func(t *testing.T) { + scheduleID := makeSchedule("one-schedule", "@daily") + th.sqlDB.Exec(t, "PAUSE SCHEDULE $1", scheduleID) + require.True(t, th.loadSchedule(t, scheduleID).IsPaused()) + }) + + t.Run("pause-one-off-schedule", func(t *testing.T) { + scheduleID := makeSchedule("one-schedule", recurringNever) + th.sqlDB.Exec(t, "PAUSE SCHEDULE $1", scheduleID) + require.True(t, th.loadSchedule(t, scheduleID).IsPaused()) + }) + + t.Run("cannot-resume-one-off-schedule", func(t *testing.T) { + schedule := th.newScheduledJob(t, "test schedule", "select 42") + require.NoError(t, schedule.Create(ctx, th.cfg.InternalExecutor, nil)) + + th.sqlDB.ExpectErr(t, "cannot set next run for schedule", + "RESUME SCHEDULE $1", schedule.ScheduleID()) + }) + + t.Run("pause-and-resume-one-schedule", func(t *testing.T) { + scheduleID := makeSchedule("one-schedule", "@daily") + th.sqlDB.Exec(t, "PAUSE SCHEDULE $1", scheduleID) + require.True(t, th.loadSchedule(t, scheduleID).IsPaused()) + th.sqlDB.Exec(t, "RESUME SCHEDULE $1", scheduleID) + + schedule := th.loadSchedule(t, scheduleID) + require.False(t, schedule.IsPaused()) + }) + + t.Run("pause-resume-and-drop-many-schedules", func(t *testing.T) { + var scheduleIDs []int64 + for i := 0; i < 10; i++ { + scheduleIDs = append( + scheduleIDs, + makeSchedule(fmt.Sprintf("pause-resume-many-%d", i), "@daily"), + ) + } + + querySchedules := "SELECT schedule_id FROM " + th.env.ScheduledJobsTableName() + + " WHERE schedule_name LIKE 'pause-resume-many-%'" + + th.sqlDB.Exec(t, "PAUSE SCHEDULES "+querySchedules) + + for _, scheduleID := range scheduleIDs { + require.True(t, th.loadSchedule(t, scheduleID).IsPaused()) + } + + th.sqlDB.Exec(t, "RESUME SCHEDULES "+querySchedules) + + for _, scheduleID := range scheduleIDs { + require.False(t, th.loadSchedule(t, scheduleID).IsPaused()) + } + + th.sqlDB.Exec(t, "DROP SCHEDULES "+querySchedules) + require.Equal(t, 0, len(th.sqlDB.QueryStr(t, querySchedules))) + }) +} diff --git a/pkg/jobs/scheduled_job.go b/pkg/jobs/scheduled_job.go index 9476773f029d..09f6f22a5804 100644 --- a/pkg/jobs/scheduled_job.go +++ b/pkg/jobs/scheduled_job.go @@ -126,6 +126,10 @@ func (j *ScheduledJob) HasRecurringSchedule() bool { // ScheduleNextRun updates next run based on job schedule. func (j *ScheduledJob) ScheduleNextRun() error { + if !j.HasRecurringSchedule() { + return errors.Newf( + "cannot set next run for schedule %d (empty schedule)", j.rec.ScheduleID) + } expr, err := cronexpr.Parse(j.rec.ScheduleExpr) if err != nil { return errors.Wrapf(err, "parsing schedule expression: %q", j.rec.ScheduleExpr) diff --git a/pkg/jobs/testutils_test.go b/pkg/jobs/testutils_test.go index 8c1e8e245ba4..e51d167cb19f 100644 --- a/pkg/jobs/testutils_test.go +++ b/pkg/jobs/testutils_test.go @@ -22,6 +22,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/scheduledjobs" "github.com/cockroachdb/cockroach/pkg/security" "github.com/cockroachdb/cockroach/pkg/settings" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" "github.com/cockroachdb/cockroach/pkg/sql/sqlutil" "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" @@ -45,13 +46,12 @@ type testHelper struct { // is disabled by this test helper. // If you want to run daemon, invoke it directly. func newTestHelper(t *testing.T) (*testHelper, func()) { - s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{ - Knobs: base.TestingKnobs{ - JobsTestingKnobs: &TestingKnobs{ - TakeOverJobsScheduling: func(_ func(ctx context.Context, maxSchedules int64, txn *kv.Txn) error) { - }, - }, + knobs := &TestingKnobs{ + TakeOverJobsScheduling: func(_ func(ctx context.Context, maxSchedules int64, txn *kv.Txn) error) { }, + } + s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{ + Knobs: base.TestingKnobs{JobsTestingKnobs: knobs}, }) sqlDB := sqlutils.MakeSQLRunner(db) @@ -69,6 +69,7 @@ func newTestHelper(t *testing.T) (*testHelper, func()) { Settings: s.ClusterSettings(), InternalExecutor: s.InternalExecutor().(sqlutil.InternalExecutor), DB: kvDB, + TestingKnobs: knobs, }, sqlDB: sqlDB, }, func() { @@ -129,3 +130,21 @@ func registerScopedScheduledJobExecutor(name string, ex ScheduledJobExecutor) fu delete(registeredExecutorFactories, name) } } + +// addFakeJob adds a fake job associated with the specified scheduleID. +// Returns the id of the newly created job. +func addFakeJob(t *testing.T, h *testHelper, scheduleID int64, status Status, txn *kv.Txn) int64 { + payload := []byte("fake payload") + datums, err := h.cfg.InternalExecutor.QueryRowEx(context.Background(), "fake-job", txn, + sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, + fmt.Sprintf(` +INSERT INTO %s (created_by_type, created_by_id, status, payload) +VALUES ($1, $2, $3, $4) +RETURNING id`, + h.env.SystemJobsTableName(), + ), + CreatedByScheduledJobs, scheduleID, status, payload, + ) + require.NoError(t, err) + return int64(tree.MustBeDInt(datums[0])) +} diff --git a/pkg/server/server_sql.go b/pkg/server/server_sql.go index 314163e3398e..9b1758df720d 100644 --- a/pkg/server/server_sql.go +++ b/pkg/server/server_sql.go @@ -344,7 +344,9 @@ func newSQLServer(ctx context.Context, cfg sqlServerArgs) (*sqlServer, error) { if distSQLTestingKnobs := cfg.TestingKnobs.DistSQL; distSQLTestingKnobs != nil { distSQLCfg.TestingKnobs = *distSQLTestingKnobs.(*execinfra.TestingKnobs) } - + if cfg.TestingKnobs.JobsTestingKnobs != nil { + distSQLCfg.TestingKnobs.JobsTestingKnobs = cfg.TestingKnobs.JobsTestingKnobs + } distSQLServer := distsql.NewServer(ctx, distSQLCfg) execinfrapb.RegisterDistSQLServer(cfg.grpcServer, distSQLServer) @@ -499,7 +501,6 @@ func newSQLServer(ctx context.Context, cfg sqlServerArgs) (*sqlServer, error) { if tenantKnobs := cfg.TestingKnobs.TenantTestingKnobs; tenantKnobs != nil { execCfg.TenantTestingKnobs = tenantKnobs.(*sql.TenantTestingKnobs) } - distSQLCfg.TestingKnobs.JobsTestingKnobs = cfg.TestingKnobs.JobsTestingKnobs statsRefresher := stats.MakeRefresher( cfg.Settings, diff --git a/pkg/sql/control_schedules.go b/pkg/sql/control_schedules.go new file mode 100644 index 000000000000..8ac1408d01d8 --- /dev/null +++ b/pkg/sql/control_schedules.go @@ -0,0 +1,156 @@ +// Copyright 2020 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 sql + +import ( + "context" + "fmt" + + "github.com/cockroachdb/cockroach/pkg/jobs" + "github.com/cockroachdb/cockroach/pkg/scheduledjobs" + "github.com/cockroachdb/cockroach/pkg/security" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" + "github.com/cockroachdb/errors" +) + +type controlSchedulesNode struct { + rows planNode + command tree.ScheduleCommand + numRows int +} + +// FastPathResults implements the planNodeFastPath interface. +func (n *controlSchedulesNode) FastPathResults() (int, bool) { + return n.numRows, true +} + +// jobSchedulerEnv returns JobSchedulerEnv. +func jobSchedulerEnv(params runParams) scheduledjobs.JobSchedulerEnv { + if knobs, ok := params.ExecCfg().DistSQLSrv.TestingKnobs.JobsTestingKnobs.(*jobs.TestingKnobs); ok { + if knobs.JobSchedulerEnv != nil { + return knobs.JobSchedulerEnv + } + } + return scheduledjobs.ProdJobSchedulerEnv +} + +// loadSchedule loads schedule information. +func loadSchedule(params runParams, scheduleID tree.Datum) (*jobs.ScheduledJob, error) { + env := jobSchedulerEnv(params) + schedule := jobs.NewScheduledJob(env) + + // Load schedule expression. This is needed for resume command, but we + // also use this query to check for the schedule existence. + datums, cols, err := params.ExecCfg().InternalExecutor.QueryWithCols( + params.ctx, + "load-schedule", + params.EvalContext().Txn, sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, + fmt.Sprintf( + "SELECT schedule_id, schedule_expr FROM %s WHERE schedule_id = $1", + env.ScheduledJobsTableName(), + ), + scheduleID) + if err != nil { + return nil, err + } + + // Not an error if schedule does not exist. + if len(datums) != 1 { + return nil, nil + } + + if err := schedule.InitFromDatums(datums[0], cols); err != nil { + return nil, err + } + return schedule, nil +} + +// updateSchedule executes update for the schedule. +func updateSchedule(params runParams, schedule *jobs.ScheduledJob) error { + return schedule.Update( + params.ctx, + params.ExecCfg().InternalExecutor, + params.EvalContext().Txn, + ) +} + +// deleteSchedule deletes specified schedule. +func deleteSchedule(params runParams, scheduleID int64) error { + env := jobSchedulerEnv(params) + _, err := params.ExecCfg().InternalExecutor.ExecEx( + params.ctx, + "delete-schedule", + params.EvalContext().Txn, + sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser}, + fmt.Sprintf( + "DELETE FROM %s WHERE schedule_id = $1", + env.ScheduledJobsTableName(), + ), + scheduleID, + ) + return err +} + +// startExec implements planNode interface. +func (n *controlSchedulesNode) startExec(params runParams) error { + for { + ok, err := n.rows.Next(params) + if err != nil { + return err + } + if !ok { + break + } + + schedule, err := loadSchedule(params, n.rows.Values()[0]) + if err != nil { + return err + } + + if schedule == nil { + continue // not an error if schedule does not exist + } + + switch n.command { + case tree.PauseSchedule: + schedule.Pause("operator paused") + err = updateSchedule(params, schedule) + case tree.ResumeSchedule: + err = schedule.ScheduleNextRun() + if err == nil { + err = updateSchedule(params, schedule) + } + case tree.DropSchedule: + err = deleteSchedule(params, schedule.ScheduleID()) + default: + err = errors.AssertionFailedf("unhandled command %s", n.command) + } + + if err != nil { + return err + } + n.numRows++ + } + + return nil +} + +// Next implements planNode interface. +func (*controlSchedulesNode) Next(runParams) (bool, error) { return false, nil } + +// Values implements planNode interface. +func (*controlSchedulesNode) Values() tree.Datums { return nil } + +// Close implements planNode interface. +func (n *controlSchedulesNode) Close(ctx context.Context) { + n.rows.Close(ctx) +} diff --git a/pkg/sql/distsql_spec_exec_factory.go b/pkg/sql/distsql_spec_exec_factory.go index c57a15b9c1eb..d93da9b28eae 100644 --- a/pkg/sql/distsql_spec_exec_factory.go +++ b/pkg/sql/distsql_spec_exec_factory.go @@ -911,6 +911,12 @@ func (e *distSQLSpecExecFactory) ConstructControlJobs( return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: control jobs") } +func (e *distSQLSpecExecFactory) ConstructControlSchedules( + command tree.ScheduleCommand, input exec.Node, +) (exec.Node, error) { + return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: control jobs") +} + func (e *distSQLSpecExecFactory) ConstructCancelQueries( input exec.Node, ifExists bool, ) (exec.Node, error) { diff --git a/pkg/sql/opt/exec/execbuilder/relational.go b/pkg/sql/opt/exec/execbuilder/relational.go index cf9582b4265c..338ffb74e5bb 100644 --- a/pkg/sql/opt/exec/execbuilder/relational.go +++ b/pkg/sql/opt/exec/execbuilder/relational.go @@ -293,6 +293,9 @@ func (b *Builder) buildRelational(e memo.RelExpr) (execPlan, error) { case *memo.ControlJobsExpr: ep, err = b.buildControlJobs(t) + case *memo.ControlSchedulesExpr: + ep, err = b.buildControlSchedules(t) + case *memo.CancelQueriesExpr: ep, err = b.buildCancelQueries(t) diff --git a/pkg/sql/opt/exec/execbuilder/statement.go b/pkg/sql/opt/exec/execbuilder/statement.go index c42ffff406d3..e7ba4c3d2d5e 100644 --- a/pkg/sql/opt/exec/execbuilder/statement.go +++ b/pkg/sql/opt/exec/execbuilder/statement.go @@ -227,6 +227,22 @@ func (b *Builder) buildControlJobs(ctl *memo.ControlJobsExpr) (execPlan, error) return execPlan{root: node}, nil } +func (b *Builder) buildControlSchedules(ctl *memo.ControlSchedulesExpr) (execPlan, error) { + input, err := b.buildRelational(ctl.Input) + if err != nil { + return execPlan{}, err + } + node, err := b.factory.ConstructControlSchedules( + ctl.Command, + input.root, + ) + if err != nil { + return execPlan{}, err + } + // ControlSchedules returns no columns. + return execPlan{root: node}, nil +} + func (b *Builder) buildCancelQueries(cancel *memo.CancelQueriesExpr) (execPlan, error) { input, err := b.buildRelational(cancel.Input) if err != nil { diff --git a/pkg/sql/opt/exec/factory.go b/pkg/sql/opt/exec/factory.go index a098ef6d9c2f..0eb413c9bc01 100644 --- a/pkg/sql/opt/exec/factory.go +++ b/pkg/sql/opt/exec/factory.go @@ -578,6 +578,10 @@ type Factory interface { // JOBS. ConstructControlJobs(command tree.JobCommand, input Node) (Node, error) + // ConstructControlSchedules creates a node that implements + // PAUSE/CANCEL/DROP SCHEDULES. + ConstructControlSchedules(command tree.ScheduleCommand, input Node) (Node, error) + // ConstructCancelQueries creates a node that implements CANCEL QUERIES. ConstructCancelQueries(input Node, ifExists bool) (Node, error) diff --git a/pkg/sql/opt/exec/stub_factory.go b/pkg/sql/opt/exec/stub_factory.go index bf95d914d856..964ce6b58e90 100644 --- a/pkg/sql/opt/exec/stub_factory.go +++ b/pkg/sql/opt/exec/stub_factory.go @@ -422,6 +422,13 @@ func (StubFactory) ConstructControlJobs(command tree.JobCommand, input Node) (No return struct{}{}, nil } +// ConstructControlSchedules is part of the exec.Factory interface. +func (StubFactory) ConstructControlSchedules( + command tree.ScheduleCommand, input Node, +) (Node, error) { + return struct{}{}, nil +} + // ConstructCancelQueries is part of the exec.Factory interface. func (StubFactory) ConstructCancelQueries(input Node, ifExists bool) (Node, error) { return struct{}{}, nil diff --git a/pkg/sql/opt/memo/interner.go b/pkg/sql/opt/memo/interner.go index 2b46818b7bf5..c7584a20f28e 100644 --- a/pkg/sql/opt/memo/interner.go +++ b/pkg/sql/opt/memo/interner.go @@ -517,6 +517,10 @@ func (h *hasher) HashJobCommand(val tree.JobCommand) { h.HashInt(int(val)) } +func (h *hasher) HashScheduleCommand(val tree.ScheduleCommand) { + h.HashInt(int(val)) +} + func (h *hasher) HashIndexOrdinal(val cat.IndexOrdinal) { h.HashInt(val) } @@ -877,6 +881,10 @@ func (h *hasher) IsJobCommandEqual(l, r tree.JobCommand) bool { return l == r } +func (h *hasher) IsScheduleCommandEqual(l, r tree.ScheduleCommand) bool { + return l == r +} + func (h *hasher) IsIndexOrdinalEqual(l, r cat.IndexOrdinal) bool { return l == r } diff --git a/pkg/sql/opt/memo/logical_props_builder.go b/pkg/sql/opt/memo/logical_props_builder.go index 94f2cec62a09..314a3172f6a9 100644 --- a/pkg/sql/opt/memo/logical_props_builder.go +++ b/pkg/sql/opt/memo/logical_props_builder.go @@ -928,6 +928,12 @@ func (b *logicalPropsBuilder) buildControlJobsProps(ctl *ControlJobsExpr, rel *p b.buildBasicProps(ctl, opt.ColList{}, rel) } +func (b *logicalPropsBuilder) buildControlSchedulesProps( + ctl *ControlSchedulesExpr, rel *props.Relational, +) { + b.buildBasicProps(ctl, opt.ColList{}, rel) +} + func (b *logicalPropsBuilder) buildCancelQueriesProps( cancel *CancelQueriesExpr, rel *props.Relational, ) { diff --git a/pkg/sql/opt/ops/statement.opt b/pkg/sql/opt/ops/statement.opt index 85006850b67a..c856217a08f6 100644 --- a/pkg/sql/opt/ops/statement.opt +++ b/pkg/sql/opt/ops/statement.opt @@ -207,6 +207,22 @@ define ControlJobsPrivate { Command JobCommand } +# ControlSchedules represents a `PAUSE/CANCEL/RESUME SCHEDULES` statement. +[Relational] +define ControlSchedules { + # The input expression returns schedule IDs (as integers). + Input RelExpr + _ ControlSchedulesPrivate +} + +[Private] +define ControlSchedulesPrivate { + # Props stores the required physical properties for the input + # expression. + Props PhysProps + Command ScheduleCommand +} + # CancelQueries represents a `CANCEL QUERIES` statement. [Relational] define CancelQueries { diff --git a/pkg/sql/opt/optbuilder/builder.go b/pkg/sql/opt/optbuilder/builder.go index ddcdb9ac5234..114b773ea36d 100644 --- a/pkg/sql/opt/optbuilder/builder.go +++ b/pkg/sql/opt/optbuilder/builder.go @@ -236,7 +236,7 @@ func (b *Builder) buildStmt( switch stmt := stmt.(type) { case *tree.Delete, *tree.Insert, *tree.Update, *tree.CreateTable, *tree.CreateView, *tree.Split, *tree.Unsplit, *tree.Relocate, - *tree.ControlJobs, *tree.CancelQueries, *tree.CancelSessions: + *tree.ControlJobs, *tree.ControlSchedules, *tree.CancelQueries, *tree.CancelSessions: panic(pgerror.Newf( pgcode.Syntax, "%s cannot be used inside a view definition", stmt.StatementTag(), )) @@ -289,6 +289,9 @@ func (b *Builder) buildStmt( case *tree.ControlJobs: return b.buildControlJobs(stmt, inScope) + case *tree.ControlSchedules: + return b.buildControlSchedules(stmt, inScope) + case *tree.CancelQueries: return b.buildCancelQueries(stmt, inScope) diff --git a/pkg/sql/opt/optbuilder/misc_statements.go b/pkg/sql/opt/optbuilder/misc_statements.go index 679793f04b48..494a5e4d135b 100644 --- a/pkg/sql/opt/optbuilder/misc_statements.go +++ b/pkg/sql/opt/optbuilder/misc_statements.go @@ -96,3 +96,35 @@ func (b *Builder) buildCancelSessions(n *tree.CancelSessions, inScope *scope) (o ) return outScope } + +func (b *Builder) buildControlSchedules( + n *tree.ControlSchedules, inScope *scope, +) (outScope *scope) { + if err := b.catalog.RequireAdminRole(b.ctx, n.StatementTag()); err != nil { + panic(err) + } + + // We don't allow the input statement to reference outer columns, so we + // pass a "blank" scope rather than inScope. + emptyScope := b.allocScope() + colTypes := []*types.T{types.Int} + inputScope := b.buildStmt(n.Schedules, colTypes, emptyScope) + + checkInputColumns( + fmt.Sprintf("%s SCHEDULES", n.Command), + inputScope, + []string{"schedule_id"}, + colTypes, + 1, /* minPrefix */ + ) + + outScope = inScope.push() + outScope.expr = b.factory.ConstructControlSchedules( + inputScope.expr.(memo.RelExpr), + &memo.ControlSchedulesPrivate{ + Props: inputScope.makePhysicalProps(), + Command: n.Command, + }, + ) + return outScope +} diff --git a/pkg/sql/opt/optgen/cmd/optgen/metadata.go b/pkg/sql/opt/optgen/cmd/optgen/metadata.go index f3ac46e66eb3..b5a9260abf05 100644 --- a/pkg/sql/opt/optgen/cmd/optgen/metadata.go +++ b/pkg/sql/opt/optgen/cmd/optgen/metadata.go @@ -230,6 +230,7 @@ func newMetadata(compiled *lang.CompiledExpr, pkg string) *metadata { "JoinMultiplicity": {fullName: "props.JoinMultiplicity"}, "OpaqueMetadata": {fullName: "opt.OpaqueMetadata", isInterface: true}, "JobCommand": {fullName: "tree.JobCommand", passByVal: true}, + "ScheduleCommand": {fullName: "tree.ScheduleCommand", passByVal: true}, "IndexOrdinal": {fullName: "cat.IndexOrdinal", passByVal: true}, "ViewDeps": {fullName: "opt.ViewDeps", passByVal: true}, "LockingItem": {fullName: "tree.LockingItem", isPointer: true}, diff --git a/pkg/sql/opt_exec_factory.go b/pkg/sql/opt_exec_factory.go index ff3813d7aafe..dc1672e78b32 100644 --- a/pkg/sql/opt_exec_factory.go +++ b/pkg/sql/opt_exec_factory.go @@ -1753,6 +1753,16 @@ func (ef *execFactory) ConstructControlJobs( }, nil } +// ConstructControlJobs is part of the exec.Factory interface. +func (ef *execFactory) ConstructControlSchedules( + command tree.ScheduleCommand, input exec.Node, +) (exec.Node, error) { + return &controlSchedulesNode{ + rows: input.(planNode), + command: command, + }, nil +} + // ConstructCancelQueries is part of the exec.Factory interface. func (ef *execFactory) ConstructCancelQueries(input exec.Node, ifExists bool) (exec.Node, error) { return &cancelQueriesNode{ diff --git a/pkg/sql/parser/help_test.go b/pkg/sql/parser/help_test.go index 4f01dd2b3354..b6d361970ad8 100644 --- a/pkg/sql/parser/help_test.go +++ b/pkg/sql/parser/help_test.go @@ -179,6 +179,9 @@ func TestContextualHelp(t *testing.T) { {`DROP VIEW IF ??`, `DROP VIEW`}, {`DROP VIEW IF EXISTS blih, bloh ??`, `DROP VIEW`}, + {`DROP SCHEDULE ???`, `DROP SCHEDULES`}, + {`DROP SCHEDULES ???`, `DROP SCHEDULES`}, + {`EXPLAIN (??`, `EXPLAIN`}, {`EXPLAIN SELECT 1 ??`, `SELECT`}, {`EXPLAIN INSERT INTO xx (SELECT 1) ??`, `INSERT`}, @@ -227,9 +230,17 @@ func TestContextualHelp(t *testing.T) { {`GRANT ALL ON foo TO ??`, `GRANT`}, {`GRANT ALL ON foo TO bar ??`, `GRANT`}, - {`PAUSE ??`, `PAUSE JOBS`}, - - {`RESUME ??`, `RESUME JOBS`}, + {`PAUSE ??`, `PAUSE`}, + {`PAUSE JOB ??`, `PAUSE JOBS`}, + {`PAUSE JOBS ??`, `PAUSE JOBS`}, + {`PAUSE SCHEDULE ??`, `PAUSE SCHEDULES`}, + {`PAUSE SCHEDULES ??`, `PAUSE SCHEDULES`}, + + {`RESUME ??`, `RESUME`}, + {`RESUME JOB ??`, `RESUME JOBS`}, + {`RESUME JOBS ??`, `RESUME JOBS`}, + {`RESUME SCHEDULE ??`, `RESUME SCHEDULES`}, + {`RESUME SCHEDULES ??`, `RESUME SCHEDULES`}, {`REVOKE ALL ??`, `REVOKE`}, {`REVOKE ALL ON foo FROM ??`, `REVOKE`}, diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 25ef3c590260..e39d86ae7a84 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -435,6 +435,12 @@ func TestParse(t *testing.T) { {`EXPLAIN RESUME JOBS SELECT a`}, {`PAUSE JOBS SELECT a`}, {`EXPLAIN PAUSE JOBS SELECT a`}, + {`PAUSE SCHEDULES SELECT a`}, + {`EXPLAIN PAUSE SCHEDULES SELECT a`}, + {`RESUME SCHEDULES SELECT a`}, + {`EXPLAIN RESUME SCHEDULES SELECT a`}, + {`DROP SCHEDULES SELECT a`}, + {`EXPLAIN DROP SCHEDULES SELECT a`}, {`SHOW JOBS SELECT a`}, {`EXPLAIN SHOW JOBS SELECT a`}, {`SHOW JOBS WHEN COMPLETE SELECT a`}, @@ -2020,6 +2026,12 @@ $function$`, {`EXPLAIN RESUME JOB a`, `EXPLAIN RESUME JOBS VALUES (a)`}, {`PAUSE JOB a`, `PAUSE JOBS VALUES (a)`}, {`EXPLAIN PAUSE JOB a`, `EXPLAIN PAUSE JOBS VALUES (a)`}, + {`PAUSE SCHEDULE a`, `PAUSE SCHEDULES VALUES (a)`}, + {`EXPLAIN PAUSE SCHEDULE a`, `EXPLAIN PAUSE SCHEDULES VALUES (a)`}, + {`RESUME SCHEDULE a`, `RESUME SCHEDULES VALUES (a)`}, + {`EXPLAIN RESUME SCHEDULE a`, `EXPLAIN RESUME SCHEDULES VALUES (a)`}, + {`DROP SCHEDULE a`, `DROP SCHEDULES VALUES (a)`}, + {`EXPLAIN DROP SCHEDULE a`, `EXPLAIN DROP SCHEDULES VALUES (a)`}, {`SHOW JOB a`, `SHOW JOBS VALUES (a)`}, {`EXPLAIN SHOW JOB a`, `EXPLAIN SHOW JOBS VALUES (a)`}, {`SHOW JOB WHEN COMPLETE a`, `SHOW JOBS WHEN COMPLETE VALUES (a)`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 82270289d89b..2f28c96ad8ac 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -623,7 +623,7 @@ func (u *sqlSymUnion) alterTypeAddValuePlacement() *tree.AlterTypeAddValuePlacem %token RELEASE RESET RESTORE RESTRICT RESUME RETURNING RETRY REVISION_HISTORY REVOKE RIGHT %token ROLE ROLES ROLLBACK ROLLUP ROW ROWS RSHIFT RULE -%token SAVEPOINT SCATTER SCHEDULE SCHEMA SCHEMAS SCRUB SEARCH SECOND SELECT SEQUENCE SEQUENCES +%token SAVEPOINT SCATTER SCHEDULE SCHEDULES SCHEMA SCHEMAS SCRUB SEARCH SECOND SELECT SEQUENCE SEQUENCES %token SERIALIZABLE SERVER SESSION SESSIONS SESSION_USER SET SETTING SETTINGS %token SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SMALLSERIAL SNAPSHOT SOME SPLIT SQL @@ -780,10 +780,11 @@ func (u *sqlSymUnion) alterTypeAddValuePlacement() *tree.AlterTypeAddValuePlacem %type grant_stmt %type insert_stmt %type import_stmt -%type pause_stmt +%type pause_stmt pause_jobs_stmt pause_schedules_stmt %type release_stmt %type reset_stmt reset_session_stmt reset_csetting_stmt -%type resume_stmt +%type resume_stmt resume_jobs_stmt resume_schedules_stmt +%type drop_schedule_stmt %type restore_stmt %type partitioned_backup %type <[]tree.PartitionedBackup> partitioned_backup_list @@ -2915,6 +2916,7 @@ discard_stmt: drop_stmt: drop_ddl_stmt // help texts in sub-rule | drop_role_stmt // EXTEND WITH HELP: DROP ROLE +| drop_schedule_stmt // EXTEND WITH HELP: DROP SCHEDULES | drop_unsupported {} | DROP error // SHOW HELP: DROP @@ -3181,22 +3183,22 @@ explain_stmt: | EXPLAIN '(' error // SHOW HELP: EXPLAIN preparable_stmt: - alter_stmt // help texts in sub-rule -| backup_stmt // EXTEND WITH HELP: BACKUP -| cancel_stmt // help texts in sub-rule -| create_stmt // help texts in sub-rule -| delete_stmt // EXTEND WITH HELP: DELETE -| drop_stmt // help texts in sub-rule -| explain_stmt // EXTEND WITH HELP: EXPLAIN -| import_stmt // EXTEND WITH HELP: IMPORT -| insert_stmt // EXTEND WITH HELP: INSERT -| pause_stmt // EXTEND WITH HELP: PAUSE JOBS -| reset_stmt // help texts in sub-rule -| restore_stmt // EXTEND WITH HELP: RESTORE -| resume_stmt // EXTEND WITH HELP: RESUME JOBS -| export_stmt // EXTEND WITH HELP: EXPORT -| scrub_stmt // help texts in sub-rule -| select_stmt // help texts in sub-rule + alter_stmt // help texts in sub-rule +| backup_stmt // EXTEND WITH HELP: BACKUP +| cancel_stmt // help texts in sub-rule +| create_stmt // help texts in sub-rule +| delete_stmt // EXTEND WITH HELP: DELETE +| drop_stmt // help texts in sub-rule +| explain_stmt // EXTEND WITH HELP: EXPLAIN +| import_stmt // EXTEND WITH HELP: IMPORT +| insert_stmt // EXTEND WITH HELP: INSERT +| pause_stmt // help texts in sub-rule +| reset_stmt // help texts in sub-rule +| restore_stmt // EXTEND WITH HELP: RESTORE +| resume_stmt // help texts in sub-rule +| export_stmt // EXTEND WITH HELP: EXPORT +| scrub_stmt // help texts in sub-rule +| select_stmt // help texts in sub-rule { $$.val = $1.slct() } @@ -4739,13 +4741,38 @@ for_grantee_clause: $$.val = tree.NameList(nil) } + +// %Help: PAUSE +// %Category: Misc +// %Text: +// +// Pause various background tasks and activities. +// +// PAUSE JOBS, PAUSE SCHEDULES +pause_stmt: + pause_jobs_stmt // EXTEND WITH HELP: PAUSE JOBS +| pause_schedules_stmt // EXTEND WITH HELP: PAUSE SCHEDULES +| PAUSE error // SHOW HELP: PAUSE + +// %Help: RESUME +// %Category: Misc +// %Text: +// +// Resume various background tasks and activities. +// +// RESUME JOBS, RESUME SCHEDULES +resume_stmt: + resume_jobs_stmt // EXTEND WITH HELP: RESUME JOBS +| resume_schedules_stmt // EXTEND WITH HELP: RESUME SCHEDULES +| RESUME error // SHOW HELP: RESUME + // %Help: PAUSE JOBS - pause background jobs // %Category: Misc // %Text: // PAUSE JOBS // PAUSE JOB // %SeeAlso: SHOW JOBS, CANCEL JOBS, RESUME JOBS -pause_stmt: +pause_jobs_stmt: PAUSE JOB a_expr { $$.val = &tree.ControlJobs{ @@ -4755,11 +4782,40 @@ pause_stmt: Command: tree.PauseJob, } } +| PAUSE JOB error // SHOW HELP: PAUSE JOBS | PAUSE JOBS select_stmt { $$.val = &tree.ControlJobs{Jobs: $3.slct(), Command: tree.PauseJob} } -| PAUSE error // SHOW HELP: PAUSE JOBS +| PAUSE JOBS error // SHOW HELP: PAUSE JOBS + +// %Help: PAUSE SCHEDULES - pause scheduled jobs +// %Category: Misc +// %Text: +// PAUSE SCHEDULES +// select clause: select statement returning schedule id to pause. +// PAUSE SCHEDULE +// %SeeAlso: RESUME SCHEDULES, SHOW JOBS, CANCEL JOBS +pause_schedules_stmt: + PAUSE SCHEDULE a_expr + { + $$.val = &tree.ControlSchedules{ + Schedules: &tree.Select{ + Select: &tree.ValuesClause{Rows: []tree.Exprs{tree.Exprs{$3.expr()}}}, + }, + Command: tree.PauseSchedule, + } + } +| PAUSE SCHEDULE error // SHOW HELP: PAUSE SCHEDULES +| PAUSE SCHEDULES select_stmt + { + $$.val = &tree.ControlSchedules{ + Schedules: $3.slct(), + Command: tree.PauseSchedule, + } + } +| with_clause PAUSE SCHEDULES +| PAUSE SCHEDULES error // SHOW HELP: PAUSE SCHEDULES // %Help: CREATE SCHEMA - create a new schema // %Category: DDL @@ -6183,7 +6239,7 @@ release_stmt: // RESUME JOBS // RESUME JOB // %SeeAlso: SHOW JOBS, CANCEL JOBS, PAUSE JOBS -resume_stmt: +resume_jobs_stmt: RESUME JOB a_expr { $$.val = &tree.ControlJobs{ @@ -6193,11 +6249,70 @@ resume_stmt: Command: tree.ResumeJob, } } +| RESUME JOB error // SHOW HELP: RESUME JOBS | RESUME JOBS select_stmt { $$.val = &tree.ControlJobs{Jobs: $3.slct(), Command: tree.ResumeJob} } -| RESUME error // SHOW HELP: RESUME JOBS +| RESUME JOBS error // SHOW HELP: RESUME JOBS + +// %Help: RESUME SCHEDULES - resume executing scheduled jobs +// %Category: Misc +// %Text: +// RESUME SCHEDULES +// selectclause: select statement returning schedule IDs to resume. +// +// RESUME SCHEDULES +// +// %SeeAlso: PAUSE SCHEDULES, SHOW JOBS, RESUME JOBS +resume_schedules_stmt: + RESUME SCHEDULE a_expr + { + $$.val = &tree.ControlSchedules{ + Schedules: &tree.Select{ + Select: &tree.ValuesClause{Rows: []tree.Exprs{tree.Exprs{$3.expr()}}}, + }, + Command: tree.ResumeSchedule, + } + } +| RESUME SCHEDULE error // SHOW HELP: RESUME SCHEDULES +| RESUME SCHEDULES select_stmt + { + $$.val = &tree.ControlSchedules{ + Schedules: $3.slct(), + Command: tree.ResumeSchedule, + } + } +| RESUME SCHEDULES error // SHOW HELP: RESUME SCHEDULES + +// %Help: DROP SCHEDULES - destroy specified schedules +// %Category: Misc +// %Text: +// DROP SCHEDULES +// selectclause: select statement returning schedule IDs to resume. +// +// DROP SCHEDULES +// +// %SeeAlso: PAUSE SCHEDULES, SHOW JOBS, CANCEL JOBS +drop_schedule_stmt: + DROP SCHEDULE a_expr + { + $$.val = &tree.ControlSchedules{ + Schedules: &tree.Select{ + Select: &tree.ValuesClause{Rows: []tree.Exprs{tree.Exprs{$3.expr()}}}, + }, + Command: tree.DropSchedule, + } + } +| DROP SCHEDULE error // SHOW HELP: DROP SCHEDULES +| DROP SCHEDULES select_stmt + { + $$.val = &tree.ControlSchedules{ + Schedules: $3.slct(), + Command: tree.DropSchedule, + } + } +| DROP SCHEDULES error // SHOW HELP: DROP SCHEDULES // %Help: SAVEPOINT - start a sub-transaction // %Category: Txn @@ -10684,6 +10799,7 @@ unreserved_keyword: | ROWS | RULE | SCHEDULE +| SCHEDULES | SETTING | SETTINGS | STATUS diff --git a/pkg/sql/plan.go b/pkg/sql/plan.go index acadff186936..93112c019788 100644 --- a/pkg/sql/plan.go +++ b/pkg/sql/plan.go @@ -213,6 +213,7 @@ var _ planNodeFastPath = &rowCountNode{} var _ planNodeFastPath = &serializeNode{} var _ planNodeFastPath = &setZoneConfigNode{} var _ planNodeFastPath = &controlJobsNode{} +var _ planNodeFastPath = &controlSchedulesNode{} var _ planNodeReadingOwnWrites = &alterIndexNode{} var _ planNodeReadingOwnWrites = &alterSequenceNode{} diff --git a/pkg/sql/sem/tree/run_control.go b/pkg/sql/sem/tree/run_control.go index 9f6de6d90db6..29902b39c5e5 100644 --- a/pkg/sql/sem/tree/run_control.go +++ b/pkg/sql/sem/tree/run_control.go @@ -10,6 +10,8 @@ package tree +import "fmt" + // ControlJobs represents a PAUSE/RESUME/CANCEL JOBS statement. type ControlJobs struct { Jobs *Select @@ -69,3 +71,40 @@ func (node *CancelSessions) Format(ctx *FmtCtx) { } ctx.FormatNode(node.Sessions) } + +// ScheduleCommand determines which type of action to effect on the selected job(s). +type ScheduleCommand int + +// ScheduleCommand values +const ( + PauseSchedule ScheduleCommand = iota + ResumeSchedule + DropSchedule +) + +func (c ScheduleCommand) String() string { + switch c { + case PauseSchedule: + return "PAUSE" + case ResumeSchedule: + return "RESUME" + case DropSchedule: + return "DROP" + default: + panic("unhandled schedule command") + } +} + +// ControlSchedules represents PAUSE/RESUME SCHEDULE statement. +type ControlSchedules struct { + Schedules *Select + Command ScheduleCommand +} + +var _ Statement = &ControlSchedules{} + +// Format implements NodeFormatter interface +func (c *ControlSchedules) Format(ctx *FmtCtx) { + fmt.Fprintf(ctx, "%s SCHEDULES ", c.Command) + c.Schedules.Format(ctx) +} diff --git a/pkg/sql/sem/tree/stmt.go b/pkg/sql/sem/tree/stmt.go index d6e88da49a6f..677f8fae6307 100644 --- a/pkg/sql/sem/tree/stmt.go +++ b/pkg/sql/sem/tree/stmt.go @@ -250,6 +250,14 @@ func (n *ControlJobs) StatementTag() string { return fmt.Sprintf("%s JOBS", JobCommandToStatement[n.Command]) } +// StatementType implements the Statement interface. +func (*ControlSchedules) StatementType() StatementType { return RowsAffected } + +// StatementTag returns a short string identifying the type of statement. +func (c *ControlSchedules) StatementTag() string { + return fmt.Sprintf("%s SCHEDULES", c.Command) +} + // StatementType implements the Statement interface. func (*CancelQueries) StatementType() StatementType { return RowsAffected } @@ -939,6 +947,7 @@ func (n *Analyze) String() string { return AsString(n) } func (n *Backup) String() string { return AsString(n) } func (n *BeginTransaction) String() string { return AsString(n) } func (n *ControlJobs) String() string { return AsString(n) } +func (c *ControlSchedules) String() string { return AsString(c) } func (n *CancelQueries) String() string { return AsString(n) } func (n *CancelSessions) String() string { return AsString(n) } func (n *CannedOptPlan) String() string { return AsString(n) } diff --git a/pkg/sql/sem/tree/walk.go b/pkg/sql/sem/tree/walk.go index 604126910f6c..c6d8861abc51 100644 --- a/pkg/sql/sem/tree/walk.go +++ b/pkg/sql/sem/tree/walk.go @@ -962,6 +962,22 @@ func (stmt *ControlJobs) walkStmt(v Visitor) Statement { return stmt } +// copyNode makes a copy of this Statement without recursing in any child Statements. +func (stmt *ControlSchedules) copyNode() *ControlSchedules { + stmtCopy := *stmt + return &stmtCopy +} + +// walkStmt is part of the walkableStmt interface. +func (stmt *ControlSchedules) walkStmt(v Visitor) Statement { + sel, changed := walkStmt(v, stmt.Schedules) + if changed { + stmt = stmt.copyNode() + stmt.Schedules = sel.(*Select) + } + return stmt +} + // copyNode makes a copy of this Statement without recursing in any child Statements. func (stmt *Import) copyNode() *Import { stmtCopy := *stmt diff --git a/pkg/sql/walk.go b/pkg/sql/walk.go index 8a482c6fe94b..f8624b5bebd8 100644 --- a/pkg/sql/walk.go +++ b/pkg/sql/walk.go @@ -767,6 +767,9 @@ func (v *planVisitor) visitInternal(plan planNode, name string) { case *controlJobsNode: n.rows = v.visit(n.rows) + case *controlSchedulesNode: + n.rows = v.visit(n.rows) + case *setZoneConfigNode: if v.observer.expr != nil { v.metadataExpr(name, "yaml", -1, n.yamlConfig) @@ -924,6 +927,7 @@ var planNodeNames = map[reflect.Type]string{ reflect.TypeOf(&commentOnIndexNode{}): "comment on index", reflect.TypeOf(&commentOnTableNode{}): "comment on table", reflect.TypeOf(&controlJobsNode{}): "control jobs", + reflect.TypeOf(&controlSchedulesNode{}): "control schedules", reflect.TypeOf(&createDatabaseNode{}): "create database", reflect.TypeOf(&createIndexNode{}): "create index", reflect.TypeOf(&createSequenceNode{}): "create sequence",