diff --git a/db/schema/opensips-trie.xml b/db/schema/opensips-trie.xml
new file mode 100644
index 00000000000..fc55d355500
--- /dev/null
+++ b/db/schema/opensips-trie.xml
@@ -0,0 +1,14 @@
+
+
+%entities;
+
+]>
+
+
+ Trie
+
+
+
diff --git a/db/schema/trie_partitions.xml b/db/schema/trie_partitions.xml
new file mode 100644
index 00000000000..14b317c5fb5
--- /dev/null
+++ b/db/schema/trie_partitions.xml
@@ -0,0 +1,56 @@
+
+
+%entities;
+
+]>
+
+
+ dr_partitions
+ 1
+ &MYSQL_TABLE_TYPE;
+
+ This table is used by the Trie module to store
+ information about the partitions used in the script (url to database and table name).
+ More information can be found at: &OPENSIPS_MOD_DOC;trie.html.
+
+
+
+
+ id
+ unsigned int
+ &table_id_len;
+
+
+
+ int,auto
+ Partition unique ID
+
+
+
+
+ partition_name
+ string
+ 255
+ The name of the partition.
+
+
+
+
+ db_url
+ string
+ 255
+ The url to the database containing the tables: dr_rules, dr_groups,
+ dr_carriers and dr_gateways
+
+
+
+ trie_table
+ string
+ 255
+
+ The name of the trie_rules table in the given database (for the given partition).
+
+
diff --git a/db/schema/trie_table.xml b/db/schema/trie_table.xml
new file mode 100644
index 00000000000..da9e87b8774
--- /dev/null
+++ b/db/schema/trie_table.xml
@@ -0,0 +1,57 @@
+
+
+%entities;
+
+]>
+
+
+ trie_table
+ 1
+ &MYSQL_TABLE_TYPE;
+
+ This table is used by the Trie module in order to build the trie that it caches
+ More information can be found at: &OPENSIPS_MOD_DOC;trie.html.
+
+
+
+
+ ruleid
+ unsigned int
+ &table_id_len;
+
+
+
+ int,auto
+ Rule unique ID
+
+
+
+
+ prefix
+ string
+ 64
+ prefix to be cached
+
+
+
+ attrs
+ string
+ 255
+
+
+ Generic string describing RULE attributes - this string is
+ to be interpreted from the script
+
+
+
+ priority
+ int
+ 11
+ 1
+ 1 if the rule is enabled, 0 if the rule is disabled.
+
+
+
diff --git a/modules/trie/Makefile b/modules/trie/Makefile
new file mode 100644
index 00000000000..24639228aaa
--- /dev/null
+++ b/modules/trie/Makefile
@@ -0,0 +1,10 @@
+# $Id$
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=trie.so
+LIBS=
+
+include ../../Makefile.modules
diff --git a/modules/trie/README b/modules/trie/README
new file mode 100644
index 00000000000..7f80b44c9a5
--- /dev/null
+++ b/modules/trie/README
@@ -0,0 +1,277 @@
+Trie Module
+ __________________________________________________________
+
+ Table of Contents
+
+ 1. Admin Guide
+
+ 1.1. Overview
+
+ 1.1.1. Introduction
+
+ 1.2. Dependencies
+
+ 1.2.1. OpenSIPS Modules
+ 1.2.2. External Libraries or Applications
+
+ 1.3. Exported Parameters
+
+ 1.3.1. trie_table(str)
+ 1.3.2. no_concurrent_reload (int)
+ 1.3.3. use_partitions (int)
+ 1.3.4. db_partitions_url (str)
+ 1.3.5. db_partitions_table (str)
+ 1.3.6. extra_prefix_chars (str)
+
+ 1.4. Exported Functions
+
+ 1.4.1. trie_search(number, [flags],
+ [trie_attrs_pvar], [match_prefix_pvar],
+ [partition])
+
+ 1.5. Exported MI Functions
+
+ 1.5.1. trie_reload
+ 1.5.2. trie_reload_status
+ 1.5.3. trie_search
+ 1.5.4. trie_number_delete
+ 1.5.5. trie_number_upsert
+
+ 1.6. Installation
+
+ List of Examples
+
+ 1.1. Set trie_table parameter
+ 1.2. Set no_concurrent_reload parameter
+ 1.3. Set use_partitions parameter
+ 1.4. Set db_partitions_url parameter
+ 1.5. Set db_partitions_table parameter
+ 1.6. Set extra_prefix_chars parameter
+ 1.7. trie_search usage
+ 1.8. trie_reload_status usage when use_partitions is 0
+
+Chapter 1. Admin Guide
+
+1.1. Overview
+
+1.1.1. Introduction
+
+ Trie is a module for efficiently caching and lookup of a set of
+ prefixes ( stored in a trie data structure )
+
+1.2. Dependencies
+
+1.2.1. OpenSIPS Modules
+
+ The following modules must be loaded before this module:
+ * a database module.
+
+1.2.2. External Libraries or Applications
+
+ * none.
+
+1.3. Exported Parameters
+
+1.3.1. trie_table(str)
+
+ The name of the db table storing prefix rules.
+
+ Default value is “trie_table”.
+
+ Example 1.1. Set trie_table parameter
+...
+modparam("drouting", "trie_table", "my_prefix_table")
+...
+
+1.3.2. no_concurrent_reload (int)
+
+ If enabled, the module will not allow do run multiple
+ trie_reload MI commands in parallel (with overlapping) Any new
+ reload will be rejected (and discarded) while an existing
+ reload is in progress.
+
+ If you have a large routing set (millions of rules/prefixes),
+ you should consider disabling concurrent reload as they will
+ exhaust the shared memory (by reloading into memory, in the
+ same time, multiple instances of routing data).
+
+ Default value is “0 (disabled)”.
+
+ Example 1.2. Set no_concurrent_reload parameter
+...
+# do not allow parallel reload operations
+modparam("trie", "no_concurrent_reload", 1)
+...
+
+1.3.3. use_partitions (int)
+
+ Flag to configure whether to use partitions for tries. If this
+ flag is set then the db_partitions_url and db_partitions_table
+ variables become mandatory.
+
+ Default value is “0”.
+
+ Example 1.3. Set use_partitions parameter
+...
+modparam("trie", "use_partitions", 1)
+...
+
+1.3.4. db_partitions_url (str)
+
+ The url to the database containing partition-specific
+ information.The use_partitions parameter must be set to 1.
+
+ Default value is “"NULL"”.
+
+ Example 1.4. Set db_partitions_url parameter
+...
+modparam("trie", "db_partitions_url", "mysql://user:password@localhost/o
+pensips_partitions")
+...
+
+1.3.5. db_partitions_table (str)
+
+ The name of the table containing partition definitions. To be
+ used with use_partitions and db_partitions_url.
+
+ Default value is “trie_partitions”.
+
+ Example 1.5. Set db_partitions_table parameter
+...
+modparam("trie", "db_partitions_table", "trie_partition_defs")
+...
+
+1.3.6. extra_prefix_chars (str)
+
+ List of ASCII (0-127) characters to be additionally accepted in
+ the prefixes. By default only '0' - '9' chars (digits) are
+ accepted.
+
+ Default value is “NULL”.
+
+ Example 1.6. Set extra_prefix_chars parameter
+...
+modparam("trie", "extra_prefix_chars", "#-%")
+...
+
+1.4. Exported Functions
+
+1.4.1. trie_search(number, [flags], [trie_attrs_pvar],
+[match_prefix_pvar], [partition])
+
+ Function to search for an entry ( number ) in a trie.
+
+ This function can be used from all routes.
+
+ If you set use_partitions to 1 the partition last parameter
+ becomes mandatory.
+
+ All parameters are optional. Any of them may be ignored,
+ provided the necessary separation marks "," are properly
+ placed.
+ * number (str) - number to be searched in the trie
+ * flags (string, optional) - a list of letter-like flags for
+ controlling the routing behavior. Possible flags are:
+ + L - Do strict length matching over the prefix -
+ actually the trie engine will do full number matching
+ and not prefix matching anymore.
+ * trie_attrs_pvar (var, optional) - a writable variable which
+ will be populated with the attributes of the matched trie
+ rule.
+ * match_prefix_pvar (var, optional) - a writable variable
+ which will be the actual prefix matched in the trie.
+ * partition (string, optional) - the name of the trie
+ partition to be used. This parameter is to be defined ONLY
+ if the "use_partition" module parameter is turned on.
+
+ Example 1.7. trie_search usage
+...
+if (trie_search("$rU","L",$avp(code_attrs),,"my_partition")) {
+ # we found it in the trie, it's a match
+ xlog("We found $rU in the trie with attrs $avp(code_attrs) \n");
+}
+
+1.5. Exported MI Functions
+
+1.5.1. trie_reload
+
+ Command to reload trie rules from database.
+ * if use_partition is set to 0 - all routing rules will be
+ reloaded.
+ * if use_partition is set to 1, the parameters are:
+ + partition_name (optional) - if not provided all the
+ partitions will be reloaded, otherwise just the
+ partition given as parameter will be reloaded.
+
+ MI FIFO Command Format:
+ opensips-cli -x mi trie_reload part_1
+
+1.5.2. trie_reload_status
+
+ Gets the time of the last reload for any partition.
+ * if use_partition is set to 0 - the function doesn't receive
+ any parameter. It will list the date of the last reload for
+ the default (and only) partition.
+ * if use_partition is set to 1, the parameters are:
+ + partition_name (optional) - if not provided the
+ function will list the time of the last update for
+ every partition. Otherwise, the function will list the
+ time of the last reload for the given partition.
+
+ Example 1.8. trie_reload_status usage when use_partitions is 0
+$ opensips-cli -x mi dr_reload_status
+Date:: Tue Aug 12 12:26:00 2014
+
+1.5.3. trie_search
+
+ Tries to match a number in the existing tries loaded from the
+ database.
+ * if use_partition is set to 1 the function will have 2
+ parameters:
+ + partition_name
+ + number - the number to test against
+ * if use_partition is set to 0 the function will have 1
+ parameter:
+ + number - the number to test against
+
+ MI FIFO Command Format:
+ opensips-cli -x mi trie_search partition_name=part1 numb
+er=012340987
+
+1.5.4. trie_number_delete
+
+ Deletes individual entries in the trie, without reloading all
+ of the data
+ * if use_partition is set to 1 the function will have 2
+ parameters:
+ + partition_name
+ + number - the array of numbers to delete
+
+ MI FIFO Command Format:
+ opensips-cli -x mi trie_number_delete partition_name=par
+t1 number=["012340987","4858345"]
+
+1.5.5. trie_number_upsert
+
+ Upserts ( insert if not found, update is found ) an array of
+ numbers in the trie, without reloading all of the data
+ * if use_partition is set to 1 the function will have 3
+ parameters:
+ + partition_name
+ + number - the array of numbers to update
+ + attrs - the array of new attributes for the numbers
+
+ MI FIFO Command Format:
+ opensips-cli -x mi trie_number_upsert partition_name=par
+t1 number=["012340987"] attrs=["my_attrs"]
+
+1.6. Installation
+
+ The module requires some tables in the OpenSIPS database. You
+ can also find the complete database documentation on the
+ project webpage,
+ https://opensips.org/docs/db/db-schema-devel.html.
+
+ Documentation Copyrights:
+
+ Copyright © 2024 OpenSIPS Project
diff --git a/modules/trie/doc/trie.xml b/modules/trie/doc/trie.xml
new file mode 100644
index 00000000000..48fd719a093
--- /dev/null
+++ b/modules/trie/doc/trie.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+%docentities;
+
+]>
+
+
+
+ Trie Module
+ &osipsname;
+
+
+
+ &admin;
+
+ &docCopyrights;
+ ©right; 2024 OpenSIPS Project
+
+
+
diff --git a/modules/trie/doc/trie_admin.xml b/modules/trie/doc/trie_admin.xml
new file mode 100644
index 00000000000..a383a447819
--- /dev/null
+++ b/modules/trie/doc/trie_admin.xml
@@ -0,0 +1,453 @@
+
+
+
+ &adminguide;
+
+
+ Overview
+
+ Introduction
+
+ Trie is a module for efficiently caching and lookup of a set of prefixes ( stored in a trie data structure )
+
+
+
+
+
+
+
+ Dependencies
+
+ &osips; Modules
+
+ The following modules must be loaded before this module:
+
+
+
+
+ a database module.
+
+
+
+
+
+
+
+ External Libraries or Applications
+
+
+
+ none.
+
+
+
+
+
+
+
+
+ Exported Parameters
+
+ trie_table(str)
+
+ The name of the db table storing prefix rules.
+
+
+ Default value is trie_table
.
+
+
+
+ Set trie_table parameter
+
+...
+modparam("drouting", "trie_table", "my_prefix_table")
+...
+
+
+
+
+
+ no_concurrent_reload (int)
+
+ If enabled, the module will not allow do run multiple trie_reload
+ MI commands in parallel (with overlapping) Any new reload will
+ be rejected (and discarded) while an existing reload is in
+ progress.
+
+
+ If you have a large routing set (millions of rules/prefixes), you
+ should consider disabling concurrent reload as they will exhaust
+ the shared memory (by reloading into memory, in the same time,
+ multiple instances of routing data).
+
+
+ Default value is 0 (disabled)
.
+
+
+
+ Set no_concurrent_reload parameter
+
+...
+# do not allow parallel reload operations
+modparam("trie", "no_concurrent_reload", 1)
+...
+
+
+
+
+
+ use_partitions (int)
+
+ Flag to configure whether to use partitions for tries. If this
+ flag is set then the db_partitions_url and
+ db_partitions_table
+ variables become mandatory.
+
+
+ Default value is 0
.
+
+
+
+ Set use_partitions parameter
+
+...
+modparam("trie", "use_partitions", 1)
+...
+
+
+
+
+
+ db_partitions_url (str)
+
+ The url to the database containing partition-specific
+ information.The use_partitions parameter
+ must be set to 1.
+
+
+ Default value is "NULL"
.
+
+
+
+ Set db_partitions_url parameter
+
+...
+modparam("trie", "db_partitions_url", "mysql://user:password@localhost/opensips_partitions")
+...
+
+
+
+
+
+ db_partitions_table (str)
+
+ The name of the table containing partition definitions. To be
+ used with use_partitions and db_partitions_url.
+
+
+ Default value is trie_partitions
.
+
+
+
+ Set db_partitions_table parameter
+
+...
+modparam("trie", "db_partitions_table", "trie_partition_defs")
+...
+
+
+
+
+
+
+
+
+ Exported Functions
+
+
+ trie_search(number, [flags], [trie_attrs_pvar], [match_prefix_pvar], [partition])
+
+
+ Function to search for an entry ( number ) in a trie.
+
+
+ This function can be used from all routes.
+
+
+ If you set use_partitions to 1 the
+ partition last parameter becomes
+ mandatory.
+
+
+ All parameters are optional. Any of them may be ignored, provided
+ the necessary separation marks "," are properly placed.
+
+
+
+
+ number (str) - number to be searched in the trie
+
+
+
+
+ flags (string, optional) - a list
+ of letter-like flags for controlling the routing behavior.
+ Possible flags are:
+
+
+
+
+ L - Do strict length matching
+ over the prefix - actually the trie engine will do full number
+ matching and not prefix matching anymore.
+
+
+
+
+
+
+ trie_attrs_pvar (var, optional) - a
+ writable variable which will be populated with the attributes of the
+ matched trie rule.
+
+
+
+
+ match_prefix_pvar (var, optional) - a
+ writable variable which will be the actual prefix matched in the trie.
+
+
+
+
+ partition (string, optional) - the name
+ of the trie partition to be used. This parameter is to be defined
+ ONLY if the "use_partition" module parameter is turned on.
+
+
+
+
+
+
+ trie_search usage
+
+...
+if (trie_search("$rU","L",$avp(code_attrs),,"my_partition")) {
+ # we found it in the trie, it's a match
+ xlog("We found $rU in the trie with attrs $avp(code_attrs) \n");
+}
+
+
+
+
+
+
+
+ Exported MI Functions
+
+
+ trie_reload
+
+
+ Command to reload trie rules from database.
+
+
+
+
+ if use_partition is set to 0 - all routing rules will be reloaded.
+
+
+
+
+
+ if use_partition is set to 1, the parameters are:
+
+
+ partition_name (optional) - if not provided
+ all the partitions will be reloaded, otherwise just the partition given as parameter will be reloaded.
+
+
+
+
+
+
+
+ MI FIFO Command Format:
+
+
+ opensips-cli -x mi trie_reload part_1
+
+
+
+
+ trie_reload_status
+
+ Gets the time of the last reload for any partition.
+
+
+
+
+ if use_partition is set to 0 - the function
+ doesn't receive any parameter. It will list the date of the
+ last reload for the default (and only) partition.
+
+
+
+
+ if use_partition is set to 1, the parameters are:
+
+
+ partition_name (optional) - if not provided
+ the function will list the time of the last update for every
+ partition. Otherwise, the function will list the time of the last
+ reload for the given partition.
+
+
+
+
+
+
+ trie_reload_status usage when use_partitions is 0
+
+$ opensips-cli -x mi dr_reload_status
+Date:: Tue Aug 12 12:26:00 2014
+
+
+
+
+
+ trie_search
+
+ Tries to match a number in the existing tries loaded from the database.
+
+
+
+
+ if use_partition is set to 1 the function
+ will have 2 parameters:
+
+
+ partition_name
+
+
+ number - the number to test against
+
+
+
+
+
+
+ if use_partition is set to 0 the function will have 1 parameter:
+
+
+ number - the number to test against
+
+
+
+
+
+
+ MI FIFO Command Format:
+
+
+ opensips-cli -x mi trie_search partition_name=part1 number=012340987
+
+
+
+
+
+ trie_number_delete
+
+
+ Deletes individual entries in the trie, without reloading all of the data
+
+
+
+
+
+ if use_partition is set to 1 the function
+ will have 2 parameters:
+
+
+ partition_name
+
+
+ number - the array of numbers to delete
+
+
+
+
+
+
+ MI FIFO Command Format:
+
+
+ opensips-cli -x mi trie_number_delete partition_name=part1 number=["012340987","4858345"]
+
+
+
+
+
+ trie_number_upsert
+
+
+ Upserts ( insert if not found, update is found ) an array of numbers in the trie, without reloading all of the data
+
+
+
+
+
+ if use_partition is set to 1 the function
+ will have 3 parameters:
+
+
+ partition_name
+
+
+ number - the array of numbers to update
+
+
+ attrs - the array of new attributes for the numbers
+
+
+
+
+
+
+ MI FIFO Command Format:
+
+
+ opensips-cli -x mi trie_number_upsert partition_name=part1 number=["012340987"] attrs=["my_attrs"]
+
+
+
+
+
+
+
+ Installation
+
+ The module requires some tables in the OpenSIPS database.
+ You can also find the complete database documentation on the project webpage, &osipsdbdocslink;.
+
+
+
+
diff --git a/modules/trie/prefix_tree.c b/modules/trie/prefix_tree.c
new file mode 100644
index 00000000000..9dd13c43164
--- /dev/null
+++ b/modules/trie/prefix_tree.c
@@ -0,0 +1,316 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#include
+#include
+
+#include "../../str.h"
+#include "../../mem/shm_mem.h"
+#include "../../time_rec.h"
+
+#include "prefix_tree.h"
+#include "trie_partitions.h"
+
+#define DR_PREFIX_ARRAY_SIZE 128
+static unsigned char *trie_char2idx = NULL;
+
+/* number of children under a prefix node */
+int ptree_children = 0;
+
+#define IDX_OF_CHAR(_c) \
+ trie_char2idx[ (unsigned char)(_c) ]
+
+#define IS_VALID_PREFIX_CHAR(_c) \
+ ((((unsigned char)(_c))=DR_PREFIX_ARRAY_SIZE) {
+ LM_ERR("extra prefix char <%c/%d> out of range (max=%d),"
+ " ignoring\n",extra_prefix_chars[i],extra_prefix_chars[i],
+ DR_PREFIX_ARRAY_SIZE);
+ continue;
+ }
+ IDX_OF_CHAR( extra_prefix_chars[i] ) = ptree_children++;
+ }
+ }
+ LM_INFO("counted %d possible chars under a node\n", ptree_children);
+
+ return 0;
+}
+
+trie_node_t *get_child(trie_node_t *parent, int child_index) {
+ char *first_child_ptr;
+
+ if (child_index < 0 || child_index >= ptree_children) {
+ LM_ERR("Out of bounds child %d requested \n",child_index);
+ return NULL;
+ }
+
+ /* get first child allocated right after current node */
+ first_child_ptr = ((char*)parent) + sizeof(trie_node_t);
+ /* offset it to get to the right child_index */
+ return *(trie_node_t**)(first_child_ptr + child_index * sizeof(trie_node_t*));
+}
+
+trie_info_t*
+get_trie_prefix(
+ trie_node_t *ptree,
+ str* prefix,
+ unsigned int *matched_len,
+ int enabled_only
+ )
+{
+ char *tmp=NULL,*last_valid_tmp;
+ char local=0;
+ int idx=0;
+ trie_info_t *last_valid = NULL;
+ trie_node_t *current = NULL;
+
+ if(NULL == ptree)
+ goto err_exit;
+ if(NULL == prefix)
+ goto err_exit;
+ tmp = prefix->s;
+ if (tmp == NULL)
+ goto err_exit;
+
+ current = ptree;
+
+ /* go the tree down to the last digit in the
+ * prefix string or down to a leaf */
+ while(tmp< (prefix->s+prefix->len)) {
+ local=*tmp;
+ idx = IDX_OF_CHAR(local);
+ if (!IS_VALID_PREFIX_CHAR(*tmp) || (current = get_child(current,idx)) == NULL) {
+ break;
+ }
+
+ if (current->info && (!enabled_only || current->info->enabled)) {
+ /* found a valid node, store it */
+ last_valid = current->info;
+ last_valid_tmp = tmp;
+ }
+ tmp++;
+ }
+
+ if (last_valid && matched_len) {
+ *matched_len = last_valid_tmp + 1 - prefix->s;
+ }
+
+ return last_valid;
+
+err_exit:
+ return NULL;
+}
+
+int add_trie_info(
+ trie_node_t *pn,
+ trie_info_t* r,
+ osips_malloc_f malloc_f,
+ osips_free_f free_f
+ )
+{
+ pn->info = r;
+ return 0;
+}
+
+int add_trie_prefix(trie_node_t *ptree, str *prefix, trie_info_t *r, osips_malloc_f malloc_f, osips_free_f free_f)
+{
+ char* tmp=NULL;
+ int res = 0;
+ trie_node_t *child;
+
+ if (ptree == NULL || prefix == NULL || prefix->s == NULL) {
+ LM_ERR("ptree or no prefix\n");
+ return -1;
+ }
+
+ tmp = prefix->s;
+ while(tmp < (prefix->s+prefix->len)) {
+ if(NULL == tmp) {
+ LM_ERR("prefix became null\n");
+ goto err_exit;
+ }
+ if( !IS_VALID_PREFIX_CHAR(*tmp) ) {
+ /* unknown character in the prefix string */
+ LM_ERR("%c is not valid char in the prefix\n", *tmp);
+ goto err_exit;
+ }
+
+ /* process the current digit in the prefix */
+ if( (child = get_child(ptree,IDX_OF_CHAR(*tmp))) == NULL) {
+ /* allocate new node */
+ INIT_TRIE_NODE(malloc_f,child);
+ SET_TRIE_CHILD(ptree,IDX_OF_CHAR(*tmp),child);
+ }
+
+ ptree = get_child(ptree,IDX_OF_CHAR(*tmp));
+
+ if( tmp == (prefix->s+prefix->len-1) ) {
+ /* last digit in the prefix string */
+ LM_DBG("adding info %p, at: "
+ "%p (%d)\n", r, ptree,
+ IDX_OF_CHAR(*tmp));
+ res = add_trie_info(ptree,r, malloc_f, free_f);
+ if(res < 0 ) {
+ LM_ERR("adding rt info doesn't work\n");
+ goto err_exit;
+ }
+ res = 1;
+ goto ok_exit;
+ }
+
+ tmp++;
+ }
+
+ok_exit:
+ return 0;
+
+err_exit:
+ return -1;
+}
+
+int del_tree(trie_node_t* t, osips_free_f free_f) {
+ if (t == NULL) {
+ return 0;
+ }
+
+ for (int i = 0; i < ptree_children; i++) {
+ trie_node_t *child = get_child(t, i);
+ if (child != NULL) {
+ if (child->info != NULL) {
+ free_trie_info(child->info, free_f);
+ }
+ del_tree(child, free_f);
+ }
+ }
+
+ func_free(free_f, t);
+
+ return 0;
+}
+
+void
+free_trie_info(
+ trie_info_t *rl,
+ osips_free_f f
+ )
+{
+ if (NULL!=rl->attrs.s)
+ shm_free(rl->attrs.s);
+ func_free(f, rl);
+ return;
+}
+
+trie_data_t*
+build_trie_data(struct head_db *part)
+{
+ trie_data_t *rdata=NULL;
+
+ if( NULL==(rdata=func_malloc(part->malloc, sizeof(trie_data_t)))) {
+ LM_ERR("no more shm mem\n");
+ goto err_exit;
+ }
+ memset(rdata, 0, sizeof(trie_data_t));
+
+ /* empty trie with no children */
+ INIT_TRIE_NODE(part->malloc, rdata->pt);
+ return rdata;
+err_exit:
+ if (rdata)
+ func_free(part->free, rdata);
+ return 0;
+}
+
+trie_info_t*
+build_trie_info(
+ str* attrs,
+ int enabled,
+ osips_malloc_f mf,
+ osips_free_f ff
+ )
+{
+ trie_info_t* rt = NULL;
+
+ rt = (trie_info_t*)func_malloc(mf, sizeof(trie_info_t));
+ if (rt==NULL) {
+ LM_ERR("no more mem(1)\n");
+ goto err_exit;
+ }
+ memset(rt, 0, sizeof(trie_info_t));
+ rt->enabled = enabled;
+
+ if (attrs && attrs->s && attrs->len) {
+ rt->attrs.s = func_malloc(mf,attrs->len);
+ if (rt->attrs.s == NULL) {
+ LM_ERR("no more shm mem(1)\n");
+ goto err_exit;
+ }
+ rt->attrs.len = attrs->len;
+ memcpy(rt->attrs.s,attrs->s,rt->attrs.len);
+ }
+
+ return rt;
+
+err_exit:
+ if (NULL!=rt->attrs.s)
+ func_free(ff,rt->attrs.s);
+ if ((NULL != rt) ) {
+ func_free(ff, rt);
+ }
+ return NULL;
+}
+
+
+void free_trie_data(
+ trie_data_t* rt_data,
+ osips_free_f free_f
+ )
+{
+ if(NULL!=rt_data) {
+ del_tree(rt_data->pt, free_f);
+ rt_data->pt = 0 ;
+
+ /* del top level */
+ func_free(free_f, rt_data);
+ }
+}
diff --git a/modules/trie/prefix_tree.h b/modules/trie/prefix_tree.h
new file mode 100644
index 00000000000..a9f773da983
--- /dev/null
+++ b/modules/trie/prefix_tree.h
@@ -0,0 +1,126 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#ifndef trie_prefix_tree_h
+#define trie_prefix_tree_h
+
+#include "../../str.h"
+#include "../../ip_addr.h"
+#include "../../time_rec.h"
+#include "../../map.h"
+#include "../../mem/mem_funcs.h"
+
+#define IS_DECIMAL_DIGIT(d) \
+ (((d)>='0') && ((d)<= '9'))
+
+extern int ptree_children;
+extern int tree_size;
+struct head_db;
+
+#define INIT_TRIE_NODE(f, n) \
+do { \
+ (n) = (trie_node_t*)func_malloc(f,sizeof(trie_node_t) + ptree_children * sizeof(trie_node_t*)); \
+ if ((n) == NULL) \
+ goto err_exit; \
+ memset((n), 0, sizeof(trie_node_t) + ptree_children * sizeof(trie_node_t*)); \
+} while(0)
+
+#define SET_TRIE_CHILD(parent, child_index, new_node) \
+do { \
+ trie_node_t **child_ptr = (trie_node_t**)(((char*)parent) + sizeof(trie_node_t) + child_index * sizeof(trie_node_t*)); \
+ if (child_ptr != NULL) { \
+ *child_ptr = new_node; /* Set the allocated node as the child */ \
+ } \
+} while(0)
+
+typedef struct trie_info_ {
+ /* opaque string with rule attributes */
+ str attrs;
+ /* enabled ? */
+ int enabled;
+} trie_info_t;
+
+typedef struct trie_node_ {
+ trie_info_t *info;
+ /* this node's children follow right after
+ * inside the initially allocated memory chunk
+ * use INIT_TRIE_NODE and SET_TRIE_CHILD for operating it */
+} trie_node_t;
+
+typedef struct trie_data_ {
+ /* tree with routing prefixes */
+ trie_node_t *pt;
+}trie_data_t;
+
+/* init new trie_data structure */
+trie_data_t*
+build_trie_data( struct head_db * );
+
+
+void
+free_trie_data(trie_data_t*, osips_free_f);
+
+int
+init_prefix_tree(
+ char *extra_prefix_chars
+ );
+
+int
+del_tree(
+ trie_node_t *,
+ osips_free_f
+ );
+
+int
+add_trie_prefix(
+ trie_node_t*,
+ str* prefix,
+ trie_info_t *info,
+ osips_malloc_f,
+ osips_free_f
+ );
+
+trie_info_t*
+get_trie_prefix(
+ trie_node_t *ptree,
+ str* prefix,
+ unsigned int *matched_len,
+ int filter_disabled
+ );
+
+trie_info_t*
+build_trie_info(
+ str *attrs,
+ int disabled,
+ osips_malloc_f mf,
+ osips_free_f ff
+ );
+
+void
+free_trie_info(
+ trie_info_t*,
+ osips_free_f
+ );
+
+#endif
diff --git a/modules/trie/trie.c b/modules/trie/trie.c
new file mode 100644
index 00000000000..9576073c6be
--- /dev/null
+++ b/modules/trie/trie.c
@@ -0,0 +1,1292 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#include
+#include
+#include
+#include
+
+
+#include "../../evi/evi.h"
+#include "../../map.h"
+#include "../../ipc.h"
+
+#include "trie_load.h"
+#include "trie_db_def.h"
+#include "prefix_tree.h"
+#include "trie_partitions.h"
+
+#include "../../mem/rpm_mem.h"
+
+#define TRIE_PARAM_STRICT_LEN (1<<0)
+
+#define TRIE_TABLE_VER 1
+#define PART_TABLE_VER 1
+
+#define MAX_LEN_NAME_W_PART 510 /* max len of variable containing
+ avp_spec and partition name */
+#define MI_PART_NAME_S "Partition"
+#define MI_PART_NAME_LEN (strlen(MI_PART_NAME_S))
+
+#define MI_LAST_UPDATE_S "Date"
+#define MI_LAST_UPDATE_LEN (strlen(MI_LAST_UPDATE_S))
+
+#define MI_LAST_DB_URL_S "DB_URL"
+#define MI_LAST_DB_URL_LEN (strlen(MI_LAST_DB_URL_S))
+
+#define MI_HASH_S "HASH"
+#define MI_HASH_LEN (strlen(MI_HASH_S))
+
+/* reload control parameter */
+static int no_concurrent_reload = 0;
+
+/* parameters */
+static str db_url = {NULL,0};
+
+/* statistic data */
+int tree_size = 0;
+static str attrs_empty = str_init("");
+
+/* configuration loader from db specific stuff */
+static str trie_partitions_table = str_init("trie_partitions");
+static str trie_partitions_url;
+
+static str data_dump_folder = {0,0};
+
+int use_partitions = 0; /* by default don't use db for config */
+static struct head_config {
+ str partition; /* partition name extracted from database */
+ str db_url;
+ str trie_table; /* trie_table name extracted from database */
+ struct head_config *next;
+} *head_start;
+int *n_partitions; /* total number of partitions (does not change at runtime) */
+
+struct head_db *head_db_start;
+
+static int get_config_from_db();
+static int add_head_config();
+void init_head_db(struct head_db *new);
+static int db_connect_head(struct head_db*); /* populate a db connection */
+static char *extra_prefix_chars;
+
+
+/* reader-writers lock for reloading the data */
+static rw_lock_t *ref_lock = NULL;
+
+static int trie_init(void);
+static int trie_child_init(int rank);
+static int trie_exit(void);
+
+static int fix_flags(void** param);
+static int fix_partition(void** param);
+
+static int trie_match(struct sip_msg* msg,str *number, long flags,
+ pv_spec_t* rule_att, pv_spec_t* match_prefix, struct head_db *part);
+
+mi_response_t *trie_reload_cmd(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+mi_response_t *trie_reload_cmd_1(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+mi_response_t *mi_trie_number_routing_1(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+mi_response_t *mi_trie_number_routing_2(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+
+mi_response_t *mi_trie_reload_status(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+mi_response_t *mi_trie_reload_status_1(const mi_params_t *params,
+ struct mi_handler *async_hdl);
+mi_response_t *mi_trie_remove_code_2(const mi_params_t *params,struct mi_handler *async_hdl);
+mi_response_t *mi_trie_upsert_code_3(const mi_params_t *params,struct mi_handler *async_hdl);
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+ {"trie_search", (cmd_function)trie_match,
+ {
+ {CMD_PARAM_STR, NULL, NULL},
+ {CMD_PARAM_STR|CMD_PARAM_OPT, fix_flags, NULL},
+ {CMD_PARAM_VAR|CMD_PARAM_OPT, NULL, NULL},
+ {CMD_PARAM_VAR|CMD_PARAM_OPT, NULL, NULL},
+ {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fix_partition,NULL},
+ {0 , 0, 0}
+ },
+ REQUEST_ROUTE|FAILURE_ROUTE|LOCAL_ROUTE|BRANCH_ROUTE|ONREPLY_ROUTE
+ },
+ {0,0,{{0,0,0}},0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+ {"use_partitions", INT_PARAM, &use_partitions },
+ {"db_partitions_url", STR_PARAM, &trie_partitions_url.s },
+ {"db_partitions_table", STR_PARAM, &trie_partitions_table.s },
+ {"db_url", STR_PARAM, &db_url.s },
+ {"trie_table", STR_PARAM, &trie_table.s },
+ {"no_concurrent_reload",INT_PARAM, &no_concurrent_reload },
+ {"extra_prefix_chars", STR_PARAM, &extra_prefix_chars },
+ {0, 0, 0}
+};
+
+
+/*
+ * Exported MI functions
+ */
+#define HLP1 "Params: none ; Forces trie module to reload data from DB "\
+ "into memory; A return string is returned only in case of error."
+#define HLP2 "Params: [partition] number ; Check if a "\
+ "number will match when searching through the trie. "\
+"The partition parameter must be defined only if use_partitions = 1."
+#define HLP3 "Params: [partition]; List the time of the last trie_reload"\
+ " (load from database) for all partitions if no parameter is supplied, or"\
+" for a partition given as parameter. If use_partitions is 0, you should"\
+" not specify a partition."
+#define HLP4 "Params: partitionid code_array ; Used to delete codes from the in-memory trie "
+#define HLP5 "Params: partitionid code_array attrs_array ; Used to upsert codes in the in-memory trie"
+
+static mi_export_t mi_cmds[] = {
+ { "trie_reload", HLP1, 0, 0, {
+ {trie_reload_cmd, {0}},
+ {trie_reload_cmd_1, {"partition_name", 0}},
+ {EMPTY_MI_RECIPE}}
+ },
+ { "trie_search", HLP2, MI_NAMED_PARAMS_ONLY, 0, {
+ {mi_trie_number_routing_1, {"number", 0}},
+ {mi_trie_number_routing_2, {"partition_name", "number", 0}},
+ {EMPTY_MI_RECIPE}}
+ },
+ { "trie_reload_status", HLP3, 0, 0, {
+ {mi_trie_reload_status, {0}},
+ {mi_trie_reload_status_1, {"partition_name", 0}},
+ {EMPTY_MI_RECIPE}}
+ },
+ { "trie_number_delete", HLP4, 0,0, {
+ {mi_trie_remove_code_2, {"partition_name","number",0}},
+ {EMPTY_MI_RECIPE}}
+ },
+ { "trie_number_upsert", HLP5, 0,0, {
+ {mi_trie_upsert_code_3, {"partition_name","number","attrs",0}},
+ {EMPTY_MI_RECIPE}}
+ },
+ {EMPTY_MI_EXPORT}
+};
+
+static dep_export_t deps = {
+ { /* OpenSIPS module dependencies */
+ { MOD_TYPE_SQLDB, NULL, DEP_ABORT },
+ { MOD_TYPE_NULL, NULL, 0 },
+ },
+ { /* modparam dependencies */
+ { NULL, NULL },
+ },
+};
+
+struct module_exports exports = {
+ "trie",
+ MOD_TYPE_DEFAULT,/* class of this module */
+ MODULE_VERSION,
+ DEFAULT_DLFLAGS, /* dlopen flags */
+ 0, /* load function */
+ &deps, /* OpenSIPS module dependencies */
+ cmds, /* Exported functions */
+ 0, /* Exported async functions */
+ params, /* Exported parameters */
+ 0, /* exported statistics */
+ mi_cmds, /* exported MI functions */
+ 0, /* exported pseudo-variables */
+ 0, /* exported transformations */
+ 0, /* additional processes */
+ 0, /* Module pre-initialization function */
+ trie_init, /* Module initialization function */
+ (response_function) 0,
+ (destroy_function) trie_exit,
+ (child_init_function) trie_child_init, /* per-child init function */
+ 0 /* reload confirm function */
+};
+
+static void bin_hash_to_hex(HASH _b, HASHHEX _h)
+{
+ unsigned short i;
+ unsigned char j;
+
+ for (i = 0; i < HASHLEN; i++) {
+ j = (_b[i] >> 4) & 0xf;
+ if (j <= 9) {
+ _h[i * 2] = (j + '0');
+ } else {
+ _h[i * 2] = (j + 'a' - 10);
+ }
+
+ j = _b[i] & 0xf;
+
+ if (j <= 9) {
+ _h[i * 2 + 1] = (j + '0');
+ } else {
+ _h[i * 2 + 1] = (j + 'a' - 10);
+ }
+ };
+
+ _h[HASHHEXLEN] = '\0';
+}
+
+/*
+ * if none is successfully loaded return
+ * -1, else return 0
+ */
+
+static inline int trie_reload_data_head(struct head_db *hd,
+ str *part_name, int initial)
+{
+ trie_data_t *new_data;
+ trie_data_t *old_data;
+ time_t rawtime;
+ MD5_CTX Md5Ctx;
+ HASH bin_md5;
+ FILE *fp=NULL;
+
+ if (no_concurrent_reload) {
+ lock_get( hd->ref_lock->lock );
+ if (hd->ongoing_reload) {
+ lock_release( hd->ref_lock->lock );
+ LM_WARN("Reload already in progress, discarding this one\n");
+ return -2;
+ }
+ hd->ongoing_reload = 1;
+ lock_release( hd->ref_lock->lock );
+ }
+
+ LM_INFO("loading trie data in partition %.*s\n",part_name->len,part_name->s);
+ MD5Init(&Md5Ctx);
+
+ new_data = trie_load_info(hd, &Md5Ctx, fp);
+ if ( new_data==0 ) {
+ LM_CRIT("failed to load routing info\n");
+ goto error;
+ }
+
+ lock_start_write( hd->ref_lock );
+
+ /* no more activ readers -> do the swapping */
+ old_data = hd->rdata;
+ hd->rdata = new_data;
+ /* update the time of the last reload for the current partition */
+ time(&rawtime);
+
+ hd->time_last_update = rawtime;
+
+ MD5Final(bin_md5, &Md5Ctx);
+ bin_hash_to_hex(bin_md5,hd->md5);
+
+ lock_stop_write( (hd->ref_lock) );
+
+ LM_INFO("loaded trie data in partition %.*s\n",part_name->len,part_name->s);
+
+ /* destroy old data */
+ if (old_data) {
+ /* free old data */
+ free_trie_data(old_data, hd->free);
+ }
+ LM_INFO("destroyed old trie data in partition %.*s\n",part_name->len,part_name->s);
+
+ if (no_concurrent_reload)
+ hd->ongoing_reload = 0;
+ return 0;
+
+error:
+ if (no_concurrent_reload)
+ hd->ongoing_reload = 0;
+
+ return -1;
+}
+
+static inline int trie_reload_data(int initial)
+{
+ struct head_db *part;
+ int ret_val = 0;
+
+ for (part = head_db_start; part; part = part->next)
+ if (trie_reload_data_head(part, &part->partition, initial) != 0)
+ ret_val = -1;
+
+ return ret_val;
+}
+
+static int cleanup_head_config( struct head_config *hd)
+{
+ if (hd == NULL)
+ return 0;
+
+ if (hd->db_url.s)
+ shm_free(hd->db_url.s);
+ if (hd->trie_table.s && hd->trie_table.s != trie_table.s)
+ shm_free(hd->trie_table.s);
+
+ return 0;
+}
+
+
+static void cleanup_head_db(struct head_db *hd)
+{
+ if (!hd)
+ return;
+
+ if (hd->db_con && *(hd->db_con))
+ hd->db_funcs.close(*(hd->db_con));
+ if( hd->ref_lock )
+ lock_destroy_rw( ref_lock );
+ if (hd->partition.s)
+ shm_free(hd->partition.s);
+ if (hd->db_url.s)
+ shm_free( hd->db_url.s );
+ if (hd->trie_table.s && hd->trie_table.s != trie_table.s)
+ shm_free(hd->trie_table.s);
+}
+
+static void cleanup_head_db_table(void)
+{
+ struct head_db * it_head_db = 0;
+ struct head_db * last_cleaned = 0;
+
+ it_head_db = head_db_start;
+ while (it_head_db) {
+
+ cleanup_head_db(it_head_db);
+ last_cleaned = it_head_db;
+ it_head_db = it_head_db->next;
+ shm_free(last_cleaned);
+ }
+ head_start = 0;
+}
+
+static void cleanup_head_config_table(void)
+{
+ struct head_config * it_head_config = 0;
+ struct head_config * last_cleaned = 0;
+
+ it_head_config = head_start;
+ while (it_head_config) {
+
+ cleanup_head_config(it_head_config);
+ last_cleaned = it_head_config;
+ it_head_config = it_head_config->next;
+ shm_free(last_cleaned);
+ }
+ head_start = 0;
+}
+
+static int trie_init(void)
+{
+ str name_w_part;
+ struct head_config * it_head_config = 0;
+ struct head_db *db_part = NULL;
+ char name_w_buf[MAX_LEN_NAME_W_PART];
+ name_w_part.s = name_w_buf;
+
+ if (data_dump_folder.s)
+ data_dump_folder.len = strlen(data_dump_folder.s);
+
+ LM_INFO("trie - initializing\n");
+
+ n_partitions = shm_malloc(sizeof *n_partitions);
+ if (!n_partitions) {
+ LM_ERR("oom\n");
+ return -1;
+ }
+ *n_partitions = 0;
+
+ trie_table.len = strlen(trie_table.s);
+
+ name_w_part.s = shm_malloc( MAX_LEN_NAME_W_PART );
+ if( name_w_part.s == 0 ) {
+ LM_ERR(" No more shm memory [trie:name_w_part.s]\n");
+ goto error;
+ }
+
+ if( use_partitions == 1 ) { /* loading configurations from db */
+ if (get_config_from_db() == -1) {
+ LM_ERR("Failed to get configuration from db_config\n");
+ return -1;
+ }
+ } else {
+ init_db_url(db_url, 0);
+
+ add_head_config();
+
+ /* if not empty save to head_config structure */
+ if (trie_table.s[0]==0) {
+ LM_CRIT("mandatory parameter \"TRIE_TABLE\" found empty\n");
+ goto error_cfg;
+ }
+ head_start->trie_table.s = shm_malloc(trie_table.len);
+ if (head_start->trie_table.s == 0) {
+ LM_ERR("no more shm memory [trie:head_start->trie_table.s]\n");
+ goto error_cfg;
+ }
+ memcpy(head_start->trie_table.s, trie_table.s, trie_table.len);
+ head_start->trie_table.len = trie_table.len;
+
+ head_start->db_url.len = db_url.len;
+ head_start->db_url.s = shm_malloc(db_url.len);
+ if( head_start->db_url.s == 0 ) {
+ LM_ERR("no more shm memory [trie:head_start->db_url.s]\n");
+ goto error_cfg;
+ }
+ memcpy(head_start->db_url.s, db_url.s, db_url.len );
+
+ head_start->partition.s = "Default";
+ head_start->partition.len = strlen(head_start->partition.s);
+ }
+
+ if (init_prefix_tree( extra_prefix_chars )!=0) {
+ LM_ERR("failed to initiate the prefix array\n");
+ goto error;
+ }
+
+ for (it_head_config = head_start; it_head_config != NULL;
+ it_head_config = it_head_config->next) {
+
+ db_part = shm_malloc(sizeof(struct head_db));
+ if (!db_part) {
+ LM_ERR("could not allocate db part!\n");
+ goto error_cfg;
+ }
+ init_head_db(db_part);
+
+ if(shm_str_dup(&db_part->db_url, &it_head_config->db_url) != 0) {
+ LM_ERR("shm_str_dup failed for db_url\n");
+ goto error_cfg;
+ }
+
+ if(shm_str_dup(&db_part->partition, &it_head_config->partition) != 0) {
+ LM_ERR("shm_str_dup failed for partition name\n");
+ goto error_cfg;
+ }
+
+ if (!it_head_config->trie_table.s) {
+ db_part->trie_table.s = trie_table.s;
+ db_part->trie_table.len = trie_table.len;
+ } else if (shm_str_dup(&db_part->trie_table, &it_head_config->trie_table) != 0) {
+ LM_ERR("shm_str_dup failed for TRIE table\n");
+ goto error_cfg;
+ }
+
+ /* create & init lock */
+ if ((db_part->ref_lock = lock_init_rw()) == NULL) {
+ LM_CRIT("failed to init lock\n");
+ goto error_cfg;
+ }
+
+ db_part->db_con = pkg_malloc(sizeof(db_con_t *));
+ if (!db_part->db_con) {
+ LM_ERR("could not allocate db_connection in pkg mem!\n");
+ goto error_cfg;
+ }
+
+ /* bind to the SQL module */
+ if (db_bind_mod( &(db_part->db_url), &( db_part->db_funcs ))) {
+ LM_CRIT("cannot bind to database module! "
+ "Did you forget to load a database module ? (%.*s)\n",
+ db_url.len, db_url.s);
+ goto error_cfg;
+ }
+
+ if( (*db_part->db_con =
+ db_part->db_funcs.init(&db_part->db_url)) == 0) {
+ LM_ERR("failed to connect to db url <%.*s>\n",
+ db_part->db_url.len, db_part->db_url.s);
+ goto error_cfg;
+ }
+
+ if (!DB_CAPABILITY( db_part->db_funcs, DB_CAP_QUERY)) {
+ LM_CRIT("database modules does not "
+ "provide QUERY functions needed by DRouting module\n");
+ goto error_cfg;
+ }
+
+ if(db_check_table_version(&db_part->db_funcs, *db_part->db_con,
+ &db_part->trie_table, TRIE_TABLE_VER) < 0) {
+ LM_ERR("error during table version check (trie table \'%.*s\',"
+ " for partition \'%.*s\')\n", db_part->trie_table.len,
+ db_part->trie_table.s, db_part->partition.len,
+ db_part->partition.s);
+ goto error_cfg;
+ }
+
+ (db_part->db_funcs).close(*db_part->db_con);
+ *db_part->db_con = 0;
+
+ /* all good now - add the partition to the list */
+ db_part->next = head_db_start;
+ head_db_start = db_part;
+ db_part->malloc = shm_malloc_func;
+ db_part->free = shm_free_func;
+ }
+ /* all good now - release the config */
+ cleanup_head_config_table();
+
+ LM_DBG("All in place in the init. Will return 0\n");
+ return 0;
+
+error_cfg:
+ cleanup_head_config_table();
+ if (db_part) {
+ cleanup_head_db(db_part);
+ shm_free(db_part);
+ }
+error:
+ cleanup_head_db_table();
+ return -1;
+}
+
+static int db_connect_head(struct head_db *x) {
+
+ if( *(x->db_con) ) {
+ LM_INFO("db_con already present\n");
+ return 1;
+ }
+ if( x->db_url.s && (*(x->db_con) = x->db_funcs.init(&(x->db_url)))==0 ) {
+ LM_ERR("cannot initialize database connection"
+ "(partition:%.*s, db_url:%.*s, len:%d)\n", x->partition.len,
+ x->partition.s, x->db_url.len, x->db_url.s, x->db_url.len);
+ return -1;
+ }
+ return 0;
+}
+
+/* simple wrapper over trie_reload_data to make it compatible with ipc_rpc_f,
+ * so triggerable via IPC */
+static void rpc_trie_reload_data(int sender_id, void *unused)
+{
+ trie_reload_data(1);
+}
+
+
+static int trie_child_init(int rank)
+{
+ struct head_db *db = head_db_start;
+
+ LM_DBG("Child initialization on rank %d \n",rank);
+
+ for (db = head_db_start; db; db = db->next) {
+ if (db_connect_head(db) < 0) {
+ LM_ERR("failed to create DB connection\n");
+ return -1;
+ }
+ }
+
+ /* if child 1, send a job for itself to run the data loading after
+ * the init sequance is done */
+ if ( (rank==1) && ipc_send_rpc( process_no, rpc_trie_reload_data, NULL)<0) {
+ LM_CRIT("failed to RPC the data loading\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int trie_exit(void)
+{
+ struct head_db * it = head_db_start, *to_clean;
+
+ while( it!=NULL ) {
+ to_clean = it;
+ it = it->next;
+
+ /* destroy data */
+ if (to_clean->rdata) {
+ free_trie_data(to_clean->rdata, to_clean->free);
+ to_clean->rdata = 0;
+ }
+
+ /* destroy lock */
+ if (to_clean->ref_lock) {
+ lock_destroy_rw( to_clean->ref_lock );
+ to_clean->ref_lock = 0;
+ }
+
+ if(to_clean->trie_table.s && to_clean->trie_table.s != trie_table.s) {
+ shm_free(to_clean->trie_table.s);
+ }
+
+ shm_free(to_clean);
+ }
+
+ return 0;
+}
+
+static mi_response_t *mi_trie_get_partition(const mi_params_t *params,
+ struct head_db **partition)
+{
+ str part_name;
+
+ if (!use_partitions)
+ return init_mi_error_extra(400,
+ MI_SSTR("Invalid parameter: 'partition_name'"),
+ MI_SSTR("'partition_name' supported only when 'use_partitions' is set"));
+
+ if (get_mi_string_param(params, "partition_name",
+ &part_name.s, &part_name.len) < 0)
+ return init_mi_param_error();
+
+ if((*partition = get_partition(&part_name)) == NULL) {
+ LM_ERR("Partition not found\n");
+ return init_mi_error(404, MI_SSTR("Partition not found"));
+ }
+
+ return NULL;
+}
+
+mi_response_t *trie_reload_cmd(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ LM_INFO("trie_reload MI command received!\n");
+
+ if (trie_reload_data(0) != 0) {
+ LM_CRIT("failed to load routing data\n");
+ return init_mi_error(500, MI_SSTR("Failed to reload"));
+ }
+
+ return init_mi_result_ok();
+}
+
+mi_response_t *trie_reload_cmd_1(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ struct head_db *part;
+ mi_response_t *resp;
+
+ LM_INFO("trie_reload MI command received!\n");
+
+ resp = mi_trie_get_partition(params, &part);
+ if (resp)
+ return resp;
+
+ if (trie_reload_data_head(part, &part->partition, 0) < 0) {
+ LM_CRIT("Failed to load data head\n");
+ return init_mi_error(500, MI_SSTR("Failed to reload"));
+ }
+
+ return init_mi_result_ok();
+}
+
+struct head_db * get_partition(const str *name)
+{
+ struct head_db * it = head_db_start;
+
+ while( it!= NULL) {
+ if( it->partition.len==name->len && memcmp( it->partition.s, name->s,
+ name->len)==0 ) {
+ return it;
+ }
+ it = it->next;
+ }
+
+ return NULL; /* partition was not found */
+}
+
+static int fix_flags(void** param)
+{
+ str *s = (str*)(*param);
+ char *p;
+ long flags=0;
+
+ if (s) {
+ for ( p=s->s ; ps+s->len ; p++ ) {
+ switch (*p) {
+ case 'L':
+ flags |= TRIE_PARAM_STRICT_LEN;
+ LM_DBG("matching prefix with strict len\n");
+ break;
+ default:
+ LM_DBG("unknown flag : [%c] . Skipping\n",*p);
+ }
+ }
+ *param = (void*)(long)flags;
+ }
+ return 0;
+}
+
+
+static int fix_partition(void** param)
+{
+ str *s = (str*)(*param);
+ struct head_db *part;
+
+ if (s==NULL) {
+ /* no partition defined */
+ if (use_partitions==0) {
+ if(head_db_start == NULL) {
+ LM_ERR("Bad configuration, missing default partition\n");
+ return -1;
+ }
+ part = head_db_start;
+ } else {
+ LM_ERR("Partition name is mandatory\n");
+ return -1;
+ }
+ } else {
+ /* partition name defined */
+ if (s->len==1 && s->s[0]=='*') {
+ /* partition wild card */
+ part = NULL;
+ } else {
+ part = get_partition( s );
+ if (part==NULL) {
+ LM_ERR("partition <%.*s> used, but not defined\n",s->len,s->s);
+ return -1;
+ }
+ }
+ }
+ *param = (void*)part;
+
+ return 0;
+}
+
+static int trie_match(struct sip_msg* msg, str *number,long flags,
+ pv_spec_t* rule_att, pv_spec_t* match_prefix, struct head_db *part)
+{
+ trie_info_t* rule;
+ unsigned int matched_len;
+ pv_value_t val;
+
+ if (part==NULL || part->rdata == 0)
+ return -1;
+
+ lock_start_read( part->ref_lock );
+
+ rule = get_trie_prefix(part->rdata->pt,number, &matched_len, 1);
+ if (rule == NULL){
+ goto failure;
+ }
+
+ /* was it a full prefix matching ? */
+ if (flags & TRIE_PARAM_STRICT_LEN) {
+ if (matched_len!=number->len)
+ goto failure;
+ }
+
+ if (rule_att) {
+ val.flags = PV_VAL_STR;
+ val.rs = !rule->attrs.s ? attrs_empty : rule->attrs;
+ if (pv_set_value(msg, rule_att, 0, &val) != 0) {
+ LM_ERR("failed to set value for rule attrs pvar\n");
+ goto failure;
+ }
+ }
+
+ /* add RULE prefix avp */
+ if (match_prefix) {
+ val.flags = PV_VAL_STR;
+ val.rs.s = number->s;
+ val.rs.len = matched_len;
+ if (pv_set_value(msg, match_prefix, 0, &val) != 0) {
+ LM_ERR("failed to set value for rule attrs pvar\n");
+ goto failure;
+ }
+ }
+
+ lock_stop_read( part->ref_lock );
+
+ return 1;
+
+failure:
+ lock_stop_read( part->ref_lock );
+ return -1;
+}
+
+void init_head_db(struct head_db *new)
+{
+ memset(new, 0, sizeof(struct head_db));
+}
+
+/* use_partitions: use configurations from database */
+int add_head_config(void)
+{
+ /* expand linked list */
+ struct head_config *new;
+
+ new = shm_malloc(sizeof(struct head_config));
+ if( new == NULL ) {
+ LM_ERR("no more shm memory\n");
+ return -1;
+ }
+ memset(new, 0, sizeof(struct head_config));
+
+ new->next = head_start;
+ head_start = new;
+
+ (*n_partitions)++;
+ return 0;
+}
+
+#define init_head_config_value( from_head, external, default_val)\
+ if( external.len!=0 ) {\
+ shm_str_dup( &(from_head), &(external));\
+ } else {\
+ from_head = default_val;\
+ }\
+
+static int populate_head_config(struct head_config *current, str attr, int index) {
+ switch(index) {
+ case 0:
+ if(shm_str_dup( &(current->partition), &attr) < 0) {
+ LM_ERR("no more shm memory for partition_name in head_config\n");
+ }
+ break;
+ case 1:
+ if( shm_str_dup(&(current->db_url), &attr) < 0) {
+ LM_ERR("no more shm memory for db_url in head_config\n");
+ }
+ break;
+ case 2:
+ init_head_config_value( current->trie_table, attr, trie_table);
+ break;
+ default:
+ LM_DBG("Column from db_config not_known\n");
+ return -1;
+ }
+ return 0;
+}
+static int get_config_from_db(void) {
+
+ db_func_t db_funcs;
+ db_res_t * query_res;
+ db_con_t * db_con = 0;
+ /* columns needed from db_confgir_url for query */
+ str partition_col = str_init("partition_name");
+ str db_url_col = str_init("db_url");
+ str table_col = str_init("trie_table");
+ int n_query_col = 4;
+ db_key_t query_cols[] = {&partition_col, &db_url_col, &table_col};
+ /* query result processing stuff */
+ int nr_rows_db_config = 0 ;
+ int nr_cols_db_config = 0 ;
+ db_val_t * value;
+ db_row_t *rows_db_config = NULL;
+ int j;
+ int i;
+ str ans_col = {NULL, 0};
+
+ init_db_url(trie_partitions_url, 0);
+ trie_partitions_url.len = strlen(trie_partitions_url.s);
+ trie_partitions_table.len = strlen(trie_partitions_table.s);
+
+ if(db_bind_mod( &trie_partitions_url, &db_funcs) < 0) {
+ LM_ERR("Unable to bind to database driver (partition definitions) "
+ "\n", trie_partitions_url.len,
+ trie_partitions_url.s);
+ goto error;
+ }
+
+ if( (db_con = db_funcs.init(&trie_partitions_url)) == 0 ) {
+ LM_ERR("Cannot init connection to partitions table "
+ "\n", trie_partitions_url.len,
+ trie_partitions_url.s);
+ goto error;
+ }
+
+
+ if(db_check_table_version(&db_funcs, db_con,
+ &trie_partitions_table, PART_TABLE_VER) < 0) {
+ LM_ERR("error during table version check .\n",
+ trie_partitions_table.len, trie_partitions_table.s);
+ return -1;
+ }
+
+ if( db_funcs.use_table( db_con, &trie_partitions_table) < 0) {
+ LM_ERR("Cannot use the partitions table "
+ "\n", trie_partitions_table.len, trie_partitions_table.s,
+ trie_partitions_url.len, trie_partitions_url.s);
+ goto error;
+ }
+
+ /* query for populating head_config structure */
+ if( db_funcs.query( db_con, NULL, NULL, NULL, query_cols, 0, n_query_col,
+ NULL, &query_res) < 0 ) {
+ LM_ERR("Failed to query the table containing the partition definitions "
+ "\n",
+ trie_partitions_url.len, trie_partitions_url.s,
+ trie_partitions_table.len, trie_partitions_table.s);
+ goto error;
+ }
+
+ nr_rows_db_config = RES_ROW_N(query_res);
+ nr_cols_db_config = RES_COL_N(query_res);
+ rows_db_config = RES_ROWS(query_res);
+
+ LM_DBG("Got %d total trie partitions \n",nr_rows_db_config);
+
+ for( i=0; irdata == 0)
+ return init_mi_result_ok();
+
+ lock_start_read( partition->ref_lock );
+
+ if (partition->rdata == NULL) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_error(400, MI_SSTR("No data"));
+ }
+
+
+ route = get_trie_prefix(partition->rdata->pt,&number,&matched_len, 1);
+ LM_DBG("Got back %p \n",route);
+ if (route == NULL){
+ lock_stop_read( partition->ref_lock );
+ return init_mi_result_string(MI_SSTR("No match"));
+ }
+
+ resp = init_mi_result_object(&resp_obj);
+ if (!resp)
+ return 0;
+
+ if (add_mi_string(resp_obj, MI_SSTR("Matched Prefix"),
+ number.s, matched_len) < 0)
+ goto error;
+
+ if (route->attrs.s != NULL && route->attrs.len > 0)
+ if (add_mi_string(resp_obj, MI_SSTR("ATTRS"),
+ route->attrs.s,route->attrs.len) < 0)
+ goto error;
+
+ lock_stop_read( partition->ref_lock );
+
+ return resp;
+
+error:
+ lock_stop_read( partition->ref_lock );
+ free_mi_response(resp);
+ return 0;
+}
+
+mi_response_t *mi_trie_number_routing_1(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ if (use_partitions)
+ return init_mi_error_extra(400,
+ MI_SSTR("Missing parameter: 'partition_name'"),
+ MI_SSTR("'partition_name' is required when 'use_partitions' is set"));
+
+ return mi_trie_number_routing(params, head_db_start);
+}
+
+mi_response_t *mi_trie_number_routing_2(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ struct head_db * current_partition=0;
+ mi_response_t *resp;
+
+ resp = mi_trie_get_partition(params, ¤t_partition);
+ if (resp)
+ return resp;
+
+ return mi_trie_number_routing(params, current_partition);
+}
+
+static int mi_trie_print_rld_status(mi_item_t *part_item, struct head_db * partition,
+ int with_name)
+{
+ char ch_time[26];
+
+ lock_start_read(partition->ref_lock);
+
+ ctime_r(&partition->time_last_update, ch_time);
+ LM_DBG("partition %.*s was last updated:%s\n",
+ partition->partition.len, partition->partition.s,
+ ch_time);
+
+ if (with_name && add_mi_string(part_item, MI_SSTR("name"),
+ partition->partition.s, partition->partition.len) < 0)
+ goto error;
+
+ if (add_mi_string(part_item, MI_SSTR(MI_LAST_UPDATE_S),
+ ch_time, strlen(ch_time)-1) < 0)
+ goto error;
+
+ if (add_mi_string(part_item,MI_SSTR(MI_HASH_S),partition->md5,strlen(partition->md5)) < 0)
+ goto error;
+
+ lock_stop_read(partition->ref_lock);
+
+ return 0;
+
+error:
+ lock_stop_read(partition->ref_lock);
+ return -1;
+}
+
+mi_response_t *mi_trie_reload_status(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ struct head_db * partition;
+ mi_response_t *resp;
+ mi_item_t *resp_obj;
+ mi_item_t *parts_arr, *part_item;
+
+ resp = init_mi_result_object(&resp_obj);
+ if (!resp)
+ return 0;
+
+ if(use_partitions){
+ /* display for all partitions */
+ parts_arr = add_mi_array(resp_obj, MI_SSTR("Partitions"));
+ if (!parts_arr)
+ goto error;
+
+ for(partition = head_db_start; partition; partition = partition->next) {
+ part_item = add_mi_object(parts_arr, NULL, 0);
+ if (!part_item)
+ goto error;
+
+ if (mi_trie_print_rld_status(part_item, partition, 1) < 0)
+ goto error;
+ }
+ } else /* just one partition */
+ if (mi_trie_print_rld_status(resp_obj, head_db_start, 0) < 0)
+ goto error;
+
+ return resp;
+
+error:
+ free_mi_response(resp);
+ return 0;
+}
+
+mi_response_t *mi_trie_reload_status_1(const mi_params_t *params,
+ struct mi_handler *async_hdl)
+{
+ struct head_db * partition;
+ mi_response_t *resp;
+ mi_item_t *resp_obj;
+
+ resp = mi_trie_get_partition(params, &partition);
+ if (resp)
+ return resp;
+
+ resp = init_mi_result_object(&resp_obj);
+ if (!resp)
+ return 0;
+
+ if (mi_trie_print_rld_status(resp_obj, partition, 1) < 0) {
+ free_mi_response(resp);
+ return 0;
+ }
+
+ return resp;
+}
+
+mi_response_t *mi_trie_remove_code_2(const mi_params_t *params,struct mi_handler *async_hdl)
+{
+ struct head_db *partition;
+ str number;
+ unsigned int matched_len;
+ trie_info_t *route;
+ mi_response_t *resp;
+ mi_item_t *code_arr;
+ int no_codes,i;
+
+ resp = mi_trie_get_partition(params,&partition);
+ if (resp)
+ return resp;
+
+ if (get_mi_array_param(params, "number", &code_arr, &no_codes) < 0)
+ return init_mi_param_error();
+
+ lock_start_read( partition->ref_lock );
+
+ if (partition->rdata == NULL) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_error(400, MI_SSTR("No data"));
+ }
+
+ for (i = 0; i < no_codes; i++) {
+ if (get_mi_arr_param_string(code_arr, i,
+ &number.s, &number.len) < 0) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_param_error();
+ }
+
+ route = get_trie_prefix(((partition->rdata))->pt,
+ &number, &matched_len,1);
+ if (route == NULL) {
+ LM_ERR("Failed to find DID to delete [%.*s]\n",number.len,number.s);
+ continue;
+ }
+
+ if (matched_len != number.len) {
+ LM_ERR("Failed to find entry to delete [%.*s]\n",number.len,number.s);
+ continue;
+ }
+
+ route->enabled = 0;
+ }
+
+ lock_stop_read( partition->ref_lock );
+
+ return init_mi_result_ok();
+}
+
+mi_response_t *mi_trie_upsert_code_3(const mi_params_t *params,struct mi_handler *async_hdl)
+{
+ struct head_db *partition;
+ str number,attr,dyn_attr;
+ unsigned int matched_len;
+ trie_info_t *route;
+ mi_response_t *resp;
+ mi_item_t *code_arr, *attrs_arr;
+ int no_codes,no_attrs,i;
+
+ resp = mi_trie_get_partition(params,&partition);
+ if (resp)
+ return resp;
+
+ if (get_mi_array_param(params, "number", &code_arr, &no_codes) < 0)
+ return init_mi_param_error();
+ if (get_mi_array_param(params, "attrs", &attrs_arr, &no_attrs) < 0)
+ return init_mi_param_error();
+
+ if (no_codes != no_attrs) {
+ return init_mi_error(400, MI_SSTR("Code attrs missmatch"));
+ }
+
+ lock_start_read( partition->ref_lock );
+ if (partition->rdata == NULL) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_error(400, MI_SSTR("No data"));
+ }
+
+ for (i = 0; i < no_codes; i++) {
+ if (get_mi_arr_param_string(code_arr, i,
+ &number.s, &number.len) < 0) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_param_error();
+ }
+
+ if (get_mi_arr_param_string(attrs_arr, i,
+ &attr.s, &attr.len) < 0) {
+ lock_stop_read( partition->ref_lock );
+ return init_mi_param_error();
+ }
+
+ /* we search for all codes, enabled or disabled */
+ route = get_trie_prefix(((partition->rdata))->pt,
+ &number, &matched_len,0);
+
+ if (matched_len != number.len){
+ /* prefix not found, need to add it */
+
+ route = build_trie_info(&attr,1,partition->malloc,partition->free);
+ if (!route) {
+ LM_ERR("Failed to build route info for DID upsert %.*s\n", number.len,number.s);
+ lock_stop_read( partition->ref_lock );
+ return init_mi_error(500, MI_SSTR("Internal Error"));
+ }
+
+ if (add_trie_prefix(((partition->rdata))->pt,&number,route,partition->malloc,partition->free) != 0) {
+ LM_ERR("Failed to add route info for DID upsert %.*s\n", number.len,number.s);
+ lock_stop_read( partition->ref_lock );
+ free_trie_info(route,partition->free);
+ return init_mi_error(500, MI_SSTR("Internal Error"));
+ }
+ } else {
+ /* we found it, need to update in-place */
+ dyn_attr.s = shm_malloc(attr.len);
+ if (!dyn_attr.s) {
+ LM_ERR("No more shm \n");
+ lock_stop_read( partition->ref_lock );
+ return init_mi_error(500, MI_SSTR("Internal Error"));
+ }
+
+ memcpy(dyn_attr.s,attr.s,attr.len);
+ if (route->attrs.s) {
+ shm_free(route->attrs.s);
+ }
+
+ route->attrs.len = attr.len;
+ route->attrs.s = dyn_attr.s;
+
+ /* if it was removed before, clear that flag */
+ route->enabled = 1;
+ }
+ }
+
+ lock_stop_read( partition->ref_lock );
+ return init_mi_result_ok();
+}
diff --git a/modules/trie/trie_db_def.c b/modules/trie/trie_db_def.c
new file mode 100644
index 00000000000..13a26351505
--- /dev/null
+++ b/modules/trie/trie_db_def.c
@@ -0,0 +1,36 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#include "../../ut.h"
+#include "trie_db_def.h"
+
+/* DR rule table related defs */
+#define PREFIX_TRIE_COL "prefix"
+#define ATTRS_TRIE_COL "attrs"
+#define DISABLED_TRIE_COL "enabled"
+
+str trie_table = str_init("trie");
+str prefix_trie_col = str_init(PREFIX_TRIE_COL);
+str attrs_trie_col = str_init(ATTRS_TRIE_COL);
+str enabled_trie_col = str_init(DISABLED_TRIE_COL);
diff --git a/modules/trie/trie_db_def.h b/modules/trie/trie_db_def.h
new file mode 100644
index 00000000000..7940913fff0
--- /dev/null
+++ b/modules/trie/trie_db_def.h
@@ -0,0 +1,37 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#ifndef _TRIE_DB_DEFS
+#define _TRIE_DB_DEFS
+
+#include "../../str.h"
+
+/* DR rule table related defs */
+extern str trie_table;
+extern str prefix_trie_col;
+extern str attrs_trie_col;
+extern str enabled_trie_col;
+
+#endif
+
diff --git a/modules/trie/trie_load.c b/modules/trie/trie_load.c
new file mode 100644
index 00000000000..d3125382b5e
--- /dev/null
+++ b/modules/trie/trie_load.c
@@ -0,0 +1,248 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+
+#include "../../dprint.h"
+#include "../../route.h"
+#include "../../db/db.h"
+#include "../../mem/shm_mem.h"
+#include "../../mem/rpm_mem.h"
+#include "../../time_rec.h"
+#include "../../socket_info.h"
+
+#include "trie_load.h"
+#include "prefix_tree.h"
+#include "trie_db_def.h"
+
+
+#define check_val2( _col, _val, _type1, _type2, _not_null, _is_empty_str) \
+ do{\
+ if ((_val)->type!=_type1 && (_val)->type!=_type2) { \
+ LM_ERR("column %.*s has a bad type [%d], accepting only [%d,%d]\n",\
+ _col.len, _col.s, (_val)->type, _type1, _type2); \
+ goto error;\
+ } \
+ if (_not_null && (_val)->nul) { \
+ LM_ERR("column %.*s is null\n", _col.len, _col.s); \
+ goto error;\
+ } \
+ if (_is_empty_str && VAL_STRING(_val)==0) { \
+ LM_ERR("column %.*s (str) is empty\n", _col.len, _col.s); \
+ goto error;\
+ } \
+ }while(0)
+
+#define check_val( _col, _val, _type, _not_null, _is_empty_str) \
+ do{\
+ if ((_val)->type!=_type) { \
+ LM_ERR("column %.*s has a bad type [%d], accepting only [%d]\n",\
+ _col.len, _col.s, (_val)->type, _type); \
+ goto error;\
+ } \
+ if (_not_null && (_val)->nul) { \
+ LM_ERR("column %.*s is null\n", _col.len, _col.s); \
+ goto error;\
+ } \
+ if (_is_empty_str && VAL_STRING(_val)==0) { \
+ LM_ERR("column %.*s (str) is empty\n", _col.len, _col.s); \
+ goto error;\
+ } \
+ }while(0)
+
+
+void hash_rule(str* prefix, trie_info_t* rule, MD5_CTX* hash_ctx, FILE* fp)
+{
+ if (prefix->s && prefix->len) {
+ MD5Update(hash_ctx, prefix->s, prefix->len);
+ if (fp)
+ fprintf(fp, " %.*s",prefix->len,prefix->s);
+ }
+
+ if (rule->attrs.s && rule->attrs.len) {
+ MD5Update(hash_ctx, rule->attrs.s, rule->attrs.len);
+ if (fp)
+ fprintf(fp, " %.*s",rule->attrs.len,rule->attrs.s);
+ }
+
+ if (fp)
+ fprintf(fp,"\n");
+}
+
+static int add_rule(trie_data_t *rdata, str *prefix,
+ trie_info_t *rule, osips_malloc_f malloc_f, osips_free_f free_f,MD5_CTX *hash_ctx, FILE *fp)
+{
+ if ( add_trie_prefix(rdata->pt, prefix, rule,malloc_f, free_f)!=0 ) {
+ LM_ERR("failed to add prefix route\n");
+ goto error;
+ }
+
+ hash_rule(prefix,rule,hash_ctx,fp);
+
+ return 0;
+error:
+ return -1;
+}
+
+/* loads trie info for given partition; if partition_name is NULL
+ * loads all partitions
+ */
+
+trie_data_t* trie_load_info(struct head_db *current_partition, MD5_CTX* hash_ctx, FILE *fp)
+{
+ db_func_t *trie_dbf;
+ db_con_t* db_hdl;
+ str *trie_table = ¤t_partition->trie_table;
+ db_key_t columns[3];
+ db_res_t* res;
+ db_row_t* row;
+ trie_info_t *ri;
+ trie_data_t *rdata;
+ int i,n;
+ int no_rows = 10;
+ str prefix,attrs;
+
+ trie_dbf = ¤t_partition->db_funcs;
+ db_hdl = *current_partition->db_con;
+
+ res = 0;
+ ri = 0;
+ rdata = 0;
+
+ /* init new data structure */
+ if ( (rdata=build_trie_data(current_partition))==0 ) {
+ LM_ERR("failed to build rdata\n");
+ goto error;
+ }
+
+
+ /* read the routing rules */
+ if (trie_dbf->use_table( db_hdl, trie_table) < 0) {
+ LM_ERR("cannot select table \"%.*s\"\n", trie_table->len, trie_table->s);
+ goto error;
+ }
+
+ columns[0] = &prefix_trie_col;
+ columns[1] = &attrs_trie_col;
+ columns[2] = &enabled_trie_col;
+
+ if (DB_CAPABILITY(*trie_dbf, DB_CAP_FETCH)) {
+ if ( trie_dbf->query( db_hdl, 0, 0, 0, columns, 0, 3, 0, 0) < 0) {
+ LM_ERR("DB query failed\n");
+ goto error;
+ }
+ no_rows = estimate_available_rows( 32+128+4, 3/*cols*/);
+ if (no_rows==0) no_rows = 10;
+ if(trie_dbf->fetch_result(db_hdl, &res, no_rows)<0) {
+ LM_ERR("Error fetching rows\n");
+ goto error;
+ }
+ } else {
+ if ( trie_dbf->query( db_hdl, 0, 0, 0, columns, 0, 3, 0, &res) < 0) {
+ LM_ERR("DB query failed\n");
+ goto error;
+ }
+ }
+
+ if (RES_ROW_N(res) == 0) {
+ LM_WARN("table \"%.*s\" is empty\n", trie_table->len, trie_table->s);
+ }
+
+ LM_DBG("initial %d records found in %.*s\n", RES_ROW_N(res),
+ trie_table->len, trie_table->s);
+
+ n = 0;
+ do {
+ for(i=0; i < RES_ROW_N(res); i++) {
+ row = RES_ROWS(res) + i;
+ /* PREFIX column */
+ check_val( prefix_trie_col, ROW_VALUES(row), DB_STRING, 1, 1);
+
+ prefix.s = (char *)VAL_STRING(ROW_VALUES(row));
+ prefix.len = strlen(prefix.s);
+
+ if ((ROW_VALUES(row)+1)->nul || VAL_STRING(ROW_VALUES(row)+1) == NULL) {
+ attrs.s = NULL;
+ attrs.len = 0;
+ } else {
+ attrs.s = (char *)VAL_STRING(ROW_VALUES(row)+1);
+ attrs.len = strlen(attrs.s);
+ }
+
+ LM_DBG("Fetched %.*s prefix \n",VAL_STR(ROW_VALUES(row)).len,VAL_STR(ROW_VALUES(row)).s);
+
+ /* build the routing rule */
+ if ((ri = build_trie_info(
+ &attrs,
+ VAL_INT(ROW_VALUES(row)+2),
+ current_partition->malloc,
+ current_partition->free))== 0 ) {
+ LM_ERR("failed to add routing info for rule prefix %.*s\n", VAL_STR(ROW_VALUES(row)+1).len,VAL_STR(ROW_VALUES(row)+1).s);
+ continue;
+ }
+ /* add the rule */
+ if (add_rule(
+ rdata,
+ &prefix,
+ ri,
+ current_partition->malloc,
+ current_partition->free,hash_ctx, fp)!=0) {
+
+ LM_ERR("failed to add routing info for rule prefix %.*s\n", VAL_STR(ROW_VALUES(row)+1).len,VAL_STR(ROW_VALUES(row)+1).s);
+ free_trie_info(ri, current_partition->free);
+ continue;
+ }
+ n++;
+ }
+ if (DB_CAPABILITY(*trie_dbf, DB_CAP_FETCH)) {
+ if(trie_dbf->fetch_result(db_hdl, &res, no_rows)<0) {
+ LM_ERR( "fetching rows (1)\n");
+ goto error;
+ }
+ LM_DBG("additional %d records found in %.*s\n", RES_ROW_N(res),
+ trie_table->len, trie_table->s);
+ } else {
+ break;
+ }
+ } while(RES_ROW_N(res)>0);
+
+ trie_dbf->free_result(db_hdl, res);
+ res = 0;
+
+ LM_DBG("%d total records loaded from table %.*s\n", n,
+ trie_table->len, trie_table->s);
+ return rdata;
+error:
+ if (res)
+ trie_dbf->free_result(db_hdl, res);
+ if (rdata)
+ free_trie_data(rdata, current_partition->free);
+ rdata = NULL;
+ return 0;
+}
diff --git a/modules/trie/trie_load.h b/modules/trie/trie_load.h
new file mode 100644
index 00000000000..3c8da7a6337
--- /dev/null
+++ b/modules/trie/trie_load.h
@@ -0,0 +1,34 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#ifndef _TRIE_LOAD_
+#define _TRIE_LOAD_
+
+#include "../../str.h"
+#include "../../db/db.h"
+#include "trie_partitions.h"
+
+trie_data_t* trie_load_info(struct head_db *current_partition, MD5_CTX *hash_ctx, FILE* fp);
+
+#endif
diff --git a/modules/trie/trie_partitions.h b/modules/trie/trie_partitions.h
new file mode 100644
index 00000000000..be8f6e75b64
--- /dev/null
+++ b/modules/trie/trie_partitions.h
@@ -0,0 +1,67 @@
+ /*
+ * Trie Module
+ *
+ * Copyright (C) 2024 OpenSIPS Project
+ *
+ * opensips 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * opensips 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * History:
+ * --------
+ * 2024-12-03 initial release (vlad)
+ */
+
+#ifndef TRIE_PARTITIONS_H
+#define TRIE_PARTITIONS_H
+
+#include "prefix_tree.h"
+#include "../../db/db.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "../../rw_locking.h"
+#include "../../action.h"
+#include "../../error.h"
+#include "../../ut.h"
+#include "../../mod_fix.h"
+#include "../../md5global.h"
+#include "../../md5.h"
+
+extern int use_partitions;
+extern rw_lock_t *reload_lock;
+
+#define HASHLEN 16
+typedef char HASH[HASHLEN];
+
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+
+struct head_db {
+ str db_url;
+ str partition;
+ db_func_t db_funcs;
+ db_con_t **db_con;
+ str trie_table; /* trie_table name extracted from database */
+ time_t time_last_update;
+ trie_data_t *rdata;
+ HASHHEX md5;
+ rw_lock_t *ref_lock;
+ int ongoing_reload;
+ struct head_db *next;
+ osips_malloc_f malloc;
+ osips_free_f free;
+};
+
+struct head_db * get_partition(const str *);
+
+#endif
diff --git a/scripts/db_berkeley/opensips/dr_partitions b/scripts/db_berkeley/opensips/dr_partitions
index b826ecadda9..31cc7518838 100644
--- a/scripts/db_berkeley/opensips/dr_partitions
+++ b/scripts/db_berkeley/opensips/dr_partitions
@@ -1,5 +1,5 @@
METADATA_COLUMNS
-id(int) partition_name(str) db_url(str) drd_table(str) drr_table(str) drg_table(str) drc_table(str) ruri_avp(str) gw_id_avp(str) gw_priprefix_avp(str) gw_sock_avp(str) rule_id_avp(str) rule_prefix_avp(str) carrier_id_avp(str)
+id(int) partition_name(str) db_url(str) trie_table(str)
METADATA_KEY
0
METADATA_READONLY
@@ -7,4 +7,4 @@ METADATA_READONLY
METADATA_LOGFLAGS
0
METADATA_DEFAULTS
-NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL
+NIL|NIL|NIL|NIL
diff --git a/scripts/db_berkeley/opensips/trie_table b/scripts/db_berkeley/opensips/trie_table
new file mode 100644
index 00000000000..8c24bac1f78
--- /dev/null
+++ b/scripts/db_berkeley/opensips/trie_table
@@ -0,0 +1,12 @@
+METADATA_COLUMNS
+ruleid(int) prefix(str) attrs(str) priority(int)
+METADATA_KEY
+0
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NULL|1
+trie_table|
+trie_table|1
diff --git a/scripts/dbtext/opensips/dr_partitions b/scripts/dbtext/opensips/dr_partitions
index da439109c38..8e304ea073e 100644
--- a/scripts/dbtext/opensips/dr_partitions
+++ b/scripts/dbtext/opensips/dr_partitions
@@ -1 +1 @@
-id(int,auto) partition_name(string) db_url(string) drd_table(string,null) drr_table(string,null) drg_table(string,null) drc_table(string,null) ruri_avp(string,null) gw_id_avp(string,null) gw_priprefix_avp(string,null) gw_sock_avp(string,null) rule_id_avp(string,null) rule_prefix_avp(string,null) carrier_id_avp(string,null)
+id(int,auto) partition_name(string) db_url(string) trie_table(string,null)
diff --git a/scripts/dbtext/opensips/trie_table b/scripts/dbtext/opensips/trie_table
new file mode 100644
index 00000000000..5812bc57d19
--- /dev/null
+++ b/scripts/dbtext/opensips/trie_table
@@ -0,0 +1,2 @@
+ruleid(int,auto) prefix(string) attrs(string,null) priority(int)
+trie_table:1
diff --git a/scripts/mysql/trie-create.sql b/scripts/mysql/trie-create.sql
new file mode 100644
index 00000000000..e1edab30e00
--- /dev/null
+++ b/scripts/mysql/trie-create.sql
@@ -0,0 +1,16 @@
+INSERT INTO version (table_name, table_version) values ('trie_table','1');
+CREATE TABLE trie_table (
+ ruleid INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ prefix CHAR(64) NOT NULL,
+ attrs CHAR(255) DEFAULT NULL,
+ priority INT(11) DEFAULT 1 NOT NULL
+) ENGINE=InnoDB;
+
+INSERT INTO version (table_name, table_version) values ('dr_partitions','1');
+CREATE TABLE dr_partitions (
+ id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ partition_name CHAR(255) NOT NULL,
+ db_url CHAR(255) NOT NULL,
+ trie_table CHAR(255)
+) ENGINE=InnoDB;
+
diff --git a/scripts/oracle/trie-create.sql b/scripts/oracle/trie-create.sql
new file mode 100644
index 00000000000..82de8054747
--- /dev/null
+++ b/scripts/oracle/trie-create.sql
@@ -0,0 +1,32 @@
+INSERT INTO version (table_name, table_version) values ('trie_table','1');
+CREATE TABLE trie_table (
+ ruleid NUMBER(10) PRIMARY KEY,
+ prefix VARCHAR2(64),
+ attrs VARCHAR2(255) DEFAULT NULL,
+ priority NUMBER(10) DEFAULT 1 NOT NULL
+);
+
+CREATE OR REPLACE TRIGGER trie_table_tr
+before insert on trie_table FOR EACH ROW
+BEGIN
+ auto_id(:NEW.id);
+END trie_table_tr;
+/
+BEGIN map2users('trie_table'); END;
+/
+INSERT INTO version (table_name, table_version) values ('dr_partitions','1');
+CREATE TABLE dr_partitions (
+ id NUMBER(10) PRIMARY KEY,
+ partition_name VARCHAR2(255),
+ db_url VARCHAR2(255),
+ trie_table VARCHAR2(255)
+);
+
+CREATE OR REPLACE TRIGGER dr_partitions_tr
+before insert on dr_partitions FOR EACH ROW
+BEGIN
+ auto_id(:NEW.id);
+END dr_partitions_tr;
+/
+BEGIN map2users('dr_partitions'); END;
+/
diff --git a/scripts/pi_http/pi_framework.xml b/scripts/pi_http/pi_framework.xml
index 58963803caf..eeba67c984d 100644
--- a/scripts/pi_http/pi_framework.xml
+++ b/scripts/pi_http/pi_framework.xml
@@ -1044,6 +1044,24 @@
fromtagDB_STR
directionDB_STR
+
+
+ trie_table
+ mysql
+ ruleidDB_INT
+ prefixDB_STR
+ attrsDB_STR
+ priorityDB_INT
+
+
+
+ dr_partitions
+ mysql
+ idDB_INT
+ partition_nameDB_STR
+ db_urlDB_STR
+ trie_tableDB_STR
+
userblacklist
@@ -5067,6 +5085,88 @@
+
+ trie_table
+ show
+ trie_table
+ DB_QUERY
+
+ ruleidupdate
+ prefix
+ attrs
+ priority
+
+
+ add
+ trie_table
+ DB_INSERT
+
+ prefix
+ attrs
+ priority
+
+
+ update
+ trie_table
+ DB_UPDATE
+
+ ruleid=
+
+
+ prefix
+ attrs
+ priority
+
+
+ delete
+ trie_table
+ DB_DELETE
+
+ ruleid=
+
+
+
+
+ dr_partitions
+ show
+ dr_partitions
+ DB_QUERY
+
+ idupdate
+ partition_name
+ db_url
+ trie_table
+
+
+ add
+ dr_partitions
+ DB_INSERT
+
+ partition_name
+ db_url
+ trie_table
+
+
+ update
+ dr_partitions
+ DB_UPDATE
+
+ id=
+
+
+ partition_name
+ db_url
+ trie_table
+
+
+ delete
+ dr_partitions
+ DB_DELETE
+
+ id=
+
+
+
userblacklist
show
diff --git a/scripts/pi_http/trie-mod b/scripts/pi_http/trie-mod
new file mode 100644
index 00000000000..5fe4e835e59
--- /dev/null
+++ b/scripts/pi_http/trie-mod
@@ -0,0 +1,82 @@
+
+ trie_table
+ show
+ trie_table
+ DB_QUERY
+
+ ruleidupdate
+ prefix
+ attrs
+ priority
+
+
+ add
+ trie_table
+ DB_INSERT
+
+ prefix
+ attrs
+ priority
+
+
+ update
+ trie_table
+ DB_UPDATE
+
+ ruleid=
+
+
+ prefix
+ attrs
+ priority
+
+
+ delete
+ trie_table
+ DB_DELETE
+
+ ruleid=
+
+
+
+
+ dr_partitions
+ show
+ dr_partitions
+ DB_QUERY
+
+ idupdate
+ partition_name
+ db_url
+ trie_table
+
+
+ add
+ dr_partitions
+ DB_INSERT
+
+ partition_name
+ db_url
+ trie_table
+
+
+ update
+ dr_partitions
+ DB_UPDATE
+
+ id=
+
+
+ partition_name
+ db_url
+ trie_table
+
+
+ delete
+ dr_partitions
+ DB_DELETE
+
+ id=
+
+
+
diff --git a/scripts/pi_http/trie-table b/scripts/pi_http/trie-table
new file mode 100644
index 00000000000..263f0ac6b9d
--- /dev/null
+++ b/scripts/pi_http/trie-table
@@ -0,0 +1,18 @@
+
+
+ trie_table
+ mysql
+ ruleidDB_INT
+ prefixDB_STR
+ attrsDB_STR
+ priorityDB_INT
+
+
+
+ dr_partitions
+ mysql
+ idDB_INT
+ partition_nameDB_STR
+ db_urlDB_STR
+ trie_tableDB_STR
+
diff --git a/scripts/postgres/trie-create.sql b/scripts/postgres/trie-create.sql
new file mode 100644
index 00000000000..fd2a8f23bbe
--- /dev/null
+++ b/scripts/postgres/trie-create.sql
@@ -0,0 +1,18 @@
+INSERT INTO version (table_name, table_version) values ('trie_table','1');
+CREATE TABLE trie_table (
+ ruleid SERIAL PRIMARY KEY NOT NULL,
+ prefix VARCHAR(64) NOT NULL,
+ attrs VARCHAR(255) DEFAULT NULL,
+ priority INTEGER DEFAULT 1 NOT NULL
+);
+
+ALTER SEQUENCE trie_table_ruleid_seq MAXVALUE 2147483647 CYCLE;
+INSERT INTO version (table_name, table_version) values ('dr_partitions','1');
+CREATE TABLE dr_partitions (
+ id SERIAL PRIMARY KEY NOT NULL,
+ partition_name VARCHAR(255) NOT NULL,
+ db_url VARCHAR(255) NOT NULL,
+ trie_table VARCHAR(255)
+);
+
+ALTER SEQUENCE dr_partitions_id_seq MAXVALUE 2147483647 CYCLE;
diff --git a/scripts/sqlite/trie-create.sql b/scripts/sqlite/trie-create.sql
new file mode 100644
index 00000000000..585e7dcbb97
--- /dev/null
+++ b/scripts/sqlite/trie-create.sql
@@ -0,0 +1,16 @@
+INSERT INTO version (table_name, table_version) values ('trie_table','1');
+CREATE TABLE trie_table (
+ ruleid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ prefix CHAR(64) NOT NULL,
+ attrs CHAR(255) DEFAULT NULL,
+ priority INTEGER DEFAULT 1 NOT NULL
+);
+
+INSERT INTO version (table_name, table_version) values ('dr_partitions','1');
+CREATE TABLE dr_partitions (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ partition_name CHAR(255) NOT NULL,
+ db_url CHAR(255) NOT NULL,
+ trie_table CHAR(255)
+);
+