summaryrefslogtreecommitdiffstats
path: root/src/sqlite-extension-func.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/sqlite-extension-func.cc470
1 files changed, 452 insertions, 18 deletions
diff --git a/src/sqlite-extension-func.cc b/src/sqlite-extension-func.cc
index 3a02f14..f6c5af5 100644
--- a/src/sqlite-extension-func.cc
+++ b/src/sqlite-extension-func.cc
@@ -32,6 +32,7 @@
#include "sqlite-extension-func.hh"
#include "base/auto_mem.hh"
+#include "base/itertools.hh"
#include "base/lnav_log.hh"
#include "base/string_util.hh"
#include "config.h"
@@ -46,6 +47,17 @@ int sqlite3_series_init(sqlite3* db,
const sqlite3_api_routines* pApi);
}
+#ifdef HAVE_RUST_DEPS
+rust::Vec<prqlc::SourceTreeElement> sqlite_extension_prql;
+#endif
+
+namespace lnav {
+namespace sql {
+std::multimap<std::string, const help_text*> prql_functions;
+
+}
+} // namespace lnav
+
sqlite_registration_func_t sqlite_registration_funcs[] = {
common_extension_functions,
state_extension_functions,
@@ -59,9 +71,73 @@ sqlite_registration_func_t sqlite_registration_funcs[] = {
nullptr,
};
+struct prql_hier {
+ std::map<std::string, prql_hier> ph_modules;
+ std::map<std::string, std::string> ph_declarations;
+
+ void to_string(std::string& accum) const
+ {
+ for (const auto& mod_pair : this->ph_modules) {
+ accum.append("module ");
+ accum.append(mod_pair.first);
+ accum.append(" {\n");
+ mod_pair.second.to_string(accum);
+ accum.append("}\n");
+ }
+ for (const auto& decl_pair : this->ph_declarations) {
+ accum.append(decl_pair.second);
+ accum.append("\n");
+ }
+ }
+};
+
+static void
+register_help(prql_hier& phier, const help_text& ht)
+{
+ auto prql_fqid
+ = fmt::format(FMT_STRING("{}"), fmt::join(ht.ht_prql_path, "."));
+ lnav::sql::prql_functions.emplace(prql_fqid, &ht);
+
+ auto* curr_hier = &phier;
+ for (size_t name_index = 0; name_index < ht.ht_prql_path.size();
+ name_index++)
+ {
+ const auto& prql_name = ht.ht_prql_path[name_index];
+ if (name_index == ht.ht_prql_path.size() - 1) {
+ auto param_names
+ = ht.ht_parameters | lnav::itertools::map([](const auto& elem) {
+ if (elem.ht_nargs == help_nargs_t::HN_OPTIONAL) {
+ return fmt::format(FMT_STRING("{}:null"),
+ elem.ht_name);
+ }
+ return fmt::format(FMT_STRING("p_{}"), elem.ht_name);
+ });
+ auto func_args
+ = ht.ht_parameters | lnav::itertools::map([](const auto& elem) {
+ if (elem.ht_nargs == help_nargs_t::HN_OPTIONAL) {
+ return fmt::format(FMT_STRING("{{{}:0}}"),
+ elem.ht_name);
+ }
+ return fmt::format(FMT_STRING("{{p_{}:0}}"),
+ elem.ht_name);
+ });
+ curr_hier->ph_declarations[prql_name]
+ = fmt::format(FMT_STRING("let {} = func {} -> s\"{}({})\""),
+ prql_name,
+ fmt::join(param_names, " "),
+ ht.ht_name,
+ fmt::join(func_args, ", "));
+ } else {
+ curr_hier = &curr_hier->ph_modules[prql_name];
+ }
+ }
+}
+
int
register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
{
+ static bool help_registration_done = false;
+ prql_hier phier;
int lpc;
require(db != nullptr);
@@ -94,11 +170,16 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
nullptr,
nullptr);
- if (fd.fd_help.ht_context != help_context_t::HC_NONE) {
- help_text& ht = fd.fd_help;
+ if (!help_registration_done
+ && fd.fd_help.ht_context != help_context_t::HC_NONE)
+ {
+ auto& ht = fd.fd_help;
sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
ht.index_tags();
+ if (!ht.ht_prql_path.empty()) {
+ register_help(phier, ht);
+ }
}
}
@@ -115,15 +196,35 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
agg_funcs[i].xStep,
agg_funcs[i].xFinalize);
- if (fda.fda_help.ht_context != help_context_t::HC_NONE) {
- help_text& ht = fda.fda_help;
+ if (!help_registration_done
+ && fda.fda_help.ht_context != help_context_t::HC_NONE)
+ {
+ auto& ht = fda.fda_help;
sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
ht.index_tags();
+ if (!ht.ht_prql_path.empty()) {
+ register_help(phier, ht);
+ }
}
}
}
+#ifdef HAVE_RUST_DEPS
+ if (sqlite_extension_prql.empty()) {
+ require(phier.ph_declarations.empty());
+ for (const auto& mod_pair : phier.ph_modules) {
+ std::string content;
+
+ mod_pair.second.to_string(content);
+ sqlite_extension_prql.emplace_back(prqlc::SourceTreeElement{
+ fmt::format(FMT_STRING("{}.prql"), mod_pair.first),
+ content,
+ });
+ }
+ }
+#endif
+
static help_text builtin_funcs[] = {
help_text("abs", "Return the absolute value of the argument")
.sql_function()
@@ -744,11 +845,338 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
"SELECT value FROM generate_series(10, 14, 2)",
})
.with_example({"To count down from five to 1",
- "SELECT value FROM generate_series(1, 5, -1)"})};
+ "SELECT value FROM generate_series(1, 5, -1)"}),
+
+ help_text("json",
+ "Verifies that its argument is valid JSON and returns a "
+ "minified version or throws an error.")
+ .sql_function()
+ .with_parameter({"X", "The string to interpret as JSON."})
+ .with_tags({"json"}),
+
+ help_text("json_array", "Constructs a JSON array from its arguments.")
+ .sql_function()
+ .with_parameter(
+ help_text{"X", "The values of the JSON array"}.zero_or_more())
+ .with_tags({"json"})
+ .with_example({"To create an array of all types",
+ "SELECT json_array(NULL, 1, 2.1, 'three', "
+ "json_array(4), json_object('five', 'six'))"})
+ .with_example({"To create an empty array", "SELECT json_array()"}),
+
+ help_text("json_array_length", "Returns the length of a JSON array.")
+ .sql_function()
+ .with_parameter({"X", "The JSON object."})
+ .with_parameter(
+ help_text{"P", "The path to the array in 'X'."}.optional())
+ .with_tags({"json"})
+ .with_example({"To get the length of an array",
+ "SELECT json_array_length('[1, 2, 3]')"})
+ .with_example(
+ {"To get the length of a nested array",
+ "SELECT json_array_length('{\"arr\": [1, 2, 3]}', '$.arr')"}),
+
+ help_text(
+ "json_extract",
+ "Returns the value(s) from the given JSON at the given path(s).")
+ .sql_function()
+ .with_parameter({"X", "The JSON value."})
+ .with_parameter(
+ help_text{"P", "The path to extract."}.one_or_more())
+ .with_tags({"json"})
+ .with_example({"To get a number",
+ R"(SELECT json_extract('{"num": 1}', '$.num'))"})
+ .with_example(
+ {"To get two numbers",
+ R"(SELECT json_extract('{"num": 1, "val": 2}', '$.num', '$.val'))"})
+ .with_example(
+ {"To get an object",
+ R"(SELECT json_extract('{"obj": {"sub": 1}}', '$.obj'))"})
+#if 0 && SQLITE_VERSION_NUMBER >= 3038000
+ .with_example({"To get a JSON value using the short-hand",
+ R"(SELECT '{"a":"b"}' -> '$.a')"})
+ .with_example({"To get a SQL value using the short-hand",
+ R"(SELECT '{"a":"b"}' ->> '$.a')"})
+#endif
+ ,
+
+ help_text("json_insert",
+ "Inserts values into a JSON object/array at the given "
+ "locations, if it does not already exist")
+ .sql_function()
+ .with_parameter({"X", "The JSON value to update"})
+ .with_parameter({"P",
+ "The path to the insertion point. A '#' array "
+ "index means append the value"})
+ .with_parameter({"Y", "The value to insert"})
+ .with_tags({"json"})
+ .with_example({"To append to an array",
+ R"(SELECT json_insert('[1, 2]', '$[#]', 3))"})
+ .with_example({"To update an object",
+ R"(SELECT json_insert('{"a": 1}', '$.b', 2))"})
+ .with_example({"To ensure a value is set",
+ R"(SELECT json_insert('{"a": 1}', '$.a', 2))"})
+ .with_example(
+ {"To update multiple values",
+ R"(SELECT json_insert('{"a": 1}', '$.b', 2, '$.c', 3))"}),
+
+ help_text("json_replace",
+ "Replaces existing values in a JSON object/array at the "
+ "given locations")
+ .sql_function()
+ .with_parameter({"X", "The JSON value to update"})
+ .with_parameter({"P", "The path to replace"})
+ .with_parameter({"Y", "The new value for the property"})
+ .with_tags({"json"})
+ .with_example({"To replace an existing value",
+ R"(SELECT json_replace('{"a": 1}', '$.a', 2))"})
+ .with_example(
+ {"To replace a value without creating a new property",
+ R"(SELECT json_replace('{"a": 1}', '$.a', 2, '$.b', 3))"}),
+
+ help_text("json_set",
+ "Inserts or replaces existing values in a JSON object/array "
+ "at the given locations")
+ .sql_function()
+ .with_parameter({"X", "The JSON value to update"})
+ .with_parameter({"P",
+ "The path to the insertion point. A '#' array "
+ "index means append the value"})
+ .with_parameter({"Y", "The value to set"})
+ .with_tags({"json"})
+ .with_example({"To replace an existing array element",
+ R"(SELECT json_set('[1, 2]', '$[1]', 3))"})
+ .with_example(
+ {"To replace a value and create a new property",
+ R"(SELECT json_set('{"a": 1}', '$.a', 2, '$.b', 3))"}),
- for (auto& ht : builtin_funcs) {
- sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
- ht.index_tags();
+ help_text("json_object",
+ "Create a JSON object from the given arguments")
+ .sql_function()
+ .with_parameter({"N", "The property name"})
+ .with_parameter({"V", "The property value"})
+ .with_tags({"json"})
+ .with_example(
+ {"To create an object", "SELECT json_object('a', 1, 'b', 'c')"})
+ .with_example(
+ {"To create an empty object", "SELECT json_object()"}),
+
+ help_text("json_remove", "Removes paths from a JSON value")
+ .sql_function()
+ .with_parameter({"X", "The JSON value to update"})
+ .with_parameter(help_text{"P", "The paths to remove"}.one_or_more())
+ .with_tags({"json"})
+ .with_example({"To remove elements of an array",
+ "SELECT json_remove('[1,2,3]', '$[1]', '$[1]')"})
+ .with_example({"To remove object properties",
+ R"(SELECT json_remove('{"a":1,"b":2}', '$.b'))"}),
+
+ help_text("json_type", "Returns the type of a JSON value")
+ .sql_function()
+ .with_parameter({"X", "The JSON value to query"})
+ .with_parameter(help_text{"P", "The path to the value"}.optional())
+ .with_tags({"json"})
+ .with_example(
+ {"To get the type of a value",
+ R"(SELECT json_type('[null,1,2.1,"three",{"four":5}]'))"})
+ .with_example(
+ {"To get the type of an array element",
+ R"(SELECT json_type('[null,1,2.1,"three",{"four":5}]', '$[0]'))"})
+ .with_example(
+ {"To get the type of a string",
+ R"(SELECT json_type('[null,1,2.1,"three",{"four":5}]', '$[3]'))"}),
+
+ help_text("json_valid", "Tests if the given value is valid JSON")
+ .sql_function()
+ .with_parameter({"X", "The value to check"})
+ .with_tags({"json"})
+ .with_example({"To check an empty string", "SELECT json_valid('')"})
+ .with_example({"To check a string", R"(SELECT json_valid('"a"'))"}),
+
+ help_text("json_quote",
+ "Returns the JSON representation of the given value, if it "
+ "is not already JSON")
+ .sql_function()
+ .with_parameter({"X", "The value to convert"})
+ .with_tags({"json"})
+ .with_example(
+ {"To convert a string", "SELECT json_quote('Hello, World!')"})
+ .with_example({"To pass through an existing JSON value",
+ R"(SELECT json_quote(json('"Hello, World!"')))"}),
+
+ help_text("json_each",
+ "A table-valued-function that returns the children of the "
+ "top-level JSON value")
+ .sql_table_valued_function()
+ .with_parameter({"X", "The JSON value to query"})
+ .with_parameter(
+ help_text{"P", "The path to the value to query"}.optional())
+ .with_result({"key",
+ "The array index for elements of an array or "
+ "property names of the object"})
+ .with_result({"value", "The value for the current element"})
+ .with_result({"type", "The type of the current element"})
+ .with_result(
+ {"atom",
+ "The SQL value of the element, if it is a primitive type"})
+ .with_result({"fullkey", "The path to the current element"})
+ .with_tags({"json"})
+ .with_example(
+ {"To iterate over an array",
+ R"(SELECT * FROM json_each('[null,1,"two",{"three":4.5}]'))"}),
+
+ help_text("json_tree",
+ "A table-valued-function that recursively descends through a "
+ "JSON value")
+ .sql_table_valued_function()
+ .with_parameter({"X", "The JSON value to query"})
+ .with_parameter(
+ help_text{"P", "The path to the value to query"}.optional())
+ .with_result({"key",
+ "The array index for elements of an array or "
+ "property names of the object"})
+ .with_result({"value", "The value for the current element"})
+ .with_result({"type", "The type of the current element"})
+ .with_result(
+ {"atom",
+ "The SQL value of the element, if it is a primitive type"})
+ .with_result({"fullkey", "The path to the current element"})
+ .with_result({"path", "The path to the container of this element"})
+ .with_tags({"json"})
+ .with_example(
+ {"To iterate over an array",
+ R"(SELECT key,value,type,atom,fullkey,path FROM json_tree('[null,1,"two",{"three":4.5}]'))"}),
+
+ help_text("text.contains", "Returns true if col contains sub")
+ .prql_function()
+ .with_parameter(
+ help_text{"sub", "The substring to look for in col"})
+ .with_parameter(help_text{"col", "The string to examine"})
+ .with_example({
+ "To check if 'Hello' contains 'lo'",
+ "from [{s='Hello'}] | select { s=text.contains 'lo' s }",
+ help_example::language::prql,
+ })
+ .with_example({
+ "To check if 'Goodbye' contains 'lo'",
+ "from [{s='Goodbye'}] | select { s=text.contains 'lo' s }",
+ help_example::language::prql,
+ }),
+ help_text("text.ends_with", "Returns true if col ends with suffix")
+ .prql_function()
+ .with_parameter(
+ help_text{"suffix", "The string to look for at the end of col"})
+ .with_parameter(help_text{"col", "The string to examine"})
+ .with_example({
+ "To check if 'Hello' ends with 'lo'",
+ "from [{s='Hello'}] | select { s=text.ends_with 'lo' s }",
+ help_example::language::prql,
+ })
+ .with_example({
+ "To check if 'Goodbye' ends with 'lo'",
+ "from [{s='Goodbye'}] | select { s=text.ends_with 'lo' s }",
+ help_example::language::prql,
+ }),
+ help_text("text.extract", "Extract a slice of a string")
+ .prql_function()
+ .with_parameter(help_text{
+ "idx",
+ "The starting index where the first character is index 1"})
+ .with_parameter(help_text{"len", "The length of the slice"})
+ .with_parameter(help_text{"str", "The string to extract from"})
+ .with_example({
+ "To extract a substring from s",
+ "from [{s='Hello, World!'}] | select { s=text.extract 1 5 s }",
+ help_example::language::prql,
+ }),
+ help_text("text.length", "Returns the number of characters in col")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to examine"})
+ .with_example({
+ "To count the number of characters in s",
+ "from [{s='Hello, World!'}] | select { s=text.length s }",
+ help_example::language::prql,
+ }),
+ help_text("text.lower", "Converts col to lowercase")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to convert"})
+ .with_example({
+ "To convert s to lowercase",
+ "from [{s='HELLO'}] | select { s=text.lower s }",
+ help_example::language::prql,
+ }),
+ help_text("text.ltrim", "Remove whitespace from the left side of col")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to trim"})
+ .with_example({
+ "To trim the left side of s",
+ "from [{s=' HELLO '}] | select { s=text.ltrim s }",
+ help_example::language::prql,
+ }),
+ help_text("text.replace",
+ "Replace all occurrences of before with after in col")
+ .prql_function()
+ .with_parameter(help_text{"before", "The string to find"})
+ .with_parameter(help_text{"after", "The replacement"})
+ .with_parameter(help_text{"col", "The string to trim"})
+ .with_example({
+ "To erase foo in s",
+ "from [{s='foobar'}] | select { s=text.replace 'foo' '' s }",
+ help_example::language::prql,
+ }),
+ help_text("text.rtrim", "Remove whitespace from the right side of col")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to trim"})
+ .with_example({
+ "To trim the right side of s",
+ "from [{s=' HELLO '}] | select { s=text.rtrim s }",
+ help_example::language::prql,
+ }),
+ help_text("text.starts_with", "Returns true if col starts with suffix")
+ .prql_function()
+ .with_parameter(help_text{
+ "suffix", "The string to look for at the start of col"})
+ .with_parameter(help_text{"col", "The string to examine"})
+ .with_example({
+ "To check if 'Hello' starts with 'lo'",
+ "from [{s='Hello'}] | select { s=text.starts_with 'He' s }",
+ help_example::language::prql,
+ })
+ .with_example({
+ "To check if 'Goodbye' starts with 'lo'",
+ "from [{s='Goodbye'}] | select { s=text.starts_with 'He' s }",
+ help_example::language::prql,
+ }),
+ help_text("text.trim", "Remove whitespace from the both sides of col")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to trim"})
+ .with_example({
+ "To trim s",
+ "from [{s=' HELLO '}] | select { s=text.trim s }",
+ help_example::language::prql,
+ }),
+ help_text("text.upper", "Converts col to uppercase")
+ .prql_function()
+ .with_parameter(help_text{"col", "The string to convert"})
+ .with_example({
+ "To convert s to uppercase",
+ "from [{s='hello'}] | select { s=text.upper s }",
+ help_example::language::prql,
+ }),
+ };
+
+ if (!help_registration_done) {
+ for (auto& ht : builtin_funcs) {
+ switch (ht.ht_context) {
+ case help_context_t::HC_PRQL_FUNCTION:
+ lnav::sql::prql_functions.emplace(ht.ht_name, &ht);
+ break;
+ default:
+ sqlite_function_help.emplace(ht.ht_name, &ht);
+ break;
+ }
+ ht.index_tags();
+ }
}
static help_text builtin_win_funcs[] = {
@@ -847,9 +1275,11 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
.with_tags({"window"}),
};
- for (auto& ht : builtin_win_funcs) {
- sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
- ht.index_tags();
+ if (!help_registration_done) {
+ for (auto& ht : builtin_win_funcs) {
+ sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
+ ht.index_tags();
+ }
}
static help_text idents[] = {
@@ -1152,16 +1582,20 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
.optional()),
};
- 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;
+ if (!help_registration_done) {
+ 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));
}
- sqlite_function_help.insert(
- make_pair(toupper(param.ht_flag_name), &ht));
}
}
+ help_registration_done = true;
+
return 0;
}