/* * 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( 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( 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(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; } // !! with_subject zamienic na with_url 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(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 // !! with_subject zamienic na with_url // !! moze nazwa poprostu EditItem (nie trzeba tego ById) ? (sprawdzic czy nie koliduje z inna nazwa) 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_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 & 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 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 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 & 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::Iterator iter; for(int i = 0 ; i( 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_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::Iterator iter; for(int i = 0 ; imembers.push_back(user_id); log << log3 << "Db: get group member: user_id:" << user_id << logend; } } } catch(const Error &) { } ClearResult(r); }