chore: hemar: more logs for god of logs
This commit is contained in:
@@ -25,6 +25,19 @@ static void render_template(TemplateNode *node, Jsonb *define, StringInfo result
|
|||||||
static void render_execute_tag(const char *code, Jsonb *define, StringInfo result, MemoryContext context);
|
static void render_execute_tag(const char *code, Jsonb *define, StringInfo result, MemoryContext context);
|
||||||
static JsonbValue *jsonb_get_by_path_internal(Jsonb *jb, const char *path_str, MemoryContext context);
|
static JsonbValue *jsonb_get_by_path_internal(Jsonb *jb, const char *path_str, MemoryContext context);
|
||||||
|
|
||||||
|
char *tnt_to_string(TemplateNodeType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TEMPLATE_NODE_SECTION: return "SECTION";
|
||||||
|
case TEMPLATE_NODE_INTERPOLATE: return "INTERPOLATE";
|
||||||
|
case TEMPLATE_NODE_EXECUTE: return "EXECUTE";
|
||||||
|
case TEMPLATE_NODE_INCLUDE: return "INCLUDE";
|
||||||
|
case TEMPLATE_NODE_TEXT: return "TEXT";
|
||||||
|
default: {
|
||||||
|
return "UNKNOWN";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
jbt_type_to_string(JsonbIteratorToken type)
|
jbt_type_to_string(JsonbIteratorToken type)
|
||||||
{
|
{
|
||||||
@@ -1178,31 +1191,103 @@ static void
|
|||||||
render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryContext context)
|
render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryContext context)
|
||||||
{
|
{
|
||||||
TemplateNode *current = node;
|
TemplateNode *current = node;
|
||||||
|
JsonbValue *value;
|
||||||
|
char *str_value;
|
||||||
|
char *debug_msg = palloc(1024);
|
||||||
|
|
||||||
while (current)
|
while (current)
|
||||||
{
|
{
|
||||||
|
snprintf(debug_msg, 1024, "Rendering node type: %.50s", tnt_to_string(current->type));
|
||||||
|
|
||||||
switch (current->type)
|
switch (current->type)
|
||||||
{
|
{
|
||||||
case TEMPLATE_NODE_TEXT:
|
case TEMPLATE_NODE_TEXT:
|
||||||
|
snprintf(debug_msg, 1024, "%s TEXT: %.50s", debug_msg, current->value->text.content);
|
||||||
if (current->value->text.content)
|
if (current->value->text.content)
|
||||||
|
{
|
||||||
|
/* Preserve whitespace in text nodes */
|
||||||
appendStringInfoString(result, current->value->text.content);
|
appendStringInfoString(result, current->value->text.content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE_NODE_INTERPOLATE:
|
||||||
|
snprintf(debug_msg, 1024, "%s INTERPOLATE: %.50s", debug_msg, current->value->interpolate.key);
|
||||||
|
if (current->value->interpolate.key)
|
||||||
|
{
|
||||||
|
/* Get the value from the JSONB context using the path */
|
||||||
|
value = jsonb_get_by_path_internal(define, current->value->interpolate.key, context);
|
||||||
|
|
||||||
|
if (value != NULL)
|
||||||
|
{
|
||||||
|
/* Convert the value to a string based on its type */
|
||||||
|
switch (value->type)
|
||||||
|
{
|
||||||
|
case jbvString:
|
||||||
|
/* Preserve whitespace in string values */
|
||||||
|
appendStringInfoString(result, value->val.string.val);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case jbvNumeric:
|
||||||
|
str_value = DatumGetCString(DirectFunctionCall1(numeric_out,
|
||||||
|
NumericGetDatum(value->val.numeric)));
|
||||||
|
appendStringInfoString(result, str_value);
|
||||||
|
pfree(str_value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case jbvBool:
|
||||||
|
appendStringInfoString(result, value->val.boolean ? "true" : "false");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case jbvNull:
|
||||||
|
/* For null values, we don't output anything */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case jbvBinary:
|
||||||
|
/* For complex types (objects/arrays), convert to JSON string */
|
||||||
|
str_value = DatumGetCString(DirectFunctionCall1(jsonb_out,
|
||||||
|
JsonbPGetDatum(JsonbValueToJsonb(value))));
|
||||||
|
appendStringInfoString(result, str_value);
|
||||||
|
pfree(str_value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
elog(WARNING, "Unsupported JSONB value type in interpolation: %s",
|
||||||
|
jbv_type_to_string(value->type));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the value since it was allocated in our context */
|
||||||
|
pfree(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TEMPLATE_NODE_EXECUTE:
|
case TEMPLATE_NODE_EXECUTE:
|
||||||
|
snprintf(debug_msg, 1024, "%s EXECUTE: %.50s", debug_msg, current->value->execute.code);
|
||||||
render_execute_tag(current->value->execute.code, define, result, context);
|
render_execute_tag(current->value->execute.code, define, result, context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* We'll implement these later */
|
|
||||||
case TEMPLATE_NODE_INTERPOLATE:
|
|
||||||
case TEMPLATE_NODE_SECTION:
|
case TEMPLATE_NODE_SECTION:
|
||||||
|
snprintf(debug_msg, 1024, "%s SECTION: %.50s", debug_msg, current->value->section.iterator);
|
||||||
|
/* We'll implement section rendering later */
|
||||||
|
break;
|
||||||
|
|
||||||
case TEMPLATE_NODE_INCLUDE:
|
case TEMPLATE_NODE_INCLUDE:
|
||||||
|
snprintf(debug_msg, 1024, "%s INCLUDE: %.50s", debug_msg, current->value->include.key);
|
||||||
|
/* We'll implement include rendering later */
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
/* Skip for now */
|
elog(WARNING, "Unknown template node type: %d", current->type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elog(DEBUG1, "%s", debug_msg);
|
||||||
|
|
||||||
current = current->next;
|
current = current->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pfree(debug_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function to calculate a simple hash of a string */
|
/* Helper function to calculate a simple hash of a string */
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ BEGIN;
|
|||||||
\ir test_jsonb_path.sql
|
\ir test_jsonb_path.sql
|
||||||
\ir test_template_parser.sql
|
\ir test_template_parser.sql
|
||||||
\ir test_render_exec.sql
|
\ir test_render_exec.sql
|
||||||
|
\ir test_render_interpolate.sql
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
@@ -479,6 +479,22 @@ BEGIN
|
|||||||
total_tests, current_path, result, passed;
|
total_tests, current_path, result, passed;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
-- Test nested object path parsing
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
result := hemar.jsonb_get_by_path(
|
||||||
|
'{"user": {"profile": {"name": "John", "age": 30}}}'::jsonb,
|
||||||
|
'user.profile.name'
|
||||||
|
);
|
||||||
|
passed := result = '"John"'::jsonb;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Nested object path parsing (path="%"): % | PASSED: %',
|
||||||
|
total_tests, current_path, result, passed;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Nested object path parsing (path="%"): % | PASSED: % (expected: "John")',
|
||||||
|
total_tests, current_path, result, passed;
|
||||||
|
END IF;
|
||||||
|
|
||||||
-- Print summary
|
-- Print summary
|
||||||
IF passed_tests = total_tests THEN
|
IF passed_tests = total_tests THEN
|
||||||
RAISE NOTICE '------------------------------------';
|
RAISE NOTICE '------------------------------------';
|
||||||
|
|||||||
190
package/c/hemar/test/test_render_interpolate.sql
Executable file
190
package/c/hemar/test/test_render_interpolate.sql
Executable file
@@ -0,0 +1,190 @@
|
|||||||
|
-- Test the render function with interpolation tags
|
||||||
|
CREATE EXTENSION IF NOT EXISTS hemar;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
total_tests INT := 0;
|
||||||
|
passed_tests INT := 0;
|
||||||
|
test_result TEXT;
|
||||||
|
expected TEXT;
|
||||||
|
passed BOOLEAN;
|
||||||
|
BEGIN
|
||||||
|
-- Test 1: Simple string interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"name": "John"}'::jsonb,
|
||||||
|
'Hello {{ name }}!'
|
||||||
|
);
|
||||||
|
expected := 'Hello John!';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Simple string interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Simple string interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 2: Numeric interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"age": 30, "price": 19.99}'::jsonb,
|
||||||
|
'Age: {{ age }}, Price: {{ price }}'
|
||||||
|
);
|
||||||
|
expected := 'Age: 30, Price: 19.99';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Numeric interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Numeric interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 3: Boolean interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"is_active": true, "is_deleted": false}'::jsonb,
|
||||||
|
'Status: {{ is_active }}, Deleted: {{ is_deleted }}'
|
||||||
|
);
|
||||||
|
expected := 'Status: true, Deleted: false';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Boolean interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Boolean interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 4: Null value interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"name": null}'::jsonb,
|
||||||
|
'Name: {{ name }}'
|
||||||
|
);
|
||||||
|
expected := 'Name: ';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Null value interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Null value interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 5: Nested object interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"user": {"profile": {"name": "John", "age": 30}}}'::jsonb,
|
||||||
|
'User: {{ user.profile.name }}, Age: {{ user.profile.age }}'
|
||||||
|
);
|
||||||
|
expected := 'User: John, Age: 30';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Nested object interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Nested object interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 6: Array interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"numbers": [1, 2, 3], "names": ["John", "Jane"]}'::jsonb,
|
||||||
|
'Numbers: {{ numbers }}, Names: {{ names }}'
|
||||||
|
);
|
||||||
|
expected := 'Numbers: [1, 2, 3], Names: ["John", "Jane"]';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Array interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Array interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 7: Array index interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
|
||||||
|
'First item: {{ items[0].name }}, Second item: {{ items[1].name }}'
|
||||||
|
);
|
||||||
|
expected := 'First item: Item 1, Second item: Item 2';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Array index interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Array index interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 8: Complex nested structure interpolation
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"company": {"name": "Tech Corp", "employees": [{"name": "John", "role": "Developer"}, {"name": "Jane", "role": "Manager"}]}}'::jsonb,
|
||||||
|
'Company: {{ company.name }}, First employee: {{ company.employees[0].name }} ({{ company.employees[0].role }})'
|
||||||
|
);
|
||||||
|
expected := 'Company: Tech Corp, First employee: John (Developer)';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Complex nested structure interpolation: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Complex nested structure interpolation: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 9: Multiple interpolations in text
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"greeting": "Hello", "name": "John", "punctuation": "!"}'::jsonb,
|
||||||
|
'{{ greeting }} {{ name }}{{ punctuation }} How are you {{ name }}?'
|
||||||
|
);
|
||||||
|
expected := 'Hello John! How are you John?';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Multiple interpolations in text: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Multiple interpolations in text: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Test 10: Invalid path handling
|
||||||
|
total_tests := total_tests + 1;
|
||||||
|
test_result := hemar.render(
|
||||||
|
'{"name": "John"}'::jsonb,
|
||||||
|
'Name: {{ name }}, Age: {{ age }}, Address: {{ address.street }}'
|
||||||
|
);
|
||||||
|
expected := 'Name: John, Age: , Address: ';
|
||||||
|
passed := test_result = expected;
|
||||||
|
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
|
||||||
|
IF passed THEN
|
||||||
|
RAISE NOTICE 'Test %: Invalid path handling: PASSED', total_tests;
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING 'Test %: Invalid path handling: FAILED. Expected "%", got "%"',
|
||||||
|
total_tests, expected, test_result;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Print summary
|
||||||
|
IF passed_tests = total_tests THEN
|
||||||
|
RAISE NOTICE '------------------------------------';
|
||||||
|
RAISE NOTICE 'SUMMARY: % of % interpolation render tests passed (100%%)',
|
||||||
|
passed_tests, total_tests;
|
||||||
|
RAISE NOTICE '------------------------------------';
|
||||||
|
ELSE
|
||||||
|
RAISE WARNING '------------------------------------';
|
||||||
|
RAISE WARNING 'SUMMARY: % of % interpolation render tests passed (%)',
|
||||||
|
passed_tests,
|
||||||
|
total_tests,
|
||||||
|
round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%';
|
||||||
|
RAISE WARNING '------------------------------------';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF passed_tests != total_tests THEN
|
||||||
|
RAISE EXCEPTION 'Tests failed: % of % interpolation render tests did not pass', (total_tests - passed_tests), total_tests;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
Reference in New Issue
Block a user