mirror of https://github.com/tstack/lnav.git
[sql] add function adapter template
Defect Number: Reviewed By: Testing Done:
This commit is contained in:
parent
81e55b82dc
commit
0b157ff867
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 },
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(®exp), 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(®exp_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(®exp_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 }
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('')"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue