SQLite Cooking

SQLite is an open source DBMS that runs embedded in a client application. It's included by default on Android, iOS, and macOS. You can easily install SQLite on Windows, Linux distros.  As the name suggests, SQLite, unlike RDBMS(MySQL, MS SQL, Oracle ...), has no network support. Provides database functionality through file I / O in applications that include SQLite support.

Install SQLite

If you want to use sqlite in your c/c++ application, first make sure that SQLite is installed in your operating system.


[root@localhost ~]# sqlite3
SQLite version 3.6.20
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

If your OS already has SQLite, sqlite3 command will work fine and sqlite> prompt will appear.
If not installed, installl the SQLite, SQLite development packages.


yum install sqlite sqlite-devel


Using SQLite in c/c++

If you want to use SQLite in your c/c++ code, just include sqlite3.h header file. Build with -lsqlite3 link options

check whether sqlite-devel is installed 

Just make a cpp file that include sqlite3.h header, and compile the source


#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>


int main(int argc, char *argv[])
{
        fprintf(stdout, "SQLite development environment check success\n");
        return 0;
}

If sqlite-devel package is installed correctly, you can see the result like this.

[root@localhost study]# g++ step1.cpp -lsqlite3 
[root@localhost study]# ./a.out
SQLite development environment check success


Open SQLite3 Database


#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>

using namespace std;
sqlite3 *db;


int init_sqlite3()
{
    int rc;

    rc = sqlite3_open("ivr.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(-1);
    } else {
       fprintf(stderr, "Opened database successfully\n");
    }
    return 0;
}


int main(int argc, char *argv[])
{
        int ret;
        fprintf(stdout, "SQLite development environment check success\n");
        ret = init_sqlite3();
        if(ret) exit(0);
        sqlite3_close(db);
        return 0;
}


sqlite3_open( const char *dbname, sqlite3 **db) function opens database file. The dbname is just a file, so you can pass a full path name like "/usr/local/data/ivr.db".

Remember that if the dbname file does not exist, sqlite3 makes a dbname file automatically. Therefore, this function always returns 0(success code) unless the file cannot be created due to permission problems.

Create table if not exists

If there is no desired table in the database, this function is used to automatically create it. "CREATE TABLE IF NOT EXISTS" SQL command will make a table if not exists.
From now on, sqlite3_exec functions will be used frequently.


int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

Pay close attention to the callback parameter and the void * parameter. Calling the sqlite3_exec function does not return a result, but passes it through a callback function.
Some people are concerned about time synchronization due to callback function calls. However, the callback function call is made in the sqlite3_exec function, so there is no time synchronization problem. In other words, when the sqlite3_exec function returns, the callback function is alreay called.

void * parameter is used when you want to pass a parameter to the callback function. It can be used to receive the function results synchronously. I'll talk more about this later.

#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>

using namespace std;
sqlite3 *db;

static int default_callback(void *NotUsed, int argc, char **argv, char **azColName) {
   int i;
   for(i = 0; i<argc; i++) {
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}


int init_sqlite3()
{
    int rc;

    rc = sqlite3_open("ivr.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(-1);
    } else {
       fprintf(stderr, "Opened database successfully\n");
    }
    return 0;
}

int create_table()
{
   string sql;
   int rc;
   char *zErrMsg = 0;

   /* Create SQL statement */
   sql = "CREATE TABLE IF NOT EXISTS CHANNEL("  \
      "ID INT PRIMARY KEY     NOT NULL," \
      "CALL_ID        INT     NOT NULL," \
      "TIME           REAL     NOT NULL," \
      "CALLER         TEXT    NOT NULL," \
      "RESERVED         INT );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql.c_str(), default_callback, 0, &zErrMsg);

   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Table created successfully\n");
   }

        return 0;
}


int main(int argc, char *argv[])
{
        int ret;
        fprintf(stdout, "SQLite development environment check success\n");
        ret = init_sqlite3();
        if(ret) exit(0);
        create_table();
        sqlite3_close(db);
        return 0;
}

Build and run the code.

[root@localhost study]# g++ step2.cpp -lsqlite3 
[root@localhost study]# ./a.out 
SQLite development environment check success
Opened database successfully
Table created successfully


insert or update SQL

Now let's add some data to the table we created earlier. In our table, the ID column is a key that does not allow duplicates. If the ID column value already exists, update the row. If not, execute the insert command. We will insert or update 4 rows. "REPLACE" command will do it.


    }
#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h> // for_gettimeofday
#include <unistd.h> // for usleep()

#include <sqlite3.h>

using namespace std;
sqlite3 *db;

static int default_callback(void *NotUsed, int argc, char **argv, char **azColName) {
   int i;
   for(i = 0; i<argc; i++) {
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}


int init_sqlite3()
{
    int rc;

    rc = sqlite3_open("ivr.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(-1);
    } else {
       fprintf(stderr, "Opened database successfully\n");
    }
    return 0;
}

int create_table()
{
   string sql;
   int rc;
   char *zErrMsg = 0;

   /* Create SQL statement */
   sql = "CREATE TABLE IF NOT EXISTS CHANNEL("  \
      "ID INT PRIMARY KEY     NOT NULL," \
      "CALL_ID        INT     NOT NULL," \
      "TIME           REAL     NOT NULL," \
      "CALLER         TEXT    NOT NULL," \
      "RESERVED         INT );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql.c_str(), default_callback, 0, &zErrMsg);

   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Table created successfully\n");
   }

        return 0;
}
int insert_or_replace()
{
   char  sql[1024];
   int rc;
   char *zErrMsg = 0;
   struct timeval start;
   int channel = 1000;
   int callid = 10000;
   double  secs_used;
        
        
   for(int x = 0; x < 4; x++){
      gettimeofday(&start, NULL);
      secs_used= (double)(start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0  ;

      sprintf(sql, "REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (%d, %d, %f, 'Caller',0 );", channel + x, callid + x, secs_used);
      printf("%s\n", sql);
      rc = sqlite3_exec(db, sql, default_callback, 0, &zErrMsg);
      if( rc != SQLITE_OK ){
         fprintf(stderr, "SQL error: %s\n", zErrMsg);
         sqlite3_free(zErrMsg);
      } else {
         fprintf(stdout, "Records created successfully\n");
      }
      sleep(1);
   }

   return 0;
}

int main(int argc, char *argv[])
{
        int ret;
        fprintf(stdout, "SQLite development environment check success\n");
        ret = init_sqlite3();
        if(ret) exit(0);
        create_table();
        insert_or_replace();
        sqlite3_close(db);
        return 0;
}

Build and run the code.


[root@localhost study]# g++ step3.cpp -lsqlite3 
[root@localhost study]# ./a.out 
SQLite development environment check success
Opened database successfully
Table created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1000, 10000, 1572012952.884573, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1001, 10001, 1572012953.914426, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1002, 10002, 1572012954.929729, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1003, 10003, 1572012955.945338, 'Caller',0 );
Records created successfully

Now let's check the table with sqlite3 command. As you can see, the 4 row are created successfully.


[root@localhost study]# sqlite3 ivr.db
SQLite version 3.6.20
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from channel;
1000|10000|1572012952.88457|Caller|0
1001|10001|1572012953.91443|Caller|0
1002|10002|1572012954.92973|Caller|0
1003|10003|1572012955.94534|Caller|0

select SQL

The select command also uses the sqlite3_exec function. This time the callback function is very important. This is because the query result is passed through the callback function.


#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h> // for clock_gettime()
#include <unistd.h> // for usleep()
#include <sqlite3.h>

using namespace std;
sqlite3 *db;

static int default_callback(void *NotUsed, int argc, char **argv, char **azColName) {
   int i;
   for(i = 0; i<argc; i++) {
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

static int select_callback(void *NotUsed, int argc, char **argv, char **azColName) {
   int i;
   struct timeval start;

   gettimeofday(&start, NULL);
   double secs_used= ((double)start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0  ;

   for(i = 0; i<argc; i++) {
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
      if(strcasecmp( azColName[i], "TIME") == 0){
          printf("secs elapsed : %10.3f\n", secs_used -atof(argv[i]));
      }
   }
   printf("\n");
   return 0;
}


int init_sqlite3()
{
    int rc;

    rc = sqlite3_open("ivr.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(-1);
    } else {
       fprintf(stderr, "Opened database successfully\n");
    }
    return 0;
}
int create_table()
{
   string sql;
   int rc;
   char *zErrMsg = 0;

   /* Create SQL statement */
   sql = "CREATE TABLE IF NOT EXISTS CHANNEL("  \
      "ID INT PRIMARY KEY     NOT NULL," \
      "CALL_ID        INT     NOT NULL," \
      "TIME           REAL     NOT NULL," \
      "CALLER         TEXT    NOT NULL," \
      "RESERVED         INT );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql.c_str(), default_callback, 0, &zErrMsg);

   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Table created successfully\n");
   }

        return 0;
}

int insert_or_replace()
{
   char  sql[1024];
   int rc;
   char *zErrMsg = 0;
   struct timeval start;
   int channel = 1000;
   int callid = 10000;
   double  secs_used;


   for(int x = 0; x < 4; x++){
      gettimeofday(&start, NULL);
      secs_used= (double)(start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0  ;

      sprintf(sql, "REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (%d, %d, %f, 'Caller',0 );", channel + x, callid + x, secs_used);
      printf("%s\n", sql);
      rc = sqlite3_exec(db, sql, default_callback, 0, &zErrMsg);
      if( rc != SQLITE_OK ){
         fprintf(stderr, "SQL error: %s\n", zErrMsg);
         sqlite3_free(zErrMsg);
      } else {
         fprintf(stdout, "Records created successfully\n");
      }
      sleep(1);
   }

   return 0;
}
int select_result()
{
   char  sql[1024];
   int rc;
   char *zErrMsg = 0;
   struct timeval start;
   const char* data = "Callback function called";

   strcpy(sql,"SELECT * from CHANNEL");
   rc = sqlite3_exec(db, sql, select_callback, (void*)data, &zErrMsg);
   if( rc != SQLITE_OK ) {
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Operation done successfully\n");
   }
   return 0;
}

int main(int argc, char *argv[])
{
        int ret;
        fprintf(stdout, "SQLite development environment check success\n");
        ret = init_sqlite3();
        if(ret) exit(0);
        create_table();
        insert_or_replace();
        select_result();
        sqlite3_close(db);
        return 0;
}

Build and run the code.


[root@localhost study]# g++ step4.cpp -lsqlite3 
[root@localhost study]# ./a.out 
SQLite development environment check success
Opened database successfully
Table created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1000, 10000, 1572013347.223009, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1001, 10001, 1572013348.267462, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1002, 10002, 1572013349.282904, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1003, 10003, 1572013350.298310, 'Caller',0 );
Records created successfully
ID = 1000
CALL_ID = 10000
TIME = 1572013347.22301
secs elapsed :      4.091
CALLER = Caller
RESERVED = 0

ID = 1001
CALL_ID = 10001
TIME = 1572013348.26746
secs elapsed :      3.047
CALLER = Caller
RESERVED = 0

ID = 1002
CALL_ID = 10002
TIME = 1572013349.2829
secs elapsed :      2.032
CALLER = Caller
RESERVED = 0

ID = 1003
CALL_ID = 10003
TIME = 1572013350.29831
secs elapsed :      1.016
CALLER = Caller
RESERVED = 0


This time "insert_or_replace()" function does not insert the row bacause of duplicated ID calues, instead the row values will be updated.
In the "select_result()" function we passed "select_callback" callback when calling sqlite3_exec functions.

"select_callback" function is called for each row. In this case the callback function is called 4 times!

Get results using void * parameter

Finally, let's get the result using void * parameter in the callback.


#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h> // for clock_gettime()
#include <unistd.h> // for usleep()
#include <sqlite3.h>

using namespace std;

typedef struct _channel_info{
   int channel;
   int call_id;
   char caller[64];
   double elapsed;
} channel_info;


sqlite3 *db;

static int default_callback(void *NotUsed, int argc, char **argv, char **azColName) {
   int i;
   for(i = 0; i<argc; i++) {
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

static int query_channel_callback(void *info, int argc, char **argv, char **azColName) {
   int i;
   struct timeval start;
   channel_info *pinfo = (channel_info *)info ;

   gettimeofday(&start, NULL);
   double secs_used= ((double)start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0  ;

   memset(pinfo, 0x00, sizeof(channel_info));
   for(i = 0; i<argc; i++) {
      if(strcasecmp( azColName[i], "TIME") == 0){
          pinfo->elapsed =  secs_used -atof(argv[i]);
      }
      else if(strcasecmp( azColName[i], "CALL_ID") == 0){
          pinfo->call_id =  atoi(argv[i]);
      }
      else if(strcasecmp( azColName[i], "CALLER") == 0){
          strncpy(pinfo->caller, argv[i], 64);
      }
      else if(strcasecmp( azColName[i], "ID") == 0){
          pinfo->channel =  atoi(argv[i]);
      }
   }
   return 0;
}


int init_sqlite3()
{
    int rc;

    rc = sqlite3_open("ivr.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(-1);
    } else {
       fprintf(stderr, "Opened database successfully\n");
    }
    return 0;
}
int create_table()
{
   string sql;
   int rc;
   char *zErrMsg = 0;

   /* Create SQL statement */
   sql = "CREATE TABLE IF NOT EXISTS CHANNEL("  \
      "ID INT PRIMARY KEY     NOT NULL," \
      "CALL_ID        INT     NOT NULL," \
      "TIME           REAL     NOT NULL," \
      "CALLER         TEXT    NOT NULL," \
      "RESERVED         INT );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql.c_str(), default_callback, 0, &zErrMsg);

   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Table created successfully\n");
   }

        return 0;
}

int insert_or_replace()
{
   char  sql[1024];
   int rc;
   char *zErrMsg = 0;
   struct timeval start;
   int channel = 1000;
   int callid = 10000;
   double  secs_used;


   for(int x = 0; x < 4; x++){
      gettimeofday(&start, NULL);
      secs_used= (double)(start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0  ;

      sprintf(sql, "REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (%d, %d, %f, 'Caller',0 );", channel + x, callid + x, secs_used);
      printf("%s\n", sql);
      rc = sqlite3_exec(db, sql, default_callback, 0, &zErrMsg);
      if( rc != SQLITE_OK ){
         fprintf(stderr, "SQL error: %s\n", zErrMsg);
         sqlite3_free(zErrMsg);
      } else {
         fprintf(stdout, "Records created successfully\n");
      }
      sleep(1);
   }

   return 0;
}
int query_channel(int channel, int *call_id, char *caller, double *elapsed)
{
   char  sql[1024];
   int rc;
   char *zErrMsg = 0;
   struct timeval start;
   float  secs_used;
   channel_info data;

   memset(&data, 0x00, sizeof(channel_info));
   sprintf(sql,"SELECT CALL_ID, TIME, CALLER from CHANNEL where ID = %d", channel);
   rc = sqlite3_exec(db, sql, query_channel_callback, (void*)&data, &zErrMsg);
   if( rc != SQLITE_OK ) {
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   } else {
      fprintf(stdout, "Querychanneln done successfully\n");
      fprintf(stdout, "caller:%s\n", data.caller);
      fprintf(stdout, "callid:%d\n", data.call_id);
      fprintf(stdout, "celapsed:%f\n", data.elapsed);
      *call_id = data.call_id;
      *elapsed = data.elapsed;
      strcpy(caller, data.caller);
   }
   return 0;

}


int main(int argc, char *argv[])
{
        int ret;
        fprintf(stdout, "SQLite development environment check success\n");
        ret = init_sqlite3();
        if(ret) exit(0);
        create_table();
        insert_or_replace();

        int call_id;
        double elapsed;
        char caller[64];

        query_channel(1000,&call_id, caller, &elapsed);
        sqlite3_close(db);
        return 0;
}


This time, I created a "query_channel" function that returns only one row value using a query statement using an ID value.

And I passed channel_info data when calling sqlite3_exec function. This value is passed as the argument of the callback function.


rc = sqlite3_exec(db, sql, query_channel_callback, (void*)&data, &zErrMsg);


static int query_channel_callback(void *info, int argc, char **argv, char **azColName) {
   int i;
   struct timeval start;
   channel_info *pinfo = (channel_info *)info ;
...............

Be careful : If the select sql returns multiple rows, the callback function will be called several times, and only the final result will be returned.


Build and run the code.


[root@localhost study]# g++ step5.cpp -lsqlite3 
[root@localhost study]# ./a.out 
SQLite development environment check success
Opened database successfully
Table created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1000, 10000, 1572014946.196102, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1001, 10001, 1572014947.225994, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1002, 10002, 1572014948.241377, 'Caller',0 );
Records created successfully
REPLACE INTO CHANNEL (ID, CALL_ID, TIME, CALLER, RESERVED)  VALUES (1003, 10003, 1572014949.256861, 'Caller',0 );
Records created successfully
Querychanneln done successfully
caller:Caller
callid:10000
celapsed:4.076267


You can get the fetched row values using void * parameter.

















댓글

이 블로그의 인기 게시물

MQTT - C/C++ Client

RabbitMQ - C++ Client #1 : Installing C/C++ Libraries

C/C++ - Everything about time, date