/**
 * Copyright (c) 2013, Timothy Stack
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither the name of Timothy Stack nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @file sqlite-extension-func.c
 */

#include "config.h"

#include <stdio.h>
#include <assert.h>

#include "lnav_util.hh"
#include "lnav_log.hh"

#include "sqlite-extension-func.hh"

using namespace std;

sqlite_registration_func_t sqlite_registration_funcs[] = {
    common_extension_functions,
    state_extension_functions,
    string_extension_functions,
    network_extension_functions,
    fs_extension_functions,
    json_extension_functions,
    time_extension_functions,

    NULL
};

multimap<std::string, help_text *> sqlite_function_help;

int register_sqlite_funcs(sqlite3 *db, sqlite_registration_func_t *reg_funcs)
{
    int lpc;

    assert(db != NULL);
    assert(reg_funcs != NULL);
    
    for (lpc = 0; reg_funcs[lpc]; lpc++) {
        struct FuncDef *basic_funcs = NULL;
        struct FuncDefAgg *agg_funcs = NULL;
        int i;

        reg_funcs[lpc](&basic_funcs, &agg_funcs);

        for (i = 0; basic_funcs && basic_funcs[i].zName; i++) {
            struct FuncDef &fd = basic_funcs[i];

            //sqlite3CreateFunc
            /* LMH no error checking */
            sqlite3_create_function(db,
                                    basic_funcs[i].zName,
                                    basic_funcs[i].nArg,
                                    basic_funcs[i].eTextRep,
                                    (void *) &fd,
                                    basic_funcs[i].xFunc,
                                    nullptr,
                                    nullptr);

            if (fd.fd_help.ht_context != HC_NONE) {
                help_text &ht = fd.fd_help;

                sqlite_function_help.insert(make_pair(ht.ht_name, &ht));
                ht.index_tags();
            }
        }

        for (i = 0; agg_funcs && agg_funcs[i].zName; i++) {
            //sqlite3CreateFunc
            sqlite3_create_function(db,
                                    agg_funcs[i].zName,
                                    agg_funcs[i].nArg,
                                    SQLITE_UTF8,
                                    (void *) &agg_funcs[i],
                                    0,
                                    agg_funcs[i].xStep,
                                    agg_funcs[i].xFinalize);
        }
    }

    static help_text builtin_funcs[] = {
        help_text("abs",
                  "Return the absolute value of the argument")
            .sql_function()
            .with_parameter({"x", "The number to convert"})
            .with_tags({"math"})
            .with_example({"SELECT abs(-1)"}),

        help_text("changes",
                  "The number of database rows that were changed, inserted, or deleted by the most recent statement.")
            .sql_function(),

        help_text("char",
                  "Returns a string composed of characters having the given unicode code point values")
            .sql_function()
            .with_parameter(help_text("X", "The unicode code point values")
                                .zero_or_more())
            .with_tags({"string"})
            .with_example({"SELECT char(0x48, 0x49)"}),

        help_text("coalesce",
                  "Returns a copy of its first non-NULL argument, or NULL if all arguments are NULL")
            .sql_function()
            .with_parameter({"X", "A value to check for NULL-ness"})
            .with_parameter(help_text("Y", "A value to check for NULL-ness")
                                .one_or_more())
            .with_example({"SELECT coalesce(null, 0, null)"}),

        help_text("glob",
                  "Match a string against Unix glob pattern")
            .sql_function()
            .with_parameter({"pattern", "The glob pattern"})
            .with_parameter({"str", "The string to match"})
            .with_example({"SELECT glob('a*', 'abc')"}),

        help_text("hex",
                  "Returns a string which is the upper-case hexadecimal rendering of the content of its argument.")
            .sql_function()
            .with_parameter({"X", "The blob to convert to hexadecimal"})
            .with_example({"SELECT hex('abc')"}),

        help_text("ifnull",
                  "Returns a copy of its first non-NULL argument, or NULL if both arguments are NULL")
            .sql_function()
            .with_parameter({"X", "A value to check for NULL-ness"})
            .with_parameter({"Y", "A value to check for NULL-ness"})
            .with_example({"SELECT ifnull(null, 0)"}),

        help_text("instr",
                  "Finds the first occurrence of the needle within the haystack and returns the number of prior characters plus 1, or 0 if Y is nowhere found within X")
            .sql_function()
            .with_parameter({"haystack", "The string to search within"})
            .with_parameter({"needle", "The string to look for in the haystack"})
            .with_tags({"string"})
            .with_example({"SELECT instr('abc', 'b')"}),

        help_text("last_insert_rowid",
                  "Returns the ROWID of the last row insert from the database connection which invoked the function")
            .sql_function(),

        help_text("length",
                  "Returns the number of characters (not bytes) in the given string prior to the first NUL character")
            .sql_function()
            .with_parameter({"str", "The string to determine the length of"})
            .with_tags({"string"})
            .with_example({"SELECT length('abc')"}),

        help_text("like",
                  "Match a string against a pattern")
            .sql_function()
            .with_parameter({"pattern",
                             "The pattern to match.  "
                                 "A percent symbol (%) will match zero or more characters "
                                 "and an underscore (_) will match a single character."})
            .with_parameter({"str", "The string to match"})
            .with_parameter(help_text("escape",
                                      "The escape character that can be used to prefix a literal percent or underscore in the pattern.")
                                .optional())
            .with_example({"SELECT like('%b%', 'aabcc')"})
            .with_example({"SELECT like('%b:%', 'aab%', ':')"}),

        help_text("likelihood",
                  "Provides a hint to the query planner that the first argument is a boolean that is true with the given probability")
            .sql_function()
            .with_parameter({"value", "The boolean value to return"})
            .with_parameter({"probability", "A floating point constant between 0.0 and 1.0"}),

        help_text("likely",
                  "Short-hand for likelihood(X,0.9375)")
            .sql_function()
            .with_parameter({"value", "The boolean value to return"}),

        help_text("load_extension",
                  "Loads SQLite extensions out of the given shared library file using the given entry point.")
            .sql_function()
            .with_parameter({"path", "The path to the shared library containing the extension."})
            .with_parameter(help_text("entry-point", "")
                                .optional()),

        help_text("lower",
                  "Returns a copy of the given string with all ASCII characters converted to lower case.")
            .sql_function()
            .with_parameter({"str", "The string to convert."})
            .with_tags({"string"})
            .with_example({"SELECT lower('AbC')"}),

        help_text("ltrim",
                  "Returns a string formed by removing any and all characters that appear in the second argument from the left side of the first.")
            .sql_function()
            .with_parameter({"str", "The string to trim characters from the left side"})
            .with_parameter(help_text("chars", "The characters to trim.  Defaults to spaces.")
                                .optional())
            .with_tags({"string"})
            .with_example({"SELECT ltrim('   abc')"})
            .with_example({"SELECT ltrim('aaaabbbc', 'ab')"}),

        help_text("max",
                  "Returns the argument with the maximum value, or return NULL if any argument is NULL.")
            .sql_function()
            .with_parameter(help_text("X", "The numbers to find the maximum of.  "
                "If only one argument is given, this function operates as an aggregate.")
                                .one_or_more())
            .with_tags({"math"})
            .with_example({"SELECT max(2, 1, 3)"})
            .with_example({"SELECT max(status) FROM http_status_codes"}),

        help_text("min",
                  "Returns the argument with the minimum value, or return NULL if any argument is NULL.")
            .sql_function()
            .with_parameter(help_text("X", "The numbers to find the minimum of.  "
                "If only one argument is given, this function operates as an aggregate.")
                                .one_or_more())
            .with_tags({"math"})
            .with_example({"SELECT min(2, 1, 3)"})
            .with_example({"SELECT min(status) FROM http_status_codes"}),

        help_text("nullif",
                  "Returns its first argument if the arguments are different and NULL if the arguments are the same.")
            .sql_function()
            .with_parameter({"X", "The first argument to compare."})
            .with_parameter({"Y", "The argument to compare against the first."})
            .with_example({"SELECT nullif(1, 1)"})
            .with_example({"SELECT nullif(1, 2)"}),

        help_text("printf",
                  "Returns a string with this functions arguments substituted into the given format.  "
                      "Substitution points are specified using percent (%) options, much like the standard C printf() function.")
            .sql_function()
            .with_parameter({"format", "The format of the string to return."})
            .with_parameter(help_text("X", "The argument to substitute at a given position in the format."))
            .with_tags({"string"})
            .with_example({"SELECT printf('Hello, %s!', 'World')"})
            .with_example({"SELECT printf('align: % 10s', 'small')"})
            .with_example({"SELECT printf('value: %05d', 11)"}),

        help_text("quote",
                  "Returns the text of an SQL literal which is the value of its argument suitable for inclusion into an SQL statement.")
            .sql_function()
            .with_parameter({"X", "The string to quote."})
            .with_example({"SELECT quote('abc')"})
            .with_example({"SELECT quote('abc''123')"}),

        help_text("random",
                  "Returns a pseudo-random integer between -9223372036854775808 and +9223372036854775807.")
            .sql_function(),

        help_text("randomblob",
                  "Return an N-byte blob containing pseudo-random bytes.")
            .sql_function()
            .with_parameter({"N", "The size of the blob in bytes."}),

        help_text("replace",
                  "Returns a string formed by substituting the replacement string for every occurrence of the old string in the given string.")
            .sql_function()
            .with_parameter({"str", "The string to perform substitutions on."})
            .with_parameter({"old", "The string to be replaced."})
            .with_parameter({"replacement", "The string to replace any occurrences of the old string with."})
            .with_tags({"string"})
            .with_example({"SELECT replace('abc', 'x', 'z')"})
            .with_example({"SELECT replace('abc', 'a', 'z')"}),

        help_text("round",
                  "Returns a floating-point value rounded to the given number of digits to the right of the decimal point.")
            .sql_function()
            .with_parameter({"num", "The value to round."})
            .with_parameter(help_text("digits", "The number of digits to the right of the decimal to round to.")
                                .optional())
            .with_tags({"math"})
            .with_example({"SELECT round(123.456)"})
            .with_example({"SELECT round(123.456, 1)"})
            .with_example({"SELECT round(123.456, 5)"}),

        help_text("rtrim",
                  "Returns a string formed by removing any and all characters that appear in the second argument from the right side of the first.")
            .sql_function()
            .with_parameter({"str", "The string to trim characters from the right side"})
            .with_parameter(help_text("chars", "The characters to trim.  Defaults to spaces.")
                                .optional())
            .with_tags({"string"})
            .with_example({"SELECT ltrim('abc   ')"})
            .with_example({"SELECT ltrim('abbbbcccc', 'bc')"}),

        help_text("sqlite_compileoption_get",
                  "Returns the N-th compile-time option used to build SQLite or NULL if N is out of range.")
            .sql_function()
            .with_parameter({"N", "The option number to get"}),

        help_text("sqlite_compileoption_used",
                  "Returns true (1) or false (0) depending on whether or not that compile-time option was used during the build.")
            .sql_function()
            .with_parameter({"option", "The name of the compile-time option."})
            .with_example({"SELECT sqlite_compileoption_used('ENABLE_FTS3')"}),

        help_text("sqlite_source_id",
                  "Returns a string that identifies the specific version of the source code that was used to build the SQLite library.")
            .sql_function(),

        help_text("sqlite_version",
                  "Returns the version string for the SQLite library that is running.")
            .sql_function(),

        help_text("substr",
                  "Returns a substring of input string X that begins with the Y-th character and which is Z characters long.")
            .sql_function()
            .with_parameter({"str", "The string to extract a substring from."})
            .with_parameter({"start", "The index within 'str' that is the start of the substring.  "
                "Indexes begin at 1.  "
                "A negative value means that the substring is found by counting from the right rather than the left.  "})
            .with_parameter(help_text("size", "The size of the substring.  "
                "If not given, then all characters through the end of the string are returned.  "
                "If the value is negative, then the characters before the start are returned.")
                                .optional())
            .with_tags({"string"})
            .with_example({"SELECT substr('abc', 2)"})
            .with_example({"SELECT substr('abc', 2, 1)"})
            .with_example({"SELECT substr('abc', -1)"})
            .with_example({"SELECT substr('abc', -1, -1)"}),

        help_text("total_changes",
                  "Returns the number of row changes caused by INSERT, UPDATE or DELETE statements since the current database connection was opened.")
            .sql_function(),

        help_text("trim",
                  "Returns a string formed by removing any and all characters that appear in the second argument from the left and right sides of the first.")
            .sql_function()
            .with_parameter({"str", "The string to trim characters from the left and right sides."})
            .with_parameter(help_text("chars", "The characters to trim.  Defaults to spaces.")
                                .optional())
            .with_tags({"string"})
            .with_example({"SELECT trim('    abc   ')"})
            .with_example({"SELECT trim('-+abc+-', '-+')"}),

        help_text("typeof",
                  "Returns a string that indicates the datatype of the expression X: \"null\", \"integer\", \"real\", \"text\", or \"blob\".")
            .sql_function()
            .with_parameter({"X", "The expression to check."})
            .with_example({"SELECT typeof(1)"})
            .with_example({"SELECT typeof('abc')"}),

        help_text("unicode",
                  "Returns the numeric unicode code point corresponding to the first character of the string X.")
            .sql_function()
            .with_parameter({"X", "The string to examine."})
            .with_example({"SELECT unicode('abc')"}),

        help_text("unlikely",
                  "Short-hand for likelihood(X, 0.0625)")
            .sql_function()
            .with_parameter({"value", "The boolean value to return"}),

        help_text("upper",
                  "Returns a copy of the given string with all ASCII characters converted to upper case.")
            .sql_function()
            .with_parameter({"str", "The string to convert."})
            .with_tags({"string"})
            .with_example({"SELECT upper('aBc')"}),

        help_text("zeroblob",
                  "Returns a BLOB consisting of N bytes of 0x00.")
            .sql_function()
            .with_parameter({"N", "The size of the BLOB."}),

        help_text("date",
                  "Returns the date in this format: YYYY-MM-DD.")
            .sql_function()
            .with_parameter({"timestring", "The string to convert to a date."})
            .with_parameter(help_text("modifier", "A transformation that is applied to the value to the left.")
                                .zero_or_more())
            .with_tags({"datetime"})
            .with_example({"SELECT date('2017-01-02T03:04:05')"})
            .with_example({"SELECT date('2017-01-02T03:04:05', '+1 day')"})
            .with_example({"SELECT date(1491341842, 'unixepoch')"}),

        help_text("time",
                  "Returns the time in this format: HH:MM:SS.")
            .sql_function()
            .with_parameter({"timestring", "The string to convert to a time."})
            .with_parameter(help_text("modifier", "A transformation that is applied to the value to the left.")
                                .zero_or_more())
            .with_tags({"datetime"})
            .with_example({"SELECT time('2017-01-02T03:04:05')"})
            .with_example({"SELECT time('2017-01-02T03:04:05', '+1 minute')"})
            .with_example({"SELECT time(1491341842, 'unixepoch')"}),

        help_text("datetime",
                  "Returns the date and time in this format: YYYY-MM-DD HH:MM:SS.")
            .sql_function()
            .with_parameter({"timestring", "The string to convert to a date with time."})
            .with_parameter(help_text("modifier", "A transformation that is applied to the value to the left.")
                                .zero_or_more())
            .with_tags({"datetime"})
            .with_example({"SELECT datetime('2017-01-02T03:04:05')"})
            .with_example({"SELECT datetime('2017-01-02T03:04:05', '+1 minute')"})
            .with_example({"SELECT datetime(1491341842, 'unixepoch')"}),

        help_text("julianday",
                  "Returns the number of days since noon in Greenwich on November 24, 4714 B.C.")
            .sql_function()
            .with_parameter({"timestring", "The string to convert to a date with time."})
            .with_parameter(help_text("modifier", "A transformation that is applied to the value to the left.")
                                .zero_or_more())
            .with_tags({"datetime"})
            .with_example({"SELECT julianday('2017-01-02T03:04:05')"})
            .with_example({"SELECT julianday('2017-01-02T03:04:05', '+1 minute')"})
            .with_example({"SELECT julianday(1491341842, 'unixepoch')"}),

        help_text("strftime",
                  "Returns the date formatted according to the format string specified as the first argument.")
            .sql_function()
            .with_parameter({"format", "A format string with substitutions similar to those found in the strftime() standard C library."})
            .with_parameter({"timestring", "The string to convert to a date with time."})
            .with_parameter(help_text("modifier", "A transformation that is applied to the value to the left.")
                                .zero_or_more())
            .with_tags({"datetime"})
            .with_example({"SELECT strftime('%Y', '2017-01-02T03:04:05')"})
            .with_example({"SELECT strftime('The time is: %H%M%S', '2017-01-02T03:04:05', '+1 minute')"})
            .with_example({"SELECT strftime('Julian day: %J', 1491341842, 'unixepoch')"}),

        help_text("avg",
                  "Returns the average value of all non-NULL numbers within a group.")
            .sql_function()
            .with_parameter({"X", "The value to compute the average of."})
            .with_tags({"math"})
            .with_example({"SELECT avg(ex_duration) FROM lnav_example_log"})
            .with_example({"SELECT ex_procname, avg(ex_duration) FROM lnav_example_log GROUP BY ex_procname"}),

        help_text("count",
                  "If the argument is '*', the total number of rows in the group is returned.  "
                      "Otherwise, the number of times the argument is non-NULL.")
            .sql_function()
            .with_parameter({"X", "The value to count."})
            .with_example({"SELECT count(*) FROM lnav_example_log"})
            .with_example({"SELECT count(log_part) FROM lnav_example_log"}),

        help_text("group_concat",
                  "Returns a string which is the concatenation of all non-NULL values of X.")
            .sql_function()
            .with_parameter({"X", "The value to concatenate."})
            .with_parameter(help_text("sep", "The separator to place between the values.")
                                .optional())
            .with_tags({"string"})
            .with_example({"SELECT group_concat(ex_procname) FROM lnav_example_log"})
            .with_example({"SELECT group_concat(ex_procname, ', ') FROM lnav_example_log"})
            .with_example({"SELECT group_concat(DISTINCT ex_procname) FROM lnav_example_log"}),

        help_text("sum",
                  "Returns the sum of the values in the group as an integer.")
            .sql_function()
            .with_parameter({"X", "The values to add."})
            .with_tags({"math"})
            .with_example({"SELECT sum(ex_duration) FROM lnav_example_log"}),

        help_text("total",
                  "Returns the sum of the values in the group as a floating-point.")
            .sql_function()
            .with_parameter({"X", "The values to add."})
            .with_tags({"math"})
            .with_example({"SELECT total(ex_duration) FROM lnav_example_log"}),

    };

    for (auto &ht : builtin_funcs) {
        sqlite_function_help.insert(make_pair(ht.ht_name, &ht));
        ht.index_tags();
    }

    static help_text idents[] = {
        help_text("ATTACH",
                  "Attach a database file to the current connection.")
            .sql_keyword()
            .with_parameter(help_text("filename", "The path to the database file.")
                                .with_flag_name("DATABASE"))
            .with_parameter(help_text("schema-name", "The prefix for tables in this database.")
                                .with_flag_name("AS"))
            .with_example({"ATTACH DATABASE '/tmp/customers.db' AS customers"}),

        help_text("DETACH",
                  "Detach a database from the current connection.")
            .sql_keyword()
            .with_parameter(help_text("schema-name", "The prefix for tables in this database.")
                                .with_flag_name("DATABASE"))
            .with_example({"DETACH DATABASE customers"}),

        help_text("CREATE", "Assign a name to a SELECT statement")
            .sql_keyword()
            .with_parameter(help_text("TEMP")
                                .optional())
            .with_parameter(help_text("")
                                .with_flag_name("VIEW"))
            .with_parameter(help_text("IF NOT EXISTS", "Do not create the view if it already exists")
                                .optional())
            .with_parameter(help_text("schema-name.", "The database to create the view in")
                                .optional())
            .with_parameter(help_text("view-name", "The name of the view"))
            .with_parameter(help_text("select-stmt", "The SELECT statement the view represents")
                                .with_flag_name("AS")),

        help_text("CREATE", "Create a table")
            .sql_keyword()
            .with_parameter(help_text("TEMP").optional())
            .with_parameter(help_text("")
                                .with_flag_name("TABLE"))
            .with_parameter(help_text("IF NOT EXISTS")
                                .optional())
            .with_parameter(help_text("schema-name.")
                                .optional())
            .with_parameter(help_text("table-name"))
            .with_parameter(help_text("select-stmt")
                                .with_flag_name("AS")),

        help_text("DELETE", "Delete rows from a table")
            .sql_keyword()
            .with_parameter(help_text("table-name", "The name of the table")
                                .with_flag_name("FROM"))
            .with_parameter(help_text("cond", "The conditions used to delete the rows.")
                                .with_flag_name("WHERE")
                                .optional())
            .with_example({"SELECT * FROM syslog_log"}),

        help_text("DROP", "Drop an index")
            .sql_keyword()
            .with_parameter(help_text("")
                                .with_flag_name("INDEX"))
            .with_parameter(help_text("IF EXISTS")
                                .optional())
            .with_parameter(help_text("schema-name.")
                                .optional())
            .with_parameter(help_text("index-name")),

        help_text("DROP", "Drop a table")
            .sql_keyword()
            .with_parameter(help_text("")
                                .with_flag_name("TABLE"))
            .with_parameter(help_text("IF EXISTS")
                                .optional())
            .with_parameter(help_text("schema-name.")
                                .optional())
            .with_parameter(help_text("table-name")),

        help_text("DROP", "Drop a view")
            .sql_keyword()
            .with_parameter(help_text("")
                                .with_flag_name("VIEW"))
            .with_parameter(help_text("IF EXISTS")
                                .optional())
            .with_parameter(help_text("schema-name.")
                                .optional())
            .with_parameter(help_text("view-name")),

        help_text("DROP", "Drop a trigger")
            .sql_keyword()
            .with_parameter(help_text("")
                                .with_flag_name("TRIGGER"))
            .with_parameter(help_text("IF EXISTS")
                                .optional())
            .with_parameter(help_text("schema-name.")
                                .optional())
            .with_parameter(help_text("trigger-name")),

        help_text("SELECT",
                  "Query the database and return zero or more rows of data.")
            .sql_keyword()
            .with_parameter(help_text("result-column", "")
                                .one_or_more())
            .with_parameter(help_text("table", "The table(s) to query for data")
                                .with_flag_name("FROM")
                                .zero_or_more())
            .with_parameter(help_text("cond", "The conditions used to select the rows to return.")
                                .with_flag_name("WHERE")
                                .optional())
            .with_parameter(help_text("grouping-expr", "The expression to use when grouping rows.")
                                .with_flag_name("GROUP BY")
                                .zero_or_more())
            .with_parameter(help_text("ordering-term", "The values to use when ordering the result set.")
                                .with_flag_name("ORDER BY")
                                .zero_or_more())
            .with_parameter(help_text("limit-expr", "The maximum number of rows to return")
                                .with_flag_name("LIMIT")
                                .zero_or_more())
            .with_example({"SELECT * FROM syslog_log"}),

        help_text("WITH",
                  "Create a temporary view that exists only for the duration of a SQL statement.")
            .sql_keyword()
            .with_parameter(help_text("", "")
                                .with_flag_name("RECURSIVE")
                                .optional())
            .with_parameter({"cte-table-name", "The name for the temporary table."})
            .with_parameter(help_text("select-stmt", "The SELECT statement used to populate the temporary table.")
                                .with_flag_name("AS")),

        help_text("UPDATE",
                  "Modify a subset of values in zero or more rows of the given table")
            .sql_keyword()
            .with_parameter(help_text("table", "The table to update"))
            .with_parameter(help_text("")
                                .with_flag_name("SET"))
            .with_parameter(help_text("column-name", "The columns in the table to update.")
                                .with_parameter(help_text("expr", "The values to place into the column.")
                                                    .with_flag_name("="))
                                .one_or_more())
            .with_parameter(help_text("cond", "The condition used to determine whether a row should be updated.")
                                .with_flag_name("WHERE")
                                .optional()),

        help_text("CASE",
                  "Evaluate a series of expressions in order until one evaluates to true and then return it's result.  "
                      "Similar to an IF-THEN-ELSE construct in other languages.")
            .sql_keyword()
            .with_parameter(help_text("base-expr", "The base expression that is used for comparison in the branches")
                                .optional())
            .with_parameter(help_text("cmp-expr", "The expression to test if this branch should be taken")
                                .with_flag_name("WHEN")
                                .one_or_more()
                                .with_parameter(help_text("then-expr", "The result for this branch.")
                                                    .with_flag_name("THEN")))
            .with_parameter(help_text("else-expr", "The result of this CASE if no branches matched.")
                                .with_flag_name("ELSE")
                                .optional())
            .with_parameter(help_text("")
                                .with_flag_name("END"))
            .with_example({"SELECT CASE 1 WHEN 0 THEN 'zero' WHEN 1 THEN 'one' END"}),

        help_text("CAST",
                  "Convert the value of the given expression to a different storage class specified by type-name.")
            .sql_function()
            .with_parameter({"expr", "The value to convert."})
            .with_parameter(help_text("type-name", "The name of the type to convert to.")
                                .with_flag_name("AS"))
            .with_example({"SELECT CAST(1.23 AS INTEGER)"}),

    };

    for (auto &ht : idents) {
        sqlite_function_help.insert(make_pair(toupper(ht.ht_name), &ht));
        for (const auto &param : ht.ht_parameters) {
            if (!param.ht_flag_name) {
                continue;
            }
            sqlite_function_help.insert(make_pair(toupper(param.ht_flag_name), &ht));
        }
    }

    for (const auto &iter : sqlite_function_help) {
        log_debug("help %s", iter.first.c_str());
    }

    return 0;
}
