feat: something
This commit is contained in:
@@ -884,509 +884,498 @@ template_render(MemoryContext context, TemplateNode *node, Datum jsonb_context,
|
|||||||
|
|
||||||
elog(DEBUG1, "Starting template rendering");
|
elog(DEBUG1, "Starting template rendering");
|
||||||
|
|
||||||
/* Use PG_TRY/PG_CATCH to handle errors during the entire rendering process */
|
|
||||||
PG_TRY();
|
PG_TRY();
|
||||||
{
|
{
|
||||||
while (current)
|
while (current)
|
||||||
{
|
{
|
||||||
PG_TRY();
|
switch (current->type)
|
||||||
{
|
{
|
||||||
switch (current->type)
|
case TEMPLATE_NODE_TEXT:
|
||||||
{
|
/* Process text node */
|
||||||
case TEMPLATE_NODE_TEXT:
|
elog(DEBUG1, "");
|
||||||
/* Process text node */
|
elog(DEBUG1, "> TEXT");
|
||||||
elog(DEBUG1, "");
|
if (current->value->text.content)
|
||||||
elog(DEBUG1, "> TEXT");
|
{
|
||||||
if (current->value->text.content)
|
elog(DEBUG1, "N*TEXT: Rendering text node: %s", current->value->text.content);
|
||||||
{
|
appendStringInfoString(&result, current->value->text.content);
|
||||||
elog(DEBUG1, "N*TEXT: Rendering text node: %s", current->value->text.content);
|
}
|
||||||
appendStringInfoString(&result, current->value->text.content);
|
break;
|
||||||
}
|
|
||||||
break;
|
case TEMPLATE_NODE_INTERPOLATE:
|
||||||
|
/* Process interpolation node */
|
||||||
case TEMPLATE_NODE_INTERPOLATE:
|
elog(DEBUG1, "");
|
||||||
/* Process interpolation node */
|
elog(DEBUG1, "> INTERPOLATE");
|
||||||
elog(DEBUG1, "");
|
|
||||||
elog(DEBUG1, "> INTERPOLATE");
|
|
||||||
|
|
||||||
char *value = NULL;
|
char *value = NULL;
|
||||||
bool found_interpolate = false;
|
bool found_interpolate = false;
|
||||||
|
|
||||||
|
if (current->value->interpolate.key)
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*INTR: Processing interpolation for key: %s", current->value->interpolate.key);
|
||||||
|
|
||||||
if (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_interpolate);
|
||||||
|
|
||||||
|
if (found_interpolate && value)
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*INTR: Processing interpolation for key: %s", current->value->interpolate.key);
|
elog(DEBUG1, "N*INTR: 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;
|
||||||
|
|
||||||
/* First try to get as a direct path */
|
array_value = get_jsonb_array(jsonb_context, current->value->interpolate.key, &array_found);
|
||||||
/* Extract value from JSONB context */
|
|
||||||
value = get_jsonb_path_value(jsonb_context, current->value->interpolate.key, &found_interpolate);
|
|
||||||
|
|
||||||
if (found_interpolate && value)
|
if (array_found)
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*INTR: Found value for key %s: %s", current->value->interpolate.key, value);
|
/* Convert array to string representation */
|
||||||
appendStringInfoString(&result, value);
|
elog(DEBUG1, "N*INTR: Found array for key %s, converting to string", current->value->interpolate.key);
|
||||||
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);
|
/* Create a string representation of the array */
|
||||||
|
StringInfoData array_str;
|
||||||
|
initStringInfo(&array_str);
|
||||||
|
appendStringInfoString(&array_str, "[");
|
||||||
|
|
||||||
if (array_found)
|
Jsonb *array_jb = (Jsonb *) DatumGetPointer(array_value);
|
||||||
|
if (array_jb && is_jsonb_container_valid(&array_jb->root))
|
||||||
{
|
{
|
||||||
/* Convert array to string representation */
|
/* Check if it's actually an array */
|
||||||
elog(DEBUG1, "N*INTR: Found array for key %s, converting to string", current->value->interpolate.key);
|
JsonbContainer *jc = &array_jb->root;
|
||||||
|
if (!(jc->header & JB_FARRAY))
|
||||||
/* 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))
|
|
||||||
{
|
{
|
||||||
/* Check if it's actually an array */
|
elog(DEBUG1, "N*INTR: JSONB value is not an array");
|
||||||
JsonbContainer *jc = &array_jb->root;
|
appendStringInfoString(&array_str, "[Not an array]");
|
||||||
if (!(jc->header & JB_FARRAY))
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Iterate through array elements */
|
||||||
|
JsonbIterator *it = JsonbIteratorInit(jc);
|
||||||
|
JsonbValue v;
|
||||||
|
JsonbIteratorToken token;
|
||||||
|
bool first_element = true;
|
||||||
|
|
||||||
|
/* Skip the WJB_BEGIN_ARRAY token */
|
||||||
|
token = JsonbIteratorNext(&it, &v, false);
|
||||||
|
|
||||||
|
/* Process each array element */
|
||||||
|
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*INTR: JSONB value is not an array");
|
if (token != WJB_ELEM)
|
||||||
appendStringInfoString(&array_str, "[Not an array]");
|
continue;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Iterate through array elements */
|
|
||||||
JsonbIterator *it = JsonbIteratorInit(jc);
|
|
||||||
JsonbValue v;
|
|
||||||
JsonbIteratorToken token;
|
|
||||||
bool first_element = true;
|
|
||||||
|
|
||||||
/* Skip the WJB_BEGIN_ARRAY token */
|
if (!first_element)
|
||||||
token = JsonbIteratorNext(&it, &v, false);
|
appendStringInfoString(&array_str, ", ");
|
||||||
|
else
|
||||||
|
first_element = false;
|
||||||
|
|
||||||
/* Process each array element */
|
if (v.type == jbvString)
|
||||||
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
|
|
||||||
{
|
{
|
||||||
if (token != WJB_ELEM)
|
appendStringInfoChar(&array_str, '"');
|
||||||
continue;
|
appendBinaryStringInfo(&array_str, v.val.string.val, v.val.string.len);
|
||||||
|
appendStringInfoChar(&array_str, '"');
|
||||||
|
}
|
||||||
|
else if (v.type == jbvNumeric)
|
||||||
|
{
|
||||||
|
char *num_str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(v.val.numeric)));
|
||||||
|
appendStringInfoString(&array_str, num_str);
|
||||||
|
pfree(num_str);
|
||||||
|
}
|
||||||
|
else if (v.type == jbvBool)
|
||||||
|
{
|
||||||
|
appendStringInfoString(&array_str, v.val.boolean ? "true" : "false");
|
||||||
|
}
|
||||||
|
else if (v.type == jbvNull)
|
||||||
|
{
|
||||||
|
appendStringInfoString(&array_str, "null");
|
||||||
|
}
|
||||||
|
else if (v.type == jbvBinary)
|
||||||
|
{
|
||||||
|
/* For complex values, convert to string */
|
||||||
|
Datum elem = PointerGetDatum(JsonbValueToJsonb(&v));
|
||||||
|
bool elem_found = false;
|
||||||
|
char *elem_str = get_jsonb_path_value(elem, "value", &elem_found);
|
||||||
|
|
||||||
if (!first_element)
|
if (elem_found && elem_str)
|
||||||
appendStringInfoString(&array_str, ", ");
|
{
|
||||||
|
appendStringInfoString(&array_str, elem_str);
|
||||||
|
pfree(elem_str);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
first_element = false;
|
|
||||||
|
|
||||||
if (v.type == jbvString)
|
|
||||||
{
|
{
|
||||||
appendStringInfoChar(&array_str, '"');
|
appendStringInfoString(&array_str, "[Complex Value]");
|
||||||
appendBinaryStringInfo(&array_str, v.val.string.val, v.val.string.len);
|
|
||||||
appendStringInfoChar(&array_str, '"');
|
|
||||||
}
|
|
||||||
else if (v.type == jbvNumeric)
|
|
||||||
{
|
|
||||||
char *num_str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(v.val.numeric)));
|
|
||||||
appendStringInfoString(&array_str, num_str);
|
|
||||||
pfree(num_str);
|
|
||||||
}
|
|
||||||
else if (v.type == jbvBool)
|
|
||||||
{
|
|
||||||
appendStringInfoString(&array_str, v.val.boolean ? "true" : "false");
|
|
||||||
}
|
|
||||||
else if (v.type == jbvNull)
|
|
||||||
{
|
|
||||||
appendStringInfoString(&array_str, "null");
|
|
||||||
}
|
|
||||||
else if (v.type == jbvBinary)
|
|
||||||
{
|
|
||||||
/* For complex values, convert to string */
|
|
||||||
Datum elem = PointerGetDatum(JsonbValueToJsonb(&v));
|
|
||||||
bool elem_found = false;
|
|
||||||
char *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, "[Complex Value]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfoString(&array_str, "]");
|
|
||||||
appendStringInfoString(&result, array_str.data);
|
|
||||||
pfree(array_str.data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elog(DEBUG1, "N*INTR: Key %s not found in context", current->value->interpolate.key);
|
|
||||||
/* Optionally append something to indicate missing key */
|
|
||||||
appendStringInfoString(&result, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendStringInfoString(&array_str, "]");
|
||||||
|
appendStringInfoString(&result, array_str.data);
|
||||||
|
pfree(array_str.data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*INTR: Key %s not found in context", current->value->interpolate.key);
|
||||||
|
/* Optionally append something to indicate missing key */
|
||||||
|
appendStringInfoString(&result, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case TEMPLATE_NODE_SECTION:
|
}
|
||||||
elog(DEBUG1, "");
|
case TEMPLATE_NODE_SECTION:
|
||||||
elog(DEBUG1, "> SECTION");
|
elog(DEBUG1, "");
|
||||||
/* Handle sections (loops) */
|
elog(DEBUG1, "> SECTION");
|
||||||
char *collection_path = current->value->section.collection;
|
/* Handle sections (loops) */
|
||||||
Datum array_value;
|
char *collection_path = current->value->section.collection;
|
||||||
bool found_section = false;
|
Datum array_value;
|
||||||
int array_length;
|
bool found_section = false;
|
||||||
int i;
|
int array_length;
|
||||||
JsonbParseState *parse_state = NULL;
|
int i;
|
||||||
JsonbValue *empty_obj;
|
JsonbParseState *parse_state = NULL;
|
||||||
Datum item_context;
|
JsonbValue *empty_obj;
|
||||||
Datum merged_context;
|
Datum item_context;
|
||||||
char *item_result;
|
Datum merged_context;
|
||||||
bool item_error = false;
|
char *item_result;
|
||||||
|
bool item_error = false;
|
||||||
|
|
||||||
|
if (collection_path)
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*SECT: Processing section with collection path: %s", collection_path);
|
||||||
|
|
||||||
if (collection_path)
|
/* Use the improved get_jsonb_array function that handles nested paths */
|
||||||
|
array_value = get_jsonb_array(jsonb_context, collection_path, &found_section);
|
||||||
|
|
||||||
|
if (found_section)
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*SECT: Processing section with collection path: %s", collection_path);
|
elog(DEBUG1, "N*SECT: Found array for section: %s", collection_path);
|
||||||
|
|
||||||
/* Use the improved get_jsonb_array function that handles nested paths */
|
/* Make sure we have a valid array */
|
||||||
array_value = get_jsonb_array(jsonb_context, collection_path, &found_section);
|
Jsonb *array_jb = (Jsonb *) DatumGetPointer(array_value);
|
||||||
|
if (!array_jb || !is_jsonb_container_valid(&array_jb->root))
|
||||||
if (found_section)
|
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*SECT: Found array for section: %s", collection_path);
|
elog(WARNING, "N*SECT: Invalid JSONB array container for path: %s", collection_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if it's actually an array */
|
||||||
|
JsonbContainer *jc = &array_jb->root;
|
||||||
|
if (!(jc->header & JB_FARRAY))
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*SECT: JSONB value is not an array");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If section body is empty, nothing to do */
|
||||||
|
if (current->value->section.body == NULL)
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*SECT: Section body is empty, skipping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
elog(DEBUG1, "N*SECT: Rendering section body for each array element");
|
||||||
|
|
||||||
|
/* Log the section body structure for debugging */
|
||||||
|
if (current->value->section.body)
|
||||||
|
{
|
||||||
|
StringInfoData section_info;
|
||||||
|
initStringInfo(§ion_info);
|
||||||
|
template_node_to_string(current->value->section.body, §ion_info, 0);
|
||||||
|
elog(DEBUG1, "N*SECT: Section body structure: %s", section_info.data);
|
||||||
|
pfree(section_info.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate through array elements */
|
||||||
|
JsonbIterator *it = JsonbIteratorInit(jc);
|
||||||
|
JsonbValue v;
|
||||||
|
JsonbIteratorToken token;
|
||||||
|
int i = 0;
|
||||||
|
int nesting_level = 0;
|
||||||
|
bool in_element = false;
|
||||||
|
JsonbParseState *element_state = NULL;
|
||||||
|
JsonbValue *element_value = NULL;
|
||||||
|
|
||||||
|
/* Skip the WJB_BEGIN_ARRAY token */
|
||||||
|
token = JsonbIteratorNext(&it, &v, false);
|
||||||
|
elog(DEBUG1, "N*SECT: Iterator started, first token: %d", token);
|
||||||
|
|
||||||
|
/* Process each array element */
|
||||||
|
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*SECT: Token: %d, Type: %s, Nesting: %d",
|
||||||
|
token, jbv_type_to_string(v.type), nesting_level);
|
||||||
|
|
||||||
/* Make sure we have a valid array */
|
/* Handle array elements */
|
||||||
Jsonb *array_jb = (Jsonb *) DatumGetPointer(array_value);
|
if (token == WJB_ELEM)
|
||||||
if (!array_jb || !is_jsonb_container_valid(&array_jb->root))
|
|
||||||
{
|
{
|
||||||
elog(WARNING, "N*SECT: Invalid JSONB array container for path: %s", collection_path);
|
item_context = (Datum) 0;
|
||||||
break;
|
item_error = false;
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if it's actually an array */
|
|
||||||
JsonbContainer *jc = &array_jb->root;
|
|
||||||
if (!(jc->header & JB_FARRAY))
|
|
||||||
{
|
|
||||||
elog(DEBUG1, "N*SECT: JSONB value is not an array");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If section body is empty, nothing to do */
|
|
||||||
if (current->value->section.body == NULL)
|
|
||||||
{
|
|
||||||
elog(DEBUG1, "N*SECT: Section body is empty, skipping");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
elog(DEBUG1, "N*SECT: Rendering section body for each array element");
|
|
||||||
|
|
||||||
/* Log the section body structure for debugging */
|
|
||||||
if (current->value->section.body)
|
|
||||||
{
|
|
||||||
StringInfoData section_info;
|
|
||||||
initStringInfo(§ion_info);
|
|
||||||
template_node_to_string(current->value->section.body, §ion_info, 0);
|
|
||||||
elog(DEBUG1, "N*SECT: Section body structure: %s", section_info.data);
|
|
||||||
pfree(section_info.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iterate through array elements */
|
|
||||||
JsonbIterator *it = JsonbIteratorInit(jc);
|
|
||||||
JsonbValue v;
|
|
||||||
JsonbIteratorToken token;
|
|
||||||
int i = 0;
|
|
||||||
int nesting_level = 0;
|
|
||||||
bool in_element = false;
|
|
||||||
JsonbParseState *element_state = NULL;
|
|
||||||
JsonbValue *element_value = NULL;
|
|
||||||
|
|
||||||
/* Skip the WJB_BEGIN_ARRAY token */
|
|
||||||
token = JsonbIteratorNext(&it, &v, false);
|
|
||||||
elog(DEBUG1, "N*SECT: Iterator started, first token: %d", token);
|
|
||||||
|
|
||||||
/* Process each array element */
|
|
||||||
while ((token = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
|
|
||||||
{
|
|
||||||
elog(DEBUG1, "N*SECT: Token: %d, Type: %s, Nesting: %d",
|
|
||||||
token, jbv_type_to_string(v.type), nesting_level);
|
|
||||||
|
|
||||||
/* Handle array elements */
|
elog(DEBUG1, "N*SECT: Processing array element %d", i);
|
||||||
if (token == WJB_ELEM)
|
|
||||||
|
/* Convert the JsonbValue to a Datum */
|
||||||
|
PG_TRY();
|
||||||
{
|
{
|
||||||
item_context = (Datum) 0;
|
if (v.type == jbvBinary)
|
||||||
item_error = false;
|
|
||||||
|
|
||||||
elog(DEBUG1, "N*SECT: Processing array element %d", i);
|
|
||||||
|
|
||||||
/* Convert the JsonbValue to a Datum */
|
|
||||||
PG_TRY();
|
|
||||||
{
|
{
|
||||||
if (v.type == jbvBinary)
|
/* For binary values, just convert directly */
|
||||||
|
item_context = PointerGetDatum(JsonbValueToJsonb(&v));
|
||||||
|
}
|
||||||
|
else if (v.type == jbvNull)
|
||||||
|
{
|
||||||
|
/* Handle null values by creating an empty object */
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* For scalar values, create a proper JSON object */
|
||||||
|
parse_state = NULL;
|
||||||
|
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 */
|
||||||
|
empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
||||||
|
|
||||||
|
/* Convert to Jsonb */
|
||||||
|
item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process this element */
|
||||||
|
process_array_element:
|
||||||
|
|
||||||
|
/* 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))
|
||||||
{
|
{
|
||||||
/* For binary values, just convert directly */
|
elog(DEBUG1, "N*SECT: Got valid array element %d", i);
|
||||||
item_context = PointerGetDatum(JsonbValueToJsonb(&v));
|
elog(DEBUG1, "N*SECT: Array Element: %s", JsonbToCString(NULL, &item_jb->root, VARSIZE_ANY_EXHDR(item_jb)));
|
||||||
}
|
|
||||||
else if (v.type == jbvNull)
|
/* Create context with iterator variable */
|
||||||
{
|
PG_TRY();
|
||||||
/* Handle null values by creating an empty object */
|
{
|
||||||
parse_state = NULL;
|
merged_context = create_iterator_context(jsonb_context, current->value->section.iterator, item_context);
|
||||||
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
|
}
|
||||||
empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
PG_CATCH();
|
||||||
item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj));
|
{
|
||||||
|
elog(WARNING, "N*SECT: 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, "N*SECT: Failed to create merged context for array element %d", i);
|
||||||
|
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, "N*SECT: Error rendering template section for array element %d", i);
|
||||||
|
*error = true;
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PG_CATCH();
|
||||||
|
{
|
||||||
|
elog(WARNING, "N*SECT: Exception during template rendering for array element %d", i);
|
||||||
|
/* Continue with next element */
|
||||||
|
FlushErrorState();
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* For scalar values, create a proper JSON object */
|
elog(WARNING, "N*SECT: Got invalid JSONB container for array element %d", i);
|
||||||
parse_state = NULL;
|
|
||||||
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 */
|
|
||||||
empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
|
||||||
|
|
||||||
/* Convert to Jsonb */
|
|
||||||
item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process this element */
|
|
||||||
process_array_element:
|
|
||||||
|
|
||||||
/* 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, "N*SECT: Got valid array element %d", i);
|
|
||||||
elog(DEBUG1, "N*SECT: Array Element: %s", JsonbToCString(NULL, &item_jb->root, VARSIZE_ANY_EXHDR(item_jb)));
|
|
||||||
|
|
||||||
/* Create context with iterator variable */
|
|
||||||
PG_TRY();
|
|
||||||
{
|
|
||||||
merged_context = create_iterator_context(jsonb_context, current->value->section.iterator, item_context);
|
|
||||||
}
|
|
||||||
PG_CATCH();
|
|
||||||
{
|
|
||||||
elog(WARNING, "N*SECT: 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, "N*SECT: Failed to create merged context for array element %d", i);
|
|
||||||
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, "N*SECT: Error rendering template section for array element %d", i);
|
|
||||||
*error = true;
|
|
||||||
return result.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PG_CATCH();
|
|
||||||
{
|
|
||||||
elog(WARNING, "N*SECT: Exception during template rendering for array element %d", i);
|
|
||||||
/* Continue with next element */
|
|
||||||
FlushErrorState();
|
|
||||||
}
|
|
||||||
PG_END_TRY();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elog(WARNING, "N*SECT: Got invalid JSONB container for array element %d", i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elog(DEBUG1, "N*SECT: 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));
|
|
||||||
goto process_array_element;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
else
|
||||||
{
|
{
|
||||||
elog(WARNING, "N*SECT: Error processing array element %d, creating empty object instead", i);
|
elog(DEBUG1, "N*SECT: Array element %d is null, creating empty object", i);
|
||||||
/* Create an empty object for problematic array elements */
|
/* Create an empty object for null array elements */
|
||||||
parse_state = NULL;
|
parse_state = NULL;
|
||||||
|
|
||||||
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
|
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
|
||||||
empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
empty_obj = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
||||||
item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj));
|
item_context = PointerGetDatum(JsonbValueToJsonb(empty_obj));
|
||||||
|
|
||||||
/* Reset error state */
|
|
||||||
FlushErrorState();
|
|
||||||
goto process_array_element;
|
|
||||||
}
|
|
||||||
PG_END_TRY();
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
/* Handle complex objects within the array */
|
|
||||||
else if (token == WJB_BEGIN_OBJECT || token == WJB_BEGIN_ARRAY)
|
|
||||||
{
|
|
||||||
if (nesting_level == 0)
|
|
||||||
{
|
|
||||||
/* Starting a new complex element */
|
|
||||||
elog(DEBUG1, "N*SECT: Starting complex element %d", i);
|
|
||||||
element_state = NULL;
|
|
||||||
element_value = pushJsonbValue(&element_state, token, NULL);
|
|
||||||
in_element = true;
|
|
||||||
}
|
|
||||||
nesting_level++;
|
|
||||||
}
|
|
||||||
else if ((token == WJB_END_OBJECT || token == WJB_END_ARRAY) && in_element)
|
|
||||||
{
|
|
||||||
nesting_level--;
|
|
||||||
|
|
||||||
if (nesting_level == 0)
|
|
||||||
{
|
|
||||||
/* Finished a complex element */
|
|
||||||
elog(DEBUG1, "N*SECT: Finished complex element %d", i);
|
|
||||||
element_value = pushJsonbValue(&element_state, token, NULL);
|
|
||||||
in_element = false;
|
|
||||||
|
|
||||||
/* Convert to Datum and process */
|
|
||||||
item_context = PointerGetDatum(JsonbValueToJsonb(element_value));
|
|
||||||
item_error = false;
|
|
||||||
|
|
||||||
/* Process this complex element */
|
|
||||||
goto process_array_element;
|
goto process_array_element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (in_element)
|
PG_CATCH();
|
||||||
{
|
{
|
||||||
/* Add to the current element being built */
|
elog(WARNING, "N*SECT: Error processing array element %d, creating empty object instead", i);
|
||||||
pushJsonbValue(&element_state, token, &v);
|
/* 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();
|
||||||
|
goto process_array_element;
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
/* Handle complex objects within the array */
|
||||||
|
else if (token == WJB_BEGIN_OBJECT || token == WJB_BEGIN_ARRAY)
|
||||||
|
{
|
||||||
|
if (nesting_level == 0)
|
||||||
|
{
|
||||||
|
/* Starting a new complex element */
|
||||||
|
elog(DEBUG1, "N*SECT: Starting complex element %d", i);
|
||||||
|
element_state = NULL;
|
||||||
|
element_value = pushJsonbValue(&element_state, token, NULL);
|
||||||
|
in_element = true;
|
||||||
|
}
|
||||||
|
nesting_level++;
|
||||||
|
}
|
||||||
|
else if ((token == WJB_END_OBJECT || token == WJB_END_ARRAY) && in_element)
|
||||||
|
{
|
||||||
|
nesting_level--;
|
||||||
|
|
||||||
|
if (nesting_level == 0)
|
||||||
|
{
|
||||||
|
/* Finished a complex element */
|
||||||
|
elog(DEBUG1, "N*SECT: Finished complex element %d", i);
|
||||||
|
element_value = pushJsonbValue(&element_state, token, NULL);
|
||||||
|
in_element = false;
|
||||||
|
|
||||||
|
/* Convert to Datum and process */
|
||||||
|
item_context = PointerGetDatum(JsonbValueToJsonb(element_value));
|
||||||
|
item_error = false;
|
||||||
|
|
||||||
|
/* Process this complex element */
|
||||||
|
goto process_array_element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (in_element)
|
||||||
else
|
{
|
||||||
{
|
/* Add to the current element being built */
|
||||||
elog(DEBUG1, "N*SECT: Collection not found: %s", collection_path);
|
pushJsonbValue(&element_state, token, &v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case TEMPLATE_NODE_EXECUTE:
|
else
|
||||||
elog(DEBUG1, "");
|
|
||||||
elog(DEBUG1, "> EXECUTE");
|
|
||||||
/* Execute is not implemented in this version */
|
|
||||||
elog(DEBUG1, "N*EXEC: Execute node type not implemented");
|
|
||||||
case TEMPLATE_NODE_INCLUDE:
|
|
||||||
elog(DEBUG1, "");
|
|
||||||
elog(DEBUG1, "> INCLUDE");
|
|
||||||
/* Handle includes */
|
|
||||||
char *template_key = current->value->include.key;
|
|
||||||
Datum include_data;
|
|
||||||
bool found_include = false;
|
|
||||||
|
|
||||||
if (template_key)
|
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*INCL: Processing include with key: %s", template_key);
|
elog(DEBUG1, "N*SECT: Collection not found: %s", collection_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case TEMPLATE_NODE_EXECUTE:
|
||||||
|
elog(DEBUG1, "");
|
||||||
|
elog(DEBUG1, "> EXECUTE");
|
||||||
|
/* Execute is not implemented in this version */
|
||||||
|
elog(DEBUG1, "N*EXEC: Execute node type not implemented");
|
||||||
|
case TEMPLATE_NODE_INCLUDE:
|
||||||
|
elog(DEBUG1, "");
|
||||||
|
elog(DEBUG1, "> INCLUDE");
|
||||||
|
/* Handle includes */
|
||||||
|
char *template_key = current->value->include.key;
|
||||||
|
Datum include_data;
|
||||||
|
bool found_include = false;
|
||||||
|
|
||||||
|
if (template_key)
|
||||||
|
{
|
||||||
|
elog(DEBUG1, "N*INCL: Processing include with key: %s", template_key);
|
||||||
|
|
||||||
|
/* Find include template in context */
|
||||||
|
include_data = get_jsonb_include_template(jsonb_context, template_key, &found_include);
|
||||||
|
|
||||||
|
if (found_include)
|
||||||
|
{
|
||||||
|
char *include_template = NULL;
|
||||||
|
Datum include_context = (Datum) 0;
|
||||||
|
char *include_result;
|
||||||
|
bool include_error = false;
|
||||||
|
|
||||||
/* Find include template in context */
|
/* Extract template and context */
|
||||||
include_data = get_jsonb_include_template(jsonb_context, template_key, &found_include);
|
get_include_data(include_data, &include_template, &include_context);
|
||||||
|
|
||||||
if (found_include)
|
/* Parse and render included template */
|
||||||
|
if (include_template)
|
||||||
{
|
{
|
||||||
char *include_template = NULL;
|
TemplateConfig config = template_default_config(context);
|
||||||
Datum include_context = (Datum) 0;
|
TemplateErrorCode error_code;
|
||||||
char *include_result;
|
const char *template_str = include_template;
|
||||||
bool include_error = false;
|
TemplateNode *include_node = template_parse(context, &template_str, &config, false, &error_code);
|
||||||
|
|
||||||
/* Extract template and context */
|
if (include_node && error_code == TEMPLATE_ERROR_NONE)
|
||||||
get_include_data(include_data, &include_template, &include_context);
|
|
||||||
|
|
||||||
/* Parse and render included template */
|
|
||||||
if (include_template)
|
|
||||||
{
|
{
|
||||||
TemplateConfig config = template_default_config(context);
|
include_result = template_render(context, include_node,
|
||||||
TemplateErrorCode error_code;
|
include_context != (Datum) 0 ? include_context : jsonb_context,
|
||||||
const char *template_str = include_template;
|
&include_error);
|
||||||
TemplateNode *include_node = template_parse(context, &template_str, &config, false, &error_code);
|
|
||||||
|
|
||||||
if (include_node && error_code == TEMPLATE_ERROR_NONE)
|
if (!include_error && include_result)
|
||||||
{
|
{
|
||||||
include_result = template_render(context, include_node,
|
appendStringInfoString(&result, include_result);
|
||||||
include_context != (Datum) 0 ? include_context : jsonb_context,
|
pfree(include_result);
|
||||||
&include_error);
|
|
||||||
|
|
||||||
if (!include_error && include_result)
|
|
||||||
{
|
|
||||||
appendStringInfoString(&result, include_result);
|
|
||||||
pfree(include_result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elog(WARNING, "N*INCL: Error rendering included template: %s", template_key);
|
|
||||||
*error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template_free_node(include_node);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
elog(WARNING, "N*INCL: Error parsing included template: %s", template_key);
|
elog(WARNING, "N*INCL: Error rendering included template: %s", template_key);
|
||||||
*error = true;
|
*error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(include_template);
|
template_free_node(include_node);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
elog(WARNING, "N*INCL: Included template is null: %s", template_key);
|
elog(WARNING, "N*INCL: Error parsing included template: %s", template_key);
|
||||||
|
*error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pfree(include_template);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
elog(DEBUG1, "N*INCL: Include key not found: %s", template_key);
|
elog(WARNING, "N*INCL: Included template is null: %s", template_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
default:
|
{
|
||||||
/* Unknown node type */
|
elog(DEBUG1, "N*INCL: Include key not found: %s", template_key);
|
||||||
elog(WARNING, "N*UNKN: Unknown node type: %d", current->type);
|
}
|
||||||
break;
|
}
|
||||||
}
|
break;
|
||||||
|
default:
|
||||||
|
/* Unknown node type */
|
||||||
|
elog(WARNING, "N*UNKN: Unknown node type: %d", current->type);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
|
||||||
{
|
|
||||||
elog(WARNING, "Error processing template node of type %d", current->type);
|
|
||||||
FlushErrorState();
|
|
||||||
/* Continue with next node */
|
|
||||||
}
|
|
||||||
PG_END_TRY();
|
|
||||||
|
|
||||||
if (*error)
|
if (*error)
|
||||||
break;
|
break;
|
||||||
@@ -2279,5 +2268,4 @@ get_jsonb_value_by_path(Jsonb *jb, const char *path, bool *found)
|
|||||||
*found = true;
|
*found = true;
|
||||||
pfree(path_copy);
|
pfree(path_copy);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user