Для приготовления шаблонизатора mustach нам понадобится postgres и mustach. Можно также воспользоваться готовым образом.

Зачем нужен шаблонизатор в базе? Ну, во-первых, если шаблонизатор в базе, то и сами шаблоны тоже должны быть в базе. А зачем нужно хранить шаблоны в базе? Да потому, что шаблоны, как и данные, тоже могут зависеть от времени. Например, пусть в базе есть счета (это данные). Очевидно, что они зависят от времени: в этом месяце сумма одна, в следующем — другая, потом — третья и т.д. Но и шаблон счёта тоже может зависеть от времени: в этом году один, а в следующем уже другой (как это было с введением 20% ). Поэтому удобнее сами шаблоны тоже хранить в базе. Ну а шаблонизатор в базе удобен тем, что можно тут же в базе шаблонизировать, потом (тут же в базе) преобразовать в pdf и (тут же в базе) отправить на email. И всё это можно сделать асинхронно с помощью планировщика.

Код получился не слишком большой, поэтому выкладываю его весь (с добавлением комментариев)

#include <postgres.h> // подключаем необходимые заголовки.

#include <catalog/pg_type.h> // и ещё
extern text *cstring_to_text(const char *s); // добавляем объявления
extern text *cstring_to_text_with_len(const char *s, int len); // используемых функций
extern char *text_to_cstring(const text *t); // потому что, 
extern void text_to_cstring_buffer(const text *src, char *dst, size_t dst_len); // если подключить заголовки
#define CStringGetTextDatum(s) PointerGetDatum(cstring_to_text(s)) // то происходит конфликт
#define TextDatumGetCString(d) text_to_cstring((text *) DatumGetPointer(d)) // с именем json_object
#include <mustach/mustach-json-c.h> // вот из этого заголовка

#define EXTENSION(function) Datum (function)(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(function); Datum (function)(PG_FUNCTION_ARGS) // макрос для объявления функции расширения

PG_MODULE_MAGIC; // необходимо для расширения

EXTENSION(json2mustach) { // функция шаблонизации 
    char *file; // имя файла результата
    char *json; // данные для шаблонизации
    char *output_data; // результат в памяти
    char *template; // шаблон
    enum json_tokener_error error; // переменная для хранения ошибки парсера
    FILE *out; // файл результата
    size_t output_len; // размер результата в памяти
    struct json_object *object; // объект парсера
    text *output; // результат
    if (PG_ARGISNULL(0)) ereport(ERROR, (errmsg("json is null!"))); // первый аргумент не может быть NULL
    if (PG_ARGISNULL(1)) ereport(ERROR, (errmsg("template is null!"))); // и второй - тоже
    json = TextDatumGetCString(PG_GETARG_DATUM(0)); // получаем C-строку из первого аргумента
    template = TextDatumGetCString(PG_GETARG_DATUM(1)); // и из второго
    if (!(object = json_tokener_parse_verbose(json, &error))) ereport(ERROR, (errmsg("!json_tokener_parse and %s", json_tokener_error_desc(error)))); // парсим данные для шаблонизации и при неудаче сообщаем об этом
    switch (PG_NARGS()) { // в зависимости от количества аргументов
        case 2: if (!(out = open_memstream(&output_data, &output_len))) ereport(ERROR, (errmsg("!open_memstream"))); break; // при двух аргументах открываем файл для результата в памяти
        case 3: // при трёх аргументах
            if (PG_ARGISNULL(2)) ereport(ERROR, (errmsg("file is null!"))); // третий аргумент не может быть NULL
            file = TextDatumGetCString(PG_GETARG_DATUM(2)); // получаем C-строку из третьего аргумента
            if (!(out = fopen(file, "wb"))) ereport(ERROR, (errmsg("!fopen"))); // открываем файл для результата и при неудаче сообщаем об этом
            pfree(file); // освобождаем память
            break; // продолжаем
        default: ereport(ERROR, (errmsg("expect be 2 or 3 args"))); // может быть только два или три аргумента 
    }
    if (fmustach_json_c(template, object, out)) ereport(ERROR, (errmsg("fmustach_json_c"))); // шаблонизируем и в случае неудачи сообщаем об этом
    pfree(json); // освобождаем память
    pfree(template); // и ещё
    if (!json_object_put(object)) ereport(ERROR, (errmsg("!json_object_put"))); // освобождаем память парсера
    switch (PG_NARGS()) { // в зависимости от количества аргументов
        case 2: // при двух аргументах
            fclose(out); // заканчиваем файл в памяти
            output = cstring_to_text_with_len(output_data, output_len); // получаем данные результата
            free(output_data); // освобождаем память
            PG_RETURN_TEXT_P(output); // возвращаем результат
            break; // продолжаем
        case 3: PG_RETURN_BOOL(true); break; // при трёх аргументах просто возвращаем успех
        default: ereport(ERROR, (errmsg("expect be 2 or 3 args"))); // может быть только два или три аргумента 
    }
}

Используем это так: сначала создаём расширение

create extension pg_mustach

а потом шаблонизируем в память

select json2mustach('{"name":"Мир"}', 'Здравствуй, {{name}}!')

или в файл

select json2mustach('{"name":"Мир"}', 'Здравствуй, {{name}}!', 'file_name')