Memory leak when using sqlite3 with C++ - c++

The program writes into SQLite database, the messages are received through a wireless module. But somehow there is a memory leak every time a message is received and written to the database, after about 10 000 writes the program is using 1GB of memory.
The documentation for SQLite3 with C++ says that memory leaks are prevented with sqlite3_finalize() and sqlite3_close() which are present:
#include <iostream>
#include <string>
#include <sstream>
#include "sqlite3.h"
using namespace std;
#define DB "test.db"
sqlite3 *dbfile;
bool connectDB();
void disonnectDB();
int insOrUpdate(string s);
int select(string s);
struct messageStruct_t {
float value;
};
bool isOpenDB = false;
int main() {
int counter = 0;
while (1) {
int header = 1;
int message = rand() % 3;
if (message) {
counter ++;
switch (header) {
case 1: {
messageStruct_t recMessage;
recMessage.value = 55;
int receivedSendersID = 2;
//SQL query to get foreign key
stringstream strm_select;
strm_select << "SELECT id FROM table1 WHERE sendersID="
<< receivedSendersID;
string s_select = strm_select.str();
cout << "SQL query: " << s_select << endl;
int sendersID = select(s_select);
cout << "Sender's ID: " << sendersID << endl;
if (sendersID == 0) {
cout << "Error: Sender doesn't exist\n";
} else {
stringstream strm_insert;
strm_insert << "INSERT into table2(id,value,sender_id) values("
<< counter << ", "
<< recMessage.value << ", " << sendersID << ")";
string s_insert = strm_insert.str();
cout << "SQL query: " << s_insert << endl;
insOrUpdate(s_insert);
cout << "Recorded data: " << recMessage.value << endl;
}
}
default: {
break;
}
}
}
}
}
bool connectDB () {
if (sqlite3_open(DB, &dbfile) == SQLITE_OK) {
isOpenDB = true;
return true;
}
return false;
}
void disonnectDB () {
if ( isOpenDB == true ) {
sqlite3_close(dbfile);
}
}
int insOrUpdate(string s) {
if (!connectDB()) {
return 0;
}
char *str = &s[0];
sqlite3_stmt *statement;
int result;
const char *query = str;
if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) {
result = sqlite3_step(statement);
//the documentation says that this destroys the statement and prevents memory leaks
sqlite3_finalize(statement);
return result;
}
//and this destroys the db object and prevents memory leaks
disonnectDB();
return 0;
}
int select(string s) {
if (!connectDB()) {
return 0;
}
char *str = &s[0];
sqlite3_stmt *statement;
const char *query = str;
string returned;
if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) {
int ctotal = sqlite3_column_count(statement);
int res = 0;
while (1) {
res = sqlite3_step(statement);
if (res == SQLITE_ROW) {
for (int i = 0; i < ctotal; i++) {
string s = (char*)sqlite3_column_text(statement, i);
cout << s << " ";
returned = s;
}
cout << endl;
}
if (res == SQLITE_DONE || res == SQLITE_ERROR) {
cout << "done " << endl;
break;
}
}
} else {
cout << "Can't prepare" << endl;
return 0;
}
sqlite3_finalize(statement);
disonnectDB();
int result;
stringstream convert(returned);
if (!(convert >> result)) {
result = 0;
}
return result;
}
CREATE TABLE table1 (
id INTEGER NOT NULL,
sendersID INTEGER,
PRIMARY KEY (id)
);
CREATE TABLE table2 (
id INTEGER NOT NULL,
value FLOAT,
sender_id INTEGER,
FOREIGN KEY(sender_id) REFERENCES table1 (id)
);
INSERT INTO table1(sendersID) values(2);

In your connectDB(..) call , you don't check if the database is already open before opening it again. Your memory leak is probably from the repeated mappings of this database into your memory space.
There may be other issues with this program but the change below to connectDB(..) should help with the leak on every successful insert.
bool connectDB() {
if (false == isOpenDB && sqlite3_open(DB, &dbfile) == SQLITE_OK) {
isOpenDB = true;
}
return isOpenDB;
}

You should definitely use RAII for your connections and also for your statements. There are several places where you return early without cleaning up statements and/or closing the connection.

Related

Hash table with pointer vector

I'm having some trouble with an assignment that requires creating a hash table using a vector of Entry (ADT created by the professor). The program compiles successfully but gdb gives me the error message "No stack." when I try debugging. I've traced the error to a call to my function search() in my function insert().
I'm not sure where the problem comes from but I think it either stems from the way I access the vector or how I tried to initialize it in the constructor.
My source code is below, my professor wrote the structure Entry and class HT as well as the functions main() and hashing() which cannot be edited although new functions can be added to the class. Even though those parts cannot be edited I included them here for reference.
Here's my header file assignment8.h:
#ifndef ASSIGNMENT8_H
#define ASSIGNMENT8_H
#include <vector>
#include <string>
struct Entry {
std::string key;
std::string description;
Entry() { key = "---"; }
};
class HT {
private:
std::vector<Entry>* hTable;
int table_size=11;
int item_count;
int hashing(const std::string&);
public:
HT();
HT(int size);
~HT();
bool insert(const Entry& e);
int search(const std::string& key);
bool remove(const std::string& s);
void print();
//Student-made member fucntions
void setsize(int size){table_size = size;}
void setItemCount(int ic){ic = item_count;}
int getItemCount(){return item_count;}
};
#endif
and my cc file assignment8.cc:
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include "assignment8.h"
using namespace std;
Entry* get_entry(const string& line)
{
//Holder for string fragments
string strArr[3];
int index = 0;
//Split the string
for(unsigned int i = 0; i < line.length(); i++)
{
if(line[i] != ':')
strArr[index] += line[i];
else
index++;
}
Entry* newEntry = new Entry;
//Pass strings to newEntry
(*newEntry).key = strArr[1];
(*newEntry).description = strArr[2];
return newEntry;
}
string get_key(const string& line)
{
//Holder for string fragments
string strArr[2];
int index = 0;
//Split string
for(unsigned int i = 0; i < line.length(); i++)
{
if(line[i] != ':')
strArr[index] += line[i];
else
index++;
}
//Return item-key
return strArr[1];
}
//----HT----//
// key is in form of letter-letter-digit
// compute sum <-- ascii(pos1)+ascii(pos2)+ascii(pos3)
// compute sum%htable_size
int HT::hashing(const string& key) {
return ((int)key[0] + (int)key[1] + (int)key[2])%table_size;
}
//--Constructors
//Default Constructor
HT::HT(){}
//Constructor
HT::HT(int size)
{
//Initializations
if(!size)
size = 11;
setsize(size);
hTable = new vector<Entry>[table_size];
}
HT::~HT()
{
delete[] hTable;
}
int HT::search(const std::string& key)
{
//Look for unused slot
for(int i = 0; i < table_size; i++)
{
if((*hTable)[i].key == key)
return hashing(key);
}
return -1;
}
bool HT::insert(const Entry& e)
{
//Get e's key
string eKey = e.key;
//If it's full, print error message and return false
if(getItemCount() == table_size)
{
cout << "Hash table is full\n";
return false;
}
//Checks to see if key already exists
if(search(eKey) != -1)
{
cout << "Key already exists in table\n";
return false;
}
//If it does not already exist,
for(int i = 0; i < table_size; i++)
{
string iKey = (*hTable)[i].key;
cout << i << "iteration(s)"<< endl;
if(iKey == "---" || iKey == "+++")
{
//Compute hash key value and insert into position
(*hTable)[i] = e;
setItemCount(++item_count);
return true;
}
}
return false;
}
bool HT::remove(const std::string& s)
{
for(int i = 0; i < table_size; i++)
{
string iKey = (*hTable)[i].key;
if(iKey == s)
{
iKey = "+++";
setItemCount(--item_count);
return true;
}
}
return false;
}
void HT::print()
{
cout << "----Hash Table----" << endl;
for(int i = 0; i < table_size; i++)
{
cout << (*hTable)[i].key << " " << (*hTable)[i].description << endl;
}
cout << endl << "------------------" << endl;
}
int main(int argc, char** argv ) {
if ( argc < 2 ) {
cerr << "argument: file-name\n";
return 1;
}
HT ht;
ifstream infile(argv[1], ios::in);
string line;
infile >> line;
while ( !infile.eof() ) {
if ( line[0] == 'A' ) {
Entry* e = get_entry( line );
ht.insert( *e );
delete e;
}
else {
string key = get_key(line);
if ( line[0] == 'D' ) {
cout << "Removing " << key << "...\n";
bool removed = ht.remove( key );
if ( removed )
cout << key << " is removed successfully...\n\n";
else
cout << key << " does not exist, no key is removed...\n\n";
}
else if ( line[0] == 'S' ) {
int found = ht.search( key );
if ( found < 0 )
cout << key << " does not exist in the hash table ..." << endl << endl;
else
cout << key << " is found at table position " << found << endl << endl;
}
else if ( line[0] == 'P' ) {
cout << "\nDisplaying the table: " << endl;
ht.print();
}
else
cerr << "wrong input: " << line << endl;
}
infile >> line;
}
infile.close();
return 0;
}
Thank you for any help or advice! :)

Write and Read method stop my execution - pipes c++

I'm just trying to make a "Tree" where I've a ctrsis and (for now) n "Children" connected to the ctrsis by pipes; But something happen when I try to pass data through pipes from childen to parent and parent to children using stdin and stdout, specificly when I use write() cpp function.
//ctrsis.cpp
int
main(void) {
vector<WriteIn> TubesReference;
WriteIn mainTube;
TubesReference.push_back(mainTube);
const char* file = "ctrsis.cfg";
int levelOneProcesses = 0;
string line;
ifstream configFile(file);
if(configFile.is_open()){
while ( getline (configFile,line) ){
WriteIn structure = createTube(line);
TubesReference.push_back(structure);
levelOneProcesses++;
}
}else{
cout << "Unable to open file" << endl;
}
configFile.close();
if ((TubesReference[0].in = open("ctrsis.cfg", O_RDONLY)) == -1) {
std::cerr << "Error open file" << std::endl;
return 1;
}
TubesReference[0].out = TubesReference[1].pipeOut[1];
for (int i = 1; i < levelOneProcesses; ++i){
TubesReference[i].in = TubesReference[i].pipeIn[0];
if(i != TubesReference.size()-1){
TubesReference[i].out = TubesReference[i+1].pipeOut[1];
}else{
TubesReference[TubesReference.size()-1].out = 1;
}
}
pthread_t levelOneThreads[levelOneProcesses];
for(int i = 0; i < levelOneProcesses; ++i){
vector<string> vec;
istringstream iss(TubesReference[i+1].line);
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter(vec));
string file = "FICHEROCFG=" + vec[4];
string folder = "DIRDETRABAJO=" + vec[3];
char* formatFile = new char[file.length()+1];
memcpy(formatFile, file.c_str(), file.length()+1);
char* formatFolder= new char[folder.length()+1];
memcpy(formatFolder, folder.c_str(), folder.length()+1);
putenv(formatFile);
putenv(formatFolder);
cout << "CTRSIS: CREATE THREAD " << i << endl;
int ret = pthread_create(&levelOneThreads[i],NULL, readWriteThread, &TubesReference[i]);
if(ret != 0) {
cerr << "Error: pthread_create() failed\n" << endl;
exit(EXIT_FAILURE);
}else{
cout << "CTRSIS: JOIN THREAD " << i << endl;
pthread_join(levelOneThreads[i], NULL);
}
}
//system( "./ctreval" );
return 0;
}
void* readWriteThread(void *arg) {
sleep(3);
WriteIn *dataInOut = (struct WriteIn *) arg;
cout << "CTRSIS: METHOD" << endl;
char c;
while (read(dataInOut->in, &c, 1) > 0) {
write(dataInOut->out, &c, 1);
}
close(dataInOut->in);
close(dataInOut->out);
return NULL;
}
This is the evaluator
#include <iostream>
#include <cctype>
int
main(void) {
int c;
while ((c = std::cin.get()) != EOF) {
c = ::toupper(c);
std::cout << (char) c;
}
return 0;
}

Seg Fault - Signal received, SIGABORT

So my code compiles well, but whenever I try running the program, it seg faults. I ran it through gdb and got this set of error:
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Program received signal SIGABRT, Aborted.
0x00007ffff722bcc9 in __GI_raise (sig=sig#entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
Here is my code if it helps:
using namespace std;
#include <iostream>
#include <vector>
#include <cmath>
#include<fstream>
#include <cstdlib>
#include <utility>
#include <string>
class Page
{
public:
int TimeStamp;
int ProgramOwner;
int LocalPageNumber;
};
//Pair: first entry is local page, second entry is global page
//Second entry: 1 if in main memory, 0 if not
class Program
{
public:
vector< pair<int,int> > MemoryMap;
};
class Memory
{
public:
vector<Program> Programs;
vector<Page> Pages;
};
void checkArguments(int argc, char *argv[]);
bool checkPageAlgorithm(char *argv[]);
bool checkPageSize(int pagesize);
bool checkPageStyle(char *argv[]);
bool checkPTrace(string ProgramTraceFile);
bool checkPList(string ProgramListFile);
int main(int argc, char *argv[])
{
//checkArguments(argc, argv);
const int MemSize = 512;
int pageSize = atoi(argv[3]);
string programListFile = argv[1];
string ProgramTraceFile = argv[2];
string pageAlgorithm = argv[4];
string pageStyle = argv[5];
int programNumber;
int programSize;
int PagesNeeded;
Memory MainMemory;
int numberFrames;
int pageFaults = 0;
int numPrograms = 0;
int PagesPerProgram;
int LocalPage;
int activeProgram;
int requestedLocation;
int newPageLocation;
bool foundInMemory;
ifstream fileIn;
fileIn.open(programListFile.c_str());
if(fileIn.fail())
{
cerr << "List file not found." << endl;
fileIn.close();
exit(1);
}
else
{
while(!fileIn.eof())
{
cout << "hey0";
fileIn >> programNumber;
fileIn >> programSize;
numPrograms++;
}
}
numberFrames = ceil(MemSize/pageSize);
PagesPerProgram = ceil(numberFrames/numPrograms);
MainMemory.Pages.resize(numberFrames);
cout << "hey1";
while(!fileIn.eof())
{
cout << "hey2";
fileIn >> programNumber;
fileIn >> programSize;
cout << "hey3";
PagesNeeded = ceil(programSize/pageSize);
for(int i = 0; i < PagesNeeded; i++)
{
LocalPage = i;
MainMemory.Pages[i].ProgramOwner = programNumber;
MainMemory.Pages[i].LocalPageNumber = LocalPage;
}
cout << "hey3";
if(PagesNeeded > PagesPerProgram)
PagesNeeded = PagesPerProgram;
for(int i = 0; i < PagesNeeded; i++)
{
MainMemory.Programs[programNumber].MemoryMap[i].first = MainMemory.Pages[i].LocalPageNumber;
MainMemory.Programs[programNumber].MemoryMap[i].second = 1;
if(pageAlgorithm == "clock")
MainMemory.Pages[i].TimeStamp = 1;
else
MainMemory.Pages[i].TimeStamp = 0;
}
}
fileIn.close();
//LRU Algorithm Implementation
if(pageAlgorithm == "lru")
{
cout << "here1";
fileIn.open(ProgramTraceFile.c_str());
if(fileIn.fail())
{
cerr << "That file does not exist." << endl;
fileIn.close();
exit(1);
}
else
{
fileIn >> activeProgram;
fileIn >> requestedLocation;
newPageLocation = ceil(requestedLocation/pageSize);
while(!fileIn.eof())
{
foundInMemory = false;
for(int i = 0; i < static_cast<int>(MainMemory.Programs[activeProgram].MemoryMap.size()); i++)
{
if((MainMemory.Programs[activeProgram].MemoryMap[i].second == 1) &&
(MainMemory.Programs[activeProgram].MemoryMap[i].first == newPageLocation))
foundInMemory = true;
if(foundInMemory)
break;
}
if(!foundInMemory)
{
pageFaults++;
if(static_cast<int>(MainMemory.Programs[activeProgram].MemoryMap.size()) < PagesPerProgram)
{
pair<int, int> temp;
temp.first = newPageLocation;
temp.second = 1;
MainMemory.Programs[activeProgram].MemoryMap.push_back(temp);
}
else
{
for(int i = 0; i < (static_cast<int>(MainMemory.Programs[activeProgram].MemoryMap.size()) - 1); i++)
{
if(MainMemory.Pages[i].TimeStamp >= MainMemory.Pages[i+1].TimeStamp)
{
MainMemory.Programs[activeProgram].MemoryMap[i].first = newPageLocation;
}
if(pageStyle == "1")
{
if(MainMemory.Pages[i].TimeStamp >= MainMemory.Pages[i+1].TimeStamp)
{
MainMemory.Programs[activeProgram].MemoryMap[i].first = MainMemory.Pages[i].LocalPageNumber;
}
}
MainMemory.Pages[i].TimeStamp++;
}
}
}
fileIn >> activeProgram;
fileIn >> requestedLocation;
newPageLocation = ceil(requestedLocation/pageSize);
}
}
}
cout << "------------------------------------" << endl;
cout << "Page Size: " << pageSize << endl;
cout << "Page Replacement Algorithm: " << pageAlgorithm << endl;
cout << "Paging Style: ";
if(pageStyle == "0")
cout << "Demand" << endl;
else
cout << "Prepaging" << endl;
cout << "Number of Page Faults: " << pageFaults << endl;
cout << "------------------------------------" << endl;
return 0;
}
bool checkPList(string programlistfile)
{
ifstream ListFile(programlistfile.c_str());
if(ListFile.fail())
{
cerr << "Cannot find file " << programlistfile << endl;
ListFile.close();
return false;
}
else
{
ListFile.close();
return true;
}
}
bool checkPTrace(string programTraceFile)
{
ifstream TraceFile;
TraceFile.open(programTraceFile.c_str());
if(TraceFile.fail())
{
cerr << "Cannot find file " << programTraceFile << endl;
TraceFile.close();
return false;
}
else
{
TraceFile.close();
return true;
}
}
bool checkPageStyle(string pageStyle)
{
if(pageStyle == "0")
return true;
else if(pageStyle == "1")
return true;
else
{
cerr << "Page Style can be: 0 or 1." << endl;
return false;
}
}
bool checkPageSize(int pagesize)
{
bool isValid = false;
switch(pagesize)
{
case 1:
isValid = true;
break;
case 2:
isValid = true;
break;
case 4:
isValid = true;
break;
case 8:
isValid = true;
break;
case 16:
isValid = true;
break;
default:
cerr << "Page Size can be: 1, 2, 4, 8, or 16." << endl;
isValid = false;
}
return isValid;
}
bool checkPageAlgorithm(string pageAlgorithm)
{
if(pageAlgorithm == "lru")
return true;
else if(pageAlgorithm == "fifo")
return true;
else if(pageAlgorithm == "clock")
return true;
else
{
cerr << "Valid Page Algorithms are: lru, fifo, or clock" << endl;
return false;
}
}
void checkArguments(int argc, char *argv[])
{
if(argc < 6)
{
cerr << "Invalid number of arguments. Should be: programlist programtrace pagesize pagealgorithm pagingstyle" << endl;
exit(1);
}
else if(argc > 6)
{
cerr << "Invalid number of arguments. Should be: programlist programtrace pagesize pagealgorithm pagingstyle" << endl;
exit(1);
}
else if(!checkPageAlgorithm(argv[4]))
exit(1);
else if(!checkPageSize(atoi(argv[3])))
exit(1);
else if(!checkPageStyle(argv[5]))
exit(1);
else if(!checkPTrace(argv[2]))
exit(1);
else if(!checkPList(argv[1]))
exit(1);
return;
}
The output 'heys' are just to see if they get triggered within gdb, which they don't.
You forgot to actually ask any question.
Presumably, your question is "why does my program die with SIGABRT?"
The answer was provided by the program itself: you've tried to construct std::string from a NULL character pointer, which throws std::logic_error exception (because it's not a valid thing to do).
You may have a follow-up quesiton: "where in my program does this happen?".
You could find the answer by using GDB where command. As a first guess, you didn't invoke your program with sufficient number of arguments. For example, if you did this:
gdb ./a.out
(gdb) run
then argv[1] is NULL, and this statement:
string programListFile = argv[1];
would throw the exception you are getting.
The common solution to this problem is to insist that you do have correct number of arguments, e.g. put this:
if (argc < 6) {
std::cerr << "Not enough arguments" << std::endl;
return 1;
}
at the start of main. Or just comment the call to checkArguments() back in.

highscore only refreshing on launch sqlite

I have a highscore system implemented using SQLite for a c++ shooter ive made but it only refreshes when I relaunch the game, I think this is because it only re- sorts from largest to smallest on game launch. I don't know why this is, it does seem to record all game scores as it should, but I think it is only ordering them on launch. How could I make it so it re-orders before it prints to screen. Here is the code
in main.cpp
case eHIGH_SCORES:
DrawString( "HIGHSCORES", iScreenWidth * 0.38, iScreenHeight * 0.95);
DrawString( "PRESS C TO CLOSE", iScreenWidth * 0.32, iScreenHeight * 0.1);
DatabaseMaker DatabaseMaker("MyDatabase.db");
DatabaseMaker.CreateDatabase();
DatabaseMaker.CreateTable("HIGHSCOREST");
if (scorer<1)
{
DatabaseMaker.InsertRecordIntoTable("HIGHSCOREST",scoreArray);
scorer++;
for (int i=0; i<10; i++)
{
scoreArray[i]=0;
}
}
DatabaseMaker.RetrieveRecordsFromTable("HIGHSCOREST");
DatabaseMaker.databaseprinter();
then in the Database maker class I have
DatabaseMaker::DatabaseMaker(std::string HighScoresT)
{
this->HighScores = HighScoresT;
myDatabase = NULL;
}
int DatabaseMaker::Callback(void* notUsed, int numRows, char **data, char **columnName)
{
for(int i = 0; i < numRows; i++)
{
std::cout << columnName[i] << ": " << data[i] << std::endl;
holder.push_back(atoi(data[i]));
}
return 0;
}
void DatabaseMaker::databaseprinter()
{
for (int i = 0; i < 10; i++)
{
itoa(holder[i], displayHolder, 10);
DrawString(displayHolder, 780/2, 700-(50*i));
}
}
void DatabaseMaker::CreateDatabase()
{
int errorCode = sqlite3_open(HighScores.c_str(), &myDatabase);
if(errorCode == SQLITE_OK)
{
std::cout << "Database opened successfully." << std::endl;
}
else
{
std::cout << "An error has occured." << std::endl;
}
}
void DatabaseMaker::CreateTable(std::string HIGHSCOREST)
{
char* errorMsg = NULL;
std::string sqlStatement;
sqlStatement = "CREATE TABLE HIGHSCOREST(" \
"ID INT," \
"SCORE INT);";
sqlite3_exec(myDatabase, sqlStatement.c_str(), Callback, 0, &errorMsg);
if(errorMsg != NULL)
{
std::cout << "Error message: " << errorMsg << std::endl;
}
}
void DatabaseMaker::InsertRecordIntoTable(std::string HIGHSCOREST,int (&scoreArray)[10] )
{
char*errorMsg = NULL;
std::string sqlStatement;
int x=5;
for(int i=0;i<10;i++)
{
std::string scorestring;
scorestring = std::to_string(scoreArray[i]);
sqlStatement = "INSERT INTO HIGHSCOREST (ID,SCORE)" \
"VALUES (1,"+scorestring+");";
sqlite3_exec(myDatabase, sqlStatement.c_str(), Callback, 0, &errorMsg);
if(errorMsg != NULL)
{
std::cout << "Error message: " << errorMsg << std::endl;
}
}
}
void DatabaseMaker::RetrieveRecordsFromTable(std::string HIGHSCOREST)
{
char* errorMsg = NULL;
std::string sqlStatement;
sqlStatement = "SELECT SCORE from HIGHSCOREST order by SCORE desc;";
sqlite3_exec(myDatabase, sqlStatement.c_str(), Callback, 0, &errorMsg);
if(errorMsg != NULL)
{
std::cout << "Error message: " << errorMsg << std::endl;
}
sqlite3_close(myDatabase);
}
DatabaseMaker::~DatabaseMaker()
{
}
Well, maybe try to move sqlite3_close statement off the RetrieveRecordsFromTable member function, maybe to destructor or seperate function?
Since you close connection to database after calling it for first time (and this might be a reason the problem is fixed by rerunning your application).
Also, consider changing this DatabaseMaker DatabaseMaker("MyDatabase.db"); into this: DatabaseMaker databaseMaker("MyDatabase.db");, otherwise you might have problem defining another DatabaseMaker in same scope.

FOREIGN KEY example: The return errmsg of sqlite3_errmsg() for using sqlite3_exec() is not the same as for using sqlite3_step() and sqlite3_bind()

Two tables (Teachers and Students) are created while the Id in Students is referenced to the TeacherId in Teachers using FOREIGN KEY. If we use two different methods to insert the values into Students:
1. sqlite3_exec();
2. sqlite3_bind();
The return message from sqlite3_errmsg() is not the same using these two methods:
1. sqlite3_exec(): return "foreign key constraints failed";
2. sqlite3_bind(): return "SQL logic error or missing database";
The message from sqlite3_errmsg() for sqlite3_exec() is more clearer than that for sqlite3_bind();
However, sqlite3_bind() is more convenient and efficient to insert values compared to sqlite3_exec();
My question: How to get a clearer returned error message for sqlite3_bind()?
The following is the full codes:
#include <string.h>
#include <stdio.h>
#include <iostream>
#include "sqlite3.h"
using namespace std;
sqlite3* db;
int first_row;
// callback function;
int select_callback(void *p_data, int num_fields, char **p_fields, char **p_col_names)
{
int i;
int* nof_records = (int*) p_data;
(*nof_records)++;
// first_row was defined in <select_stmt> function;
// if first_row == 1, print the first row
// and then set first_row = 0 to avoid the subsequent execution for the following rows.
if (first_row == 1)
{
first_row = 0;
for (i=0; i < num_fields; i++)
{ printf("%20s", p_col_names[i]);
}
printf("\n");
for (i=0; i< num_fields*20; i++)
{ printf("=");
}
printf("\n");
}
for(i=0; i < num_fields; i++)
{ if (p_fields[i])
{ printf("%20s", p_fields[i]);
}
else
{ printf("%20s", " ");
}
}
printf("\n");
return 0;
}
// With callback function;
void select_stmt(const char* stmt)
{ char *errmsg;
int ret;
int nrecs = 0;
first_row = 1;
ret = sqlite3_exec(db, stmt, select_callback, &nrecs, &errmsg);
if(ret!=SQLITE_OK)
{ printf("Error in select statement %s [%s].\n", stmt, errmsg);
}
else
{ printf("\n %d records returned.\n", nrecs);
}
}
// Without callback function;
void sql_stmt(const char* stmt)
{ char *errmsg;
int ret;
ret = sqlite3_exec(db, stmt, 0, 0, &errmsg);
if(ret != SQLITE_OK)
{ printf("Error in statement: %s [%s].\n", stmt, errmsg);
}
}
//////////////////////////////////////// Main /////////////////////////////////
int main()
{ cout << "sqlite3_open("", &db): " << sqlite3_open("./shcool.db", &db) << endl;
if(db == 0)
{ printf("\nCould not open database.");
return 1;
}
char *errmsg;
int result;
result = sqlite3_exec ( db,
"Drop TABLE IF EXISTS Teachers", // stmt
0,
0,
&errmsg
);
if ( result != SQLITE_OK )
{ cout << "\nCould not prepare statement: Drop TABLE: " << result << endl;
cout << "errmsg: " << errmsg << endl;
return 1;
}
result = sqlite3_exec ( db,
"Drop TABLE IF EXISTS Students", // stmt
0,
0,
&errmsg
);
if ( result != SQLITE_OK )
{ cout << "\nCould not prepare statement: Drop TABLE: " << result << endl;
cout << "errmsg: " << errmsg << endl;
return 1;
}
// CREATE TABLE Teachers;
sql_stmt("CREATE TABLE Teachers(Id integer PRIMARY KEY,Name text,Age integer NOT NULL)");
//////////////////////////// insert values into Teachers; /////////////////////////////////
sqlite3_stmt *stmt;
sqlite3_prepare(db, "PRAGMA foreign_keys = ON;", -1, &stmt, 0);
if ( sqlite3_prepare
( db,
"insert into Teachers values (:Id,:Name,:Age)", // stmt
-1, // If than zero, then stmt is read up to the first nul terminator
&stmt,
0 // Pointer to unused portion of stmt
)
!= SQLITE_OK )
{ printf("\nCould not prepare statement.");
return 1;
}
int index1, index2, index3;
index1 = sqlite3_bind_parameter_index(stmt, ":Id");
index2 = sqlite3_bind_parameter_index(stmt, ":Name");
index3 = sqlite3_bind_parameter_index(stmt, ":Age");
cout << index1 << endl;
cout << index2 << endl;
cout << index3 << endl;
printf("\nThe statement has %d wildcards\n", sqlite3_bind_parameter_count(stmt));
int id[] = {1, 2, 3};
string name[] = {"Zhang", "Hou", "Liu"};
int age[] = {28, 29, 31};
for ( int i = 0; i != 3; i++ )
{ if (sqlite3_bind_int
(stmt,
index1, // Index of wildcard
id[i]
)
!= SQLITE_OK)
{ printf("\nCould not bind sqlite3_bind_int.\n");
return 1;
}
if (sqlite3_bind_text
( stmt,
index2, // Index of wildcard
name[i].c_str(),
strlen(name[i].c_str()),
SQLITE_STATIC
)
!= SQLITE_OK)
{ printf("\nCould not bind sqlite3_bind_text.\n");
return 1;
}
if (sqlite3_bind_int
( stmt,
index3, // Index of wildcard
age[i]
)
!= SQLITE_OK)
{ printf("\nCould not bind sqlite3_bind_int.\n");
return 1;
}
// execute sqlite3_step(), and check the state of the statement
if (sqlite3_step(stmt) != SQLITE_DONE)
{ printf("\nCould not step (execute) stmt.\n");
return 1;
}
// reset the statement if you want to continue the sqlite3_bind().
cout << "sqlite3_reset(stmt): " << sqlite3_reset(stmt) << endl;
}
//////////////////////////// insert values into Students; /////////////////////////////////
// CREATE TABLE Students;
sql_stmt("CREATE TABLE Students (Id integer PRIMARY KEY, TeacherId integer, FOREIGN KEY(TeacherId) REFERENCES Teachers(id) )" );
//////// Method 1: use sqlite3_exec to insert values; ////////////
sql_stmt("INSERT INTO Students Values (0, 1)" );
sql_stmt("INSERT INTO Students Values (1, 2)" );
sql_stmt("INSERT INTO Students Values (2, 9)" );
cout << "sqlite3_errmsg: " << sqlite3_errmsg(db) << endl;
//////// Method 2: use sqlite3_bind to insert values; ////////////
result = sqlite3_exec ( db,
"Drop TABLE IF EXISTS Students", // stmt
0,
0,
&errmsg
);
if ( result != SQLITE_OK )
{ cout << "\nCould not prepare statement: Drop TABLE: " << result << endl;
cout << "errmsg: " << errmsg << endl;
return 1;
}
// CREATE TABLE Students;
sql_stmt("CREATE TABLE Students (Id integer PRIMARY KEY, TeacherId integer, FOREIGN KEY(TeacherId) REFERENCES Teachers(id) )" );
if ( sqlite3_prepare
( db,
"insert into Students values ( :Id,:TeacherId )", // stmt
-1, // If than zero, then stmt is read up to the first nul terminator
&stmt,
0 // Pointer to unused portion of stmt
)
!= SQLITE_OK )
{ printf("\nCould not prepare statement.");
return 1;
}
index1 = sqlite3_bind_parameter_index(stmt, ":Id");
index2 = sqlite3_bind_parameter_index(stmt, ":TeacherId");
cout << index1 << endl;
cout << index2 << endl;
printf("\nThe statement has %d wildcards\n", sqlite3_bind_parameter_count(stmt));
int studentId[] = {0, 1, 2};
/// if the FOREIGN KEY works, the teacherId should not be 9;
int teacherId[] = {1, 2, 9};
for ( int i = 0; i != 3; i++ )
{ if (sqlite3_bind_int
(stmt,
index1, // Index of wildcard
studentId[i]
)
!= SQLITE_OK)
{ printf("\nCould not bind sqlite3_bind_int.\n");
return 1;
}
if (sqlite3_bind_int
( stmt,
index2, // Index of wildcard
teacherId[i]
)
!= SQLITE_OK)
{ printf("\nCould not bind sqlite3_bind_int.\n");
cout << "sqlite3_errmsg: " << sqlite3_errmsg(db) << endl;
return 1;
}
// execute sqlite3_step(), and check the state of the statement
// cout << "sqlite3_errmsg: " << sqlite3_errmsg(db) << endl;
if ( result = sqlite3_step(stmt) != SQLITE_DONE )
{ printf("\nCould not step (execute) stmt.\n");
cout << "sqlite3_errmsg: " << sqlite3_errmsg(db) << endl;
cout << "result: " << result << endl;
return 1;
}
cout << "result: " << result << endl;
// reset the statement if you want to continue the sqlite3_bind().
cout << "sqlite3_reset(stmt): " << sqlite3_reset(stmt) << endl;
}
printf("\n");
// Print all;
select_stmt("select * from Teachers");
select_stmt("select * from Students");
sqlite3_close(db);
return 0;
}
It's recommended to use sqlite3_prepare_v2 instead of sqlite3_prepare. Excerpt:
The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are recommended for all new programs. The two older interfaces are retained for backwards compatibility, but their use is discouraged. In the "v2" interfaces, the prepared statement that is returned (the sqlite3_stmt object) contains a copy of the original SQL text. This causes the sqlite3_step() interface to behave differently in three ways:
[Point 1 omitted]
2. When an error occurs, sqlite3_step() will return one of the detailed error codes or extended error codes. The legacy behavior was that sqlite3_step() would only return a generic SQLITE_ERROR result code and the application would have to make a second call to sqlite3_reset() in order to find the underlying cause of the problem. With the "v2" prepare interfaces, the underlying reason for the error is returned immediately.
[Point 3 omitted]