fix: more parsing errors handling, section body fix

This commit is contained in:
2025-05-14 14:09:48 +00:00
parent e9ba38f4eb
commit 56fa799d53
3 changed files with 274 additions and 167 deletions

View File

@@ -319,12 +319,16 @@
];
initialScript = pkgs.writeText "init-sql-script" ''
SET log_min_messages TO DEBUG1;
SET client_min_messages TO DEBUG1;
ALTER DATABASE postgres SET log_min_messages TO DEBUG1;
ALTER DATABASE postgres SET client_min_messages TO DEBUG1;
CREATE EXTENSION "hemar";
-- SELECT hemar.parse('{% zalupa %}');
SELECT hemar.render('{"a": "b"}'::JSONB, 'a {% a %}');
SELECT hemar.render('{"a": ["b", "c"]}'::JSONB, 'a {% for i in a do text %}');
SELECT hemar.render('{"a": {"g": ["b", "c"]}}'::JSONB, 'a {% for i in a.g do {% i %} %}');
SELECT hemar.render('{"a": {"g": ["b", "c"], "b": [{"c": "a"}, {"c": "b"}]}}'::JSONB, 'a {% for i in a.b do {% i.c %} %}');
'';
};

View File

@@ -33,6 +33,7 @@ static void get_include_data(Datum include_data, char **template_out, Datum *con
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);
static JsonbValue *get_jsonb_value_by_path(Jsonb *jb, const char *path, bool *found);
/* Implementation of a simplified validity check for JsonbContainer */
static bool
@@ -121,8 +122,9 @@ init_template_node(MemoryContext context, TemplateNodeType type)
/* Error code to string conversion */
const char *
template_error_to_string(TemplateErrorCode code)
template_error_to_string(TemplateErrorCode code, TemplateConfig *config)
{
char *message = "";
switch (code)
{
case TEMPLATE_ERROR_NONE:
@@ -131,8 +133,32 @@ template_error_to_string(TemplateErrorCode code)
return "Unknown tag";
case TEMPLATE_ERROR_NESTED_INTERPOLATION:
return "Nested interpolation";
case TEMPLATE_ERROR_NESTED_SECTION_ITERATOR:
return "Nested section iterator";
case TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE:
message = "Found `";
strcat(message, config->Syntax.Braces.open);
strcat(message, "` in `");
strcat(message, config->Syntax.Section.control);
strcat(message, "` in section block");
return message;
case TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE:
message = "Found `";
strcat(message, config->Syntax.Braces.open);
strcat(message, "` in `");
strcat(message, config->Syntax.Section.source);
strcat(message, "` in section block");
return message;
case TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END:
return "Unexpected interpolation end";
case TEMPLATE_ERROR_NO_SOURSE_IN_SECTION:
message = "Not found `";
strcat(message, config->Syntax.Section.source);
strcat(message, "` keyword in section block");
return message;
case TEMPLATE_ERROR_NO_BEGIN_IN_SECTION:
message = "Not found `";
strcat(message, config->Syntax.Section.begin);
strcat(message, "` keyword in section block");
return message;
case TEMPLATE_ERROR_UNEXPECTED_SECTION_END:
return "Unexpected section end";
case TEMPLATE_ERROR_NESTED_INCLUDE:
@@ -298,6 +324,7 @@ template_parse_interpolation(MemoryContext context, const char **s_ptr,
key_len = *s - key_start;
node->value->interpolate.key = MemoryContextStrdup(context, pnstrdup(key_start, key_len));
elog(DEBUG1, "Parsing: %s", node->value->interpolate.key);
*s = skip_whitespace(*s);
@@ -305,7 +332,7 @@ template_parse_interpolation(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
*error_code = TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END;
template_free_node(node);
return NULL;
}
@@ -345,7 +372,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR;
*error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE;
template_free_node(node);
return NULL;
}
@@ -353,7 +380,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
*error_code = TEMPLATE_ERROR_NO_SOURSE_IN_SECTION;
template_free_node(node);
return NULL;
}
@@ -371,7 +398,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
*error_code = TEMPLATE_ERROR_NO_SOURSE_IN_SECTION;
template_free_node(node);
return NULL;
}
@@ -389,7 +416,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR;
*error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE;
template_free_node(node);
return NULL;
}
@@ -397,7 +424,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
*error_code = TEMPLATE_ERROR_NO_BEGIN_IN_SECTION;
template_free_node(node);
return NULL;
}
@@ -411,10 +438,11 @@ template_parse_section(MemoryContext context, const char **s_ptr,
/* Check for 'do' keyword */
*s = skip_whitespace(*s);
// TODO: why check begin second time, first in while
if (strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) != 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
*error_code = TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE;
template_free_node(node);
return NULL;
}
@@ -436,9 +464,28 @@ template_parse_section(MemoryContext context, const char **s_ptr,
const char *body_start = *s;
const char *original_s = *s;
int inner_braces_opened_count = 0;
/* Find the end of the section */
while (**s && strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
while (**s) {
// s = {% a %} %}
elog(DEBUG1, "Step, braces opened: %d, s: %s", inner_braces_opened_count, *s);
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) {
elog(DEBUG1, "inner_braces_opened_count++");
inner_braces_opened_count++;
}
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) {
if (inner_braces_opened_count > 0) {
elog(DEBUG1, "inner_braces_opened_count--");
inner_braces_opened_count--;
}
else {
elog(DEBUG1, "exit");
break;
}
}
(*s)++;
}
if (!**s)
{
@@ -1243,7 +1290,6 @@ 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;
@@ -1266,19 +1312,11 @@ get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found)
PG_TRY();
{
/* Handle simple top-level key */
if (strchr(path, '.') == NULL && strchr(path, '[') == NULL)
/* Use the new path traversal function */
jbv_result = get_jsonb_value_by_path(jb, path, found);
if (*found && jbv_result)
{
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);
@@ -1303,9 +1341,9 @@ get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found)
else if (jbv_result->type == jbvBinary)
{
/* Check if it's an array first */
if (is_jsonb_container_valid((JsonbContainer *)&jbv_result->val.binary))
if (is_jsonb_container_valid((JsonbContainer *)jbv_result->val.binary.data))
{
it = JsonbIteratorInit((JsonbContainer *)&jbv_result->val.binary);
it = JsonbIteratorInit((JsonbContainer *)jbv_result->val.binary.data);
token = JsonbIteratorNext(&it, &v, false);
if (token == WJB_BEGIN_ARRAY)
@@ -1340,15 +1378,7 @@ get_jsonb_path_value(Datum jsonb_context, const char *path, bool *found)
}
else
{
elog(DEBUG1, "Key %s not found in object", path);
}
}
else
{
/* Handle nested paths using a JSON path expression */
elog(DEBUG1, "Complex path not fully supported: %s", path);
result = pstrdup("[Complex Path]");
*found = true;
elog(DEBUG1, "Path %s not found in object", path);
}
}
PG_CATCH();
@@ -1372,7 +1402,6 @@ 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;
@@ -1393,23 +1422,18 @@ get_jsonb_array(Datum jsonb_context, const char *path, bool *found)
return result;
}
/* Handle simple top-level key */
if (strchr(path, '.') == NULL)
{
elog(DEBUG1, "Looking for array at path: %s", path);
/* 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);
/* Use the new path traversal function */
bool path_found = false;
jbv_result = get_jsonb_value_by_path(jb, path, &path_found);
jbv_result = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key);
if (jbv_result)
if (path_found && jbv_result)
{
elog(DEBUG1, "Found value for key %s (type=%d) value=%s", path, jbv_result->type, jbv_result->val.string.val);
elog(DEBUG1, "Found value for key %s (type=%d)", path, jbv_result->type);
if (jbv_result->type == jbvBinary)
{
@@ -1428,10 +1452,10 @@ get_jsonb_array(Datum jsonb_context, const char *path, bool *found)
PG_TRY();
{
/* Log raw pointer for debugging */
elog(DEBUG1, "Binary container address: %p", &jbv_result->val.binary);
elog(DEBUG1, "Binary container address: %p", jbv_result->val.binary.data);
/* Try to get the first 4 bytes of the binary data */
uint32 header = *(uint32 *)&jbv_result->val.binary;
uint32 header = *(uint32 *)jbv_result->val.binary.data;
elog(DEBUG1, "Binary container header: %u", header);
/* Initialize the iterator with careful error handling */
@@ -1490,12 +1514,6 @@ get_jsonb_array(Datum jsonb_context, const char *path, bool *found)
FlushErrorState();
}
PG_END_TRY();
}
else {
elog(DEBUG1, "Complex path not fully supported: %s", path);
result = pstrdup("[Complex Path]");
*found = true;
}
return result;
}
@@ -2327,7 +2345,7 @@ pg_template_parse(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Template parsing error: %s", template_error_to_string(error_code))));
errmsg("Template parsing error: %s", template_error_to_string(error_code, &config))));
}
/* Convert the parsed template to a string representation for debugging */
@@ -2408,7 +2426,7 @@ pg_render(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Template parsing error: %s", template_error_to_string(error_code))));
errmsg("Template parsing error: %s", template_error_to_string(error_code, &config))));
}
elog(DEBUG1, "Template parsed successfully, starting render");
@@ -2587,3 +2605,84 @@ direct_array_check(Datum jsonb_context, const char *path, bool *found)
return result;
}
/* Function to get JsonbValue by dot-separated path */
static JsonbValue *
get_jsonb_value_by_path(Jsonb *jb, const char *path, bool *found)
{
JsonbValue *result = NULL;
char *path_copy, *token, *saveptr;
JsonbValue key;
JsonbContainer *container;
*found = false;
if (!jb || !path || !is_jsonb_container_valid(&jb->root))
{
elog(DEBUG1, "Invalid JSONB or path in get_jsonb_value_by_path");
return NULL;
}
/* Make a copy of the path to tokenize */
path_copy = pstrdup(path);
container = &jb->root;
/* Use strtok_r to split the path by dots */
token = strtok_r(path_copy, ".", &saveptr);
while (token != NULL)
{
/* Check if we're still working with an object (?) */
if (!(container->header & JB_FOBJECT))
{
elog(DEBUG1, "Path segment '%s' cannot be applied to non-object", token);
pfree(path_copy);
return NULL;
}
/* Set up the key to search for */
key.type = jbvString;
key.val.string.val = token;
key.val.string.len = strlen(token);
/* Find the value for this key */
result = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
if (!result)
{
elog(DEBUG1, "Key '%s' not found in object", token);
pfree(path_copy);
return NULL;
}
/* If there are more path segments, we need to continue with the next container */
token = strtok_r(NULL, ".", &saveptr);
if (token != NULL)
{
/* We need to go deeper, so the current result must be a container */
if (result->type != jbvBinary)
{
elog(DEBUG1, "Path segment '%s' points to a non-container value", token);
pfree(path_copy);
return NULL;
}
/* Move to the next container */
container = (JsonbContainer *)result->val.binary.data;
/* Validate the container */
if (!is_jsonb_container_valid(container))
{
elog(WARNING, "Invalid JSONB container during path traversal");
pfree(path_copy);
return NULL;
}
}
}
/* If we got here, we found the value */
*found = true;
pfree(path_copy);
return result;
}

View File

@@ -15,9 +15,13 @@
typedef enum {
TEMPLATE_ERROR_NONE = 0,
TEMPLATE_ERROR_UNKNOWN_TAG,
TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_CONTROLE,
TEMPLATE_UNEXPECTED_OPEN_BRACES_AFFTER_SECTION_SOURCE,
TEMPLATE_ERROR_UNEXPECTED_INTERPOLATION_END,
TEMPLATE_ERROR_NO_SOURSE_IN_SECTION,
TEMPLATE_ERROR_NESTED_INTERPOLATION,
TEMPLATE_ERROR_NESTED_SECTION_ITERATOR,
TEMPLATE_ERROR_UNEXPECTED_SECTION_END,
TEMPLATE_ERROR_NO_BEGIN_IN_SECTION,
TEMPLATE_ERROR_NESTED_INCLUDE,
TEMPLATE_ERROR_NESTED_EXECUTE,
TEMPLATE_ERROR_INVALID_CONFIG,
@@ -106,6 +110,6 @@ TemplateConfig template_default_config(MemoryContext context);
bool template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code);
TemplateNode *template_parse(MemoryContext context, const char **s, const TemplateConfig *config, bool inner_parse, TemplateErrorCode *error_code);
void template_free_node(TemplateNode *node);
const char *template_error_to_string(TemplateErrorCode code);
const char *template_error_to_string(TemplateErrorCode code, TemplateConfig *config);
#endif /* HEMAR_TEMPLATE_H */