/* * This file is a part of CMSLU -- Content Management System like Unix * and is not publicly distributed * * Copyright (c) 2008-2009, Tomasz Sowa * All rights reserved. * */ #include "db.h" #include "log.h" #include "misc.h" Db::Db(bool close_at_end_) { pg_conn = 0; close_at_end = close_at_end_; } Db::~Db() { if( close_at_end ) Close(); } PGconn * Db::GetPGconn() { return pg_conn; } 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 << log3 << "Db: Socket: " << PQsocket(pg_conn) << logend; // warning! pg_conn can be not null but there cannnot be a connection established // use PQstatus(pg_conn) to check whether the connection works fine } void Db::SetDbParameters() { 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() { bool was_connection = true; if( !pg_conn ) { was_connection = false; Connect(); } else if( PQstatus(pg_conn) != CONNECTION_OK ) { log << log2 << "Db: connection to the database is lost, trying to recover" << logend; was_connection = false; PQreset(pg_conn); } if( pg_conn && PQstatus(pg_conn) == CONNECTION_OK ) { if( was_connection == false ) log << log2 << "Db: Connection to the database works fine" << logend; SetDbParameters(); } else { log << log1 << "Db: Connection to db server cannot be established" << logend; throw Error(Error::db_fatal_error_during_connecting); } } 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: \"" << q << '\"' << logend; log << log1 << "Db: " << 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 << "', "; query << '\'' << item.default_item << "', "; 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 = Error::ok; if( item.type == Item::file ) result = AddItemIntoContent(item); else item.content_id = -1; 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, date_creation, date_modification, type, default_item, parent_id"; if( with_subject ) query << ", url"; query << ") = ("; query << '\'' << item.user_id << "', "; query << '\'' << item.group_id << "', "; query << '\'' << item.privileges << "', "; query << '\'' << ConvertTime(item.date_creation) << "', "; query << '\'' << ConvertTime(item.date_modification) << "', "; query << '\'' << static_cast(item.type) << "', "; query << '\'' << item.default_item << "', "; 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; // !! tutaj chyba nie ma potrzeby robic left join z core.content (nie uzywamy nic z tamtej tabeli) 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 = Error::ok; // !! dla katalogow nie testowane jeszcze if( item.type == Item::file ) result = EditItemGetContentId(item); if( result == Error::ok ) { if( item.type == Item::file ) result = EditItemInContent(item); if( result == Error::ok ) result = EditItemInItem(item, with_subject); } return result; } // item.url and item.parent_id must be set // doesn't work with directiories 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; } Error Db::EditDefaultItem(long id, long new_default_item) { PGresult * r = 0; Error result = Error::ok; try { AssertConnection(); std::ostringstream query; query << "update core.item set (default_item) = ('" << new_default_item << "') where id='" << id << "';"; r = AssertQuery(query.str()); AssertResultStatus(r, PGRES_COMMAND_OK); char * rows_str = PQcmdTuples(r); long rows = 0; if( rows_str ) rows = atol(rows_str); if( rows == 0 ) { result = Error::db_no_item; log << log1 << "Db: EditDefaultItem: no such item, id: " << id << logend; } } catch(const Error & e) { result = e; } ClearResult(r); return result; } PGresult * Db::GetItemsQuery(Item & item_ref, bool asc) { 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 << " order by item.date_modification"; if( asc ) query << " asc"; else query << " desc"; query << ';'; return AssertQuery(query.str()); } void Db::GetItems(std::vector & item_table, Item & item_ref, bool asc) { item_table.clear(); PGresult * r = 0; try { AssertConnection(); r = GetItemsQuery(item_ref, asc); 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); } tm Db::ConvertTime(const char * str) { tm t; memset(&t, 0, sizeof(t)); if( !str ) return t; size_t len = strlen(str); if( len != 19 ) { // unknown format // the format must be like this: 2008-12-31 22:30:00 return t; } t.tm_year = atoi(str + 0) - 1900; /* year - 1900 */ t.tm_mon = atoi(str + 5) - 1; /* month of year (0 - 11) */ t.tm_mday = atoi(str + 8); /* day of month (1 - 31) */ t.tm_hour = atoi(str + 11); /* hours (0 - 23) */ t.tm_min = atoi(str + 14); /* minutes (0 - 59) */ t.tm_sec = atoi(str + 17); /* seconds (0 - 60) */ // t.tm_wday = 0; /* day of week (Sunday = 0) */ // t.tm_yday = 0; /* day of year (0 - 365) */ // t.tm_isdst = 0; /* is summer time in effect? */ // t.tm_zone = 0; // const_cast(""); /* abbreviation of timezone name */ //return mktime(&t); return t; } const char * Db::ConvertTime(const tm & t) { // not thread safe static char buffer[100]; sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); return buffer; } Error Db::GetThreadByDirId(long dir_id, Thread & thread) { PGresult * r = 0; Error status = Error::ok; try { AssertConnection(); std::ostringstream query; query << "select id, dir_id, subject, closed from core.thread where thread.dir_id = " << dir_id << ";"; r = AssertQuery( query.str() ); AssertResultStatus(r, PGRES_TUPLES_OK); int rows = PQntuples(r); if( rows > 1 ) log << log1 << "Db: there is more than one thread with dir_id: " << dir_id << logend; else if( rows == 0 ) { log << log1 << "Db: there is no a thread with dir_id: " << dir_id << logend; throw Error(Error::no_thread); } int cid = AssertColumn(r, "id"); int cdir_id = AssertColumn(r, "dir_id"); int csubject = AssertColumn(r, "subject"); int cclosed = AssertColumn(r, "closed"); thread.id = atol( AssertValue(r, 0, cid) ); thread.dir_id = atol( AssertValue(r, 0, cdir_id) ); thread.subject = AssertValue(r, 0, csubject); thread.closed = atol( AssertValue(r, 0, cclosed) ) == 0 ? false : true; } catch(const Error & e) { status = e; } ClearResult(r); return status; } Error Db::AddThread(Thread & thread) { PGresult * r = 0; Error status = Error::ok; try { AssertConnection(); std::ostringstream query; query << "insert into core.thread (dir_id, subject, closed) values ("; query << '\'' << thread.dir_id << "', "; query << '\'' << Escape(thread.subject) << "', "; query << '\'' << (thread.closed ? 1 : 0 ) << "'); "; r = AssertQuery(query.str()); AssertResultStatus(r, PGRES_COMMAND_OK); thread.id = AssertCurrval("core.thread_id_seq"); } catch(const Error & e) { status = e; } ClearResult(r); return status; }