#include "pg_query.h" #include "pg_query_internal.h" #include "gramparse.h" #include "lib/stringinfo.h" #include #include PgQuerySplitResult pg_query_split_with_scanner(const char* input) { MemoryContext ctx = NULL; PgQuerySplitResult result = {0}; core_yyscan_t yyscanner; core_yy_extra_type yyextra; core_YYSTYPE yylval; YYLTYPE yylloc; size_t curstmt = 0; bool keyword_before_terminator = false; int stmtstart = 0; bool is_keyword = false; size_t open_parens = 0; ctx = pg_query_enter_memory_context(); MemoryContext parse_context = CurrentMemoryContext; char stderr_buffer[STDERR_BUFFER_LEN + 1] = {0}; #ifndef DEBUG int stderr_global; int stderr_pipe[2]; #endif #ifndef DEBUG // Setup pipe for stderr redirection if (pipe(stderr_pipe) != 0) { PgQueryError* error = malloc(sizeof(PgQueryError)); error->message = strdup("Failed to open pipe, too many open file descriptors") result.error = error; return result; } fcntl(stderr_pipe[0], F_SETFL, fcntl(stderr_pipe[0], F_GETFL) | O_NONBLOCK); // Redirect stderr to the pipe stderr_global = dup(STDERR_FILENO); dup2(stderr_pipe[1], STDERR_FILENO); close(stderr_pipe[1]); #endif PG_TRY(); { // Really this is stupid, we only run twice so we can pre-allocate the output array correctly yyscanner = scanner_init(input, &yyextra, &ScanKeywords, ScanKeywordTokens); while (true) { int tok = core_yylex(&yylval, &yylloc, yyscanner); switch (tok) { #define PG_KEYWORD(a,b,c,d) case b: is_keyword = true; break; #include "parser/kwlist.h" #undef PG_KEYWORD default: is_keyword = false; } if (is_keyword) keyword_before_terminator = true; else if (tok == '(') open_parens++; else if (tok == ')') open_parens--; else if (keyword_before_terminator && open_parens == 0 && (tok == ';' || tok == 0)) { result.n_stmts++; keyword_before_terminator = false; } if (tok == 0) break; } scanner_finish(yyscanner); result.stmts = malloc(sizeof(PgQuerySplitStmt *) * result.n_stmts); // Now actually set the output values keyword_before_terminator = false; open_parens = 0; yyscanner = scanner_init(input, &yyextra, &ScanKeywords, ScanKeywordTokens); while (true) { int tok = core_yylex(&yylval, &yylloc, yyscanner); switch (tok) { #define PG_KEYWORD(a,b,c,d) case b: is_keyword = true; break; #include "parser/kwlist.h" default: is_keyword = false; } if (is_keyword) keyword_before_terminator = true; else if (tok == '(') open_parens++; else if (tok == ')') open_parens--; else if (keyword_before_terminator && open_parens == 0 && (tok == ';' || tok == 0)) { // Add statement up to the current position result.stmts[curstmt] = malloc(sizeof(PgQuerySplitStmt)); result.stmts[curstmt]->stmt_location = stmtstart; result.stmts[curstmt]->stmt_len = yylloc - stmtstart; stmtstart = yylloc + 1; keyword_before_terminator = false; curstmt++; } else if (open_parens == 0 && tok == ';') // Advance statement start in case we skip an empty statement { stmtstart = yylloc + 1; } if (tok == 0) break; } scanner_finish(yyscanner); #ifndef DEBUG // Save stderr for result read(stderr_pipe[0], stderr_buffer, STDERR_BUFFER_LEN); #endif result.stderr_buffer = strdup(stderr_buffer); } PG_CATCH(); { ErrorData* error_data; PgQueryError* error; MemoryContextSwitchTo(parse_context); error_data = CopyErrorData(); // Note: This is intentionally malloc so exiting the memory context doesn't free this error = malloc(sizeof(PgQueryError)); error->message = strdup(error_data->message); error->filename = strdup(error_data->filename); error->funcname = strdup(error_data->funcname); error->context = NULL; error->lineno = error_data->lineno; error->cursorpos = error_data->cursorpos; result.error = error; FlushErrorState(); } PG_END_TRY(); #ifndef DEBUG // Restore stderr, close pipe dup2(stderr_global, STDERR_FILENO); close(stderr_pipe[0]); close(stderr_global); #endif pg_query_exit_memory_context(ctx); return result; } PgQuerySplitResult pg_query_split_with_parser(const char* input) { MemoryContext ctx = NULL; PgQueryInternalParsetreeAndError parsetree_and_error; PgQuerySplitResult result = {0}; ctx = pg_query_enter_memory_context(); parsetree_and_error = pg_query_raw_parse(input, PG_QUERY_PARSE_DEFAULT); // These are all malloc-ed and will survive exiting the memory context, the caller is responsible to free them now result.stderr_buffer = parsetree_and_error.stderr_buffer; result.error = parsetree_and_error.error; if (parsetree_and_error.tree != NULL) { ListCell *lc; result.n_stmts = list_length(parsetree_and_error.tree); result.stmts = malloc(sizeof(PgQuerySplitStmt*) * result.n_stmts); foreach (lc, parsetree_and_error.tree) { RawStmt *raw_stmt = castNode(RawStmt, lfirst(lc)); result.stmts[foreach_current_index(lc)] = malloc(sizeof(PgQuerySplitStmt)); result.stmts[foreach_current_index(lc)]->stmt_location = raw_stmt->stmt_location; if (raw_stmt->stmt_len == 0) result.stmts[foreach_current_index(lc)]->stmt_len = strlen(input) - raw_stmt->stmt_location; else result.stmts[foreach_current_index(lc)]->stmt_len = raw_stmt->stmt_len; } } else { result.n_stmts = 0; result.stmts = NULL; } pg_query_exit_memory_context(ctx); return result; } void pg_query_free_split_result(PgQuerySplitResult result) { if (result.error) { pg_query_free_error(result.error); } free(result.stderr_buffer); if (result.stmts != NULL) { for (int i = 0; i < result.n_stmts; ++i) { free(result.stmts[i]); } free(result.stmts); } }