Browse code

db_redis: Implement db_redis generic db driver

This module implements a generic db driver for kamailio. It
requires a "schema" and "key" definition of "tables" and corresponding
keys for redis in the kamailio config file, otherwise it's supposed to
work with every module.

Implemented methods are query (w/o order-by), insert, update, delete.

Tested with usrloc and acc.

Andreas Granig authored on 07/02/2018 12:52:56
Showing 14 changed files
... ...
@@ -153,7 +153,7 @@ mod_list_jansson=jansson
153 153
 mod_list_jansson_event=janssonrpcc
154 154
 
155 155
 # - modules depending on redis library
156
-mod_list_redis=ndb_redis topos_redis
156
+mod_list_redis=db_redis ndb_redis topos_redis
157 157
 
158 158
 # - modules depending on mono library
159 159
 mod_list_mono=app_mono
160 160
new file mode 100644
... ...
@@ -0,0 +1,31 @@
1
+#
2
+# WARNING: do not run this directly, it should be run by the master Makefile
3
+
4
+include ../../Makefile.defs
5
+auto_gen=
6
+NAME=db_redis.so
7
+
8
+ifeq ($(CROSS_COMPILE),)
9
+HIREDIS_BUILDER = $(shell \
10
+	if pkg-config --exists hiredis; then \
11
+		echo 'pkg-config hiredis'; \
12
+	fi)
13
+endif
14
+
15
+ifeq ($(HIREDIS_BUILDER),)
16
+	HIREDISDEFS=-I$(LOCALBASE)/include
17
+	HIREDISLIBS=-L$(LOCALBASE)/lib -lhiredis
18
+else
19
+	HIREDISDEFS = $(shell $(HIREDIS_BUILDER) --cflags)
20
+	HIREDISLIBS = $(shell $(HIREDIS_BUILDER) --libs)
21
+endif
22
+
23
+DEFS+=$(HIREDISDEFS)
24
+LIBS=$(HIREDISLIBS)
25
+
26
+DEFS+=-DKAMAILIO_MOD_INTERFACE
27
+
28
+SERLIBPATH=../../lib
29
+SER_LIBS=$(SERLIBPATH)/srdb2/srdb2 $(SERLIBPATH)/srdb1/srdb1
30
+
31
+include ../../Makefile.modules
0 32
new file mode 100644
... ...
@@ -0,0 +1,199 @@
1
+DB_REDIS Module
2
+
3
+Andreas Granig
4
+
5
+   <agranig@sipwise.com>
6
+
7
+Edited by
8
+
9
+Andreas Granig
10
+
11
+   <agranig@sipwise.com>
12
+
13
+   Copyright © 2018 sipwise.com
14
+     __________________________________________________________________
15
+
16
+   Table of Contents
17
+
18
+   1. Admin Guide
19
+
20
+        1. Overview
21
+
22
+              1.1. Limitations
23
+
24
+        2. Dependencies
25
+
26
+              2.1. Kamailio Modules
27
+              2.2. Parameters
28
+
29
+                    2.2.1. schema (string)
30
+                    2.2.2. keys (string)
31
+
32
+              2.3. External Libraries or Applications
33
+
34
+        3. Usage
35
+
36
+   List of Examples
37
+
38
+   1.1. Setting schema module parameter
39
+   1.2. Setting keys module parameter
40
+   1.3. Usage
41
+
42
+Chapter 1. Admin Guide
43
+
44
+   Table of Contents
45
+
46
+   1. Overview
47
+
48
+        1.1. Limitations
49
+
50
+   2. Dependencies
51
+
52
+        2.1. Kamailio Modules
53
+        2.2. Parameters
54
+
55
+              2.2.1. schema (string)
56
+              2.2.2. keys (string)
57
+
58
+        2.3. External Libraries or Applications
59
+
60
+   3. Usage
61
+
62
+1. Overview
63
+
64
+   1.1. Limitations
65
+
66
+   This module provides a DB APIv1 connector for Redis server.
67
+
68
+   It can be used as a replacement for other database modules such as
69
+   db_mysql and db_postgres. Not all the specs of DB APIv1 are
70
+   implemented, thus the usage of this module might be restricted to
71
+   specific modules. Also, for proper performance, the module needs
72
+   particular configuration tailored to the using modules.
73
+
74
+   Since Redis does not provide a schema, a schema has to be defined as
75
+   module parameter "schema". The schema definition is composed of a
76
+   semi-column separated list of table definitions in format
77
+   <table-name>=<column-name>/<type>[<column-name>/<type> ...].
78
+
79
+   Example:
80
+        version=table_name/string,table_version/int;location=username/string,dom
81
+ain/string,contact/string,received/string,path/string,expires/timestamp,q/double
82
+,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/
83
+string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_i
84
+d/int,connection_id/int,keepalive/int,partition/int
85
+
86
+   Also since Redis is a key-value store with keys having to be unique,
87
+   tables and rows e.g. from MySQL can not be ported 1:1 to Redis. For
88
+   instance, usrloc relies on a key "username@domain", but it must not be
89
+   unique for being able to store multiple contacts per AoR. Thus,
90
+   db_redis supports mapping sets in a way for example for usrloc to have
91
+   a set with a key "username@domain", with its entries being unique keys
92
+   per contact being the ruid of a contact. Thus, one contact in usrloc
93
+   consists of a unique key "location:entry::example-ruid-1" being a hash
94
+   with the columns like username, domain, contact, path etc. In addition,
95
+   this unique key is stored in a set
96
+   "location:usrdom::exampleuser:exampledomain.org". When usrloc does a
97
+   lookup based on "username@domain", db_redis figures out via the
98
+   keys/values the query is constructed by usrloc to look for the final
99
+   entry key in the mapping set first, then querying the actual entries
100
+   from there, avoiding full table scans. For usrloc, the same holds true
101
+   for exipired contacts, requiring a different kind of mapping. There is
102
+   a certain balance of read performance vs. write performance to
103
+   consider, because inserts and deletes also have to maintain the
104
+   mappings, in favor of much faster selects. The mappings can be freely
105
+   defined, so even though other kamailio modules don't require a specific
106
+   mapping to be in place for proper performance, mappings could be
107
+   defined for external applications to read faster (for instance letting
108
+   the acc module also write mappings besides the actual records for
109
+   billing systems to correlate start and stop records faster).
110
+
111
+   The mappings can be freely defined in the "keys" module parameter. It
112
+   is composed of a semi-colon separated list of definitions in format
113
+   <table-name>=<entry>:<column-name>[&<map-name>:<column-name>,<column-na
114
+   me>...]
115
+
116
+   Example:
117
+        version=entry:table_name;location=entry:ruid&usrdom:username,domain&time
118
+r:partition,keepalive;acc=entry:callid,time_hires&cid:callid
119
+
120
+   Note that as of now, you have to have version information in your Redis
121
+   db, similar to your MySQL schema. To insert table versions (e.g. for
122
+   usrloc and acc), execute the following:
123
+        # redis-cli -h $host -n $dbnumber HMSET version:entry::location table_ve
124
+rsion 8
125
+        # redis-cli -h $host -n $dbnumber HMSET version:entry::acc table_version
126
+ 5
127
+
128
+   You can read more about Redis at: https://www.redis.io.
129
+
130
+1.1. Limitations
131
+
132
+     * This module has implemented the equivalent operations for INSERT,
133
+       UPDATE, DELETE and SELECT. The ORDER BY for SELECT is not
134
+       implemented. Raw query is not implemented inside this module, use
135
+       db_redis for sending any kind of command to a Redis server.
136
+
137
+2. Dependencies
138
+
139
+   2.1. Kamailio Modules
140
+   2.2. Parameters
141
+
142
+        2.2.1. schema (string)
143
+        2.2.2. keys (string)
144
+
145
+   2.3. External Libraries or Applications
146
+
147
+2.1. Kamailio Modules
148
+
149
+   The following modules must be loaded before this module:
150
+     * none.
151
+
152
+2.2. Parameters
153
+
154
+2.2.1. schema (string)
155
+
156
+   The schema of your tables.
157
+
158
+   Example 1.1. Setting schema module parameter
159
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;loca
160
+tion=username/string,domain/string,contact/string,received/string,path/string,ex
161
+pires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/in
162
+t,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,
163
+instance/string,server_id/int,connection_id/int,keepalive/int,partition/int")
164
+
165
+2.2.2. keys (string)
166
+
167
+   The lookup and mapping keys of your tables.
168
+
169
+   Example 1.2. Setting keys module parameter
170
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdo
171
+m:username,domain&timer:partition,keepalive")
172
+
173
+2.3. External Libraries or Applications
174
+
175
+   The following libraries or applications must be installed before
176
+   running Kamailio with this module loaded:
177
+     * hiredis - available at https://github.com/redis/hiredis
178
+
179
+3. Usage
180
+
181
+   Load the module and set the the DB URL for specific modules to:
182
+   redis://[username]@host:port/database. Username is optional. Database
183
+   must be a valid redis database number.
184
+
185
+   Example 1.3. Usage
186
+...
187
+loadmodule "db_redis.so"
188
+...
189
+#!define DBURL "redis://127.0.0.1:6379/5"
190
+...
191
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;loca
192
+tion=username/string,domain/string,contact/string,received/string,path/string,ex
193
+pires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/in
194
+t,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,
195
+instance/string,server_id/int,connection_id/int,keepalive/int,partition/int")
196
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdo
197
+m:username,domain&timer:partition,keepalive")
198
+modparam("usrloc", "db_url", DBURL)
199
+...
0 200
new file mode 100644
... ...
@@ -0,0 +1,105 @@
1
+/**
2
+ * Copyright (C) 2018 Andreas Granig (sipwise.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ *
20
+ */
21
+
22
+#define DB_REDIS_DEBUG
23
+
24
+#include <string.h>
25
+#include <stdlib.h>
26
+#include <stdio.h>
27
+
28
+#include "db_redis_mod.h"
29
+#include "redis_dbase.h"
30
+
31
+MODULE_VERSION
32
+
33
+str redis_keys = str_init("");
34
+str redis_schema = str_init("");
35
+
36
+static int db_redis_bind_api(db_func_t *dbb);
37
+static int mod_init(void);
38
+static void mod_destroy(void);
39
+
40
+static cmd_export_t cmds[] = {
41
+    {"db_bind_api",    (cmd_function)db_redis_bind_api,    0, 0, 0, 0},
42
+    {0, 0, 0, 0, 0, 0}
43
+};
44
+
45
+
46
+/*
47
+ * Exported parameters
48
+ */
49
+static param_export_t params[] = {
50
+    {"keys", PARAM_STR, &redis_keys },
51
+    {"schema", PARAM_STR, &redis_schema },
52
+    {0, 0, 0}
53
+};
54
+
55
+
56
+struct module_exports exports = {
57
+    "db_redis",
58
+    DEFAULT_DLFLAGS, /* dlopen flags */
59
+    cmds,
60
+    params,             /*  module parameters */
61
+    0,                  /* exported statistics */
62
+    0,                  /* exported MI functions */
63
+    0,                  /* exported pseudo-variables */
64
+    0,                  /* extra processes */
65
+    mod_init,           /* module initialization function */
66
+    0,                  /* response function*/
67
+    mod_destroy,        /* destroy function */
68
+    0                   /* per-child init function */
69
+};
70
+
71
+static int db_redis_bind_api(db_func_t *dbb) {
72
+    if(dbb==NULL)
73
+        return -1;
74
+
75
+    memset(dbb, 0, sizeof(db_func_t));
76
+
77
+    dbb->use_table        = db_redis_use_table;
78
+    dbb->init             = db_redis_init;
79
+    dbb->close            = db_redis_close;
80
+    dbb->query            = db_redis_query;
81
+    dbb->fetch_result     = 0; //db_redis_fetch_result;
82
+    dbb->raw_query        = 0; //db_redis_raw_query;
83
+    dbb->free_result      = db_redis_free_result;
84
+    dbb->insert           = db_redis_insert;
85
+    dbb->delete           = db_redis_delete;
86
+    dbb->update           = db_redis_update;
87
+    dbb->replace          = 0; //db_redis_replace;
88
+
89
+    return 0;
90
+}
91
+
92
+int mod_register(char *path, int *dlflags, void *p1, void *p2) {
93
+    if(db_api_init()<0)
94
+        return -1;
95
+    return 0;
96
+}
97
+
98
+static int mod_init(void) {
99
+    LM_DBG("module initializing\n");
100
+    return 0;
101
+}
102
+
103
+static void mod_destroy(void) {
104
+    LM_DBG("module destroying\n");
105
+}
0 106
\ No newline at end of file
1 107
new file mode 100644
... ...
@@ -0,0 +1,53 @@
1
+/*
2
+ * Copyright (C) 2018 Andreas Granig (sipwise.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ *
20
+ */
21
+
22
+
23
+#ifndef _DB_REDIS_MOD_H
24
+#define _DB_REDIS_MOD_H
25
+
26
+#include "../../lib/srdb1/db.h"
27
+#include "../../lib/srdb1/db_ut.h"
28
+#include "../../lib/srdb1/db_query.h"
29
+#include "../../lib/srdb1/db_pool.h"
30
+#include "../../lib/srdb1/db_id.h"
31
+#include "../../lib/srdb1/db_con.h"
32
+#include "../../lib/srdb1/db_res.h"
33
+#include "../../lib/srdb1/db_key.h"
34
+#include "../../lib/srdb1/db_op.h"
35
+#include "../../lib/srdb1/db_val.h"
36
+
37
+#include "../../core/mem/mem.h"
38
+
39
+#include "../../core/dprint.h"
40
+#include "../../core/sr_module.h"
41
+#include "../../core/str.h"
42
+#include "../../core/str_hash.h"
43
+#include "../../core/ut.h"
44
+
45
+#define REDIS_DIRECT_PREFIX "entry"
46
+#define REDIS_DIRECT_PREFIX_LEN 5
47
+
48
+#define REDIS_HT_SIZE 8
49
+
50
+extern str redis_keys;
51
+extern str redis_schema;
52
+
53
+#endif /* _DB_REDIS_MOD_H */
0 54
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+docs = db_redis.xml
2
+
3
+docbook_dir = ../../../docbook
4
+include $(docbook_dir)/Makefile.module
0 5
new file mode 100644
... ...
@@ -0,0 +1,37 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+
11
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
12
+    <bookinfo>
13
+	<title>DB_REDIS Module</title>
14
+	<productname class="trade">kamailio.org</productname>
15
+	<authorgroup>
16
+	    <author>
17
+		<firstname>Andreas</firstname>
18
+		<surname>Granig</surname>
19
+		<email>agranig@sipwise.com</email>
20
+	    </author>
21
+	    <editor>
22
+		<firstname>Andreas</firstname>
23
+		<surname>Granig</surname>
24
+		<email>agranig@sipwise.com</email>
25
+	    </editor>
26
+	</authorgroup>
27
+	<copyright>
28
+	    <year>2018</year>
29
+	    <holder>sipwise.com</holder>
30
+	</copyright>
31
+    </bookinfo>
32
+    <toc></toc>
33
+    
34
+    <xi:include href="db_redis_admin.xml"/>
35
+    
36
+    
37
+</book>
0 38
new file mode 100644
... ...
@@ -0,0 +1,190 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+<!-- Module User's Guide -->
11
+
12
+<chapter>
13
+	
14
+	<title>&adminguide;</title>
15
+	
16
+	<section>
17
+	<title>Overview</title>
18
+	<para>
19
+		This module provides a DB APIv1 connector for Redis server.
20
+	</para>
21
+	<para>
22
+	It can be used as a replacement for other database modules such as
23
+	db_mysql and db_postgres. Not all the specs of DB APIv1 are
24
+	implemented, thus the usage of this module might be restricted to
25
+	specific modules. Also, for proper performance, the module needs
26
+	particular configuration tailored to the using modules.
27
+	</para>
28
+	<para>
29
+	Since Redis does not provide a schema, a schema has to be defined as
30
+	module parameter "schema". The schema definition is composed of a
31
+	semi-column	separated list of table definitions in format
32
+	&lt;table-name&gt;=&lt;column-name&gt;/&lt;type&gt;[,&lt;column-name&gt;/&lt;type&gt; ...].
33
+	</para>
34
+	<para>
35
+	Example:
36
+	<programlisting format="linespecific">
37
+	version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int
38
+	</programlisting>
39
+	</para>
40
+	<para>
41
+	Also since Redis is a key-value store with keys having to be unique,
42
+	tables and rows e.g. from MySQL can not be ported 1:1 to Redis. For
43
+	instance, usrloc relies on a key "username@domain", but it must not be
44
+	unique for being able to store multiple contacts per AoR. Thus, db_redis
45
+	supports mapping sets in a way for example for usrloc to have a set with
46
+	a key "username@domain", with its entries being unique keys per contact being
47
+	the ruid of a contact. Thus, one contact in usrloc consists of a unique
48
+	key "location:entry::example-ruid-1" being a hash with the columns like
49
+	username, domain, contact, path etc. In addition, this unique key is stored
50
+	in a set "location:usrdom::exampleuser:exampledomain.org". When usrloc does
51
+	a lookup based on "username@domain", db_redis figures out via the keys/values
52
+	the query is constructed by usrloc to look for the final entry key in the
53
+	mapping set first, then querying the actual entries from there, avoiding full
54
+	table scans. For usrloc, the same holds true for expired contacts, requiring
55
+	a different kind of mapping. There is a certain balance of read performance
56
+	vs. write performance to consider, because inserts and deletes also have to
57
+	maintain the mappings, in favor of much faster selects. The mappings can be
58
+	freely defined, so even though other kamailio modules don't require a specific
59
+	mapping to be in place for proper performance, mappings could be defined
60
+	for external applications to read faster (for instance letting the acc module
61
+	also write mappings besides the actual records for billing systems to
62
+	correlate start and stop records faster).
63
+	</para>
64
+	<para>
65
+	The mappings can be freely defined in the "keys" module parameter. It is
66
+	composed of a semi-colon separated list of definitions in format
67
+	&lt;table-name&gt;=&lt;entry&gt;:&lt;column-name&gt;[&amp;&lt;map-name&gt;:&lt;column-name&gt;,&lt;column-name&gt;...].
68
+	Each table must at least have an "entry" key for db_redis to be able to store data.
69
+	</para>
70
+	<para>
71
+	Example:
72
+	<programlisting format="linespecific">
73
+	version=entry:table_name;location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive;acc=entry:callid,time_hires&amp;cid:callid
74
+	</programlisting>
75
+	</para>
76
+	<para>
77
+	Note that as of now, you have to have version information in your Redis db,
78
+	similar to your MySQL schema. To insert table versions (e.g. for usrloc and acc),
79
+	execute the following:
80
+	</para>
81
+	<programlisting format="linespecific">
82
+	# redis-cli -h $host -n $dbnumber HMSET version:entry::location table_version 8
83
+	# redis-cli -h $host -n $dbnumber HMSET version:entry::acc table_version 5
84
+	</programlisting>
85
+
86
+	<para>
87
+		You can read more about Redis at:
88
+		<ulink url="https://www.redis.io">https://www.redis.io</ulink>.
89
+	</para>
90
+
91
+	<section>
92
+	<title>Limitations</title>
93
+	<itemizedlist>
94
+	<listitem>
95
+	<para>
96
+		This module has implemented the equivalent operations for INSERT,
97
+		UPDATE, DELETE and SELECT. The ORDER BY for SELECT is not implemented.
98
+		Raw query is not implemented inside this module, use ndb_redis for sending any
99
+		kind of command to a Redis server.
100
+	</para>
101
+	</listitem>
102
+	</itemizedlist>
103
+	</section>
104
+	</section>
105
+
106
+	<section>
107
+	<title>Dependencies</title>
108
+	<section>
109
+		<title>&kamailio; Modules</title>
110
+		<para>
111
+		The following modules must be loaded before this module:
112
+			<itemizedlist>
113
+			<listitem>
114
+			<para>
115
+				<emphasis>none</emphasis>.
116
+			</para>
117
+			</listitem>
118
+			</itemizedlist>
119
+		</para>
120
+	</section>
121
+
122
+	<section>
123
+	<title>Parameters</title>
124
+	<section>
125
+		<title><varname>schema</varname> (string)</title>
126
+		<para>
127
+		The schema of your tables.
128
+		</para>
129
+		<example>
130
+		<title>Setting schema module parameter</title>
131
+		<programlisting format="linespecific">
132
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int")
133
+		</programlisting>
134
+		</example>
135
+	</section>
136
+	<section>
137
+		<title><varname>keys</varname> (string)</title>
138
+		<para>
139
+		The entry and mapping keys of your tables.
140
+		</para>
141
+		<example>
142
+		<title>Setting keys module parameter</title>
143
+		<programlisting format="linespecific">
144
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive")
145
+		</programlisting>
146
+		</example>
147
+	</section>
148
+	</section>
149
+
150
+	<section>
151
+		<title>External Libraries or Applications</title>
152
+		<para>
153
+		The following libraries or applications must be installed before running
154
+		&kamailio; with this module loaded:
155
+			<itemizedlist>
156
+			<listitem>
157
+			<para>
158
+				<emphasis>hiredis</emphasis> - available at
159
+				<ulink url="https://github.com/redis/hiredis">https://github.com/redis/hiredis</ulink>
160
+			</para>
161
+			</listitem>
162
+			</itemizedlist>
163
+		</para>
164
+	</section>
165
+	</section>
166
+
167
+	<section>
168
+	<title>Usage</title>
169
+		<para>
170
+			Load the module and set the the DB URL for specific modules to:
171
+			redis://[username]@host:port/database. Username is optional.
172
+			Database must be a valid redis database number.
173
+		</para>
174
+		<example>
175
+		<title>Usage</title>
176
+		<programlisting format="linespecific">
177
+...
178
+loadmodule "db_redis.so"
179
+...
180
+#!define DBURL "redis://127.0.0.1:6379/5"
181
+...
182
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int")
183
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive")
184
+modparam("usrloc", "db_url", DBURL)
185
+...
186
+</programlisting>
187
+		</example>
188
+	</section>
189
+</chapter>
190
+
0 191
new file mode 100644
... ...
@@ -0,0 +1,293 @@
1
+/*
2
+ * Copyright (C) 2018 Andreas Granig (sipwise.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ */
20
+
21
+#include <stdlib.h>
22
+
23
+#include "db_redis_mod.h"
24
+#include "redis_connection.h"
25
+#include "redis_table.h"
26
+
27
+int db_redis_connect(km_redis_con_t *con) {
28
+    struct timeval tv;
29
+    redisReply *reply;
30
+    int db;
31
+
32
+    tv.tv_sec = 1;
33
+    tv.tv_usec = 0;
34
+
35
+    db = atoi(con->id->database);
36
+    reply = NULL;
37
+
38
+    // TODO: introduce require_master mod-param and check if we're indeed master
39
+    // TODO: on carrier, if we have db fail-over, the currently connected
40
+    // redis server will become slave without dropping connections?
41
+
42
+    con->con = redisConnectWithTimeout(con->id->host, con->id->port, tv);
43
+
44
+    if (!con->con) {
45
+        LM_ERR("cannot open connection: %.*s\n", con->id->url.len, con->id->url.s);
46
+        goto err;
47
+    }
48
+    if (con->con->err) {
49
+        LM_ERR("cannot open connection to %.*s: %s\n", con->id->url.len, con->id->url.s,
50
+            con->con->errstr);
51
+        goto err;
52
+    }
53
+
54
+    if (con->id->password) {
55
+        reply = redisCommand(con->con, "AUTH %s", con->id->password);
56
+        if (!reply) {
57
+            LM_ERR("cannot authenticate connection %.*s: %s\n",
58
+                    con->id->url.len, con->id->url.s, con->con->errstr);
59
+            goto err;
60
+        }
61
+        if (reply->type == REDIS_REPLY_ERROR) {
62
+            LM_ERR("cannot authenticate connection %.*s: %s\n",
63
+                    con->id->url.len, con->id->url.s, reply->str);
64
+            goto err;
65
+        }
66
+        freeReplyObject(reply); reply = NULL;
67
+    }
68
+
69
+    reply = redisCommand(con->con, "PING");
70
+    if (!reply) {
71
+        LM_ERR("cannot ping server on connection %.*s: %s\n",
72
+                con->id->url.len, con->id->url.s, con->con->errstr);
73
+        goto err;
74
+    }
75
+    if (reply->type == REDIS_REPLY_ERROR) {
76
+        LM_ERR("cannot ping server on connection %.*s: %s\n",
77
+                con->id->url.len, con->id->url.s, reply->str);
78
+        goto err;
79
+    }
80
+    freeReplyObject(reply); reply = NULL;
81
+
82
+    reply = redisCommand(con->con, "SELECT %i", db);
83
+    if (!reply) {
84
+        LM_ERR("cannot select db on connection %.*s: %s\n",
85
+                con->id->url.len, con->id->url.s, con->con->errstr);
86
+        goto err;
87
+    }
88
+    if (reply->type == REDIS_REPLY_ERROR) {
89
+        LM_ERR("cannot select db on connection %.*s: %s\n",
90
+                con->id->url.len, con->id->url.s, reply->str);
91
+        goto err;
92
+    }
93
+    freeReplyObject(reply); reply = NULL;
94
+
95
+    return 0;
96
+
97
+err:
98
+    if (reply)
99
+        freeReplyObject(reply);
100
+    if (con->con) {
101
+        redisFree(con->con);
102
+        con->con = NULL;
103
+    }
104
+    return -1;
105
+}
106
+
107
+/*! \brief
108
+ * Create a new connection structure,
109
+ * open the redis connection and set reference count to 1
110
+ */
111
+km_redis_con_t* db_redis_new_connection(const struct db_id* id) {
112
+    km_redis_con_t *ptr = NULL;
113
+
114
+    if (!id) {
115
+        LM_ERR("invalid id parameter value\n");
116
+        return 0;
117
+    }
118
+
119
+    ptr = (km_redis_con_t*)pkg_malloc(sizeof(km_redis_con_t));
120
+    if (!ptr) {
121
+        LM_ERR("no private memory left\n");
122
+        return 0;
123
+    }
124
+    memset(ptr, 0, sizeof(km_redis_con_t));
125
+    ptr->id = (struct db_id*)id;
126
+
127
+    /*
128
+    LM_DBG("trying to initialize connection to '%.*s' with schema '%.*s' and keys '%.*s'\n",
129
+            id->url.len, id->url.s,
130
+            redis_schema.len, redis_schema.s,
131
+            redis_keys.len, redis_keys.s);
132
+    */
133
+    LM_DBG("trying to initialize connection to '%.*s'\n",
134
+            id->url.len, id->url.s);
135
+    if (db_redis_parse_schema(ptr) != 0) {
136
+        LM_ERR("failed to parse 'schema' module parameter\n");
137
+        goto err;
138
+    }
139
+    if (db_redis_parse_keys(ptr) != 0) {
140
+        LM_ERR("failed to parse 'keys' module parameter\n");
141
+        goto err;
142
+    }
143
+
144
+    db_redis_print_all_tables(ptr);
145
+
146
+    ptr->ref = 1;
147
+    ptr->append_counter = 0;
148
+
149
+    if (db_redis_connect(ptr) != 0) {
150
+        LM_ERR("Failed to connect to redis db\n");
151
+        goto err;
152
+    }
153
+
154
+    LM_DBG("connection opened to %.*s\n", id->url.len, id->url.s);
155
+
156
+    return ptr;
157
+
158
+ err:
159
+    if (ptr) {
160
+        if (ptr->con) {
161
+            redisFree(ptr->con);
162
+        }
163
+        pkg_free(ptr);
164
+    }
165
+    return 0;
166
+}
167
+
168
+
169
+/*! \brief
170
+ * Close the connection and release memory
171
+ */
172
+void db_redis_free_connection(struct pool_con* con) {
173
+    km_redis_con_t * _c;
174
+
175
+    LM_DBG("freeing db_redis connection\n");
176
+
177
+    if (!con) return;
178
+
179
+    _c = (km_redis_con_t*) con;
180
+
181
+    if (_c->id) free_db_id(_c->id);
182
+    if (_c->con) {
183
+        redisFree(_c->con);
184
+    }
185
+
186
+    db_redis_free_tables(_c);
187
+    pkg_free(_c);
188
+}
189
+
190
+
191
+static void print_query(redis_key_t *query) {
192
+    LM_DBG("Query dump:\n");
193
+    for (redis_key_t *k = query; k; k = k->next) {
194
+        LM_DBG("  %s\n", k->key.s);
195
+    }
196
+}
197
+
198
+void *db_redis_command_argv(km_redis_con_t *con, redis_key_t *query) {
199
+    char **argv = NULL;
200
+    int argc;
201
+
202
+    print_query(query);
203
+
204
+    argc = db_redis_key_list2arr(query, &argv);
205
+    if (argc < 0) {
206
+        LM_ERR("Failed to allocate memory for query array\n");
207
+        return NULL;
208
+    }
209
+    LM_DBG("query has %d args\n", argc);
210
+
211
+    redisReply *reply = redisCommandArgv(con->con, argc, (const char**)argv, NULL);
212
+    if (con->con->err == REDIS_ERR_EOF &&
213
+        strcmp(con->con->errstr,"Server closed the connection") == 0) {
214
+
215
+        if (db_redis_connect(con) != 0) {
216
+            LM_ERR("Failed to reconnect to redis db\n");
217
+            pkg_free(argv);
218
+            return NULL;
219
+        }
220
+        reply = redisCommandArgv(con->con, argc, (const char**)argv, NULL);
221
+    }
222
+    pkg_free(argv);
223
+    return reply;
224
+}
225
+
226
+int db_redis_append_command_argv(km_redis_con_t *con, redis_key_t *query) {
227
+    char **argv = NULL;
228
+    int ret, argc;
229
+
230
+    print_query(query);
231
+
232
+    argc = db_redis_key_list2arr(query, &argv);
233
+    if (argc < 0) {
234
+        LM_ERR("Failed to allocate memory for query array\n");
235
+        return -1;
236
+    }
237
+    LM_DBG("query has %d args\n", argc);
238
+
239
+    ret = redisAppendCommandArgv(con->con, argc, (const char**)argv, NULL);
240
+    if (con->con->err == REDIS_ERR_EOF &&
241
+        strcmp(con->con->errstr,"Server closed the connection") == 0) {
242
+
243
+        if (db_redis_connect(con) != 0) {
244
+            LM_ERR("Failed to reconnect to redis db\n");
245
+            pkg_free(argv);
246
+            return ret;
247
+        }
248
+        ret = redisAppendCommandArgv(con->con, argc, (const char**)argv, NULL);
249
+    }
250
+    pkg_free(argv);
251
+    if (!con->con->err) {
252
+        con->append_counter++;
253
+    }
254
+    return ret;
255
+}
256
+
257
+int db_redis_get_reply(km_redis_con_t *con, void **reply) {
258
+    int ret;
259
+
260
+    *reply = NULL;
261
+    ret = redisGetReply(con->con, reply);
262
+    if (con->con->err == REDIS_ERR_EOF &&
263
+        strcmp(con->con->errstr,"Server closed the connection") == 0) {
264
+
265
+        if (db_redis_connect(con) != 0) {
266
+            LM_ERR("Failed to reconnect to redis db\n");
267
+            return ret;
268
+        }
269
+        ret = redisGetReply(con->con, reply);
270
+    }
271
+    if (!con->con->err)
272
+        con->append_counter--;
273
+    return ret;
274
+}
275
+
276
+void db_redis_free_reply(redisReply **reply) {
277
+    if (reply && *reply) {
278
+        freeReplyObject(*reply);
279
+        *reply = NULL;
280
+    }
281
+}
282
+
283
+void db_redis_consume_replies(km_redis_con_t *con) {
284
+    redisReply *reply = NULL;
285
+    while (con->append_counter > 0 && !con->con->err) {
286
+        LM_DBG("consuming outstanding reply %u", con->append_counter);
287
+        db_redis_get_reply(con, (void**)&reply);
288
+        if (reply) {
289
+            freeReplyObject(reply);
290
+            reply = NULL;
291
+        }
292
+    }
293
+}
0 294
new file mode 100644
... ...
@@ -0,0 +1,73 @@
1
+/**
2
+ * Copyright (C) 2018 Andreas Granig (sipwise.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ *
20
+ */
21
+
22
+
23
+#ifndef _REDIS_CONNECTION_H_
24
+#define _REDIS_CONNECTION_H_
25
+
26
+#include <hiredis.h>
27
+
28
+#include "db_redis_mod.h"
29
+
30
+#define db_redis_check_reply(con, reply, err) do { \
31
+    if (!(reply) && !(con)->con) { \
32
+        LM_ERR("Failed to fetch type entry: no connection to server\n"); \
33
+        goto err; \
34
+    } \
35
+    if (!(reply)) { \
36
+        LM_ERR("Failed to fetch type entry: %s\n", \
37
+                (con)->con->errstr); \
38
+        goto err; \
39
+    } \
40
+    if ((reply)->type == REDIS_REPLY_ERROR) { \
41
+        LM_ERR("Failed to fetch type entry: %s\n", \
42
+                (reply)->str); \
43
+        goto err; \
44
+    } \
45
+} while(0);
46
+
47
+typedef struct km_redis_con {
48
+    struct db_id* id;
49
+    unsigned int ref;
50
+    struct pool_con* next;
51
+
52
+    redisContext *con;
53
+    unsigned int append_counter;
54
+    struct str_hash_table tables;
55
+} km_redis_con_t;
56
+
57
+
58
+struct redis_key;
59
+typedef struct redis_key redis_key_t;
60
+
61
+#define REDIS_CON(db_con)  ((km_redis_con_t*)((db_con)->tail))
62
+
63
+km_redis_con_t* db_redis_new_connection(const struct db_id* id);
64
+void db_redis_free_connection(struct pool_con* con);
65
+
66
+int db_redis_connect(km_redis_con_t *con);
67
+void *db_redis_command_argv(km_redis_con_t *con, redis_key_t *query);
68
+int db_redis_append_command_argv(km_redis_con_t *con, redis_key_t *query);
69
+int db_redis_get_reply(km_redis_con_t *con, void **reply);
70
+void db_redis_consume_replies(km_redis_con_t *con);
71
+void db_redis_free_reply(redisReply **reply);
72
+
73
+#endif /* _REDIS_CONNECTION_H_ */
0 74
new file mode 100644
... ...
@@ -0,0 +1,2058 @@
1
+/*
2
+ * Copyright (C) 2018 Andreas Granig (sipwise.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * Kamailio is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ * Kamailio is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
+ *
20
+ */
21
+
22
+#include <stdlib.h>
23
+#include <time.h>
24
+
25
+#include "db_redis_mod.h"
26
+#include "redis_connection.h"
27
+#include "redis_dbase.h"
28
+#include "redis_table.h"
29
+
30
+static void db_redis_dump_reply(redisReply *reply) {
31
+    if (reply->type == REDIS_REPLY_STRING) {
32
+        LM_DBG("%s\n", reply->str);
33
+    } else if (reply->type == REDIS_REPLY_INTEGER) {
34
+        LM_DBG("%lld\n", reply->integer);
35
+    } else if (reply->type == REDIS_REPLY_NIL) {
36
+        LM_DBG("<null>\n");
37
+    } else if (reply->type == REDIS_REPLY_ARRAY) {
38
+        LM_DBG("printing %lu elements in array reply\n", reply->elements);
39
+        for(int i = 0; i < reply->elements; ++i) {
40
+            db_redis_dump_reply(reply->element[i]);
41
+        }
42
+    } else {
43
+        LM_DBG("not printing invalid reply type\n");
44
+    }
45
+}
46
+
47
+// TODO: utilize auto-expiry? on insert/update, also update expire value
48
+// of mappings
49
+
50
+/*
51
+ * Initialize database module
52
+ * No function should be called before this
53
+ */
54
+db1_con_t* db_redis_init(const str* _url) {
55
+    return db_do_init(_url, (void *)db_redis_new_connection);
56
+}
57
+
58
+/*
59
+ * Shut down database module
60
+ * No function should be called after this
61
+ */
62
+void db_redis_close(db1_con_t* _h) {
63
+    LM_DBG("closing redis db connection\n");
64
+    db_do_close(_h, db_redis_free_connection);
65
+}
66
+
67
+static int db_redis_val2str(const db_val_t *v, str *_str) {
68
+    const char *s;
69
+    const str *tmpstr;
70
+    int vtype = VAL_TYPE(v);
71
+    _str->s = NULL;
72
+    _str->len = 32; // default for numbers
73
+
74
+    if (VAL_NULL(v)) {
75
+        LM_DBG("converting <null> value to str\n");
76
+        _str->len = 0;
77
+        return 0;
78
+    }
79
+
80
+    switch (vtype) {
81
+        case DB1_INT:
82
+            LM_DBG("converting int value %d to str\n", VAL_INT(v));
83
+            _str->s = (char*)pkg_malloc(_str->len);
84
+            if (!_str->s) goto memerr;
85
+            snprintf(_str->s, _str->len, "%d", VAL_INT(v));
86
+            _str->len = strlen(_str->s);
87
+            break;
88
+        case DB1_BIGINT:
89
+            LM_DBG("converting bigint value %lld to str\n", VAL_BIGINT(v));
90
+            _str->s = (char*)pkg_malloc(_str->len);
91
+            if (!_str->s) goto memerr;
92
+            snprintf(_str->s, _str->len, "%lld", VAL_BIGINT(v));
93
+            _str->len = strlen(_str->s);
94
+            break;
95
+        case DB1_STRING:
96
+            s = VAL_STRING(v);
97
+            _str->len = strlen(s);
98
+            LM_DBG("converting string value '%s' with len %d to str\n", s, _str->len);
99
+            _str->s = (char*)pkg_malloc(_str->len + 1);
100
+            if (!_str->s) goto memerr;
101
+            //memcpy(_str->s, s, _str->len);
102
+            //_str->s[_str->len] = '\0';
103
+            memset(_str->s, 0, _str->len + 1);
104
+            strncpy(_str->s, s, _str->len);
105
+            break;
106
+        case DB1_STR:
107
+            tmpstr = &(VAL_STR(v));
108
+            LM_DBG("converting str value '%.*s' with len %d to str\n", tmpstr->len, tmpstr->s, tmpstr->len);
109
+            // copy manually to add 0 termination
110
+            _str->s = (char*)pkg_malloc(tmpstr->len + 1);
111
+            if (!_str->s) goto memerr;
112
+            _str->len = tmpstr->len;
113
+            memcpy(_str->s, tmpstr->s, _str->len);
114
+            _str->s[_str->len] = '\0';
115
+            break;
116
+        case DB1_DATETIME:
117
+            LM_DBG("converting datetime value %ld to str\n", VAL_TIME(v));
118
+            _str->s = (char*)pkg_malloc(_str->len);
119
+            if (!_str->s) goto memerr;
120
+            strftime(_str->s, _str->len, "%Y-%m-%d %H:%M:%S", localtime(&(VAL_TIME(v))));
121
+            _str->len = strlen(_str->s);
122
+            break;
123
+        case DB1_DOUBLE:
124
+            LM_DBG("converting double value %f to str\n", VAL_DOUBLE(v));
125
+            _str->s = (char*)pkg_malloc(_str->len);
126
+            if (!_str->s) goto memerr;
127
+            snprintf(_str->s, _str->len, "%.6f", VAL_DOUBLE(v));
128
+            _str->len = strlen(_str->s);
129
+            break;
130
+        case DB1_BITMAP:
131
+            LM_DBG("converting bitmap value %u to str\n", VAL_BITMAP(v));
132
+            _str->s = (char*)pkg_malloc(_str->len);
133
+            if (!_str->s) goto memerr;
134
+            snprintf(_str->s, _str->len, "%u", VAL_BITMAP(v));
135
+            _str->len = strlen(_str->s);
136
+            break;
137
+        case DB1_BLOB:
138
+        default:
139
+            LM_ERR("Unsupported val type %d\n", vtype);
140
+            goto err;
141
+    }
142
+
143
+    return 0;
144
+
145
+memerr:
146
+    LM_ERR("Failed to allocate memory to convert value to string\n");
147
+err:
148
+    return -1;
149
+}
150
+
151
+static int db_redis_build_entry_manual_keys(redis_table_t *table, const db_key_t *_k, const db_val_t *_v, const int _n, int **manual_keys, int *manual_key_count) {
152
+
153
+    // TODO: we also put keys here which are already part of type mapping!
154
+    // there must be removed for performance reasons
155
+
156
+    redis_key_t *key = NULL;
157
+
158
+    *manual_keys = (int*)pkg_malloc(_n * sizeof(int));
159
+    if (! *manual_keys) {
160
+        LM_ERR("Failed to allocate memory for manual key indices\n");
161
+        goto err;
162
+    }
163
+    memset(*manual_keys, 0, _n * sizeof(int));
164
+    *manual_key_count = 0;
165
+
166
+    for (key = table->entry_keys; key; key = key->next) {
167
+        int subkey_found = 0;
168
+        LM_DBG("checking for existence of entry key '%.*s' in query to get manual key\n",
169
+                key->key.len, key->key.s);
170
+        for (int i = 0; i < _n; ++i) {
171
+            const db_key_t k = _k[i];
172
+            if (!str_strcmp(&key->key, (str*)k)) {
173
+                LM_DBG("found key in entry key\n");
174
+                subkey_found = 1;
175
+                break;
176
+            } else {
177
+                (*manual_keys)[*manual_key_count] = i;
178
+                (*manual_key_count)++;
179
+            }
180
+        }
181
+        if (!subkey_found) {
182
+            break;
183
+        }
184
+    }
185
+    return 0;
186
+
187
+err:
188
+    if (*manual_keys) {
189
+        pkg_free(*manual_keys);
190
+        *manual_keys = NULL;
191
+    }
192
+    return -1;
193
+}
194
+
195
+static int db_redis_find_query_key(redis_key_t *key, const str *table_name, str *type_name, const db_key_t *_k, const db_val_t *_v, const int _n, str *key_name, int *key_found) {
196
+
197
+    unsigned int len;
198
+    str val = {NULL, 0};
199
+
200
+    *key_found = 1;
201
+    key_name->len = 0;
202
+    key_name->s = NULL;
203
+
204
+    for (; key; key = key->next) {
205
+        int subkey_found = 0;
206
+        LM_DBG("checking for existence of entry key '%.*s' in query\n",
207
+                key->key.len, key->key.s);
208
+        for (int i = 0; i < _n; ++i) {
209
+            const db_key_t k = _k[i];
210
+            const db_val_t v = _v[i];
211
+
212
+            if (VAL_NULL(&v)) {
213
+                LM_DBG("Skipping null value for given key '%.*s'\n",
214
+                        k->len, k->s);
215
+                break;
216
+            } else if (!str_strcmp(&key->key, (str*)k)) {
217
+                LM_DBG("found key in entry key\n");
218
+                if (db_redis_val2str(&v, &val) != 0) goto err;
219
+                if (val.s == NULL) {
220
+                    LM_DBG("key value in entry key is null, skip key\n");
221
+                    subkey_found = 0;
222
+                    break;
223
+                }
224
+                if (!key_name->len) {
225
+                    // <table_name>:<type>::<val>
226
+                    len = table_name->len + 1 + type_name->len + 2 + val.len + 1; //snprintf writes term 0 char
227
+                    key_name->s = (char*)pkg_malloc(len);
228
+                    if (!key_name->s) {
229
+                        LM_ERR("Failed to allocate key memory\n");
230
+                        goto err;
231
+                    }
232
+                    snprintf(key_name->s, len, "%.*s:%.*s::%.*s",
233
+                            table_name->len, table_name->s,
234
+                            type_name->len, type_name->s,
235
+                            val.len, val.s);
236
+                    key_name->len = len-1; // subtract the term 0 char
237
+
238
+                } else {
239
+                    // :<val>
240
+                    key_name->s = (char*)pkg_realloc(key_name->s, key_name->len + val.len + 2);
241
+                    if (!key_name->s) {
242
+                        LM_ERR("Failed to allocate key memory\n");
243
+                        goto err;
244
+                    }
245
+                    snprintf(key_name->s + key_name->len, 1 + val.len + 1, ":%.*s",
246
+                            val.len, val.s);
247
+                    key_name->len += (1 + val.len);
248
+                }
249
+                LM_DBG("entry key so far is '%.*s'\n", key_name->len, key_name->s);
250
+                subkey_found = 1;
251
+                pkg_free(val.s);
252
+                val.s = NULL;
253
+                break;
254
+            }
255
+        }
256
+        if (!subkey_found) {
257
+            LM_DBG("key '%.*s' for type '%.*s' not found, unable to use this type\n",
258
+                    key->key.len, key->key.s, type_name->len, type_name->s);
259
+            if (key_name->s) {
260
+                pkg_free(key_name->s);
261
+                key_name->s = NULL;
262
+                key_name->len = 0;
263
+            }
264
+            *key_found = 0;
265
+            break;
266
+        }
267
+    }
268
+
269
+    return 0;
270
+
271
+err:
272
+    if (val.s)
273
+        pkg_free(val.s);
274
+    if(key_name->s) {
275
+        pkg_free(key_name->s);
276
+        key_name->s = NULL;
277
+        key_name->len = 0;
278
+    }
279
+    return -1;
280
+}
281
+
282
+static int db_redis_build_entry_keys(km_redis_con_t *con, const str *table_name,
283
+        const db_key_t *_k, const db_val_t *_v, const int _n,
284
+        redis_key_t **keys, int *keys_count) {
285
+
286
+    struct str_hash_entry *table_e;
287
+    redis_table_t *table;
288
+    redis_key_t *key;
289
+    int key_found;
290
+    str type_name = str_init("entry");
291
+    str keyname = {NULL, 0};
292
+
293
+    LM_DBG("build entry keys\n");
294
+
295
+    table_e = str_hash_get(&con->tables, table_name->s, table_name->len);
296
+    if (!table_e) {
297
+        LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!",
298
+                table_name->len, table_name->s);
299
+        return -1;
300
+    }
301
+    table = (redis_table_t*)table_e->u.p;
302
+    key = table->entry_keys;
303
+    if (db_redis_find_query_key(key, table_name, &type_name, _k, _v, _n, &keyname, &key_found) != 0) {
304
+        goto err;
305
+    }
306
+    if (key_found) {
307
+        db_redis_key_add_str(keys, &keyname);
308
+
309
+        if (db_redis_key_add_str(keys, &keyname) != 0) {
310
+            LM_ERR("Failed to add key string\n");
311
+            goto err;
312
+        }
313
+        LM_DBG("found suitable entry key '%.*s' for query\n",
314
+                (*keys)->key.len, (*keys)->key.s);
315
+        *keys_count = 1;
316
+        pkg_free(keyname.s);
317
+    } else {
318
+        LM_ERR("Failed to create direct entry key, no matching key definition\n");
319
+        goto err;
320
+    }
321
+
322
+    return 0;
323
+
324
+err:
325
+    db_redis_key_free(keys);
326
+    if (keyname.s)
327
+        pkg_free(keyname.s);
328
+    return -1;
329
+}
330
+
331
+static int db_redis_get_keys_for_all_types(km_redis_con_t *con, const str *table_name,
332
+        redis_key_t **keys, int *keys_count) {
333
+
334
+    struct str_hash_entry *table_e;
335
+    redis_table_t *table;
336
+    redis_type_t *type;
337
+    redis_key_t *key;
338
+
339
+    *keys = NULL;
340
+    *keys_count = 0;
341
+
342
+    table_e = str_hash_get(&con->tables, table_name->s, table_name->len);
343
+    if (!table_e) {
344
+        LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!",
345
+                table_name->len, table_name->s);
346
+        return -1;
347
+    }
348
+    table = (redis_table_t*)table_e->u.p;
349
+
350
+    for (type = table->types; type; type = type->next) {
351
+        for (key = type->keys; key; key = key->next) {
352
+            if (db_redis_key_add_str(keys, &key->key) != 0) {
353
+                LM_ERR("Failed to add key string\n");
354
+                goto err;
355
+            }
356
+            (*keys_count)++;
357
+        }
358
+    }
359
+
360
+    return 0;
361
+
362
+err:
363
+    db_redis_key_free(keys);
364
+    return -1;
365
+}
366
+
367
+static int db_redis_build_type_keys(km_redis_con_t *con, const str *table_name,
368
+        const db_key_t *_k, const db_val_t *_v, const int _n,
369
+        redis_key_t **keys, int *keys_count) {
370
+
371
+    struct str_hash_entry *table_e;
372
+    redis_table_t *table;
373
+    redis_type_t *type;
374
+    redis_key_t *key;
375
+
376
+    *keys = NULL;
377
+    *keys_count = 0;
378
+
379
+    LM_DBG("build type keys\n");
380
+
381
+    table_e = str_hash_get(&con->tables, table_name->s, table_name->len);
382
+    if (!table_e) {
383
+        LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!",
384
+                table_name->len, table_name->s);
385
+        return -1;
386
+    }
387