// src/Database.hh // This file is part of libpbe; see http://decimail.org // (C) 2004 - 2007 Philip Endecott // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // any later version. // // 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. #ifndef libpbe_Database_hh #define libpbe_Database_hh #include "Exception.hh" #include "FileDescriptor.hh" #include "endian.hh" #include #include #include #include #include #include #include #include #include namespace pbe { // The maximum number of query parameters is set by this macro, which user code can // define before #including this file. #ifndef PBE_DB_MAX_QUERY_PARAMS #define PBE_DB_MAX_QUERY_PARAMS 7 #endif // Making it too large has the disadvantage that error messages become even more // incomprehensible, and may also increase compile times. // private: // We support queries with variable numbers of parameters by means of // sentinel default paramter values. The sentinel is of a special empty type // null_t. // ? Could we just use void for this ? struct null_t {}; static const null_t nullval = null_t(); // We need to map between C++ types and PostgreSQL types in query parameters. // This enum lists all of the PostgreSQL types that we map to, plus two // sentinels: unknown_type indicates that no PostgreSQL type corresponds to the // supplied C++ type, and should result in a compile-time error; null_type is used // in unused arguments slots in the varargs-like query syntax. enum typecode_t { /*unknown_type=0,*/ null_type, text_type, numeric_type, timestamptz_type, bytea_type, float_type, double_type, typecode_t_max }; // Now we use template specialisation to get the typecode_t corresponding to a C++ // type at compile time. get_typecode() is the typecode_t for type T. // The generic template is not implemented anywhere, so it will fail at link // time for these unsupported types. template inline typecode_t get_typecode(void); template <> inline typecode_t get_typecode(void) { return null_type; } template <> inline typecode_t get_typecode(void) { return text_type; } template <> inline typecode_t get_typecode(void) { return numeric_type; } template <> inline typecode_t get_typecode(void) { return timestamptz_type; } template <> inline typecode_t get_typecode(void) { return float_type; } template <> inline typecode_t get_typecode(void) { return double_type; } // public: // class Database represents a connection to the database server. // It's marked non-copyable because it's not clear what should be done with // the underlying libpq connection object in that case. class Database: public boost::noncopyable { public: // Connect to the database server using the supplied connection info. Database(std::string conninfo); // Disconnect on destruction. ~Database(); // Get the file descriptor for the connction to the server. // This is useful if you want to use select() to wait for asynchronous // notifications from the server. You obviously mustn't read or write to // it. const pbe::FileDescriptor& get_fd(void) const; // Return any asynchronous notifications received from the server. std::string get_any_notification(void); private: // libpq data structure representing the connection. PGconn* pgconn; // Connection file descriptor. pbe::FileDescriptor conn_fd; // This is needed so that we can support nested transactions. // (PostgreSQL now has built-in support for this, so it should be reviewed.) bool transaction_in_progress; // Run arbitary queries void exec_sql(std::string cmd); friend class QueryCore; friend class Transaction; }; // Exceptions thrown by the Database functions: class DatabaseException: public Exception { public: DatabaseException(PGconn* pgconn, std::string doing_what_): postgres_error(PQerrorMessage(pgconn)), doing_what(doing_what_) {} void report(std::ostream& s) const; private: std::string postgres_error; std::string doing_what; }; class QueryFailed: public DatabaseException { public: QueryFailed(PGconn* pgconn, std::string query): DatabaseException(pgconn,"Executing query " + query) {} }; class TypeError: public DatabaseException { public: TypeError(PGconn* pgconn): DatabaseException(pgconn,"Type error") {} }; // Transactions. Typical usage: // { // Transaction t(db); // ... run queries ... // t.commit(); // } // If the transaction goes out of scope without commit() having been called, e.g. // because an exception was thrown and not caught, the transaction will be rolled // back. // TODO: PostgreSQL now has some sort of built-in support for nested // transactions. class Transaction: public boost::noncopyable { public: Transaction(Database& database_); ~Transaction(); void commit(void); private: Database& database; bool nested; bool committed; }; // Convert from the representation returned by libpq to a normal C++ type. template T decode_pq_res(const char* data, int length __attribute__((unused))); template <> inline int decode_pq_res(const char* data, int length __attribute__((unused))) { const int* valp = reinterpret_cast(data); return ntohl(*valp); } template <> inline uint64_t decode_pq_res(const char* data, int length __attribute__((unused))) { const uint64_t* valp = reinterpret_cast(data); return ntoh64(*valp); } template <> inline int64_t decode_pq_res(const char* data, int length __attribute__((unused))) { const int64_t* valp = reinterpret_cast(data); return ntoh64(*valp); } template <> inline time_t decode_pq_res(const char* data, int length __attribute__((unused))) { // Timestamp values are returned by PostgreSQL as 64-bit microsecond // values. 946684800000000 is a magic number to convert to the Unix // 1970 epoch. int64_t t = decode_pq_res(data, length); return t/1000000 + 946684800; } template <> inline std::string decode_pq_res(const char* data, int length __attribute__((unused))) { return std::string(data,length); } union float_or_int { float f; int i; }; template <> inline float decode_pq_res(const char* data, int length __attribute__((unused))) { const float_or_int* uptr = reinterpret_cast(data); float_or_int ucopy; ucopy.i = ntohl(uptr->i); return ucopy.f; } union double_or_two_ints { double d; int i[2]; }; template <> inline double decode_pq_res(const char* data, int length __attribute__((unused))) { const double_or_two_ints* uptr = reinterpret_cast(data); double_or_two_ints ucopy; ucopy.i[0] = ntohl(uptr->i[0]); ucopy.i[1] = ntohl(uptr->i[1]); return ucopy.d; } // Result of a SELECT query: class Result { public: // Construct from the result object from libpq Result(boost::shared_ptr); // For insert, update and delete statements the result tells us how many // rows were inserted, updated or deleted, using the following: int get_rows_changed(void) const; // Everything below here is for getting at the table returned for a select // statement: // The size of the table const int rows; const int cols; // Map between column names and numbers int column(std::string name) const ; std::string column_name(int pos) const; // Get data // The pointer returned by rawget is valid for as long as the Result object exists. char* rawget(int row, int col) const; int getlength(int row, int col) const; template T get_nocheck(int row, int col) const { return decode_pq_res(rawget(row,col),getlength(row,col)); } template T get(int row, int col) const { check_column_type(col); return get_nocheck(row,col); } template T get(int row, std::string colname) const { return get(row, column(colname)); } #if 0 // ... does this work? template T operator()(int row, int col) const { } template T operator()(int row, std::string colname) const { return operator()(row, column(colname)); } #endif bool is_null(int row, int col) const; bool is_null(int row, std::string colname) const { return is_null(row, column(colname)); } // Column types typecode_t column_typecode(int col) const; template void check_column_type(int col) const { if (column_typecode(col)!=get_typecode()) { throw StrException("type error for column " +boost::lexical_cast(col)+", expecting typecode " +boost::lexical_cast(get_typecode())+" but got typecode " +boost::lexical_cast(column_typecode(col))); } } private: boost::shared_ptr res; }; // Alternative result for a query that generates a single column. // Values can be accessed using (), e.g. r(i). template class ColumnResult: public Result { public: ColumnResult(Result r): Result(r) { if (cols!=1) { throw "Single column expected"; } if (rows>0) { check_column_type(0); } } T operator ()(int row) const { return get(row,0); } bool is_null (int row) const { return Result::is_null(row,0); } class const_iterator: public boost::iterator_facade { public: const_iterator(): res(NULL), row(0) {} const_iterator(const const_iterator& other): res(other.res), row(other.row) {} private: const ColumnResult& res; int row; friend class ColumnResult; const_iterator(const ColumnResult& res_, int row_): res(res_), row(row_) {} friend class boost::iterator_core_access; void increment() { ++row; } void decrement() { --row; } void advance(int n) { row+=n; } int distance_to(const_iterator other) { return other.row-row; } bool equal(const const_iterator& other) const { return row==other.row && &res==&(other.res); } const T dereference() const { return res(row); } }; const_iterator begin() const { return const_iterator(*this,0); } const_iterator end() const { return const_iterator(*this,rows); } }; // Alternative result for a query that generates a single value // (e.g. a "count(*)" query). // This is convertable to the type of that value. template class SingletonResult: public Result { public: SingletonResult(Result r): Result(r) { if (rows!=1 || cols!=1) { throw "Singleton expected"; } check_column_type(0); } operator T () const { return get(0,0); } bool is_null () const { return Result::is_null(0,0); } }; // Alternative result for a query that generates a zero or one values. // This is convertable to the type of that value. template class OptResult: public Result { public: OptResult(Result r): Result(r) { if (rows>1 || cols!=1) { throw "Zero or one values expected"; } if (rows==1) { check_column_type(0); } } operator T () { return get(0,0); } bool is_null () { return Result::is_null(0,0); } bool empty() { return rows==0; } }; // private: // Generate names for prepared statements class statement_name_t: public std::string { public: statement_name_t(void): std::string("stmt_"+boost::lexical_cast(counter++)) {} private: static int counter; }; // Most of the work of Query is delagated to QueryCore which is not a template // class and so need not be coded inline. // This is "semi-private": code can use it if it needs to pass more than // PBE_DB_MAX_QUERY_PARAMS paramters to a query, but if that's not necessary then it // should not be used. class QueryCore { public: QueryCore(Database& database_, std::string querystr_, int nparams, typecode_t* argtypecodes, int* lengths, int* formats); ~QueryCore(); Result operator()(const char* enc_args[]); Result runonce(const char* enc_args[]); private: Database& database; const std::string querystr; const bool params_ok; const statement_name_t statement_name; int nparams; int* param_lengths; int* param_formats; Oid* argoids; bool prepared; void prepare(void); }; // Convert from C++ types to the representation passed to libpq. // Types are either binary or character. Binary is used for numeric types and // character is used for text types. Binary types must be in network byte // order. // This uses template specialisation. // It's OK for these function to modify their paramters in place. template inline const char* encode_pq_arg(T& t); template <> inline const char* encode_pq_arg(null_t& i __attribute__((unused)) ) { return NULL; } template <> inline const char* encode_pq_arg(int& i) { i = htonl(i); return reinterpret_cast(&i); } template <> inline const char* encode_pq_arg(uint64_t& i) { i = hton64(i); return reinterpret_cast(&i); } template <> inline const char* encode_pq_arg(int64_t& i) { i = hton64(i); return reinterpret_cast(&i); } template <> inline const char* encode_pq_arg(time_t& t) { // No! This leaks! int64_t* iptr = new int64_t; *iptr = static_cast(t-946684800)*1000000; return encode_pq_arg(*iptr); } template <> inline const char* encode_pq_arg(std::string& s) { return s.c_str(); } static inline float htonfloat(float f) { uint32_t i; memcpy(&i,&f,4); i = htonl(i); float r; memcpy(&r,&i,4); return r; } template <> inline const char* encode_pq_arg(float& f) { f = htonfloat(f); return reinterpret_cast(&f); } template <> inline const char* encode_pq_arg(double& d) { uint32_t* ptr = reinterpret_cast(&d); ptr[0] = htonl(ptr[0]); ptr[1] = htonl(ptr[1]); return reinterpret_cast(&d); } // We need to know the size of the encoded data. // In most cases this is sizeof(T), but there are exceptions when the // encoding process changes the size. template inline size_t get_enc_type_size(void) { return sizeof(T); } template <> inline size_t get_enc_type_size(void) { return 8; } // The Boost preprocessor library is used to build query parameter lists (etc.) with // multiple argumnets. #define PBE_DB_Q_TEMPLATE_PARAM(z,n,data) typename T##n=null_t #define PBE_DB_Q_TEMPLATE_PARAMS BOOST_PP_ENUM(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_TEMPLATE_PARAM,) // Expands to e.g. typename T0=null_t,typename T1=null_t #define PBE_DB_Q_OP_APPLY_PARAM(z,n,data) T##n arg##n=nullval #define PBE_DB_Q_OP_APPLY_PARAMS BOOST_PP_ENUM(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_OP_APPLY_PARAM,) // Expands to e.g. T0 arg0=nullval,T1 arg1=nullval #define PBE_DB_Q_BASE_TEMPL_PARAM(z,n,data) T##n #define PBE_DB_Q_BASE_TEMPL_PARAMS BOOST_PP_ENUM(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_BASE_TEMPL_PARAM,) // Expands to e.g. T0,T1 #define PBE_DB_Q_BASE_OP_APPLY_PARAM(z,n,data) arg##n #define PBE_DB_Q_BASE_OP_APPLY_PARAMS BOOST_PP_ENUM(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_BASE_OP_APPLY_PARAM,) // Expands to e.g. arg0,arg1 // This base class is used to form the parameters passed to Query, below, into arrays // that can be passed to QueryCore. template struct QueryBase { typecode_t typecodes[PBE_DB_MAX_QUERY_PARAMS]; int lengths[PBE_DB_MAX_QUERY_PARAMS]; int formats[PBE_DB_MAX_QUERY_PARAMS]; QueryBase() { #define PBE_DB_QB_INIT_ONE(z,n,data) \ typecodes[n]=get_typecode(); \ lengths[n]=get_enc_type_size();\ formats[n]=!boost::is_same::value; BOOST_PP_REPEAT(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_QB_INIT_ONE,) } }; // Prepare and run a query. Typical usage: // Query insert_item(db,"insert into items(name,qty) values ($1,$2)"); // insert_item("nut",100); // insert_item("bolt",102); // The query can have up to PBE_DB_MAX_QUERY_PARAMS parameters. This limit can be // increased at the top of this file. template class Query: private QueryBase { public: // Create a query. SQL is supplied in querystr with parameters indicated by // $1, $2 etc. C++ types of these parameters are the template paramters. Query(Database& database, std::string querystr): core(database, querystr, PBE_DB_MAX_QUERY_PARAMS, QueryBase::typecodes, QueryBase::lengths, QueryBase::formats) {} // Run a query. It is prepared the first time that it is run. Result operator()(PBE_DB_Q_OP_APPLY_PARAMS) { const char* arg_cs[PBE_DB_MAX_QUERY_PARAMS]; #define PBE_DB_Q_OP_APPLY_ONE(z,n,data) \ T##n arg##n##_c = arg##n; \ arg_cs[n] = encode_pq_arg(arg##n##_c); BOOST_PP_REPEAT(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_OP_APPLY_ONE,) return core(arg_cs); } // Run a query immediately, without preparation. Use this for queries that will only be // run once. Result runonce(PBE_DB_Q_OP_APPLY_PARAMS) { const char* arg_cs[PBE_DB_MAX_QUERY_PARAMS]; #define PBE_DB_Q_RUNONCE_ONE(z,n,data) \ T##n arg##n##_c = arg##n; \ arg_cs[n] = encode_pq_arg(arg##n##_c); BOOST_PP_REPEAT(PBE_DB_MAX_QUERY_PARAMS,PBE_DB_Q_RUNONCE_ONE,) return core.runonce(arg_cs); } private: QueryCore core; }; // A query that returns a ColumnResult. template class ColumnQuery: public Query< PBE_DB_Q_BASE_TEMPL_PARAMS> { public: typedef ColumnResult result_t; ColumnQuery(Database& database, std::string querystr): Query(database,querystr) {} result_t operator()(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::operator()(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } result_t runonce(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::runonce(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } }; // A query that returns a SingeltonResult. In this case there's no need to // delcare the result object, thanks to implicit type conversion. For // example: int a = query(param); template class SingletonQuery: public Query< PBE_DB_Q_BASE_TEMPL_PARAMS> { public: typedef SingletonResult result_t; SingletonQuery(Database& database, std::string querystr): Query(database,querystr) {} result_t operator()(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::operator()(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } result_t runonce(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::runonce(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } }; // A query that returns a OptResult. template class OptQuery: public Query< PBE_DB_Q_BASE_TEMPL_PARAMS> { public: typedef OptResult result_t; OptQuery(Database& database, std::string querystr): Query(database,querystr) {} result_t operator()(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::operator()(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } result_t runonce(PBE_DB_Q_OP_APPLY_PARAMS) { return Query::runonce(PBE_DB_Q_BASE_OP_APPLY_PARAMS); } }; }; #endif