Source file: src/sqlstream.cpp
/* Copyright (C) 2004-2012 Daniel Verite
This file is part of Manitou-Mail (see http://www.manitou-mail.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include <string>
#include <iostream>
#include <ctype.h>
#include <stdlib.h>
#include "main.h"
#include "sqlstream.h"
#include "db.h"
sql_stream::sql_stream(const QString query, db_cnx& db, bool auto_exec) :
m_db(db), m_auto_exec(auto_exec)
{
QByteArray qba = query.toUtf8();
init(qba.constData());
}
sql_stream::sql_stream(const char *query, db_cnx& db, bool auto_exec) :
m_db(db), m_auto_exec(auto_exec)
{
init(query);
}
void
sql_stream::init(const char *query)
{
m_nArgPos = 0;
m_queryBuf = m_localQueryBuf;
m_queryBufSize = sizeof(m_localQueryBuf)-1;
m_queryFmt = query;
m_chunk_size = 1024;
m_bExecuted = 0;
m_pgRes = NULL;
int len=strlen(query);
if (len>m_queryBufSize)
query_make_space(len);
m_queryLen=m_initialQueryLen=len;
strcpy(m_queryBuf, query);
const char* q=query;
while (*q) {
// if (*q=='\'') {
// q++;
// while (*q && *q!='\'') q++;
// }
// else
if (*q==':') {
q++;
const char* start_var=q;
while ((*q>='A' && *q<='Z') || (*q>='a' && *q<='z') ||
(*q>='0' && *q<='9') || *q=='_')
{
q++;
}
if (q-start_var>0) {
// if the ':' was actually followed by a parameter name
sql_bind_param p(std::string(start_var,q-start_var), (start_var-1)-query);
m_vars.push_back(p);
}
else {
/* '::' is a special case because we don't want the parser to
find the second colon at the start of the loop. Otherwise
'::int' will be understood as a colon followed by the
parameter ':int'. So in this case, we skip the second colon */
if (*q==':')
q++;
}
}
else
q++;
}
if (m_vars.size()==0 && m_auto_exec)
execute();
}
sql_stream::~sql_stream()
{
#if 0
if (m_nArgPos<(int)m_vars.size()) {
QString q(m_queryBuf);
if (q.length()>40) { // cut the query to an reasonable size for debug output
q.truncate(37);
q.append("...");
}
DBG_PRINTF(2, "WRN: m_nArgPos=%d while m_vars.size()=%d for query '%s'", m_nArgPos, (int)m_vars.size(), q.toLocal8Bit().constData());
}
#endif
if (m_pgRes)
PQclear(m_pgRes);
if (m_queryBuf!=m_localQueryBuf)
free(m_queryBuf);
}
void
sql_stream::reset_results()
{
m_bExecuted=false;
m_nArgPos=0;
if (m_pgRes) {
PQclear(m_pgRes);
m_pgRes=NULL;
}
strcpy(m_queryBuf, m_queryFmt.c_str());
for (unsigned int i=0; i<m_vars.size(); i++) {
m_vars[i].resetOffset();
}
m_queryLen=m_initialQueryLen;
}
void
sql_stream::query_make_space(int len)
{
if (m_queryLen+len < m_queryBufSize)
return; // m_queryBuf is big enough
if (m_queryBuf==m_localQueryBuf) {
char* p=(char*)malloc(1+m_queryBufSize+len+m_chunk_size);
if (p) {
strcpy (p, m_queryBuf);
m_queryBuf = p;
}
}
else {
m_queryBuf=(char*)realloc(m_queryBuf, 1+m_queryBufSize+len+m_chunk_size);
}
if (!m_queryBuf)
throw "not enough memory";
m_queryBufSize+=len+m_chunk_size;
m_chunk_size<<=1;
}
void
sql_stream::replace_placeholder(int argPos, const char* buf, int size)
{
query_make_space(size);
sql_bind_param& p=m_vars[argPos];
// Replace the placeholder with the value
int placeholder_len=p.name().size()+1; // +1 for the leading ':'
// shift the substring at the right of the placeholder
memmove(m_queryBuf+p.pos()+size,
m_queryBuf+p.pos()+placeholder_len,
m_queryLen-(p.pos()+placeholder_len));
// insert the value where the placeholder was
memcpy(m_queryBuf+p.pos(), buf, size);
m_queryLen+=(size-placeholder_len);
m_queryBuf[m_queryLen]='\0';
// change the offsets of the remaining placeholders
for (unsigned int i=argPos+1; i<m_vars.size(); i++) {
m_vars[i].offset(size-placeholder_len);
}
}
void
sql_stream::next_bind()
{
if (++m_nArgPos>=(int)m_vars.size() && m_auto_exec)
execute();
}
sql_stream&
sql_stream::operator<<(const char* p)
{
check_binds();
size_t len=p?strlen(p):0;
char local_buf[1024+1];
char* buf;
if (len<(sizeof(local_buf)-1)/2)
buf=local_buf;
else
buf=(char*)malloc(2*len+1);
int escaped_size=PQescapeString(buf, p, len);
buf[escaped_size]='\0';
/* Very bad kludge here: if the bind parameter inside the query
doesn't start with a quote, we add quotes around the value at this point.
TODO: make all the callers NOT enclose the values, since it's a
problem for backends that support real bind parameters
(oracle) */
int pos = m_vars[m_nArgPos].pos();
if (pos>0 && m_queryBuf[pos-1]!='\'') {
// DBG_PRINTF(5,"bindpos=%d", pos);
if (p) {
char placeholder[2+escaped_size+1];
placeholder[0]='\'';
strcpy(placeholder+1, buf);
placeholder[1+escaped_size]='\'';
placeholder[1+escaped_size+1]='\0';
replace_placeholder(m_nArgPos, placeholder, escaped_size+2);
}
else {
// null pointer => null sql value
replace_placeholder(m_nArgPos, "null", 4);
}
}
else {
if (p)
replace_placeholder(m_nArgPos, buf, escaped_size);
else
replace_placeholder(m_nArgPos, "null", 4);
}
if (buf!=local_buf)
free(buf);
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(const QString& s)
{
QByteArray q;
if (m_db.datab()->encoding() == "UTF8")
q=s.toUtf8();
else
q=s.toLocal8Bit();
return operator<<((const char*)q);
}
sql_stream&
sql_stream::operator<<(int i)
{
check_binds();
char buf[15];
sprintf(buf,"%d", i);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(char c)
{
check_binds();
char buf[1];
buf[0]=c;
replace_placeholder(m_nArgPos, buf, 1);
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(unsigned int i)
{
check_binds();
char buf[15];
sprintf(buf,"%u", i);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(long l)
{
check_binds();
char buf[15];
sprintf(buf,"%ld", l);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(unsigned long l)
{
check_binds();
char buf[15];
sprintf(buf,"%lu", l);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(short s)
{
check_binds();
char buf[15];
sprintf(buf,"%hd", s);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(unsigned short s)
{
check_binds();
char buf[15];
sprintf(buf,"%hu", s);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(float f)
{
check_binds();
char buf[100];
sprintf(buf,"%g", f);
replace_placeholder(m_nArgPos, buf, strlen(buf));
next_bind();
return *this;
}
sql_stream&
sql_stream::operator<<(sql_null n _UNUSED_)
{
check_binds();
replace_placeholder(m_nArgPos, "null", 4);
next_bind();
return *this;
}
void
sql_stream::check_binds()
{
if (m_bExecuted && m_nArgPos==(int)m_vars.size()) {
// reset the query for another execution
reset_results();
}
if (m_nArgPos>=(int)m_vars.size())
throw db_excpt(m_queryBuf, "Mismatch between bound variables and query");
}
void
sql_stream::print()
{
#if 0
std::cout << "buf=" << m_queryBuf << std::endl;
std::cout << "len=" << m_queryLen << ", bufsize=" << m_queryBufSize << std::endl;
std::cout << "params\n";
for (int i=0; i<(int)m_vars.size(); i++) {
const sql_bind_param& v=m_vars[i];
std::cout << v.name() << " => " << v.pos() << std::endl;
}
#endif
}
void
sql_stream::execute()
{
/* in the general case execute() will be called implicitly as soon
as all the variables are bound. However the user code might want
to call it explicitly to have a better control over the execution
workflow. That's why in the case of a second call to execute() we
choose not to throw any error and instead just return, except if
m_auto_exec is false in which case execute() is always
explicit */
if (m_bExecuted && m_auto_exec)
return;
if (m_nArgPos<(int)m_vars.size())
throw db_excpt(m_queryBuf, QString("Not all variables are bound (%1 out of %2)").arg(m_nArgPos).arg(m_vars.size()));
DBG_PRINTF(5,"execute: %s", m_queryBuf);
m_pgRes=PQexec(m_db.connection(), m_queryBuf);
if (!m_pgRes)
throw db_excpt(m_queryBuf, PQerrorMessage(m_db.connection()));
if (PQresultStatus(m_pgRes)!=PGRES_TUPLES_OK && PQresultStatus(m_pgRes)!=PGRES_COMMAND_OK) {
throw db_excpt(m_queryBuf, PQresultErrorMessage(m_pgRes),
QString(PQresultErrorField(m_pgRes, PG_DIAG_SQLSTATE)));
}
const char* t=PQcmdTuples(m_pgRes);
if (t && *t) {
m_affected_rows=atoi(t);
}
else
m_affected_rows=0;
m_rowNumber=0;
m_colNumber=0;
m_bExecuted=1;
}
int
sql_stream::row_count() const
{
return (m_pgRes && PQresultStatus(m_pgRes)==PGRES_TUPLES_OK) ? PQntuples(m_pgRes) : 0;
}
int
sql_stream::eof()
{
if (!m_bExecuted)
execute();
return m_rowNumber>=PQntuples(m_pgRes);
}
void
sql_stream::next_result()
{
m_colNumber=((m_colNumber+1)%PQnfields(m_pgRes));
if (m_colNumber==0)
m_rowNumber++;
}
void
sql_stream::check_eof()
{
if (eof())
throw db_excpt(m_queryBuf, "End of stream reached");
}
sql_stream&
sql_stream::operator>>(int& i)
{
check_eof();
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
if (!m_val_null)
i=atoi(PQgetvalue(m_pgRes, m_rowNumber, m_colNumber));
else
i=0;
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(unsigned int& i)
{
check_eof();
unsigned long ul=strtoul(PQgetvalue(m_pgRes, m_rowNumber, m_colNumber),
NULL, 10);
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
if (!m_val_null)
i=(unsigned int)ul;
else
i=0;
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(char& c)
{
check_eof();
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
char* p=PQgetvalue(m_pgRes, m_rowNumber, m_colNumber);
c = p?*p:'\0';
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(char* p)
{
check_eof();
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
if (m_val_null)
*p='\0';
else {
// pretty dangerous if the buffer is not big enough, but
// we have no way of knowing. Better use C++ strings
strcpy(p, PQgetvalue(m_pgRes, m_rowNumber, m_colNumber));
}
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(QString& s)
{
check_eof();
if (m_db.datab()->encoding() == "UTF8")
s=QString::fromUtf8(PQgetvalue(m_pgRes, m_rowNumber, m_colNumber));
else
s=PQgetvalue(m_pgRes, m_rowNumber, m_colNumber);
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(float& f)
{
check_eof();
char* endptr;
f = strtof(PQgetvalue(m_pgRes, m_rowNumber, m_colNumber), &endptr);
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
next_result();
return *this;
}
sql_stream&
sql_stream::operator>>(double& f)
{
check_eof();
char* endptr;
f = strtod(PQgetvalue(m_pgRes, m_rowNumber, m_colNumber), &endptr);
m_val_null = PQgetisnull(m_pgRes, m_rowNumber, m_colNumber);
next_result();
return *this;
}
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files