From 1d664d31bdfc341ca642948efdf395e7922141f6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 7 Nov 2011 15:00:07 +0200 Subject: Add support for SQL statement tracing --- odb/oracle/connection.cxx | 152 +------------------------- odb/oracle/connection.hxx | 20 ++++ odb/oracle/database.hxx | 20 ++++ odb/oracle/forward.hxx | 2 + odb/oracle/makefile | 1 + odb/oracle/statement.cxx | 233 +++++++++++++++++++++++++++++++++++++++- odb/oracle/statement.hxx | 34 +++++- odb/oracle/tracer.cxx | 62 +++++++++++ odb/oracle/tracer.hxx | 62 +++++++++++ odb/oracle/transaction-impl.cxx | 20 ++++ odb/oracle/transaction.hxx | 20 ++++ 11 files changed, 472 insertions(+), 154 deletions(-) create mode 100644 odb/oracle/tracer.cxx create mode 100644 odb/oracle/tracer.hxx diff --git a/odb/oracle/connection.cxx b/odb/oracle/connection.cxx index dc37a07..c0eae59 100644 --- a/odb/oracle/connection.cxx +++ b/odb/oracle/connection.cxx @@ -4,12 +4,14 @@ // license : ODB NCUEL; see accompanying LICENSE file #include +#include #include #include #include #include +#include #include #include #include @@ -149,154 +151,8 @@ namespace odb unsigned long long connection:: execute (const char* s, std::size_t n) { - sword r (0); - - // OCI requires statement text to be NULL terminated. - // - string sql (s, n); - - auto_handle stmt; - { - OCIStmt* s (0); - r = OCIStmtPrepare2 (handle_, - &s, - error_, - reinterpret_cast (sql.c_str ()), - static_cast (sql.size ()), - 0, - 0, - OCI_NTV_SYNTAX, - OCI_DEFAULT); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - - stmt.reset (s, OCI_STRLS_CACHE_DELETE, error_); - } - - ub2 stmt_type; - r = OCIAttrGet (stmt, - OCI_HTYPE_STMT, - &stmt_type, - 0, - OCI_ATTR_STMT_TYPE, - error_); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - - ub4 row_count (0); - - if (stmt_type == OCI_STMT_SELECT) - { - // Do not prefetch any rows. - // - r = OCIStmtExecute (handle_, stmt, error_, 0, 0, 0, 0, OCI_DEFAULT); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - - // In order to succesfully execute a select statement, OCI/Oracle - // requires that there be OCIDefine handles provided for all select - // list columns. Since we are not interested in any data returned by - // the select statement, all buffer pointers, indication variable - // pointers, and data length pointers are specified as NULL (we still - // specify a valid data type identifier as not doing so results in - // undefined behaviour). This action results in truncation errors - // being returned for all attempted row fetches. However, cursor - // behaviour is normal, thus allowing us to return the row count for - // a select statement. - // - for (ub4 i (1); ; ++i) - { - auto_descriptor param; - { - OCIParam* p (0); - r = OCIParamGet (stmt, - OCI_HTYPE_STMT, - error_, - reinterpret_cast (&p), - i); - - if (r == OCI_ERROR) - break; - - param.reset (p); - } - - ub2 data_type; - r = OCIAttrGet (param, - OCI_DTYPE_PARAM, - &data_type, - 0, - OCI_ATTR_DATA_TYPE, - error_); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - - // No need to keep track of the OCIDefine handles - these will - // be deallocated with the statement. - // - OCIDefine* define (0); - r = OCIDefineByPos (stmt, - &define, - error_, - i, - 0, // NULL value buffer pointer - 0, // zero length value buffer - data_type, - 0, // NULL indicator pointer - 0, // NULL length data pointer - 0, // NULL column level return code pointer - OCI_DEFAULT); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - } - - while (1) - { - r = OCIStmtFetch2 (stmt, error_, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); - - if (r == OCI_NO_DATA) - break; - else if (r == OCI_ERROR) - { - sb4 e; - r = OCIErrorGet (error_, 1, 0, &e, 0, 0, OCI_HTYPE_ERROR); - - // ORA-01406 is returned if there is a truncation error. We expect - // and ignore all truncation errors. - // - if (e != 1406) - translate_error (error_, r); - } - else if (r == OCI_INVALID_HANDLE) - translate_error (error_, r); - } - } - else - { - // OCIStmtExecute requires a non-zero iters param for DML statements. - // - r = OCIStmtExecute (handle_, stmt, error_, 1, 0, 0, 0, OCI_DEFAULT); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - } - - r = OCIAttrGet (stmt, - OCI_HTYPE_STMT, - &row_count, - 0, - OCI_ATTR_ROW_COUNT, - error_); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (error_, r); - - return row_count; + generic_statement st (*this, string (s, n)); + return st.execute (); } } } diff --git a/odb/oracle/connection.hxx b/odb/oracle/connection.hxx index 12add74..56ac6b9 100644 --- a/odb/oracle/connection.hxx +++ b/odb/oracle/connection.hxx @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,25 @@ namespace odb virtual unsigned long long execute (const char* statement, std::size_t length); + // SQL statement tracing. + // + public: + typedef oracle::tracer tracer_type; + + void + tracer (tracer_type& t) + { + odb::connection::tracer (t); + } + + void + tracer (tracer_type* t) + { + odb::connection::tracer (t); + } + + using odb::connection::tracer; + public: OCISvcCtx* handle () diff --git a/odb/oracle/database.hxx b/odb/oracle/database.hxx index 80b6fba..232b320 100644 --- a/odb/oracle/database.hxx +++ b/odb/oracle/database.hxx @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -145,6 +146,25 @@ namespace odb return environment_; } + // SQL statement tracing. + // + public: + typedef oracle::tracer tracer_type; + + void + tracer (tracer_type& t) + { + odb::database::tracer (t); + } + + void + tracer (tracer_type* t) + { + odb::database::tracer (t); + } + + using odb::database::tracer; + public: virtual ~database (); diff --git a/odb/oracle/forward.hxx b/odb/oracle/forward.hxx index ae45ed4..baad4af 100644 --- a/odb/oracle/forward.hxx +++ b/odb/oracle/forward.hxx @@ -18,7 +18,9 @@ namespace odb class connection; typedef details::shared_ptr connection_ptr; class connection_factory; + class statement; class transaction; + class tracer; class query; // Implementation details. diff --git a/odb/oracle/makefile b/odb/oracle/makefile index f173b95..c2fb892 100644 --- a/odb/oracle/makefile +++ b/odb/oracle/makefile @@ -19,6 +19,7 @@ query.cxx \ query-const-expr.cxx \ statement.cxx \ statements-base.cxx \ +tracer.cxx \ traits.cxx \ transaction.cxx \ transaction-impl.cxx \ diff --git a/odb/oracle/statement.cxx b/odb/oracle/statement.cxx index cb204d5..8b608a4 100644 --- a/odb/oracle/statement.cxx +++ b/odb/oracle/statement.cxx @@ -3,11 +3,12 @@ // copyright : Copyright (c) 2005-2011 Code Synthesis Tools CC // license : ODB NCUEL; see accompanying LICENSE file -#include #include +#include #include +#include #include // object_not_persistent #include @@ -134,6 +135,18 @@ namespace odb // statement:: + ~statement () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->deallocate (conn_, *this); + } + } + + statement:: statement (connection& conn, const string& s) : conn_ (conn) { @@ -154,6 +167,33 @@ namespace odb translate_error (err, r); stmt_.reset (handle, OCI_STRLS_CACHE_DELETE, err); + + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->prepare (conn_, *this); + } + } + + const char* statement:: + text () const + { + OCIError* err (conn_.error_handle ()); + + OraText* s (0); + sword r (OCIAttrGet (stmt_, + OCI_HTYPE_STMT, + &s, + 0, + OCI_ATTR_STATEMENT, + err)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + return reinterpret_cast (s); } void statement:: @@ -760,11 +800,166 @@ namespace odb } } - statement:: - ~statement () + // + // generic_statement + // + + generic_statement:: + generic_statement (connection& conn, const string& s) + : statement (conn, s), bound_ (false) + { + OCIError* err (conn_.error_handle ()); + + sword r (OCIAttrGet (stmt_, + OCI_HTYPE_STMT, + &stmt_type_, + 0, + OCI_ATTR_STMT_TYPE, + err)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + generic_statement:: + ~generic_statement () { } + unsigned long long generic_statement:: + execute () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + sword r (0); + + OCISvcCtx* handle (conn_.handle ()); + OCIError* err (conn_.error_handle ()); + + if (stmt_type_ == OCI_STMT_SELECT) + { + // Do not prefetch any rows. + // + r = OCIStmtExecute (handle, stmt_, err, 0, 0, 0, 0, OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + // In order to successfully execute a select statement, OCI/Oracle + // requires that there be OCIDefine handles provided for all select + // list columns. Since we are not interested in any data returned by + // the select statement, all buffer pointers, indicator variable + // pointers, and data length pointers are specified as NULL (we still + // specify a valid data type identifier; not doing so results in + // undefined behavior). This results in truncation errors being + // returned for all attempted row fetches. However, cursor behaves + // normally allowing us to return the row count for a select + // statement. Note also that we only need to do this once. + // + if (!bound_) + { + for (ub4 i (1); ; ++i) + { + auto_descriptor param; + { + OCIParam* p (0); + r = OCIParamGet (stmt_, + OCI_HTYPE_STMT, + err, + reinterpret_cast (&p), + i); + + if (r == OCI_ERROR) // No more result columns. + break; + + param.reset (p); + } + + ub2 data_type; + r = OCIAttrGet (param, + OCI_DTYPE_PARAM, + &data_type, + 0, + OCI_ATTR_DATA_TYPE, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + // No need to keep track of the OCIDefine handles - these will + // be deallocated with the statement. + // + OCIDefine* define (0); + r = OCIDefineByPos (stmt_, + &define, + err, + i, + 0, // NULL value buffer pointer + 0, // zero length value buffer + data_type, + 0, // NULL indicator pointer + 0, // NULL length data pointer + 0, // NULL column level return code pointer + OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + bound_ = true; + } + + for (;;) + { + r = OCIStmtFetch2 (stmt_, err, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); + + if (r == OCI_NO_DATA) + break; + else if (r == OCI_ERROR) + { + sb4 e; + r = OCIErrorGet (err, 1, 0, &e, 0, 0, OCI_HTYPE_ERROR); + + // ORA-01406 is returned if there is a truncation error. We expect + // and ignore this error. + // + if (e != 1406) + translate_error (err, r); + } + else if (r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + } + else + { + // OCIStmtExecute requires a non-zero iters param for DML statements. + // + r = OCIStmtExecute (handle, stmt_, err, 1, 0, 0, 0, OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + ub4 row_count (0); + r = OCIAttrGet (stmt_, + OCI_HTYPE_STMT, + &row_count, + 0, + OCI_ATTR_ROW_COUNT, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + return row_count; + } + // // select_statement // @@ -812,6 +1007,14 @@ namespace odb if (!done_) free_result (); + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + OCIError* err (conn_.error_handle ()); // @@ Retrieve a single row into the already bound output buffers as an @@ -1012,6 +1215,14 @@ namespace odb bool insert_statement:: execute () { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + OCIError* err (conn_.error_handle ()); sword r (OCIStmtExecute (conn_.handle (), @@ -1087,6 +1298,14 @@ namespace odb unsigned long long update_statement:: execute () { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + OCIError* err (conn_.error_handle ()); sword r (OCIStmtExecute (conn_.handle (), @@ -1140,6 +1359,14 @@ namespace odb unsigned long long delete_statement:: execute () { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + OCIError* err (conn_.error_handle ()); sword r (OCIStmtExecute (conn_.handle (), diff --git a/odb/oracle/statement.hxx b/odb/oracle/statement.hxx index e0d30d9..f3e86f0 100644 --- a/odb/oracle/statement.hxx +++ b/odb/oracle/statement.hxx @@ -12,8 +12,7 @@ #include // std::size_t #include - -#include +#include #include #include @@ -27,12 +26,21 @@ namespace odb { namespace oracle { - class LIBODB_ORACLE_EXPORT statement: public details::shared_base + class LIBODB_ORACLE_EXPORT statement: public odb::statement { public: virtual ~statement () = 0; + OCIStmt* + handle () const + { + return stmt_; + } + + virtual const char* + text () const; + protected: statement (connection&, const std::string& statement); @@ -73,6 +81,26 @@ namespace odb auto_handle stmt_; }; + class LIBODB_ORACLE_EXPORT generic_statement: public statement + { + public: + virtual + ~generic_statement (); + + generic_statement (connection&, const std::string& statement); + + unsigned long long + execute (); + + private: + generic_statement (const generic_statement&); + generic_statement& operator= (const generic_statement&); + + private: + ub2 stmt_type_; + bool bound_; + }; + class LIBODB_ORACLE_EXPORT select_statement: public statement { public: diff --git a/odb/oracle/tracer.cxx b/odb/oracle/tracer.cxx new file mode 100644 index 0000000..98a8c1e --- /dev/null +++ b/odb/oracle/tracer.cxx @@ -0,0 +1,62 @@ +// file : odb/oracle/tracer.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#include +#include +#include + +namespace odb +{ + namespace oracle + { + tracer:: + ~tracer () + { + } + + void tracer:: + prepare (connection&, const statement&) + { + } + + void tracer:: + execute (connection& c, const statement& s) + { + execute (c, s.text ()); + } + + void tracer:: + deallocate (connection&, const statement&) + { + } + + void tracer:: + prepare (odb::connection& c, const odb::statement& s) + { + prepare (static_cast (c), + static_cast (s)); + } + + void tracer:: + execute (odb::connection& c, const odb::statement& s) + { + execute (static_cast (c), + static_cast (s)); + } + + void tracer:: + execute (odb::connection& c, const char* s) + { + execute (static_cast (c), s); + } + + void tracer:: + deallocate (odb::connection& c, const odb::statement& s) + { + deallocate (static_cast (c), + static_cast (s)); + } + } +} diff --git a/odb/oracle/tracer.hxx b/odb/oracle/tracer.hxx new file mode 100644 index 0000000..149c627 --- /dev/null +++ b/odb/oracle/tracer.hxx @@ -0,0 +1,62 @@ +// file : odb/oracle/tracer.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#ifndef ODB_ORACLE_TRACER_HXX +#define ODB_ORACLE_TRACER_HXX + +#include + +#include + +#include +#include + +namespace odb +{ + namespace oracle + { + class LIBODB_EXPORT tracer: private odb::tracer + { + public: + virtual + ~tracer (); + + virtual void + prepare (connection&, const statement&); + + virtual void + execute (connection&, const statement&); + + virtual void + execute (connection&, const char* statement) = 0; + + virtual void + deallocate (connection&, const statement&); + + private: + // Allow these classes to convert oracle::tracer to odb::tracer. + // + friend class database; + friend class connection; + friend class transaction; + + virtual void + prepare (odb::connection&, const odb::statement&); + + virtual void + execute (odb::connection&, const odb::statement&); + + virtual void + execute (odb::connection&, const char* statement); + + virtual void + deallocate (odb::connection&, const odb::statement&); + }; + } +} + +#include + +#endif // ODB_ORACLE_TRACER_HXX diff --git a/odb/oracle/transaction-impl.cxx b/odb/oracle/transaction-impl.cxx index 4751366..e46aac1 100644 --- a/odb/oracle/transaction-impl.cxx +++ b/odb/oracle/transaction-impl.cxx @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -89,6 +91,12 @@ namespace odb auto_t.release (); } + { + odb::tracer* t; + if ((t = connection_->tracer ()) || (t = database_.tracer ())) + t->execute (*connection_, "BEGIN"); + } + // We never use OCITransDetach so the timeout parameter is // of no consequence. // @@ -104,6 +112,12 @@ namespace odb void transaction_impl:: commit () { + { + odb::tracer* t; + if ((t = connection_->tracer ()) || (t = database_.tracer ())) + t->execute (*connection_, "COMMIT"); + } + sword s (OCITransCommit (connection_->handle (), connection_->error_handle (), OCI_DEFAULT)); @@ -115,6 +129,12 @@ namespace odb void transaction_impl:: rollback () { + { + odb::tracer* t; + if ((t = connection_->tracer ()) || (t = database_.tracer ())) + t->execute (*connection_, "ROLLBACK"); + } + sword s (OCITransRollback (connection_->handle (), connection_->error_handle (), OCI_DEFAULT)); diff --git a/odb/oracle/transaction.hxx b/odb/oracle/transaction.hxx index 5d6c460..7b2999c 100644 --- a/odb/oracle/transaction.hxx +++ b/odb/oracle/transaction.hxx @@ -12,6 +12,7 @@ #include #include +#include #include @@ -51,6 +52,25 @@ namespace odb static void current (transaction&); + // SQL statement tracing. + // + public: + typedef oracle::tracer tracer_type; + + void + tracer (tracer_type& t) + { + odb::transaction::tracer (t); + } + + void + tracer (tracer_type* t) + { + odb::transaction::tracer (t); + } + + using odb::transaction::tracer; + public: transaction_impl& implementation (); -- cgit v1.1