winix/core/db.cpp

1084 lines
19 KiB
C++
Executable File

/*
* This file is a part of CMSLU -- Content Management System like Unix
* and is not publicly distributed
*
* Copyright (c) 2008, Tomasz Sowa
* All rights reserved.
*
*/
#include "db.h"
Db::Db()
{
pg_conn = 0;
}
Db::~Db()
{
Close();
}
void Db::Init(const std::string & d, const std::string & u, const std::string & p)
{
db_database = d;
db_user = u;
db_pass = p;
Connect();
}
void Db::Connect()
{
Close();
std::ostringstream buf;
buf << "dbname=" << db_database << " user=" << db_user << " password=" << db_pass;
pg_conn = PQconnectdb(buf.str().c_str());
if( !pg_conn )
log << log1 << "Db: Fatal error during connecting" << logend;
if( PQsetClientEncoding(pg_conn, "LATIN2") == -1 )
log << log1 << "Db: Can't set the proper client encoding" << logend;
}
void Db::Close()
{
if( pg_conn )
{
PQfinish(pg_conn);
pg_conn = 0;
}
}
void Db::AssertConnection()
{
if( !pg_conn )
{
log << log1 << "Db: Trying to connect into the database...";
Connect();
if( !pg_conn )
// logend from Connect()
throw Error(Error::db_fatal_error_during_connecting);
log << log1 << "ok" << logend;
}
if( PQstatus(pg_conn) != CONNECTION_OK )
{
log << log2 << "Db: connection into the database is lost, trying to recover..." << logend;
PQreset(pg_conn);
if( PQstatus(pg_conn) != CONNECTION_OK )
{
log << log2 << "no" << logend;
throw Error(Error::db_fatal_error_during_connecting);
}
else
{
log << log2 << "ok" << logend;
if( PQsetClientEncoding(pg_conn, "LATIN2") == -1 )
log << log1 << "Db: Can't set the proper client encoding" << logend;
}
}
}
std::string Db::Escape(const std::string & s)
{
std::string result;
result.resize(s.length() * 2 + 1);
size_t len = PQescapeStringConn(pg_conn, const_cast<char*>( result.c_str() ), s.c_str(), s.length(), 0);
result.resize(len);
return result;
}
std::string Db::Escape(const char * s)
{
std::string result;
int len;
for(len=0 ; s[len] != 0 ; ++len);
result.resize(len * 2 + 1);
size_t len_new = PQescapeStringConn(pg_conn, const_cast<char*>( result.c_str() ), s, len, 0);
result.resize(len_new);
return result;
}
// ------------------
PGresult * Db::AssertQuery(const std::string & q)
{
PGresult * r = PQexec(pg_conn, q.c_str());
if( !r )
{
log << log1 << "Db: Problem with query: " << PQerrorMessage(pg_conn) << logend;
throw Error(Error::db_incorrect_query);
}
return r;
}
void Db::AssertResultStatus(PGresult * r, ExecStatusType t)
{
if( PQresultStatus(r) != t )
{
log << "Db: Incorrect result status: " << PQerrorMessage(pg_conn) << logend;
throw Error(Error::db_incorrent_result_status);
}
}
int Db::AssertColumn(PGresult * r, const char * column_name)
{
int c = PQfnumber(r, column_name);
if( c == -1 )
{
log << log1 << "Db: there is no column: " << column_name << logend;
throw Error(Error::db_no_column);
}
return c;
}
const char * Db::AssertValue(PGresult * r, int row, int col)
{
const char * res = PQgetvalue(r, row, col);
if( !res )
{
log << log1 << "Db: there is no such an item in the result, row:" << row << ", col:" << col << logend;
throw Error(Error::db_no_item);
}
return res;
}
void Db::ClearResult(PGresult * r)
{
if( r )
PQclear(r);
}
bool Db::CheckUser(std::string & login, std::string & password, long & user_id)
{
PGresult * r = 0;
bool user_ok = false;
try
{
AssertConnection();
std::ostringstream query;
query << "select id from core.user where login='" << Escape(login) << "' and password='" << Escape(password) << "';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
int rows = PQntuples(r);
if( rows == 0 )
throw Error(Error::db_incorrect_login);
if( rows > 1 )
{
log << log1 << "Db: there is more than one user: " << login << " (with the same password)" << logend;
throw Error(Error::db_more_than_one_login);
}
int cuser_id = AssertColumn(r, "id");
const char * fuser_id = AssertValue(r, 0, cuser_id);
user_id = atol( fuser_id );
user_ok = true;
}
catch(const Error &)
{
}
ClearResult(r);
return user_ok;
}
bool Db::AddItemCreateUrlSubject(Item & item)
{
bool is_that_url;
PGresult * r = 0;
int index = 1;
const int max_index = 100;
char appendix[20];
appendix[0] = 0;
try
{
do
{
std::ostringstream query;
// this Escape can be put at the beginning (performance)
query << "select id from core.item where url='" << Escape(item.url) << appendix << "' and parent_id='" << item.parent_id << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_TUPLES_OK);
if( PQntuples(r) != 0 )
{
sprintf(appendix, "_(%d)", ++index);
is_that_url = true;
}
else
{
item.url += appendix;
is_that_url = false;
}
ClearResult(r);
r = 0;
}
while( is_that_url && index <= max_index );
}
catch(const Error &)
{
is_that_url = true; // for return false
}
ClearResult(r);
return !is_that_url;
}
// for testing consistency
void Db::CheckAllUrlSubjectModifyItem(Item & item)
{
PGresult * r = 0;
try
{
std::ostringstream query;
query << "update core.item set url=";
// url
if( AddItemCreateUrlSubject(item) )
query << '\'' << Escape(item.url) << '\'';
else
{
query << '\'' << item.id << '\'';
item.url.clear();
}
query << " where id='" << item.id << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_COMMAND_OK);
}
catch(const Error &)
{
}
ClearResult(r);
}
// for checking consistency
void Db::CheckAllUrlSubject()
{
PGresult * r = 0;
Item item;
try
{
AssertConnection();
std::ostringstream query, query2;
query << "select item.id, subject from core.item left join core.content on item.content_id = content.id where url is null or url=''";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_TUPLES_OK);
int rows = PQntuples(r);
int cid = AssertColumn(r, "id");
int csubject = AssertColumn(r, "subject");
for(int i = 0 ; i<rows ; ++i)
{
item.id = atol( AssertValue(r, i, cid) );
item.subject = AssertValue(r, i, csubject);
CheckAllUrlSubjectModifyItem(item);
}
}
catch(const Error &)
{
}
ClearResult(r);
}
long Db::AssertCurrval(const char * table)
{
PGresult * r;
std::ostringstream query;
query << "select currval('" << table << "');";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_TUPLES_OK);
if( PQntuples(r) != 1 )
{
log << log1 << "Db: error (currval) for table: " << table << ", " << PQerrorMessage(pg_conn) << logend;
throw Error(Error::db_err_currval);
}
long res = strtol( AssertValue(r, 0, 0), 0, 10 );
return res;
}
Error Db::AddItemIntoItem(Item & item)
{
PGresult * r = 0;
Error result = Error::ok;
bool url_without_id = false;
try
{
AssertConnection();
std::ostringstream query;
query << "insert into core.item (user_id, group_id, privileges, type, parent_id, content_id, url) values (";
query << '\'' << item.user_id << "', ";
query << '\'' << item.group_id << "', ";
query << '\'' << item.privileges << "', ";
query << '\'' << static_cast<int>(item.type) << "', ";
query << '\'' << item.parent_id << "', ";
query << '\'' << item.content_id << "', ";
url_without_id = AddItemCreateUrlSubject(item);
if( url_without_id )
query << '\'' << Escape(item.url) << "');";
else
query << "currval('core.item_id_seq')" << ");";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_COMMAND_OK);
item.id = AssertCurrval("core.item_id_seq");
if( !url_without_id )
ToString(item.url, item.id);
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
Error Db::AddItemIntoContent(Item & item)
{
PGresult * r = 0;
Error result = Error::ok;
try
{
AssertConnection();
std::ostringstream query;
query << "insert into core.content (subject, content, content_type) values (";
query << '\'' << Escape(item.subject) << "', ";
query << '\'' << Escape(item.content) << "', ";
query << '\'' << item.content_type << "');";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_COMMAND_OK);
item.content_id = AssertCurrval("core.content_id_seq");
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
Error Db::AddItem(Item & item)
{
Error result = AddItemIntoContent(item);
if( result == Error::ok )
result = AddItemIntoItem(item);
return result;
}
Error Db::EditItemInItem(Item & item, bool with_subject)
{
PGresult * r = 0;
Error result = Error::ok;
bool url_without_id = false;
try
{
AssertConnection();
std::ostringstream query;
query << "update core.item set (user_id, group_id, privileges, type, parent_id";
if( with_subject )
query << ", url";
query << ") = (";
query << '\'' << item.user_id << "', ";
query << '\'' << item.group_id << "', ";
query << '\'' << item.privileges << "', ";
query << '\'' << static_cast<int>(item.type) << "', ";
query << '\'' << item.parent_id << "' ";
if( with_subject )
{
url_without_id = AddItemCreateUrlSubject(item);
if( url_without_id )
query << ", '" << Escape(item.url) << "'";
else
query << ", '" << item.id << "'";
}
query << ") where id='" << item.id << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_COMMAND_OK);
if( with_subject && !url_without_id )
ToString(item.url, item.id);
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
Error Db::EditItemInContent(Item & item)
{
PGresult * r = 0;
Error result = Error::ok;
try
{
AssertConnection();
std::ostringstream query;
query << "update core.content set (subject, content, content_type) = (";
query << '\'' << Escape(item.subject) << "', ";
query << '\'' << Escape(item.content) << "', ";
query << '\'' << item.content_type << "' ";
query << ") where id='" << item.content_id << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_COMMAND_OK);
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
Error Db::EditItemGetId(Item & item)
{
PGresult * r = 0;
Error result = Error::ok;
try
{
AssertConnection();
std::ostringstream query;
query << "select item.id, content.id from core.item left join core.content on item.content_id = content.id where item.parent_id='";
query << item.parent_id << "' and item.url='" << Escape(item.url) << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_TUPLES_OK);
if( PQntuples(r) != 1 || PQnfields(r) != 2 )
throw Error(Error::db_no_item);
// we cannot use AssertColumn() with a name because both columns are called 'id'
item.id = atol( AssertValue(r, 0, 0) );
item.content_id = atol( AssertValue(r, 0, 1) );
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
Error Db::EditItemGetContentId(Item & item)
{
PGresult * r = 0;
Error result = Error::ok;
try
{
AssertConnection();
std::ostringstream query;
query << "select content_id from core.item left join core.content on item.content_id = content.id where item.id='";
query << item.id << "';";
r = AssertQuery(query.str());
AssertResultStatus(r, PGRES_TUPLES_OK);
if( PQntuples(r) != 1 || PQnfields(r) != 1 )
throw Error(Error::db_no_item);
item.content_id = atol( AssertValue(r, 0, 0) );
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
// item.id must be set
Error Db::EditItemById(Item & item, bool with_subject)
{
Error result = EditItemGetContentId(item);
if( result == Error::ok )
{
result = EditItemInContent(item);
if( result == Error::ok )
result = EditItemInItem(item, with_subject);
}
return result;
}
// item.url and item.parent_id must be set
Error Db::EditItemByUrl(Item & item, bool with_subject)
{
Error result = EditItemGetId(item);
if( result == Error::ok )
{
result = EditItemInContent(item);
if( result == Error::ok )
result = EditItemInItem(item, with_subject);
}
return result;
}
PGresult * Db::GetItemsQuery(Item & item_ref)
{
std::ostringstream query;
query << "select * from core.item left join core.content on item.content_id = content.id where type!='0' and parent_id='" << item_ref.parent_id << "'";
if( item_ref.id != -1 )
query << " and item.id='" << item_ref.id << "'";
if( !item_ref.url.empty() )
query << " and url='" << Escape(item_ref.url) << "'";
query << ';';
return AssertQuery(query.str());
}
void Db::GetItems(std::vector<Item> & item_table, Item & item_ref)
{
item_table.clear();
PGresult * r = 0;
try
{
AssertConnection();
r = GetItemsQuery(item_ref);
AssertResultStatus(r, PGRES_TUPLES_OK);
Item item;
int rows = PQntuples(r);
ItemColumns col;
col.SetColumns(r);
for(int i = 0 ; i<rows ; ++i)
{
col.SetItem(r, i, item);
item_table.push_back(item);
}
}
catch(const Error &)
{
}
ClearResult(r);
}
void Db::GetItem(std::vector<Item> & item_table, long id)
{
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select * from core.item left join core.content on item.content_id = content.id where type='1' and item.id='" << id << "';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
Item item;
int rows = PQntuples(r);
if( rows > 1 )
log << log1 << "Db: we have more than one item with id: " << id << logend;
ItemColumns col;
col.SetColumns(r);
for(int i = 0 ; i<rows ; ++i)
{
col.SetItem(r, i, item);
item_table.push_back(item);
}
}
catch(const Error &)
{
}
ClearResult(r);
}
bool Db::GetPriv(Item & item, long id)
{
bool result = false;
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select user_id, group_id, privileges, subject, content from core.item left join core.content on item.content_id = content.id where item.id='" << id << "';"; // !! tymczasowo odczytujemy z content i subject
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
int rows = PQntuples(r);
if( rows != 1 )
throw Error();
ItemColumns col;
col.SetColumns(r);
col.SetItem(r, 0, item);
result = true;
}
catch(const Error &)
{
}
ClearResult(r);
return result;
}
bool Db::DelItemDelItem(const Item & item)
{
long rows = 0;
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "delete from core.item where id='" << item.id << "';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_COMMAND_OK);
const char * crows = PQcmdTuples(r);
if( crows )
{
rows = atol(crows);
if( rows > 1 )
log << log1 << "Db: more than one item were deleted" << logend;
else
if( rows == 0 )
log << log1 << "Db: no item has been deleted" << logend;
}
}
catch(const Error &)
{
}
ClearResult(r);
return rows != 0;
}
void Db::DelItemDelContent(const Item & item)
{
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "delete from core.content where id='" << item.content_id << "';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_COMMAND_OK);
const char * crows = PQcmdTuples(r);
if( crows )
{
long rows = atol(crows);
if( rows > 1 )
log << log1 << "Db: more than one content were deleted" << logend;
else
if( rows == 0 )
log << log1 << "Db: no content has been deleted" << logend;
}
}
catch(const Error &)
{
}
ClearResult(r);
}
Error Db::DelItemCountContents(const Item & item, long & contents)
{
Error result = Error::ok;
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select count('id') from core.item where content_id='" << item.content_id << "';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
contents = atol( AssertValue(r, 0, 0) );
log << log1 << "counters: " << contents << logend; // !!
}
catch(const Error & e)
{
result = e;
}
ClearResult(r);
return result;
}
bool Db::DelItem(const Item & item)
{
long contents;
Error result = DelItemCountContents(item, contents);
if( result == Error::ok && contents == 1 )
DelItemDelContent(item);
return DelItemDelItem(item);
}
void Db::GetDirs(DirContainer & dir_table)
{
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select * from core.item left join core.content on item.content_id = content.id where type='0';";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
Item item;
int rows = PQntuples(r);
ItemColumns col;
col.SetColumns(r);
for(int i = 0 ; i<rows ; ++i)
{
col.SetItem(r, i, item);
dir_table.PushBack( item );
}
}
catch(const Error &)
{
}
ClearResult(r);
}
void Db::GetUsers(UGContainer<User> & user_table)
{
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select id, login, super_user, group_id from core.user left outer join core.group_mem on core.user.id = core.group_mem.user_id order by id asc;";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
int rows = PQntuples(r);
int cid = AssertColumn(r, "id");
int cname = AssertColumn(r, "login");
int csuper_user = AssertColumn(r, "super_user");
int cgroup_id = AssertColumn(r, "group_id");
User u;
long last_id = -1;
UGContainer<User>::Iterator iter;
for(int i = 0 ; i<rows ; ++i)
{
u.id = atol( AssertValue(r, i, cid) );
if( u.id != last_id )
{
u.name = AssertValue(r, i, cname);
u.super_user = static_cast<bool>( atoi( AssertValue(r, i, csuper_user) ) );
log << log1 << "Db: get user: id:" << u.id << ", name:" << u.name << ", super_user:" << u.super_user << logend;
iter = user_table.PushBack( u );
last_id = u.id;
}
long group_id = atol( AssertValue(r, i, cgroup_id) );
if( !PQgetisnull(r, i, cgroup_id) && group_id!=-1 && !user_table.Empty() )
{
iter->groups.push_back(group_id);
log << log3 << "Db: user:" << iter->name << " is a member of group_id:" << group_id << logend;
}
}
}
catch(const Error &)
{
}
ClearResult(r);
}
void Db::GetGroups(UGContainer<Group> & group_table)
{
PGresult * r = 0;
try
{
AssertConnection();
std::ostringstream query;
query << "select id, core.group.group, user_id from core.group left outer join core.group_mem on core.group.id = core.group_mem.group_id order by id asc;";
r = AssertQuery( query.str() );
AssertResultStatus(r, PGRES_TUPLES_OK);
int rows = PQntuples(r);
int cid = AssertColumn(r, "id");
int cname = AssertColumn(r, "group");
int cuser_id = AssertColumn(r, "user_id");
Group g;
long last_id = -1;
UGContainer<Group>::Iterator iter;
for(int i = 0 ; i<rows ; ++i)
{
g.id = atol( AssertValue(r, i, cid) );
if( g.id != last_id )
{
g.name = AssertValue(r, i, cname);
log << log3 << "Db: get group, id:" << g.id << ", group:" << g.name << logend;
iter = group_table.PushBack( g );
last_id = g.id;
}
long user_id = atol( AssertValue(r, i, cuser_id) );
if( !PQgetisnull(r, i, cuser_id) && user_id!=-1 && !group_table.Empty() )
{
iter->members.push_back(user_id);
log << log3 << "Db: get group member: user_id:" << user_id << logend;
}
}
}
catch(const Error &)
{
}
ClearResult(r);
}