2014-04-12 17:10:43 +02:00
|
|
|
/*
|
|
|
|
* db.c
|
|
|
|
*
|
|
|
|
* See the README file for copyright information and how to reach the author.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
#include <errmsg.h>
|
2014-04-12 17:10:43 +02:00
|
|
|
|
|
|
|
#include "db.h"
|
|
|
|
|
|
|
|
// #define DEB_HANDLER
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// DB Statement
|
|
|
|
//***************************************************************************
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
int cDbStatement::explain = no;
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
cDbStatement::cDbStatement(cDbTable* aTable)
|
|
|
|
{
|
|
|
|
table = aTable;
|
|
|
|
connection = table->getConnection();
|
|
|
|
stmtTxt = "";
|
|
|
|
stmt = 0;
|
|
|
|
inCount = 0;
|
|
|
|
outCount = 0;
|
|
|
|
inBind = 0;
|
|
|
|
outBind = 0;
|
|
|
|
affected = 0;
|
|
|
|
metaResult = 0;
|
|
|
|
bindPrefix = 0;
|
2014-05-09 01:05:21 +02:00
|
|
|
firstExec = yes;
|
|
|
|
|
|
|
|
callsPeriod = 0;
|
|
|
|
callsTotal = 0;
|
|
|
|
duration = 0;
|
|
|
|
|
|
|
|
if (connection)
|
|
|
|
connection->statements.append(this);
|
2014-04-12 17:10:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
cDbStatement::cDbStatement(cDbConnection* aConnection, const char* stmt)
|
|
|
|
{
|
|
|
|
table = 0;
|
|
|
|
connection = aConnection;
|
|
|
|
stmtTxt = stmt;
|
|
|
|
stmt = 0;
|
|
|
|
inCount = 0;
|
|
|
|
outCount = 0;
|
|
|
|
inBind = 0;
|
|
|
|
outBind = 0;
|
|
|
|
affected = 0;
|
|
|
|
metaResult = 0;
|
|
|
|
bindPrefix = 0;
|
2014-05-09 01:05:21 +02:00
|
|
|
firstExec = yes;
|
|
|
|
|
|
|
|
callsPeriod = 0;
|
|
|
|
callsTotal = 0;
|
|
|
|
duration = 0;
|
|
|
|
|
|
|
|
if (connection)
|
|
|
|
connection->statements.append(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
cDbStatement::~cDbStatement()
|
|
|
|
{
|
|
|
|
if (connection)
|
|
|
|
connection->statements.remove(this);
|
|
|
|
|
|
|
|
clear();
|
2014-04-12 17:10:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Execute
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbStatement::execute(int noResult)
|
|
|
|
{
|
|
|
|
affected = 0;
|
|
|
|
|
|
|
|
if (!connection || !connection->getMySql())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
if (!stmt)
|
|
|
|
return connection->errorSql(connection, "execute(missing statement)");
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
// if (explain && firstExec)
|
|
|
|
// {
|
|
|
|
// firstExec = no;
|
|
|
|
|
|
|
|
// if (strstr(stmtTxt.c_str(), "select "))
|
|
|
|
// {
|
|
|
|
// MYSQL_RES* result;
|
|
|
|
// MYSQL_ROW row;
|
|
|
|
// string q = "explain " + stmtTxt;
|
|
|
|
|
|
|
|
// if (connection->query(q.c_str()) != success)
|
|
|
|
// connection->errorSql(connection, "explain ", 0);
|
|
|
|
// else if ((result = mysql_store_result(connection->getMySql())))
|
|
|
|
// {
|
|
|
|
// while ((row = mysql_fetch_row(result)))
|
|
|
|
// {
|
|
|
|
// tell(0, "EXPLAIN: %s) %s %s %s %s %s %s %s %s %s",
|
|
|
|
// row[0], row[1], row[2], row[3],
|
|
|
|
// row[4], row[5], row[6], row[7], row[8], row[9]);
|
|
|
|
// }
|
|
|
|
|
|
|
|
// mysql_free_result(result);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
// tell(0, "execute %d [%s]", stmt, stmtTxt.c_str());
|
|
|
|
|
2014-05-10 09:24:30 +02:00
|
|
|
double start = usNow();
|
2014-05-09 01:05:21 +02:00
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
if (mysql_stmt_execute(stmt))
|
|
|
|
return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str());
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
duration += usNow() - start;
|
|
|
|
callsPeriod++;
|
|
|
|
callsTotal++;
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
// out binding - if needed
|
|
|
|
|
|
|
|
if (outCount && !noResult)
|
|
|
|
{
|
|
|
|
if (mysql_stmt_store_result(stmt))
|
|
|
|
return connection->errorSql(connection, "execute(store_result)", stmt, stmtTxt.c_str());
|
|
|
|
|
|
|
|
// fetch the first result - if any
|
|
|
|
|
|
|
|
if (mysql_stmt_affected_rows(stmt) > 0)
|
|
|
|
mysql_stmt_fetch(stmt);
|
|
|
|
}
|
|
|
|
else if (outCount)
|
|
|
|
{
|
|
|
|
mysql_stmt_store_result(stmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
// result was stored (above) only if output (outCound) is expected,
|
|
|
|
// therefore we don't need to call freeResult() after insert() or update()
|
|
|
|
|
|
|
|
affected = mysql_stmt_affected_rows(stmt);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::getResultCount()
|
|
|
|
{
|
|
|
|
mysql_stmt_store_result(stmt);
|
|
|
|
|
|
|
|
return mysql_stmt_affected_rows(stmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::find()
|
|
|
|
{
|
|
|
|
if (execute() != success)
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
return getAffected() > 0 ? yes : no;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::fetch()
|
|
|
|
{
|
|
|
|
if (!mysql_stmt_fetch(stmt))
|
|
|
|
return yes;
|
|
|
|
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::freeResult()
|
|
|
|
{
|
|
|
|
if (metaResult)
|
|
|
|
mysql_free_result(metaResult);
|
|
|
|
|
|
|
|
if (stmt)
|
|
|
|
mysql_stmt_free_result(stmt);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Build Statements - new Interface
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbStatement::build(const char* format, ...)
|
|
|
|
{
|
|
|
|
if (format)
|
|
|
|
{
|
|
|
|
char* tmp;
|
|
|
|
|
|
|
|
va_list more;
|
|
|
|
va_start(more, format);
|
|
|
|
vasprintf(&tmp, format, more);
|
|
|
|
|
|
|
|
stmtTxt += tmp;
|
|
|
|
free(tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::bind(int field, int mode, const char* delim)
|
|
|
|
{
|
|
|
|
return bind(table->getRow()->getValue(field), mode, delim);
|
|
|
|
}
|
|
|
|
|
2014-04-18 17:08:11 +02:00
|
|
|
int cDbStatement::bind(cDbTable* aTable, int field, int mode, const char* delim)
|
|
|
|
{
|
|
|
|
return bind(aTable->getRow()->getValue(field), mode, delim);
|
|
|
|
}
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
int cDbStatement::bind(cDbValue* value, int mode, const char* delim)
|
|
|
|
{
|
|
|
|
if (!value || !value->getField())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
if (delim)
|
|
|
|
stmtTxt += delim;
|
|
|
|
|
|
|
|
if (bindPrefix)
|
|
|
|
stmtTxt += bindPrefix;
|
|
|
|
|
|
|
|
if (mode & bndIn)
|
|
|
|
{
|
|
|
|
if (mode & bndSet)
|
|
|
|
stmtTxt += value->getName() + string(" =");
|
|
|
|
|
|
|
|
stmtTxt += " ?";
|
|
|
|
appendBinding(value, bndIn);
|
|
|
|
}
|
|
|
|
else if (mode & bndOut)
|
|
|
|
{
|
|
|
|
stmtTxt += value->getName();
|
|
|
|
appendBinding(value, bndOut);
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::bindCmp(const char* ctable, cDbValue* value,
|
|
|
|
const char* comp, const char* delim)
|
|
|
|
{
|
|
|
|
if (ctable)
|
|
|
|
build("%s.", ctable);
|
|
|
|
|
2014-04-18 17:08:11 +02:00
|
|
|
build("%s%s%s %s ?", delim ? delim : "", bindPrefix ? bindPrefix : "", value->getName(), comp);
|
2014-04-12 17:10:43 +02:00
|
|
|
|
|
|
|
appendBinding(value, bndIn);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbStatement::bindCmp(const char* ctable, int field, cDbValue* value,
|
|
|
|
const char* comp, const char* delim)
|
|
|
|
{
|
|
|
|
cDbValue* vf = table->getRow()->getValue(field);
|
|
|
|
cDbValue* vv = value ? value : vf;
|
|
|
|
|
|
|
|
if (ctable)
|
|
|
|
build("%s.", ctable);
|
|
|
|
|
2014-04-18 17:08:11 +02:00
|
|
|
build("%s%s%s %s ?", delim ? delim : "", bindPrefix ? bindPrefix : "", vf->getName(), comp);
|
2014-04-12 17:10:43 +02:00
|
|
|
|
|
|
|
appendBinding(vv, bndIn);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Clear
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
void cDbStatement::clear()
|
|
|
|
{
|
|
|
|
stmtTxt = "";
|
|
|
|
affected = 0;
|
|
|
|
|
|
|
|
if (inCount)
|
|
|
|
{
|
|
|
|
free(inBind);
|
|
|
|
inCount = 0;
|
|
|
|
inBind = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (outCount)
|
|
|
|
{
|
|
|
|
free(outBind);
|
|
|
|
outCount = 0;
|
|
|
|
outBind = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stmt)
|
|
|
|
{
|
|
|
|
mysql_stmt_free_result(stmt);
|
|
|
|
mysql_stmt_close(stmt);
|
|
|
|
stmt = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Append Binding
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbStatement::appendBinding(cDbValue* value, BindType bt)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
MYSQL_BIND** bindings = 0;
|
|
|
|
MYSQL_BIND* newBinding;
|
|
|
|
|
|
|
|
if (bt & bndIn)
|
|
|
|
{
|
|
|
|
count = ++inCount;
|
|
|
|
bindings = &inBind;
|
|
|
|
}
|
|
|
|
else if (bt & bndOut)
|
|
|
|
{
|
|
|
|
count = ++outCount;
|
|
|
|
bindings = &outBind;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!bindings)
|
|
|
|
*bindings = (MYSQL_BIND*)malloc(count * sizeof(MYSQL_BIND));
|
|
|
|
else
|
2014-05-09 01:05:21 +02:00
|
|
|
*bindings = (MYSQL_BIND*)srealloc(*bindings, count * sizeof(MYSQL_BIND));
|
2014-04-12 17:10:43 +02:00
|
|
|
|
|
|
|
newBinding = &((*bindings)[count-1]);
|
|
|
|
|
|
|
|
if (value->getField()->format == ffAscii || value->getField()->format == ffText)
|
|
|
|
{
|
|
|
|
newBinding->buffer_type = MYSQL_TYPE_STRING;
|
|
|
|
newBinding->buffer = value->getStrValueRef();
|
|
|
|
newBinding->buffer_length = value->getField()->size;
|
|
|
|
newBinding->length = value->getStrValueSizeRef();
|
|
|
|
|
|
|
|
newBinding->is_null = value->getNullRef();
|
|
|
|
newBinding->error = 0; // #TODO
|
|
|
|
}
|
|
|
|
else if (value->getField()->format == ffMlob)
|
|
|
|
{
|
|
|
|
newBinding->buffer_type = MYSQL_TYPE_BLOB;
|
|
|
|
newBinding->buffer = value->getStrValueRef();
|
|
|
|
newBinding->buffer_length = value->getField()->size;
|
|
|
|
newBinding->length = value->getStrValueSizeRef();
|
|
|
|
|
|
|
|
newBinding->is_null = value->getNullRef();
|
|
|
|
newBinding->error = 0; // #TODO
|
|
|
|
}
|
|
|
|
else if (value->getField()->format == ffFloat)
|
|
|
|
{
|
|
|
|
newBinding->buffer_type = MYSQL_TYPE_FLOAT;
|
|
|
|
newBinding->buffer = value->getFloatValueRef();
|
|
|
|
|
|
|
|
newBinding->length = 0; // #TODO
|
|
|
|
newBinding->is_null = value->getNullRef();
|
|
|
|
newBinding->error = 0; // #TODO
|
|
|
|
}
|
|
|
|
else if (value->getField()->format == ffDateTime)
|
|
|
|
{
|
|
|
|
newBinding->buffer_type = MYSQL_TYPE_DATETIME;
|
|
|
|
newBinding->buffer = value->getTimeValueRef();
|
|
|
|
|
|
|
|
newBinding->length = 0; // #TODO
|
|
|
|
newBinding->is_null = value->getNullRef();
|
|
|
|
newBinding->error = 0; // #TODO
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newBinding->buffer_type = MYSQL_TYPE_LONG;
|
|
|
|
newBinding->buffer = value->getIntValueRef();
|
|
|
|
newBinding->is_unsigned = (value->getField()->format == ffUInt);
|
|
|
|
|
|
|
|
newBinding->length = 0;
|
|
|
|
newBinding->is_null = value->getNullRef();
|
|
|
|
newBinding->error = 0; // #TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Prepare Statement
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbStatement::prepare()
|
|
|
|
{
|
|
|
|
if (!stmtTxt.length() || !connection->getMySql())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
stmt = mysql_stmt_init(connection->getMySql());
|
|
|
|
|
|
|
|
// prepare statement
|
|
|
|
|
|
|
|
if (mysql_stmt_prepare(stmt, stmtTxt.c_str(), stmtTxt.length()))
|
|
|
|
return connection->errorSql(connection, "prepare(stmt_prepare)", stmt, stmtTxt.c_str());
|
|
|
|
|
|
|
|
if (outBind)
|
|
|
|
{
|
|
|
|
if (mysql_stmt_bind_result(stmt, outBind))
|
|
|
|
return connection->errorSql(connection, "execute(bind_result)", stmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inBind)
|
|
|
|
{
|
|
|
|
if (mysql_stmt_bind_param(stmt, inBind))
|
|
|
|
return connection->errorSql(connection, "buildPrimarySelect(bind_param)", stmt);
|
|
|
|
}
|
|
|
|
|
2014-04-18 17:08:11 +02:00
|
|
|
tell(2, "Statement '%s' with (%ld) in parameters and (%d) out bindings prepared",
|
2014-04-12 17:10:43 +02:00
|
|
|
stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
//***************************************************************************
|
|
|
|
// Show Statistic
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
void cDbStatement::showStat()
|
|
|
|
{
|
|
|
|
if (callsPeriod)
|
|
|
|
{
|
|
|
|
tell(0, "calls %4ld in %6.2fms; total %4ld [%s]",
|
|
|
|
callsPeriod, duration/1000, callsTotal, stmtTxt.c_str());
|
|
|
|
|
|
|
|
callsPeriod = 0;
|
|
|
|
duration = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
//***************************************************************************
|
|
|
|
// cDbService
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
const char* cDbService::formats[] =
|
|
|
|
{
|
|
|
|
"INT",
|
|
|
|
"INT",
|
|
|
|
"VARCHAR",
|
|
|
|
"TEXT",
|
|
|
|
"MEDIUMBLOB",
|
|
|
|
"FLOAT",
|
|
|
|
"DATETIME",
|
|
|
|
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
const char* cDbService::toString(FieldFormat t)
|
|
|
|
{
|
|
|
|
return formats[t];
|
|
|
|
}
|
|
|
|
|
2014-05-09 01:05:21 +02:00
|
|
|
const char* cDbService::dictFormats[] =
|
|
|
|
{
|
|
|
|
"int",
|
|
|
|
"uint",
|
|
|
|
"ascii",
|
|
|
|
"text",
|
|
|
|
"mlob",
|
|
|
|
"float",
|
|
|
|
"datetime",
|
|
|
|
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
cDbService::FieldFormat cDbService::toDictFormat(const char* format)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < ffCount; i++)
|
|
|
|
if (strcasecmp(dictFormats[i], format) == 0)
|
|
|
|
return (FieldFormat)i;
|
|
|
|
|
|
|
|
return ffUnknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* cDbService::types[] =
|
|
|
|
{
|
|
|
|
"data",
|
|
|
|
"primary",
|
|
|
|
"meta",
|
|
|
|
"calc",
|
|
|
|
"autoinc",
|
|
|
|
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
cDbService::FieldType cDbService::toType(const char* type)
|
|
|
|
{
|
|
|
|
// #TODO !!!
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
if (strcasecmp(types[i], type) == 0)
|
|
|
|
return (FieldType)i;
|
|
|
|
|
|
|
|
return ftUnknown;
|
|
|
|
}
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
//***************************************************************************
|
|
|
|
// Class cDbTable
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
char* cDbTable::confPath = 0;
|
|
|
|
|
|
|
|
char* cDbConnection::encoding = 0;
|
|
|
|
char* cDbConnection::dbHost = strdup("localhost");
|
|
|
|
int cDbConnection::dbPort = 3306;
|
|
|
|
char* cDbConnection::dbUser = 0;
|
|
|
|
char* cDbConnection::dbPass = 0;
|
|
|
|
char* cDbConnection::dbName = 0;
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Object
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
cDbTable::cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i)
|
|
|
|
{
|
|
|
|
connection = aConnection;
|
|
|
|
row = new cDbRow(f);
|
|
|
|
holdInMemory = no;
|
|
|
|
|
|
|
|
stmtSelect = 0;
|
|
|
|
stmtInsert = 0;
|
|
|
|
stmtUpdate = 0;
|
|
|
|
|
|
|
|
indices = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
cDbTable::~cDbTable()
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
|
|
|
|
delete row;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Open / Close
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::open()
|
|
|
|
{
|
|
|
|
if (connection->attachConnection() != success)
|
|
|
|
{
|
|
|
|
tell(0, "Could not access database '%s:%d' (tried to open %s)",
|
|
|
|
connection->getHost(), connection->getPort(), TableName());
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return init();
|
|
|
|
}
|
|
|
|
|
|
|
|
int cDbTable::close()
|
|
|
|
{
|
|
|
|
if (stmtSelect) { delete stmtSelect; stmtSelect = 0; }
|
|
|
|
if (stmtInsert) { delete stmtInsert; stmtInsert = 0; }
|
|
|
|
if (stmtUpdate) { delete stmtUpdate; stmtUpdate = 0; }
|
|
|
|
|
|
|
|
connection->detachConnection();
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Init
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::init()
|
|
|
|
{
|
|
|
|
string str;
|
|
|
|
|
|
|
|
if (!isConnected()) return fail;
|
|
|
|
|
|
|
|
// check/create table ...
|
|
|
|
|
|
|
|
if (createTable() != success)
|
|
|
|
return fail;
|
|
|
|
|
2014-04-18 17:08:11 +02:00
|
|
|
// check/create indices
|
|
|
|
|
|
|
|
createIndices();
|
|
|
|
|
2014-04-12 17:10:43 +02:00
|
|
|
// ------------------------------
|
|
|
|
// prepare BASIC statements
|
|
|
|
// ------------------------------
|
|
|
|
|
|
|
|
// select by primary key ...
|
|
|
|
|
|
|
|
stmtSelect = new cDbStatement(this);
|
|
|
|
|
|
|
|
stmtSelect->build("select ");
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; row->getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (row->getField(i)->type & ftCalc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stmtSelect->bind(i, bndOut, n++ ? ", " : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
stmtSelect->build(" from %s where ", TableName());
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; row->getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (!(row->getField(i)->type & ftPrimary))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stmtSelect->bind(i, bndIn | bndSet, n++ ? " and " : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
stmtSelect->build(";");
|
|
|
|
|
|
|
|
if (stmtSelect->prepare() != success)
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
// -----------------------------------------
|
|
|
|
// insert
|
|
|
|
|
|
|
|
stmtInsert = new cDbStatement(this);
|
|
|
|
|
|
|
|
stmtInsert->build("insert into %s set ", TableName());
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; row->getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
// don't insert autoinc and calculated fields
|
|
|
|
|
|
|
|
if (row->getField(i)->type & ftCalc || row->getField(i)->type & ftAutoinc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stmtInsert->bind(i, bndIn | bndSet, n++ ? ", " : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
stmtInsert->build(";");
|
|
|
|
|
|
|
|
if (stmtInsert->prepare() != success)
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
// -----------------------------------------
|
|
|
|
// update via primary key ...
|
|
|
|
|
|
|
|
stmtUpdate = new cDbStatement(this);
|
|
|
|
|
|
|
|
stmtUpdate->build("update %s set ", TableName());
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; row->getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
// don't update PKey, autoinc and calculated fields
|
|
|
|
|
|
|
|
if (row->getField(i)->type & ftPrimary ||
|
|
|
|
row->getField(i)->type & ftCalc ||
|
|
|
|
row->getField(i)->type & ftAutoinc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (strcmp(row->getField(i)->name, "inssp") == 0) // don't update the insert stamp
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stmtUpdate->bind(i, bndIn | bndSet, n++ ? ", " : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
stmtUpdate->build(" where ");
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; row->getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (!(row->getField(i)->type & ftPrimary))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stmtUpdate->bind(i, bndIn | bndSet, n++ ? " and " : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
stmtUpdate->build(";");
|
|
|
|
|
|
|
|
if (stmtUpdate->prepare() != success)
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Check Table
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::exist(const char* name)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
name = TableName();
|
|
|
|
|
|
|
|
MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name);
|
|
|
|
MYSQL_ROW tabRow = mysql_fetch_row(result);
|
|
|
|
mysql_free_result(result);
|
|
|
|
|
|
|
|
return tabRow ? yes : no;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Create Table
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::createTable()
|
|
|
|
{
|
|
|
|
string statement;
|
|
|
|
string aKey;
|
|
|
|
|
|
|
|
if (!isConnected())
|
|
|
|
{
|
|
|
|
if (connection->attachConnection() != success)
|
|
|
|
{
|
|
|
|
tell(0, "Could not access database '%s:%d' (tried to create %s)",
|
|
|
|
connection->getHost(), connection->getPort(), TableName());
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// table exists -> nothing to do
|
|
|
|
|
|
|
|
if (exist())
|
|
|
|
return done;
|
|
|
|
|
|
|
|
tell(0, "Initialy creating table '%s'", TableName());
|
|
|
|
|
|
|
|
// build 'create' statement ...
|
|
|
|
|
|
|
|
statement = string("create table ") + TableName() + string("(");
|
|
|
|
|
|
|
|
for (int i = 0; getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
int size = getField(i)->size;
|
|
|
|
char num[10];
|
|
|
|
|
|
|
|
if (getField(i)->type & ftCalc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (i) statement += string(", ");
|
|
|
|
statement += string(getField(i)->name) + " " + string(toString(getField(i)->format));
|
|
|
|
|
|
|
|
if (getField(i)->format != ffMlob)
|
|
|
|
{
|
|
|
|
if (!size) size = getField(i)->format == ffAscii || getField(i)->format == ffText ? 100 : 11;
|
|
|
|
|
|
|
|
if (getField(i)->format != ffFloat)
|
|
|
|
sprintf(num, "%d", size);
|
|
|
|
else
|
|
|
|
sprintf(num, "%d,%d", size/10, size%10);
|
|
|
|
|
|
|
|
statement += "(" + string(num) + ")";
|
|
|
|
|
|
|
|
if (getField(i)->format == ffUInt)
|
|
|
|
statement += " unsigned";
|
|
|
|
|
|
|
|
if (getField(i)->type & ftAutoinc)
|
|
|
|
statement += " not null auto_increment";
|
|
|
|
else if (getField(i)->type & ftDef0)
|
|
|
|
statement += " default '0'";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aKey = "";
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (getField(i)->type & ftPrimary)
|
|
|
|
{
|
|
|
|
if (n++) aKey += string(", ");
|
|
|
|
aKey += string(getField(i)->name) + " DESC";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aKey.length())
|
|
|
|
{
|
|
|
|
statement += string(", PRIMARY KEY(");
|
|
|
|
statement += aKey;
|
|
|
|
statement += ")";
|
|
|
|
}
|
|
|
|
|
|
|
|
aKey = "";
|
|
|
|
|
|
|
|
for (int i = 0, n = 0; getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (getField(i)->type & ftAutoinc && !(getField(i)->type & ftPrimary))
|
|
|
|
{
|
|
|
|
if (n++) aKey += string(", ");
|
|
|
|
aKey += string(getField(i)->name) + " DESC";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aKey.length())
|
|
|
|
{
|
|
|
|
statement += string(", KEY(");
|
|
|
|
statement += aKey;
|
|
|
|
statement += ")";
|
|
|
|
}
|
|
|
|
|
|
|
|
// statement += string(") ENGINE MYISAM;");
|
|
|
|
statement += string(") ENGINE InnoDB;");
|
|
|
|
|
|
|
|
tell(1, "%s", statement.c_str());
|
|
|
|
|
|
|
|
if (connection->query(statement.c_str()))
|
|
|
|
return connection->errorSql(getConnection(), "createTable()",
|
|
|
|
0, statement.c_str());
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Create Indices
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::createIndices()
|
|
|
|
{
|
|
|
|
string statement;
|
|
|
|
|
|
|
|
tell(5, "Initialy checking indices for '%s'", TableName());
|
|
|
|
|
|
|
|
// check/create indexes
|
|
|
|
|
|
|
|
if (!indices)
|
|
|
|
return done;
|
|
|
|
|
|
|
|
for (int i = 0; getIndex(i)->name; i++)
|
|
|
|
{
|
|
|
|
IndexDef* index = getIndex(i);
|
|
|
|
int fCount;
|
|
|
|
string idxName;
|
|
|
|
int expectCount = 0;
|
|
|
|
|
|
|
|
for (; index->fields[expectCount] != na; expectCount++) ;
|
|
|
|
|
|
|
|
if (!expectCount)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// check
|
|
|
|
|
|
|
|
idxName = "idx" + string(index->name);
|
|
|
|
|
|
|
|
checkIndex(idxName.c_str(), fCount);
|
|
|
|
|
|
|
|
if (fCount != expectCount)
|
|
|
|
{
|
|
|
|
// create index
|
|
|
|
|
|
|
|
statement = "create index " + idxName;
|
|
|
|
statement += " on " + string(TableName()) + "(";
|
|
|
|
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for (int f = 0; index->fields[f] != na; f++)
|
|
|
|
{
|
|
|
|
FieldDef* fld = getField(index->fields[f]);
|
|
|
|
|
|
|
|
if (fld && !(fld->type & ftCalc))
|
|
|
|
{
|
|
|
|
if (n++) statement += string(", ");
|
|
|
|
statement += fld->name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!n) continue;
|
|
|
|
|
|
|
|
statement += ");";
|
|
|
|
tell(1, "%s", statement.c_str());
|
|
|
|
|
|
|
|
if (connection->query(statement.c_str()))
|
|
|
|
return connection->errorSql(getConnection(), "createIndices()",
|
|
|
|
0, statement.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Check Index
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::checkIndex(const char* idxName, int& fieldCount)
|
|
|
|
{
|
|
|
|
enum IndexQueryFields
|
|
|
|
{
|
|
|
|
idTable,
|
|
|
|
idNonUnique,
|
|
|
|
idKeyName,
|
|
|
|
idSeqInIndex,
|
|
|
|
idColumnName,
|
|
|
|
idCollation,
|
|
|
|
idCardinality,
|
|
|
|
idSubPart,
|
|
|
|
idPacked,
|
|
|
|
idNull,
|
|
|
|
idIndexType,
|
|
|
|
idComment,
|
|
|
|
idIndexComment,
|
|
|
|
|
|
|
|
idCount
|
|
|
|
};
|
|
|
|
|
|
|
|
MYSQL_RES* result;
|
|
|
|
MYSQL_ROW row;
|
|
|
|
|
|
|
|
fieldCount = 0;
|
|
|
|
|
|
|
|
if (connection->query("show index from %s", TableName()) != success)
|
|
|
|
{
|
|
|
|
connection->errorSql(getConnection(), "checkIndex()", 0);
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((result = mysql_store_result(connection->getMySql())))
|
|
|
|
{
|
|
|
|
while ((row = mysql_fetch_row(result)))
|
|
|
|
{
|
|
|
|
tell(5, "%s: %-20s %s %s",
|
|
|
|
row[idTable], row[idKeyName],
|
|
|
|
row[idSeqInIndex], row[idColumnName]);
|
|
|
|
|
|
|
|
if (strcasecmp(row[idKeyName], idxName) == 0)
|
|
|
|
fieldCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mysql_free_result(result);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
connection->errorSql(getConnection(), "checkIndex()");
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Copy Values
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
void cDbTable::copyValues(cDbRow* r)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fieldCount(); i++)
|
|
|
|
{
|
|
|
|
if (getField(i)->format == ffAscii || getField(i)->format == ffText)
|
|
|
|
row->setValue(i, r->getStrValue(i));
|
|
|
|
else
|
|
|
|
row->setValue(i, r->getIntValue(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// SQL Error
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbConnection::errorSql(cDbConnection* connection, const char* prefix, MYSQL_STMT* stmt, const char* stmtTxt)
|
|
|
|
{
|
|
|
|
if (!connection || !connection->mysql)
|
|
|
|
{
|
|
|
|
tell(0, "SQL-Error in '%s'", prefix);
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
int error = mysql_errno(connection->mysql);
|
|
|
|
char* conErr = 0;
|
|
|
|
char* stmtErr = 0;
|
|
|
|
|
|
|
|
if (error == CR_SERVER_LOST ||
|
|
|
|
error == CR_SERVER_GONE_ERROR ||
|
|
|
|
error == CR_INVALID_CONN_HANDLE ||
|
|
|
|
error == CR_SERVER_LOST_EXTENDED)
|
|
|
|
connectDropped = yes;
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
asprintf(&conErr, "%s (%d) ", mysql_error(connection->mysql), error);
|
|
|
|
|
|
|
|
if (stmt || stmtTxt)
|
|
|
|
asprintf(&stmtErr, "'%s' [%s]",
|
|
|
|
stmt ? mysql_stmt_error(stmt) : "",
|
|
|
|
stmtTxt ? stmtTxt : "");
|
|
|
|
|
|
|
|
tell(0, "SQL-Error in '%s' - %s%s", prefix,
|
|
|
|
conErr ? conErr : "", stmtErr ? stmtErr : "");
|
|
|
|
|
|
|
|
free(conErr);
|
|
|
|
free(stmtErr);
|
|
|
|
|
|
|
|
if (connectDropped)
|
|
|
|
tell(0, "Fatal, lost connection to mysql server, aborting pending actions");
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Delete Where
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::deleteWhere(const char* where)
|
|
|
|
{
|
|
|
|
string tmp;
|
|
|
|
|
|
|
|
if (!connection || !connection->getMySql())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
tmp = "delete from " + string(TableName()) + " where " + string(where);
|
|
|
|
|
|
|
|
if (connection->query(tmp.c_str()))
|
|
|
|
return connection->errorSql(connection, "deleteWhere()", 0, tmp.c_str());
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Coiunt Where
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::countWhere(const char* where, int& count, const char* what)
|
|
|
|
{
|
|
|
|
string tmp;
|
|
|
|
MYSQL_RES* res;
|
|
|
|
MYSQL_ROW data;
|
|
|
|
|
|
|
|
count = 0;
|
|
|
|
|
|
|
|
if (isEmpty(what))
|
|
|
|
what = "count(1)";
|
|
|
|
|
|
|
|
if (!isEmpty(where))
|
|
|
|
tmp = "select " + string(what) + " from " + string(TableName()) + " where " + string(where);
|
|
|
|
else
|
|
|
|
tmp = "select " + string(what) + " from " + string(TableName());
|
|
|
|
|
|
|
|
if (connection->query(tmp.c_str()))
|
|
|
|
return connection->errorSql(connection, "countWhere()", 0, tmp.c_str());
|
|
|
|
|
|
|
|
if (res = mysql_store_result(connection->getMySql()))
|
|
|
|
{
|
|
|
|
data = mysql_fetch_row(res);
|
|
|
|
|
|
|
|
if (data)
|
|
|
|
count = atoi(data[0]);
|
|
|
|
|
|
|
|
mysql_free_result(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Truncate
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::truncate()
|
|
|
|
{
|
|
|
|
string tmp;
|
|
|
|
|
|
|
|
tmp = "delete from " + string(TableName());
|
|
|
|
|
|
|
|
if (connection->query(tmp.c_str()))
|
|
|
|
return connection->errorSql(connection, "truncate()", 0, tmp.c_str());
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Store
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::store()
|
|
|
|
{
|
|
|
|
int found;
|
|
|
|
|
|
|
|
// insert or just update ...
|
|
|
|
|
|
|
|
if (stmtSelect->execute(/*noResult =*/ yes) != success)
|
|
|
|
{
|
|
|
|
connection->errorSql(connection, "store()");
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
found = stmtSelect->getAffected() == 1;
|
|
|
|
stmtSelect->freeResult();
|
|
|
|
|
|
|
|
if (found)
|
|
|
|
return update();
|
|
|
|
else
|
|
|
|
return insert();
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Insert
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::insert()
|
|
|
|
{
|
|
|
|
if (!stmtInsert)
|
|
|
|
{
|
|
|
|
tell(0, "Fatal missing insert statement\n");
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (strcmp(getField(i)->name, "updsp") == 0 || strcmp(getField(i)->name, "inssp") == 0)
|
|
|
|
setValue(getField(i)->index, time(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEB_HANDLER
|
|
|
|
|
|
|
|
if (strcmp(TableName(), "events") == 0)
|
|
|
|
tell(1, "inserting vdr event %d for '%s', starttime = %ld, updflg = '%s'",
|
|
|
|
getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (stmtInsert->execute())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
return stmtInsert->getAffected() == 1 ? success : fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Update
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::update()
|
|
|
|
{
|
|
|
|
if (!stmtUpdate)
|
|
|
|
{
|
|
|
|
tell(0, "Fatal missing update statement\n");
|
|
|
|
return fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; getField(i)->name; i++)
|
|
|
|
{
|
|
|
|
if (strcmp(getField(i)->name, "updsp") == 0)
|
|
|
|
{
|
|
|
|
setValue(getField(i)->index, time(0));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEB_HANDLER
|
|
|
|
if (strcmp(TableName(), "events") == 0)
|
|
|
|
tell(1, "updating vdr event %d for '%s', starttime = %ld, updflg = '%s'",
|
|
|
|
getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (stmtUpdate->execute())
|
|
|
|
return fail;
|
|
|
|
|
|
|
|
return stmtUpdate->getAffected() == 1 ? success : fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Find
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::find()
|
|
|
|
{
|
|
|
|
if (!stmtSelect)
|
|
|
|
return no;
|
|
|
|
|
|
|
|
if (stmtSelect->execute() != success)
|
|
|
|
{
|
|
|
|
connection->errorSql(connection, "find()");
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
return stmtSelect->getAffected() == 1 ? yes : no;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Find via Statement
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::find(cDbStatement* stmt)
|
|
|
|
{
|
|
|
|
if (!stmt)
|
|
|
|
return no;
|
|
|
|
|
|
|
|
if (stmt->execute() != success)
|
|
|
|
{
|
|
|
|
connection->errorSql(connection, "find(stmt)");
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
return stmt->getAffected() > 0 ? yes : no;
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Fetch
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
int cDbTable::fetch(cDbStatement* stmt)
|
|
|
|
{
|
|
|
|
if (!stmt)
|
|
|
|
return no;
|
|
|
|
|
|
|
|
return stmt->fetch();
|
|
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
|
|
// Reset Fetch
|
|
|
|
//***************************************************************************
|
|
|
|
|
|
|
|
void cDbTable::reset(cDbStatement* stmt)
|
|
|
|
{
|
|
|
|
if (stmt)
|
|
|
|
stmt->freeResult();
|
|
|
|
}
|