feat!: some section parse issue

This commit is contained in:
2025-05-13 15:44:55 +00:00
parent 7f58985c41
commit e260e8f7cc
2 changed files with 987 additions and 34 deletions

View File

@@ -1,31 +1,32 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION -- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION hemar" to load this file. \quit \echo Use "CREATE EXTENSION hemar" to load this file. \quit
CREATE SCHEMA hemar; CREATE SCHEMA hemar;
-- Define the parse_text_with_hectic function that uses hectic library -- Define the template rendering functions using hectic library
-- Expected usage: -- Expected usage:
-- ```sql -- ```sql
-- SELECT "hemar"."render"( -- SELECT "hemar"."render"(
-- "declare" := -- "declare" :=
-- jsonb_build_object( -- jsonb_build_object(
-- 'name', 'test', -- 'name', 'test',
-- 'config', jsonb_build_object( -- 'config', jsonb_build_object(
-- 'debug', true, -- 'debug', true,
-- 'limit', 100 -- 'limit', 100
-- ) -- )
-- ), -- ),
-- "template" := $hemar$ -- "template" := $hemar$
-- {{ name }} {{ config.limit }} -- {{ name }} {{ config.limit }}
-- $hemar$ -- $hemar$
-- ); -- );
-- ``` -- ```
--CREATE FUNCTION "hemar"."render"("declare" jsonb, "template" text) CREATE FUNCTION "hemar"."render"("declare" jsonb, "template" text)
--RETURNS text RETURNS text
--LANGUAGE C STRICT LANGUAGE C STRICT
--AS 'hemar', 'pg_render'; AS 'hemar', 'pg_render';
CREATE FUNCTION "hemar"."parse"("template" text) -- Parse function returns the structure of a template for debugging
RETURNS text CREATE FUNCTION "hemar"."parse"("template" text)
LANGUAGE C STRICT RETURNS text
AS 'hemar', 'pg_template_parse'; LANGUAGE C STRICT
AS 'hemar', 'pg_template_parse';

View File

@@ -11,6 +11,23 @@
#include "utils/elog.h" #include "utils/elog.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "mb/pg_wchar.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);
/* Helper function to skip whitespace */ /* Helper function to skip whitespace */
static const char * static const char *
@@ -515,6 +532,8 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
const char *start; const char *start;
TemplateNode *root, *current, *tag_node; TemplateNode *root, *current, *tag_node;
bool current_node_filled = false; bool current_node_filled = false;
const char *tag_prefix;
size_t text_len;
if (!template_validate_config(config, error_code)) if (!template_validate_config(config, error_code))
{ {
@@ -550,14 +569,14 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
*current->value = init_template_value(TEMPLATE_NODE_TEXT); *current->value = init_template_value(TEMPLATE_NODE_TEXT);
} }
size_t text_len = *s - start; text_len = *s - start;
current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len));
current_node_filled = true; current_node_filled = true;
} }
/* Parse the tag */ /* Parse the tag */
tag_node = NULL; tag_node = NULL;
const char *tag_prefix = *s + strlen(config->Syntax.Braces.open); tag_prefix = *s + strlen(config->Syntax.Braces.open);
tag_prefix = skip_whitespace(tag_prefix); tag_prefix = skip_whitespace(tag_prefix);
/* Determine tag type by prefix */ /* Determine tag type by prefix */
@@ -626,7 +645,7 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
*current->value = init_template_value(TEMPLATE_NODE_TEXT); *current->value = init_template_value(TEMPLATE_NODE_TEXT);
} }
size_t text_len = *s - start; text_len = *s - start;
current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len));
current_node_filled = true; current_node_filled = true;
} }
@@ -704,4 +723,937 @@ template_free_node(TemplateNode *node)
pfree(current); pfree(current);
current = next; 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;
while (current)
{
switch (current->type)
{
case TEMPLATE_NODE_TEXT:
if (current->value->text.content)
appendStringInfoString(&result, current->value->text.content);
break;
case TEMPLATE_NODE_INTERPOLATE:
{
char *value = NULL;
bool found = false;
if (current->value->interpolate.key)
{
/* Extract value from JSONB context */
value = get_jsonb_path_value(jsonb_context, current->value->interpolate.key, &found);
if (found && value)
{
appendStringInfoString(&result, value);
pfree(value);
}
}
}
break;
case TEMPLATE_NODE_SECTION:
{
/* Handle sections (loops) */
char *collection_path = current->value->section.collection;
Datum array_value;
bool found = false;
int array_length;
int i;
if (collection_path && current->value->section.body)
{
/* Get array from context */
array_value = get_jsonb_array(jsonb_context, collection_path, &found);
if (found)
{
array_length = get_jsonb_array_length(array_value);
/* Handle empty arrays gracefully */
if (array_length <= 0)
break;
for (i = 0; i < array_length; i++)
{
Datum item_context = get_jsonb_array_element(array_value, i);
Datum merged_context;
char *item_result;
bool item_error = false;
if (item_context == (Datum) 0)
{
/* Create an empty object for null array elements */
JsonbParseState *parse_state = NULL;
JsonbValue *empty_obj;
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 */
merged_context = create_iterator_context(jsonb_context, current->value->section.iterator, item_context);
if (merged_context == (Datum) 0)
continue;
/* Render section body with new context */
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)
{
*error = true;
return result.data;
}
}
}
}
}
break;
case TEMPLATE_NODE_INCLUDE:
{
/* Handle includes */
char *template_key = current->value->include.key;
Datum include_data;
bool found = false;
if (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
{
*error = true;
}
template_free_node(include_node);
}
else
{
*error = true;
}
pfree(include_template);
}
}
}
}
break;
case TEMPLATE_NODE_EXECUTE:
/* Execute is not implemented in this version */
break;
default:
/* Unknown node type */
break;
}
if (*error)
break;
current = current->next;
}
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;
JsonbIteratorToken token;
char *result = NULL;
*found = false;
if (!jb || !path)
return NULL;
/* 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);
}
else if (jbv_result->type == jbvNumeric)
{
Numeric num = jbv_result->val.numeric;
result = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num)));
}
else if (jbv_result->type == jbvBool)
{
result = pstrdup(jbv_result->val.boolean ? "true" : "false");
}
else if (jbv_result->type == jbvNull)
{
result = pstrdup("");
}
else if (jbv_result->type == jbvBinary)
{
/* Convert binary type to string representation */
StringInfoData buf;
JsonbValue v;
initStringInfo(&buf);
it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary);
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
if (token == WJB_VALUE)
{
if (v.type == jbvString)
{
appendBinaryStringInfo(&buf, v.val.string.val, v.val.string.len);
}
else if (v.type == jbvNumeric)
{
Numeric num = v.val.numeric;
char *numstr = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num)));
appendStringInfoString(&buf, numstr);
pfree(numstr);
}
else if (v.type == jbvBool)
{
appendStringInfoString(&buf, v.val.boolean ? "true" : "false");
}
}
}
result = buf.data;
}
}
}
else
{
/* Handle nested paths using a JSON path expression */
/* This is a simplified implementation and would need to be expanded for a full solution */
char *current_path = pstrdup(path);
char *token;
char *saveptr;
JsonbValue *current_jbv;
token = strtok_r(current_path, ".", &saveptr);
while (token != NULL)
{
key.type = jbvString;
key.val.string.val = token;
key.val.string.len = strlen(token);
current_jbv = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key);
if (!current_jbv)
{
pfree(current_path);
return NULL;
}
token = strtok_r(NULL, ".", &saveptr);
/* If there are more path segments, current value must be an object */
if (token != NULL)
{
if (current_jbv->type != jbvBinary)
{
pfree(current_path);
return NULL;
}
jb = (Jsonb *) DatumGetPointer(JsonbValueToJsonb(current_jbv));
}
else
{
/* We found the value */
*found = true;
if (current_jbv->type == jbvString)
{
result = pnstrdup(current_jbv->val.string.val, current_jbv->val.string.len);
}
else if (current_jbv->type == jbvNumeric)
{
Numeric num = current_jbv->val.numeric;
result = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num)));
}
else if (current_jbv->type == jbvBool)
{
result = pstrdup(current_jbv->val.boolean ? "true" : "false");
}
else if (current_jbv->type == jbvNull)
{
result = pstrdup("");
}
}
}
pfree(current_path);
}
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;
Datum result = (Datum) 0;
*found = false;
if (!jb || !path)
return result;
/* Handle simple top-level key */
if (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 && jbv_result->type == jbvBinary)
{
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken token;
it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary);
token = JsonbIteratorNext(&it, &v, false);
if (token == WJB_BEGIN_ARRAY)
{
*found = true;
result = PointerGetDatum(JsonbValueToJsonb(jbv_result));
}
}
}
else
{
/* Handle nested paths */
/* This is a simplified implementation */
char *current_path = pstrdup(path);
char *token;
char *saveptr;
JsonbValue *current_jbv;
token = strtok_r(current_path, ".", &saveptr);
while (token != NULL)
{
key.type = jbvString;
key.val.string.val = token;
key.val.string.len = strlen(token);
current_jbv = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key);
if (!current_jbv)
{
pfree(current_path);
return result;
}
token = strtok_r(NULL, ".", &saveptr);
/* If there are more path segments, current value must be an object */
if (token != NULL)
{
if (current_jbv->type != jbvBinary)
{
pfree(current_path);
return result;
}
jb = (Jsonb *) DatumGetPointer(JsonbValueToJsonb(current_jbv));
}
else
{
/* Check if the final value is an array */
if (current_jbv->type == jbvBinary)
{
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken token;
it = JsonbIteratorInit((JsonbContainer *)&current_jbv->val.binary);
token = JsonbIteratorNext(&it, &v, false);
if (token == WJB_BEGIN_ARRAY)
{
*found = true;
result = PointerGetDatum(JsonbValueToJsonb(current_jbv));
}
}
}
}
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)
return 0;
it = JsonbIteratorInit((JsonbContainer *)&jb->root);
/* Skip the WJB_BEGIN_ARRAY token */
token = JsonbIteratorNext(&it, &v, false);
if (token != WJB_BEGIN_ARRAY)
return 0;
/* Count array elements */
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
if (token == WJB_ELEM)
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;
if (!jb || index < 0)
return result;
it = JsonbIteratorInit((JsonbContainer *)&jb->root);
/* Skip the WJB_BEGIN_ARRAY token */
token = JsonbIteratorNext(&it, &v, false);
if (token != WJB_BEGIN_ARRAY)
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 */
if (v.type == jbvBinary)
{
/* For binary values, just convert directly */
result = PointerGetDatum(JsonbValueToJsonb(&v));
}
else
{
/* For scalar values, we need to create a proper JSON value */
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
/* Add a dummy key "value" */
JsonbValue key;
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));
}
break;
}
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)
return parent_context;
/* 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)
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)
{
/* 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;
pushJsonbValue(&parse_state, WJB_VALUE, &v);
}
}
}
/* If not a scalar, use the whole item as is */
if (!is_scalar)
{
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);
return PointerGetDatum(JsonbValueToJsonb(result));
}
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;
*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 */
Jsonb *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));
}
}
/* 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;
/* 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))));
}
/* 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")));
}
/* 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;
}
} }