From 15c856219cf0ff9217e6a989e3d86b4cf0dffc8a Mon Sep 17 00:00:00 2001
From: Roman Khlopkov <roman.khlopkov@demlabs.net>
Date: Mon, 27 Sep 2021 19:20:43 +0300
Subject: [PATCH] [+] PostgreSQL driver base

---
 dap-sdk/crypto/include/dap_hash.h             |  22 +-
 dap-sdk/net/core/dap_events_socket.c          |   2 +-
 modules/chain/dap_chain_ledger.c              |   3 +-
 modules/global-db/CMakeLists.txt              |  30 +-
 .../global-db/dap_chain_global_db_driver.c    |   5 +
 .../dap_chain_global_db_driver_mdbx.c         |   1 +
 .../dap_chain_global_db_driver_pgsql.c        | 997 ++++++++++++++++++
 .../dap_chain_global_db_driver_pgsql.h        |   8 +
 modules/net/dap_chain_node_cli.c              |   2 +-
 modules/net/dap_chain_node_cli_cmd_tx.c       |   2 +-
 modules/type/dag/dap_chain_cs_dag.c           |   7 +-
 11 files changed, 1039 insertions(+), 40 deletions(-)
 create mode 100644 modules/global-db/dap_chain_global_db_driver_pgsql.c
 create mode 100644 modules/global-db/include/dap_chain_global_db_driver_pgsql.h

diff --git a/dap-sdk/crypto/include/dap_hash.h b/dap-sdk/crypto/include/dap_hash.h
index c8804a1bc1..f247fa5b58 100755
--- a/dap-sdk/crypto/include/dap_hash.h
+++ b/dap-sdk/crypto/include/dap_hash.h
@@ -36,7 +36,7 @@
 
 #define DAP_HASH_FAST_SIZE  32
 #define DAP_CHAIN_HASH_FAST_SIZE    32
-#define DAP_CHAIN_HASH_FAST_STR_SIZE (DAP_CHAIN_HASH_FAST_SIZE * 2 + 3)
+#define DAP_CHAIN_HASH_FAST_STR_SIZE (DAP_CHAIN_HASH_FAST_SIZE * 2 + 2 /* heading 0x */ + 1 /*trailing zero*/)
 #define DAP_CHAIN_HASH_MAX_SIZE 63
 
 typedef enum dap_hash_type {
@@ -100,36 +100,32 @@ static inline bool dap_hash_fast_is_blank( dap_hash_fast_t *a_hash )
 
 DAP_STATIC_INLINE int dap_chain_hash_fast_to_str( dap_hash_fast_t *a_hash, char *a_str, size_t a_str_max )
 {
-    if(!a_str )
+    if(! a_hash )
         return -1;
     if(! a_str )
         return -2;
-    if( a_str_max < (DAP_CHAIN_HASH_FAST_SIZE * 2 + 2) )
+    if( a_str_max < DAP_CHAIN_HASH_FAST_STR_SIZE )
         return -3;
     a_str[0] = '0';
     a_str[1] = 'x';
-    a_str[ DAP_CHAIN_HASH_FAST_SIZE * 2 + 2] = 0;
+    a_str[ DAP_CHAIN_HASH_FAST_STR_SIZE - 1 ] = 0;
     dap_htoa64((a_str + 2), a_hash->raw, DAP_CHAIN_HASH_FAST_SIZE);
-    return DAP_CHAIN_HASH_FAST_SIZE * 2 + 2;
+    return DAP_CHAIN_HASH_FAST_STR_SIZE;
 }
 
-DAP_STATIC_INLINE int dap_hash_fast_to_str(dap_hash_fast_t *a_hash, char *a_str, size_t a_str_max){
-    return dap_chain_hash_fast_to_str(a_hash,a_str,a_str_max);
-}
+#define dap_hash_fast_to_str dap_chain_hash_fast_to_str
 
 DAP_STATIC_INLINE char *dap_chain_hash_fast_to_str_new(dap_hash_fast_t * a_hash)
 {
-    const size_t c_hash_str_size = sizeof(*a_hash)*2 +1 /*trailing zero*/ +2 /* heading 0x */+4/*just to be sure*/ ;
+    const size_t c_hash_str_size = DAP_CHAIN_HASH_FAST_STR_SIZE;
     char * ret = DAP_NEW_Z_SIZE(char, c_hash_str_size);
     if(dap_chain_hash_fast_to_str( a_hash, ret, c_hash_str_size ) < 0 )
         DAP_DEL_Z(ret);
     return ret;
 }
 
-DAP_STATIC_INLINE char *dap_hash_fast_to_str_new(dap_hash_fast_t * a_hash)
-{
-    return dap_chain_hash_fast_to_str_new(a_hash);
-}
+#define dap_hash_fast_to_str_new dap_chain_hash_fast_to_str_new
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/dap-sdk/net/core/dap_events_socket.c b/dap-sdk/net/core/dap_events_socket.c
index 982eccfb68..dc53aa3411 100644
--- a/dap-sdk/net/core/dap_events_socket.c
+++ b/dap-sdk/net/core/dap_events_socket.c
@@ -2117,7 +2117,7 @@ size_t dap_events_socket_write_unsafe(dap_events_socket_t *a_es, const void * a_
             a_es->buf_out = DAP_REALLOC(a_es->buf_out, l_new_size);
             a_es->buf_out_size_max = l_new_size;
         }
-    }
+     }
      a_data_size = (a_es->buf_out_size + a_data_size < a_es->buf_out_size_max) ? a_data_size : (a_es->buf_out_size_max - a_es->buf_out_size);
      memcpy(a_es->buf_out + a_es->buf_out_size, a_data, a_data_size);
      a_es->buf_out_size += a_data_size;
diff --git a/modules/chain/dap_chain_ledger.c b/modules/chain/dap_chain_ledger.c
index d9eaf907ae..69d885f18e 100644
--- a/modules/chain/dap_chain_ledger.c
+++ b/modules/chain/dap_chain_ledger.c
@@ -290,8 +290,7 @@ int dap_chain_ledger_token_decl_add_check(dap_ledger_t * a_ledger,  dap_chain_da
     HASH_FIND_STR(PVT(a_ledger)->tokens,a_token->ticker,l_token_item);
     pthread_rwlock_unlock(&PVT(a_ledger)->tokens_rwlock);
     if ( l_token_item != NULL ){
-        if(s_debug_more)
-            log_it(L_WARNING,"Duplicate token declaration for ticker '%s' ", a_token->ticker);
+        log_it(L_WARNING,"Duplicate token declaration for ticker '%s' ", a_token->ticker);
         return -3;
     }
     // Checks passed
diff --git a/modules/global-db/CMakeLists.txt b/modules/global-db/CMakeLists.txt
index 7ab3deeefb..31122a6cd4 100644
--- a/modules/global-db/CMakeLists.txt
+++ b/modules/global-db/CMakeLists.txt
@@ -1,31 +1,23 @@
 cmake_minimum_required(VERSION 3.1)
 project (dap_chain_global_db C)
-  
-set(DAP_CHAIN_GLOBAL_DB_SRC
-            dap_chain_global_db.c
-            dap_chain_global_db_driver.c
-            dap_chain_global_db_driver_cdb.c
-            dap_chain_global_db_driver_sqlite.c
-            dap_chain_global_db_hist.c
-            dap_chain_global_db_remote.c
-    )
-set(DAP_CHAIN_GLOBAL_DB_HDR
-        include/dap_chain_global_db.h
-        include/dap_chain_global_db_driver.h
-        include/dap_chain_global_db_driver_cdb.h
-        include/dap_chain_global_db_driver_sqlite.h
-        include/dap_chain_global_db_hist.h
-        include/dap_chain_global_db_remote.h
-    )
+
+set(BUILD_WITH_GDB_DRIVER_PGSQL ON)
+
+file(GLOB DAP_CHAIN_GLOBAL_DB_SRC *.c)
+file(GLOB DAP_CHAIN_GLOBAL_DB_HDR include/*.h)
+
 set(DAP_CHAIN_GLOBAL_DB_LIBS dap_core dap_crypto dap_chain sqlite3 dap_cuttdb json-c)
 
 if(BUILD_WITH_GDB_DRIVER_MDBX)
-    set(DAP_CHAIN_GLOBAL_DB_SRC ${DAP_CHAIN_GLOBAL_DB_SRC} dap_chain_global_db_driver_mdbx.c)
-    set(DAP_CHAIN_GLOBAL_DB_HDR ${DAP_CHAIN_GLOBAL_DB_HDR} include/dap_chain_global_db_driver_mdbx.h)
     set(DAP_CHAIN_GLOBAL_DB_LIBS ${DAP_CHAIN_GLOBAL_DB_LIBS} mdbx-static)
     add_definitions ("-DDAP_CHAIN_GDB_ENGINE_MDBX")
 endif()
 
+if(BUILD_WITH_GDB_DRIVER_PGSQL)
+    set(DAP_CHAIN_GLOBAL_DB_LIBS ${DAP_CHAIN_GLOBAL_DB_LIBS} pq)
+    add_definitions ("-DDAP_CHAIN_GDB_ENGINE_PGSQL")
+endif()
+
 add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_GLOBAL_DB_SRC} ${DAP_CHAIN_GLOBAL_DB_HDR})
 
 target_link_libraries(${PROJECT_NAME}  ${DAP_CHAIN_GLOBAL_DB_LIBS})
diff --git a/modules/global-db/dap_chain_global_db_driver.c b/modules/global-db/dap_chain_global_db_driver.c
index ed6511d71d..2acb41cae4 100644
--- a/modules/global-db/dap_chain_global_db_driver.c
+++ b/modules/global-db/dap_chain_global_db_driver.c
@@ -38,6 +38,7 @@
 #include "dap_chain_global_db_driver_sqlite.h"
 #include "dap_chain_global_db_driver_cdb.h"
 #include "dap_chain_global_db_driver_mdbx.h"
+#include "dap_chain_global_db_driver_pgsql.h"
 #include "dap_chain_global_db_driver.h"
 
 #define LOG_TAG "db_driver"
@@ -103,6 +104,10 @@ int dap_db_driver_init(const char *a_driver_name, const char *a_filename_db)
 #ifdef DAP_CHAIN_GDB_ENGINE_MDBX
     else if(!dap_strcmp(s_used_driver, "mdbx"))
         l_ret = dap_db_driver_mdbx_init(l_db_path_ext, &s_drv_callback);
+#endif
+#ifdef DAP_CHAIN_GDB_ENGINE_PGSQL
+    else if(!dap_strcmp(s_used_driver, "pgsql"))
+        l_ret = dap_db_driver_pgsql_init(l_db_path_ext, &s_drv_callback);
 #endif
     else
         log_it(L_ERROR, "Unknown global_db driver \"%s\"", a_driver_name);
diff --git a/modules/global-db/dap_chain_global_db_driver_mdbx.c b/modules/global-db/dap_chain_global_db_driver_mdbx.c
index da15504224..91151f0b2c 100644
--- a/modules/global-db/dap_chain_global_db_driver_mdbx.c
+++ b/modules/global-db/dap_chain_global_db_driver_mdbx.c
@@ -200,5 +200,6 @@ static int s_driver_callback_apply_store_obj(pdap_store_obj_t a_store_obj)
         return -1;
     }
     int ret = 0;
+    return ret;
 }
 
diff --git a/modules/global-db/dap_chain_global_db_driver_pgsql.c b/modules/global-db/dap_chain_global_db_driver_pgsql.c
new file mode 100644
index 0000000000..fc56c2fc32
--- /dev/null
+++ b/modules/global-db/dap_chain_global_db_driver_pgsql.c
@@ -0,0 +1,997 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * CellFrame       https://cellframe.net
+ * Sources         https://gitlab.demlabs.net/cellframe
+ * Copyright  (c) 2017-2019
+ * All rights reserved.
+
+ This file is part of CellFrame SDK the open source project
+
+    CellFrame SDK is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    CellFrame SDK 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 any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stddef.h>
+#include <string.h>
+#include <pthread.h>
+#include <errno.h>
+
+#ifdef DAP_OS_UNIX
+#include <unistd.h>
+#endif
+#include "dap_common.h"
+#include "dap_hash.h"
+#include "dap_file_utils.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "dap_chain_global_db_driver_pgsql.h"
+
+#define LOG_TAG "db_pgsql"
+
+static PGconn *s_conn_pool;
+static pthread_rwlock_t s_db_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+/**
+ * SQLite library initialization, no thread safe
+ *
+ * return 0 if Ok, else error code >0
+ */
+int dap_db_driver_pgsql_init(const char *a_filename_dir, dap_db_driver_callbacks_t *a_drv_callback)
+{
+    // Check paths and create them if nessesary
+    if(!dap_dir_test(a_filename_dir)){
+        log_it(L_NOTICE, "No directory %s, trying to create...", a_filename_dir);
+        int l_mkdir_ret = dap_mkdir_with_parents(a_filename_dir);
+        int l_errno = errno;
+        if(!dap_dir_test(a_filename_dir)){
+            char l_errbuf[255];
+            l_errbuf[0] = '\0';
+            strerror_r(l_errno,l_errbuf,sizeof(l_errbuf));
+            log_it(L_ERROR, "Can't create directory, error code %d, error string \"%s\"", l_mkdir_ret, l_errbuf);
+            return -1;
+        }else
+            log_it(L_NOTICE,"Directory created");
+    }
+    dap_hash_fast_t l_dir_hash;
+    dap_chain_hash_fast_from_str(a_filename_dir, &l_dir_hash);
+    char *l_db_name = dap_hash_fast_to_str_new(&l_dir_hash);
+    // Open PostgreSQL database, create if nessesary
+    const char *l_base_conn_str = "dbname = postgres";
+    char *l_error_message = NULL;
+    PGconn *l_base_conn = PQconnectdb(l_base_conn_str);
+    if (PQstatus(l_base_conn) != CONNECTION_OK) {
+        l_error_message = PQerrorMessage(l_base_conn);
+        log_it(L_ERROR, "Can't init PostgreSQL database: \"%s\"", l_error_message);
+        DAP_DELETE(l_error_message);
+        return -2;
+    }
+    char *l_query_str = dap_strdup_printf("SELECT EXISTS (SELECT * FROM pg_database WHERE datname = 'ololo')");//, l_db_name);
+    PGresult *l_res = PQexec(l_base_conn, l_query_str);
+    if (PQresultStatus(l_res) != PGRES_TUPLES_OK) {
+        log_it(L_ERROR, "Can't read PostgreSQL database: \"%s\"", PQresultErrorMessage(l_res));
+        return -3;
+    }
+    return 0;
+}
+#if 0
+a_drv_callback->apply_store_obj = dap_db_driver_sqlite_apply_store_obj;
+a_drv_callback->read_store_obj = dap_db_driver_sqlite_read_store_obj;
+a_drv_callback->read_cond_store_obj = dap_db_driver_sqlite_read_cond_store_obj;
+a_drv_callback->read_last_store_obj = dap_db_driver_sqlite_read_last_store_obj;
+a_drv_callback->transaction_start = dap_db_driver_sqlite_start_transaction;
+a_drv_callback->transaction_end = dap_db_driver_sqlite_end_transaction;
+a_drv_callback->get_groups_by_mask  = dap_db_driver_sqlite_get_groups_by_mask;
+a_drv_callback->read_count_store = dap_db_driver_sqlite_read_count_store;
+a_drv_callback->is_obj = dap_db_driver_sqlite_is_obj;
+a_drv_callback->deinit = dap_db_driver_sqlite_deinit;
+a_drv_callback->flush = dap_db_driver_sqlite_flush;
+
+/* Set always-secure search path, so malicious users can't take
+control. */
+res = PQexec(conn,
+"SELECT pg_catalog.set_config('search_path', '',
+false)");
+if (PQresultStatus(res) != PGRES_TUPLES_OK)
+{
+fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+PQclear(res);
+exit_nicely(conn);
+}
+/*
+* Should PQclear PGresult whenever it is no longer needed to
+avoid memory
+* leaks
+*/
+PQclear(res);
+/*
+* Our test case here involves using a cursor, for which we
+must be inside
+* a transaction block. We could do the whole thing with a
+single
+* PQexec() of "select * from pg_database", but that's too
+trivial to make
+* a good example.
+*/
+/* Start a transaction block */
+res = PQexec(conn, "BEGIN");
+if (PQresultStatus(res) != PGRES_COMMAND_OK)
+{
+fprintf(stderr, "BEGIN command failed: %s",
+PQerrorMessage(conn));
+PQclear(res);
+exit_nicely(conn);
+}
+893
+libpq — C Library
+PQclear(res);
+/*
+* Fetch rows from pg_database, the system catalog of databases
+*/
+res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from
+pg_database");
+if (PQresultStatus(res) != PGRES_COMMAND_OK)
+{
+fprintf(stderr, "DECLARE CURSOR failed: %s",
+PQerrorMessage(conn));
+PQclear(res);
+exit_nicely(conn);
+}
+PQclear(res);
+res = PQexec(conn, "FETCH ALL in myportal");
+if (PQresultStatus(res) != PGRES_TUPLES_OK)
+{
+fprintf(stderr, "FETCH ALL failed: %s",
+PQerrorMessage(conn));
+PQclear(res);
+exit_nicely(conn);
+}
+/* first, print out the attribute names */
+nFields = PQnfields(res);
+for (i = 0; i < nFields; i++)
+printf("%-15s", PQfname(res, i));
+printf("\n\n");
+/* next, print out the rows */
+for (i = 0; i < PQntuples(res); i++)
+{
+for (j = 0; j < nFields; j++)
+printf("%-15s", PQgetvalue(res, i, j));
+printf("\n");
+}
+PQclear(res);
+/* close the portal ... we don't bother to check for errors ...
+*/
+res = PQexec(conn, "CLOSE myportal");
+PQclear(res);
+/* end the transaction */
+res = PQexec(conn, "END");
+PQclear(res);
+/* close the connection to the database and cleanup */
+PQfinish(conn);
+return 0;
+}
+
+
+int dap_db_driver_sqlite_deinit(void)
+{
+        pthread_rwlock_wrlock(&s_db_rwlock);
+        if(!s_db){
+            pthread_rwlock_unlock(&s_db_rwlock);
+            return -666;
+        }
+        dap_db_driver_sqlite_close(s_db);
+        pthread_rwlock_unlock(&s_db_rwlock);
+        s_db = NULL;
+        return sqlite3_shutdown();
+}
+
+// additional function for sqlite to convert byte to number
+static void byte_to_bin(sqlite3_context *l_context, int a_argc, sqlite3_value **a_argv)
+{
+    const unsigned char *l_text;
+    if(a_argc != 1)
+        sqlite3_result_null(l_context);
+    l_text = (const unsigned char *) sqlite3_value_blob(a_argv[0]);
+    if(l_text && l_text[0])
+            {
+        int l_result = (int) l_text[0];
+        sqlite3_result_int(l_context, l_result);
+        return;
+    }
+    sqlite3_result_null(l_context);
+}
+
+/**
+ * Open SQLite database
+ * a_filename_utf8 - database file name
+ * a_flags - database access flags (SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
+ * a_error_message[out] - Error messages (the memory requires deletion via sqlite_free ())
+ *
+ * return: database identifier, NULL when an error occurs.
+ */
+sqlite3* dap_db_driver_sqlite_open(const char *a_filename_utf8, int a_flags, char **a_error_message)
+{
+    sqlite3 *l_db = NULL;
+
+    int l_rc = sqlite3_open_v2(a_filename_utf8, &l_db, a_flags | SQLITE_OPEN_NOMUTEX, NULL);
+    // if unable to open the database file
+    if(l_rc == SQLITE_CANTOPEN) {
+        log_it(L_WARNING,"No database on path %s, creating one from scratch", a_filename_utf8);
+        if(l_db)
+            sqlite3_close(l_db);
+        // try to create database
+        l_rc = sqlite3_open_v2(a_filename_utf8, &l_db, a_flags | SQLITE_OPEN_NOMUTEX| SQLITE_OPEN_CREATE, NULL);
+    }
+
+    if(l_rc != SQLITE_OK) {
+        log_it(L_CRITICAL,"Can't open database on path %s (code %d: \"%s\" )", a_filename_utf8, l_rc, sqlite3_errstr(l_rc));
+        if(a_error_message)
+            *a_error_message = sqlite3_mprintf("Can't open database: %s\n", sqlite3_errmsg(l_db));
+        sqlite3_close(l_db);
+        return NULL;
+    }
+    // added user functions
+    sqlite3_create_function(l_db, "byte_to_bin", 1, SQLITE_UTF8, NULL, &byte_to_bin, NULL, NULL);
+    return l_db;
+}
+
+/**
+ * Close the database
+ */
+void dap_db_driver_sqlite_close(sqlite3 *l_db)
+{
+    if(l_db)
+        sqlite3_close(l_db);
+}
+/*
+ * Clear the memory allocated via sqlite3_mprintf()
+ */
+void dap_db_driver_sqlite_free(char *memory)
+{
+    if(memory)
+        sqlite3_free(memory);
+}
+
+/**
+ * Set specific pragma statements
+ * www.sqlite.org/pragma.html
+ *
+ *PRAGMA page_size = bytes; // page size DB; it is reasonable to make it equal to the size of the disk cluster 4096
+ *PRAGMA cache_size = -kibibytes; // by default it is equal to 2000 pages of database
+ *PRAGMA encoding = "UTF-8";  // default = UTF-8
+ *PRAGMA foreign_keys = 1; // default = 0
+ *PRAGMA journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF;
+ *PRAGMA synchronous = 0 | OFF | 1 | NORMAL | 2 | FULL;
+ */
+bool dap_db_driver_sqlite_set_pragma(sqlite3 *a_db, char *a_param, char *a_mode)
+{
+    if(!a_param || !a_mode)
+            {
+        printf("[sqlite_set_pragma] err!!! no param or mode\n");
+        return false;
+    }
+    char *l_str_query = sqlite3_mprintf("PRAGMA %s = %s", a_param, a_mode);
+    int l_rc = dap_db_driver_sqlite_exec(a_db, l_str_query, NULL); // default synchronous=FULL
+    sqlite3_free(l_str_query);
+    if(l_rc == SQLITE_OK)
+        return true;
+    return false;
+}
+
+int dap_db_driver_sqlite_flush()
+{
+    log_it(L_DEBUG, "Start flush sqlite data base.");
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -666;
+    }
+    dap_db_driver_sqlite_close(s_db);
+    char *l_error_message = NULL;
+    s_db = dap_db_driver_sqlite_open(s_filename_db, SQLITE_OPEN_READWRITE, &l_error_message);
+    if(!s_db) {
+        pthread_rwlock_unlock(&s_db_rwlock);
+        log_it(L_ERROR, "Can't init sqlite err: \"%s\"", l_error_message? l_error_message: "UNKNOWN");
+        dap_db_driver_sqlite_free(l_error_message);
+        return -3;
+    }
+#ifndef _WIN32
+    sync();
+#endif
+    if(!dap_db_driver_sqlite_set_pragma(s_db, "synchronous", "NORMAL")) // 0 | OFF | 1 | NORMAL | 2 | FULL
+        log_it(L_WARNING, "Can't set new synchronous mode\n");
+    if(!dap_db_driver_sqlite_set_pragma(s_db, "journal_mode", "OFF")) // DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
+        log_it(L_WARNING, "Can't set new journal mode\n");
+
+    if(!dap_db_driver_sqlite_set_pragma(s_db, "page_size", "1024")) // DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
+        log_it(L_WARNING, "Can't set page_size\n");
+    pthread_rwlock_unlock(&s_db_rwlock);
+    return 0;
+}
+
+/**
+ * Execute SQL query to database that does not return data
+ *
+ * return 0 if Ok, else error code >0
+ */
+static int dap_db_driver_sqlite_exec(sqlite3 *l_db, const char *l_query, char **l_error_message)
+{
+    char *l_zErrMsg = NULL;
+    int l_rc = sqlite3_exec(l_db, l_query, NULL, 0, &l_zErrMsg);
+    //printf("%s\n",l_query);
+    if(l_rc != SQLITE_OK)
+    {
+        if(l_error_message && l_zErrMsg)
+            *l_error_message = sqlite3_mprintf("SQL error: %s", l_zErrMsg);
+        if(l_zErrMsg)
+            sqlite3_free(l_zErrMsg);
+        return l_rc;
+    }
+    if(l_zErrMsg)
+        sqlite3_free(l_zErrMsg);
+    return l_rc;
+}
+
+/**
+ * Create table
+ *
+ * return 0 if Ok, else error code
+ */
+static int dap_db_driver_sqlite_create_group_table(const char *a_table_name)
+{
+    char *l_error_message = NULL;
+    if(!s_db || !a_table_name)
+        return -1;
+    char *l_query =
+            dap_strdup_printf(
+                    "create table if not exists '%s'(id INTEGER NOT NULL PRIMARY KEY, key TEXT KEY, hash BLOB, ts INTEGER KEY, value BLOB)",
+                    a_table_name);
+    if(dap_db_driver_sqlite_exec(s_db, (const char*) l_query, &l_error_message) != SQLITE_OK)
+    {
+        log_it(L_ERROR, "Creatу_table : %s\n", l_error_message);
+        dap_db_driver_sqlite_free(l_error_message);
+        DAP_DELETE(l_query);
+        return -1;
+    }
+    DAP_DELETE(l_query);
+    // create unique index - key
+    l_query = dap_strdup_printf("create unique index if not exists 'idx_key_%s' ON '%s' (key)", a_table_name,
+            a_table_name);
+    if(dap_db_driver_sqlite_exec(s_db, (const char*) l_query, &l_error_message) != SQLITE_OK) {
+        log_it(L_ERROR, "Create unique index : %s\n", l_error_message);
+        dap_db_driver_sqlite_free(l_error_message);
+        DAP_DELETE(l_query);
+        return -1;
+    }
+    DAP_DELETE(l_query);
+    return 0;
+}
+
+/**
+ * Prepare SQL query for database
+ * l_query [in] SQL-string with a query to database, example:
+ * SELECT * FROM data
+ * SELECT id, sd FROM data LIMIT 300
+ * SELECT id, sd FROM data ORDER BY id ASC/DESC
+ * SELECT * FROM data WHERE time>449464766900000 and time<449464766910000"
+ * SELECT * FROM data WHERE hex(sd) LIKE '%370%'
+ * hex(x'0806') -> '08f6' или quote(sd) -> X'08f6'
+ * substr(x'031407301210361320690000',3,2) -> x'0730'
+ *
+ * CAST(substr(sd,5,2) as TEXT)
+ * additional function of line to number _uint8
+ * byte_to_bin(x'ff') -> 255
+ */
+static int dap_db_driver_sqlite_query(sqlite3 *db, char *query, sqlite3_stmt **l_res, char **l_error_message)
+{
+    const char *pzTail; // OUT: Pointer to unused portion of zSql
+    int l_rc = sqlite3_prepare_v2(db, query, -1, l_res, &pzTail);
+    if(l_rc != SQLITE_OK)
+    {
+        if(l_error_message)
+        {
+            const char *zErrMsg = sqlite3_errmsg(db);
+            if(zErrMsg)
+                *l_error_message = sqlite3_mprintf("SQL Query error: %s\n", zErrMsg);
+        }
+        return l_rc;
+    }
+    return l_rc;
+}
+
+/**
+ * Clear memory after fetching a string
+ *
+ * return 0 if Ok, else -1
+ */
+static void dap_db_driver_sqlite_row_free(SQLITE_ROW_VALUE *row)
+{
+    if(row) {
+        // delete the whole string
+        sqlite3_free(row->val);
+        // delete structure
+        sqlite3_free(row);
+    }
+}
+
+/**
+ * Selects the next entry from the result of the query and returns an array
+ *
+ * l_res: identifier received in sqlite_query ()
+ * l_row_out [out]: pointer to a column or NULL
+ *
+ * return:
+ * SQLITE_ROW(100) has another row ready
+ * SQLITE_DONE(101) finished executing,
+ * SQLITE_CONSTRAINT(19) data is not unique and will not be added
+ */
+static int dap_db_driver_sqlite_fetch_array(sqlite3_stmt *l_res, SQLITE_ROW_VALUE **l_row_out)
+{
+    SQLITE_ROW_VALUE *l_row = NULL;
+    // go to next the string
+    int l_rc = sqlite3_step(l_res);
+    if(l_rc == SQLITE_ROW) // SQLITE_ROW(100) or SQLITE_DONE(101) or SQLITE_BUSY(5)
+    {
+        int l_iCol; // number of the column in the row
+        // allocate memory for a row with data
+        l_row = (SQLITE_ROW_VALUE*) sqlite3_malloc(sizeof(SQLITE_ROW_VALUE));
+        int l_count = sqlite3_column_count(l_res); // get the number of columns
+        // allocate memory for all columns
+        l_row->val = (SQLITE_VALUE*) sqlite3_malloc(l_count * (int)sizeof(SQLITE_VALUE));
+        if(l_row->val)
+        {
+            l_row->count = l_count; // number of columns
+            for(l_iCol = 0; l_iCol < l_row->count; l_iCol++)
+                    {
+                SQLITE_VALUE *cur_val = l_row->val + l_iCol;
+                cur_val->len = sqlite3_column_bytes(l_res, l_iCol); // how many bytes will be needed
+                cur_val->type = (signed char)sqlite3_column_type(l_res, l_iCol); // field type
+                if(cur_val->type == SQLITE_INTEGER)
+                {
+                    cur_val->val.val_int64 = sqlite3_column_int64(l_res, l_iCol);
+                }
+                else if(cur_val->type == SQLITE_FLOAT)
+                    cur_val->val.val_float = sqlite3_column_double(l_res, l_iCol);
+                else if(cur_val->type == SQLITE_BLOB)
+                    cur_val->val.val_blob = (const unsigned char*) sqlite3_column_blob(l_res, l_iCol);
+                else if(cur_val->type == SQLITE_TEXT)
+                    cur_val->val.val_str = (const char*) sqlite3_column_text(l_res, l_iCol); //sqlite3_mprintf("%s",sqlite3_column_text(l_res,iCol));
+                else
+                    cur_val->val.val_str = NULL;
+            }
+        }
+        else
+            l_row->count = 0; // number of columns
+    }
+    if(l_row_out)
+        *l_row_out = l_row;
+    else
+        dap_db_driver_sqlite_row_free(l_row);
+    return l_rc;
+}
+
+/**
+ * Clear memory when request processing is complete
+ */
+static bool dap_db_driver_sqlite_query_free(sqlite3_stmt *l_res)
+{
+    if(!l_res)
+        return false;
+    int rc = sqlite3_finalize(l_res);
+    if(rc != SQLITE_OK)
+        return false;
+    return true;
+}
+
+/**
+ * Convert the array into a string to save to blob
+ */
+static char* dap_db_driver_get_string_from_blob(uint8_t *blob, int len)
+{
+    char *str_out;
+    int ret;
+    if(!blob)
+        return NULL;
+    str_out = (char*) sqlite3_malloc(len * 2 + 1);
+    ret = (int)dap_bin2hex(str_out, (const void*)blob, (size_t)len);
+    str_out[len * 2] = 0;
+    return str_out;
+
+}
+
+/**
+ * Cleaning the database from the deleted data
+ *
+ * return 0 if Ok, else error code >0
+ */
+int dap_db_driver_sqlite_vacuum(sqlite3 *l_db)
+{
+    if(!s_db)
+        return -1;
+    int l_rc = dap_db_driver_sqlite_exec(l_db, "VACUUM", NULL);
+    return l_rc;
+}
+
+/**
+ * Start a transaction
+ */
+int dap_db_driver_sqlite_start_transaction(void)
+{
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -666;
+    }
+
+    if(SQLITE_OK == dap_db_driver_sqlite_exec(s_db, "BEGIN", NULL)){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return 0;
+    }else{
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -1;
+    }
+}
+
+/**
+ * End of transaction
+ */
+int dap_db_driver_sqlite_end_transaction(void)
+{
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -666;
+    }
+    if(SQLITE_OK == dap_db_driver_sqlite_exec(s_db, "COMMIT", NULL)){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return 0;
+    }else{
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -1;
+    }
+}
+
+char *dap_db_driver_sqlite_make_group_name(const char *a_table_name)
+{
+    char *l_table_name = dap_strdup(a_table_name);
+    ssize_t l_table_name_len = (ssize_t)dap_strlen(l_table_name);
+    const char *l_needle = "_";
+    // replace '_' to '.'
+    while(1){
+    char *l_str = dap_strstr_len(l_table_name, l_table_name_len, l_needle);
+    if(l_str)
+        *l_str = '.';
+    else
+        break;
+    }
+    return l_table_name;
+}
+
+char *dap_db_driver_sqlite_make_table_name(const char *a_group_name)
+{
+    char *l_group_name = dap_strdup(a_group_name);
+    ssize_t l_group_name_len = (ssize_t)dap_strlen(l_group_name);
+    const char *l_needle = ".";
+    // replace '.' to '_'
+    while(1){
+    char *l_str = dap_strstr_len(l_group_name, l_group_name_len, l_needle);
+    if(l_str)
+        *l_str = '_';
+    else
+        break;
+    }
+    return l_group_name;
+}
+
+/**
+ * Apply data (write or delete)
+ *
+ */
+int dap_db_driver_sqlite_apply_store_obj(dap_store_obj_t *a_store_obj)
+{
+    if(!a_store_obj || !a_store_obj->group )
+        return -1;
+    char *l_query = NULL;
+    char *l_error_message = NULL;
+    char *l_table_name = dap_db_driver_sqlite_make_table_name(a_store_obj->group);
+    if(a_store_obj->type == 'a') {
+        if(!a_store_obj->key || !a_store_obj->value || !a_store_obj->value_len)
+            return -1;
+        //dap_chain_hash_fast_t l_hash;
+        //dap_hash_fast(a_store_obj->value, a_store_obj->value_len, &l_hash);
+
+        char *l_blob_hash = "";//dap_db_driver_get_string_from_blob((uint8_t*) &l_hash, sizeof(dap_chain_hash_fast_t));
+        char *l_blob_value = dap_db_driver_get_string_from_blob(a_store_obj->value, (int)a_store_obj->value_len);
+        //add one record
+        l_query = sqlite3_mprintf("insert into '%s' values(NULL, '%s', x'%s', '%lld', x'%s')",
+                                   l_table_name, a_store_obj->key, l_blob_hash, a_store_obj->timestamp, l_blob_value);
+        //dap_db_driver_sqlite_free(l_blob_hash);
+        dap_db_driver_sqlite_free(l_blob_value);
+    }
+    else if(a_store_obj->type == 'd') {
+        //delete one record
+        if(a_store_obj->key)
+            l_query = sqlite3_mprintf("delete from '%s' where key = '%s'",
+                                      l_table_name, a_store_obj->key);
+        // remove all group
+        else {
+            l_query = sqlite3_mprintf("drop table if exists '%s'", l_table_name);
+        }
+    }
+    else {
+        log_it(L_ERROR, "Unknown store_obj type '0x%x'", a_store_obj->type);
+        return -1;
+    }
+    // execute request
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return -666;
+    }
+
+    int l_ret = dap_db_driver_sqlite_exec(s_db, l_query, &l_error_message);
+    if(l_ret == SQLITE_ERROR) {
+        dap_db_driver_sqlite_free(l_error_message);
+        l_error_message = NULL;
+        // create table
+        dap_db_driver_sqlite_create_group_table(l_table_name);
+        // repeat request
+        l_ret = dap_db_driver_sqlite_exec(s_db, l_query, &l_error_message);
+
+    }
+    // entry with the same hash is already present
+    if(l_ret == SQLITE_CONSTRAINT) {
+        dap_db_driver_sqlite_free(l_error_message);
+        l_error_message = NULL;
+        //delete exist record
+        char *l_query_del = sqlite3_mprintf("delete from '%s' where key = '%s'", l_table_name, a_store_obj->key);
+        l_ret = dap_db_driver_sqlite_exec(s_db, l_query_del, &l_error_message);
+        dap_db_driver_sqlite_free(l_query_del);
+        if(l_ret != SQLITE_OK) {
+            log_it(L_INFO, "Entry with the same key is already present and can't delete, %s", l_error_message);
+            dap_db_driver_sqlite_free(l_error_message);
+            l_error_message = NULL;
+        }
+        // repeat request
+        l_ret = dap_db_driver_sqlite_exec(s_db, l_query, &l_error_message);
+    }
+    pthread_rwlock_unlock(&s_db_rwlock);
+    // missing database
+    if(l_ret != SQLITE_OK) {
+        log_it(L_ERROR, "sqlite apply error: %s", l_error_message);
+        dap_db_driver_sqlite_free(l_error_message);
+        l_ret = -1;
+    }
+    dap_db_driver_sqlite_free(l_query);
+    DAP_DELETE(l_table_name);
+    return l_ret;
+}
+
+static void fill_one_item(const char *a_group, dap_store_obj_t *a_obj, SQLITE_ROW_VALUE *a_row)
+{
+    a_obj->group = dap_strdup(a_group);
+
+    for(int l_iCol = 0; l_iCol < a_row->count; l_iCol++) {
+        SQLITE_VALUE *l_cur_val = a_row->val + l_iCol;
+        switch (l_iCol) {
+        case 0:
+            if(l_cur_val->type == SQLITE_INTEGER)
+                a_obj->id = (uint64_t)l_cur_val->val.val_int64;
+            break; // id
+        case 1:
+            if(l_cur_val->type == SQLITE_INTEGER)
+                a_obj->timestamp = l_cur_val->val.val_int64;
+            break; // ts
+        case 2:
+            if(l_cur_val->type == SQLITE_TEXT)
+                a_obj->key = dap_strdup(l_cur_val->val.val_str);
+            break; // key
+        case 3:
+            if(l_cur_val->type == SQLITE_BLOB)
+            {
+                a_obj->value_len = (size_t) l_cur_val->len;
+                a_obj->value = DAP_NEW_SIZE(uint8_t, a_obj->value_len);
+                memcpy(a_obj->value, l_cur_val->val.val_blob, a_obj->value_len);
+            }
+            break; // value
+        }
+    }
+
+}
+
+/**
+ * Read last items
+ *
+ * a_group - group name
+ */
+dap_store_obj_t* dap_db_driver_sqlite_read_last_store_obj(const char *a_group)
+{
+
+    dap_store_obj_t *l_obj = NULL;
+    char *l_error_message = NULL;
+    sqlite3_stmt *l_res;
+    if(!a_group)
+        return NULL;
+    char * l_table_name = dap_db_driver_sqlite_make_table_name(a_group);
+    char *l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' ORDER BY id DESC LIMIT 1", l_table_name);
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return NULL;
+    }
+
+    int l_ret = dap_db_driver_sqlite_query(s_db, l_str_query, &l_res, &l_error_message);
+    pthread_rwlock_unlock(&s_db_rwlock);
+    sqlite3_free(l_str_query);
+    DAP_DEL_Z(l_table_name);
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "read last l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        dap_db_driver_sqlite_free(l_error_message);
+        return NULL;
+    }
+
+    SQLITE_ROW_VALUE *l_row = NULL;
+    l_ret = dap_db_driver_sqlite_fetch_array(l_res, &l_row);
+    if(l_ret != SQLITE_ROW && l_ret != SQLITE_DONE)
+    {
+        //log_it(L_ERROR, "read l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+    }
+    if(l_ret == SQLITE_ROW && l_row) {
+        l_obj = DAP_NEW_Z(dap_store_obj_t);
+        fill_one_item(a_group, l_obj, l_row);
+    }
+    dap_db_driver_sqlite_row_free(l_row);
+    dap_db_driver_sqlite_query_free(l_res);
+
+    return l_obj;
+}
+
+/**
+ * Read several items with conditoin
+ *
+ * a_group - group name
+ * a_id - read from this id
+ * a_count_out[in], how many items to read, 0 - no limits
+ * a_count_out[out], how many items was read
+ */
+dap_store_obj_t* dap_db_driver_sqlite_read_cond_store_obj(const char *a_group, uint64_t a_id, size_t *a_count_out)
+{
+    dap_store_obj_t *l_obj = NULL;
+    char *l_error_message = NULL;
+    sqlite3_stmt *l_res;
+    if(!a_group)
+        return NULL;
+
+    char * l_table_name = dap_db_driver_sqlite_make_table_name(a_group);
+    // no limit
+    int l_count_out = 0;
+    if(a_count_out)
+        l_count_out = (int)*a_count_out;
+    char *l_str_query;
+    if(l_count_out)
+        l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' WHERE id>='%lld' ORDER BY id ASC LIMIT %d",
+                l_table_name, a_id, l_count_out);
+    else
+        l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' WHERE id>='%lld' ORDER BY id ASC",
+                l_table_name, a_id);
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    if(!s_db){
+        pthread_rwlock_unlock(&s_db_rwlock);
+        return NULL;
+    }
+
+    int l_ret = dap_db_driver_sqlite_query(s_db, l_str_query, &l_res, &l_error_message);
+    pthread_rwlock_unlock(&s_db_rwlock);
+    sqlite3_free(l_str_query);
+    DAP_DEL_Z(l_table_name);
+
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "read l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        dap_db_driver_sqlite_free(l_error_message);
+        return NULL;
+    }
+
+    //int b = qlite3_column_count(s_db);
+    SQLITE_ROW_VALUE *l_row = NULL;
+    l_count_out = 0;
+    int l_count_sized = 0;
+    do {
+        l_ret = dap_db_driver_sqlite_fetch_array(l_res, &l_row);
+        if(l_ret != SQLITE_ROW && l_ret != SQLITE_DONE)
+        {
+           // log_it(L_ERROR, "read l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        }
+        if(l_ret == SQLITE_ROW && l_row) {
+            // realloc memory
+            if(l_count_out >= l_count_sized) {
+                l_count_sized += 10;
+                l_obj = DAP_REALLOC(l_obj, sizeof(dap_store_obj_t) * (uint64_t)l_count_sized);
+                memset(l_obj + l_count_out, 0, sizeof(dap_store_obj_t) * (uint64_t)(l_count_sized - l_count_out));
+            }
+            // fill current item
+            dap_store_obj_t *l_obj_cur = l_obj + l_count_out;
+            fill_one_item(a_group, l_obj_cur, l_row);
+            l_count_out++;
+        }
+        dap_db_driver_sqlite_row_free(l_row);
+    } while(l_row);
+
+    dap_db_driver_sqlite_query_free(l_res);
+
+    if(a_count_out)
+        *a_count_out = (size_t)l_count_out;
+    return l_obj;
+}
+
+/**
+ * Read several items
+ *
+ * a_group - group name
+ * a_key - key name, may by NULL, it means reading the whole group
+ * a_count_out[in], how many items to read, 0 - no limits
+ * a_count_out[out], how many items was read
+ */
+dap_store_obj_t* dap_db_driver_sqlite_read_store_obj(const char *a_group, const char *a_key, size_t *a_count_out)
+{
+    if(!a_group || !s_db)
+        return NULL;
+    dap_store_obj_t *l_obj = NULL;
+    sqlite3_stmt *l_res;
+    char * l_table_name = dap_db_driver_sqlite_make_table_name(a_group);
+    // no limit
+    uint64_t l_count_out = 0;
+    if(a_count_out)
+        l_count_out = *a_count_out;
+    char *l_str_query;
+    if(a_key) {
+        if(l_count_out)
+            l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' WHERE key='%s' ORDER BY id ASC LIMIT %d",
+                    l_table_name, a_key, l_count_out);
+        else
+            l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' WHERE key='%s' ORDER BY id ASC",
+                    l_table_name, a_key);
+    }
+    else {
+        if(l_count_out)
+            l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' ORDER BY id ASC LIMIT %d",
+                    l_table_name, l_count_out);
+        else
+            l_str_query = sqlite3_mprintf("SELECT id,ts,key,value FROM '%s' ORDER BY id ASC", l_table_name);
+    }
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    int l_ret = dap_db_driver_sqlite_query(s_db, l_str_query, &l_res, NULL);
+    pthread_rwlock_unlock(&s_db_rwlock);
+
+    sqlite3_free(l_str_query);
+    DAP_DEL_Z(l_table_name);
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "read l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        return NULL;
+    }
+
+    //int b = qlite3_column_count(s_db);
+    SQLITE_ROW_VALUE *l_row = NULL;
+    l_count_out = 0;
+    uint64_t l_count_sized = 0;
+    do {
+        l_ret = dap_db_driver_sqlite_fetch_array(l_res, &l_row);
+        if(l_ret != SQLITE_ROW && l_ret != SQLITE_DONE)
+        {
+           // log_it(L_ERROR, "read l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        }
+        if(l_ret == SQLITE_ROW && l_row) {
+            // realloc memory
+            if(l_count_out >= l_count_sized) {
+                l_count_sized += 10;
+                l_obj = DAP_REALLOC(l_obj, sizeof(dap_store_obj_t) * l_count_sized);
+                memset(l_obj + l_count_out, 0, sizeof(dap_store_obj_t) * (l_count_sized - l_count_out));
+            }
+            // fill currrent item
+            dap_store_obj_t *l_obj_cur = l_obj + l_count_out;
+            fill_one_item(a_group, l_obj_cur, l_row);
+            l_count_out++;
+        }
+        dap_db_driver_sqlite_row_free(l_row);
+    } while(l_row);
+
+    dap_db_driver_sqlite_query_free(l_res);
+
+    if(a_count_out)
+        *a_count_out = l_count_out;
+    return l_obj;
+}
+
+dap_list_t* dap_db_driver_sqlite_get_groups_by_mask(const char *a_group_mask)
+{
+    if(!a_group_mask || !s_db)
+        return NULL;
+    sqlite3_stmt *l_res;
+    const char *l_str_query = "SELECT name FROM sqlite_master WHERE type ='table' AND name NOT LIKE 'sqlite_%'";
+    dap_list_t *l_ret_list = NULL;
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    int l_ret = dap_db_driver_sqlite_query(s_db, (char *)l_str_query, &l_res, NULL);
+    pthread_rwlock_unlock(&s_db_rwlock);
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "Get tables l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        return NULL;
+    }
+    char * l_mask = dap_db_driver_sqlite_make_table_name(a_group_mask);
+    SQLITE_ROW_VALUE *l_row = NULL;
+    while (dap_db_driver_sqlite_fetch_array(l_res, &l_row) == SQLITE_ROW && l_row) {
+        char *l_table_name = (char *)l_row->val->val.val_str;
+        if(!dap_fnmatch(l_mask, l_table_name, 0))
+            l_ret_list = dap_list_prepend(l_ret_list, dap_db_driver_sqlite_make_group_name(l_table_name));
+        dap_db_driver_sqlite_row_free(l_row);
+    }
+    dap_db_driver_sqlite_query_free(l_res);
+    return l_ret_list;
+}
+
+size_t dap_db_driver_sqlite_read_count_store(const char *a_group, uint64_t a_id)
+{
+    sqlite3_stmt *l_res;
+    if(!a_group || ! s_db)
+        return 0;
+
+    char * l_table_name = dap_db_driver_sqlite_make_table_name(a_group);
+    char *l_str_query = sqlite3_mprintf("SELECT COUNT(*) FROM '%s' WHERE id>='%lld'", l_table_name, a_id);
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    int l_ret = dap_db_driver_sqlite_query(s_db, l_str_query, &l_res, NULL);
+    pthread_rwlock_unlock(&s_db_rwlock);
+    sqlite3_free(l_str_query);
+    DAP_DEL_Z(l_table_name);
+
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "Count l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        return 0;
+    }
+    size_t l_ret_val;
+    SQLITE_ROW_VALUE *l_row = NULL;
+    if (dap_db_driver_sqlite_fetch_array(l_res, &l_row) == SQLITE_ROW && l_row) {
+        l_ret_val = (size_t)l_row->val->val.val_int64;
+        dap_db_driver_sqlite_row_free(l_row);
+    }
+    dap_db_driver_sqlite_query_free(l_res);
+    return l_ret_val;
+}
+
+bool dap_db_driver_sqlite_is_obj(const char *a_group, const char *a_key)
+{
+    sqlite3_stmt *l_res;
+    if(!a_group || ! s_db)
+        return false;
+
+    char * l_table_name = dap_db_driver_sqlite_make_table_name(a_group);
+    char *l_str_query = sqlite3_mprintf("SELECT EXISTS(SELECT * FROM '%s' WHERE key='%s')", l_table_name, a_key);
+    pthread_rwlock_wrlock(&s_db_rwlock);
+    int l_ret = dap_db_driver_sqlite_query(s_db, l_str_query, &l_res, NULL);
+    pthread_rwlock_unlock(&s_db_rwlock);
+    sqlite3_free(l_str_query);
+    DAP_DEL_Z(l_table_name);
+
+    if(l_ret != SQLITE_OK) {
+        //log_it(L_ERROR, "Exists l_ret=%d, %s\n", sqlite3_errcode(s_db), sqlite3_errmsg(s_db));
+        return false;
+    }
+    bool l_ret_val;
+    SQLITE_ROW_VALUE *l_row = NULL;
+    if (dap_db_driver_sqlite_fetch_array(l_res, &l_row) == SQLITE_ROW && l_row) {
+        l_ret_val = (size_t)l_row->val->val.val_int64;
+        dap_db_driver_sqlite_row_free(l_row);
+    }
+    dap_db_driver_sqlite_query_free(l_res);
+    return l_ret_val;
+}
+#endif
diff --git a/modules/global-db/include/dap_chain_global_db_driver_pgsql.h b/modules/global-db/include/dap_chain_global_db_driver_pgsql.h
new file mode 100644
index 0000000000..72fc70db64
--- /dev/null
+++ b/modules/global-db/include/dap_chain_global_db_driver_pgsql.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "dap_chain_global_db_driver.h"
+#ifdef DAP_CHAIN_GDB_ENGINE_PGSQL
+#include "/usr/include/postgresql/libpq-fe.h"
+#endif
+
+int dap_db_driver_pgsql_init(const char *a_filename_dir, dap_db_driver_callbacks_t *a_drv_callback);
diff --git a/modules/net/dap_chain_node_cli.c b/modules/net/dap_chain_node_cli.c
index e065f87730..5259aa5715 100644
--- a/modules/net/dap_chain_node_cli.c
+++ b/modules/net/dap_chain_node_cli.c
@@ -988,7 +988,7 @@ int dap_chain_node_cli_init(dap_config_t * g_config)
             "mempool_list -net <net name> -chain <chain name>\n");
 
     dap_chain_node_cli_cmd_item_create ("mempool_proc", com_mempool_proc, NULL, "Proc mempool entries for selected chain network and chain id",
-            "mempool_proc -net <net name> -chain <chain name>\n");
+            "mempool_proc -net <net name> -chain <chain name> -datum <datum hash>\n");
 
     dap_chain_node_cli_cmd_item_create ("mempool_delete", com_mempool_delete, NULL, "Delete datum with hash <datum hash>",
             "mempool_delete -net <net name> -chain <chain name> -datum <datum hash>\n");
diff --git a/modules/net/dap_chain_node_cli_cmd_tx.c b/modules/net/dap_chain_node_cli_cmd_tx.c
index 3f7b307e1b..1633f6fe57 100644
--- a/modules/net/dap_chain_node_cli_cmd_tx.c
+++ b/modules/net/dap_chain_node_cli_cmd_tx.c
@@ -256,7 +256,7 @@ void _dap_chain_datum_tx_out_data(dap_chain_datum_tx_t *a_datum,
                                      dap_chain_tx_out_cond_subtype_to_str(((dap_chain_tx_out_cond_t*)item)->header.subtype));
             switch (((dap_chain_tx_out_cond_t*)item)->header.subtype) {
             case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY:
-                l_hash_str_tmp = dap_hash_fast_to_str_new(&((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.pkey_hash);
+                l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.pkey_hash);
                 dap_string_append_printf(a_str_out, "\t\t\t unit: 0x%08x\n"
                                                     "\t\t\t uid: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                     "\t\t\t pkey: %s\n"
diff --git a/modules/type/dag/dap_chain_cs_dag.c b/modules/type/dag/dap_chain_cs_dag.c
index 015eb3fe86..c6a8b7dc73 100644
--- a/modules/type/dag/dap_chain_cs_dag.c
+++ b/modules/type/dag/dap_chain_cs_dag.c
@@ -1522,9 +1522,10 @@ static int s_cli_dag(int argc, char ** argv, void *arg_func, char **a_str_reply)
                         pthread_rwlock_rdlock(&PVT(l_dag)->events_rwlock);
                         HASH_FIND(hh,PVT(l_dag)->events,&l_event_hash,sizeof(l_event_hash),l_event_item);
                         pthread_rwlock_unlock(&PVT(l_dag)->events_rwlock);
-                        if ( l_event_item )
+                        if ( l_event_item ) {
                             l_event = l_event_item->event;
-                        else {
+                            l_event_size = l_event_item->event_size;
+                        } else {
                             ret = -24;
                             dap_chain_node_cli_set_reply_text(a_str_reply,
                                                               "Can't find event %s in events table\n", l_event_hash_str);
@@ -1594,7 +1595,7 @@ static int s_cli_dag(int argc, char ** argv, void *arg_func, char **a_str_reply)
                         dap_chain_addr_fill(&l_addr, l_sign->header.type, &l_pkey_hash, l_net->pub.id);
                         char * l_addr_str = dap_chain_addr_to_str(&l_addr);
                         dap_string_append_printf(l_str_tmp,"\t\t\t\t\t\ttype: %s\taddr: %s"
-                                                           "n", dap_sign_type_to_str( l_sign->header.type ),
+                                                           "\n", dap_sign_type_to_str( l_sign->header.type ),
                                                  l_addr_str );
                         l_offset += l_sign_size;
                         DAP_DELETE( l_addr_str);
-- 
GitLab