-
Notifications
You must be signed in to change notification settings - Fork 212
C API for parsing, deparsing and fingerprinting queries #321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 17-latest
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Wrapper header for raw parse tree access | ||
| // This header includes the PostgreSQL internal types needed for direct parse tree access | ||
|
|
||
| #ifndef PG_QUERY_RAW_H | ||
| #define PG_QUERY_RAW_H | ||
|
|
||
| #include "pg_query.h" | ||
|
|
||
| // Include PostgreSQL headers needed for parse tree access | ||
| #include "src/postgres/include/postgres.h" | ||
| #include "src/postgres/include/nodes/nodes.h" | ||
| #include "src/postgres/include/nodes/pg_list.h" | ||
| #include "src/postgres/include/nodes/value.h" | ||
| #include "src/postgres/include/nodes/primnodes.h" | ||
| #include "src/postgres/include/nodes/parsenodes.h" | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,188 @@ pg_query_free_deparse_result(PgQueryDeparseResult result) | |
| free(result.query); | ||
| } | ||
|
|
||
| /* | ||
| * Helper functions for building nodes from Rust (bypassing protobuf) | ||
| * | ||
| * These wrap PostgreSQL's internal functions to allow Rust to construct | ||
| * parse trees directly. | ||
| */ | ||
|
|
||
| /* Memory context management - exposed for Rust */ | ||
| void * | ||
| pg_query_deparse_enter_context(void) | ||
| { | ||
| return (void *) pg_query_enter_memory_context(); | ||
| } | ||
|
|
||
| void | ||
| pg_query_deparse_exit_context(void *ctx) | ||
| { | ||
| pg_query_exit_memory_context((MemoryContext) ctx); | ||
| } | ||
|
|
||
| /* Node allocation helper */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm, I see why you added this and the other methods, but I do wonder if this is the right approach (e.g. why not access the include header that has |
||
| void * | ||
| pg_query_alloc_node(size_t size, int tag) | ||
| { | ||
| Node *result = (Node *) palloc0(size); | ||
| result->type = (NodeTag) tag; | ||
| return result; | ||
| } | ||
|
|
||
| /* String duplication helper */ | ||
| char * | ||
| pg_query_pstrdup(const char *str) | ||
| { | ||
| if (str == NULL) | ||
| return NULL; | ||
| return pstrdup(str); | ||
| } | ||
|
|
||
| /* List building helpers */ | ||
| void * | ||
| pg_query_list_make1(void *datum) | ||
| { | ||
| return (void *) list_make1(datum); | ||
| } | ||
|
|
||
| void * | ||
| pg_query_list_append(void *list, void *datum) | ||
| { | ||
| return (void *) lappend((List *) list, datum); | ||
| } | ||
|
|
||
| /* Deparse a list of RawStmt nodes to SQL */ | ||
| PgQueryDeparseResult | ||
| pg_query_deparse_nodes(void *stmts_ptr) | ||
| { | ||
| List *stmts = (List *) stmts_ptr; | ||
| PgQueryDeparseResult result = {0}; | ||
| StringInfoData str; | ||
| ListCell *lc; | ||
|
|
||
| if (stmts == NULL) | ||
| { | ||
| result.query = strdup(""); | ||
| return result; | ||
| } | ||
|
|
||
| /* Note: The caller must have already entered a memory context */ | ||
| PG_TRY(); | ||
| { | ||
| PostgresDeparseOpts opts; | ||
| MemSet(&opts, 0, sizeof(PostgresDeparseOpts)); | ||
|
|
||
| initStringInfo(&str); | ||
|
|
||
| foreach(lc, stmts) | ||
| { | ||
| deparseRawStmtOpts(&str, castNode(RawStmt, lfirst(lc)), opts); | ||
| if (lnext(stmts, lc)) | ||
| appendStringInfoString(&str, "; "); | ||
| } | ||
| result.query = strdup(str.data); | ||
| } | ||
| PG_CATCH(); | ||
| { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't we have
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree, that seems like an oversight - CopyErrorData will fail when the context isn't switched. |
||
| ErrorData *error_data; | ||
| PgQueryError *error; | ||
|
|
||
| error_data = CopyErrorData(); | ||
|
|
||
| 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(); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| PgQueryDeparseResult | ||
| pg_query_deparse_raw(PgQueryRawParseResult parse_result) | ||
| { | ||
| PostgresDeparseOpts opts; | ||
|
|
||
| MemSet(&opts, 0, sizeof(PostgresDeparseOpts)); | ||
| return pg_query_deparse_raw_opts(parse_result, opts); | ||
| } | ||
|
|
||
| PgQueryDeparseResult | ||
| pg_query_deparse_raw_opts(PgQueryRawParseResult parse_result, PostgresDeparseOpts opts) | ||
| { | ||
| PgQueryDeparseResult result = {0}; | ||
| StringInfoData str; | ||
| ListCell *lc; | ||
|
|
||
| /* If there was a parse error, propagate it */ | ||
| if (parse_result.error != NULL) | ||
| { | ||
| PgQueryError *error = malloc(sizeof(PgQueryError)); | ||
| error->message = parse_result.error->message ? strdup(parse_result.error->message) : NULL; | ||
| error->filename = parse_result.error->filename ? strdup(parse_result.error->filename) : NULL; | ||
| error->funcname = parse_result.error->funcname ? strdup(parse_result.error->funcname) : NULL; | ||
| error->context = parse_result.error->context ? strdup(parse_result.error->context) : NULL; | ||
| error->lineno = parse_result.error->lineno; | ||
| error->cursorpos = parse_result.error->cursorpos; | ||
| result.error = error; | ||
| return result; | ||
| } | ||
|
|
||
| /* If tree is NULL, return empty string */ | ||
| if (parse_result.tree == NULL) | ||
| { | ||
| result.query = strdup(""); | ||
| return result; | ||
| } | ||
|
|
||
| /* | ||
| * Note: We use the parse_result's memory context which is already active. | ||
| * The caller must ensure the parse_result is still valid. | ||
| */ | ||
| PG_TRY(); | ||
| { | ||
| initStringInfo(&str); | ||
|
|
||
| foreach(lc, parse_result.tree) | ||
| { | ||
| deparseRawStmtOpts(&str, castNode(RawStmt, lfirst(lc)), opts); | ||
| if (lnext(parse_result.tree, lc)) | ||
| appendStringInfoString(&str, "; "); | ||
| } | ||
| result.query = strdup(str.data); | ||
| } | ||
| PG_CATCH(); | ||
| { | ||
| ErrorData *error_data; | ||
| PgQueryError *error; | ||
|
|
||
| MemoryContextSwitchTo(parse_result.context); | ||
| error_data = CopyErrorData(); | ||
|
|
||
| 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(); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| PgQueryDeparseCommentsResult | ||
| pg_query_deparse_comments_for_query(const char *query) | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be better if we put all of this into
pg_query_raw.h, so authors of bindings that do not use the direct access to the AST structs don't get confused which methods to use.