feat: find element in jsonb by path is works!!!

This commit is contained in:
2025-05-15 17:48:28 +00:00
parent c28e7fcfd5
commit 54684c0f78
4 changed files with 754 additions and 1413 deletions

View File

@@ -2,31 +2,14 @@
\echo Use "CREATE EXTENSION hemar" to load this file. \quit \echo Use "CREATE EXTENSION hemar" to load this file. \quit
CREATE SCHEMA hemar; CREATE SCHEMA hemar;
-- Define the template rendering functions using hectic library
-- Expected usage:
-- ```sql
-- SELECT "hemar"."render"(
-- "declare" :=
-- jsonb_build_object(
-- 'name', 'test',
-- 'config', jsonb_build_object(
-- 'debug', true,
-- 'limit', 100
-- )
-- ),
-- "template" := $hemar$
-- {{ name }} {{ config.limit }}
-- $hemar$
-- );
-- ```
CREATE FUNCTION "hemar"."render"("declare" jsonb, "template" text)
RETURNS text
LANGUAGE C STRICT
AS 'hemar', 'pg_render';
-- Parse function returns the structure of a template for debugging -- Parse function returns the structure of a template for debugging
CREATE FUNCTION "hemar"."parse"("template" text) CREATE FUNCTION "hemar"."parse"("template" text)
RETURNS text RETURNS text
LANGUAGE C STRICT LANGUAGE C STRICT
AS 'hemar', 'pg_template_parse'; 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';

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,333 +1,267 @@
-- Test file for hemar.jsonb_get_by_path function
-- Test file for hemar.jsonb_get_by_path function
-- Run with: psql -f test_jsonb_path.sql -- Run with: psql -f test_jsonb_path.sql
-- Load extension if not already loaded -- Load extension if not already loaded
-- CREATE EXTENSION IF NOT EXISTS hemar; -- CREATE EXTENSION IF NOT EXISTS hemar;
-- Create sample test data -- Create sample test data
DO $$ DO $$
DECLARE DECLARE
test_json jsonb; test_json jsonb;
result jsonb; result jsonb;
passed boolean; passed boolean;
total_tests integer := 0; total_tests integer := 0;
passed_tests integer := 0; passed_tests integer := 0;
current_path text;
BEGIN BEGIN
test_json := jsonb_build_object( test_json := jsonb_build_object(
'name', 'John Doe', 'name', 'John Doe',
'age', 30, 'age', 30,
'is_active', true, 'is_active', true,
'tags', jsonb_build_array('developer', 'postgresql', 'jsonb'), 'tags', jsonb_build_array('developer', 'postgresql', 'jsonb'),
'address', jsonb_build_object( 'address', jsonb_build_object(
'street', '123 Main St', 'street', '123 Main St',
'city', 'New York', 'city', 'New York',
'zip', '10001' 'zip', '10001'
), ),
'contacts', jsonb_build_array( 'contacts', jsonb_build_array(
jsonb_build_object( jsonb_build_object(
'type', 'email', 'type', 'email',
'value', 'john@example.com' 'value', 'john@example.com'
), ),
jsonb_build_object( jsonb_build_object(
'type', 'phone', 'type', 'phone',
'value', '555-1234', 'value', '555-1234',
'verified', true 'verified', true
) )
), ),
'skills', jsonb_build_array( 'skills', jsonb_build_array(
jsonb_build_array('PostgreSQL', 5), jsonb_build_array('PostgreSQL', 5),
jsonb_build_array('Python', 4), jsonb_build_array('Python', 4),
jsonb_build_array('JavaScript', 3) jsonb_build_array('JavaScript', 3)
) )
); );
-- Test basic field access -- Test basic field access
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'name'); current_path := 'name';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"John Doe"'::jsonb; passed := result = '"John Doe"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Simple field access (string): % | PASSED: %', RAISE NOTICE 'Test %: Simple field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Simple field access (string): % | PASSED: % (expected: "John Doe")', RAISE WARNING 'Test %: Simple field access (path="%"): % | PASSED: % (expected: "John Doe")',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'age'); current_path := 'age';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '30'::jsonb; passed := result = '30'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Numeric field access (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Numeric field access: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Numeric field access: % | PASSED: % (expected: 30)', RAISE WARNING 'Test %: Numeric field access (path="%"): % | PASSED: % (expected: 30)',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'is_active'); current_path := 'is_active';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = 'true'::jsonb; passed := result = 'true'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Boolean field access: % | PASSED: %', RAISE NOTICE 'Test %: Boolean field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Boolean field access: % | PASSED: % (expected: true)', RAISE WARNING 'Test %: Boolean field access (path="%"): % | PASSED: % (expected: true)',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
-- Test nested field access -- Test nested field access
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'address.city'); current_path := 'address.city';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"New York"'::jsonb; passed := result = '"New York"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Nested object field access (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Nested object field access: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Nested object field access: % | PASSED: % (expected: "New York")', RAISE WARNING 'Test %: Nested object field access (path="%"): % | PASSED: % (expected: "New York")',
total_tests, current_path, result, passed;
total_tests, result, passed;
END IF; END IF;
-- Test array access -- Test array access
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'tags[1]'); current_path := 'tags[1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"postgresql"'::jsonb; passed := result = '"postgresql"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Simple array access (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Simple array access: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Simple array access: % | PASSED: % (expected: "postgresql")',
total_tests, result, passed; RAISE WARNING 'Test %: Simple array access (path="%"): % | PASSED: % (expected: "postgresql")',
total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'contacts[0].type'); current_path := 'contacts[0].type';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"email"'::jsonb; passed := result = '"email"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Object in array access: % | PASSED: %', RAISE NOTICE 'Test %: Object in array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Object in array access: % | PASSED: % (expected: "email")', RAISE WARNING 'Test %: Object in array access (path="%"): % | PASSED: % (expected: "email")',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'skills[1][0]'); current_path := 'skills[1][0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"Python"'::jsonb; passed := result = '"Python"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Nested array access (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Nested array access: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Nested array access: % | PASSED: % (expected: "Python")', RAISE WARNING 'Test %: Nested array access (path="%"): % | PASSED: % (expected: "Python")',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'contacts[1].value'); current_path := 'contacts[1].value';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"555-1234"'::jsonb; passed := result = '"555-1234"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Complex path with multiple array indices: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Complex path with multiple array indices: % | PASSED: % (expected: "555-1234")', RAISE WARNING 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: % (expected: "555-1234")',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
-- Test object and array returns -- Test object and array returns
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'address'); current_path := 'address';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'object'; passed := jsonb_typeof(result) = 'object';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Path to object (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Path to object: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Path to object: % | PASSED: % (expected type: object, got: %)', RAISE WARNING 'Test %: Path to object (path="%"): % | PASSED: % (expected type: object, got: %)',
total_tests, current_path, result, passed, jsonb_typeof(result);
total_tests, result, passed, jsonb_typeof(result);
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'contacts'); current_path := 'contacts';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'array'; passed := jsonb_typeof(result) = 'array';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Path to array (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Path to array: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Path to array: % | PASSED: % (expected type: array, got: %)', RAISE WARNING 'Test %: Path to array (path="%"): % | PASSED: % (expected type: array, got: %)',
total_tests, result, passed, jsonb_typeof(result); total_tests, current_path, result, passed, jsonb_typeof(result);
END IF; END IF;
-- Test error cases -- Test error cases
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'unknown_field'); current_path := 'unknown_field';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL; passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Non-existent field (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Non-existent field: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Non-existent field: % | PASSED: % (expected: NULL)', RAISE WARNING 'Test %: Non-existent field (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
total_tests, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'address.country'); current_path := 'address.country';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL; passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Non-existent nested field: % | PASSED: %', RAISE NOTICE 'Test %: Non-existent nested field (path="%"): % | PASSED: %',
total_tests, result, passed; total_tests, current_path, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Non-existent nested field (path="%"): % | PASSED: % (expected: NULL)',
RAISE WARNING 'Test %: Non-existent nested field: % | PASSED: % (expected: NULL)', total_tests, current_path, result, passed;
total_tests, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'tags[10]'); current_path := 'tags[10]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL; passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Array index out of bounds (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Array index out of bounds: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Array index out of bounds: % | PASSED: % (expected: NULL)', RAISE WARNING 'Test %: Array index out of bounds (path="%"): % | PASSED: % (expected: NULL)',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
-- Test edge cases -- Test edge cases
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, ''); current_path := '';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL; passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Empty path (path="%"): % | PASSED: %',
RAISE NOTICE 'Test %: Empty path: % | PASSED: %', total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Empty path: % | PASSED: % (expected: NULL)', RAISE WARNING 'Test %: Empty path (path="%"): % | PASSED: % (expected: NULL)',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(test_json, 'skills[0][1]'); current_path := 'skills[0][1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '5'::jsonb; passed := result = '5'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN IF passed THEN
RAISE NOTICE 'Test %: Multiple array indices: % | PASSED: %', RAISE NOTICE 'Test %: Multiple array indices (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
total_tests, result, passed;
ELSE ELSE
RAISE WARNING 'Test %: Multiple array indices: % | PASSED: % (expected: 5)', RAISE WARNING 'Test %: Multiple array indices (path="%"): % | PASSED: % (expected: 5)',
total_tests, result, passed; total_tests, current_path, result, passed;
END IF; END IF;
-- Print summary -- Print summary
RAISE NOTICE '------------------------------------';
IF passed_tests = total_tests THEN IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % tests passed (100%%)', RAISE NOTICE 'SUMMARY: % of % tests passed (100%%)',
passed_tests, total_tests; passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % tests passed (%)', RAISE WARNING 'SUMMARY: % of % tests passed (%)',
passed_tests, passed_tests,
total_tests, total_tests,
round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%'; round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%';
RAISE WARNING '------------------------------------';
END IF; END IF;
RAISE NOTICE '------------------------------------';
IF passed_tests != total_tests THEN IF passed_tests != total_tests THEN
RAISE EXCEPTION 'Tests failed: % of % tests did not pass', (total_tests - passed_tests), total_tests; RAISE EXCEPTION 'Tests failed: % of % tests did not pass', (total_tests - passed_tests), total_tests;
END IF; END IF;
END $$;
END $$;