Change-Id: I419deec561ff4c107cedaf0c7ff1e86bad04ce0achanges/84/14384/1
parent
c3a6a631ef
commit
3ac0a074ab
@ -0,0 +1,37 @@
|
||||
export LIBNAME=libhsclient-c-wrapper
|
||||
export VERSION=0
|
||||
export LIBSO=$(LIBNAME).so
|
||||
export LIBSOVER=$(LIBNAME).so.$(VERSION)
|
||||
|
||||
LIBDIR=lib
|
||||
HDR=include/hsclient-c-wrapper
|
||||
|
||||
DESTDIR?=/usr/local
|
||||
|
||||
all:
|
||||
$(MAKE) -C src
|
||||
$(MAKE) -C tests
|
||||
|
||||
clean:
|
||||
$(MAKE) -C src clean
|
||||
$(MAKE) -C tests clean
|
||||
rm -rf project.tgz cov-int
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)/$(HDR)
|
||||
cp include/*.h $(DESTDIR)/$(HDR)
|
||||
mkdir -p $(DESTDIR)/$(LIBDIR)
|
||||
cp src/$(LIBSOVER) $(DESTDIR)/$(LIBDIR)/$(LIBSOVER)
|
||||
ln -sf $(LIBSOVER) $(DESTDIR)/$(LIBDIR)/$(LIBSO)
|
||||
|
||||
coverity:
|
||||
cov-build --dir cov-int $(MAKE)
|
||||
tar -czf project.tgz cov-int
|
||||
curl --form token=$(COVERITY_TOKEN) \
|
||||
--form email=$(DEBEMAIL) \
|
||||
--form file=@project.tgz \
|
||||
--form version="$(COVERITY_VERSION)" \
|
||||
--form description="automatic upload" \
|
||||
https://scan.coverity.com/builds?project=$(COVERITY_PROJECT)
|
||||
|
||||
.PHONY: all clean install coverity
|
||||
@ -0,0 +1,5 @@
|
||||
libhsclient-c-wrapper (5.3.0.0+0~mr5.3.0.0) unstable; urgency=medium
|
||||
|
||||
* Initial revision
|
||||
|
||||
-- Andreas Granig <agranig@sipwise.com> Wed, 12 Apr 2017 21:09:23 +0200
|
||||
@ -0,0 +1 @@
|
||||
9
|
||||
@ -0,0 +1,26 @@
|
||||
Source: libhsclient-c-wrapper
|
||||
Section: admin
|
||||
Priority: extra
|
||||
Maintainer: Sipwise Development Team <support@sipwise.com>
|
||||
Build-Depends: debhelper (>= 9)
|
||||
Standards-Version: 3.9.7
|
||||
Homepage: http://sipwise.com/
|
||||
|
||||
Package: libhsclient-c-wrapper0
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, libhsclient-dev
|
||||
Description: library for handlersocket handling in C programs
|
||||
This library provides a high-level interface for C programs
|
||||
to interact with the HandlerSocket interface in MySQL flavor
|
||||
databases. It uses the C++ libhsclient library under the hood.
|
||||
|
||||
Package: libhsclient-c-wrapper-dev
|
||||
Architecture: any
|
||||
Section: libdevel
|
||||
Depends: libhsclient-c-wrapper0 (= ${binary:Version}), ${misc:Depends}
|
||||
Description: Headers for libhsclient-c-wrapper0
|
||||
This library provides a high-level interface for C programs
|
||||
to interact with the HandlerSocket interface in MySQL flavor
|
||||
databases. It uses the C++ libhsclient library under the hood.
|
||||
.
|
||||
This package provides the header files.
|
||||
@ -0,0 +1,23 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Source: https://www.sipwise.com/
|
||||
Upstream-Contact: Sipwise Development Team <support@sipwise.com>
|
||||
|
||||
Files: *
|
||||
Copyright:
|
||||
Copyright © 2013-2016 Sipwise GmbH, Austria
|
||||
License: GPL-3+
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
Comment:
|
||||
On Debian systems, the full text of the GNU General Public License
|
||||
version 3 can be found in the file '/usr/share/common-licenses/GPL-3'.
|
||||
@ -0,0 +1,2 @@
|
||||
debian/tmp/include/* /usr/include/
|
||||
debian/tmp/lib/*.so /usr/lib/
|
||||
@ -0,0 +1 @@
|
||||
debian/tmp/lib/*.so.* /usr/lib/
|
||||
@ -0,0 +1 @@
|
||||
activate-noawait ldconfig
|
||||
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/make -f
|
||||
# Uncomment this to turn on verbose mode.
|
||||
# export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
@ -0,0 +1,88 @@
|
||||
#ifndef _HSCLIENT_C_WRAPPER_H
|
||||
#define _HSCLIENT_C_WRAPPER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef char* hs_element;
|
||||
typedef hs_element* hs_row;
|
||||
typedef hs_row* hs_table;
|
||||
typedef struct hs_result_s {
|
||||
hs_table table;
|
||||
size_t cols, rows;
|
||||
size_t row_iter;
|
||||
char error[1024];
|
||||
int success;
|
||||
} hs_result;
|
||||
typedef hs_result* hs_res;
|
||||
|
||||
typedef struct hs_filterkey_s {
|
||||
char *name;
|
||||
char *op;
|
||||
char *val;
|
||||
} hs_filterkey;
|
||||
typedef hs_filterkey hs_key;
|
||||
|
||||
typedef enum hs_value_type_e {
|
||||
HS_NULL,
|
||||
HS_INT,
|
||||
HS_DOUBLE,
|
||||
HS_STRING,
|
||||
} hs_valtype;
|
||||
|
||||
typedef struct hs_value_s {
|
||||
char *name;
|
||||
union {
|
||||
int64_t val_int;
|
||||
char *val_string;
|
||||
double val_double;
|
||||
};
|
||||
hs_valtype type;
|
||||
} hs_value;
|
||||
typedef hs_value hs_val;
|
||||
|
||||
typedef struct hs_connection_s {
|
||||
uint32_t index;
|
||||
} hs_connection;
|
||||
typedef hs_connection* hs_con;
|
||||
|
||||
size_t hs_num_rows(hs_res res);
|
||||
size_t hs_num_cols(hs_res res);
|
||||
hs_row hs_fetch_row(hs_res res);
|
||||
void hs_free_result(hs_res res);
|
||||
int hs_success(hs_res res);
|
||||
char* hs_get_error(hs_res res);
|
||||
size_t hs_max();
|
||||
|
||||
hs_res hs_init_connection(hs_con *con,
|
||||
const char *host, uint16_t port,
|
||||
const char *dbname, const char *table,
|
||||
const char *index, const char *indexcols,
|
||||
const char *secret);
|
||||
|
||||
hs_res hs_prepare_query(hs_con con, hs_key** keys);
|
||||
|
||||
|
||||
|
||||
hs_res hs_connect(hs_con *con, char **cfg, size_t num_cfg);
|
||||
void hs_close(hs_con con);
|
||||
|
||||
hs_res hs_select(hs_con con, char **keys, size_t num_keys, size_t limit, size_t offset);
|
||||
hs_res hs_insert(hs_con con, char **keys, size_t num_keys);
|
||||
|
||||
hs_res hs_filtered_update(hs_con con, char **keys, size_t num_keys, char **filters, size_t num_filters, char **vals, size_t num_vals);
|
||||
hs_res hs_update(hs_con con, char **keys, size_t num_keys, char **vals, size_t num_vals);
|
||||
|
||||
hs_res hs_filtered_delete(hs_con con, char **keys, size_t num_keys, char **filters, size_t num_filters);
|
||||
hs_res hs_delete(hs_con con, char **keys, size_t num_keys);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _HSCLIENT_C_WRAPPER_H
|
||||
@ -0,0 +1,26 @@
|
||||
CC=g++
|
||||
CXXFLAGS+=-I../include -g -Wall -O3 -fPIC -std=c++11
|
||||
CXXFLAGS+=-D_GLIBCXX_USE_CXX11_ABI=0
|
||||
LDFLAGS=-lhsclient -O3 -shared -fPIC -Wl,-soname,$(LIBNAME).so.$(VERSION)
|
||||
|
||||
CPPFILES=$(wildcard *.cpp)
|
||||
OFILES=$(CPPFILES:.cpp=.o)
|
||||
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(LIBSO)
|
||||
|
||||
$(LIBSO): $(LIBSOVER)
|
||||
ln -fs $(LIBSOVER) $(LIBSO)
|
||||
|
||||
$(LIBSOVER): $(OFILES)
|
||||
$(CC) -o $@ $(OFILES) $(LDFLAGS)
|
||||
|
||||
%.o: %.cpp
|
||||
$(CC) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(OFILES)
|
||||
rm -f $(LIBSO)
|
||||
rm -f $(LIBSOVER)
|
||||
@ -0,0 +1,235 @@
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <handlersocket/hstcpcli.hpp>
|
||||
#include <handlersocket/string_util.hpp>
|
||||
|
||||
#include "hsclient-c-wrapper.h"
|
||||
|
||||
typedef std::shared_ptr<dena::hstcpcli_i> hstcpcli_sptr;
|
||||
|
||||
typedef std::vector<std::string> hs_indexlist;
|
||||
std::vector<hs_indexlist> g_indexlist;
|
||||
|
||||
struct hs_handle_s {
|
||||
dena::config config;
|
||||
hs_indexlist indexcols;
|
||||
hstcpcli_sptr cli;
|
||||
};
|
||||
typedef struct hs_handle_s hs_handle;
|
||||
typedef std::shared_ptr<hs_handle> hs_handle_sptr;
|
||||
std::vector<hs_handle_sptr> g_handles;
|
||||
|
||||
|
||||
extern hs_result *hs_create_result();
|
||||
extern void hs_set_error(hs_result *res, const char* format, ...);
|
||||
|
||||
hstcpcli_sptr hs_get_cli(hs_connection *con) {
|
||||
hs_handle_sptr handle;
|
||||
try {
|
||||
handle = g_handles.at(con->index);
|
||||
} catch(const std::out_of_range &e) {
|
||||
//hs_set_error(r, "invalid connection index %lu", con->index);
|
||||
//return r;
|
||||
return NULL;
|
||||
}
|
||||
if(!handle) {
|
||||
//hs_set_error(r, "invalid connection index %lu", con->index);
|
||||
//return r;
|
||||
return NULL;
|
||||
}
|
||||
return handle->cli;
|
||||
}
|
||||
|
||||
static hs_connection *hs_create_connection() {
|
||||
hs_connection *con = (hs_connection*)malloc(sizeof(hs_connection));
|
||||
if(!con) {
|
||||
return NULL;
|
||||
}
|
||||
con->index= 0;
|
||||
return con;
|
||||
}
|
||||
|
||||
std::vector<std::string> hs_splitstring(const char *str, char c = ',')
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
do
|
||||
{
|
||||
const char *begin = str;
|
||||
while(*str != c && *str) {
|
||||
str++;
|
||||
}
|
||||
res.push_back(std::string(begin, str));
|
||||
} while (0 != *str++);
|
||||
return res;
|
||||
}
|
||||
|
||||
hs_result* hs_init_connection(hs_connection **con,
|
||||
const char *host, uint16_t port,
|
||||
const char *dbname, const char* table,
|
||||
const char *index, const char* indexcols,
|
||||
const char *secret) {
|
||||
|
||||
dena::config conf;
|
||||
dena::socket_args sockargs;
|
||||
|
||||
hs_result *r = hs_create_result();
|
||||
if(!r) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*con = hs_create_connection();
|
||||
if(!*con) {
|
||||
hs_set_error(r, "failed to create connection: %s", strerror(errno));
|
||||
return r;
|
||||
}
|
||||
|
||||
conf["host"] = std::string(host);
|
||||
conf["port"] = std::to_string(port);
|
||||
conf["dbname"] = std::string(dbname);
|
||||
conf["table"] = std::string(table);
|
||||
conf["index"] = std::string(index);
|
||||
conf["indexcols"] = std::string(indexcols);
|
||||
sockargs.set(conf);
|
||||
|
||||
hs_indexlist index_list = hs_splitstring(indexcols, ',');
|
||||
|
||||
hstcpcli_sptr cli = dena::hstcpcli_i::create(sockargs);
|
||||
hs_handle_sptr handle = hs_handle_sptr(new hs_handle);
|
||||
handle->cli = cli;
|
||||
handle->indexcols = index_list;
|
||||
handle->config = conf;
|
||||
g_handles.push_back(handle);
|
||||
(*con)->index = g_handles.size()-1;
|
||||
|
||||
if(secret != NULL) {
|
||||
int code = 0;
|
||||
size_t numflds = 0;
|
||||
cli->request_buf_auth(secret, "1");
|
||||
do {
|
||||
if(cli->request_send() != 0) {
|
||||
hs_set_error(r, "failed to send auth request: %s", cli->get_error().c_str());
|
||||
hs_close(*con);
|
||||
break;
|
||||
}
|
||||
if((code = cli->response_recv(numflds)) != 0) {
|
||||
hs_set_error(r, "failed to receive auth response: %s", cli->get_error().c_str());
|
||||
hs_close(*con);
|
||||
}
|
||||
cli->response_buf_remove();
|
||||
} while(false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
hs_result* hs_prepare_query(hs_connection *con, hs_filterkey** keys) {
|
||||
printf("preparing result\n");
|
||||
hs_result *r = hs_create_result();
|
||||
if(!r) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
1. if all keys->name and keys->op are found in sets, use con as is
|
||||
2. else find keys->name in index and prepare index list,
|
||||
prepare remaining names as filter list,
|
||||
open index, store lists in sets
|
||||
*/
|
||||
|
||||
hs_handle_sptr handle;
|
||||
hs_indexlist index_list;
|
||||
hstcpcli_sptr cli;
|
||||
|
||||
try {
|
||||
handle = g_handles.at(con->index);
|
||||
} catch(const std::out_of_range &e) {
|
||||
hs_set_error(r, "invalid connection index %lu", con->index);
|
||||
return r;
|
||||
}
|
||||
if(!handle) {
|
||||
hs_set_error(r, "invalid connection index %lu", con->index);
|
||||
return r;
|
||||
}
|
||||
index_list = handle->indexcols;
|
||||
cli = handle->cli;
|
||||
|
||||
std::vector<std::string> filter_list;
|
||||
for(hs_filterkey **k = keys; *k != NULL; ++k) {
|
||||
size_t pos;
|
||||
char *name = (*k)->name;
|
||||
printf("checking: %s %s %s\n", name, (*k)->op, (*k)->val);
|
||||
auto it = std::find(index_list.begin(), index_list.end(), std::string(name));
|
||||
if(it != index_list.end()) {
|
||||
pos = std::distance(index_list.begin(), it);
|
||||
printf("found key %s at position %lu in index list\n", name, pos);
|
||||
// TODO: add val in right index slot
|
||||
} else {
|
||||
printf("key %s not in index list, search filter list\n", name);
|
||||
auto it = std::find(filter_list.begin(), filter_list.end(), std::string(name));
|
||||
if(it != filter_list.end()) {
|
||||
pos = std::distance(filter_list.begin(), it);
|
||||
printf("found key %s at position %lu in filter list\n", name, pos);
|
||||
} else {
|
||||
filter_list.push_back(std::string(name));
|
||||
pos = filter_list.size()-1;
|
||||
printf("added key %s at position %lu in filter list\n", name, pos);
|
||||
}
|
||||
// TODO: add val in right filter slot
|
||||
}
|
||||
}
|
||||
|
||||
std::string index;
|
||||
for(auto it = index_list.begin(); it != index_list.end(); it++) {
|
||||
if(it != index_list.begin()) {
|
||||
index.append(",");
|
||||
}
|
||||
index.append(*it);
|
||||
}
|
||||
|
||||
std::string filter;
|
||||
for(auto it = filter_list.begin(); it != filter_list.end(); it++) {
|
||||
if(it != filter_list.begin()) {
|
||||
filter.append(",");
|
||||
}
|
||||
filter.append(*it);
|
||||
}
|
||||
|
||||
printf("index=%s\n", index.c_str()); // that's not actually needed, just make sure we have all the vals
|
||||
printf("filter=%s\n", filter.c_str());
|
||||
// TODO: add field list
|
||||
|
||||
|
||||
std::string fields("strvalue,intvalue");
|
||||
do {
|
||||
int code;
|
||||
size_t numflds;
|
||||
cli->request_buf_open_index(con->index, handle->config["dbname"].c_str(), handle->config["table"].c_str(),
|
||||
handle->config["index"].c_str(), fields.c_str(), filter.c_str());
|
||||
if(cli->request_send() != 0) {
|
||||
hs_set_error(r, "failed to send opening request: %s", cli->get_error().c_str());
|
||||
hs_close(con);
|
||||
break;
|
||||
}
|
||||
if((code = cli->response_recv(numflds)) != 0) {
|
||||
hs_set_error(r, "failed to receive opening response: %s", cli->get_error().c_str());
|
||||
cli->response_buf_remove();
|
||||
hs_close(con);
|
||||
break;
|
||||
}
|
||||
cli->response_buf_remove();
|
||||
} while(false);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void hs_close(hs_connection *con) {
|
||||
if(!con) {
|
||||
return;
|
||||
}
|
||||
g_handles.at(con->index) = NULL;
|
||||
// TODO: also clear g_indexlist.at(con->index) ?
|
||||
free(con);
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <handlersocket/hstcpcli.hpp>
|
||||
#include <handlersocket/string_util.hpp>
|
||||
|
||||
#include "hsclient-c-wrapper.h"
|
||||
|
||||
typedef struct dena::hstcpcli_filter hs_filter;
|
||||
typedef std::shared_ptr<dena::hstcpcli_i> hstcpcli_sptr;
|
||||
|
||||
extern hstcpcli_sptr hs_get_cli(hs_connection *con);
|
||||
extern hs_result *hs_create_result();
|
||||
extern void hs_set_error(hs_result *res, const char* format, ...);
|
||||
extern int hs_set_element(hs_result *res, size_t row, size_t col, const char *val);
|
||||
extern int hs_add_row(hs_result *res);
|
||||
|
||||
static hs_result* hs_query_full(hs_connection *con, char **keys, size_t num_keys, size_t limit, size_t offset, const dena::string_ref op_ref, char **vals, size_t num_vals, const dena::string_ref mod_ref, char **filters, size_t num_filters) {
|
||||
|
||||
std::vector<dena::string_ref> keyrefs;
|
||||
std::vector<dena::string_ref> valrefs;
|
||||
std::vector<hs_filter> filterrefs;
|
||||
|
||||
int code = 0;
|
||||
size_t numflds = 0;
|
||||
|
||||
hs_result *r = hs_create_result();
|
||||
if(!r) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hstcpcli_sptr cli = hs_get_cli(con);
|
||||
if(!cli) {
|
||||
hs_set_error(r, "invalid connection index %lu", con->index);
|
||||
return r;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < num_keys; ++i) {
|
||||
const dena::string_ref ref(keys[i], strlen(keys[i]));
|
||||
keyrefs.push_back(ref);
|
||||
}
|
||||
for(size_t i = 0; i < num_vals; ++i) {
|
||||
if(vals[i] == NULL) {
|
||||
valrefs.push_back(dena::string_ref(0, 1));
|
||||
} else {
|
||||
valrefs.push_back(dena::string_ref(vals[i], strlen(vals[i])));
|
||||
}
|
||||
}
|
||||
for(size_t i = 0; i < num_filters; ++i) {
|
||||
hs_filter ref;
|
||||
ref.filter_type = dena::string_ref("F", 1); // F for filter, W for while (stop on first mismatch)
|
||||
ref.op = dena::string_ref("=", 1);
|
||||
ref.val = dena::string_ref(filters[i], strlen(filters[i]));
|
||||
filterrefs.push_back(ref);
|
||||
}
|
||||
|
||||
cli->request_buf_exec_generic(con->index,
|
||||
op_ref, num_keys == 0 ? 0 : &keyrefs[0], num_keys,
|
||||
limit, offset,
|
||||
mod_ref, num_vals == 0 ? 0 : &valrefs[0], num_vals,
|
||||
num_filters == 0 ? 0 : &filterrefs[0], num_filters);
|
||||
|
||||
if(cli->request_send() != 0) {
|
||||
hs_set_error(r, "failed to send hs request: %s", cli->get_error().c_str());
|
||||
return r;
|
||||
}
|
||||
|
||||
do {
|
||||
if((code = cli->response_recv(numflds)) != 0) {
|
||||
hs_set_error(r, "failed to receive hs response: %s", cli->get_error().c_str());
|
||||
break;
|
||||
}
|
||||
r->cols = numflds;
|
||||
int rowcnt = 0;
|
||||
while(true) {
|
||||
const dena::string_ref *const row = cli->get_next_row();
|
||||
if(row == 0) {
|
||||
break;
|
||||
}
|
||||
if(hs_add_row(r) < 0) {
|
||||
break;
|
||||
}
|
||||
for(size_t i = 0; i < numflds; ++i) {
|
||||
const std::string val(row[i].begin(), row[i].size());
|
||||
if(hs_set_element(r, rowcnt, i, val.c_str()) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rowcnt++;
|
||||
}
|
||||
} while(false);
|
||||
cli->response_buf_remove();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static hs_result* hs_query(hs_connection *con, char **keys, size_t num_keys, size_t limit, size_t offset, const dena::string_ref op_ref) {
|
||||
return hs_query_full(con, keys, num_keys, limit, offset, op_ref, NULL, 0, dena::string_ref(), NULL, 0);
|
||||
}
|
||||
|
||||
hs_result* hs_select(hs_connection *con, char **keys, size_t num_keys, size_t limit, size_t offset) {
|
||||
const dena::string_ref op_ref("=", 1);
|
||||
return hs_query(con, keys, num_keys, limit, offset, op_ref);
|
||||
}
|
||||
|
||||
hs_result* hs_insert(hs_connection *con, char **keys, size_t num_keys) {
|
||||
const dena::string_ref op_ref("+", 1);
|
||||
return hs_query(con, keys, num_keys, 0, 0, op_ref);
|
||||
}
|
||||
|
||||
hs_result* hs_filtered_update(hs_connection *con, char **keys, size_t num_keys, char **filters, size_t num_filters, char **vals, size_t num_vals) {
|
||||
const dena::string_ref op_ref("=", 1);
|
||||
const dena::string_ref mod_ref("U", 1);
|
||||
return hs_query_full(con, keys, num_keys, hs_max(), 0, op_ref, vals, num_vals, mod_ref, filters, num_filters);
|
||||
}
|
||||
|
||||
hs_result* hs_delete(hs_connection *con, char **keys, size_t num_keys) {
|
||||
return hs_filtered_delete(con, keys, num_keys, NULL, 0);
|
||||
}
|
||||
|
||||
hs_result* hs_filtered_delete(hs_connection *con, char **keys, size_t num_keys, char **filters, size_t num_filters) {
|
||||
const dena::string_ref op_ref("=", 1);
|
||||
const dena::string_ref mod_ref("D", 1);
|
||||
return hs_query_full(con, keys, num_keys, hs_max(), 0, op_ref, NULL, 0, mod_ref, filters, num_filters);
|
||||
}
|
||||
|
||||
hs_result* hs_update(hs_connection *con, char **keys, size_t num_keys, char **vals, size_t num_vals) {
|
||||
return hs_filtered_update(con, keys, num_keys, NULL, 0, vals, num_vals);
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <handlersocket/hstcpcli.hpp>
|
||||
#include <handlersocket/string_util.hpp>
|
||||
|
||||
#include "hsclient-c-wrapper.h"
|
||||
|
||||
size_t hs_max() {
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
hs_result *hs_create_result() {
|
||||
hs_result *res = (hs_result*)malloc(sizeof(hs_result));
|
||||
if(!res) {
|
||||
fprintf(stderr, "failed to allocate result: %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
snprintf(res->error, sizeof(res->error), "success");
|
||||
res->success = 1;
|
||||
res->rows = 0;
|
||||
res->cols = 0;
|
||||
res->row_iter = 0;
|
||||
res->table = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
void hs_set_error(hs_result *res, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
snprintf(res->error, sizeof(res->error), format, args);
|
||||
va_end(args);
|
||||
res->success = 0;
|
||||
}
|
||||
|
||||
int hs_add_row(hs_result *res) {
|
||||
hs_row row = (hs_row)malloc(res->cols * sizeof(hs_element));
|
||||
if(!row) {
|
||||
hs_set_error(res, "failed to allocate row memory: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
memset(row, 0, res->cols * sizeof(hs_element));
|
||||
if(!res->rows) {
|
||||
res->table = (hs_table)malloc(sizeof(hs_row));
|
||||
if(!res->table) {
|
||||
hs_set_error(res, "failed to allocate table memory: %s", strerror(errno));
|
||||
free(row);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
res->table = (hs_table)realloc(res->table, (res->rows+1) * sizeof(hs_row));
|
||||
if(!res->table) {
|
||||
hs_set_error(res, "failed to increase table memory: %s", strerror(errno));
|
||||
free(row);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->table[res->rows++] = row;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hs_set_element(hs_result *res, size_t row, size_t col, const char *val) {
|
||||
if(row > res->rows) {
|
||||
hs_set_error(res, "failed row boundary check while setting element");
|
||||
return -1;
|
||||
}
|
||||
if(col > res->cols) {
|
||||
hs_set_error(res, "failed col boundary check while setting element");
|
||||
return -1;
|
||||
}
|
||||
res->table[row][col] = strdup(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t hs_num_rows(hs_result *res) {
|
||||
return res->rows;
|
||||
}
|
||||
|
||||
size_t hs_num_cols(hs_result *res) {
|
||||
return res->cols;
|
||||
}
|
||||
|
||||
hs_row hs_fetch_row(hs_result *res) {
|
||||
if(res->row_iter == res->rows) {
|
||||
return NULL;
|
||||
}
|
||||
return res->table[res->row_iter++];
|
||||
}
|
||||
|
||||
void hs_free_result(hs_result *res) {
|
||||
if(!res || !res->table) {
|
||||
return;
|
||||
}
|
||||
for(size_t row = 0; row < res->rows; ++row) {
|
||||
if(!res->table[row]) {
|
||||
continue;
|
||||
}
|
||||
for(size_t col = 0; col < res->cols; ++col) {
|
||||
if(res->table[row][col]) {
|
||||
free(res->table[row][col]);
|
||||
}
|
||||
}
|
||||
free(res->table[row]);
|
||||
}
|
||||
free(res->table);
|
||||
free(res);
|
||||
}
|
||||
|
||||
int hs_success(hs_result *res) {
|
||||
if(res) {
|
||||
return res->success;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char* hs_get_error(hs_result *res) {
|
||||
if(!res) {
|
||||
return (char*) "invalid result handle";
|
||||
} else {
|
||||
return res->error;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
CFILES=$(wildcard *.c)
|
||||
BINFILES=$(CFILES:.c=.bin)
|
||||
|
||||
all: $(BINFILES)
|
||||
|
||||
%.bin: %.c
|
||||
gcc -o $@ $< -Wall -I../include -L../src -lhsclient-c-wrapper
|
||||
|
||||
clean:
|
||||
rm -f *.bin
|
||||
@ -0,0 +1,78 @@
|
||||
#!/usr/bin/perl -w
|
||||
use strict;
|
||||
|
||||
# apt-get install libnet-handlersocket-perl
|
||||
use Net::HandlerSocket;
|
||||
use DBI;
|
||||
use Time::HiRes qw/gettimeofday tv_interval/;
|
||||
use Data::Dumper;
|
||||
|
||||
my $runs = 10000;
|
||||
|
||||
|
||||
my $acc_cols = "method,from_tag,to_tag,callid,sip_code,sip_reason,time,time_hires,src_leg,dst_leg,dst_user,dst_ouser,dst_domain,src_user,src_domain";
|
||||
|
||||
my @acc = (
|
||||
'INVITE',
|
||||
'test_from_tag',
|
||||
'test_to_tag',
|
||||
'test_call_id',
|
||||
'200',
|
||||
'OK',
|
||||
'2017-04-12 12:00:00',
|
||||
Time::HiRes::time,
|
||||
'test_src_leg',
|
||||
'test_dst_leg',
|
||||
'test_dst_user',
|
||||
'test_dst_ouser',
|
||||
'test_dst_domain',
|
||||
'test_src_user',
|
||||
'test_src_domain',
|
||||
);
|
||||
|
||||
my %prefs;
|
||||
my $start;
|
||||
my $duration;
|
||||
|
||||
###### Handlersocket version goes here: ######################
|
||||
my $idx = 1;
|
||||
my $hs = Net::HandlerSocket->new({
|
||||
host => '127.0.0.1',
|
||||
port => 9997,
|
||||
}) or die "Failed to connect to HS port\n";
|
||||
|
||||
if($hs->open_index($idx, 'kamailio', 'acc', 'PRIMARY', $acc_cols)) {
|
||||
die "Failed to open index on kamailio.acc: " . $hs->get_error() . "\n";
|
||||
}
|
||||
|
||||
$start = [gettimeofday];
|
||||
for(my $i = 0; $i < $runs; ++$i) {
|
||||
my $res = $hs->execute_single($idx, '+', \@acc, -1, 0);
|
||||
my $status = shift @{ $res };
|
||||
if($status != 0) {
|
||||
die "Failed to execute query: " . $hs->get_error() . "\n";
|
||||
}
|
||||
}
|
||||
$duration = tv_interval($start, [gettimeofday]);
|
||||
print "--------- handlersocket results --------------\n";
|
||||
print "Duration: $duration sec for $runs interations\n";
|
||||
print "Req/sec: " . ($runs/$duration) . "\n";
|
||||
print "Dur/req: " . ($duration/$runs) . " sec\n";
|
||||
|
||||
###### SQL version goes here: ######################
|
||||
|
||||
my $dsn = "DBI:mysql:database=kamailio;host=localhost;port=3306";
|
||||
my $dbh = DBI->connect($dsn, "kamailio", "XHNLjd3L4fWtdwE3ta7L");
|
||||
my $valtmpl = join ",", (("?") x @acc);
|
||||
my $q = "insert into acc($acc_cols) values($valtmpl)";
|
||||
my $sth = $dbh->prepare($q);
|
||||
|
||||
for(my $i = 0; $i < $runs; ++$i) {
|
||||
%prefs = ();
|
||||
$sth->execute(@acc);
|
||||
}
|
||||
$duration = tv_interval($start, [gettimeofday]);
|
||||
print "--------- sql results --------------\n";
|
||||
print "Duration: $duration sec for $runs interations\n";
|
||||
print "Req/sec: " . ($runs/$duration) . "\n";
|
||||
print "Dur/req: " . ($duration/$runs) . " sec\n";
|
||||
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/perl -w
|
||||
use strict;
|
||||
|
||||
# apt-get install libnet-handlersocket-perl
|
||||
use Net::HandlerSocket;
|
||||
use DBI;
|
||||
use Time::HiRes qw/gettimeofday tv_interval/;
|
||||
use Data::Dumper;
|
||||
|
||||
my $uuid = 'fcd7f63c-a99e-4f13-befe-df0a9d1941f8';
|
||||
|
||||
my %prefs;
|
||||
my $start;
|
||||
my $duration;
|
||||
#my $runs = 100000;
|
||||
my $runs = 1;
|
||||
|
||||
###### Handlersocket version goes here: ######################
|
||||
my $kam_usr_pref = 1;
|
||||
my $hs = Net::HandlerSocket->new({
|
||||
host => '127.0.0.1',
|
||||
port => 9996,
|
||||
}) or die "Failed to connect to HS port\n";
|
||||
|
||||
if($hs->auth("readsecret")) {
|
||||
die "Failed to auth\n";
|
||||
}
|
||||
|
||||
if($hs->open_index($kam_usr_pref, 'kamailio', 'usr_preferences', 'ua_idx', 'attribute,value')) {
|
||||
die "Failed to open index on kamailio.usr_preferences: " . $hs->get_error() . "\n";
|
||||
}
|
||||
|
||||
$start = [gettimeofday];
|
||||
for(my $i = 0; $i < $runs; ++$i) {
|
||||
%prefs = ();
|
||||
my $res = $hs->execute_single($kam_usr_pref, '=', [$uuid], -1, 0);
|
||||
my $status = shift @{ $res };
|
||||
if($status != 0) {
|
||||
die "Failed to execute query: " . $hs->get_error() . "\n";
|
||||
}
|
||||
|
||||
while(@{ $res }) {
|
||||
my $k = shift @{ $res };
|
||||
my $v = shift @{ $res };
|
||||
# we already had this key, so it's an attribute
|
||||
# with at least 2 values
|
||||
if(exists $prefs{$k}) {
|
||||
# if we already have >1 values, it's
|
||||
# converted to an array ref, so just
|
||||
# push our value at the end
|
||||
if(ref $prefs{$k} eq "ARRAY") {
|
||||
push @{ $prefs{$k} }, $v;
|
||||
# otherwise we have to convert the existing
|
||||
# value to an array ref and attach our new
|
||||
# value at the end
|
||||
} else {
|
||||
$prefs{$k} = [ $prefs{$k}, $v ];
|
||||
}
|
||||
} else {
|
||||
$prefs{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
$duration = tv_interval($start, [gettimeofday]);
|
||||
print "--------- handlersocket results --------------\n";
|
||||
print "Duration: $duration sec for $runs interations\n";
|
||||
print "Req/sec: " . ($runs/$duration) . "\n";
|
||||
print "Dur/req: " . ($duration/$runs) . " sec\n";
|
||||
print "Result:\n";
|
||||
print Dumper \%prefs;
|
||||
|
||||
exit;
|
||||
|
||||
###### SQL version goes here: ######################
|
||||
|
||||
my $dsn = "DBI:mysql:database=kamailio;host=localhost;port=3306";
|
||||
my $dbh = DBI->connect($dsn, "kamailioro", "VvKthesty9cVziaVCidM");
|
||||
my $sth = $dbh->prepare("select attribute,value from usr_preferences where uuid=?") or die "Failed to prepare select statement\n";
|
||||
|
||||
for(my $i = 0; $i < $runs; ++$i) {
|
||||
%prefs = ();
|
||||
$sth->execute($uuid);
|
||||
while(my $ref = $sth->fetchrow_hashref()) {
|
||||
my $k = $ref->{'attribute'};
|
||||
my $v = $ref->{'value'};
|
||||
# we already had this key, so it's an attribute
|
||||
# with at least 2 values
|
||||
if(exists $prefs{$k}) {
|
||||
# if we already have >1 values, it's
|
||||
# converted to an array ref, so just
|
||||
# push our value at the end
|
||||
if(ref $prefs{$k} eq "ARRAY") {
|
||||
push @{ $prefs{$k} }, $v;
|
||||
# otherwise we have to convert the existing
|
||||
# value to an array ref and attach our new
|
||||
# value at the end
|
||||
} else {
|
||||
$prefs{$k} = [ $prefs{$k}, $v ];
|
||||
}
|
||||
} else {
|
||||
$prefs{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
$duration = tv_interval($start, [gettimeofday]);
|
||||
print "--------- sql results --------------\n";
|
||||
print "Duration: $duration sec for $runs interations\n";
|
||||
print "Req/sec: " . ($runs/$duration) . "\n";
|
||||
print "Dur/req: " . ($duration/$runs) . " sec\n";
|
||||
print "Result:\n";
|
||||
print Dumper \%prefs;
|
||||
@ -0,0 +1,81 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "hsclient-c-wrapper.h"
|
||||
|
||||
/*
|
||||
create table test (
|
||||
id int(11) not null auto_increment primary key,
|
||||
uuid varchar(255) not null default '',
|
||||
attribute varchar(255) not null default '',
|
||||
somekey varchar(255) not null default '',
|
||||
strvalue varchar(255) default null,
|
||||
intvalue int(11) not null default 0,
|
||||
key ua_idx(uuid,attribute)
|
||||
);
|
||||
insert into test values(NULL, 'update-key', 'testattr', 'someval', 'testval', 42);
|
||||
*/
|
||||
|
||||
static int update_prefs() {
|
||||
|
||||
char *host = "127.0.0.1";
|
||||
uint16_t port = 9997;
|
||||
char *dbname = "kamailio";
|
||||
char *table = "test";
|
||||
char *index = "ua_idx";
|
||||
char *indexcols = "uuid,attribute";
|
||||
char *secret = "writesecret";
|
||||
|
||||
hs_key *keys[] = {
|
||||
&((hs_key){ .name = "uuid", .op = "=", .val = "update-key" }),
|
||||
&((hs_key){ .name = "attribute", .op = "=", .val = "testattr" }),
|
||||
&((hs_key){ .name = "somekey", .op = "=", .val = "someval" }),
|
||||
NULL
|
||||
};
|
||||
|
||||
hs_val *vals[] = {
|
||||
&((hs_val){ .name = "strval", .type = HS_NULL }),
|
||||
&((hs_val){ .name = "intval", .val_int = 99, .type = HS_INT }),
|
||||
NULL
|
||||
};
|
||||
|
||||
hs_res res;
|
||||
hs_con con;
|
||||
|
||||
res = hs_init_connection(&con, host, port, dbname, table, index, indexcols, secret);
|
||||
if(!hs_success(res)) {
|
||||
printf("failed to init usr_pref connection: %s\n", hs_get_error(res));
|
||||
hs_free_result(res);
|
||||
return -1;
|
||||
}
|
||||
hs_free_result(res);
|
||||
|
||||
res = hs_prepare_query(con, keys);
|
||||
if(!hs_success(res)) {
|
||||
printf("failed to prepare query: %s\n", hs_get_error(res));
|
||||
hs_free_result(res);
|
||||
return -1;
|
||||
}
|
||||
hs_free_result(res);
|
||||
|
||||
#if 0
|
||||
for(i = 0; i < runs; ++i) {
|
||||
res = hs_update(con, vals);
|
||||
if(!hs_success(res)) {
|
||||
printf("failed to update prefs: %s\n", hs_get_error(res));
|
||||
hs_free_result(res);
|
||||
return -1;
|
||||
}
|
||||
hs_free_result(res);
|
||||
}
|
||||
#endif
|
||||
hs_close(con);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
update_prefs();
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in new issue