// file : odb/pgsql/statement.cxx // copyright : Copyright (c) 2005-2013 Code Synthesis Tools CC // license : GNU GPL v2; see accompanying LICENSE file #include // std::atol #include #include // istringstream #include #include #include #include #include #include #include #include #include using namespace std; namespace odb { namespace pgsql { using namespace details; static unsigned long long affected_row_count (PGresult* h) { const char* s (PQcmdTuples (h)); unsigned long long count; if (s[0] != '\0' && s[1] == '\0') count = static_cast (s[0] - '0'); else { // @@ Using stringstream conversion for now. See if we can optimize // this (atoll possibly, even though it is not standard). // istringstream ss (s); ss >> count; } return count; } // // statement // statement:: ~statement () { try { deallocate (); } catch (...) { } } void statement:: deallocate () { if (deallocated_) return; { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->deallocate (conn_, *this); } string s ("deallocate \""); s += name_; s += "\""; auto_handle h (PQexec (conn_.handle (), s.c_str ())); deallocated_ = true; } statement:: statement (connection_type& conn, const string& name, const string& text, const Oid* types, size_t types_count) : conn_ (conn), name_copy_ (name), name_ (name_copy_.c_str ()), text_copy_ (text), text_ (text_copy_.c_str ()), deallocated_ (false) { init (types, types_count); // // If any code after this line throws, the statement will be leaked // (on the server) since deallocate() won't be called for it. // } statement:: statement (connection_type& conn, const char* name, const char* text, bool copy, const Oid* types, size_t types_count) : conn_ (conn), deallocated_ (false) { if (copy) { name_copy_ = name; name_ = name_copy_.c_str (); text_copy_ = text; text_ = text_copy_.c_str (); } else { name_ = name; text_ = text; } init (types, types_count); // // If any code after this line throws, the statement will be leaked // (on the server) since deallocate() won't be called for it. // } void statement:: init (const Oid* types, size_t types_count) { { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->prepare (conn_, *this); } auto_handle h ( PQprepare (conn_.handle (), name_, text_, static_cast (types_count), types)); if (!is_good_result (h)) translate_error (conn_, h); // // If any code after this line throws, the statement will be leaked // (on the server) since deallocate() won't be called for it. // } const char* statement:: text () const { return text_; } void statement:: bind_param (native_binding& n, const binding& b) { assert (n.count == b.count); for (size_t i (0); i < n.count; ++i) { const bind& current_bind (b.bind[i]); n.formats[i] = 1; if (current_bind.is_null != 0 && *current_bind.is_null) { n.values[i] = 0; n.lengths[i] = 0; continue; } n.values[i] = static_cast (current_bind.buffer); size_t l (0); switch (current_bind.type) { case bind::boolean_: { l = sizeof (bool); break; } case bind::smallint: { l = sizeof (short); break; } case bind::integer: { l = sizeof (int); break; } case bind::bigint: { l = sizeof (long long); break; } case bind::real: { l = sizeof (float); break; } case bind::double_: { l = sizeof (double); break; } case bind::date: { l = sizeof (int); break; } case bind::time: case bind::timestamp: { l = sizeof (long long); break; } case bind::numeric: case bind::text: case bind::bytea: case bind::bit: case bind::varbit: { l = *current_bind.size; break; } case bind::uuid: { // UUID is a 16-byte sequence. // l = 16; break; } } n.lengths[i] = static_cast (l); } } bool statement:: bind_result (bind* p, size_t count, PGresult* result, size_t row, bool truncated) { bool r (true); int int_row (static_cast (row)); // Make sure that the number of columns in the result returned by // the database matches the number that we expect. A common cause // of this assertion is a native view with a number of data members // not matching the number of columns in the SELECT-list. // assert (static_cast (PQnfields (result)) == count); for (int i (0); i < static_cast (count); ++i) { const bind& b (p[i]); if (truncated && (b.truncated == 0 || !*b.truncated)) continue; if (b.truncated != 0) *b.truncated = false; // Check for NULL unless we are reloading a truncated result. // if (!truncated) { *b.is_null = PQgetisnull (result, int_row, i) == 1; if (*b.is_null) continue; } const char* v (PQgetvalue (result, int_row, i)); switch (b.type) { case bind::boolean_: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::smallint: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::integer: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::bigint: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::real: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::double_: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::date: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::time: case bind::timestamp: { *static_cast (b.buffer) = *reinterpret_cast (v); break; } case bind::numeric: case bind::text: case bind::bytea: case bind::bit: case bind::varbit: { *b.size = static_cast (PQgetlength (result, int_row, i)); if (b.capacity < *b.size) { if (b.truncated) *b.truncated = true; r = false; continue; } memcpy (b.buffer, v, *b.size); break; } case bind::uuid: { memcpy (b.buffer, v, 16); break; } } } return r; } // // select_statement // select_statement:: ~select_statement () { } select_statement:: select_statement (connection_type& conn, const std::string& name, const std::string& text, const Oid* types, std::size_t types_count, binding& param, native_binding& native_param, binding& result) : statement (conn, name, text, types, types_count), param_ (¶m), native_param_ (&native_param), result_ (result), row_count_ (0), current_row_ (0) { } select_statement:: select_statement (connection_type& conn, const char* name, const char* text, const Oid* types, std::size_t types_count, binding& param, native_binding& native_param, binding& result, bool copy) : statement (conn, name, text, copy, types, types_count), param_ (¶m), native_param_ (&native_param), result_ (result), row_count_ (0), current_row_ (0) { } select_statement:: select_statement (connection_type& conn, const std::string& name, const std::string& text, binding& result) : statement (conn, name, text, 0, 0), param_ (0), native_param_ (0), result_ (result), row_count_ (0), current_row_ (0) { } select_statement:: select_statement (connection_type& conn, const char* name, const char* text, binding& result, bool copy) : statement (conn, name, text, copy, 0, 0), param_ (0), native_param_ (0), result_ (result), row_count_ (0), current_row_ (0) { } select_statement:: select_statement (connection_type& conn, const std::string& name, const std::string& text, const Oid* types, std::size_t types_count, native_binding& native_param, binding& result) : statement (conn, name, text, types, types_count), param_ (0), native_param_ (&native_param), result_ (result), row_count_ (0), current_row_ (0) { } void select_statement:: execute () { handle_.reset (); if (param_ != 0) bind_param (*native_param_, *param_); { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); } bool in (native_param_ != 0); handle_.reset ( PQexecPrepared (conn_.handle (), name_, in ? native_param_->count : 0, in ? native_param_->values : 0, in ? native_param_->lengths : 0, in ? native_param_->formats : 0, 1)); if (!is_good_result (handle_)) translate_error (conn_, handle_); row_count_ = static_cast (PQntuples (handle_)); current_row_ = 0; } void select_statement:: free_result () { handle_.reset (); row_count_ = 0; current_row_ = 0; } bool select_statement:: next () { if (current_row_ <= row_count_) ++current_row_; return current_row_ <= row_count_; } select_statement::result select_statement:: load () { if (current_row_ > row_count_) return no_data; assert (current_row_ > 0); return bind_result (result_.bind, result_.count, handle_, current_row_ - 1) ? success : truncated; } void select_statement:: reload () { assert (current_row_ > 0); assert (current_row_ <= row_count_); if (!bind_result (result_.bind, result_.count, handle_, current_row_ - 1, true)) assert (false); } // // insert_statement // insert_statement:: ~insert_statement () { } insert_statement:: insert_statement (connection_type& conn, const string& name, const string& text, const Oid* types, size_t types_count, binding& param, native_binding& native_param, bool returning) : statement (conn, name, text, types, types_count), param_ (param), native_param_ (native_param), returning_ (returning) { } insert_statement:: insert_statement (connection_type& conn, const char* name, const char* text, const Oid* types, size_t types_count, binding& param, native_binding& native_param, bool returning, bool copy) : statement (conn, name, text, copy, types, types_count), param_ (param), native_param_ (native_param), returning_ (returning) { } bool insert_statement:: execute () { bind_param (native_param_, param_); { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); } auto_handle h ( PQexecPrepared (conn_.handle (), name_, native_param_.count, native_param_.values, native_param_.lengths, native_param_.formats, 1)); ExecStatusType stat (PGRES_FATAL_ERROR); if (!is_good_result (h, &stat)) { if (PGRES_FATAL_ERROR == stat) { string s (PQresultErrorField (h, PG_DIAG_SQLSTATE)); if (s == "23505") return false; } translate_error (conn_, h); } if (returning_) { // Get the id value that was returned using the RETURNING clause. // const char* v (PQgetvalue (h, 0, 0)); // While the ODB auto id type can only be INT or BIGINT, handle the // SMALLINT integer in case we are dealing with a custom schema. // switch (PQftype (h, 0)) { case int2_oid: { id_ = endian_traits::ntoh ( *reinterpret_cast (v)); break; } case int4_oid: { id_ = endian_traits::ntoh ( *reinterpret_cast (v)); break; } case int8_oid: { id_ = endian_traits::ntoh ( *reinterpret_cast (v)); break; } default: { assert (false); break; } } } return true; } // // update_statement // update_statement:: ~update_statement () { } update_statement:: update_statement (connection_type& conn, const string& name, const string& text, const Oid* types, size_t types_count, binding& param, native_binding& native_param) : statement (conn, name, text, types, types_count), param_ (param), native_param_ (native_param) { } update_statement:: update_statement (connection_type& conn, const char* name, const char* text, const Oid* types, size_t types_count, binding& param, native_binding& native_param, bool copy) : statement (conn, name, text, copy, types, types_count), param_ (param), native_param_ (native_param) { } unsigned long long update_statement:: execute () { bind_param (native_param_, param_); { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); } auto_handle h ( PQexecPrepared (conn_.handle (), name_, native_param_.count, native_param_.values, native_param_.lengths, native_param_.formats, 1)); if (!is_good_result (h)) translate_error (conn_, h); return affected_row_count (h); } // // delete_statement // delete_statement:: ~delete_statement () { } delete_statement:: delete_statement (connection_type& conn, const string& name, const string& text, const Oid* types, size_t types_count, binding& param, native_binding& native_param) : statement (conn, name, text, types, types_count), param_ (¶m), native_param_ (native_param) { } delete_statement:: delete_statement (connection_type& conn, const char* name, const char* text, const Oid* types, size_t types_count, binding& param, native_binding& native_param, bool copy) : statement (conn, name, text, copy, types, types_count), param_ (¶m), native_param_ (native_param) { } delete_statement:: delete_statement (connection_type& conn, const string& name, const string& text, const Oid* types, size_t types_count, native_binding& native_param) : statement (conn, name, text, types, types_count), param_ (0), native_param_ (native_param) { } unsigned long long delete_statement:: execute () { if (param_ != 0) bind_param (native_param_, *param_); { odb::tracer* t; if ((t = conn_.transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); } auto_handle h ( PQexecPrepared (conn_.handle (), name_, native_param_.count, native_param_.values, native_param_.lengths, native_param_.formats, 1)); if (!is_good_result (h)) translate_error (conn_, h); return affected_row_count (h); } } }