refactor: hemar reboot

This commit is contained in:
2025-09-28 04:44:46 +00:00
parent 5dd03a947f
commit 38bf9151eb
27 changed files with 374 additions and 38 deletions

8
package/c/hemar-legacy/.gitignore vendored Executable file
View File

@@ -0,0 +1,8 @@
<<<<<<< HEAD:package/c/hemar/.gitignore
hemar.o
hemar.so
=======
package/c/postgreact/postgreact.control
package/c/postgreact/postgreact.o
package/c/postgreact/postgreact.so
>>>>>>> 016db3d06ae814e0f0cc8f39cd4e5af729bb39ac:package/c/postgreact/.gitignore

View File

@@ -0,0 +1,16 @@
MODULE_big = hemar
OBJS = hemar.o
EXTENSION = hemar
DATA = $(wildcard *.sql)
HECTIC_CONFIG = hectic-config
PG_CONFIG = pg_config
PG_CFLAGS += $(shell $(HECTIC_CONFIG) --cflags)
PG_LDFLAGS += -Wl,-rpath,$(shell $(HECTIC_CONFIG) --libdir)
SHLIB_LINK += $(shell $(HECTIC_CONFIG) --libs)
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

View File

@@ -0,0 +1,17 @@
{ postgresql, pkg-config, patchelf }:
buildPostgresqlExtension { inherit postgresql; } {
pname = "hemar";
version = "0.1";
src = ./.;
nativeBuildInputs = [pkg-config c-hectic];
dontShrinkRPath = true;
postFixup = ''
echo ">>> postFixup running..."
${patchelf}/bin/patchelf --set-rpath ${c-hectic}/lib $out/lib/hemar.so
'';
preInstall = ''mkdir $out'';
};

View File

@@ -0,0 +1,21 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION hemar" to load this file. \quit
CREATE SCHEMA hemar;
-- Parse function returns the structure of a template for debugging
CREATE FUNCTION "hemar"."parse"("template" text)
RETURNS text
LANGUAGE C STRICT
AS 'hemar', 'pg_template_parse';
-- JSONB path access function
CREATE FUNCTION "hemar"."jsonb_get_by_path"("json" jsonb, "path" text)
RETURNS jsonb
LANGUAGE C STRICT
AS 'hemar', 'pg_jsonb_get_by_path';
-- Template rendering function
CREATE FUNCTION "hemar"."render"("define" jsonb, "template" text)
RETURNS text
LANGUAGE C STRICT
AS 'hemar', 'pg_template_render';

2612
package/c/hemar-legacy/hemar.c Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
comment = 'My first extension'
default_version = '0.1'
module_pathname = '$libdir/hemar'

116
package/c/hemar-legacy/hemar.h Executable file
View File

@@ -0,0 +1,116 @@
/*
* hemar.h
* Template parser for Hemar
*/
#ifndef HEMAR_TEMPLATE_H
#define HEMAR_TEMPLATE_H
#include "postgres.h"
#include "utils/memutils.h"
/* Maximum length for template syntax elements */
#define TEMPLATE_MAX_PREFIX_LEN 32
/* Template error codes */
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_UNEXPECTED_SECTION_END,
TEMPLATE_ERROR_NO_BEGIN_IN_SECTION,
TEMPLATE_ERROR_NESTED_INCLUDE,
TEMPLATE_ERROR_NESTED_EXECUTE,
TEMPLATE_ERROR_INVALID_CONFIG,
TEMPLATE_ERROR_OUT_OF_MEMORY,
TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END,
TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END
} TemplateErrorCode;
/* Template node types */
typedef enum {
TEMPLATE_NODE_TEXT,
TEMPLATE_NODE_INTERPOLATE,
TEMPLATE_NODE_SECTION,
TEMPLATE_NODE_EXECUTE,
TEMPLATE_NODE_INCLUDE
} TemplateNodeType;
/* Template configuration structure */
typedef struct {
struct {
struct {
const char *open; /* Default: "{%" */
const char *close; /* Default: "%}" */
} Braces;
struct {
const char *control; /* default: "for " */
const char *source; /* default: "in " */
const char *begin; /* default: "do " */
const char *end; /* default: "end" */
} Section;
struct {
const char *invoke; /* default: "" */
} Interpolate;
struct {
const char *invoke; /* default: "include " */
} Include;
struct {
const char *invoke; /* default: "exec " */
} Execute;
const char *nesting; /* default: "->" */
} Syntax;
} TemplateConfig;
/* Forward declaration */
typedef struct TemplateNode TemplateNode;
/* Template value structures */
typedef struct {
char *iterator;
char *collection;
TemplateNode *body;
} TemplateSectionValue;
typedef struct {
char *key;
} TemplateInterpolateValue;
typedef struct {
char *code;
} TemplateExecuteValue;
typedef struct {
char *key;
} TemplateIncludeValue;
typedef struct {
char *content;
} TemplateTextValue;
typedef union {
TemplateSectionValue section;
TemplateInterpolateValue interpolate;
TemplateExecuteValue execute;
TemplateIncludeValue include;
TemplateTextValue text;
} TemplateValue;
/* Template node structure */
struct TemplateNode {
TemplateNodeType type;
TemplateValue *value;
TemplateNode *next;
};
/* Function declarations */
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, TemplateConfig *config);
#endif /* HEMAR_TEMPLATE_H */

110
package/c/hemar-legacy/make.sh Executable file
View File

@@ -0,0 +1,110 @@
#!/bin/sh
# Usage: make.sh [build|watch] [--debug] [--color]
# Options:
# build Build the postgres extension (default if no mode is provided).
# watch Build the extension and watch for changes.
# --debug Build with -O0 (debug mode).
# --color Pass -fdiagnostics-color=always to compiler.
# help, --help Show this help message.
set -u
check_dependencies() {
for dep in gcc pg_config; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "Error: Required dependency '$dep' not found." >&2
exit 1
fi
done
# Check for either fswatch or inotifywait for watch mode
if [ "$MODE" = "watch" ] && ! command -v fswatch >/dev/null 2>&1 && ! command -v inotifywait >/dev/null 2>&1; then
echo "Error: Neither fswatch nor inotifywait found. Please install one of them." >&2
echo " On macOS: brew install fswatch" >&2
echo " On Linux: sudo apt install inotify-tools" >&2
exit 1
fi
}
print_help() {
cat <<EOF
Usage: $0 [build|watch] [--debug] [--color]
build Build the postgres extension (default).
watch Build the extension and watch for changes.
--debug Build with debug flags (-O0).
--color Force colored compiler diagnostics.
help, --help Display this help message.
EOF
}
# Show help if requested
case "$1" in
help|--help)
print_help
exit 0
;;
esac
# Default flags
OPTFLAGS="-O2"
CFLAGS="-Wall -Wextra -pedantic -fPIC"
COLOR_FLAG=""
DEBUG=0
# Process options
while [ $# -gt 0 ]; do
case "$1" in
--debug)
OPTFLAGS="-O0 -gdwarf-2 -g3 -Wno-error"
DEBUG=1
;;
--color)
COLOR_FLAG="-fdiagnostics-color=always"
;;
*)
break
;;
esac
shift
done
MODE="${1:-build}"
shift 2> /dev/null
if [ -n "$COLOR_FLAG" ]; then
CFLAGS="$CFLAGS $COLOR_FLAG"
fi
check_dependencies
# Get PostgreSQL include directory
PG_INCLUDE=$(pg_config --includedir-server)
PG_LIBDIR=$(pg_config --libdir)
case "$MODE" in
watch)
find . -type d | nix run .#watch -- 'sh ./make.sh build' -p '*.c' -p '*.h' 2>&1
;;
build)
mkdir -p target
echo "# Building PostgreSQL extension"
# Get hectic library paths from nix
HECTIC_PATH=$(nix build --print-out-paths -f ../../../. c-hectic)
HECTIC_INCLUDE="$HECTIC_PATH/include"
HECTIC_LIB="$HECTIC_PATH/lib"
# shellcheck disable=SC2086
gcc $CFLAGS $OPTFLAGS -I$PG_INCLUDE -I$HECTIC_INCLUDE -shared -o target/hemar.so hemar.c -L$HECTIC_LIB -lhectic
# Copy extension files to target directory
cp hemar.control target/
cp hemar--0.1.sql target/
echo "Build complete. Files available in target/ directory."
;;
*)
print_help
exit 1
;;
esac

View File

@@ -0,0 +1,12 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION postgreact" to load this file. \quit
-- Define the hello function that uses our C implementation
CREATE FUNCTION hello()
RETURNS
TEXT
STRICT VOLATILE
LANGUAGE C
AS
'MODULE_PATHNAME', 'hello'
;

View File

@@ -0,0 +1,4 @@
comment = '@EXTENSION_COMMENT@'
default_version = '@EXTENSION_VERSION@'
module_pathname = '$libdir/@EXTENSION@'
relocatable = false

View File

@@ -0,0 +1,16 @@
#ifndef POSTGREACT_H
#define POSTGREACT_H
#include "postgres.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
void _PG_init(void);
void _PG_fini(void);
Datum hello(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hello);
#endif // POSTGREACT_H

View File

@@ -0,0 +1,38 @@
BEGIN;
CREATE OR REPLACE FUNCTION pg_temp.diff(string1 text, string2 text) RETURNS TABLE("index" int, char1 text, char2 text) AS $$
BEGIN
RETURN QUERY WITH
s1 AS (SELECT string1 AS str),
s2 AS (SELECT string2 AS str)
SELECT i,
substring(s1.str FROM i FOR 1) AS char1,
substring(s2.str FROM i FOR 1) AS char2
FROM s1, s2,
generate_series(1, GREATEST(length(s1.str), length(s2.str))) AS i
WHERE substring(s1.str FROM i FOR 1) IS DISTINCT FROM substring(s2.str FROM i FOR 1);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION pg_temp.test_regexp_replace(string text) RETURNS text AS $$
BEGIN
RETURN regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(string, E'\t', '\\t', 'g'),
E'\n', '\\n', 'g'),
E'\r', '\\r', 'g'),
' ', '[S]', 'g'),
'\s', '\\s', 'g');
END;
$$ LANGUAGE plpgsql;
\ir test_jsonb_path.sql
--\ir test_template_parser.sql
\ir test_render_exec.sql
\ir test_render_interpolate.sql
\ir test_render_section.sql
\ir test_render_include.sql
--\ir test_render_all.sql
ROLLBACK;

View File

@@ -0,0 +1,516 @@
-- Test file for hemar.jsonb_get_by_path function
-- Run with: psql -f test_jsonb_path.sql
-- Load extension if not already loaded
-- CREATE EXTENSION IF NOT EXISTS hemar;
-- Create sample test data
DO $$
DECLARE
test_json jsonb;
result jsonb;
passed boolean;
total_tests integer := 0;
passed_tests integer := 0;
current_path text;
BEGIN
test_json := jsonb_build_object(
'name', 'John Doe',
'age', 30,
'is_active', true,
'tags', jsonb_build_array('developer', 'postgresql', 'jsonb'),
'address', jsonb_build_object(
'street', '123 Main St',
'city', 'New York',
'zip', '10001'
),
'contacts', jsonb_build_array(
jsonb_build_object(
'type', 'email',
'value', 'john@example.com'
),
jsonb_build_object(
'type', 'phone',
'value', '555-1234',
'verified', true
)
),
'skills', jsonb_build_array(
jsonb_build_array('PostgreSQL', 5),
jsonb_build_array('Python', 4),
jsonb_build_array('JavaScript', 3)
)
);
-- Test basic field access
total_tests := total_tests + 1;
current_path := 'name';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"John Doe"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Simple field access (path="%"): % | PASSED: % (expected: "John Doe")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'age';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '30'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Numeric field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Numeric field access (path="%"): % | PASSED: % (expected: 30)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'is_active';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = 'true'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Boolean field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Boolean field access (path="%"): % | PASSED: % (expected: true)',
total_tests, current_path, result, passed;
END IF;
-- Test nested field access
total_tests := total_tests + 1;
current_path := 'address.city';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"New York"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested object field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Nested object field access (path="%"): % | PASSED: % (expected: "New York")',
total_tests, current_path, result, passed;
END IF;
-- Test array access
total_tests := total_tests + 1;
current_path := 'tags[1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"postgresql"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Simple array access (path="%"): % | PASSED: % (expected: "postgresql")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'contacts[0].type';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"email"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Object in array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Object in array access (path="%"): % | PASSED: % (expected: "email")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'skills[1][0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"Python"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Nested array access (path="%"): % | PASSED: % (expected: "Python")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'contacts[1].value';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"555-1234"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: % (expected: "555-1234")',
total_tests, current_path, result, passed;
END IF;
-- Test object and array returns
total_tests := total_tests + 1;
current_path := 'address';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'object';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path to object (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path to object (path="%"): % | PASSED: % (expected type: object, got: %)',
total_tests, current_path, result, passed, jsonb_typeof(result);
END IF;
total_tests := total_tests + 1;
current_path := 'contacts';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'array';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path to array (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path to array (path="%"): % | PASSED: % (expected type: array, got: %)',
total_tests, current_path, result, passed, jsonb_typeof(result);
END IF;
-- Test error cases
total_tests := total_tests + 1;
current_path := 'unknown_field';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Non-existent field (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Non-existent field (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'address.country';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Non-existent nested field (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Non-existent nested field (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'tags[10]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array index out of bounds (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array index out of bounds (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
-- Test edge cases
total_tests := total_tests + 1;
current_path := '';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Empty path (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Empty path (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'skills[0][1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '5'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Multiple array indices (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Multiple array indices (path="%"): % | PASSED: % (expected: 5)',
total_tests, current_path, result, passed;
END IF;
-- Additional complex test cases
-- Test 16: Deep nested object access
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'level1', jsonb_build_object(
'level2', jsonb_build_object(
'level3', jsonb_build_object(
'level4', jsonb_build_object(
'value', 'deep nested value'
)
)
)
)
);
current_path := 'level1.level2.level3.level4.value';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"deep nested value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Deep nested object access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Deep nested object access (path="%"): % | PASSED: % (expected: "deep nested value")',
total_tests, current_path, result, passed;
END IF;
-- Test 17: Deep nested array access
total_tests := total_tests + 1;
test_json := jsonb_build_array(
jsonb_build_array(
jsonb_build_array(
jsonb_build_array(
'nested array value'
)
)
)
);
current_path := '[0][0][0][0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"nested array value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Deep nested array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Deep nested array access (path="%"): % | PASSED: % (expected: "nested array value")',
total_tests, current_path, result, passed;
END IF;
-- Test 18: Complex mixed nesting (object -> array -> object -> array)
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'users', jsonb_build_array(
jsonb_build_object(
'name', 'Alice',
'permissions', jsonb_build_array('read', 'write', 'admin')
),
jsonb_build_object(
'name', 'Bob',
'permissions', jsonb_build_array('read', 'write')
)
)
);
current_path := 'users[1].permissions[0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"read"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex mixed nesting (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Complex mixed nesting (path="%"): % | PASSED: % (expected: "read")',
total_tests, current_path, result, passed;
END IF;
-- Test 19: Array with mixed types
total_tests := total_tests + 1;
test_json := jsonb_build_array(
'string',
42,
true,
jsonb_build_object('key', 'value'),
jsonb_build_array(1, 2, 3)
);
current_path := '[3].key';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array with mixed types (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array with mixed types (path="%"): % | PASSED: % (expected: "value")',
total_tests, current_path, result, passed;
END IF;
-- Test 20: Path with array at the end
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'data', jsonb_build_object(
'items', jsonb_build_array(10, 20, 30, 40)
)
);
current_path := 'data.items[2]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '30'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path with array at the end (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path with array at the end (path="%"): % | PASSED: % (expected: 30)',
total_tests, current_path, result, passed;
END IF;
-- Test 21: Numeric field names
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'123', 'numeric key',
'456', jsonb_build_object(
'789', 'nested numeric key'
)
);
current_path := '456.789';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"nested numeric key"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Numeric field names (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Numeric field names (path="%"): % | PASSED: % (expected: "nested numeric key")',
total_tests, current_path, result, passed;
END IF;
-- Test 22: Special characters in field names
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'special@field', 'special value',
'nested', jsonb_build_object(
'field-with-hyphens', 'hyphenated value'
)
);
current_path := 'nested.field-with-hyphens';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"hyphenated value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Special characters in field names (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Special characters in field names (path="%"): % | PASSED: % (expected: "hyphenated value")',
total_tests, current_path, result, passed;
END IF;
-- Test 23: Array of arrays of arrays
total_tests := total_tests + 1;
test_json := jsonb_build_array(
jsonb_build_array(
jsonb_build_array(1, 2),
jsonb_build_array(3, 4)
),
jsonb_build_array(
jsonb_build_array(5, 6),
jsonb_build_array(7, 8)
)
);
current_path := '[1][0][1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '6'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array of arrays of arrays (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array of arrays of arrays (path="%"): % | PASSED: % (expected: 6)',
total_tests, current_path, result, passed;
END IF;
-- Test 24: Complex path with multiple array indices and object fields
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'companies', jsonb_build_array(
jsonb_build_object(
'name', 'Company A',
'departments', jsonb_build_array(
jsonb_build_object(
'name', 'Engineering',
'teams', jsonb_build_array(
jsonb_build_object(
'name', 'Backend',
'members', jsonb_build_array(
jsonb_build_object('name', 'John', 'role', 'Developer'),
jsonb_build_object('name', 'Jane', 'role', 'Lead')
)
)
)
)
)
)
)
);
current_path := 'companies[0].departments[0].teams[0].members[1].role';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"Lead"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Very complex path (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Very complex path (path="%"): % | PASSED: % (expected: "Lead")',
total_tests, current_path, result, passed;
END IF;
-- Test 25: Empty array and object edge cases
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'emptyArray', jsonb_build_array(),
'emptyObject', jsonb_build_object(),
'arrayWithEmptyObject', jsonb_build_array(jsonb_build_object()),
'objectWithEmptyArray', jsonb_build_object('empty', jsonb_build_array())
);
current_path := 'objectWithEmptyArray.empty';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'array' AND jsonb_array_length(result) = 0;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Empty array/object edge cases (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Empty array/object edge cases (path="%"): % | PASSED: % (expected: empty array)',
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 '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % jsonb_get_by_path tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % jsonb_get_by_path 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 % jsonb_get_by_path tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,261 @@
-- Test all template tags together
CREATE OR REPLACE FUNCTION pg_temp.diff(string1 text, string2 text) RETURNS TABLE("index" int, char1 text, char2 text) AS $$
BEGIN
RETURN QUERY WITH
s1 AS (SELECT string1 AS str),
s2 AS (SELECT string2 AS str)
SELECT i,
substring(s1.str FROM i FOR 1) AS char1,
substring(s2.str FROM i FOR 1) AS char2
FROM s1, s2,
generate_series(1, GREATEST(length(s1.str), length(s2.str))) AS i
WHERE substring(s1.str FROM i FOR 1) IS DISTINCT FROM substring(s2.str FROM i FOR 1);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION pg_temp.test_regexp_replace(string text) RETURNS text AS $$
BEGIN
RETURN regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(string, E'\t', '[TAB]', 'g'),
E'\n', '[LF]', 'g'),
E'\r', '[CR]', 'g'),
' ', '[SPACE]', 'g'),
'\s', '[WHITESPACE]', 'g');
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
total_tests INT := 0;
passed_tests INT := 0;
test_result TEXT;
expected TEXT;
passed BOOLEAN;
item INT;
c1 TEXT;
c2 TEXT;
BEGIN
-- Test 1: Template with execute tag using context from section
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"items": [
{"id": 1, "value": 100},
{"id": 2, "value": 200},
{"id": 3, "value": 300}
]
}'::jsonb,
$template$Items:
{{ for item in items }}
Item {{ item.id }}: {{ exec RETURN (context->'item'->>'value')::int * 2; }}
{{ end }}$template$
);
expected:='Items:
Item 1: 200
Item 2: 400
Item 3: 600
';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Template with execute tag using context from section: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"',
total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
FOR item, c1, c2 IN
SELECT * FROM pg_temp.diff(expected, test_result)
LOOP
RAISE NOTICE ' % | % | %', item, c1, c2;
END LOOP;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Test 2: Complex template with all tag types
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"page": {
"title": "My Page",
"sections": [
{
"id": "section1",
"title": "Section 1",
"items": [
{
"id": "item1",
"status": "active",
"content": "Item 1 Content",
"template": "item_template"
},
{
"id": "item2",
"status": "inactive",
"content": "Item 2 Content",
"template": "item_template"
}
]
}
]
},
"include": {
"meta_tags": {
"content": "<meta name=\"description\" content=\"Test Page\">"
},
"header": {
"template": "Welcome to {{ page.title }}!",
"context": {
"page": {
"title": "My Page"
}
}
},
"item_template": {
"template": "Status: {{ status }}, Content: {{ content }}"
},
"footer": {
"content": "<footer>Copyright 2024</footer>"
}
}
}'::jsonb,
$template$<!DOCTYPE html>
<html>
<head>
<title>{{ page.title }}</title>
{{ include meta_tags }}
</head>
<body>
<header>{{ include header }}</header>
<main>
{{ for section in page.sections }}
<section id="{{ section.id }}">
<h2>{{ section.title }}</h2>
{{ for item in section.items }}
<div class="item {{ item.status }}">
{{ include item.template }}
{{ exec
DECLARE
v_status TEXT;
BEGIN
v_status := context->'item'->>'status';
RETURN CASE
WHEN v_status = 'active' THEN ' (Active Item)'
ELSE ' (Inactive Item)'
END;
END;
}}
</div>
{{ end }}
</section>
{{ end }}
</main>
<footer>{{ include footer }}</footer>
</body>
</html>$template$
);
expected := '<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<meta name="description" content="Test Page">
</head>
<body>
<header>Welcome to My Page!</header>
<main>
<section id="section1">
<h2>Section 1</h2>
<div class="item active">
Status: active, Content: Item 1 Content (Active Item)
</div>
<div class="item inactive">
Status: inactive, Content: Item 2 Content (Inactive Item)
</div>
</section>
</main>
<footer><footer>Copyright 2024</footer></footer>
</body>
</html>';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"',
total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Test 3: Template with nested includes and shared context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"user": {
"name": "John",
"role": "admin"
},
"include": {
"user_info": {
"template": "User: {{ user.name }} ({{ user.role }})"
},
"permissions": {
"template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}",
"context": {
"user": {
"name": "John",
"role": "admin",
"permissions": ["read", "write", "delete"]
}
}
}
}
}'::jsonb,
$template${{ include permissions }}$template$
);
expected := 'User: John (admin) - Permissions: read write delete ';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Template with nested includes and shared context: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % combined template 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 % combined template tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,343 @@
-- Test the render function with execute 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 execute tag that sets a variable
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
'Hello {{ exec PERFORM 1; }}'
);
expected := 'Hello ';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple execute tag: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Simple execute tag: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 2: Execute tag with context access
total_tests := total_tests + 1;
DROP TABLE IF EXISTS test_output;
CREATE TEMP TABLE test_output (value TEXT);
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec INSERT INTO test_output VALUES (context->'name'); }}$expected$
);
SELECT value INTO expected FROM test_output;
passed := expected = '"John"';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with context access: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with context access: FAILED. Expected "John", got "%"',
total_tests, expected;
END IF;
-- Test 3: Execute tag with quotes and complex SQL
total_tests := total_tests + 1;
DROP TABLE IF EXISTS test_output;
CREATE TEMP TABLE test_output (value TEXT);
test_result := hemar.render(
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
$expected$Items: {{ exec
INSERT INTO test_output
SELECT jsonb_array_elements(context->'items')->>'name';
}}$expected$
);
SELECT string_agg(value, ', ' ORDER BY value) INTO expected FROM test_output;
passed := expected = 'Item 1, Item 2';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex SQL: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex SQL: FAILED. Expected "Item 1, Item 2", got "%"',
total_tests, expected;
END IF;
-- Test 4: Execute tag with output capture
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec RETURN context->>'name'; }}$expected$
);
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 %: Execute tag with output capture: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with output capture: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 5: Execute tag with complex output
total_tests := total_tests + 1;
test_result := hemar.render(
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
$expected$Items: {{ exec
RETURN (SELECT string_agg(value, ', ')
FROM (
SELECT jsonb_array_elements(context->'items')->>'name' as value
) t);
}}$expected$
);
expected := 'Items: Item 1, Item 2';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex output: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex output: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 6: Execute tag with multiple statements
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec
DECLARE
v_name TEXT;
BEGIN
v_name := context->>'name';
RETURN v_name;
END;
}}$expected$
);
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 %: Execute tag with multiple statements: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with multiple statements: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 7: Execute tag with array operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"numbers": [1, 2, 3, 4, 5]}'::jsonb,
$expected$Sum: {{ exec
RETURN (SELECT sum(value::int)
FROM jsonb_array_elements_text(context->'numbers') as value);
}}$expected$
);
expected := 'Sum: 15';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with array operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with array operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 8: Execute tag with nested JSON operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"user": {"profile": {"settings": {"theme": "dark", "notifications": true}}}}'::jsonb,
$expected$Settings: {{ exec
RETURN context->'user'->'profile'->'settings'->>'theme';
}}$expected$
);
expected := 'Settings: dark';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with nested JSON operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with nested JSON operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 9: Execute tag with conditional logic
total_tests := total_tests + 1;
test_result := hemar.render(
'{"age": 25, "country": "US"}'::jsonb,
$expected$Status: {{ exec
DECLARE
v_status TEXT;
BEGIN
IF (context->>'age')::int >= 21 AND context->>'country' = 'US' THEN
v_status := 'Adult in US';
ELSE
v_status := 'Other';
END IF;
RETURN v_status;
END;
}}$expected$
);
expected := 'Status: Adult in US';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with conditional logic: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with conditional logic: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 10: Execute tag with string manipulation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"text": "hello world"}'::jsonb,
$expected$Text: {{ exec
RETURN upper(context->>'text');
}}$expected$
);
expected := 'Text: HELLO WORLD';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with string manipulation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with string manipulation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 11: Execute tag with date operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"date": "2024-03-15"}'::jsonb,
$expected$Date: {{ exec
RETURN to_char((context->>'date')::date, 'Month DD, YYYY');
}}$expected$
);
expected := 'Date: March 15, 2024';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with date operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with date operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 12: Execute tag with aggregation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"scores": [85, 92, 78, 95, 88]}'::jsonb,
$expected$Stats: {{ exec
RETURN (SELECT format('Avg: %s, Max: %s',
round(avg(value::float)::numeric, 1),
max(value::int))
FROM jsonb_array_elements_text(context->'scores') as value);
}}$expected$
);
expected := 'Stats: Avg: 87.6, Max: 95';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with aggregation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with aggregation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 13: Execute tag with error handling
total_tests := total_tests + 1;
test_result := hemar.render(
'{"value": "not_a_number"}'::jsonb,
$expected$Result: {{ exec
BEGIN
RETURN (context->>'value')::int::text;
EXCEPTION WHEN OTHERS THEN
RETURN 'Error: Invalid number';
END;
}}$expected$
);
expected := 'Result: Error: Invalid number';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with error handling: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with error handling: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 14: Execute tag with complex JSON transformation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"users": [{"name": "Alice", "roles": ["admin", "user"]}, {"name": "Bob", "roles": ["user"]}]}'::jsonb,
$expected$Users: {{ exec
RETURN (SELECT string_agg(
format('%s (%s)',
user_data->>'name',
(SELECT string_agg(role, ', ')
FROM jsonb_array_elements_text(user_data->'roles') as role)
),
'; '
)
FROM jsonb_array_elements(context->'users') as user_data);
}}$expected$
);
expected := 'Users: Alice (admin, user); Bob (user)';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex JSON transformation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex JSON transformation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 15: Execute tag with empty/null handling
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": null, "items": []}'::jsonb,
$expected$Result: {{ exec
DECLARE
v_name TEXT;
v_count INT;
BEGIN
v_name := COALESCE(context->>'name', 'Unknown');
v_count := jsonb_array_length(context->'items');
RETURN format('Name: %s, Items: %s', v_name, v_count);
END;
}}$expected$
);
expected := 'Result: Name: Unknown, Items: 0';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with empty/null handling: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with empty/null 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 % render exec tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % render exec 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 % render exec tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,235 @@
-- Test include tag functionality
DO $$
DECLARE
result text;
total_tests integer := 0;
passed_tests integer := 0;
BEGIN
-- Test 1: Plain text inclusion
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": {
"inner_template": {
"content": "<p>Hello World</p>"
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = '<p>Hello World</p>' THEN
RAISE NOTICE 'Test %: Plain text inclusion works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: failed, Expected "<p>Hello World</p>", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Plain text inclusion: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 2: Template with separate context
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"inner_template": {
"template": "Hello {{ name }}!",
"context": {
"name": "John"
}
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = 'Hello John!' THEN
RAISE NOTICE 'Test %: Template with separate context works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: failed, Expected "Hello John!", got "%"', total_tests, result;
END IF;
-- Test 3: Template with shared context
total_tests := total_tests + 1;
result := hemar.render(
'{
"name": "John",
"include": {
"inner_template": {
"template": "Hello {{ name }}!"
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = 'Hello John!' THEN
RAISE NOTICE 'Test % passed: Template with shared context works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "Hello John!", got "%"', total_tests, result;
END IF;
-- Test 4: Nested includes
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"outer_template": {
"template": "Outer: {{ include inner_template }}",
"context": {
"include": {
"inner_template": {
"template": "Inner: {{ name }}",
"context": {
"name": "John"
}
}
}
}
}
}
}'::jsonb,
$hemar${{ include outer_template }}$hemar$
);
IF result = 'Outer: Inner: John' THEN
RAISE NOTICE 'Test % passed: Nested includes work correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "Outer: Inner: John", got "%"', total_tests, result;
END IF;
-- Test 5: Complex template with multiple includes
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"header": {
"content": "<header>Welcome</header>"
},
"content": {
"template": "Hello {{ user.name }}!",
"context": {
"user": {
"name": "John"
}
}
},
"footer": {
"template": "Copyright {{ year }}",
"context": {
"year": "2024"
}
}
}
}'::jsonb,
$hemar$Header: {{ include header }}
Content: {{ include content }}
Footer: {{ include footer }}$hemar$
);
IF result = 'Header: <header>Welcome</header>
Content: Hello John!
Footer: Copyright 2024' THEN
RAISE NOTICE 'Test % passed: Complex template with multiple includes works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected , got "%"', total_tests, result;
END IF;
-- Test 6: Error handling - missing include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{{ include missing_template }}',
'{}'::jsonb
);
RAISE WARNING 'Test % failed: Should have raised an error for missing include data', total_tests;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Test % passed: Error handling for missing include data works correctly', total_tests;
passed_tests := passed_tests + 1;
END;
-- Test 7: Error handling - invalid include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": {
"invalid_template": "not an object"
}
}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for invalid include data works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Should have raised an error for invalid include data', total_tests;
END;
-- Test 8: Error handling - unexisting include object
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for unexisting include object works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Should have raised an error for unexisting include object', total_tests;
END;
-- Test 9: Error handling - unexisting include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": { }
}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for unexisting include data works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Should have raised an error for unexisting include data', total_tests;
END;
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template include tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % template include 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 % template include tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View 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 $$;

View File

@@ -0,0 +1,401 @@
-- Test section rendering
DO $$
DECLARE
test_result text;
expected text;
total_tests integer := 0;
passed_tests integer := 0;
BEGIN
-- Test 1: String iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"text": "Hello"}'::jsonb,
'{{for char in text}}{{char}}{{end}}'
);
expected := 'Hello';
IF test_result = expected THEN
RAISE NOTICE 'Test %: String iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: String iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: String iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 2: Array iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"numbers": [1, 2, 3]}'::jsonb,
'{{for num in numbers}}{{num}}{{end}}'
);
expected := '123';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Array iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Array iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Array iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 3: Object iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"user": {"name": "John", "age": 30}}'::jsonb,
'{{for item in user}}{{item.key}}: {{item.value}}{{end}}'
);
expected := 'age: 30name: John';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Object iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Object iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Object iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 4: Boolean condition (true)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"show": true}'::jsonb,
'{{for show in show}}Content{{end}}'
);
expected := 'Content';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Boolean condition (true): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (true): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (true): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 5: Boolean condition (false)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"show": false}'::jsonb,
'{{for show in show}}Content{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Boolean condition (false): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (false): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (false): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 6: Nested sections
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": [{"name": "Item 1", "tags": ["tag1", "tag2"]}, {"name": "Item 2", "tags": ["tag3"]}]}'::jsonb,
'{{for item in items}}{{item.name}}: {{for tag in item.tags}}{{tag}} {{end}}{{end}}'
);
expected := 'Item 1: tag1 tag2 Item 2: tag3 ';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Nested sections: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Nested sections: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Nested sections: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 7: Section with context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": ["a", "b"], "prefix": "Item: "}'::jsonb,
'{{for item in items}}{{prefix}}{{item}}{{end}}'
);
expected := 'Item: aItem: b';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section with context: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section with context: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section with context: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 8: Empty array
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": []}'::jsonb,
'{{for item in items}}{{item}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Empty array: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty array: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty array: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 9: Empty object
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"user": {}}'::jsonb,
'{{for key in user}}{{key}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Empty object: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty object: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty object: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 10: Invalid collection type (number)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"number": 42}'::jsonb,
'{{for item in number}}{{item}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Invalid collection type: PASSED (error raised as expected)', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Invalid collection type: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Invalid collection type: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 11: Section whitespaces
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}item{{end}}'
);
expected := 'itemitemitem';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 12: Section whitespaces 2
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item
{{end}}'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 2: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 2: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 2: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 13: Section whitespaces 3
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}} item
{{end}}'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 3: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 3: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 3: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 14: Section whitespaces 4
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item {{end}}'
);
expected := ' item item item ';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 4: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 4: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 4: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 15: Section whitespaces 5
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item
{{end}}
'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 5: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 5: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 5: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 16: Tabs
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 17: Tabs 2
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 18: Context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"value": 12, "array": [1, 2, 3]}'::jsonb,
'
{{for item in array}}
{{exec RETURN context::TEXT}}
{{end}}
'
);
expected := '
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Context: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Context: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Context: FAILED with error: %', total_tests, SQLERRM;
END;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template section tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % template section 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 % template section tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

File diff suppressed because it is too large Load Diff