diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index ee85c05a00d..f2a69bdbb25 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -3793,8 +3793,8 @@ is_branching_command(const char *cmd) * Prepare to possibly restore query buffer to its current state * (cf. discard_query_text). * - * We need to remember the length of the query buffer, and the lexer's - * notion of the parenthesis nesting depth. + * We need to remember the length of the query buffer, and assorted + * lexer internal state such as parenthesis nesting depth. */ static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, @@ -3802,8 +3802,8 @@ save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, { if (query_buf) conditional_stack_set_query_len(cstack, query_buf->len); - conditional_stack_set_paren_depth(cstack, - psql_scan_get_paren_depth(scan_state)); + conditional_stack_set_lex_state(cstack, + psql_scan_get_lex_state(scan_state)); } /* @@ -3812,9 +3812,7 @@ save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, * We must discard data that was appended to query_buf during an inactive * \if branch. We don't have to do anything there if there's no query_buf. * - * Also, reset the lexer state to the same paren depth there was before. - * (The rest of its state doesn't need attention, since we could not be - * inside a comment or literal or partial token.) + * Also, reset the lexer's state to what it was before. */ static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, @@ -3828,8 +3826,8 @@ discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, query_buf->len = new_len; query_buf->data[new_len] = '\0'; } - psql_scan_set_paren_depth(scan_state, - conditional_stack_get_paren_depth(cstack)); + psql_scan_set_lex_state(scan_state, + conditional_stack_get_lex_state(cstack)); } /* diff --git a/src/bin/psql/psqlscanslash.h b/src/bin/psql/psqlscanslash.h index d95a6ff2d1e..2ab08d5520b 100644 --- a/src/bin/psql/psqlscanslash.h +++ b/src/bin/psql/psqlscanslash.h @@ -31,9 +31,10 @@ extern char *psql_scan_slash_option(PsqlScanState state, extern void psql_scan_slash_command_end(PsqlScanState state); -extern int psql_scan_get_paren_depth(PsqlScanState state); +extern PsqlScanStateSave *psql_scan_get_lex_state(PsqlScanState state); -extern void psql_scan_set_paren_depth(PsqlScanState state, int depth); +extern void psql_scan_set_lex_state(PsqlScanState state, + const PsqlScanStateSave *lex_state); extern void dequote_downcase_identifier(char *str, bool downcase, int encoding); diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index 2d755752374..837158ae0d3 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -705,22 +705,46 @@ psql_scan_slash_command_end(PsqlScanState state) } /* - * Fetch current paren nesting depth + * Save current lexer state + * + * Relevant parts of the state are returned in a pg_malloc'd struct. + * It is caller's responsibility to free the struct eventually. */ -int -psql_scan_get_paren_depth(PsqlScanState state) +PsqlScanStateSave * +psql_scan_get_lex_state(PsqlScanState state) { - return state->paren_depth; + PsqlScanStateSave *lex_state = pg_malloc_object(PsqlScanStateSave); + StaticAssertDecl(sizeof(lex_state->init_idents) == sizeof(state->init_idents), + "init_idents array lengths must match"); + StaticAssertDecl(sizeof(lex_state->sub_idents) == sizeof(state->sub_idents), + "sub_idents array lengths must match"); + + lex_state->paren_depth = state->paren_depth; + lex_state->begin_depth = state->begin_depth; + lex_state->init_idents_count = state->init_idents_count; + memcpy(lex_state->init_idents, state->init_idents, + sizeof(lex_state->init_idents)); + lex_state->sub_idents_count = state->sub_idents_count; + memcpy(lex_state->sub_idents, state->sub_idents, + sizeof(lex_state->sub_idents)); + return lex_state; } /* - * Set paren nesting depth + * Restore lexer state to what it was when saved */ void -psql_scan_set_paren_depth(PsqlScanState state, int depth) +psql_scan_set_lex_state(PsqlScanState state, + const PsqlScanStateSave *lex_state) { - Assert(depth >= 0); - state->paren_depth = depth; + state->paren_depth = lex_state->paren_depth; + state->begin_depth = lex_state->begin_depth; + state->init_idents_count = lex_state->init_idents_count; + memcpy(state->init_idents, lex_state->init_idents, + sizeof(state->init_idents)); + state->sub_idents_count = lex_state->sub_idents_count; + memcpy(state->sub_idents, lex_state->sub_idents, + sizeof(state->sub_idents)); } /* diff --git a/src/fe_utils/conditional.c b/src/fe_utils/conditional.c index 537c76ed3cd..2d09d21ecb1 100644 --- a/src/fe_utils/conditional.c +++ b/src/fe_utils/conditional.c @@ -56,7 +56,7 @@ conditional_stack_push(ConditionalStack cstack, ifState new_state) p->if_state = new_state; p->query_len = -1; - p->paren_depth = -1; + p->lex_state = NULL; p->next = cstack->head; cstack->head = p; } @@ -73,6 +73,8 @@ conditional_stack_pop(ConditionalStack cstack) if (!p) return false; cstack->head = cstack->head->next; + if (p->lex_state) + free(p->lex_state); free(p); return true; } @@ -167,23 +169,29 @@ conditional_stack_get_query_len(ConditionalStack cstack) } /* - * Save current parenthesis nesting depth in topmost stack entry. + * Save current lexer state in topmost stack entry. + * + * The lexer state is presumed to be a single pg_malloc'd chunk. + * It will be freed automatically when the stack entry is popped. */ void -conditional_stack_set_paren_depth(ConditionalStack cstack, int depth) +conditional_stack_set_lex_state(ConditionalStack cstack, + PsqlScanStateSave *lex_state) { Assert(!conditional_stack_empty(cstack)); - cstack->head->paren_depth = depth; + if (cstack->head->lex_state) /* free old state, if any */ + free(cstack->head->lex_state); + cstack->head->lex_state = lex_state; } /* - * Fetch last-recorded parenthesis nesting depth from topmost stack entry. - * Will return -1 if no stack or it was never saved. + * Fetch last-recorded lexer state from topmost stack entry. + * Will return NULL if no stack or it was never saved. */ -int -conditional_stack_get_paren_depth(ConditionalStack cstack) +PsqlScanStateSave * +conditional_stack_get_lex_state(ConditionalStack cstack) { if (conditional_stack_empty(cstack)) - return -1; - return cstack->head->paren_depth; + return NULL; + return cstack->head->lex_state; } diff --git a/src/include/fe_utils/conditional.h b/src/include/fe_utils/conditional.h index 2ed796b5c50..96efd81a7c8 100644 --- a/src/include/fe_utils/conditional.h +++ b/src/include/fe_utils/conditional.h @@ -49,17 +49,16 @@ typedef enum ifState * query_len is used to determine what accumulated text to throw away at the * end of an inactive branch. (We could, perhaps, teach the lexer to not add * stuff to the query buffer in the first place when inside an inactive branch; - * but that would be very invasive.) We also need to save and restore the - * lexer's parenthesis nesting depth when throwing away text. (We don't need - * to save and restore any of its other state, such as comment nesting depth, - * because a backslash command could never appear inside a comment or SQL - * literal.) + * but that would be very invasive.) We also need to save and restore some + * lexer state, such as parenthesis nesting depth, when throwing away text. */ +typedef struct PsqlScanStateSave PsqlScanStateSave; /* opaque outside lexer */ + typedef struct IfStackElem { ifState if_state; /* current state, see enum above */ int query_len; /* length of query_buf at last branch start */ - int paren_depth; /* parenthesis depth at last branch start */ + PsqlScanStateSave *lex_state; /* lexer state at last branch start */ struct IfStackElem *next; /* next surrounding \if, if any */ } IfStackElem; @@ -95,8 +94,9 @@ extern void conditional_stack_set_query_len(ConditionalStack cstack, int len); extern int conditional_stack_get_query_len(ConditionalStack cstack); -extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth); +extern void conditional_stack_set_lex_state(ConditionalStack cstack, + PsqlScanStateSave *lex_state); -extern int conditional_stack_get_paren_depth(ConditionalStack cstack); +extern PsqlScanStateSave *conditional_stack_get_lex_state(ConditionalStack cstack); #endif /* CONDITIONAL_H */ diff --git a/src/include/fe_utils/psqlscan.h b/src/include/fe_utils/psqlscan.h index abd44fa140b..f3e972e8892 100644 --- a/src/include/fe_utils/psqlscan.h +++ b/src/include/fe_utils/psqlscan.h @@ -26,6 +26,9 @@ /* Abstract type for lexer's internal state */ typedef struct PsqlScanStateData *PsqlScanState; +/* Abstract type for state save/restore */ +typedef struct PsqlScanStateSave PsqlScanStateSave; + /* Termination states for psql_scan() */ typedef enum { diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h index 8b0d153261b..ada1089f70d 100644 --- a/src/include/fe_utils/psqlscan_int.h +++ b/src/include/fe_utils/psqlscan_int.h @@ -132,6 +132,26 @@ typedef struct PsqlScanStateData void *cb_passthrough; } PsqlScanStateData; +/* + * Conditional scanning (\if ... \endif) needs to be able to reset the + * lexer's state to what it was at the beginning of a chunk of text that + * we choose to ignore. PsqlScanStateSave holds the values that need + * to be saved and restored. We assume that saving/restoring happens only + * while processing a backslash command, so we needn't save state that is + * concerned with comment or SQL literal processing: we won't be inside + * one of those. + */ +typedef struct PsqlScanStateSave +{ + int paren_depth; /* depth of nesting in parentheses */ + int begin_depth; /* depth of begin/end pairs */ + int init_idents_count; /* # identifiers since start of statement */ + char init_idents[4]; /* records the first few identifiers */ + int sub_idents_count; /* # identifiers since start of a CREATE + * SCHEMA element */ + char sub_idents[4]; /* records the first few of those identifiers */ +} PsqlScanStateSave; + /* * Functions exported by psqlscan.l, but only meant for use within diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 42635a56a06..7e67f795f25 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4789,6 +4789,22 @@ invalid command \lo \echo 'should print #8-1' should print #8-1 \endif +-- test that begin/end matching ignores to-be-ignored text +create function silly_function(int) returns int +begin atomic select $1; +\if false +end +\endif +; +end; +\sf silly_function(int) +CREATE OR REPLACE FUNCTION public.silly_function(integer) + RETURNS integer + LANGUAGE sql +BEGIN ATOMIC + SELECT $1; +END +drop function silly_function(int); -- :{?...} defined variable test \set i 1 \if :{?i} diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index e51767b3e2f..42eca3b8a6b 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1126,6 +1126,17 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; \echo 'should print #8-1' \endif +-- test that begin/end matching ignores to-be-ignored text +create function silly_function(int) returns int +begin atomic select $1; +\if false +end +\endif +; +end; +\sf silly_function(int) +drop function silly_function(int); + -- :{?...} defined variable test \set i 1 \if :{?i} diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 85d989f395d..eb3dd5481e6 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2466,6 +2466,7 @@ PsqlScanQuoteType PsqlScanResult PsqlScanState PsqlScanStateData +PsqlScanStateSave PsqlSettings Publication PublicationActions