[sql] add function adapter template

Defect Number:
    Reviewed By:
   Testing Done:
This commit is contained in:
Timothy Stack 2017-03-19 07:50:01 -07:00
parent 81e55b82dc
commit 0b157ff867
19 changed files with 2828 additions and 262 deletions

View File

@ -109,6 +109,7 @@ set(diag_STAT_SRCS
log_format_impls.cc
log_search_table.hh
logfile_stats.hh
optional.hpp
papertrail_proc.hh
plain_text_source.hh
pretty_printer.hh
@ -135,6 +136,11 @@ set(diag_STAT_SRCS
views_vtab.hh
vtab_module.hh
mapbox/recursive_wrapper.hpp
mapbox/variant.hpp
mapbox/variant_io.hpp
mapbox/variant_visitor.hpp
yajl/api/yajl_common.h
yajl/api/yajl_gen.h
yajl/api/yajl_parse.h

View File

@ -169,6 +169,11 @@ noinst_HEADERS = \
log_search_table.hh \
logfile.hh \
logfile_sub_source.hh \
mapbox/recursive_wrapper.hpp \
mapbox/variant.hpp \
mapbox/variant_io.hpp \
mapbox/variant_visitor.hpp \
optional.hpp \
papertrail_proc.hh \
pcrepp.hh \
piper_proc.hh \

View File

@ -43,31 +43,24 @@
#include "sqlite-extension-func.h"
static void sql_basename(sqlite3_context *context,
int argc, sqlite3_value **argv)
#include "vtab_module.hh"
using namespace mapbox;
static
util::variant<const char *, string_fragment>
sql_basename(const char *path_in)
{
const char *path_in;
int text_end = -1;
if (sqlite3_value_type(argv[0]) == SQLITE_NULL) {
sqlite3_result_null(context);
return;
}
path_in = (const char *)sqlite3_value_text(argv[0]);
int text_end = -1;
if (path_in[0] == '\0') {
sqlite3_result_text(context, ".", 1, SQLITE_STATIC);
return;
return ".";
}
for (ssize_t lpc = strlen(path_in) - 1; lpc >= 0; lpc--) {
if (path_in[lpc] == '/' || path_in[lpc] == '\\') {
if (text_end != -1) {
sqlite3_result_text(context,
&path_in[lpc + 1], (int) (text_end - lpc - 1),
SQLITE_TRANSIENT);
return;
return string_fragment(path_in, lpc + 1, text_end - lpc);
}
}
else if (text_end == -1) {
@ -76,12 +69,10 @@ static void sql_basename(sqlite3_context *context,
}
if (text_end == -1) {
sqlite3_result_text(context, "/", 1, SQLITE_STATIC);
return "/";
}
else {
sqlite3_result_text(context,
path_in, text_end,
SQLITE_TRANSIENT);
return string_fragment(path_in, 0, text_end);
}
}
@ -161,7 +152,14 @@ int fs_extension_functions(const struct FuncDef **basic_funcs,
const struct FuncDefAgg **agg_funcs)
{
static const struct FuncDef fs_funcs[] = {
{ "basename", 1, 0, SQLITE_UTF8, 0, sql_basename },
sqlite_func_adapter<decltype(&sql_basename), sql_basename>::builder(
"basename",
"Extract the base portion of a pathname",
{
{"path", "The path"},
}),
{ "dirname", 1, 0, SQLITE_UTF8, 0, sql_dirname },
{ "joinpath", -1, 0, SQLITE_UTF8, 0, sql_joinpath },

View File

@ -147,6 +147,24 @@ std::string hash_string(const std::string &str);
std::string hash_bytes(const char *str1, size_t s1len, ...);
struct string_fragment {
string_fragment(const char *str, int begin, int end)
: sf_string(str), sf_begin(begin), sf_end(end) {
};
bool is_valid() const {
return this->sf_begin != -1;
};
int length() const {
return this->sf_end - this->sf_begin;
};
const char *sf_string;
int sf_begin;
int sf_end;
};
template<typename UnaryFunction, typename Member>
struct object_field_t {
object_field_t(UnaryFunction &func, Member &mem)

74
src/mapbox/optional.hpp Normal file
View File

@ -0,0 +1,74 @@
#ifndef MAPBOX_UTIL_OPTIONAL_HPP
#define MAPBOX_UTIL_OPTIONAL_HPP
#pragma message("This implementation of optional is deprecated. See https://github.com/mapbox/variant/issues/64.")
#include <type_traits>
#include <utility>
#include <mapbox/variant.hpp>
namespace mapbox {
namespace util {
template <typename T>
class optional
{
static_assert(!std::is_reference<T>::value, "optional doesn't support references");
struct none_type
{
};
variant<none_type, T> variant_;
public:
optional() = default;
optional(optional const& rhs)
{
if (this != &rhs)
{ // protect against invalid self-assignment
variant_ = rhs.variant_;
}
}
optional(T const& v) { variant_ = v; }
explicit operator bool() const noexcept { return variant_.template is<T>(); }
T const& get() const { return variant_.template get<T>(); }
T& get() { return variant_.template get<T>(); }
T const& operator*() const { return this->get(); }
T operator*() { return this->get(); }
optional& operator=(T const& v)
{
variant_ = v;
return *this;
}
optional& operator=(optional const& rhs)
{
if (this != &rhs)
{
variant_ = rhs.variant_;
}
return *this;
}
template <typename... Args>
void emplace(Args&&... args)
{
variant_ = T{std::forward<Args>(args)...};
}
void reset() { variant_ = none_type{}; }
}; // class optional
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_OPTIONAL_HPP

View File

@ -0,0 +1,122 @@
#ifndef MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
#define MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
// Based on variant/recursive_wrapper.hpp from boost.
//
// Original license:
//
// Copyright (c) 2002-2003
// Eric Friedman, Itay Maman
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <cassert>
#include <utility>
namespace mapbox {
namespace util {
template <typename T>
class recursive_wrapper
{
T* p_;
void assign(T const& rhs)
{
this->get() = rhs;
}
public:
using type = T;
/**
* Default constructor default initializes the internally stored value.
* For POD types this means nothing is done and the storage is
* uninitialized.
*
* @throws std::bad_alloc if there is insufficient memory for an object
* of type T.
* @throws any exception thrown by the default constructur of T.
*/
recursive_wrapper()
: p_(new T){}
~recursive_wrapper() noexcept { delete p_; }
recursive_wrapper(recursive_wrapper const& operand)
: p_(new T(operand.get())) {}
recursive_wrapper(T const& operand)
: p_(new T(operand)) {}
recursive_wrapper(recursive_wrapper&& operand)
: p_(new T(std::move(operand.get()))) {}
recursive_wrapper(T&& operand)
: p_(new T(std::move(operand))) {}
inline recursive_wrapper& operator=(recursive_wrapper const& rhs)
{
assign(rhs.get());
return *this;
}
inline recursive_wrapper& operator=(T const& rhs)
{
assign(rhs);
return *this;
}
inline void swap(recursive_wrapper& operand) noexcept
{
T* temp = operand.p_;
operand.p_ = p_;
p_ = temp;
}
recursive_wrapper& operator=(recursive_wrapper&& rhs) noexcept
{
swap(rhs);
return *this;
}
recursive_wrapper& operator=(T&& rhs)
{
get() = std::move(rhs);
return *this;
}
T& get()
{
assert(p_);
return *get_pointer();
}
T const& get() const
{
assert(p_);
return *get_pointer();
}
T* get_pointer() { return p_; }
const T* get_pointer() const { return p_; }
operator T const&() const { return this->get(); }
operator T&() { return this->get(); }
}; // class recursive_wrapper
template <typename T>
inline void swap(recursive_wrapper<T>& lhs, recursive_wrapper<T>& rhs) noexcept
{
lhs.swap(rhs);
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP

1037
src/mapbox/variant.hpp Normal file

File diff suppressed because it is too large Load Diff

45
src/mapbox/variant_io.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef MAPBOX_UTIL_VARIANT_IO_HPP
#define MAPBOX_UTIL_VARIANT_IO_HPP
#include <iosfwd>
#include <mapbox/variant.hpp>
namespace mapbox {
namespace util {
namespace detail {
// operator<< helper
template <typename Out>
class printer
{
public:
explicit printer(Out& out)
: out_(out) {}
printer& operator=(printer const&) = delete;
// visitor
template <typename T>
void operator()(T const& operand) const
{
out_ << operand;
}
private:
Out& out_;
};
}
// operator<<
template <typename CharT, typename Traits, typename... Types>
VARIANT_INLINE std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& out, variant<Types...> const& rhs)
{
detail::printer<std::basic_ostream<CharT, Traits>> visitor(out);
apply_visitor(visitor, rhs);
return out;
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_VARIANT_IO_HPP

View File

@ -0,0 +1,38 @@
#ifndef MAPBOX_UTIL_VARIANT_VISITOR_HPP
#define MAPBOX_UTIL_VARIANT_VISITOR_HPP
namespace mapbox {
namespace util {
template <typename... Fns>
struct visitor;
template <typename Fn>
struct visitor<Fn> : Fn
{
using type = Fn;
using Fn::operator();
visitor(Fn fn) : Fn(fn) {}
};
template <typename Fn, typename... Fns>
struct visitor<Fn, Fns...> : Fn, visitor<Fns...>
{
using type = visitor;
using Fn::operator();
using visitor<Fns...>::operator();
visitor(Fn fn, Fns... fns) : Fn(fn), visitor<Fns...>(fns...) {}
};
template <typename... Fns>
visitor<Fns...> make_visitor(Fns... fns)
{
return visitor<Fns...>(fns...);
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_VARIANT_VISITOR_HPP

1083
src/optional.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -333,12 +333,11 @@ class pcrepp {
public:
class error : public std::exception {
public:
error(std::string msg, int offset)
error(std::string msg, int offset = 0)
: e_msg(msg), e_offset(offset) { };
virtual ~error() throw () { };
virtual const char *what() const throw()
{
virtual const char *what() const throw() {
return this->e_msg.c_str();
};

View File

@ -131,6 +131,7 @@ void rl_change(void *dummy, readline_curses *rc)
meta.sm_description.c_str());
lnav_data.ld_bottom_source.set_prompt(help_text);
}
break;
}
default:
break;

View File

@ -223,8 +223,14 @@ static int rcFilter(sqlite3_vtab_cursor *pVtabCursor,
pCur->c_content = value;
pCur->c_pattern = make_unique<pcrepp>(pattern);
pCur->c_pattern_string = pattern;
try {
pCur->c_pattern = make_unique<pcrepp>(pattern);
pCur->c_pattern_string = pattern;
} catch (const pcrepp::error &e) {
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
"Invalid regular expression: %s", e.e_msg.c_str());
return SQLITE_ERROR;
}
pCur->c_index = 0;
pCur->c_context.set_count(0);

View File

@ -40,12 +40,17 @@ extern "C" {
#endif
struct FuncDef {
const char *zName;
signed char nArg;
uint8_t argType; /* 0: none. 1: db 2: (-1) */
uint8_t eTextRep; /* 1: UTF-16. 0: UTF-8 */
uint8_t needCollSeq;
void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
const char *zName;
signed char nArg;
uint8_t argType; /* 0: none. 1: db 2: (-1) */
int eTextRep; /* 1: UTF-16. 0: UTF-8 */
uint8_t needCollSeq;
void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
const struct ParamDoc {
const char *name;
const char *doc;
} *paramDoc;
const char *description;
};
struct FuncDefAgg {

View File

@ -15,6 +15,8 @@
#include <sqlite3.h>
#include <pcrecpp.h>
#include <unordered_map>
#include "pcrepp.hh"
#include "yajlpp.hh"
@ -24,144 +26,67 @@
#include "data_scanner.hh"
#include "data_parser.hh"
#include "elem_to_json.hh"
#include "vtab_module.hh"
typedef struct {
char * s;
pcrecpp::RE *re;
pcrepp *re2;
} cache_entry;
#ifndef CACHE_SIZE
#define CACHE_SIZE 16
#endif
#define JSON_SUBTYPE 74 /* Ascii for "J" */
#include "optional.hpp"
#include "mapbox/variant.hpp"
using namespace std;
using namespace mapbox;
static
cache_entry *find_re(sqlite3_context *ctx, const char *re)
typedef struct {
shared_ptr<pcrecpp::RE> re;
shared_ptr<pcrepp> re2;
} cache_entry;
static cache_entry *find_re(const char *re)
{
static cache_entry cache[CACHE_SIZE];
int i;
int found = 0;
static unordered_map<string, cache_entry> CACHE;
for (i = 0; i < CACHE_SIZE && cache[i].s; i++) {
if (strcmp(re, cache[i].s) == 0) {
found = 1;
break;
}
}
if (found) {
if (i > 0) {
cache_entry c = cache[i];
memmove(cache + 1, cache, i * sizeof(cache_entry));
cache[0] = c;
}
}
else {
string re_str = re;
auto iter = CACHE.find(re_str);
if (iter == CACHE.end()) {
cache_entry c;
c.re = new pcrecpp::RE(re);
c.re2 = make_shared<pcrepp>(re_str);
c.re = make_shared<pcrecpp::RE>(re);
if (!c.re->error().empty()) {
char *e2 = sqlite3_mprintf("%s: %s", re, c.re->error().c_str());
sqlite3_result_error(ctx, e2, -1);
sqlite3_free(e2);
delete c.re;
auto_mem<char> e2(sqlite3_free);
return NULL;
e2 = sqlite3_mprintf("%s: %s", re, c.re->error().c_str());
throw new pcrepp::error(e2.in(), 0);
}
c.s = strdup(re);
if (!c.s) {
sqlite3_result_error(ctx, "strdup: ENOMEM", -1);
delete c.re;
return NULL;
}
c.re2 = new pcrepp(c.s);
i = CACHE_SIZE - 1;
if (cache[i].s) {
free(cache[i].s);
assert(cache[i].re);
delete cache[i].re;
delete cache[i].re2;
}
memmove(cache + 1, cache, i * sizeof(cache_entry));
cache[0] = c;
CACHE[re_str] = c;
iter = CACHE.find(re_str);
}
return &cache[0];
return &iter->second;
}
static bool regexp(const char *re, const char *str)
{
cache_entry *reobj = find_re(re);
return reobj->re->PartialMatch(str);
}
static
void regexp(sqlite3_context *ctx, int argc, sqlite3_value **argv)
util::variant<int64_t, double, const char*, string_fragment, json_string>
regexp_match(const char *re, const char *str)
{
const char *re, *str;
cache_entry *reobj;
assert(argc == 2);
re = (const char *)sqlite3_value_text(argv[0]);
if (!re) {
sqlite3_result_error(ctx, "no regexp", -1);
return;
}
str = (const char *)sqlite3_value_text(argv[1]);
if (!str) {
sqlite3_result_error(ctx, "no string", -1);
return;
}
reobj = find_re(ctx, re);
if (reobj == NULL)
return;
{
bool rc = reobj->re->PartialMatch(str);
sqlite3_result_int(ctx, rc);
return;
}
}
static
void regexp_match(sqlite3_context *ctx, int argc, sqlite3_value **argv)
{
const char *re, *str;
cache_entry *reobj;
assert(argc == 2);
re = (const char *)sqlite3_value_text(argv[0]);
if (!re) {
sqlite3_result_error(ctx, "no regexp", -1);
return;
}
str = (const char *)sqlite3_value_text(argv[1]);
if (!str) {
sqlite3_result_null(ctx);
return;
}
reobj = find_re(ctx, re);
if (reobj == NULL) {
return;
}
cache_entry *reobj = find_re(re);
pcre_context_static<30> pc;
pcre_input pi(str);
pcrepp &extractor = *reobj->re2;
if (extractor.get_capture_count() == 0) {
sqlite3_result_error(ctx,
"regular expression does not have any captures",
-1);
return;
throw pcrepp::error("regular expression does not have any captures");
}
if (!extractor.match(pc, pi)) {
sqlite3_result_null(ctx);
return;
return static_cast<const char *>(nullptr);
}
auto_mem<yajl_gen_t> gen(yajl_gen_free);
@ -174,7 +99,7 @@ void regexp_match(sqlite3_context *ctx, int argc, sqlite3_value **argv)
const char *cap_start = pi.get_substr_start(cap);
if (!cap->is_valid()) {
sqlite3_result_null(ctx);
return static_cast<const char *>(nullptr);
}
else {
char *cap_copy = (char *)alloca(cap->length() + 1);
@ -187,17 +112,16 @@ void regexp_match(sqlite3_context *ctx, int argc, sqlite3_value **argv)
if (sscanf(cap_copy, "%lld%n", &i_value, &end_index) == 1 &&
(end_index == cap->length())) {
sqlite3_result_int64(ctx, i_value);
return i_value;
}
else if (sscanf(cap_copy, "%lf%n", &d_value, &end_index) == 1 &&
(end_index == cap->length())) {
sqlite3_result_double(ctx, d_value);
return d_value;
}
else {
sqlite3_result_text(ctx, cap_start, cap->length(), SQLITE_TRANSIENT);
return string_fragment(str, cap->c_begin, cap->c_end);
}
}
return;
}
else {
yajlpp_map root_map(gen);
@ -237,14 +161,13 @@ void regexp_match(sqlite3_context *ctx, int argc, sqlite3_value **argv)
}
}
const unsigned char *buf;
size_t len;
yajl_gen_get_buf(gen, &buf, &len);
return json_string(gen);
#if 0
sqlite3_result_text(ctx, (const char *) buf, len, SQLITE_TRANSIENT);
#ifdef HAVE_SQLITE3_VALUE_SUBTYPE
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
#endif
#endif
}
static
@ -284,107 +207,62 @@ void extract(sqlite3_context *ctx, int argc, sqlite3_value **argv)
}
static
void regexp_replace(sqlite3_context *ctx, int argc, sqlite3_value **argv)
string regexp_replace(const char *str, const char *re, const char *repl)
{
const char *re, *str, *repl;
cache_entry *reobj;
cache_entry *reobj = find_re(re);
string dest(str);
assert(argc == 3);
str = (const char *)sqlite3_value_text(argv[0]);
if (!str) {
sqlite3_result_error(ctx, "no string", -1);
return;
}
re = (const char *)sqlite3_value_text(argv[1]);
if (!re) {
sqlite3_result_error(ctx, "no regexp", -1);
return;
}
repl = (const char *)sqlite3_value_text(argv[2]);
if (!repl) {
sqlite3_result_error(ctx, "no string", -1);
return;
}
reobj = find_re(ctx, re);
if (reobj == NULL)
return;
{
string dest(str);
reobj->re->GlobalReplace(repl, &dest);
sqlite3_result_text(ctx, dest.c_str(), dest.length(), SQLITE_TRANSIENT);
return;
}
}
static
void sql_startswith(sqlite3_context *context,
int argc, sqlite3_value **argv)
{
const char *str_in;
const char *prefix;
if ((sqlite3_value_type(argv[0]) == SQLITE_NULL) ||
(sqlite3_value_type(argv[1]) == SQLITE_NULL)) {
sqlite3_result_null(context);
return;
}
str_in = (const char *)sqlite3_value_text(argv[0]);
prefix = (const char *)sqlite3_value_text(argv[1]);
if (strncmp(str_in, prefix, strlen(prefix)) == 0)
sqlite3_result_int(context, 1);
else
sqlite3_result_int(context, 0);
}
static
void sql_endswith(sqlite3_context *context,
int argc, sqlite3_value **argv)
{
const char *str_in;
const char *suffix;
if ((sqlite3_value_type(argv[0]) == SQLITE_NULL) ||
(sqlite3_value_type(argv[1]) == SQLITE_NULL)) {
sqlite3_result_null(context);
return;
}
str_in = (const char *)sqlite3_value_text(argv[0]);
suffix = (const char *)sqlite3_value_text(argv[1]);
int str_len = strlen(str_in);
int suffix_len = strlen(suffix);
if (str_len < suffix_len) {
sqlite3_result_int(context, 0);
}
else if (strcmp(&str_in[str_len - suffix_len], suffix) == 0) {
sqlite3_result_int(context, 1);
}
else {
sqlite3_result_int(context, 0);
}
reobj->re->GlobalReplace(repl, &dest);
return dest;
}
int string_extension_functions(const struct FuncDef **basic_funcs,
const struct FuncDefAgg **agg_funcs)
{
static const struct FuncDef string_funcs[] = {
{ "regexp", 2, 0, SQLITE_UTF8, 0, regexp },
{ "regexp_replace", 3, 0, SQLITE_UTF8, 0, regexp_replace },
{ "regexp_match", 2, 0, SQLITE_UTF8, 0, regexp_match },
sqlite_func_adapter<decltype(&regexp), regexp>::builder(
"regexp",
"Test if a string matches a regular expression",
{
{"re", "The regular expression to use"},
{"str", "The string to test against the regular expression"},
}),
sqlite_func_adapter<decltype(&regexp_match), regexp_match>::builder(
"regexp_match",
"Match a string against a regular expression and return the capture groups",
{
{"re", "The regular expression to use"},
{"str", "The string to test against the regular expression"},
}),
sqlite_func_adapter<decltype(&regexp_replace), regexp_replace>::builder(
"regexp_replace",
"Replace the parts of a string that match a regular expression",
{
{"str", "The string to perform replacements on"},
{"re", "The regular expression to match"},
{"repl", "The replacement string"},
}),
{ "extract", 1, 0, SQLITE_UTF8, 0, extract },
{ "startswith", 2, 0, SQLITE_UTF8, 0, sql_startswith },
{ "endswith", 2, 0, SQLITE_UTF8, 0, sql_endswith },
sqlite_func_adapter<decltype(
static_cast<bool (*)(const char *, const char *)>(&startswith)),
startswith>::builder(
"startswith",
"Test if a string begins with the given prefix",
{
{"str", "The string to test"},
{"prefix", "The prefix to check in the string"},
}),
sqlite_func_adapter<decltype(&endswith), endswith>::builder(
"endswith",
"Test if a string ends with the given suffix",
{
{"str", "The string to test"},
{"suffix", "The suffix to check in the string"},
}),
{ NULL }
};

View File

@ -35,32 +35,252 @@
#include <string>
#include <utility>
#include "optional.hpp"
#include "lnav_log.hh"
#include "lnav_util.hh"
#include "yajlpp.hh"
#include "mapbox/variant.hpp"
#include "sqlite-extension-func.h"
struct from_sqlite_conversion_error : std::exception {
from_sqlite_conversion_error(const char *type, int argi)
: e_type(type), e_argi(argi) {
};
const char *e_type;
int e_argi;
};
template<typename T>
inline T from_sqlite(sqlite3_value *val)
{
return T();
}
struct from_sqlite {
inline T operator()(int argc, sqlite3_value **val, int argi) {
return T();
};
};
template<>
inline int64_t from_sqlite<int64_t>(sqlite3_value *val)
{
return sqlite3_value_int64(val);
}
struct from_sqlite<int64_t> {
inline int64_t operator()(int argc, sqlite3_value **val, int argi) {
if (sqlite3_value_numeric_type(val[argi]) != SQLITE_INTEGER) {
throw from_sqlite_conversion_error("integer", argi);
}
return sqlite3_value_int64(val[argi]);
}
};
template<>
inline const char *from_sqlite<const char *>(sqlite3_value *val)
{
return (const char *) sqlite3_value_text(val);
}
struct from_sqlite<int> {
inline int operator()(int argc, sqlite3_value **val, int argi) {
return sqlite3_value_int(val[argi]);
}
};
template<>
inline double from_sqlite<double>(sqlite3_value *val)
struct from_sqlite<const char *> {
inline const char *operator()(int argc, sqlite3_value **val, int argi) {
return (const char *) sqlite3_value_text(val[argi]);
}
};
template<>
struct from_sqlite<double> {
inline double operator()(int argc, sqlite3_value **val, int argi) {
return sqlite3_value_double(val[argi]);
}
};
template<typename T>
struct from_sqlite<nonstd::optional<T>> {
inline nonstd::optional<T> operator()(int argc, sqlite3_value **val, int argi) {
if (argi >= argc || sqlite3_value_type(val[argi]) == SQLITE_NULL) {
return nonstd::optional<T>();
}
return nonstd::optional<T>(from_sqlite<T>()(argc, val, argi));
}
};
inline void to_sqlite(sqlite3_context *ctx, const char *str)
{
return sqlite3_value_double(val);
if (str == nullptr) {
sqlite3_result_null(ctx);
} else {
sqlite3_result_text(ctx, str, -1, SQLITE_STATIC);
}
}
inline void to_sqlite(sqlite3_context *ctx, const std::string &str)
{
sqlite3_result_text(ctx, str.c_str(), str.length(), SQLITE_TRANSIENT);
}
inline void to_sqlite(sqlite3_context *ctx, const string_fragment &sf)
{
if (sf.is_valid()) {
sqlite3_result_text(ctx,
&sf.sf_string[sf.sf_begin], sf.length(),
SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
}
inline void to_sqlite(sqlite3_context *ctx, bool val)
{
sqlite3_result_int(ctx, val);
}
inline void to_sqlite(sqlite3_context *ctx, int64_t val)
{
sqlite3_result_int64(ctx, val);
}
inline void to_sqlite(sqlite3_context *ctx, double val)
{
sqlite3_result_double(ctx, val);
}
inline void to_sqlite(sqlite3_context *ctx, const json_string &val)
{
sqlite3_result_text(ctx,
(const char *) val.js_content,
val.js_len,
free);
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
}
struct ToSqliteVisitor {
ToSqliteVisitor(sqlite3_context *vctx) : tsv_context(vctx) {
};
template<typename T>
void operator()(T t) const {
to_sqlite(this->tsv_context, t);
}
sqlite3_context *tsv_context;
};
template<typename ... Types>
void to_sqlite(sqlite3_context *ctx, const mapbox::util::variant<Types...> &val)
{
ToSqliteVisitor visitor(ctx);
mapbox::util::apply_visitor(visitor, val);
}
template<typename ... Args>
struct optional_counter;
template<typename T>
struct optional_counter<nonstd::optional<T>> {
constexpr static int value = 1;
};
template<typename T, typename ... Rest>
struct optional_counter<nonstd::optional<T>, Rest...> {
constexpr static int value = 1 + sizeof...(Rest);
};
template<typename Arg>
struct optional_counter<Arg> {
constexpr static int value = 0;
};
template<typename Arg1, typename ... Args>
struct optional_counter<Arg1, Args...> : optional_counter<Args...> {
};
template<typename F, F f> struct sqlite_func_adapter;
template<typename Return, typename ... Args, Return (*f)(Args...)>
struct sqlite_func_adapter<Return (*)(Args...), f> {
constexpr static int OPT_COUNT = optional_counter<Args...>::value;
constexpr static int REQ_COUNT = sizeof...(Args) - OPT_COUNT;
template<size_t ... Idx>
static void func2(sqlite3_context *context,
int argc, sqlite3_value **argv,
std::index_sequence<Idx...>) {
try {
Return retval = f(from_sqlite<Args>()(argc, argv, Idx)...);
to_sqlite(context, retval);
} catch (from_sqlite_conversion_error &e) {
char buffer[64];
snprintf(buffer, sizeof(buffer),
"Expecting an %s for argument number %d",
e.e_type,
e.e_argi);
sqlite3_result_error(context, buffer, -1);
} catch (const std::exception &e) {
sqlite3_result_error(context, e.what(), -1);
} catch (...) {
sqlite3_result_error(context, "Function threw an unexpected exception", -1);
}
};
static void func1(sqlite3_context *context,
int argc, sqlite3_value **argv) {
if (argc < REQ_COUNT) {
char buffer[128];
if (OPT_COUNT == 0) {
snprintf(buffer, sizeof(buffer),
"%s expects exactly %d argument%s",
"foo",
REQ_COUNT,
REQ_COUNT == 1 ? "s" : "");
} else {
snprintf(buffer, sizeof(buffer),
"%s expects between %d and %d arguments",
"bar",
REQ_COUNT,
OPT_COUNT);
}
sqlite3_result_error(context, buffer, -1);
return;
}
for (int lpc = 0; lpc < REQ_COUNT; lpc++) {
if (sqlite3_value_type(argv[lpc]) == SQLITE_NULL) {
sqlite3_result_null(context);
return;
}
}
func2(context, argc, argv, std::make_index_sequence<sizeof...(Args)>{});
};
static FuncDef builder(const char name[],
const char description[],
std::initializer_list<FuncDef::ParamDoc> pdocs) {
static FuncDef::ParamDoc PARAM_DOCS[sizeof...(Args) + 1];
require(pdocs.size() == sizeof...(Args));
std::copy(std::begin(pdocs), std::end(pdocs), std::begin(PARAM_DOCS));
return {
name,
OPT_COUNT > 0 ? -1 : REQ_COUNT,
0,
SQLITE_UTF8 | SQLITE_DETERMINISTIC,
0,
func1,
PARAM_DOCS,
description,
};
};
};
extern std::string vtab_module_schemas;
class vtab_index_constraints {
@ -181,7 +401,7 @@ struct vtab_module {
template<typename ... Args, size_t... Idx>
static int apply_impl(T &obj, int (T::*func)(sqlite3_vtab *, sqlite3_int64 &, Args...), sqlite3_vtab *tab, sqlite3_int64 &rowid, sqlite3_value **argv, std::index_sequence<Idx...>)
{
return (obj.*func)(tab, rowid, from_sqlite<Args>(argv[Idx])...);
return (obj.*func)(tab, rowid, from_sqlite<Args>()(sizeof...(Args), argv, Idx)...);
}
template<typename ... Args>
@ -333,6 +553,7 @@ struct vtab_module {
// XXX Eponymous tables don't seem to work in older sqlite versions
impl_name += "_impl";
int rc = sqlite3_create_module(db, impl_name.c_str(), &this->vm_module, NULL);
ensure(rc == SQLITE_OK);
std::string create_stmt = std::string("CREATE VIRTUAL TABLE ") + name + " USING " + impl_name + "()";
return sqlite3_exec(db, create_stmt.c_str(), NULL, NULL, NULL);
};

View File

@ -700,4 +700,20 @@ public:
json_path_handler *ygc_handlers;
};
#define JSON_SUBTYPE 74 /* Ascii for "J" */
struct json_string {
json_string(yajl_gen_t *gen) {
const unsigned char *buf;
yajl_gen_get_buf(gen, &buf, &this->js_len);
this->js_content = (const unsigned char *) malloc(this->js_len);
memcpy((void *) this->js_content, buf, this->js_len);
};
const unsigned char *js_content;
size_t js_len;
};
#endif

View File

@ -42,6 +42,13 @@ Row 0:
Column basename('/foo///'): foo
EOF
run_test ./drive_sql "select basename('foo')"
check_output "basename('foo') is not 'foo'" <<EOF
Row 0:
Column basename('foo'): foo
EOF
run_test ./drive_sql "select dirname('')"

View File

@ -86,8 +86,9 @@ EOF
run_test ./drive_sql "select regexp_match(null, 'abc')"
check_error_output "" <<EOF
error: sqlite3_exec failed -- no regexp
check_output "" <<EOF
Row 0:
Column regexp_match(null, 'abc'): (null)
EOF
run_test ./drive_sql "select regexp_match('abc', null) as result"
@ -238,6 +239,12 @@ run_test ./drive_sql "SELECT * FROM regexp_capture('foo bar')"
check_output "" <<EOF
EOF
run_test ./drive_sql "SELECT * FROM regexp_capture('foo bar', '(')"
check_error_output "" <<EOF
error: sqlite3_exec failed -- Invalid regular expression: missing )
EOF
run_test ./drive_sql "SELECT * FROM regexp_capture('1 2 3 45', '(\d+)')"
check_output "" <<EOF