diff --git a/TESTS_ENVIRONMENT.in b/TESTS_ENVIRONMENT.in index 31bc805a..61f7577d 100644 --- a/TESTS_ENVIRONMENT.in +++ b/TESTS_ENVIRONMENT.in @@ -42,6 +42,9 @@ export LNAV_LOG_PATH SFTP_TEST_URL="@SFTP_TEST_URL@" export SFTP_TEST_URL +HAVE_SQLITE3_VALUE_SUBTYPE="@HAVE_SQLITE3_VALUE_SUBTYPE@" +export HAVE_SQLITE3_VALUE_SUBTYPE + ## BEGIN Functions LAST_TEST="" diff --git a/m4/lnav_with_sqlite3.m4 b/m4/lnav_with_sqlite3.m4 index 8aadd58c..d79d0364 100644 --- a/m4/lnav_with_sqlite3.m4 +++ b/m4/lnav_with_sqlite3.m4 @@ -88,6 +88,15 @@ AC_DEFUN([LNAV_WITH_SQLITE3], ) ) + AC_CHECK_FUNC(sqlite3_value_subtype, + HAVE_SQLITE3_VALUE_SUBTYPE=1 + AC_DEFINE([HAVE_SQLITE3_VALUE_SUBTYPE], [], + [Have the sqlite3_value_subtype function] + ) + ) + + AC_SUBST(HAVE_SQLITE3_VALUE_SUBTYPE) + AS_VAR_SET(CFLAGS, $saved_CFLAGS) AS_VAR_SET(CPPFLAGS, $saved_CPPFLAGS) AS_VAR_SET(LDFLAGS, $saved_LDFLAGS) diff --git a/src/json-extension-functions.cc b/src/json-extension-functions.cc index 6fc6ea51..8cd2119d 100644 --- a/src/json-extension-functions.cc +++ b/src/json-extension-functions.cc @@ -42,10 +42,13 @@ #include "json_op.hh" +#include "yajl/api/yajl_gen.h" #include "sqlite-extension-func.h" using namespace std; +#define JSON_SUBTYPE 74 /* Ascii for "J" */ + class sql_json_op : public json_op { public: sql_json_op(json_ptr &ptr) : json_op(ptr), sjo_type(-1), sjo_int(0) { }; @@ -55,13 +58,6 @@ public: int sjo_int; }; -static void printer(void *ctx, const char *numberVal, size_t numberLen) -{ - string &str = *(string *)ctx; - - str.append(numberVal, numberLen); -} - static void null_or_default(sqlite3_context *context, int argc, sqlite3_value **argv) { if (argc > 2) { @@ -145,10 +141,8 @@ static void sql_jget(sqlite3_context *context, auto_mem gen(yajl_gen_free); auto_mem handle(yajl_free); const unsigned char *err; - string result; gen = yajl_gen_alloc(NULL); - yajl_gen_config(gen.in(), yajl_gen_print_callback, printer, &result); yajl_gen_config(gen.in(), yajl_gen_beautify, false); jo.jo_ptr_callbacks = json_op::gen_callbacks; @@ -204,12 +198,17 @@ static void sql_jget(sqlite3_context *context, return; } - if (result.empty()) { + const unsigned char *buf; + size_t len; + + yajl_gen_get_buf(gen, &buf, &len); + + if (len == 0) { null_or_default(context, argc, argv); return; } - sqlite3_result_text(context, result.c_str(), result.size(), SQLITE_TRANSIENT); + sqlite3_result_text(context, (const char *) buf, len, SQLITE_TRANSIENT); } struct json_agg_context { @@ -255,10 +254,22 @@ static void sql_json_group_object_step(sqlite3_context *context, break; case SQLITE3_TEXT: { const unsigned char *value = sqlite3_value_text(argv[lpc + 1]); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + int subtype = sqlite3_value_subtype(argv[lpc + 1]); - yajl_gen_string(jac->jac_yajl_gen, - value, - strlen((const char *) value)); + if (subtype == JSON_SUBTYPE) { + yajl_gen_number(jac->jac_yajl_gen, + (const char *) value, + strlen((const char *)value)); + } + else { +#endif + yajl_gen_string(jac->jac_yajl_gen, + value, + strlen((const char *) value)); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + } +#endif break; } case SQLITE_INTEGER: { @@ -295,6 +306,89 @@ static void sql_json_group_object_final(sqlite3_context *context) yajl_gen_map_close(jac->jac_yajl_gen); yajl_gen_get_buf(jac->jac_yajl_gen, &buf, &len); sqlite3_result_text(context, (const char *) buf, len, SQLITE_TRANSIENT); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + sqlite3_result_subtype(context, JSON_SUBTYPE); +#endif + yajl_gen_free(jac->jac_yajl_gen); + } +} + +static void sql_json_group_array_step(sqlite3_context *context, + int argc, + sqlite3_value **argv) +{ + json_agg_context *jac = (json_agg_context *) sqlite3_aggregate_context( + context, sizeof(json_agg_context)); + + if (jac->jac_yajl_gen == NULL) { + jac->jac_yajl_gen = yajl_gen_alloc(NULL); + yajl_gen_config(jac->jac_yajl_gen, yajl_gen_beautify, false); + + yajl_gen_array_open(jac->jac_yajl_gen); + } + + for (int lpc = 0; lpc < argc; lpc++) { + switch (sqlite3_value_type(argv[lpc])) { + case SQLITE_NULL: + yajl_gen_null(jac->jac_yajl_gen); + break; + case SQLITE3_TEXT: { + const unsigned char *value = sqlite3_value_text(argv[lpc]); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + int subtype = sqlite3_value_subtype(argv[lpc]); + + if (subtype == JSON_SUBTYPE) { + yajl_gen_number(jac->jac_yajl_gen, + (const char *) value, + strlen((const char *)value)); + } + else { +#endif + yajl_gen_string(jac->jac_yajl_gen, + value, + strlen((const char *) value)); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + } +#endif + break; + } + case SQLITE_INTEGER: { + const unsigned char *value = sqlite3_value_text(argv[lpc]); + + yajl_gen_number(jac->jac_yajl_gen, + (const char *) value, + strlen((const char *) value)); + break; + } + case SQLITE_FLOAT: { + double value = sqlite3_value_double(argv[lpc]); + + yajl_gen_double(jac->jac_yajl_gen, value); + break; + } + } + } + +} + +static void sql_json_group_array_final(sqlite3_context *context) +{ + json_agg_context *jac = (json_agg_context *) sqlite3_aggregate_context( + context, 0); + + if (jac == NULL) { + sqlite3_result_text(context, "{}", -1, SQLITE_STATIC); + } + else { + const unsigned char *buf; + size_t len; + + yajl_gen_array_close(jac->jac_yajl_gen); + yajl_gen_get_buf(jac->jac_yajl_gen, &buf, &len); + sqlite3_result_text(context, (const char *) buf, len, SQLITE_TRANSIENT); +#ifdef HAVE_SQLITE3_VALUE_SUBTYPE + sqlite3_result_subtype(context, JSON_SUBTYPE); +#endif yajl_gen_free(jac->jac_yajl_gen); } } @@ -311,6 +405,8 @@ int json_extension_functions(const struct FuncDef **basic_funcs, static const struct FuncDefAgg json_agg_funcs[] = { { "json_group_object", -1, 0, 0, sql_json_group_object_step, sql_json_group_object_final, }, + { "json_group_array", -1, 0, 0, + sql_json_group_array_step, sql_json_group_array_final, }, { NULL } }; diff --git a/test/test_sql_json_func.sh b/test/test_sql_json_func.sh index a286dfe7..0213a6c5 100644 --- a/test/test_sql_json_func.sh +++ b/test/test_sql_json_func.sh @@ -64,6 +64,7 @@ Row 0: Column jget('[null, true, 20, 30, 40]', '/0/foo'): (null) EOF + run_test ./drive_sql "select json_group_object(key) from (select 1 as key)" check_error_output "" <