diff --git a/package/c/hemar/hemar.c b/package/c/hemar/hemar.c index 0803f6d..68df22e 100755 --- a/package/c/hemar/hemar.c +++ b/package/c/hemar/hemar.c @@ -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 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 * jbt_type_to_string(JsonbIteratorToken type) { @@ -1178,31 +1191,103 @@ static void render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryContext context) { TemplateNode *current = node; + JsonbValue *value; + char *str_value; + char *debug_msg = palloc(1024); while (current) { + snprintf(debug_msg, 1024, "Rendering node type: %.50s", tnt_to_string(current->type)); + switch (current->type) { case TEMPLATE_NODE_TEXT: + snprintf(debug_msg, 1024, "%s TEXT: %.50s", debug_msg, current->value->text.content); if (current->value->text.content) + { + /* Preserve whitespace in text nodes */ 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; 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); break; - /* We'll implement these later */ - case TEMPLATE_NODE_INTERPOLATE: 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: + snprintf(debug_msg, 1024, "%s INCLUDE: %.50s", debug_msg, current->value->include.key); + /* We'll implement include rendering later */ + break; + default: - /* Skip for now */ + elog(WARNING, "Unknown template node type: %d", current->type); break; } - + + elog(DEBUG1, "%s", debug_msg); + current = current->next; } + + pfree(debug_msg); } /* Helper function to calculate a simple hash of a string */ diff --git a/package/c/hemar/test/mod.sql b/package/c/hemar/test/mod.sql index 9ee06ff..9ed8eb2 100755 --- a/package/c/hemar/test/mod.sql +++ b/package/c/hemar/test/mod.sql @@ -2,4 +2,5 @@ BEGIN; \ir test_jsonb_path.sql \ir test_template_parser.sql \ir test_render_exec.sql + \ir test_render_interpolate.sql ROLLBACK; \ No newline at end of file diff --git a/package/c/hemar/test/test_jsonb_path.sql b/package/c/hemar/test/test_jsonb_path.sql index 286a6c7..b524d15 100755 --- a/package/c/hemar/test/test_jsonb_path.sql +++ b/package/c/hemar/test/test_jsonb_path.sql @@ -479,6 +479,22 @@ BEGIN total_tests, current_path, result, passed; 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 IF passed_tests = total_tests THEN RAISE NOTICE '------------------------------------'; diff --git a/package/c/hemar/test/test_render_interpolate.sql b/package/c/hemar/test/test_render_interpolate.sql new file mode 100755 index 0000000..129e07e --- /dev/null +++ b/package/c/hemar/test/test_render_interpolate.sql @@ -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 $$; \ No newline at end of file