diff --git a/README.md b/README.md index 8f236e0..0e5c44f 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ Hello {{you}}! You just won {{qt}} at {{at}}. $$ language plmustache; -select win_money('Slonik', '12000', now()); +select win_money('Sir Meowalot', '12000', now()); win_money ----------------------------------------------------------- - Hello Slonik! + + Hello Sir Meowalot! + You just won $12,000.00 at 2023-12-04 07:44:26.915735-05. (1 row) ``` @@ -60,6 +60,8 @@ select do_not_escape_me(''); ### Sections +Boolean sections: + ```sql create or replace function show_cat(cat text, show bool default true) returns text as $$ {{#show}} @@ -85,6 +87,21 @@ select show_cat('Mr. Sleepy', false); (1 row) ``` +Array iterators: + +```sql +create or replace function hello_cats(cats text[]) returns text as $$ +Say hello to: {{#cats}}{{.}}, {{/cats}} +$$ language plmustache; + + +postgres=# select hello_cats('{Sir Meowalot, Mr. Sleepy, Paquito}'); + hello_cats +--------------------------------------------------- + Say hello to: Sir Meowalot, Mr. Sleepy, Paquito, +(1 row) +``` + ## Installation Clone the repo and submodules: diff --git a/src/build.c b/src/build.c index c84df1d..2f0543e 100644 --- a/src/build.c +++ b/src/build.c @@ -19,13 +19,16 @@ const char IMPLICIT_ITERATOR = '.'; static int -plmustache_enter_section(void *userdata, const char *name){ +plmustache_section_enter(void *userdata, const char *name){ plmustache_ctx *ctx = (plmustache_ctx *)userdata; for(size_t i = 0; i < ctx->num_params; i++){ plmustache_param* prm = &ctx->params[i]; if(strcmp(prm->prm_name, name) == 0){ + ctx->section_key = prm->prm_name; + ctx->section_arr_length = prm->is_array? prm->prm_arr_length : 0; + ctx->section_idx = 0; return prm->enters_section; } } @@ -33,13 +36,17 @@ plmustache_enter_section(void *userdata, const char *name){ return 0; } -// TODO these are used to handle sections, they're mandatory otherwise mustach segfaults -static int plmustache_next(void *closure){ - return MUSTACH_OK; +static int plmustache_section_next(void *userdata){ + plmustache_ctx *ctx = (plmustache_ctx *)userdata; + return ctx->section_idx < ctx->section_arr_length; } static int -plmustache_leave(void *closure){ +plmustache_section_leave(void *userdata){ + plmustache_ctx *ctx = (plmustache_ctx *)userdata; + ctx->section_key = NULL; + ctx->section_idx = 0; + ctx->section_arr_length = 0; return MUSTACH_OK; } @@ -47,10 +54,20 @@ static int plmustache_get_variable(void *userdata, const char *name, struct mustach_sbuf *sbuf){ plmustache_ctx *ctx = (plmustache_ctx *)userdata; - for(size_t i = 0; i < ctx->num_params; i++){ + if (name[0] == IMPLICIT_ITERATOR){ + for(size_t i = 0; i < ctx->num_params; i++){ + plmustache_param* prm = &ctx->params[i]; + + if(strcmp(prm->prm_name,ctx->section_key) == 0){ + sbuf->value = prm->prm_arr[ctx->section_idx]; + } + } + ctx->section_idx = ctx->section_idx + 1; + } + else for(size_t i = 0; i < ctx->num_params; i++){ plmustache_param* prm = &ctx->params[i]; - if(strcmp(prm->prm_name, name) == 0){ + if(strcmp(name, prm->prm_name) == 0){ sbuf->value = prm->prm_value; } } @@ -60,9 +77,9 @@ plmustache_get_variable(void *userdata, const char *name, struct mustach_sbuf *s struct mustach_itf build_mustach_itf(){ return (struct mustach_itf) - { .enter = plmustache_enter_section - , .next = plmustache_next - , .leave = plmustache_leave + { .enter = plmustache_section_enter + , .next = plmustache_section_next + , .leave = plmustache_section_leave , .get = plmustache_get_variable }; } @@ -143,15 +160,42 @@ build_mustache_ctx(plmustache_call_info call_info, NullableDatum args[]){ for(size_t i = 0; i < call_info.numargs; i++){ params[i].prm_name = call_info.argnames[i]; NullableDatum arg = args[i]; + Oid arg_type = call_info.argtypes[i]; + Oid array_elem_type = get_element_type(arg_type); + bool arg_is_array = array_elem_type != InvalidOid; + if(arg.isnull){ params[i].prm_value = NULL; params[i].enters_section = false; + params[i].is_array = false; }else{ params[i].prm_value = datum_to_cstring(arg.value, call_info.argtypes[i], ereporter); - if(call_info.argtypes[i] == BOOLOID) + if(arg_type == BOOLOID) params[i].enters_section = DatumGetBool(arg.value); else params[i].enters_section = true; + + if(arg_is_array){ + params[i].is_array = true; + ArrayType *array = DatumGetArrayTypeP(arg.value); + ArrayIterator array_iterator = array_create_iterator(array, 0, NULL); + int arr_ndim = ARR_NDIM(array); + int arr_length = ArrayGetNItems(arr_ndim, ARR_DIMS(array)); + if(arr_ndim > 1) + ereport(ERROR, errmsg("support for multidimensional arrays is not implemented")); + + if(arr_length > 0){ + Datum value; bool isnull; int j = 0; + params[i].prm_arr_length = arr_length; + params[i].prm_arr = palloc0(sizeof(char*) * arr_length); + + while (array_iterate(array_iterator, &value, &isnull)) { + params[i].prm_arr[j] = isnull? NULL : datum_to_cstring(value, array_elem_type, ereporter); + j++; + } + } else + params[i].enters_section = false; + } } } } diff --git a/src/build.h b/src/build.h index daa6469..c90caef 100644 --- a/src/build.h +++ b/src/build.h @@ -6,14 +6,20 @@ #include "observation.h" typedef struct { - char *prm_name; - char *prm_value; - bool enters_section; + char *prm_name; + char *prm_value; + char **prm_arr; + size_t prm_arr_length; + bool enters_section; + bool is_array; } plmustache_param; typedef struct { size_t num_params; plmustache_param *params; + char *section_key; + size_t section_idx; + size_t section_arr_length; char *template; } plmustache_ctx; diff --git a/test/expected/sections.out b/test/expected/sections.out index be5512b..6699b65 100644 --- a/test/expected/sections.out +++ b/test/expected/sections.out @@ -67,6 +67,8 @@ select foo_test(false); create or replace function foo_null_test(foo bool) returns text as $$ foo is {{#foo}}full{{/foo}}{{^foo}}null{{/foo}} $$ language plmustache; +\echo + select foo_null_test(null); foo_null_test --------------- @@ -79,3 +81,76 @@ select foo_null_test(true); foo is full (1 row) +create or replace function foo_array(arr text[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select foo_array(ARRAY['one', 'two', 'three']); + foo_array +-------------------------- + arr is one, two, three, +(1 row) + +create or replace function foo_array(arr int[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select foo_array(ARRAY[1, 2, 3]::int[]); + foo_array +------------------ + arr is 1, 2, 3, +(1 row) + +-- empty array is handled properly +select foo_array(ARRAY[]::int[]); + foo_array +----------- + arr is +(1 row) + +create or replace function mixed_var_array(var int, arr int[]) returns text as $$ +var is {{var}}, arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select mixed_var_array(4, ARRAY[1, 2, 3]::int[]); + mixed_var_array +---------------------------- + var is 4, arr is 1, 2, 3, +(1 row) + +create or replace function mixed_array_var(arr int[], var int) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} var is {{var}} +$$ language plmustache; +\echo + +select mixed_array_var(ARRAY[1, 2, 3]::int[], 4); + mixed_array_var +--------------------------- + arr is 1, 2, 3, var is 4 +(1 row) + +create or replace function mixed_array_var_array(arr1 int[], var text, arr2 int[]) returns text as $$ +arr1 is {{#arr1}}{{.}}, {{/arr1}} var is {{var}} +arr2 is {{#arr2}}{{.}}, {{/arr2}} var is {{var}} +$$ language plmustache; +\echo + +select mixed_array_var_array(ARRAY[1, 2, 3], 'something', ARRAY[4, 5, 6]); + mixed_array_var_array +------------------------------------ + arr1 is 1, 2, 3, var is something+ + arr2 is 4, 5, 6, var is something +(1 row) + +create or replace function nested_array(arr int[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select nested_array(ARRAY[[1,2,3], [4,5,6]]); +ERROR: support for multidimensional arrays is not implemented +select nested_array(ARRAY[[[1,2], [3,4]], [[5,6], [7,8]]]); +ERROR: support for multidimensional arrays is not implemented diff --git a/test/sql/sections.sql b/test/sql/sections.sql index 6aa892e..8806bea 100644 --- a/test/sql/sections.sql +++ b/test/sql/sections.sql @@ -38,7 +38,56 @@ select foo_test(false); create or replace function foo_null_test(foo bool) returns text as $$ foo is {{#foo}}full{{/foo}}{{^foo}}null{{/foo}} $$ language plmustache; +\echo select foo_null_test(null); select foo_null_test(true); + +create or replace function foo_array(arr text[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select foo_array(ARRAY['one', 'two', 'three']); + +create or replace function foo_array(arr int[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select foo_array(ARRAY[1, 2, 3]::int[]); + +-- empty array is handled properly +select foo_array(ARRAY[]::int[]); + +create or replace function mixed_var_array(var int, arr int[]) returns text as $$ +var is {{var}}, arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select mixed_var_array(4, ARRAY[1, 2, 3]::int[]); + +create or replace function mixed_array_var(arr int[], var int) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} var is {{var}} +$$ language plmustache; +\echo + +select mixed_array_var(ARRAY[1, 2, 3]::int[], 4); + +create or replace function mixed_array_var_array(arr1 int[], var text, arr2 int[]) returns text as $$ +arr1 is {{#arr1}}{{.}}, {{/arr1}} var is {{var}} +arr2 is {{#arr2}}{{.}}, {{/arr2}} var is {{var}} +$$ language plmustache; +\echo + +select mixed_array_var_array(ARRAY[1, 2, 3], 'something', ARRAY[4, 5, 6]); + +create or replace function nested_array(arr int[]) returns text as $$ +arr is {{#arr}}{{.}}, {{/arr}} +$$ language plmustache; +\echo + +select nested_array(ARRAY[[1,2,3], [4,5,6]]); + +select nested_array(ARRAY[[[1,2], [3,4]], [[5,6], [7,8]]]);