/* * hemar.c * Template parser implementation for Hemar */ #include "hemar.h" #include "postgres.h" #include #include #include "utils/elog.h" #include "utils/memutils.h" #include "mb/pg_wchar.h" #include "fmgr.h" #include "utils/builtins.h" #include "lib/stringinfo.h" #include "utils/jsonb.h" #include "catalog/pg_type.h" #include "executor/spi.h" #include "funcapi.h" /* Forward declarations */ static char *get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found); static Datum get_jsonb_array(Datum jsonb_context, const char *path, bool *found); static int get_jsonb_array_length(Datum jsonb_array); static Datum get_jsonb_array_element(Datum jsonb_array, int index); static Datum create_iterator_context(Datum parent_context, const char *iterator_name, Datum item_context); static Datum get_jsonb_include_template(Datum jsonb_context, const char *key, bool *found); static void get_include_data(Datum include_data, char **template_out, Datum *context_out); static void template_node_to_string(TemplateNode *node, StringInfo result, int indent); static void debug_jsonb_value(Datum jsonb_value, const char *label); static bool is_jsonb_container_valid(JsonbContainer *container); /* Implementation of a simplified validity check for JsonbContainer */ static bool is_jsonb_container_valid(JsonbContainer *container) { /* Basic sanity check: non-NULL pointer */ if (container == NULL) return false; /* Check that container has a valid header size (at least 4 bytes) */ uint32 header = *(uint32 *)container; if (header == 0) /* Simplistic check for corrupted/empty header */ return false; /* This is a simplified check. In a real implementation, we'd do more validation. */ return true; } /* Helper function to skip whitespace */ static const char * skip_whitespace(const char *s) { while (*s && isspace((unsigned char)*s)) s++; return s; } /* Initialize template value based on type */ static TemplateValue init_template_value(TemplateNodeType type) { TemplateValue value; switch (type) { case TEMPLATE_NODE_TEXT: value.text.content = NULL; break; case TEMPLATE_NODE_INTERPOLATE: value.interpolate.key = NULL; break; case TEMPLATE_NODE_SECTION: value.section.iterator = NULL; value.section.collection = NULL; value.section.body = NULL; break; case TEMPLATE_NODE_EXECUTE: value.execute.code = NULL; break; case TEMPLATE_NODE_INCLUDE: value.include.key = NULL; break; default: elog(ERROR, "Unknown template node type: %d", type); /* This won't be reached due to elog(ERROR) */ memset(&value, 0, sizeof(value)); break; } return value; } /* Initialize a new template node */ static TemplateNode * init_template_node(MemoryContext context, TemplateNodeType type) { TemplateNode *node; node = (TemplateNode *) MemoryContextAlloc(context, sizeof(TemplateNode)); node->next = NULL; node->type = type; node->value = (TemplateValue *) MemoryContextAlloc(context, sizeof(TemplateValue)); *node->value = init_template_value(type); return node; } /* Error code to string conversion */ const char * template_error_to_string(TemplateErrorCode code) { switch (code) { case TEMPLATE_ERROR_NONE: return "No error"; case TEMPLATE_ERROR_UNKNOWN_TAG: return "Unknown tag"; case TEMPLATE_ERROR_NESTED_INTERPOLATION: return "Nested interpolation"; case TEMPLATE_ERROR_NESTED_SECTION_ITERATOR: return "Nested section iterator"; case TEMPLATE_ERROR_UNEXPECTED_SECTION_END: return "Unexpected section end"; case TEMPLATE_ERROR_NESTED_INCLUDE: return "Nested include"; case TEMPLATE_ERROR_NESTED_EXECUTE: return "Nested execute"; case TEMPLATE_ERROR_INVALID_CONFIG: return "Invalid config"; case TEMPLATE_ERROR_OUT_OF_MEMORY: return "Out of memory"; case TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END: return "Unexpected include end"; case TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END: return "Unexpected execute end"; default: return "Unknown error"; } } /* Default template configuration */ TemplateConfig template_default_config(MemoryContext context) { TemplateConfig config; config.Syntax.Braces.open = "{%"; config.Syntax.Braces.close = "%}"; config.Syntax.Section.control = "for "; config.Syntax.Section.source = "in "; config.Syntax.Section.begin = "do "; config.Syntax.Interpolate.invoke = ""; config.Syntax.Include.invoke = "include "; config.Syntax.Execute.invoke = "exec "; config.Syntax.nesting = "->"; return config; } /* Validate template configuration */ bool template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code) { if (!config) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check open brace */ if (!config->Syntax.Braces.open || strlen(config->Syntax.Braces.open) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check close brace */ if (!config->Syntax.Braces.close || strlen(config->Syntax.Braces.close) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check section control */ if (!config->Syntax.Section.control || strlen(config->Syntax.Section.control) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check section source */ if (!config->Syntax.Section.source || strlen(config->Syntax.Section.source) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check section begin */ if (!config->Syntax.Section.begin || strlen(config->Syntax.Section.begin) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check interpolate invoke */ if (!config->Syntax.Interpolate.invoke || strlen(config->Syntax.Interpolate.invoke) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check include invoke */ if (!config->Syntax.Include.invoke || strlen(config->Syntax.Include.invoke) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check execute invoke */ if (!config->Syntax.Execute.invoke || strlen(config->Syntax.Execute.invoke) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } /* Check nesting */ if (!config->Syntax.nesting || strlen(config->Syntax.nesting) > TEMPLATE_MAX_PREFIX_LEN) { if (error_code) *error_code = TEMPLATE_ERROR_INVALID_CONFIG; return false; } if (error_code) *error_code = TEMPLATE_ERROR_NONE; return true; } /* Parse interpolation tag */ static TemplateNode * template_parse_interpolation(MemoryContext context, const char **s_ptr, const TemplateConfig *config, TemplateErrorCode *error_code) { const char **s = s_ptr; const char *key_start; size_t key_len; TemplateNode *node; node = init_template_node(context, TEMPLATE_NODE_INTERPOLATE); /* Skip to the content */ *s += strlen(config->Syntax.Braces.open); *s = skip_whitespace(*s); *s += strlen(config->Syntax.Interpolate.invoke); *s = skip_whitespace(*s); key_start = *s; while (**s != '\0') { if (isspace((unsigned char)**s) || strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) break; if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_NESTED_INTERPOLATION; template_free_node(node); return NULL; } (*s)++; } key_len = *s - key_start; node->value->interpolate.key = MemoryContextStrdup(context, pnstrdup(key_start, key_len)); *s = skip_whitespace(*s); /* Check for closing brace */ if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } *s_ptr = *s + strlen(config->Syntax.Braces.close); return node; } /* Parse section tag */ static TemplateNode * template_parse_section(MemoryContext context, const char **s_ptr, const TemplateConfig *config, TemplateErrorCode *error_code) { const char **s = s_ptr; const char *iterator_start, *collection_start; size_t iterator_len, collection_len; TemplateNode *node, *body_node; node = init_template_node(context, TEMPLATE_NODE_SECTION); /* Skip to the content */ *s += strlen(config->Syntax.Braces.open); *s = skip_whitespace(*s); *s += strlen(config->Syntax.Section.control); /* Find the iterator name */ *s = skip_whitespace(*s); iterator_start = *s; while (**s != '\0') { if (isspace((unsigned char)**s) || strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) == 0) break; if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; template_free_node(node); return NULL; } if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } (*s)++; } iterator_len = *s - iterator_start; node->value->section.iterator = MemoryContextStrdup(context, pnstrdup(iterator_start, iterator_len)); /* Find the collection name */ *s = skip_whitespace(*s); if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } *s += strlen(config->Syntax.Section.source); *s = skip_whitespace(*s); collection_start = *s; while (**s != '\0') { if (isspace((unsigned char)**s) || strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) == 0) break; if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; template_free_node(node); return NULL; } if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } (*s)++; } collection_len = *s - collection_start; node->value->section.collection = MemoryContextStrdup(context, pnstrdup(collection_start, collection_len)); /* Check for 'do' keyword */ *s = skip_whitespace(*s); if (strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } *s += strlen(config->Syntax.Section.begin); *s = skip_whitespace(*s); /* Check if there's a closing brace right after 'do' */ if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { /* Empty section body */ *s_ptr = *s + strlen(config->Syntax.Braces.close); node->value->section.body = NULL; return node; } /* Parse the body */ body_node = template_parse(context, s, config, true, error_code); if (!body_node) { template_free_node(node); return NULL; } node->value->section.body = body_node; /* Skip to the end of the section */ *s = skip_whitespace(*s); /* Check for closing brace */ if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; template_free_node(node); return NULL; } *s_ptr = *s + strlen(config->Syntax.Braces.close); return node; } /* Parse include tag */ static TemplateNode * template_parse_include(MemoryContext context, const char **s_ptr, const TemplateConfig *config, TemplateErrorCode *error_code) { const char **s = s_ptr; const char *include_start; size_t include_len; TemplateNode *node; node = init_template_node(context, TEMPLATE_NODE_INCLUDE); /* Skip to the content */ *s += strlen(config->Syntax.Braces.open); *s = skip_whitespace(*s); *s += strlen(config->Syntax.Include.invoke); *s = skip_whitespace(*s); include_start = *s; while (**s != '\0') { if (isspace((unsigned char)**s) || strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) break; if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_NESTED_INCLUDE; template_free_node(node); return NULL; } (*s)++; } include_len = *s - include_start; node->value->include.key = MemoryContextStrdup(context, pnstrdup(include_start, include_len)); *s = skip_whitespace(*s); /* Check for closing brace */ if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END; template_free_node(node); return NULL; } *s_ptr = *s + strlen(config->Syntax.Braces.close); return node; } /* Parse execute tag */ static TemplateNode * template_parse_execute(MemoryContext context, const char **s_ptr, const TemplateConfig *config, TemplateErrorCode *error_code) { const char **s = s_ptr; const char *code_start; size_t code_len; TemplateNode *node; node = init_template_node(context, TEMPLATE_NODE_EXECUTE); /* Skip to the content */ *s += strlen(config->Syntax.Braces.open); *s = skip_whitespace(*s); *s += strlen(config->Syntax.Execute.invoke); *s = skip_whitespace(*s); code_start = *s; while (**s != '\0') { if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) break; if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { if (error_code) *error_code = TEMPLATE_ERROR_NESTED_EXECUTE; template_free_node(node); return NULL; } (*s)++; } code_len = *s - code_start; node->value->execute.code = MemoryContextStrdup(context, pnstrdup(code_start, code_len)); /* Check for closing brace */ if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) { if (error_code) *error_code = TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END; template_free_node(node); return NULL; } *s_ptr = *s + strlen(config->Syntax.Braces.close); return node; } /* Main template parser function */ TemplateNode * template_parse(MemoryContext context, const char **s, const TemplateConfig *config, bool inner_parse, TemplateErrorCode *error_code) { const char *start; TemplateNode *root, *current, *tag_node; bool current_node_filled = false; const char *tag_prefix; size_t text_len; if (!template_validate_config(config, error_code)) { return NULL; } /* Check for empty template */ if (!s || !*s || !**s) { TemplateNode *empty_node = init_template_node(context, TEMPLATE_NODE_TEXT); empty_node->value->text.content = MemoryContextStrdup(context, ""); if (error_code) *error_code = TEMPLATE_ERROR_NONE; return empty_node; } start = *s; root = init_template_node(context, TEMPLATE_NODE_TEXT); current = root; while (*s && **s != '\0') { /* Check for closing brace in inner parse */ if (inner_parse && strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { break; } if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) { /* Handle text before tag */ if (start != *s) { if (current_node_filled) { TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT); current->next = new_node; current = new_node; } else { current->type = TEMPLATE_NODE_TEXT; *current->value = init_template_value(TEMPLATE_NODE_TEXT); } text_len = *s - start; current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); current_node_filled = true; } /* Parse the tag */ tag_node = NULL; tag_prefix = *s + strlen(config->Syntax.Braces.open); tag_prefix = skip_whitespace(tag_prefix); /* Determine tag type by prefix */ if (strncmp(tag_prefix, config->Syntax.Section.control, strlen(config->Syntax.Section.control)) == 0) { tag_node = template_parse_section(context, s, config, error_code); } else if (strncmp(tag_prefix, config->Syntax.Include.invoke, strlen(config->Syntax.Include.invoke)) == 0) { tag_node = template_parse_include(context, s, config, error_code); } else if (strncmp(tag_prefix, config->Syntax.Execute.invoke, strlen(config->Syntax.Execute.invoke)) == 0) { tag_node = template_parse_execute(context, s, config, error_code); } else if (strncmp(tag_prefix, config->Syntax.Interpolate.invoke, strlen(config->Syntax.Interpolate.invoke)) == 0) { tag_node = template_parse_interpolation(context, s, config, error_code); } else { if (error_code) *error_code = TEMPLATE_ERROR_UNKNOWN_TAG; template_free_node(root); return NULL; } if (!tag_node) { template_free_node(root); return NULL; } if (current_node_filled) { current->next = tag_node; current = tag_node; } else { *current = *tag_node; pfree(tag_node); } current_node_filled = true; start = *s; } else { (*s)++; } } /* Handle remaining text */ if (start != *s) { if (current_node_filled) { TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT); current->next = new_node; current = new_node; } else { current->type = TEMPLATE_NODE_TEXT; *current->value = init_template_value(TEMPLATE_NODE_TEXT); } text_len = *s - start; current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); current_node_filled = true; } /* If no nodes were created, ensure we have at least an empty text node */ if (!current_node_filled) { current->type = TEMPLATE_NODE_TEXT; *current->value = init_template_value(TEMPLATE_NODE_TEXT); current->value->text.content = MemoryContextStrdup(context, ""); } if (error_code) *error_code = TEMPLATE_ERROR_NONE; return root; } /* Free a template node and all its children */ void template_free_node(TemplateNode *node) { TemplateNode *current, *next; if (!node) return; current = node; while (current) { next = current->next; if (current->value) { switch (current->type) { case TEMPLATE_NODE_TEXT: if (current->value->text.content) pfree(current->value->text.content); break; case TEMPLATE_NODE_INTERPOLATE: if (current->value->interpolate.key) pfree(current->value->interpolate.key); break; case TEMPLATE_NODE_SECTION: if (current->value->section.iterator) pfree(current->value->section.iterator); if (current->value->section.collection) pfree(current->value->section.collection); if (current->value->section.body) template_free_node(current->value->section.body); break; case TEMPLATE_NODE_EXECUTE: if (current->value->execute.code) pfree(current->value->execute.code); break; case TEMPLATE_NODE_INCLUDE: if (current->value->include.key) pfree(current->value->include.key); break; default: /* Should not happen */ break; } pfree(current->value); } pfree(current); current = next; } } /* Render a template with the given context */ static char * template_render(MemoryContext context, TemplateNode *node, Datum jsonb_context, bool *error) { StringInfoData result; TemplateNode *current; if (!node || !context || error == NULL) { if (error) *error = true; return NULL; } *error = false; initStringInfo(&result); current = node; elog(DEBUG1, "Starting template rendering"); /* Use PG_TRY/PG_CATCH to handle errors during the entire rendering process */ PG_TRY(); { while (current) { switch (current->type) { case TEMPLATE_NODE_TEXT: if (current->value->text.content) { elog(DEBUG1, "Rendering text node: %s", current->value->text.content); appendStringInfoString(&result, current->value->text.content); } break; case TEMPLATE_NODE_INTERPOLATE: case TEMPLATE_NODE_SECTION: case TEMPLATE_NODE_EXECUTE: case TEMPLATE_NODE_INCLUDE: /* Use nested PG_TRY/PG_CATCH blocks for each node type for better error handling */ PG_TRY(); { if (current->type == TEMPLATE_NODE_INTERPOLATE) { /* Process interpolation node */ char *value = NULL; bool found = false; if (current->value->interpolate.key) { elog(DEBUG1, "Processing interpolation for key: %s", current->value->interpolate.key); /* First try to get as a direct path */ /* Extract value from JSONB context */ value = get_jsonb_path_value(jsonb_context, current->value->interpolate.key, &found); if (found && value) { elog(DEBUG1, "Found value for key %s: %s", current->value->interpolate.key, value); appendStringInfoString(&result, value); pfree(value); } else { /* If not found as direct path, check if it's an array */ Datum array_value; bool array_found = false; array_value = get_jsonb_array(jsonb_context, current->value->interpolate.key, &array_found); if (array_found) { /* Convert array to string representation */ elog(DEBUG1, "Found array for key %s, converting to string", current->value->interpolate.key); debug_jsonb_value(array_value, "Array for interpolation"); /* Create a string representation of the array */ StringInfoData array_str; initStringInfo(&array_str); appendStringInfoString(&array_str, "["); Jsonb *array_jb = (Jsonb *) DatumGetPointer(array_value); if (array_jb && is_jsonb_container_valid(&array_jb->root)) { int array_length = get_jsonb_array_length(array_value); int i; Datum elem; char *elem_str = NULL; bool elem_found = false; elog(DEBUG1, "Array length: %d", array_length); for (i = 0; i < array_length; i++) { if (i > 0) appendStringInfoString(&array_str, ", "); elem = get_jsonb_array_element(array_value, i); if (elem != (Datum) 0) { elem_found = false; /* Try to get value from the element */ elem_str = get_jsonb_path_value(elem, "value", &elem_found); if (elem_found && elem_str) { appendStringInfoString(&array_str, elem_str); pfree(elem_str); } else { appendStringInfoString(&array_str, "null"); } } else { appendStringInfoString(&array_str, "null"); } } } appendStringInfoString(&array_str, "]"); appendStringInfoString(&result, array_str.data); pfree(array_str.data); } else { elog(DEBUG1, "Key %s not found in context", current->value->interpolate.key); /* Optionally append something to indicate missing key */ appendStringInfoString(&result, ""); } } } } else if (current->type == TEMPLATE_NODE_SECTION) { /* Handle sections (loops) */ char *collection_path = current->value->section.collection; Datum array_value; bool found = false; int array_length; int i; JsonbParseState *parse_state = NULL; JsonbValue *empty_obj; Datum item_context; Datum merged_context; char *item_result; bool item_error = false; if (collection_path) { elog(DEBUG1, "Processing section with collection path: %s", collection_path); /* Get array from context */ array_value = get_jsonb_array(jsonb_context, collection_path, &found); if (found) { /* Make sure we have a valid array */ Jsonb *array_jb = (Jsonb *) DatumGetPointer(array_value); if (!array_jb || !is_jsonb_container_valid(&array_jb->root)) { elog(WARNING, "Invalid JSONB array container for path: %s", collection_path); break; } array_length = get_jsonb_array_length(array_value); elog(DEBUG1, "Found array with length: %d", array_length); debug_jsonb_value(array_value, "Section Array"); /* Handle empty arrays gracefully */ if (array_length <= 0) { elog(DEBUG1, "Array is empty, skipping section"); break; } /* If section body is empty, nothing to do */ if (current->value->section.body == NULL) { elog(DEBUG1, "Section body is empty, skipping"); break; } for (i = 0; i < array_length; i++) { item_context = (Datum) 0; item_error = false; elog(DEBUG1, "Processing array element %d", i); /* Safely get array element with error handling */ PG_TRY(); { item_context = get_jsonb_array_element(array_value, i); /* Validate we got a valid item back */ if (item_context != (Datum) 0) { Jsonb *item_jb = (Jsonb *) DatumGetPointer(item_context); if (item_jb && is_jsonb_container_valid(&item_jb->root)) { elog(DEBUG1, "Got valid array element %d", i); debug_jsonb_value(item_context, "Array Element"); } else { elog(WARNING, "Got invalid JSONB container for array element %d", i); item_context = (Datum) 0; /* Reset to create empty object below */ } } } PG_CATCH(); { elog(WARNING, "Error getting array element %d, creating empty object instead", i); /* Create an empty object for problematic array elements */ parse_state = NULL; pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL); empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL); item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj)); /* Reset error state */ FlushErrorState(); } PG_END_TRY(); if (item_context == (Datum) 0) { elog(DEBUG1, "Array element %d is null, creating empty object", i); /* Create an empty object for null array elements */ parse_state = NULL; pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL); empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL); item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj)); } /* Create context with iterator variable */ PG_TRY(); { merged_context = create_iterator_context(jsonb_context, current->value->section.iterator, item_context); } PG_CATCH(); { elog(WARNING, "Error creating merged context for array element %d", i); /* Use parent context as fallback */ merged_context = jsonb_context; /* Reset error state */ FlushErrorState(); } PG_END_TRY(); if (merged_context == (Datum) 0) { elog(WARNING, "Failed to create merged context for array element %d", i); continue; } /* Render section body with new context */ PG_TRY(); { item_result = template_render(context, current->value->section.body, merged_context, &item_error); if (!item_error && item_result) { appendStringInfoString(&result, item_result); pfree(item_result); } else if (item_error) { elog(WARNING, "Error rendering template section for array element %d", i); *error = true; return result.data; } } PG_CATCH(); { elog(WARNING, "Exception during template rendering for array element %d", i); /* Continue with next element */ FlushErrorState(); } PG_END_TRY(); } } else { elog(DEBUG1, "Collection not found: %s", collection_path); } } } else if (current->type == TEMPLATE_NODE_INCLUDE) { /* Handle includes */ char *template_key = current->value->include.key; Datum include_data; bool found = false; if (template_key) { elog(DEBUG1, "Processing include with key: %s", template_key); /* Find include template in context */ include_data = get_jsonb_include_template(jsonb_context, template_key, &found); if (found) { char *include_template = NULL; Datum include_context = (Datum) 0; char *include_result; bool include_error = false; /* Extract template and context */ get_include_data(include_data, &include_template, &include_context); /* Parse and render included template */ if (include_template) { TemplateConfig config = template_default_config(context); TemplateErrorCode error_code; const char *template_str = include_template; TemplateNode *include_node = template_parse(context, &template_str, &config, false, &error_code); if (include_node && error_code == TEMPLATE_ERROR_NONE) { include_result = template_render(context, include_node, include_context != (Datum) 0 ? include_context : jsonb_context, &include_error); if (!include_error && include_result) { appendStringInfoString(&result, include_result); pfree(include_result); } else { elog(WARNING, "Error rendering included template: %s", template_key); *error = true; } template_free_node(include_node); } else { elog(WARNING, "Error parsing included template: %s", template_key); *error = true; } pfree(include_template); } else { elog(WARNING, "Included template is null: %s", template_key); } } else { elog(DEBUG1, "Include key not found: %s", template_key); } } } else if (current->type == TEMPLATE_NODE_EXECUTE) { /* Execute is not implemented in this version */ elog(DEBUG1, "Execute node type not implemented"); } } PG_CATCH(); { elog(WARNING, "Error processing template node of type %d", current->type); FlushErrorState(); /* Continue with next node */ } PG_END_TRY(); break; default: /* Unknown node type */ elog(WARNING, "Unknown node type: %d", current->type); break; } if (*error) break; current = current->next; } } PG_CATCH(); { elog(WARNING, "Unhandled exception during template rendering"); FlushErrorState(); *error = true; return result.data; } PG_END_TRY(); elog(DEBUG1, "Template rendering completed successfully"); return result.data; } /* Helper functions for JSONB handling */ static char * get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_context); JsonbValue *jbv_result; JsonbValue key; JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; char *result = NULL; *found = false; if (!jb || !path) { elog(DEBUG1, "Null JSONB or path in get_jsonb_path_value"); return NULL; } /* Check if this is a valid Jsonb before proceeding */ if (!is_jsonb_container_valid(&jb->root)) { elog(WARNING, "Invalid JSONB container in get_jsonb_path_value"); return NULL; } /* Use PG_TRY/PG_CATCH to handle any errors during extraction */ PG_TRY(); { /* Handle simple top-level key */ if (strchr(path, '.') == NULL && strchr(path, '[') == NULL) { key.type = jbvString; key.val.string.val = (char *) path; key.val.string.len = strlen(path); jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); if (jbv_result) { *found = true; if (jbv_result->type == jbvString) { result = pnstrdup(jbv_result->val.string.val, jbv_result->val.string.len); elog(DEBUG1, "Found string value for key %s: %s", path, result); } else if (jbv_result->type == jbvNumeric) { Numeric num = jbv_result->val.numeric; result = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); elog(DEBUG1, "Found numeric value for key %s: %s", path, result); } else if (jbv_result->type == jbvBool) { result = pstrdup(jbv_result->val.boolean ? "true" : "false"); elog(DEBUG1, "Found boolean value for key %s: %s", path, result); } else if (jbv_result->type == jbvNull) { result = pstrdup(""); elog(DEBUG1, "Found null value for key %s", path); } else if (jbv_result->type == jbvBinary) { /* Check if it's an array first */ if (is_jsonb_container_valid((JsonbContainer *)&jbv_result->val.binary)) { it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary); token = JsonbIteratorNext(&it, &v, false); if (token == WJB_BEGIN_ARRAY) { /* For arrays, convert to "[Array]" placeholder */ result = pstrdup("[Array]"); elog(DEBUG1, "Found array value for key %s", path); } else if (token == WJB_BEGIN_OBJECT) { /* For objects, convert to "{Object}" placeholder */ result = pstrdup("{Object}"); elog(DEBUG1, "Found object value for key %s", path); } else { /* Convert binary type to string representation */ StringInfoData buf; initStringInfo(&buf); appendStringInfoString(&buf, "[Complex Value]"); result = buf.data; elog(DEBUG1, "Found complex value for key %s", path); } } else { result = pstrdup("[Invalid Binary]"); elog(DEBUG1, "Found invalid binary value for key %s", path); } } } else { elog(DEBUG1, "Key %s not found in object", path); } } else { /* Handle nested paths using a JSON path expression */ /* This is a simplified implementation */ elog(DEBUG1, "Complex path not fully supported: %s", path); result = pstrdup("[Complex Path]"); *found = true; } } PG_CATCH(); { elog(WARNING, "Exception while extracting value for path %s", path); FlushErrorState(); *found = false; if (result) { pfree(result); result = NULL; } } PG_END_TRY(); return result; } static Datum get_jsonb_array(Datum jsonb_context, const char *path, bool *found) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_context); JsonbValue *jbv_result; JsonbValue key; JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; Datum result = (Datum) 0; *found = false; if (!jb || !path) { elog(DEBUG1, "Null JSONB or path in get_jsonb_array"); return result; } /* Validate the container */ if (!is_jsonb_container_valid(&jb->root)) { elog(WARNING, "Invalid JSONB container in get_jsonb_array"); return result; } /* Handle simple top-level key */ if (strchr(path, '.') == NULL) { /* Use PG_TRY/PG_CATCH to handle any errors during iteration */ PG_TRY(); { key.type = jbvString; key.val.string.val = (char *) path; key.val.string.len = strlen(path); jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); if (jbv_result) { if (jbv_result->type == jbvBinary) { /* Validate the binary container before iterating */ if (!is_jsonb_container_valid((JsonbContainer *)&jbv_result->val.binary)) { elog(WARNING, "Invalid binary JSONB container for key: %s", path); return result; } it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary); token = JsonbIteratorNext(&it, &v, false); if (token == WJB_BEGIN_ARRAY) { *found = true; result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); elog(DEBUG1, "Found array at path %s", path); debug_jsonb_value(result, "Array"); } else { elog(DEBUG1, "Path %s exists but is not an array (token type: %d)", path, token); } } else { elog(DEBUG1, "Path %s exists but is not binary JSONB (type: %d)", path, jbv_result->type); } } else { elog(DEBUG1, "Path %s not found in JSONB", path); } } PG_CATCH(); { elog(WARNING, "Exception while processing array at path %s", path); FlushErrorState(); return result; } PG_END_TRY(); } else { /* Handle nested paths */ /* This is a simplified implementation */ char *current_path = pstrdup(path); char *token_str; char *saveptr; JsonbValue *current_jbv; /* Use PG_TRY/PG_CATCH to handle any errors during iteration */ PG_TRY(); { token_str = strtok_r(current_path, ".", &saveptr); while (token_str != NULL) { key.type = jbvString; key.val.string.val = token_str; key.val.string.len = strlen(token_str); current_jbv = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); if (!current_jbv) { elog(DEBUG1, "Path segment %s not found in nested path %s", token_str, path); pfree(current_path); return result; } token_str = strtok_r(NULL, ".", &saveptr); /* If there are more path segments, current value must be an object */ if (token_str != NULL) { if (current_jbv->type != jbvBinary) { elog(DEBUG1, "Path segment %s is not a binary JSONB object in nested path %s", token_str, path); pfree(current_path); return result; } /* Validate the binary container before using it */ if (!is_jsonb_container_valid((JsonbContainer *)¤t_jbv->val.binary)) { elog(WARNING, "Invalid binary JSONB container for path segment: %s", token_str); pfree(current_path); return result; } jb = (Jsonb *) DatumGetPointer(JsonbValueToJsonb(current_jbv)); /* Validate the new container */ if (!jb || !is_jsonb_container_valid(&jb->root)) { elog(WARNING, "Invalid JSONB container after path segment: %s", token_str); pfree(current_path); return result; } } else { /* Check if the final value is an array */ if (current_jbv->type == jbvBinary) { /* Validate the binary container before iterating */ if (!is_jsonb_container_valid((JsonbContainer *)¤t_jbv->val.binary)) { elog(WARNING, "Invalid binary JSONB container for nested path: %s", path); pfree(current_path); return result; } it = JsonbIteratorInit((JsonbContainer *)¤t_jbv->val.binary); token = JsonbIteratorNext(&it, &v, false); if (token == WJB_BEGIN_ARRAY) { *found = true; result = PointerGetDatum(JsonbValueToJsonb(current_jbv)); elog(DEBUG1, "Found array at nested path %s", path); debug_jsonb_value(result, "Array"); } else { elog(DEBUG1, "Nested path %s exists but is not an array (token type: %d)", path, token); } } else { elog(DEBUG1, "Nested path %s exists but is not binary JSONB (type: %d)", path, current_jbv->type); } } } } PG_CATCH(); { elog(WARNING, "Exception while processing nested array at path %s", path); if (current_path) pfree(current_path); FlushErrorState(); return result; } PG_END_TRY(); if (current_path) pfree(current_path); } return result; } static int get_jsonb_array_length(Datum jsonb_array) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_array); JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; int count = 0; if (!jb) { elog(DEBUG1, "Null JSONB array passed to get_jsonb_array_length"); return 0; } /* Check if this is a valid Jsonb before proceeding */ if (!is_jsonb_container_valid(&jb->root)) { elog(WARNING, "Invalid JSONB container in get_jsonb_array_length"); return 0; } it = JsonbIteratorInit((JsonbContainer *)&jb->root); /* Skip the WJB_BEGIN_ARRAY token */ token = JsonbIteratorNext(&it, &v, false); if (token != WJB_BEGIN_ARRAY) { elog(DEBUG1, "JSONB value is not an array (token=%d)", token); return 0; } /* Count array elements */ while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { if (token == WJB_ELEM) count++; } elog(DEBUG1, "Array length: %d", count); return count; } static Datum get_jsonb_array_element(Datum jsonb_array, int index) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_array); JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; int current_index = 0; Datum result = (Datum) 0; JsonbParseState *parse_state = NULL; JsonbValue *jbv_result; JsonbValue key; if (!jb || index < 0) { elog(DEBUG1, "Invalid array or index: array=%p, index=%d", jb, index); return result; } /* Check if this is a valid Jsonb before proceeding */ if (!is_jsonb_container_valid(&jb->root)) { elog(WARNING, "Invalid JSONB container in get_jsonb_array_element"); return result; } /* Use PG_TRY/PG_CATCH to handle any errors during iteration */ PG_TRY(); { it = JsonbIteratorInit((JsonbContainer *)&jb->root); /* Skip the WJB_BEGIN_ARRAY token */ token = JsonbIteratorNext(&it, &v, false); if (token != WJB_BEGIN_ARRAY) { elog(DEBUG1, "JSON value is not an array (token=%d)", token); return result; } /* Find the element at the specified index */ while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { if (token == WJB_ELEM) { if (current_index == index) { /* Found the element */ elog(DEBUG1, "Found array element at index %d (type=%d)", index, v.type); if (v.type == jbvBinary) { /* For binary values, just convert directly */ result = PointerGetDatum(JsonbValueToJsonb(&v)); debug_jsonb_value(result, "Array Element (Binary)"); } else if (v.type == jbvNull) { /* Handle null values by creating an empty object */ pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL); jbv_result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL); result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); elog(DEBUG1, "Converted null array element to empty object"); } else { /* For scalar values, we need to create a proper JSON value */ pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL); /* Add a dummy key "value" */ key.type = jbvString; key.val.string.val = "value"; key.val.string.len = 5; pushJsonbValue(&parse_state, WJB_KEY, &key); /* Add the value */ pushJsonbValue(&parse_state, WJB_VALUE, &v); /* Finish the object */ jbv_result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL); /* Convert to Jsonb */ result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); elog(DEBUG1, "Wrapped scalar array element in object with 'value' key"); } break; } current_index++; } } } PG_CATCH(); { elog(WARNING, "Exception while processing array element at index %d", index); FlushErrorState(); return (Datum) 0; } PG_END_TRY(); if (result == (Datum) 0) { elog(DEBUG1, "Array element at index %d not found (array length = %d)", index, current_index); } return result; } static Datum create_iterator_context(Datum parent_context, const char *iterator_name, Datum item_context) { Jsonb *parent_jb = (Jsonb *) DatumGetPointer(parent_context); Jsonb *item_jb = (Jsonb *) DatumGetPointer(item_context); JsonbParseState *parse_state = NULL; JsonbValue *result; JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; JsonbValue key; JsonbValue val; bool is_scalar = false; if (!parent_jb || !item_jb || !iterator_name) { elog(DEBUG1, "Invalid parameters for create_iterator_context: parent=%p, item=%p, iterator=%s", parent_jb, item_jb, iterator_name ? iterator_name : "(null)"); return parent_context; } /* Validate the containers */ if (!is_jsonb_container_valid(&parent_jb->root)) { elog(WARNING, "Invalid parent JSONB container in create_iterator_context"); return parent_context; } if (!is_jsonb_container_valid(&item_jb->root)) { elog(WARNING, "Invalid item JSONB container in create_iterator_context"); return parent_context; } elog(DEBUG1, "Creating iterator context with iterator name: %s", iterator_name); /* Start with a copy of the parent context */ pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL); /* Copy all fields from parent context */ it = JsonbIteratorInit((JsonbContainer *)&parent_jb->root); /* Skip the WJB_BEGIN_OBJECT token */ token = JsonbIteratorNext(&it, &v, false); if (token != WJB_BEGIN_OBJECT) { elog(WARNING, "Parent context is not an object (token=%d)", token); return parent_context; } /* Copy all key-value pairs from parent */ while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { if (token == WJB_KEY) { /* Copy the key */ key.type = jbvString; key.val.string.val = pnstrdup(v.val.string.val, v.val.string.len); key.val.string.len = v.val.string.len; pushJsonbValue(&parse_state, WJB_KEY, &key); /* Get and copy the value */ token = JsonbIteratorNext(&it, &val, false); pushJsonbValue(&parse_state, WJB_VALUE, &val); pfree(key.val.string.val); } } /* Add the iterator variable */ key.type = jbvString; key.val.string.val = (char *) iterator_name; key.val.string.len = strlen(iterator_name); pushJsonbValue(&parse_state, WJB_KEY, &key); /* Check if the item is a scalar value wrapped in an object */ it = JsonbIteratorInit((JsonbContainer *)&item_jb->root); token = JsonbIteratorNext(&it, &v, false); if (token == WJB_BEGIN_OBJECT) { elog(DEBUG1, "Item context is an object"); /* Check if it's our special scalar wrapper with "value" key */ token = JsonbIteratorNext(&it, &v, false); if (token == WJB_KEY && v.type == jbvString && v.val.string.len == 5 && strncmp(v.val.string.val, "value", 5) == 0) { /* Get the scalar value */ token = JsonbIteratorNext(&it, &v, false); if (token == WJB_VALUE) { is_scalar = true; elog(DEBUG1, "Found scalar value with 'value' key (type=%d)", v.type); pushJsonbValue(&parse_state, WJB_VALUE, &v); } } } else { elog(DEBUG1, "Item context is not an object (token=%d)", token); } /* If not a scalar, use the whole item as is */ if (!is_scalar) { elog(DEBUG1, "Using entire item as context value"); it = JsonbIteratorInit((JsonbContainer *)&item_jb->root); token = JsonbIteratorNext(&it, &v, false); pushJsonbValue(&parse_state, WJB_VALUE, &v); } /* Finalize the new context object */ result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL); /* Validate the result before returning */ Jsonb *result_jb = JsonbValueToJsonb(result); if (!result_jb || !is_jsonb_container_valid(&result_jb->root)) { elog(WARNING, "Created invalid JSONB container in create_iterator_context"); return parent_context; } return PointerGetDatum(result_jb); } static Datum get_jsonb_include_template(Datum jsonb_context, const char *key, bool *found) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_context); JsonbValue *jbv_result; JsonbValue search_key; Datum result = (Datum) 0; char *include_key; *found = false; if (!jb || !key) return result; /* Construct the include key */ include_key = psprintf("include %s", key); /* Look for the include template in the context */ search_key.type = jbvString; search_key.val.string.val = include_key; search_key.val.string.len = strlen(include_key); jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &search_key); if (jbv_result && jbv_result->type == jbvBinary) { *found = true; result = PointerGetDatum(JsonbValueToJsonb(jbv_result)); } pfree(include_key); return result; } static void get_include_data(Datum include_data, char **template_out, Datum *context_out) { Jsonb *jb = (Jsonb *) DatumGetPointer(include_data); JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; JsonbValue template_key, context_key; JsonbValue *template_jbv, *context_jbv; Jsonb *include_obj; *template_out = NULL; *context_out = (Datum) 0; if (!jb) return; /* Check if it's an array of include objects */ it = JsonbIteratorInit((JsonbContainer *)&jb->root); token = JsonbIteratorNext(&it, &v, false); if (token != WJB_BEGIN_ARRAY) return; /* Get the first element (we only support one include for now) */ token = JsonbIteratorNext(&it, &v, false); if (token != WJB_ELEM || v.type != jbvBinary) return; /* Get the include object */ include_obj = (Jsonb *) DatumGetPointer(JsonbValueToJsonb(&v)); /* Look for template and context keys */ template_key.type = jbvString; template_key.val.string.val = "template"; template_key.val.string.len = strlen("template"); context_key.type = jbvString; context_key.val.string.val = "context"; context_key.val.string.len = strlen("context"); template_jbv = findJsonbValueFromContainer(&include_obj->root, JB_FOBJECT, &template_key); /* Extract template string */ if (template_jbv && template_jbv->type == jbvString) { *template_out = pnstrdup(template_jbv->val.string.val, template_jbv->val.string.len); } context_jbv = findJsonbValueFromContainer(&include_obj->root, JB_FOBJECT, &context_key); /* Extract context object */ if (context_jbv && context_jbv->type == jbvBinary) { *context_out = PointerGetDatum(JsonbValueToJsonb(context_jbv)); } } /* Debug function to print JSONB structure */ static void debug_jsonb_value(Datum jsonb_value, const char *label) { Jsonb *jb = (Jsonb *) DatumGetPointer(jsonb_value); JsonbIterator *it; JsonbValue v; JsonbIteratorToken token; int depth = 0; if (!jb) { elog(DEBUG1, "%s: NULL JSONB value", label); return; } elog(DEBUG1, "%s: Starting JSONB debug", label); it = JsonbIteratorInit((JsonbContainer *)&jb->root); while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (token) { case WJB_BEGIN_ARRAY: elog(DEBUG1, "%s: %*sBegin Array", label, depth * 2, ""); depth++; break; case WJB_END_ARRAY: depth--; elog(DEBUG1, "%s: %*sEnd Array", label, depth * 2, ""); break; case WJB_BEGIN_OBJECT: elog(DEBUG1, "%s: %*sBegin Object", label, depth * 2, ""); depth++; break; case WJB_END_OBJECT: depth--; elog(DEBUG1, "%s: %*sEnd Object", label, depth * 2, ""); break; case WJB_KEY: elog(DEBUG1, "%s: %*sKey: %.*s", label, depth * 2, "", v.val.string.len, v.val.string.val); break; case WJB_VALUE: switch (v.type) { case jbvString: elog(DEBUG1, "%s: %*sString Value: %.*s", label, depth * 2, "", v.val.string.len, v.val.string.val); break; case jbvNumeric: { char *num_str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(v.val.numeric))); elog(DEBUG1, "%s: %*sNumeric Value: %s", label, depth * 2, "", num_str); pfree(num_str); } break; case jbvBool: elog(DEBUG1, "%s: %*sBoolean Value: %s", label, depth * 2, "", v.val.boolean ? "true" : "false"); break; case jbvNull: elog(DEBUG1, "%s: %*sNull Value", label, depth * 2, ""); break; case jbvBinary: elog(DEBUG1, "%s: %*sBinary Value", label, depth * 2, ""); break; default: elog(DEBUG1, "%s: %*sUnknown Value Type: %d", label, depth * 2, "", v.type); break; } break; case WJB_ELEM: elog(DEBUG1, "%s: %*sArray Element", label, depth * 2, ""); break; default: elog(DEBUG1, "%s: %*sUnknown Token: %d", label, depth * 2, "", token); break; } } elog(DEBUG1, "%s: Finished JSONB debug", label); } /* PostgreSQL function wrappers */ PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pg_template_parse); Datum pg_template_parse(PG_FUNCTION_ARGS) { text *template_text = PG_GETARG_TEXT_PP(0); char *template_str = text_to_cstring(template_text); const char *template_ptr = template_str; MemoryContext old_context; MemoryContext parse_context; TemplateConfig config; TemplateNode *root = NULL; TemplateErrorCode error_code = TEMPLATE_ERROR_NONE; text *result_text = NULL; StringInfoData result; /* Create a memory context for parsing */ parse_context = AllocSetContextCreate(CurrentMemoryContext, "Template Parse Context", ALLOCSET_DEFAULT_SIZES); /* Switch to the new context for parsing */ old_context = MemoryContextSwitchTo(parse_context); /* Initialize default config */ config = template_default_config(parse_context); PG_TRY(); { /* Parse the template */ root = template_parse(parse_context, &template_ptr, &config, false, &error_code); /* Check for parsing errors */ if (error_code != TEMPLATE_ERROR_NONE || !root) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Template parsing error: %s", template_error_to_string(error_code)))); } /* Convert the parsed template to a string representation for debugging */ initStringInfo(&result); appendStringInfo(&result, "Template parsed successfully. Structure:\n"); template_node_to_string(root, &result, 0); /* Switch back to the original memory context */ MemoryContextSwitchTo(old_context); /* Return the result */ result_text = cstring_to_text(result.data); pfree(result.data); } PG_CATCH(); { /* Switch back to the original memory context for error handling */ MemoryContextSwitchTo(old_context); /* Clean up */ if (template_str) pfree(template_str); /* Delete the parse context */ MemoryContextDelete(parse_context); PG_RE_THROW(); } PG_END_TRY(); /* Clean up */ MemoryContextDelete(parse_context); pfree(template_str); PG_RETURN_TEXT_P(result_text); } PG_FUNCTION_INFO_V1(pg_render); Datum pg_render(PG_FUNCTION_ARGS) { text *template_text = PG_GETARG_TEXT_PP(1); Jsonb *context_jsonb = PG_GETARG_JSONB_P(0); char *template_str = text_to_cstring(template_text); const char *template_ptr = template_str; MemoryContext old_context; MemoryContext render_context; TemplateConfig config; TemplateNode *root = NULL; TemplateErrorCode error_code = TEMPLATE_ERROR_NONE; bool render_error = false; char *rendered_result = NULL; text *result_text = NULL; /* Log the template and context for debugging */ elog(DEBUG1, "Template: %s", template_str); elog(DEBUG1, "Context JSONB: %p", context_jsonb); debug_jsonb_value(PointerGetDatum(context_jsonb), "Context"); /* Create a memory context for rendering */ render_context = AllocSetContextCreate(CurrentMemoryContext, "Template Render Context", ALLOCSET_DEFAULT_SIZES); /* Switch to the new context for parsing and rendering */ old_context = MemoryContextSwitchTo(render_context); /* Initialize default config */ config = template_default_config(render_context); PG_TRY(); { /* Parse the template */ root = template_parse(render_context, &template_ptr, &config, false, &error_code); /* Check for parsing errors */ if (error_code != TEMPLATE_ERROR_NONE || !root) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Template parsing error: %s", template_error_to_string(error_code)))); } elog(DEBUG1, "Template parsed successfully, starting render"); /* Render the template with the provided context */ rendered_result = template_render(render_context, root, PointerGetDatum(context_jsonb), &render_error); /* Check for rendering errors */ if (render_error || !rendered_result) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Template rendering error"))); } elog(DEBUG1, "Template rendered successfully: %s", rendered_result); /* Convert the result to a text datum in the original memory context */ MemoryContextSwitchTo(old_context); result_text = cstring_to_text(rendered_result); } PG_CATCH(); { /* Switch back to the original memory context for error handling */ MemoryContextSwitchTo(old_context); /* Clean up */ if (template_str) pfree(template_str); /* Delete the render context */ MemoryContextDelete(render_context); PG_RE_THROW(); } PG_END_TRY(); /* Clean up */ MemoryContextDelete(render_context); pfree(template_str); PG_RETURN_TEXT_P(result_text); } /* Helper function to convert template node to string for debugging */ static void template_node_to_string(TemplateNode *node, StringInfo result, int indent) { TemplateNode *current = node; int i; while (current) { /* Add indentation */ for (i = 0; i < indent; i++) appendStringInfoChar(result, ' '); /* Add node type */ switch (current->type) { case TEMPLATE_NODE_TEXT: appendStringInfo(result, "TEXT: \"%s\"\n", current->value->text.content ? current->value->text.content : ""); break; case TEMPLATE_NODE_INTERPOLATE: appendStringInfo(result, "INTERPOLATE: \"%s\"\n", current->value->interpolate.key ? current->value->interpolate.key : ""); break; case TEMPLATE_NODE_SECTION: appendStringInfo(result, "SECTION: iterator=\"%s\", collection=\"%s\"\n", current->value->section.iterator ? current->value->section.iterator : "", current->value->section.collection ? current->value->section.collection : ""); if (current->value->section.body) { template_node_to_string(current->value->section.body, result, indent + 2); } break; case TEMPLATE_NODE_EXECUTE: appendStringInfo(result, "EXECUTE: \"%s\"\n", current->value->execute.code ? current->value->execute.code : ""); break; case TEMPLATE_NODE_INCLUDE: appendStringInfo(result, "INCLUDE: \"%s\"\n", current->value->include.key ? current->value->include.key : ""); break; default: appendStringInfo(result, "UNKNOWN NODE TYPE: %d\n", current->type); break; } current = current->next; } }