Commits

Glen Walker committed 84509fa

Add support for query result set change notification

  • Participants
  • Parent commits 7b96101

Comments (0)

Files changed (6)

File Connection.c

 // constants for the OCI attributes
 //-----------------------------------------------------------------------------
 static ub4 gc_ClientIdentifierAttribute = OCI_ATTR_CLIENT_IDENTIFIER;
-#ifdef ORACLE_10G
+#if ORACLE_VERSION_HEX >= 0x0A01
 static ub4 gc_ModuleAttribute = OCI_ATTR_MODULE;
 static ub4 gc_ActionAttribute = OCI_ATTR_ACTION;
 static ub4 gc_ClientInfoAttribute = OCI_ATTR_CLIENT_INFO;
 #endif
 
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
 static ub4 gc_CurrentSchemaAttribute = OCI_ATTR_CURRENT_SCHEMA;
 #endif
 
 static PyObject *Connection_ChangePasswordExternal(udt_Connection*, PyObject*);
 static PyObject *Connection_GetStmtCacheSize(udt_Connection*, void*);
 static int Connection_SetStmtCacheSize(udt_Connection*, PyObject*, void*);
-#ifdef ORACLE_10G
+#if ORACLE_VERSION_HEX >= 0x0A02
 static PyObject *Connection_GetOCIAttr(udt_Connection*, ub4*);
 #endif
 static int Connection_SetOCIAttr(udt_Connection*, PyObject*, ub4*);
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
 #if !defined(AIX5) || defined(ORACLE_11g)
 static PyObject *Connection_Ping(udt_Connection*, PyObject*);
 #endif
     { "unregister", (PyCFunction) Connection_UnregisterCallback, METH_VARARGS },
     { "__enter__", (PyCFunction) Connection_ContextManagerEnter, METH_NOARGS },
     { "__exit__", (PyCFunction) Connection_ContextManagerExit, METH_VARARGS },
-#ifdef ORACLE_10GR2
-#if !defined(AIX5) || defined(ORACLE_11g)
+#if ORACLE_VERSION_HEX >= 0x0A02
+#if !defined(AIX5) || ORACLE_VERSION_HEX >= 0x0B01
     { "ping", (PyCFunction) Connection_Ping, METH_NOARGS },
 #endif
     { "shutdown", (PyCFunction) Connection_Shutdown,
             0, 0, 0 },
     { "stmtcachesize", (getter) Connection_GetStmtCacheSize,
             (setter) Connection_SetStmtCacheSize, 0, 0 },
-#ifdef ORACLE_10G
+#if ORACLE_VERSION_HEX >= 0x0A01
     { "module", 0, (setter) Connection_SetOCIAttr, 0, &gc_ModuleAttribute },
     { "action", 0, (setter) Connection_SetOCIAttr, 0, &gc_ActionAttribute },
     { "clientinfo", 0, (setter) Connection_SetOCIAttr, 0,
 #endif
     { "client_identifier", 0, (setter) Connection_SetOCIAttr, 0,
             &gc_ClientIdentifierAttribute },
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
     { "current_schema", (getter) Connection_GetOCIAttr,
             (setter) Connection_SetOCIAttr, 0, &gc_CurrentSchemaAttribute },
 #endif
         if (!pool && externalCredentials)
             mode |= OCI_SESSGET_CREDEXT;
 
-#ifdef ORACLE_11G
+#if ORACLE_VERSION_HEX >= 0x0B01
         // set the connection class, if applicable
         if (cxBuffer_FromObject(&buffer, cclassObj,
                 self->environment->encoding) < 0)
 }
 
 
-#ifdef ORACLE_10G
+#if ORACLE_VERSION_HEX >= 0x0A02
 //-----------------------------------------------------------------------------
 // Connection_GetOCIAttr()
 //   Get the value of the OCI attribute.
             "Connection_Connect(): set session handle") < 0)
         return -1;
 
-#ifdef ORACLE_10G
+#if ORACLE_VERSION_HEX >= 0x0A01
     if (moduleObj) {
         if (cxBuffer_FromObject(&buffer, moduleObj,
                 self->environment->encoding))
 
 #include "Cursor.c"
 #include "Callback.c"
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
 #include "Subscription.c"
 #endif
 
 }
 
 
-#ifdef ORACLE_10GR2
-#if !defined(AIX5) || defined(ORACLE_11g)
+#if ORACLE_VERSION_HEX >= 0x0A02
+#if !defined(AIX5) || ORACLE_VERSION_HEX >= 0x0B01
 //-----------------------------------------------------------------------------
 // Connection_Ping()
 //   Makes a round trip call to the server to confirm that the connection and
     PyObject* keywordArgs)              // keyword arguments
 {
     static char *keywordList[] = { "namespace", "protocol", "callback",
-            "timeout", "operations", "rowids", "port", NULL };
-    ub4 namespace, protocol, port, timeout, rowids, operations;
+            "timeout", "operations", "rowids", "port", "qos", "cqqos", NULL };
+    ub4 namespace, protocol, port, timeout, rowids, operations, qos, cqqos;
     PyObject *rowidsObj, *callback;
     int temp;
 
     // parse arguments
-    timeout = rowids = port = 0;
+    timeout = rowids = port = qos = cqqos = 0;
     rowidsObj = callback = NULL;
     namespace = OCI_SUBSCR_NAMESPACE_DBCHANGE;
     protocol = OCI_SUBSCR_PROTO_OCI;
     operations = OCI_OPCODE_ALLOPS;
-    if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|iiOiiOi", keywordList,
+    if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|iiOiiOiii", keywordList,
             &namespace, &protocol, &callback, &timeout, &operations,
-            &rowidsObj, &port))
+            &rowidsObj, &port, &qos, &cqqos))
         return NULL;
 
     // set the value for rowids
     }
 
     return (PyObject*) Subscription_New(self, namespace, protocol, port,
-            callback, timeout, operations, rowids);
+            callback, timeout, operations, qos, cqqos, rowids);
 }
 #endif
 

File Subscription.c

     ub4 port;
     ub4 timeout;
     ub4 operations;
+    ub4 qos;
+    ub4 cqqos;
     ub4 rowids;
+    ub4 id;
 } udt_Subscription;
 
 typedef struct {
     PyObject_HEAD
+    udt_Subscription *subscription;
     ub4 type;
     PyObject *dbname;
     PyObject *tables;
+    PyObject *queries;
 } udt_Message;
 
 typedef struct {
     ub4 operation;
 } udt_MessageRow;
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+typedef struct {
+    PyObject_HEAD
+    ub8 id;
+    ub4 operation;
+    PyObject *tables;
+} udt_MessageQuery;
+#endif
+
 
 //-----------------------------------------------------------------------------
 // Declaration of subscription functions
 static void Message_Free(udt_Message*);
 static void MessageTable_Free(udt_MessageTable*);
 static void MessageRow_Free(udt_MessageRow*);
+#if ORACLE_VERSION_HEX >= 0x0B01
+static void MessageQuery_Free(udt_MessageQuery*);
+#endif
 
 //-----------------------------------------------------------------------------
 // declaration of members for Python types
     { "port", T_INT, offsetof(udt_Subscription, port), READONLY },
     { "timeout", T_INT, offsetof(udt_Subscription, timeout), READONLY },
     { "operations", T_INT, offsetof(udt_Subscription, operations), READONLY },
+    { "qos", T_INT, offsetof(udt_Subscription, qos), READONLY },
+    { "cqqos", T_INT, offsetof(udt_Subscription, cqqos), READONLY },
     { "rowids", T_BOOL, offsetof(udt_Subscription, rowids), READONLY },
+    { "id", T_INT, offsetof(udt_Subscription, id), READONLY },
     { NULL }
 };
 
 static PyMemberDef g_MessageTypeMembers[] = {
+    { "subscription", T_OBJECT, offsetof(udt_Message, subscription),
+            READONLY },
     { "type", T_INT, offsetof(udt_Message, type), READONLY },
     { "dbname", T_OBJECT, offsetof(udt_Message, dbname), READONLY },
     { "tables", T_OBJECT, offsetof(udt_Message, tables), READONLY },
+    { "queries", T_OBJECT, offsetof(udt_Message, queries), READONLY },
     { NULL }
 };
 
     { NULL }
 };
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+static PyMemberDef g_MessageQueryTypeMembers[] = {
+    { "id", T_INT, offsetof(udt_MessageQuery, id), READONLY },
+    { "operation", T_INT, offsetof(udt_MessageQuery, operation), READONLY },
+    { "tables", T_OBJECT, offsetof(udt_MessageQuery, tables), READONLY },
+    { NULL }
+};
+#endif
+
 
 //-----------------------------------------------------------------------------
 // declaration of methods for Python types
 };
 
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+static PyTypeObject g_MessageQueryType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "cx_Oracle.MessageQuery",           // tp_name
+    sizeof(udt_MessageQuery),           // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor) MessageQuery_Free,     // tp_dealloc
+    0,                                  // tp_print
+    0,                                  // tp_getattr
+    0,                                  // tp_setattr
+    0,                                  // tp_compare
+    0,                                  // tp_repr
+    0,                                  // tp_as_number
+    0,                                  // tp_as_sequence
+    0,                                  // tp_as_mapping
+    0,                                  // tp_hash
+    0,                                  // tp_call
+    0,                                  // tp_str
+    0,                                  // tp_getattro
+    0,                                  // tp_setattro
+    0,                                  // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    0,                                  // tp_doc
+    0,                                  // tp_traverse
+    0,                                  // tp_clear
+    0,                                  // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    0,                                  // tp_iter
+    0,                                  // tp_iternext
+    0,                                  // tp_methods
+    g_MessageQueryTypeMembers,          // tp_members
+    0,                                  // tp_getset
+    0,                                  // tp_base
+    0,                                  // tp_dict
+    0,                                  // tp_descr_get
+    0,                                  // tp_descr_set
+    0,                                  // tp_dictoffset
+    0,                                  // tp_init
+    0,                                  // tp_alloc
+    0,                                  // tp_new
+    0,                                  // tp_free
+    0,                                  // tp_is_gc
+    0                                   // tp_bases
+};
+#endif
+
+
 //-----------------------------------------------------------------------------
 // MessageRow_Initialize()
 //   Initialize a new message row with the information from the descriptor.
 }
 
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+//-----------------------------------------------------------------------------
+// MessageQuery_Initialize()
+//   Initialize a new message query with the information from the descriptor.
+//-----------------------------------------------------------------------------
+static int MessageQuery_Initialize(
+    udt_MessageQuery *self,             // object to initialize
+    udt_Environment *env,               // environment to use
+    dvoid *descriptor)                  // descriptor to get information from
+{
+    dvoid **tableDescriptor, *indicator;
+    udt_MessageTable *table;
+    ub4 i;
+    OCIColl *tables;
+    boolean exists;
+    sb4 numTables;
+    sword status;
+
+    // determine query id
+    status = OCIAttrGet(descriptor, OCI_DTYPE_CQDES, &self->id,
+            NULL, OCI_ATTR_CQDES_QUERYID, env->errorHandle);
+    if (Environment_CheckForError(env, status,
+            "MessageQuery_Initialize(): get query id") < 0)
+        return -1;
+
+    // determine operation
+    status = OCIAttrGet(descriptor, OCI_DTYPE_CQDES, &self->operation,
+            NULL, OCI_ATTR_CQDES_OPERATION, env->errorHandle);
+    if (Environment_CheckForError(env, status,
+            "MessageQuery_Initialize(): get operation") < 0)
+        return -1;
+
+    // determine table collection
+    status = OCIAttrGet(descriptor, OCI_DTYPE_CQDES, &tables, NULL,
+            OCI_ATTR_CQDES_TABLE_CHANGES, env->errorHandle);
+    if (Environment_CheckForError(env, status,
+            "MessageQuery_Initialize(): get tables collection") < 0)
+        return -1;
+
+    // determine number of tables
+    if (!tables)
+        numTables = 0;
+    else {
+        status = OCICollSize(env->handle, env->errorHandle, tables,
+                &numTables);
+        if (Environment_CheckForError(env, status,
+                "MessageQuery_Initialize(): get size of collection") < 0)
+            return -1;
+    }
+
+    // create list to hold results
+    self->tables = PyList_New(numTables);
+    if (!self->tables)
+        return -1;
+
+    // populate each entry with a message table instance
+    for (i = 0; i < numTables; i++) {
+        status = OCICollGetElem(env->handle, env->errorHandle, tables, i,
+                &exists, (dvoid*) &tableDescriptor, &indicator);
+        if (Environment_CheckForError(env, status,
+                "MessageQuery_Initialize(): get element from collection") < 0)
+            return -1;
+        table = (udt_MessageTable*)
+                g_MessageTableType.tp_alloc(&g_MessageTableType, 0);
+        if (!table)
+            return -1;
+        PyList_SET_ITEM(self->tables, i, (PyObject*) table);
+        if (MessageTable_Initialize(table, env, *tableDescriptor) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+#endif
+
+
 //-----------------------------------------------------------------------------
 // Message_Initialize()
 //   Initialize a new message with the information from the descriptor.
 static int Message_Initialize(
     udt_Message *self,                  // object to initialize
     udt_Environment *env,               // environment to use
+    udt_Subscription *subscription,     // associated subscription for message
     dvoid *descriptor)                  // descriptor to get information from
 {
     dvoid **tableDescriptor, *indicator;
     boolean exists;
     sb4 numTables;
     char *dbname;
+#if ORACLE_VERSION_HEX >= 0x0B01
+    dvoid **queryDescriptor;
+    udt_MessageQuery *query;
+	OCIColl *queries;
+	sb4 numQueries;
+#endif
     sword status;
 
+    // assign reference to associated subscription
+    Py_INCREF(subscription);
+    self->subscription = subscription;
+
     // determine type
     status = OCIAttrGet(descriptor, OCI_DTYPE_CHDES, &self->type, NULL,
             OCI_ATTR_CHDES_NFYTYPE, env->errorHandle);
     if (!self->dbname)
         return -1;
 
-    // determine table collection
-    status = OCIAttrGet(descriptor, OCI_DTYPE_CHDES, &tables, NULL,
-            OCI_ATTR_CHDES_TABLE_CHANGES, env->errorHandle);
-    if (Environment_CheckForError(env, status,
-            "Message_Initialize(): get tables collection") < 0)
-        return -1;
+    if (self->type == OCI_EVENT_OBJCHANGE) {
+        // determine table collection
+        status = OCIAttrGet(descriptor, OCI_DTYPE_CHDES, &tables, NULL,
+                OCI_ATTR_CHDES_TABLE_CHANGES, env->errorHandle);
+        if (Environment_CheckForError(env, status,
+                "Message_Initialize(): get tables collection") < 0)
+            return -1;
 
-    // determine number of tables
-    if (!tables)
-        numTables = 0;
-    else {
-        status = OCICollSize(env->handle, env->errorHandle, tables,
-                &numTables);
-        if (Environment_CheckForError(env, status,
-                "Message_Initialize(): get size of collection") < 0)
+        // determine number of tables
+        if (!tables)
+            numTables = 0;
+        else {
+            status = OCICollSize(env->handle, env->errorHandle, tables,
+                    &numTables);
+            if (Environment_CheckForError(env, status,
+                    "Message_Initialize(): get size of collection") < 0)
+                return -1;
+        }
+
+        // create list to hold results
+        self->tables = PyList_New(numTables);
+        if (!self->tables)
             return -1;
+
+        // populate each entry with a message table instance
+        for (i = 0; i < numTables; i++) {
+            status = OCICollGetElem(env->handle, env->errorHandle, tables, i,
+                    &exists, (dvoid*) &tableDescriptor, &indicator);
+            if (Environment_CheckForError(env, status,
+                    "Message_Initialize(): get element from collection") < 0)
+                return -1;
+            table = (udt_MessageTable*)
+                    g_MessageTableType.tp_alloc(&g_MessageTableType, 0);
+            if (!table)
+                return -1;
+            PyList_SET_ITEM(self->tables, i, (PyObject*) table);
+            if (MessageTable_Initialize(table, env, *tableDescriptor) < 0)
+                return -1;
+        }
     }
 
-    // create list to hold results
-    self->tables = PyList_New(numTables);
-    if (!self->tables)
-        return -1;
+#if ORACLE_VERSION_HEX >= 0x0B01
+    if (self->type == OCI_EVENT_QUERYCHANGE) {
+        // determine query collection
+        status = OCIAttrGet(descriptor, OCI_DTYPE_CHDES, &queries, NULL,
+                OCI_ATTR_CHDES_QUERIES, env->errorHandle);
+        if (Environment_CheckForError(env, status,
+                "Message_Initialize(): get queries collection") < 0)
+            return -1;
 
-    // populate each entry with a message table instance
-    for (i = 0; i < numTables; i++) {
-        status = OCICollGetElem(env->handle, env->errorHandle, tables, i,
-                &exists, (dvoid*) &tableDescriptor, &indicator);
-        if (Environment_CheckForError(env, status,
-                "Message_Initialize(): get element from collection") < 0)
+        // determine number of queries
+        if (!queries)
+            numQueries = 0;
+        else {
+            status = OCICollSize(env->handle, env->errorHandle, queries,
+                    &numQueries);
+            if (Environment_CheckForError(env, status,
+                    "Message_Initialize(): get size of collection") < 0)
+                return -1;
+        }
+
+        // create list to hold results
+        self->queries = PyList_New(numQueries);
+        if (!self->queries)
             return -1;
-        table = (udt_MessageTable*)
-                g_MessageTableType.tp_alloc(&g_MessageTableType, 0);
-        if (!table)
-            return -1;
-        PyList_SET_ITEM(self->tables, i, (PyObject*) table);
-        if (MessageTable_Initialize(table, env, *tableDescriptor) < 0)
-            return -1;
+
+        // populate each entry with a message query instance
+        for (i = 0; i < numQueries; i++) {
+            status = OCICollGetElem(env->handle, env->errorHandle, queries, i,
+                    &exists, (dvoid*) &queryDescriptor, &indicator);
+            if (Environment_CheckForError(env, status,
+                    "Message_Initialize(): get element from collection") < 0)
+                return -1;
+            query = (udt_MessageQuery*)
+                    g_MessageQueryType.tp_alloc(&g_MessageQueryType, 0);
+            if (!query)
+                return -1;
+            PyList_SET_ITEM(self->queries, i, (PyObject*) query);
+            if (MessageQuery_Initialize(query, env, *queryDescriptor) < 0)
+                return -1;
+        }
     }
+#endif
 
     return 0;
 }
     message = (udt_Message*) g_MessageType.tp_alloc(&g_MessageType, 0);
     if (!message)
         return -1;
-    if (Message_Initialize(message, env, descriptor) < 0) {
+    if (Message_Initialize(message, env, self, descriptor) < 0) {
         Py_DECREF(message);
         return -1;
     }
             return -1;
     }
 
+    // set suscription QOS
+    status = OCIAttrSet(self->handle, OCI_HTYPE_SUBSCRIPTION,
+            (dvoid*) &self->qos, sizeof(ub4), OCI_ATTR_SUBSCR_QOSFLAGS,
+            env->errorHandle);
+    if (Environment_CheckForError(env, status,
+                "Subscription_Register(): set qos flags") < 0)
+        return -1;
+
+#if ORACLE_VERSION_HEX >= 0x0B01
+    // set subscription change notification QOS flags
+    status = OCIAttrSet(self->handle, OCI_HTYPE_SUBSCRIPTION,
+            (dvoid*) &self->cqqos, sizeof(ub4), OCI_ATTR_SUBSCR_CQ_QOSFLAGS,
+            env->errorHandle);
+    if (Environment_CheckForError(env, status,
+                "Subscription_Register(): set cq qos flags") < 0)
+        return -1;
+#endif
+
     // set whether or not rowids are desired
     status = OCIAttrSet(self->handle, OCI_HTYPE_SUBSCRIPTION,
             (dvoid*) &self->rowids, sizeof(ub4), OCI_ATTR_CHNF_ROWIDS,
                 "Subscription_Register(): register") < 0)
         return -1;
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+    // get the registration id
+    status = OCIAttrGet(self->handle, OCI_HTYPE_SUBSCRIPTION, &self->id,
+              NULL, OCI_ATTR_SUBSCR_CQ_REGID, env->errorHandle);
+    if (Environment_CheckForError(env, status,
+                "Subscription_Register(): get registration id") < 0) {
+        return -1;
+    }
+#endif
+
     return 0;
 }
 
     PyObject *callback,                 // callback routine
     ub4 timeout,                        // timeout (in seconds)
     ub4 operations,                     // operations to notify
+    ub4 qos,                            // QOS flags
+    ub4 cqqos,                          // change notification QOS flags
     int rowids)                         // retrieve rowids?
 {
     udt_Subscription *self;
     self->timeout = timeout;
     self->rowids = rowids;
     self->operations = operations;
+    self->qos = qos;
+    self->cqqos = cqqos;
     self->handle = NULL;
+    self->id = 0;
     if (Subscription_Register(self) < 0) {
         Py_DECREF(self);
         return NULL;
     udt_Buffer statementBuffer;
     udt_Environment *env;
     udt_Cursor *cursor;
+#if ORACLE_VERSION_HEX >= 0x0B01
+    ub8 queryid;
+#endif
     sword status;
 
     // parse arguments
         Py_DECREF(cursor);
         return NULL;
     }
+
+#if ORACLE_VERSION_HEX >= 0x0B01
+    if (self->cqqos & OCI_SUBSCR_CQ_QOS_QUERY) {
+        // get the query id
+        status = OCIAttrGet(cursor->handle, OCI_HTYPE_STMT, &queryid, NULL,
+                OCI_ATTR_CQ_QUERYID, env->errorHandle);
+        if (Environment_CheckForError(env, status,
+                    "Subscription_RegisterQuery(): get query id") < 0) {
+            Py_DECREF(cursor);
+            return NULL;
+        }
+    }
+#endif
+
     Py_DECREF(cursor);
 
+#if ORACLE_VERSION_HEX >= 0x0B01
+    if (self->cqqos & OCI_SUBSCR_CQ_QOS_QUERY)
+        return PyInt_FromLong(queryid);
+#endif
+
     Py_INCREF(Py_None);
     return Py_None;
 }
 static void Message_Free(
     udt_Message *self)                  // object to free
 {
+    Py_CLEAR(self->subscription);
     Py_CLEAR(self->dbname);
     Py_CLEAR(self->tables);
+    Py_CLEAR(self->queries);
     Py_TYPE(self)->tp_free((PyObject*) self);
 }
 
     Py_TYPE(self)->tp_free((PyObject*) self);
 }
 
+
+#if ORACLE_VERSION_HEX >= 0x0B01
+//-----------------------------------------------------------------------------
+// MessageQuery_Free()
+//   Free the memory associated with a query in a message.
+//-----------------------------------------------------------------------------
+static void MessageQuery_Free(
+    udt_MessageQuery *self)               // object to free
+{
+    Py_CLEAR(self->tables);
+    Py_TYPE(self)->tp_free((PyObject*) self);
+}
+#endif
+
 #include <xa.h>
 
 // define what version of Oracle we are building
-#ifdef OCI_ATTR_MODULE
-#define ORACLE_10G
+#if !defined(OCI_MAJOR_VERSION) && defined(OCI_ATTR_MODULE)
+#define OCI_MAJOR_VERSION 10
+#define OCI_MINOR_VERSION 1
 #endif
-#ifdef OCI_MAJOR_VERSION
-#define ORACLE_10GR2
-#endif
-#ifdef OCI_ATTR_CONNECTION_CLASS
-#define ORACLE_11G
+#if defined(OCI_MAJOR_VERSION) && defined(OCI_MINOR_VERSION)
+#define ORACLE_VERSION_HEX ((OCI_MAJOR_VERSION << 8) | OCI_MINOR_VERSION)
+#else
+#error Unsupported version of OCI.
 #endif
 
 // PY_LONG_LONG was called LONG_LONG before Python 2.3
 }
 
 
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
 //-----------------------------------------------------------------------------
 // ClientVersion()
 //   Return the version of the Oracle client being used as a 5-tuple.
     { "DateFromTicks", (PyCFunction) DateFromTicks, METH_VARARGS },
     { "TimeFromTicks", (PyCFunction) TimeFromTicks, METH_VARARGS },
     { "TimestampFromTicks", (PyCFunction) TimestampFromTicks, METH_VARARGS },
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
     { "clientversion", (PyCFunction) ClientVersion, METH_NOARGS },
 #endif
     { NULL }
     MAKE_TYPE_READY(&g_ObjectAttributeType);
     MAKE_TYPE_READY(&g_ExternalLobVarType);
     MAKE_TYPE_READY(&g_ExternalObjectVarType);
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
     MAKE_TYPE_READY(&g_SubscriptionType);
     MAKE_TYPE_READY(&g_MessageType);
     MAKE_TYPE_READY(&g_MessageTableType);
     MAKE_TYPE_READY(&g_MessageRowType);
 #endif
+#if ORACLE_VERSION_HEX > 0x0B01
+    MAKE_TYPE_READY(&g_MessageQueryType);
+#endif
     MAKE_VARIABLE_TYPE_READY(&g_StringVarType);
     MAKE_VARIABLE_TYPE_READY(&g_FixedCharVarType);
     MAKE_VARIABLE_TYPE_READY(&g_RowidVarType);
     ADD_OCI_CONSTANT(SPOOL_ATTRVAL_WAIT)
     ADD_OCI_CONSTANT(SPOOL_ATTRVAL_NOWAIT)
     ADD_OCI_CONSTANT(SPOOL_ATTRVAL_FORCEGET)
-#ifdef ORACLE_10GR2
+#if ORACLE_VERSION_HEX >= 0x0A02
     ADD_OCI_CONSTANT(PRELIM_AUTH)
     ADD_OCI_CONSTANT(DBSHUTDOWN_ABORT)
     ADD_OCI_CONSTANT(DBSHUTDOWN_FINAL)
     ADD_OCI_CONSTANT(SUBSCR_PROTO_MAIL)
     ADD_OCI_CONSTANT(SUBSCR_PROTO_SERVER)
     ADD_OCI_CONSTANT(SUBSCR_PROTO_HTTP)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_RELIABLE)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_PAYLOAD)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_REPLICATE)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_SECURE)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_PURGE_ON_NTFN)
+	ADD_OCI_CONSTANT(SUBSCR_QOS_MULTICBK)
 #endif
-#ifdef ORACLE_11G
+#if ORACLE_VERSION_HEX >= 0x0B01
     ADD_OCI_CONSTANT(ATTR_PURITY_DEFAULT)
     ADD_OCI_CONSTANT(ATTR_PURITY_NEW)
     ADD_OCI_CONSTANT(ATTR_PURITY_SELF)
+	ADD_OCI_CONSTANT(EVENT_QUERYCHANGE)
+	ADD_OCI_CONSTANT(SUBSCR_CQ_QOS_QUERY)
+	ADD_OCI_CONSTANT(SUBSCR_CQ_QOS_BEST_EFFORT)
+	ADD_OCI_CONSTANT(SUBSCR_CQ_QOS_CLQRYCACHE)
+#endif
+#if ORACLE_VERSION_HEX >= 0x0B02
+    ADD_OCI_CONSTANT(SUBSCR_QOS_HAREG)
 #endif
 
     return module;

File doc/connection.rst

       This attribute is an extension to the DB API definition.
 
 
-.. method:: Connection.subscribe(namespace=cx_Oracle.SUBSCR_NAMESPACE_DBCHANGE, protocol=cx_Oracle.SUBSCR_PROTO_OCI, callback=None, timeout=0, operations=OPCODE_ALLOPS, rowids=False, port=0)
+.. method:: Connection.subscribe(namespace=cx_Oracle.SUBSCR_NAMESPACE_DBCHANGE, protocol=cx_Oracle.SUBSCR_PROTO_OCI, callback=None, timeout=0, operations=OPCODE_ALLOPS, rowids=False, port=0, qos=0, cqqos=0)
 
    Return a new Subscription object (:ref:`subscrobj`) using the connection.
    Currently the namespace and protocol arguments cannot have any other
    argument enables filtering of the messages that are sent (insert, update,
    delete). The rowids flag specifies whether the rowids of affected rows
    should be included in the messages that are sent. The port specifies the
-   listening port for callback notifications from the database server.
+   listening port for callback notifications from the database server. The qos
+   argument specifies quality of service options common to all subscriptions.
+   Currently the only meaningful option is cx_Oracle.SUBSCR_QOS_PURGE_ON_NTFN
+   which indicates that the subscription should be automatically unregistered
+   after the first notification. The default value of 0 indicates that the
+   subscription should persist after notification. The cqqos argument specifies
+   quality of service options specific to continuous query notification. The
+   default value of 0 indicates that change notification is required at
+   database object granularity. The flag cx_Oracle.SUBSCR_CQ_QOS_QUERY
+   specifies that change notification is required at query result set
+   granularity, and the flag SUBSCR_CQ_QOS_BEST_EFFORT specifies that best
+   effort filtering is accpetable, and false positive notifications may be
+   received.
 
    .. note::
 
 
    .. note::
 
+      Query result set change notification is only available in Oracle 11g and
+      higher.
+
+   .. note::
+
       Do not close the connection before the subscription object is deleted or
       the subscription object will not be deregistered in the database. This is
       done automatically if connection.close() is never called.

File doc/module.rst

    that an object change of some sort has taken place.
 
 
+.. data:: EVENT_QUERYCHANGE
+
+   This constant is a possible value for the type of a message and indicates
+   that the result set of a registered query has changed.
+
+
 .. data:: EVENT_SHUTDOWN
 
    This constant is a possible value for the type of a message and indicates
    of a message.
 
 
+.. data:: SUBSCR_CQ_QOS_QUERY
+
+   This constant can be used when creating a subscription and specifies that
+   notifications should only be sent if the result set of the registered query
+   changes. By default no false positive notifictions will be generated.
+
+
+.. data:: SUBSCR_CQ_QOS_BEST_EFFORT
+
+   This constant can be used when creating a subscription and specifies that
+   best effort filtering for query result set changes is acceptable. False
+   positive notifications may be received. This behaviour may be suitable for
+   caching applications.
+
+
+.. data:: SUBSCR_CQ_QOS_CLQRYCACHE
+
+   This constant is a future possible value for the cqqos argument when
+   creating a subscription. It specifies that client query caching be enabled.
+
+
 .. data:: SUBSCR_NAMESPACE_DBCHANGE
 
    This constant is the default (and currently only) value for the namespace
    invoked when a message is generated.
 
 
+.. data:: SUBSCR_QOS_HAREG
+	
+   This constant is a future possible value for the qos argument when
+   creating a subscription.
+
+	
+.. data:: SUBSCR_QOS_MULTICBK
+	
+   This constant is a future possible value for the qos argument when
+   creating a subscription.
+   
+
+.. data:: SUBSCR_QOS_PAYLOAD
+
+   This constant is a future possible value for the qos argument when
+   creating a subscription. It specifies that a payload be delivered with the
+   message.
+
+
+.. data:: SUBSCR_QOS_PURGE_ON_NTFN
+
+   This constant can be used when creating a subscription and specifies that
+   the subscription should be automatically unregistered after the first
+   notification.
+
+
+.. data:: SUBSCR_QOS_RELIABLE
+
+   This constant is a future possible value for the qos argument when
+   creating a subscription. It specifies that notifications should not be lost
+   in the event of database failure.
+
+
+.. data:: SUBSCR_QOS_REPLICATE
+
+   This constant is a future possible value for the qos argument when
+   creating a subscription.
+
+
+.. data:: SUBSCR_QOS_SECURE
+
+   This constant is a future possible value for the qos argument when
+   creating a subscription.
+
+
 Database Resident Connection Pooling
 ------------------------------------
 

File doc/subscription.rst

    the subscription when it was created.
 
 
+.. attribute:: Subscription.cqqos
+
+   This read-only attribute returns the cqqos used to register the
+   subscription when it was created.
+
+
+.. attribute:: Subscription.id
+
+   This read-only attribute returns the registration ID returned by the
+   database when this subscription was created. Support for this attribute
+   requires Oracle 11g or higher.
+
+
 .. attribute:: Subscription.namespace
 
    This read-only attribute returns the namespace used to register the
 .. attribute:: Subscription.operations
 
    This read-only attribute returns the operations that will send notifications
-   for each table that is registered using this subscription.
+   for each table or query that is registered using this subscription.
 
 
 .. attribute:: Subscription.port
    subscription when it was created.
 
 
+.. attribute:: Subscription.qos
+
+   This read-only attribute returns the qos used to register the
+   subscription when it was created.
+
+
 .. method:: Subscription.registerquery(statement, [args])
 
    Register the query for subsequent notification when tables referenced by the
    query are changed. This behaves similarly to cursor.execute() but only
    queries are permitted and the arguments must be a sequence or dictionary.
+   If the cqqos parameter included the flag cx_Oracle.SUBSCR_CQ_QOS_QUERY when
+   the subscription was created then the queryid for the registeredquery is
+   returned.
+
+
+.. note::
+
+   Query result set change notification is only available in Oracle 11g and
+   higher.
 
 
 .. attribute:: Subscription.rowids
    notification.
 
 
+.. attribute:: Message.queries
+
+   This read-only attribute returns a list of message query objects that give
+   information about query result sets  changed for this notification. This
+   attribute will be None if the cqqos parameter did not include the flag
+   cx_Oracle.SUBSCR_CQ_QOS_QUERY when the subscription was created.
+
+
+.. attribute:: Message.subscription
+
+   This read-only attribute returns the subscription object for which this
+   notification was generated.
+
+
 .. attribute:: Message.tables
 
    This read-only attribute returns a list of message table objects that give
-   information about the tables changed for this notification.
+   information about the tables changed for this notification. This
+   attribute will be None if the cqqos parameter included the flag
+   cx_Oracle.SUBSCR_CQ_QOS_QUERY when the subscription was created.
 
 
 .. attribute:: Message.type
 .. note::
 
    This object is created internally for each table changed when notification
-   is received and is found in the tables attribute of message objects.
+   is received and is found in the tables attribute of message objects, and
+   the tables attribute of message query objects.
 
 
 .. attribute:: MessageTable.name
 
    This read-only attribute returns the rowid of the row that was changed.
 
+
+Message Query Objects
+=====================
+
+.. note::
+
+   This object is created internally for each query result set changed when
+   notification is received and is found in the queries attribute of message
+   objects.
+   
+
+.. note::
+
+   Query result set change notification is only available in Oracle 11g and
+   higher.
+
+
+.. attribute:: MessageQuery.id
+
+   This read-only attribute returns the query id of the query for which the
+   result set changed. The value will match the value returned by 
+   Subscription.registerquery when the related query was registered.
+
+
+.. attribute:: MessageQuery.operation
+
+   This read-only attribute returns the operation that took place on the query
+   result set that was changed. Valid values for this attribute are
+   cx_Oracle.EVENT_DEREG and cx_Oracle.EVENT_QUERYCHANGE.
+
+
+.. attribute:: MessageQuery.tables
+
+   This read-only attribute returns a list of message table objects that give
+   information about the table changes that caused the query result set to
+   change for this notification.
+