mirror of https://github.com/postgres/postgres
This patch adds nestable conditional blocks to psql. The control structure feature per se is complete, but the boolean expressions understood by \if and \elif are pretty primitive; basically, after variable substitution and backtick expansion, the result has to be "true" or "false" or one of the other standard spellings of a boolean value. But that's enough for many purposes, since you can always do the heavy lifting on the server side; and we can extend it later. Along the way, pay down some of the technical debt that had built up around psql/command.c: * Refactor exec_command() into a function per command, instead of being a 1500-line monstrosity. This makes the file noticeably longer because of repetitive function header/trailer overhead, but it seems much more readable. * Teach psql_get_variable() and psqlscanslash.l to suppress variable substitution and backtick expansion on the basis of the conditional stack state, thereby allowing removal of the OT_NO_EVAL kluge. * Fix the no-doubt-once-expedient hack of sometimes silently substituting mainloop.c's previous_buf for query_buf when calling HandleSlashCmds. (It's a bit remarkable that commands like \r worked at all with that.) Recall of a previous query is now done explicitly in the slash commands where that should happen. Corey Huinker, reviewed by Fabien Coelho, further hacking by me Discussion: https://postgr.es/m/CADkLM=c94OSRTnat=LX0ivNq4pxDNeoomFfYvBKM5N_xfmLtAA@mail.gmail.compull/3/merge
parent
ffae6733db
commit
e984ef5861
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,153 @@ |
||||
/*
|
||||
* psql - the PostgreSQL interactive terminal |
||||
* |
||||
* Copyright (c) 2000-2017, PostgreSQL Global Development Group |
||||
* |
||||
* src/bin/psql/conditional.c |
||||
*/ |
||||
#include "postgres_fe.h" |
||||
|
||||
#include "conditional.h" |
||||
|
||||
/*
|
||||
* create stack |
||||
*/ |
||||
ConditionalStack |
||||
conditional_stack_create(void) |
||||
{ |
||||
ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData)); |
||||
|
||||
cstack->head = NULL; |
||||
return cstack; |
||||
} |
||||
|
||||
/*
|
||||
* destroy stack |
||||
*/ |
||||
void |
||||
conditional_stack_destroy(ConditionalStack cstack) |
||||
{ |
||||
while (conditional_stack_pop(cstack)) |
||||
continue; |
||||
free(cstack); |
||||
} |
||||
|
||||
/*
|
||||
* Create a new conditional branch. |
||||
*/ |
||||
void |
||||
conditional_stack_push(ConditionalStack cstack, ifState new_state) |
||||
{ |
||||
IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem)); |
||||
|
||||
p->if_state = new_state; |
||||
p->query_len = -1; |
||||
p->paren_depth = -1; |
||||
p->next = cstack->head; |
||||
cstack->head = p; |
||||
} |
||||
|
||||
/*
|
||||
* Destroy the topmost conditional branch. |
||||
* Returns false if there was no branch to end. |
||||
*/ |
||||
bool |
||||
conditional_stack_pop(ConditionalStack cstack) |
||||
{ |
||||
IfStackElem *p = cstack->head; |
||||
|
||||
if (!p) |
||||
return false; |
||||
cstack->head = cstack->head->next; |
||||
free(p); |
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* Fetch the current state of the top of the stack. |
||||
*/ |
||||
ifState |
||||
conditional_stack_peek(ConditionalStack cstack) |
||||
{ |
||||
if (conditional_stack_empty(cstack)) |
||||
return IFSTATE_NONE; |
||||
return cstack->head->if_state; |
||||
} |
||||
|
||||
/*
|
||||
* Change the state of the topmost branch. |
||||
* Returns false if there was no branch state to set. |
||||
*/ |
||||
bool |
||||
conditional_stack_poke(ConditionalStack cstack, ifState new_state) |
||||
{ |
||||
if (conditional_stack_empty(cstack)) |
||||
return false; |
||||
cstack->head->if_state = new_state; |
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* True if there are no active \if-blocks. |
||||
*/ |
||||
bool |
||||
conditional_stack_empty(ConditionalStack cstack) |
||||
{ |
||||
return cstack->head == NULL; |
||||
} |
||||
|
||||
/*
|
||||
* True if we should execute commands normally; that is, the current |
||||
* conditional branch is active, or there is no open \if block. |
||||
*/ |
||||
bool |
||||
conditional_active(ConditionalStack cstack) |
||||
{ |
||||
ifState s = conditional_stack_peek(cstack); |
||||
|
||||
return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE; |
||||
} |
||||
|
||||
/*
|
||||
* Save current query buffer length in topmost stack entry. |
||||
*/ |
||||
void |
||||
conditional_stack_set_query_len(ConditionalStack cstack, int len) |
||||
{ |
||||
Assert(!conditional_stack_empty(cstack)); |
||||
cstack->head->query_len = len; |
||||
} |
||||
|
||||
/*
|
||||
* Fetch last-recorded query buffer length from topmost stack entry. |
||||
* Will return -1 if no stack or it was never saved. |
||||
*/ |
||||
int |
||||
conditional_stack_get_query_len(ConditionalStack cstack) |
||||
{ |
||||
if (conditional_stack_empty(cstack)) |
||||
return -1; |
||||
return cstack->head->query_len; |
||||
} |
||||
|
||||
/*
|
||||
* Save current parenthesis nesting depth in topmost stack entry. |
||||
*/ |
||||
void |
||||
conditional_stack_set_paren_depth(ConditionalStack cstack, int depth) |
||||
{ |
||||
Assert(!conditional_stack_empty(cstack)); |
||||
cstack->head->paren_depth = depth; |
||||
} |
||||
|
||||
/*
|
||||
* Fetch last-recorded parenthesis nesting depth from topmost stack entry. |
||||
* Will return -1 if no stack or it was never saved. |
||||
*/ |
||||
int |
||||
conditional_stack_get_paren_depth(ConditionalStack cstack) |
||||
{ |
||||
if (conditional_stack_empty(cstack)) |
||||
return -1; |
||||
return cstack->head->paren_depth; |
||||
} |
@ -0,0 +1,83 @@ |
||||
/*
|
||||
* psql - the PostgreSQL interactive terminal |
||||
* |
||||
* Copyright (c) 2000-2017, PostgreSQL Global Development Group |
||||
* |
||||
* src/bin/psql/conditional.h |
||||
*/ |
||||
#ifndef CONDITIONAL_H |
||||
#define CONDITIONAL_H |
||||
|
||||
/*
|
||||
* Possible states of a single level of \if block. |
||||
*/ |
||||
typedef enum ifState |
||||
{ |
||||
IFSTATE_NONE = 0, /* not currently in an \if block */ |
||||
IFSTATE_TRUE, /* currently in an \if or \elif that is true
|
||||
* and all parent branches (if any) are true */ |
||||
IFSTATE_FALSE, /* currently in an \if or \elif that is false
|
||||
* but no true branch has yet been seen, and |
||||
* all parent branches (if any) are true */ |
||||
IFSTATE_IGNORED, /* currently in an \elif that follows a true
|
||||
* branch, or the whole \if is a child of a |
||||
* false parent branch */ |
||||
IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all
|
||||
* parent branches (if any) are true */ |
||||
IFSTATE_ELSE_FALSE /* currently in an \else that is false or
|
||||
* ignored */ |
||||
} ifState; |
||||
|
||||
/*
|
||||
* The state of nested \ifs is stored in a stack. |
||||
* |
||||
* 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.) |
||||
*/ |
||||
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 */ |
||||
struct IfStackElem *next; /* next surrounding \if, if any */ |
||||
} IfStackElem; |
||||
|
||||
typedef struct ConditionalStackData |
||||
{ |
||||
IfStackElem *head; |
||||
} ConditionalStackData; |
||||
|
||||
typedef struct ConditionalStackData *ConditionalStack; |
||||
|
||||
|
||||
extern ConditionalStack conditional_stack_create(void); |
||||
|
||||
extern void conditional_stack_destroy(ConditionalStack cstack); |
||||
|
||||
extern void conditional_stack_push(ConditionalStack cstack, ifState new_state); |
||||
|
||||
extern bool conditional_stack_pop(ConditionalStack cstack); |
||||
|
||||
extern ifState conditional_stack_peek(ConditionalStack cstack); |
||||
|
||||
extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state); |
||||
|
||||
extern bool conditional_stack_empty(ConditionalStack cstack); |
||||
|
||||
extern bool conditional_active(ConditionalStack cstack); |
||||
|
||||
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 int conditional_stack_get_paren_depth(ConditionalStack cstack); |
||||
|
||||
#endif /* CONDITIONAL_H */ |
Loading…
Reference in new issue