Add support (as preview) for SODA.

This commit is contained in:
Anthony Tuininga 2018-09-10 11:37:53 -06:00
parent b611246a7b
commit 64f65050a2
22 changed files with 2957 additions and 0 deletions

View File

@ -280,6 +280,26 @@ Connection Object
This attribute is an extension to the DB API definition.
.. method:: Connection.getSodaDatabase()
Return a :ref:`SodaDatabase <sodadb>` object for Simple Oracle Document
Access (SODA). All SODA operations are performed either on the returned
SodaDatabase object or from objects created by SodaDatabase. See
`here <http://www.oracle.com/pls/topic/lookup?
ctx=dblatest&id=GUID-BE42F8D3-B86B-43B4-B2A3-5760A4DF79FB>`__ for
additional information on SODA.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. versionadded:: 7.0
.. note::
This method is an extension to the DB API definition.
.. method:: Connection.gettype(name)
Return a :ref:`type object <objecttype>` given its name. This can then be

View File

@ -24,6 +24,11 @@ Contents:
lob.rst
objecttype.rst
aq.rst
sodadb.rst
sodacoll.rst
sodaop.rst
sodadoc.rst
sodadoccur.rst
whatsnew.rst
releasenotes.rst
license.rst

118
doc/src/sodacoll.rst Normal file
View File

@ -0,0 +1,118 @@
.. _sodacoll:
**********************
SODA Collection Object
**********************
.. note::
This object is an extension the DB API. It is used to represent SODA
collections and is created by methods
:meth:`SodaDatabase.createCollection()` and
:meth:`SodaDatabase.openCollection()`.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. method:: SodaCollection.createIndex(spec)
Creates an index on a SODA collection. The spec is expected to be a
dictionary or a JSON-encoded string. See this `overview
<https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-4848E6A0-58A7-44FD-8D6D-A033D0CCF9CB>`__
for information on indexes in SODA.
Note that a commit should be performed before attempting to create an
index.
.. versionadded:: 7.0
.. method:: SodaCollection.drop()
Drops the collection from the database, if it exists. Note that if the
collection was created with mapMode set to True the underlying table will
not be dropped.
A boolean value is returned indicating if the collection was actually
dropped.
.. versionadded:: 7.0
.. method:: SodaCollection.dropIndex(name, force=False)
Drops the index with the specified name, if it exists.
The force parameter, if set to True, can be used to force the dropping of
an index that the underlying Oracle Database domain index doesn't normally
permit. This is only applicable to spatial and JSON search indexes.
See `here <https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-F60F75DF-2866-4F93-BB7F-8FCE64BF67B6>`__
for more information.
A boolean value is returned indicating if the index was actually dropped.
.. versionadded:: 7.0
.. method:: SodaCollection.find()
This method is used to begin an operation that will act upon documents in
the collection. It creates and returns a
:ref:`SodaOperation object <sodaop>` which is used to specify the criteria
and the operation that will be performed on the documents that match that
criteria.
.. versionadded:: 7.0
.. method:: SodaCollection.getDataGuide()
Returns a :ref:`SODA document object <sodadoc>` containing property names,
data types and lengths inferred from the JSON documents in the collection.
It can be useful for exploring the schema of a collection. Note that this
method is only supported for JSON-only collections where a JSON search
index has been created with the 'dataguide' option enabled. If there are
no documents in the collection, None is returned.
.. versionadded:: 7.0
.. method:: SodaCollection.insertOne(doc)
Inserts a given document into the collection. The input document can be a
dictionary or list or an existing :ref:`SODA document object <sodadoc>`.
.. versionadded:: 7.0
.. method:: SodaCollection.insertOneAndGet(doc)
Similarly to :meth:`~SodaCollection.insertOne()` this method inserts a
given document into the collection. The only difference is that it
returns a :ref:`SODA Document object <sodadoc>`. Note that for performance
reasons the returned document does not contain the content.
.. versionadded:: 7.0
.. attribute:: SodaCollection.metadata
This read-only attribute returns a dicationary containing the metadata that
was used to create the collection. See this `collection metadata reference
<https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-49EFF3D3-9FAB-4DA6-BDE2-2650383566A3>`__
for more information.
.. versionadded:: 7.0
.. attribute:: SodaCollection.name
This read-only attribute returns the name of the collection.
.. versionadded:: 7.0

90
doc/src/sodadb.rst Normal file
View File

@ -0,0 +1,90 @@
.. _sodadb:
********************
SODA Database Object
********************
.. note::
This object is an extension the DB API. It is returned by the method
:meth:`Connection.getSodaDatabase()`.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. method:: SodaDatabase.createCollection(name, metadata=None, mapMode=False)
Creates a SODA collection with the given name and returns a new
:ref:`SODA collection object <sodacoll>`. If you try to create a
collection, and a collection with the same name and metadata already
exists, then that existing collection is opened without error.
If metadata is specified, it is expected to be a string containing valid
JSON or a dictionary that will be transformed into a JSON string. This JSON
permits you to specify the configuration of the collection including
storage options; specifying the presence or absence of columns for creation
timestamp, last modified timestamp and version; whether the collection can
store only JSON documents; and methods of key and version generation. The
default metadata creates a collection that only supports JSON documents and
uses system generated keys. See this `collection metadata reference
<https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-49EFF3D3-9FAB-4DA6-BDE2-2650383566A3>`__
for more information.
If the mapMode parameter is set to True, the new collection is mapped to an
existing table instead of creating a table. If a collection is created in
this way, dropping the collection will not drop the existing table either.
.. versionadded:: 7.0
.. method:: SodaDatabase.createDocument(content, key=None, mediaType="application/json")
Creates a :ref:`Soda document <sodadoc>` usable for SODA write operations.
You only need to use this method if your collection requires
client-assigned keys or has non-JSON content; otherwise, you can pass your
content directly to SODA write operations. SodaDocument attributes
'createdOn', 'lastModified' and 'version' will be None.
The content parameter can be a dictionary or list which will be transformed
into a JSON string and then UTF-8 encoded. It can also be a string which
will be UTF-8 encoded or it can be a bytes object which will be stored
unchanged. If a bytes object is provided and the content is expected to be
JSON, note that SODA only supports UTF-8, UTF-16LE and UTF-16BE encodings.
The key parameter should only be supplied if the collection in which the
document is to be placed requires client-assigned keys.
The mediaType parameter should only be supplied if the collection in which
the document is to be placed supports non-JSON documents and the content
for this document is non-JSON. Using a standard MIME type for this value is
recommended but any string will be accepted.
.. versionadded:: 7.0
.. method:: SodaDatabase.getCollectionNames(startName=None, limit=0)
Returns a list of the names of collections in the database that match the
criteria, in alphabetical order.
If the startName parameter is specified, the list of names returned will
start with this value and also contain any names that fall after this value
in alphabetical order.
If the limit parameter is specified and is non-zero, the number of
collection names returned will be limited to this value.
.. versionadded:: 7.0
.. method:: SodaDatabase.openCollection(name)
Opens an existing collection with the given name and returns a new
:ref:`SODA collection object <sodacoll>`. If a collection with that name
does not exist, None is returned.
.. versionadded:: 7.0

97
doc/src/sodadoc.rst Normal file
View File

@ -0,0 +1,97 @@
.. _sodadoc:
********************
SODA Document Object
********************
.. note::
This object is an extension the DB API. It is returned by the methods
:meth:`SodaDatabase.createDocument()`,
:meth:`SodaOperation.getDocuments()` and
:meth:`SodaOperation.getOne()` as well as by iterating over
:ref:`SODA document cursors <sodadoccur>`.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. attribute:: SodaDoc.createdOn
This read-only attribute returns the creation time of the document in
`ISO 8601 <https://www.iso.org/iso-8601-date-and-time-format.html>`__
format. Documents created by :meth:`SodaDatabase.createDocument()` or
fetched from collections where this attribute is not stored will return
None.
.. versionadded:: 7.0
.. method:: SodaDoc.getContent()
Returns the content of the document as a dictionary or list. This method
assumes that the content is application/json and will raise an exception if
this is not the case. If there is no content, however, None will be
returned.
.. versionadded:: 7.0
.. method:: SodaDoc.getContentAsBytes()
Returns the content of the document as a bytes object. If there is no
content, however, None will be returned.
.. versionadded:: 7.0
.. method:: SodaDoc.getContentAsString()
Returns the content of the document as a string. This method assumes that
the content is application/json and will raise an exception if this is not
the case. If there is no content, however, None will be returned.
.. versionadded:: 7.0
.. attribute:: SodaDoc.key
This read-only attribute returns the unique key assigned to this document.
Documents created by :meth:`SodaDatabase.createDocument()` may not have a
value assigned to them and return None.
.. versionadded:: 7.0
.. attribute:: SodaDoc.lastModified
This read-only attribute returns the last modified time of the document in
`ISO 8601 <https://www.iso.org/iso-8601-date-and-time-format.html>`__
format. Documents created by :meth:`SodaDatabase.createDocument()` or
fetched from collections where this attribute is not stored will return
None.
.. versionadded:: 7.0
.. attribute:: SodaDoc.mediaType
This read-only attribute returns the media type assigned to the document.
By convention this is expected to be a MIME type but no checks are
performed on this value. If a value is not specified when calling
:meth:`SodaDatabase.createDocument()` or the document is fetched from a
collection where this component is not stored, the string
"application/json" is returned.
.. versionadded:: 7.0
.. attribute:: SodaDoc.version
This read-only attribute returns the version assigned to this document.
Documents created by :meth:`SodaDatabase.createDocument()` or fetched
from collections where this attribute is not stored will return None.
.. versionadded:: 7.0

25
doc/src/sodadoccur.rst Normal file
View File

@ -0,0 +1,25 @@
.. _sodadoccur:
***************************
SODA Document Cursor Object
***************************
.. note::
This object is an extension the DB API. It is returned by the method
:meth:`SodaOperation.getCursor()` and implements the iterator protocol.
Each iteration will return a :ref:`SODA document object <sodadoc>`.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. method:: SodaDocCursor.close()
Close the cursor now, rather than whenever __del__ is called. The cursor
will be unusable from this point forward; an Error exception will be raised
if any operation is attempted with the cursor.
.. versionadded:: 7.0

165
doc/src/sodaop.rst Normal file
View File

@ -0,0 +1,165 @@
.. _sodaop:
*********************
SODA Operation Object
*********************
.. note::
This object is an extension the DB API. It represents an operation that
will be performed on all or some of the documents in a SODA collection. It
is created by the method :meth:`SodaCollection.find()`.
SODA support in cx_Oracle is in Preview status and should not be used in
production. It will be supported with a future version of Oracle Client
libraries.
.. method:: SodaOperation.count()
Returns a count of the number of documents in the collection that match
the criteria. If :meth:`~SodaOperation.skip()` or
:meth:`~SodaOperation.limit()` were called on this object, an exception is
raised.
.. versionadded:: 7.0
.. method:: SodaOperation.filter(value)
Sets a filter specification for complex document queries and ordering of
JSON documents. Filter specifications must be provided as a dictionary or
JSON-encoded string and can include comparisons, regular expressions,
logical and spatial operators, among others. See the
`overview of SODA filter specifications
<https://www.oracle.com/pls/topic/
lookup?ctx=dblatest&id=GUID-CB09C4E3-BBB1-40DC-88A8-8417821B0FBE>`__
for more information.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0
.. method:: SodaOperation.getCursor()
Returns a :ref:`SODA Document Cursor object <sodadoccur>` that can be used
to iterate over the documents that match the criteria.
.. versionadded:: 7.0
.. method:: SodaOperation.getDocuments()
Returns a list of :ref:`SODA Document objects <sodadoc>` that match the
criteria.
.. versionadded:: 7.0
.. method:: SodaOperation.getOne()
Returns a single :ref:`SODA Document object <sodadoc>` that matches the
criteria. Note that if multiple documents match the criteria only the first
one is returned.
.. versionadded:: 7.0
.. method:: SodaOperation.key(value)
Specifies that the document with the specified key should be returned.
This causes any previous calls made to this method and
:meth:`~SodaOperation.keys()` to be ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0
.. method:: SodaOperation.keys(seq)
Specifies that documents that match the keys found in the supplied sequence
should be returned. This causes any previous calls made to this method and
:meth:`~SodaOperation.key()` to be ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0
.. method:: SodaOperation.limit(value)
Specifies that only the specified number of documents should be returned.
This method is only usable for read operations such as
:meth:`~SodaOperation.getCursor()` and
:meth:`~SodaOperation.getDocuments()`. For write operations, any value set
using this method is ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0
.. method:: SodaOperation.remove()
Removes all of the documents in the collection that match the criteria. The
number of documents that have been removed is returned.
.. versionadded:: 7.0
.. method:: SodaOperation.replaceOne(doc)
Replaces a single document in the collection with the specified document.
The input document can be a dictionary or list or an existing
:ref:`SODA document object <sodadoc>`. A boolean indicating if a document
was replaced or not is returned.
Currently the method :meth:`~SodaOperation.key()` must be called before
this method can be called.
.. versionadded:: 7.0
.. method:: SodaOperation.replaceOneAndGet(doc)
Similarly to :meth:`~SodaOperation.replaceOne()`, this method replaces a
single document in the collection with the specified document. The only
difference is that it returns a :ref:`SODA document object <sodadoc>`.
Note that for performance reasons the returned document does not contain
the content.
.. versionadded:: 7.0
.. method:: SodaOperation.skip(value)
Specifies the number of documents that match the other criteria that will
be skipped. This method is only usable for read operations such as
:meth:`~SodaOperation.getCursor()` and
:meth:`~SodaOperation.getDocuments()`. For write operations, any value set
using this method is ignored.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0
.. method:: SodaOperation.version(value)
Specifies that documents with the specified version should be returned.
Typically this is used with :meth:`~SodaOperation.key()` to implement
optimistic locking, so that the write operation called later does not
affect a document that someone else has modified.
As a convenience, the SodaOperation object is returned so that further
criteria can be specified by chaining methods together.
.. versionadded:: 7.0

88
samples/SodaBasic.py Normal file
View File

@ -0,0 +1,88 @@
#------------------------------------------------------------------------------
# Copyright 2018, Oracle and/or its affiliates. All rights reserved.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# soda1.py
# A basic Simple Oracle Document Access (SODA) example.
#
# This script requires cx_Oracle 7.0 and higher.
# The user must have been granted the SODA_APP privilege
#------------------------------------------------------------------------------
from __future__ import print_function
import cx_Oracle
import SampleEnv
connection = cx_Oracle.Connection(SampleEnv.MAIN_CONNECT_STRING)
# The general recommendation for simple SODA usage is to enable autocommit
connection.autocommit = True
# Create the parent object for SODA
soda = connection.getSodaDatabase()
# Create a new SODA collection and index
# This will open an existing collection, if the name is already in use.
collection = soda.createCollection("mycollection")
indexSpec = { 'name': 'CITY_IDX',
'fields': [ {
'path': 'address.city',
'datatype': 'string',
'order': 'asc' } ] }
collection.createIndex(indexSpec)
# Insert a documents.
# A system generated key is created by default.
content = {'name': 'Matilda', 'address': {'city': 'Melbourne'}}
doc = collection.insertOneAndGet(content)
key = doc.key
print('The key of the new SODA document is: ', key)
# Fetch the document back
doc = collection.find().key(key).getOne() # A SodaDocument
content = doc.getContent() # A JavaScript object
print('Retrieved SODA document dictionary is:')
print(content)
content = doc.getContentAsString() # A JSON string
print('Retrieved SODA document string is:')
print(content)
# Replace document contents
content = {'name': 'Matilda', 'address': {'city': 'Sydney'}}
collection.find().key(key).replaceOne(content)
# Insert some more documents without caring about their keys
content = {'name': 'Venkat', 'address': {'city': 'Bengaluru'}}
collection.insertOne(content)
content = {'name': 'May', 'address': {'city': 'London'}}
collection.insertOne(content)
content = {'name': 'Sally-Ann', 'address': {'city': 'San Francisco'}}
collection.insertOne(content)
# Find all documents with names like 'Ma%'
print("Names matching 'Ma%'")
documents = collection.find().filter({'name': {'$like': 'Ma%'}}).getDocuments()
for d in documents:
content = d.getContent()
print(content["name"])
# Count all documents
c = collection.find().count()
print('Collection has', c, 'documents')
# Remove documents with cities containing 'o'
print('Removing documents')
c = collection.find().filter({'address.city': {'$regex': '.*o.*'}}).remove()
print('Dropped', c, 'documents')
# Count all documents
c = collection.find().count()
print('Collection has', c, 'documents')
# Drop the collection
if collection.drop():
print('Collection was dropped')

View File

@ -39,6 +39,19 @@ to &main_user;
grant execute on dbms_aqadm to &main_user;
grant execute on dbms_lock to &main_user;
begin
for r in
( select role
from dba_roles
where role in ('SODA_APP')
) loop
execute immediate 'grant ' || r.role || ' to &main_user';
end loop;
end;
/
create user &edition_user identified by &edition_password;
grant

View File

@ -50,6 +50,7 @@ static PyObject *cxoConnection_startup(cxoConnection*, PyObject*, PyObject*);
static PyObject *cxoConnection_subscribe(cxoConnection*, PyObject*, PyObject*);
static PyObject *cxoConnection_unsubscribe(cxoConnection*, PyObject*,
PyObject*);
static PyObject *cxoConnection_getSodaDatabase(cxoConnection*, PyObject*);
static PyObject *cxoConnection_getLTXID(cxoConnection*, void*);
static PyObject *cxoConnection_getHandle(cxoConnection*, void*);
static PyObject *cxoConnection_getCurrentSchema(cxoConnection*, void*);
@ -108,6 +109,8 @@ static PyMethodDef cxoConnectionMethods[] = {
{ "enq", (PyCFunction) cxoConnection_enqueue,
METH_VARARGS | METH_KEYWORDS },
{ "createlob", (PyCFunction) cxoConnection_createLob, METH_O },
{ "getSodaDatabase", (PyCFunction) cxoConnection_getSodaDatabase,
METH_NOARGS },
{ NULL }
};
@ -498,6 +501,22 @@ static int cxoConnectionParams_finalize(cxoConnectionParams *params)
}
//-----------------------------------------------------------------------------
// cxoConnection_getSodaFlags()
// Get the flags to use for SODA. This checks the autocommit flag and enables
// atomic commit if set to a true value. It also checks to ensure that the
// connection is valid.
//-----------------------------------------------------------------------------
int cxoConnection_getSodaFlags(cxoConnection *conn, uint32_t *flags)
{
if (cxoConnection_isConnected(conn) < 0)
return -1;
*flags = (conn->autocommit) ? DPI_SODA_FLAGS_ATOMIC_COMMIT :
DPI_SODA_FLAGS_DEFAULT;
return 0;
}
//-----------------------------------------------------------------------------
// cxoConnection_isConnected()
// Determines if the connection object is connected to the database. If not,
@ -1615,6 +1634,19 @@ static PyObject *cxoConnection_unsubscribe(cxoConnection *conn, PyObject* args,
}
//-----------------------------------------------------------------------------
// cxoConnection_commit()
// Commit the transaction on the connection.
//-----------------------------------------------------------------------------
static PyObject *cxoConnection_getSodaDatabase(cxoConnection *conn,
PyObject *args)
{
if (cxoConnection_isConnected(conn) < 0)
return NULL;
return (PyObject*) cxoSodaDatabase_new(conn);
}
//-----------------------------------------------------------------------------
// cxoConnection_getCurrentSchema()
// Return the current schema associated with the connection.

View File

@ -46,6 +46,8 @@ PyObject *cxoIntegrityErrorException = NULL;
PyObject *cxoInternalErrorException = NULL;
PyObject *cxoProgrammingErrorException = NULL;
PyObject *cxoNotSupportedErrorException = NULL;
PyObject *cxoJsonDumpFunction = NULL;
PyObject *cxoJsonLoadFunction = NULL;
cxoFuture *cxoFutureObj = NULL;
dpiContext *cxoDpiContext = NULL;
dpiVersionInfo cxoClientVersionInfo;
@ -277,6 +279,11 @@ static PyObject *cxoModule_initialize(void)
CXO_MAKE_TYPE_READY(&cxoPyTypeObjectVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeRowidVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeSessionPool);
CXO_MAKE_TYPE_READY(&cxoPyTypeSodaCollection);
CXO_MAKE_TYPE_READY(&cxoPyTypeSodaDatabase);
CXO_MAKE_TYPE_READY(&cxoPyTypeSodaDoc);
CXO_MAKE_TYPE_READY(&cxoPyTypeSodaDocCursor);
CXO_MAKE_TYPE_READY(&cxoPyTypeSodaOperation);
CXO_MAKE_TYPE_READY(&cxoPyTypeStringVar);
CXO_MAKE_TYPE_READY(&cxoPyTypeSubscr);
CXO_MAKE_TYPE_READY(&cxoPyTypeTimestampVar);
@ -339,6 +346,11 @@ static PyObject *cxoModule_initialize(void)
CXO_ADD_TYPE_OBJECT("EnqOptions", &cxoPyTypeEnqOptions)
CXO_ADD_TYPE_OBJECT("DeqOptions", &cxoPyTypeDeqOptions)
CXO_ADD_TYPE_OBJECT("MessageProperties", &cxoPyTypeMsgProps)
CXO_ADD_TYPE_OBJECT("SodaCollection", &cxoPyTypeSodaCollection)
CXO_ADD_TYPE_OBJECT("SodaDatabase", &cxoPyTypeSodaDatabase)
CXO_ADD_TYPE_OBJECT("SodaDoc", &cxoPyTypeSodaDoc)
CXO_ADD_TYPE_OBJECT("SodaDocCursor", &cxoPyTypeSodaDocCursor)
CXO_ADD_TYPE_OBJECT("SodaOperation", &cxoPyTypeSodaOperation)
// the name "connect" is required by the DB API
CXO_ADD_TYPE_OBJECT("connect", &cxoPyTypeConnection)

View File

@ -80,6 +80,11 @@ typedef struct cxoObject cxoObject;
typedef struct cxoObjectAttr cxoObjectAttr;
typedef struct cxoObjectType cxoObjectType;
typedef struct cxoSessionPool cxoSessionPool;
typedef struct cxoSodaCollection cxoSodaCollection;
typedef struct cxoSodaDatabase cxoSodaDatabase;
typedef struct cxoSodaDoc cxoSodaDoc;
typedef struct cxoSodaDocCursor cxoSodaDocCursor;
typedef struct cxoSodaOperation cxoSodaOperation;
typedef struct cxoSubscr cxoSubscr;
typedef struct cxoVar cxoVar;
typedef struct cxoVarType cxoVarType;
@ -137,6 +142,11 @@ extern PyTypeObject cxoPyTypeObjectType;
extern PyTypeObject cxoPyTypeObjectVar;
extern PyTypeObject cxoPyTypeRowidVar;
extern PyTypeObject cxoPyTypeSessionPool;
extern PyTypeObject cxoPyTypeSodaCollection;
extern PyTypeObject cxoPyTypeSodaDatabase;
extern PyTypeObject cxoPyTypeSodaDoc;
extern PyTypeObject cxoPyTypeSodaDocCursor;
extern PyTypeObject cxoPyTypeSodaOperation;
extern PyTypeObject cxoPyTypeStringVar;
extern PyTypeObject cxoPyTypeSubscr;
extern PyTypeObject cxoPyTypeTimestampVar;
@ -145,6 +155,10 @@ extern PyTypeObject cxoPyTypeTimestampVar;
extern PyTypeObject *cxoPyTypeDate;
extern PyTypeObject *cxoPyTypeDateTime;
// JSON dump and load functions for use with SODA
extern PyObject *cxoJsonDumpFunction;
extern PyObject *cxoJsonLoadFunction;
// ODPI-C context and version information
extern dpiContext *cxoDpiContext;
extern dpiVersionInfo cxoClientVersionInfo;
@ -353,6 +367,43 @@ struct cxoSessionPool {
PyTypeObject *connectionType;
};
struct cxoSodaCollection {
PyObject_HEAD
dpiSodaColl *handle;
cxoSodaDatabase *db;
PyObject *name;
};
struct cxoSodaDatabase {
PyObject_HEAD
dpiSodaDb *handle;
cxoConnection *connection;
};
struct cxoSodaDoc {
PyObject_HEAD
cxoSodaDatabase *db;
dpiSodaDoc *handle;
};
struct cxoSodaDocCursor {
PyObject_HEAD
cxoSodaDatabase *db;
dpiSodaDocCursor *handle;
};
struct cxoSodaOperation {
PyObject_HEAD
cxoSodaCollection *coll;
dpiSodaOperOptions options;
uint32_t numKeyBuffers;
cxoBuffer *keyBuffers;
cxoBuffer keyBuffer;
cxoBuffer versionBuffer;
cxoBuffer filterBuffer;
};
struct cxoSubscr {
PyObject_HEAD
dpiSubscr *handle;
@ -403,6 +454,7 @@ struct cxoVarType {
int cxoBuffer_fromObject(cxoBuffer *buf, PyObject *obj, const char *encoding);
int cxoBuffer_init(cxoBuffer *buf);
int cxoConnection_getSodaFlags(cxoConnection *conn, uint32_t *flags);
int cxoConnection_isConnected(cxoConnection *conn);
int cxoCursor_performBind(cxoCursor *cursor);
@ -436,6 +488,18 @@ cxoObjectType *cxoObjectType_new(cxoConnection *connection,
cxoObjectType *cxoObjectType_newByName(cxoConnection *connection,
PyObject *name);
cxoSodaCollection *cxoSodaCollection_new(cxoSodaDatabase *db,
dpiSodaColl *handle);
cxoSodaDatabase *cxoSodaDatabase_new(cxoConnection *connection);
cxoSodaDoc *cxoSodaDoc_new(cxoSodaDatabase *db, dpiSodaDoc *handle);
cxoSodaDocCursor *cxoSodaDocCursor_new(cxoSodaDatabase *db,
dpiSodaDocCursor *handle);
cxoSodaOperation *cxoSodaOperation_new(cxoSodaCollection *collection);
void cxoSubscr_callback(cxoSubscr *subscr, dpiSubscrMessage *message);
PyObject *cxoTransform_dateFromTicks(PyObject *args);
@ -459,6 +523,9 @@ int cxoUtils_getBooleanValue(PyObject *obj, int defaultValue, int *value);
int cxoUtils_getModuleAndName(PyTypeObject *type, PyObject **module,
PyObject **name);
int cxoUtils_initializeDPI(void);
int cxoUtils_processJsonArg(PyObject *arg, cxoBuffer *buffer);
int cxoUtils_processSodaDocArg(cxoSodaDatabase *db, PyObject *arg,
cxoSodaDoc **doc);
cxoVarType *cxoVarType_fromDataTypeInfo(dpiDataTypeInfo *info);
cxoVarType *cxoVarType_fromPythonType(PyTypeObject *type);

406
src/cxoSodaCollection.c Normal file
View File

@ -0,0 +1,406 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoSodaCollection.c
// Defines the routines for handling the SODA collection.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// declaration of functions
//-----------------------------------------------------------------------------
static void cxoSodaCollection_free(cxoSodaCollection*);
static PyObject *cxoSodaCollection_repr(cxoSodaCollection*);
static PyObject *cxoSodaCollection_createIndex(cxoSodaCollection*, PyObject*);
static PyObject *cxoSodaCollection_drop(cxoSodaCollection*, PyObject*);
static PyObject *cxoSodaCollection_dropIndex(cxoSodaCollection*, PyObject*,
PyObject*);
static PyObject *cxoSodaCollection_find(cxoSodaCollection*, PyObject*);
static PyObject *cxoSodaCollection_getDataGuide(cxoSodaCollection*, PyObject*);
static PyObject *cxoSodaCollection_insertOne(cxoSodaCollection*, PyObject*);
static PyObject *cxoSodaCollection_insertOneAndGet(cxoSodaCollection*,
PyObject*);
static PyObject *cxoSodaCollection_getMetadata(cxoSodaCollection*, PyObject*);
//-----------------------------------------------------------------------------
// declaration of methods
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "createIndex", (PyCFunction) cxoSodaCollection_createIndex, METH_O },
{ "drop", (PyCFunction) cxoSodaCollection_drop, METH_NOARGS },
{ "dropIndex", (PyCFunction) cxoSodaCollection_dropIndex,
METH_VARARGS | METH_KEYWORDS },
{ "find", (PyCFunction) cxoSodaCollection_find, METH_NOARGS },
{ "getDataGuide", (PyCFunction) cxoSodaCollection_getDataGuide,
METH_NOARGS },
{ "insertOne", (PyCFunction) cxoSodaCollection_insertOne, METH_O },
{ "insertOneAndGet", (PyCFunction) cxoSodaCollection_insertOneAndGet,
METH_O },
{ NULL }
};
//-----------------------------------------------------------------------------
// declaration of members
//-----------------------------------------------------------------------------
static PyMemberDef cxoMembers[] = {
{ "name", T_OBJECT, offsetof(cxoSodaCollection, name), READONLY },
{ NULL }
};
//-----------------------------------------------------------------------------
// declaration of calculated members
//-----------------------------------------------------------------------------
static PyGetSetDef cxoCalcMembers[] = {
{ "metadata", (getter) cxoSodaCollection_getMetadata, 0, 0, 0 },
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declarations
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeSodaCollection = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.SodaCollection", // tp_name
sizeof(cxoSodaCollection), // tp_basicsize
0, // tp_itemsize
(destructor) cxoSodaCollection_free,// tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
(reprfunc) cxoSodaCollection_repr, // 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
cxoMethods, // tp_methods
cxoMembers, // tp_members
cxoCalcMembers, // 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
};
//-----------------------------------------------------------------------------
// cxoSodaCollection_initialize()
// Initialize a new collection with its attributes.
//-----------------------------------------------------------------------------
static int cxoSodaCollection_initialize(cxoSodaCollection *coll,
cxoSodaDatabase *db, const char *encoding, dpiSodaColl *handle)
{
uint32_t nameLength;
const char *name;
// get name from ODPI-C
if (dpiSodaColl_getName(handle, &name, &nameLength) < 0)
return cxoError_raiseAndReturnInt();
coll->name = cxoPyString_fromEncodedString(name, nameLength, encoding,
NULL);
if (!coll->name)
return -1;
// set base attributes (handle should not be added until there is no
// possibility of further failure)
coll->handle = handle;
Py_INCREF(db);
coll->db = db;
return 0;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_new()
// Create a new SODA collection object.
//-----------------------------------------------------------------------------
cxoSodaCollection *cxoSodaCollection_new(cxoSodaDatabase *db,
dpiSodaColl *handle)
{
cxoSodaCollection *coll;
coll = (cxoSodaCollection*)
cxoPyTypeSodaCollection.tp_alloc(&cxoPyTypeSodaCollection, 0);
if (!coll)
return NULL;
if (cxoSodaCollection_initialize(coll, db,
db->connection->encodingInfo.encoding, handle) < 0) {
Py_DECREF(coll);
return NULL;
}
return coll;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_free()
// Free the memory associated with a SODA collection.
//-----------------------------------------------------------------------------
static void cxoSodaCollection_free(cxoSodaCollection *coll)
{
if (coll->handle) {
dpiSodaColl_release(coll->handle);
coll->handle = NULL;
}
Py_CLEAR(coll->db);
Py_CLEAR(coll->name);
Py_TYPE(coll)->tp_free((PyObject*) coll);
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_repr()
// Return a string representation of a SODA collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_repr(cxoSodaCollection *coll)
{
PyObject *module, *name, *result;
if (cxoUtils_getModuleAndName(Py_TYPE(coll), &module, &name) < 0)
return NULL;
result = cxoUtils_formatString("<%s.%s %s>",
PyTuple_Pack(3, module, name, coll->name));
Py_DECREF(module);
Py_DECREF(name);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_createIndex()
// Create an index on a SODA collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_createIndex(cxoSodaCollection *coll,
PyObject *specObj)
{
cxoBuffer specBuffer;
uint32_t flags;
int status;
if (cxoUtils_processJsonArg(specObj, &specBuffer) < 0)
return NULL;
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_createIndex(coll->handle, specBuffer.ptr,
specBuffer.size, flags);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&specBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_drop()
// Create a SODA collection and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_drop(cxoSodaCollection *coll,
PyObject *args)
{
uint32_t flags;
int isDropped;
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
if (dpiSodaColl_drop(coll->handle, flags, &isDropped) < 0)
return cxoError_raiseAndReturnNull();
if (isDropped)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_dropIndex()
// Drop an index on a SODA collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_dropIndex(cxoSodaCollection *coll,
PyObject *args, PyObject *keywordArgs)
{
static char *keywordList[] = { "name", "force", NULL };
int status, isDropped, force;
PyObject *nameObj, *forceObj;
cxoBuffer nameBuffer;
uint32_t flags;
// parse arguments
forceObj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O|O", keywordList,
&nameObj, &forceObj))
return NULL;
if (cxoUtils_getBooleanValue(forceObj, 0, &force) < 0)
return NULL;
// drop index
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
if (force)
flags |= DPI_SODA_FLAGS_INDEX_DROP_FORCE;
if (cxoBuffer_fromObject(&nameBuffer, nameObj,
coll->db->connection->encodingInfo.encoding) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_dropIndex(coll->handle, nameBuffer.ptr,
nameBuffer.size, flags, &isDropped);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&nameBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
if (isDropped)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_find()
// Creates an operation options object which can be used to perform a number
// of operations on the collection using the criteria set on the object.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_find(cxoSodaCollection *coll,
PyObject *args)
{
return (PyObject*) cxoSodaOperation_new(coll);
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_getDataGuide()
// Return the data guide associated with the collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_getDataGuide(cxoSodaCollection *coll,
PyObject *args)
{
dpiSodaDoc *handle;
cxoSodaDoc *doc;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_getDataGuide(coll->handle, flags, &handle);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
if (handle) {
doc = cxoSodaDoc_new(coll->db, handle);
if (!doc)
return NULL;
return (PyObject*) doc;
}
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_insertOne()
// Insert a single document into the collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_insertOne(cxoSodaCollection *coll,
PyObject *arg)
{
cxoSodaDoc *doc;
uint32_t flags;
int status;
if (cxoUtils_processSodaDocArg(coll->db, arg, &doc) < 0)
return NULL;
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_insertOne(coll->handle, doc->handle, flags, NULL);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
Py_DECREF(doc);
return NULL;
}
Py_DECREF(doc);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_insertOneAndGet()
// Insert a single document into the collection and return a document
// containing all but the content itself.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_insertOneAndGet(cxoSodaCollection *coll,
PyObject *arg)
{
dpiSodaDoc *returnedDoc;
cxoSodaDoc *doc;
uint32_t flags;
int status;
if (cxoUtils_processSodaDocArg(coll->db, arg, &doc) < 0)
return NULL;
if (cxoConnection_getSodaFlags(coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_insertOne(coll->handle, doc->handle, flags,
&returnedDoc);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
Py_DECREF(doc);
return NULL;
}
Py_DECREF(doc);
return (PyObject*) cxoSodaDoc_new(coll->db, returnedDoc);
}
//-----------------------------------------------------------------------------
// cxoSodaCollection_getMetadata()
// Retrieve the metadata for the collection.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaCollection_getMetadata(cxoSodaCollection *coll,
PyObject *unused)
{
PyObject *str, *result;
uint32_t valueLength;
const char *value;
if (dpiSodaColl_getMetadata(coll->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
str = PyUnicode_Decode(value, valueLength,
coll->db->connection->encodingInfo.encoding, NULL);
if (!str)
return NULL;
result = PyObject_CallFunctionObjArgs(cxoJsonLoadFunction, str, NULL);
Py_DECREF(str);
return result;
}

381
src/cxoSodaDatabase.c Normal file
View File

@ -0,0 +1,381 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoSodaDatabase.c
// Defines the routines for handling the SODA database.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// Declaration of functions
//-----------------------------------------------------------------------------
static void cxoSodaDatabase_free(cxoSodaDatabase*);
static PyObject *cxoSodaDatabase_repr(cxoSodaDatabase*);
static PyObject *cxoSodaDatabase_createCollection(cxoSodaDatabase*,
PyObject*, PyObject*);
static PyObject *cxoSodaDatabase_createDocument(cxoSodaDatabase*,
PyObject*, PyObject*);
static PyObject *cxoSodaDatabase_getCollectionNames(cxoSodaDatabase*,
PyObject*, PyObject*);
static PyObject *cxoSodaDatabase_openCollection(cxoSodaDatabase*, PyObject*);
//-----------------------------------------------------------------------------
// declaration of methods for Python type "SodaDatabase"
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "createCollection", (PyCFunction) cxoSodaDatabase_createCollection,
METH_VARARGS | METH_KEYWORDS },
{ "createDocument", (PyCFunction) cxoSodaDatabase_createDocument,
METH_VARARGS | METH_KEYWORDS },
{ "getCollectionNames", (PyCFunction) cxoSodaDatabase_getCollectionNames,
METH_VARARGS | METH_KEYWORDS },
{ "openCollection", (PyCFunction) cxoSodaDatabase_openCollection, METH_O },
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declarations
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeSodaDatabase = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.SodaDatabase", // tp_name
sizeof(cxoSodaDatabase), // tp_basicsize
0, // tp_itemsize
(destructor) cxoSodaDatabase_free, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
(reprfunc) cxoSodaDatabase_repr, // 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
cxoMethods, // tp_methods
0, // 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
};
//-----------------------------------------------------------------------------
// cxoSodaDatabase_new()
// Create a new SODA database object.
//-----------------------------------------------------------------------------
cxoSodaDatabase *cxoSodaDatabase_new(cxoConnection *connection)
{
cxoSodaDatabase *db;
PyObject *module;
// load JSON dump/load functions, if needed
if (!cxoJsonDumpFunction || !cxoJsonLoadFunction) {
module = PyImport_ImportModule("json");
if (!module)
return NULL;
if (!cxoJsonDumpFunction) {
cxoJsonDumpFunction = PyObject_GetAttrString(module, "dumps");
if (!cxoJsonDumpFunction)
return NULL;
}
if (!cxoJsonLoadFunction) {
cxoJsonLoadFunction = PyObject_GetAttrString(module, "loads");
if (!cxoJsonLoadFunction)
return NULL;
}
}
// create SODA database object
db = (cxoSodaDatabase*)
cxoPyTypeSodaDatabase.tp_alloc(&cxoPyTypeSodaDatabase, 0);
if (!db)
return NULL;
if (dpiConn_getSodaDb(connection->handle, &db->handle) < 0) {
Py_DECREF(db);
cxoError_raiseAndReturnNull();
return NULL;
}
Py_INCREF(connection);
db->connection = connection;
return db;
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_free()
// Free the memory associated with a SODA database.
//-----------------------------------------------------------------------------
static void cxoSodaDatabase_free(cxoSodaDatabase *db)
{
if (db->handle) {
dpiSodaDb_release(db->handle);
db->handle = NULL;
}
Py_CLEAR(db->connection);
Py_TYPE(db)->tp_free((PyObject*) db);
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_repr()
// Return a string representation of a SODA database.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDatabase_repr(cxoSodaDatabase *db)
{
PyObject *connectionRepr, *module, *name, *result;
connectionRepr = PyObject_Repr((PyObject*) db->connection);
if (!connectionRepr)
return NULL;
if (cxoUtils_getModuleAndName(Py_TYPE(db), &module, &name) < 0) {
Py_DECREF(connectionRepr);
return NULL;
}
result = cxoUtils_formatString("<%s.%s on %s>",
PyTuple_Pack(3, module, name, connectionRepr));
Py_DECREF(module);
Py_DECREF(name);
Py_DECREF(connectionRepr);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_createCollection()
// Create a SODA collection and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDatabase_createCollection(cxoSodaDatabase *db,
PyObject *args, PyObject *keywordArgs)
{
static char *keywordList[] = { "name", "metadata", "mapMode", NULL };
PyObject *nameObj, *metadataObj, *mapModeObj;
cxoBuffer nameBuffer, metadataBuffer;
cxoSodaCollection *coll;
const char *encoding;
dpiSodaColl *handle;
int status, mapMode;
uint32_t flags;
// parse arguments
nameObj = metadataObj = mapModeObj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O|OO", keywordList,
&nameObj, &metadataObj, &mapModeObj))
return NULL;
encoding = db->connection->encodingInfo.encoding;
if (cxoBuffer_fromObject(&nameBuffer, nameObj, encoding) < 0)
return NULL;
if (cxoUtils_processJsonArg(metadataObj, &metadataBuffer) < 0) {
cxoBuffer_clear(&nameBuffer);
return NULL;
}
if (cxoUtils_getBooleanValue(mapModeObj, 0, &mapMode) < 0) {
cxoBuffer_clear(&nameBuffer);
cxoBuffer_clear(&metadataBuffer);
return NULL;
}
// create collection
if (cxoConnection_getSodaFlags(db->connection, &flags) < 0)
return NULL;
if (mapMode)
flags |= DPI_SODA_FLAGS_CREATE_COLL_MAP;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaDb_createCollection(db->handle, nameBuffer.ptr,
nameBuffer.size, metadataBuffer.ptr, metadataBuffer.size, flags,
&handle);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&nameBuffer);
cxoBuffer_clear(&metadataBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
coll = cxoSodaCollection_new(db, handle);
if (!coll) {
dpiSodaColl_release(handle);
return NULL;
}
return (PyObject*) coll;
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_createDocument()
// Create a SODA document with the specified key, content and media type.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDatabase_createDocument(cxoSodaDatabase *db,
PyObject *args, PyObject *keywordArgs)
{
static char *keywordList[] = { "content", "key", "mediaType", NULL };
cxoBuffer contentBuffer, keyBuffer, mediaTypeBuffer;
PyObject *contentObj, *keyObj, *mediaTypeObj;
const char *encoding;
dpiSodaDoc *doc;
int status;
// parse arguments
keyObj = mediaTypeObj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "O|OO", keywordList,
&contentObj, &keyObj, &mediaTypeObj))
return NULL;
// content must be converted to string if it is a dictionary
if (PyDict_Check(contentObj)) {
contentObj = PyObject_CallFunctionObjArgs(cxoJsonDumpFunction,
contentObj, NULL);
if (!contentObj)
return NULL;
}
// get buffers for each of the content, key and media type parameters
if (cxoUtils_processJsonArg(contentObj, &contentBuffer) < 0)
return NULL;
encoding = db->connection->encodingInfo.encoding;
if (cxoBuffer_fromObject(&keyBuffer, keyObj, encoding) < 0) {
cxoBuffer_clear(&contentBuffer);
return NULL;
}
if (cxoBuffer_fromObject(&mediaTypeBuffer, mediaTypeObj, encoding) < 0) {
cxoBuffer_clear(&contentBuffer);
cxoBuffer_clear(&keyBuffer);
return NULL;
}
// create SODA document
status = dpiSodaDb_createDocument(db->handle, keyBuffer.ptr,
keyBuffer.size, contentBuffer.ptr, contentBuffer.size,
mediaTypeBuffer.ptr, mediaTypeBuffer.size, DPI_SODA_FLAGS_DEFAULT,
&doc);
cxoBuffer_clear(&contentBuffer);
cxoBuffer_clear(&keyBuffer);
cxoBuffer_clear(&mediaTypeBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
return (PyObject*) cxoSodaDoc_new(db, doc);
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_getCollectionNames()
// Return a list of the names of the collections found in the database.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDatabase_getCollectionNames(cxoSodaDatabase *db,
PyObject *args, PyObject *keywordArgs)
{
static char *keywordList[] = { "startName", "limit", NULL };
PyObject *startName, *result, *temp;
dpiSodaCollNames collNames;
cxoBuffer startNameBuffer;
uint32_t limit, i, flags;
const char *encoding;
int status;
// parse arguments
limit = 0;
startName = NULL;
if (!PyArg_ParseTupleAndKeywords(args, keywordArgs, "|Oi", keywordList,
&startName, &limit))
return NULL;
// get collection names from the database
encoding = db->connection->encodingInfo.encoding;
if (cxoBuffer_fromObject(&startNameBuffer, startName, encoding) < 0)
return NULL;
if (cxoConnection_getSodaFlags(db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaDb_getCollectionNames(db->handle,
(const char*) startNameBuffer.ptr, startNameBuffer.size, limit,
flags, &collNames);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&startNameBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
// transform results into a Python list
result = PyList_New(collNames.numNames);
if (!result)
return NULL;
for (i = 0; i < collNames.numNames; i++) {
temp = cxoPyString_fromEncodedString(collNames.names[i],
collNames.nameLengths[i], encoding, NULL);
if (!temp) {
Py_DECREF(result);
return NULL;
}
PyList_SET_ITEM(result, i, temp);
}
if (dpiSodaDb_freeCollectionNames(db->handle, &collNames) < 0) {
Py_DECREF(result);
return cxoError_raiseAndReturnNull();
}
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaDatabase_openCollection()
// Open a SODA collection and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDatabase_openCollection(cxoSodaDatabase *db,
PyObject *nameObj)
{
cxoSodaCollection *coll;
cxoBuffer nameBuffer;
dpiSodaColl *handle;
uint32_t flags;
int status;
// open collection
if (cxoBuffer_fromObject(&nameBuffer, nameObj,
db->connection->encodingInfo.encoding) < 0)
return NULL;
if (cxoConnection_getSodaFlags(db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaDb_openCollection(db->handle, nameBuffer.ptr,
nameBuffer.size, flags, &handle);
Py_END_ALLOW_THREADS
cxoBuffer_clear(&nameBuffer);
if (status < 0)
return cxoError_raiseAndReturnNull();
if (!handle)
Py_RETURN_NONE;
coll = cxoSodaCollection_new(db, handle);
if (!coll) {
dpiSodaColl_release(handle);
return NULL;
}
return (PyObject*) coll;
}

309
src/cxoSodaDoc.c Normal file
View File

@ -0,0 +1,309 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoSodaDoc.c
// Defines the routines for handling SODA documents.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// Declaration of functions
//-----------------------------------------------------------------------------
static void cxoSodaDoc_free(cxoSodaDoc*);
static PyObject *cxoSodaDoc_repr(cxoSodaDoc*);
static PyObject *cxoSodaDoc_getCreatedOn(cxoSodaDoc*, void*);
static PyObject *cxoSodaDoc_getKey(cxoSodaDoc*, void*);
static PyObject *cxoSodaDoc_getLastModified(cxoSodaDoc*, void*);
static PyObject *cxoSodaDoc_getMediaType(cxoSodaDoc*, void*);
static PyObject *cxoSodaDoc_getVersion(cxoSodaDoc*, void*);
static PyObject *cxoSodaDoc_getContent(cxoSodaDoc*, PyObject*);
static PyObject *cxoSodaDoc_getContentAsBytes(cxoSodaDoc*, PyObject*);
static PyObject *cxoSodaDoc_getContentAsString(cxoSodaDoc*, PyObject*);
//-----------------------------------------------------------------------------
// declaration of methods
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "getContent", (PyCFunction) cxoSodaDoc_getContent, METH_NOARGS },
{ "getContentAsBytes", (PyCFunction) cxoSodaDoc_getContentAsBytes,
METH_NOARGS },
{ "getContentAsString", (PyCFunction) cxoSodaDoc_getContentAsString,
METH_NOARGS },
{ NULL }
};
//-----------------------------------------------------------------------------
// declaration of calculated members
//-----------------------------------------------------------------------------
static PyGetSetDef cxoCalcMembers[] = {
{ "createdOn", (getter) cxoSodaDoc_getCreatedOn, 0, 0, 0 },
{ "key", (getter) cxoSodaDoc_getKey, 0, 0, 0 },
{ "lastModified", (getter) cxoSodaDoc_getLastModified, 0, 0, 0 },
{ "mediaType", (getter) cxoSodaDoc_getMediaType, 0, 0, 0 },
{ "version", (getter) cxoSodaDoc_getVersion, 0, 0, 0 },
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declarations
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeSodaDoc = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.SodaDoc", // tp_name
sizeof(cxoSodaDoc), // tp_basicsize
0, // tp_itemsize
(destructor) cxoSodaDoc_free, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
(reprfunc) cxoSodaDoc_repr, // 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
cxoMethods, // tp_methods
0, // tp_members
cxoCalcMembers, // 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
};
//-----------------------------------------------------------------------------
// cxoSodaDoc_new()
// Create a new SODA document.
//-----------------------------------------------------------------------------
cxoSodaDoc *cxoSodaDoc_new(cxoSodaDatabase *db, dpiSodaDoc *handle)
{
cxoSodaDoc *doc;
doc = (cxoSodaDoc*) cxoPyTypeSodaDoc.tp_alloc(&cxoPyTypeSodaDoc, 0);
if (!doc) {
dpiSodaDoc_release(handle);
return NULL;
}
Py_INCREF(db);
doc->db = db;
doc->handle = handle;
return doc;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_free()
// Free the memory associated with a SODA document.
//-----------------------------------------------------------------------------
static void cxoSodaDoc_free(cxoSodaDoc *doc)
{
if (doc->handle) {
dpiSodaDoc_release(doc->handle);
doc->handle = NULL;
}
Py_CLEAR(doc->db);
Py_TYPE(doc)->tp_free((PyObject*) doc);
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_repr()
// Return a string representation of a SODA document.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_repr(cxoSodaDoc *doc)
{
PyObject *module, *name, *result, *keyObj;
uint32_t keyLength;
const char *key;
if (dpiSodaDoc_getKey(doc->handle, &key, &keyLength) < 0)
return cxoError_raiseAndReturnNull();
keyObj = cxoPyString_fromEncodedString(key, keyLength,
doc->db->connection->encodingInfo.encoding, NULL);
if (!keyObj)
return NULL;
if (cxoUtils_getModuleAndName(Py_TYPE(doc), &module, &name) < 0) {
Py_DECREF(keyObj);
return NULL;
}
result = cxoUtils_formatString("<%s.%s with key %s>",
PyTuple_Pack(3, module, name, keyObj));
Py_DECREF(module);
Py_DECREF(name);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getCreatedOn()
// Retrieve the time the SODA document was created, as a string in ISO 8601
// format.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getCreatedOn(cxoSodaDoc *doc, void *unused)
{
uint32_t valueLength;
const char *value;
if (dpiSodaDoc_getCreatedOn(doc->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
if (valueLength > 0)
return cxoPyString_fromEncodedString(value, valueLength,
doc->db->connection->encodingInfo.encoding, NULL);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getKey()
// Retrieve the key for the SODA document.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getKey(cxoSodaDoc *doc, void *unused)
{
uint32_t valueLength;
const char *value;
if (dpiSodaDoc_getKey(doc->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
if (valueLength > 0)
return cxoPyString_fromEncodedString(value, valueLength,
doc->db->connection->encodingInfo.encoding, NULL);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getLastModified()
// Retrieve the time the SODA document was last modified, as a string in ISO
// 8601 format.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getLastModified(cxoSodaDoc *doc, void *unused)
{
uint32_t valueLength;
const char *value;
if (dpiSodaDoc_getLastModified(doc->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
if (valueLength > 0)
return cxoPyString_fromEncodedString(value, valueLength,
doc->db->connection->encodingInfo.encoding, NULL);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getMediaType()
// Retrieve the media type of the SODA document.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getMediaType(cxoSodaDoc *doc, void *unused)
{
uint32_t valueLength;
const char *value;
if (dpiSodaDoc_getMediaType(doc->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
if (valueLength > 0)
return cxoPyString_fromEncodedString(value, valueLength,
doc->db->connection->encodingInfo.encoding, NULL);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getVersion()
// Retrieve the version for the SODA document.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getVersion(cxoSodaDoc *doc, void *unused)
{
uint32_t valueLength;
const char *value;
if (dpiSodaDoc_getVersion(doc->handle, &value, &valueLength) < 0)
return cxoError_raiseAndReturnNull();
if (valueLength > 0)
return cxoPyString_fromEncodedString(value, valueLength,
doc->db->connection->encodingInfo.encoding, NULL);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getContent()
// Get the content from the document and return a Python object.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getContent(cxoSodaDoc *doc, PyObject *args)
{
PyObject *str, *result;
str = cxoSodaDoc_getContentAsString(doc, args);
if (!str)
return NULL;
if (str == Py_None)
return str;
result = PyObject_CallFunctionObjArgs(cxoJsonLoadFunction, str, NULL);
Py_DECREF(str);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getContentAsBytes()
// Get the content from the document and return a bytes object.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getContentAsBytes(cxoSodaDoc *doc, PyObject *args)
{
const char *content, *encoding;
uint32_t contentLength;
if (dpiSodaDoc_getContent(doc->handle, &content, &contentLength,
&encoding) < 0)
return cxoError_raiseAndReturnNull();
if (contentLength > 0)
return PyBytes_FromStringAndSize(content, contentLength);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDoc_getContentAsString()
// Get the content from the document and return a string.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDoc_getContentAsString(cxoSodaDoc *doc, PyObject *args)
{
const char *content, *encoding;
uint32_t contentLength;
if (dpiSodaDoc_getContent(doc->handle, &content, &contentLength,
&encoding) < 0)
return cxoError_raiseAndReturnNull();
if (contentLength > 0)
return PyUnicode_Decode(content, contentLength, encoding, NULL);
Py_RETURN_NONE;
}

184
src/cxoSodaDocCursor.c Normal file
View File

@ -0,0 +1,184 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoSodaDocCursor.c
// Defines the routines for handling SODA document cursors. These cursors
// permit iterating over the documents that match the criteria that was
// specified by the user.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// Declaration of functions
//-----------------------------------------------------------------------------
static void cxoSodaDocCursor_free(cxoSodaDocCursor*);
static PyObject *cxoSodaDocCursor_repr(cxoSodaDocCursor*);
static PyObject *cxoSodaDocCursor_getIter(cxoSodaDocCursor*);
static PyObject *cxoSodaDocCursor_getNext(cxoSodaDocCursor*);
static PyObject *cxoSodaDocCursor_close(cxoSodaDocCursor*, PyObject*);
//-----------------------------------------------------------------------------
// declaration of methods
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "close", (PyCFunction) cxoSodaDocCursor_close, METH_NOARGS },
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declarations
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeSodaDocCursor = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.SodaDocCursor", // tp_name
sizeof(cxoSodaDocCursor), // tp_basicsize
0, // tp_itemsize
(destructor) cxoSodaDocCursor_free, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
(reprfunc) cxoSodaDocCursor_repr, // 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
(getiterfunc) cxoSodaDocCursor_getIter, // tp_iter
(iternextfunc) cxoSodaDocCursor_getNext, // tp_iternext
cxoMethods, // tp_methods
0, // 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
};
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_new()
// Create a new SODA document cursor.
//-----------------------------------------------------------------------------
cxoSodaDocCursor *cxoSodaDocCursor_new(cxoSodaDatabase *db,
dpiSodaDocCursor *handle)
{
cxoSodaDocCursor *cursor;
cursor = (cxoSodaDocCursor*)
cxoPyTypeSodaDocCursor.tp_alloc(&cxoPyTypeSodaDocCursor, 0);
if (!cursor) {
dpiSodaDocCursor_release(handle);
return NULL;
}
Py_INCREF(db);
cursor->db = db;
cursor->handle = handle;
return cursor;
}
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_free()
// Free the memory associated with a SODA document cursor.
//-----------------------------------------------------------------------------
static void cxoSodaDocCursor_free(cxoSodaDocCursor *cursor)
{
if (cursor->handle) {
dpiSodaDocCursor_release(cursor->handle);
cursor->handle = NULL;
}
Py_CLEAR(cursor->db);
Py_TYPE(cursor)->tp_free((PyObject*) cursor);
}
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_repr()
// Return a string representation of a SODA document cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDocCursor_repr(cxoSodaDocCursor *cursor)
{
PyObject *module, *name, *result;
if (cxoUtils_getModuleAndName(Py_TYPE(cursor), &module, &name) < 0)
return NULL;
result = cxoUtils_formatString("<%s.%s>", PyTuple_Pack(2, module, name));
Py_DECREF(module);
Py_DECREF(name);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_close()
// Create a SODA collection and return it.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDocCursor_close(cxoSodaDocCursor *cursor,
PyObject *args)
{
if (dpiSodaDocCursor_close(cursor->handle) < 0)
return cxoError_raiseAndReturnNull();
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_getIter()
// Return a reference to the cursor which supports the iterator protocol.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDocCursor_getIter(cxoSodaDocCursor *cursor)
{
Py_INCREF(cursor);
return (PyObject*) cursor;
}
//-----------------------------------------------------------------------------
// cxoSodaDocCursor_getNext()
// Return the next document from the cursor.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaDocCursor_getNext(cxoSodaDocCursor *cursor)
{
dpiSodaDoc *handle;
cxoSodaDoc *doc;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(cursor->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaDocCursor_getNext(cursor->handle, flags, &handle);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
if (!handle)
return NULL;
doc = cxoSodaDoc_new(cursor->db, handle);
if (!doc)
return NULL;
return (PyObject*) doc;
}

564
src/cxoSodaOperation.c Normal file
View File

@ -0,0 +1,564 @@
//-----------------------------------------------------------------------------
// Copyright 2018, Oracle and/or its affiliates. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// cxoSodaOperation.c
// Defines the routines for the various operations performed on SODA
// collections.
//-----------------------------------------------------------------------------
#include "cxoModule.h"
//-----------------------------------------------------------------------------
// Declaration of functions
//-----------------------------------------------------------------------------
static void cxoSodaOperation_free(cxoSodaOperation*);
static PyObject *cxoSodaOperation_repr(cxoSodaOperation*);
static PyObject *cxoSodaOperation_filter(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_key(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_keys(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_limit(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_skip(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_version(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_count(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_getCursor(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_getDocuments(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_getOne(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_remove(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_replaceOne(cxoSodaOperation*, PyObject*);
static PyObject *cxoSodaOperation_replaceOneAndGet(cxoSodaOperation*,
PyObject*);
//-----------------------------------------------------------------------------
// declaration of methods for Python type "SodaOperation"
//-----------------------------------------------------------------------------
static PyMethodDef cxoMethods[] = {
{ "filter", (PyCFunction) cxoSodaOperation_filter, METH_O },
{ "key", (PyCFunction) cxoSodaOperation_key, METH_O },
{ "keys", (PyCFunction) cxoSodaOperation_keys, METH_O },
{ "limit", (PyCFunction) cxoSodaOperation_limit, METH_O },
{ "skip", (PyCFunction) cxoSodaOperation_skip, METH_O },
{ "version", (PyCFunction) cxoSodaOperation_version, METH_O },
{ "count", (PyCFunction) cxoSodaOperation_count, METH_NOARGS },
{ "getCursor", (PyCFunction) cxoSodaOperation_getCursor, METH_NOARGS },
{ "getDocuments", (PyCFunction) cxoSodaOperation_getDocuments,
METH_NOARGS },
{ "getOne", (PyCFunction) cxoSodaOperation_getOne, METH_NOARGS },
{ "remove", (PyCFunction) cxoSodaOperation_remove, METH_NOARGS },
{ "replaceOne", (PyCFunction) cxoSodaOperation_replaceOne, METH_O },
{ "replaceOneAndGet", (PyCFunction) cxoSodaOperation_replaceOneAndGet,
METH_O },
{ NULL }
};
//-----------------------------------------------------------------------------
// Python type declarations
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeSodaOperation = {
PyVarObject_HEAD_INIT(NULL, 0)
"cx_Oracle.SodaOperation", // tp_name
sizeof(cxoSodaOperation), // tp_basicsize
0, // tp_itemsize
(destructor) cxoSodaOperation_free, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
(reprfunc) cxoSodaOperation_repr, // 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
cxoMethods, // tp_methods
0, // 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
};
//-----------------------------------------------------------------------------
// cxoSodaOperation_clearKeys()
// Clear the keys set on the operation object, if applicable.
//-----------------------------------------------------------------------------
void cxoSodaOperation_clearKeys(cxoSodaOperation *op)
{
uint32_t i;
if (op->keyBuffers) {
for (i = 0; i < op->numKeyBuffers; i++)
cxoBuffer_clear(&op->keyBuffers[i]);
PyMem_Free(op->keyBuffers);
op->keyBuffers = NULL;
}
op->numKeyBuffers = 0;
op->options.numKeys = 0;
if (op->options.keys) {
PyMem_Free(op->options.keys);
op->options.keys = NULL;
}
if (op->options.keyLengths) {
PyMem_Free(op->options.keyLengths);
op->options.keyLengths = NULL;
}
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_new()
// Create a new SODA operation object.
//-----------------------------------------------------------------------------
cxoSodaOperation *cxoSodaOperation_new(cxoSodaCollection *coll)
{
cxoSodaOperation *op;
op = (cxoSodaOperation*)
cxoPyTypeSodaOperation.tp_alloc(&cxoPyTypeSodaOperation, 0);
if (!op)
return NULL;
if (dpiContext_initSodaOperOptions(cxoDpiContext, &op->options) < 0) {
Py_DECREF(op);
return NULL;
}
cxoBuffer_init(&op->keyBuffer);
cxoBuffer_init(&op->versionBuffer);
cxoBuffer_init(&op->filterBuffer);
Py_INCREF(coll);
op->coll = coll;
return op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_free()
// Free the memory associated with a SODA operation object.
//-----------------------------------------------------------------------------
static void cxoSodaOperation_free(cxoSodaOperation *op)
{
cxoSodaOperation_clearKeys(op);
cxoBuffer_clear(&op->keyBuffer);
cxoBuffer_clear(&op->versionBuffer);
cxoBuffer_clear(&op->filterBuffer);
Py_CLEAR(op->coll);
Py_TYPE(op)->tp_free((PyObject*) op);
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_repr()
// Return a string representation of a SODA operation object.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_repr(cxoSodaOperation *op)
{
PyObject *collRepr, *module, *name, *result;
collRepr = PyObject_Repr((PyObject*) op->coll);
if (!collRepr)
return NULL;
if (cxoUtils_getModuleAndName(Py_TYPE(op), &module, &name) < 0) {
Py_DECREF(collRepr);
return NULL;
}
result = cxoUtils_formatString("<%s.%s on %s>",
PyTuple_Pack(3, module, name, collRepr));
Py_DECREF(module);
Py_DECREF(name);
Py_DECREF(collRepr);
return result;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_filter()
// Set the filter to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_filter(cxoSodaOperation *op,
PyObject *filterObj)
{
cxoBuffer_clear(&op->filterBuffer);
if (PyDict_Check(filterObj)) {
filterObj = PyObject_CallFunctionObjArgs(cxoJsonDumpFunction,
filterObj, NULL);
if (!filterObj)
return NULL;
}
if (cxoBuffer_fromObject(&op->filterBuffer, filterObj,
op->coll->db->connection->encodingInfo.encoding) < 0)
return NULL;
op->options.filter = op->filterBuffer.ptr;
op->options.filterLength = op->filterBuffer.size;
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_key()
// Set the key to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_key(cxoSodaOperation *op,
PyObject *keyObj)
{
cxoBuffer_clear(&op->keyBuffer);
if (cxoBuffer_fromObject(&op->keyBuffer, keyObj,
op->coll->db->connection->encodingInfo.encoding) < 0)
return NULL;
op->options.key = op->keyBuffer.ptr;
op->options.keyLength = op->keyBuffer.size;
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_keys()
// Set the keys to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_keys(cxoSodaOperation *op,
PyObject *keysObj)
{
Py_ssize_t size, i;
PyObject *element;
// determine size of sequence passed to method
size = PySequence_Size(keysObj);
if (PyErr_Occurred())
return NULL;
// clear original keys, if applicable
cxoSodaOperation_clearKeys(op);
// zero-length arrays don't need any further processing
if (size == 0) {
Py_INCREF(op);
return (PyObject*) op;
}
// initialize memory
op->keyBuffers = PyMem_Malloc(size * sizeof(cxoBuffer));
if (!op->keyBuffers)
return NULL;
op->numKeyBuffers = (uint32_t) size;
for (i = 0; i < size; i++)
cxoBuffer_init(&op->keyBuffers[i]);
op->options.keys = PyMem_Malloc(size * sizeof(const char *));
op->options.keyLengths = PyMem_Malloc(size * sizeof(uint32_t));
if (!op->options.keys || !op->options.keyLengths) {
cxoSodaOperation_clearKeys(op);
return NULL;
}
op->options.numKeys = op->numKeyBuffers;
// process each of the elements of the sequence
for (i = 0; i < size; i++) {
element = PySequence_GetItem(keysObj, i);
if (!element) {
cxoSodaOperation_clearKeys(op);
return NULL;
}
if (cxoBuffer_fromObject(&op->keyBuffers[i], element,
op->coll->db->connection->encodingInfo.encoding) < 0) {
Py_DECREF(element);
cxoSodaOperation_clearKeys(op);
return NULL;
}
Py_DECREF(element);
op->options.keys[i] = op->keyBuffers[i].ptr;
op->options.keyLengths[i] = op->keyBuffers[i].size;
}
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_limit()
// Set the limit value to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_limit(cxoSodaOperation *op,
PyObject *limitObj)
{
op->options.limit = PyLong_AsUnsignedLong(limitObj);
if (PyErr_Occurred())
return NULL;
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_skip()
// Set the skip value to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_skip(cxoSodaOperation *op,
PyObject *skipObj)
{
op->options.skip = PyLong_AsUnsignedLong(skipObj);
if (PyErr_Occurred())
return NULL;
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_version()
// Set the version to be used for the operation.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_version(cxoSodaOperation *op,
PyObject *versionObj)
{
cxoBuffer_clear(&op->versionBuffer);
if (cxoBuffer_fromObject(&op->versionBuffer, versionObj,
op->coll->db->connection->encodingInfo.encoding) < 0)
return NULL;
op->options.version = op->versionBuffer.ptr;
op->options.versionLength = op->versionBuffer.size;
Py_INCREF(op);
return (PyObject*) op;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_count()
// Returns the number of documents that match the criteria.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_count(cxoSodaOperation *op, PyObject *args)
{
uint64_t count;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_getDocCount(op->coll->handle, &op->options, flags,
&count);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
return PyLong_FromUnsignedLongLong(count);
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_getCursor()
// Returns a document cursor which can be used to iterate over the documents
// that match the criteria.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_getCursor(cxoSodaOperation *op,
PyObject *args)
{
dpiSodaDocCursor *handle;
cxoSodaDocCursor *cursor;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_find(op->coll->handle, &op->options, flags, &handle);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
cursor = cxoSodaDocCursor_new(op->coll->db, handle);
if (!cursor)
return NULL;
return (PyObject*) cursor;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_getDocuments()
// Returns a list of documents that match the criteria.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_getDocuments(cxoSodaOperation *op,
PyObject *args)
{
dpiSodaDocCursor *cursor;
PyObject *docObj;
dpiSodaDoc *doc;
PyObject *list;
uint32_t flags;
int status;
// acquire cursor
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_find(op->coll->handle, &op->options, flags, &cursor);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
// iterate cursor and create array of documents
list = PyList_New(0);
if (!list) {
dpiSodaDocCursor_release(cursor);
return NULL;
}
while (1) {
Py_BEGIN_ALLOW_THREADS
status = dpiSodaDocCursor_getNext(cursor, flags, &doc);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
dpiSodaDocCursor_release(cursor);
return NULL;
}
if (!doc)
break;
docObj = (PyObject*) cxoSodaDoc_new(op->coll->db, doc);
if (!docObj) {
dpiSodaDocCursor_release(cursor);
return NULL;
}
if (PyList_Append(list, docObj) < 0) {
Py_DECREF(docObj);
dpiSodaDocCursor_release(cursor);
return NULL;
}
Py_DECREF(docObj);
}
dpiSodaDocCursor_release(cursor);
return list;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_getOne()
// Returns a single document that matches the criteria or None if no
// documents match the criteria.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_getOne(cxoSodaOperation *op, PyObject *args)
{
dpiSodaDoc *handle;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_findOne(op->coll->handle, &op->options, flags,
&handle);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
if (handle)
return (PyObject*) cxoSodaDoc_new(op->coll->db, handle);
Py_RETURN_NONE;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_remove()
// Remove all of the documents that match the criteria.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_remove(cxoSodaOperation *op, PyObject *args)
{
uint64_t count;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_remove(op->coll->handle, &op->options, flags, &count);
Py_END_ALLOW_THREADS
if (status < 0)
return cxoError_raiseAndReturnNull();
return PyLong_FromUnsignedLongLong(count);
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_replaceOne()
// Replace a single document in the collection with the provided replacement.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_replaceOne(cxoSodaOperation *op,
PyObject *arg)
{
int status, replaced;
cxoSodaDoc *doc;
uint32_t flags;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
if (cxoUtils_processSodaDocArg(op->coll->db, arg, &doc) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_replaceOne(op->coll->handle, &op->options,
doc->handle, flags, &replaced, NULL);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
Py_DECREF(doc);
return NULL;
}
Py_DECREF(doc);
if (replaced)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
//-----------------------------------------------------------------------------
// cxoSodaOperation_replaceOneAndGet()
// Replace a single document in the collection with the provided replacement
// and return a document (without the content) to the caller.
//-----------------------------------------------------------------------------
static PyObject *cxoSodaOperation_replaceOneAndGet(cxoSodaOperation *op,
PyObject *arg)
{
dpiSodaDoc *replacedDoc;
cxoSodaDoc *doc;
uint32_t flags;
int status;
if (cxoConnection_getSodaFlags(op->coll->db->connection, &flags) < 0)
return NULL;
if (cxoUtils_processSodaDocArg(op->coll->db, arg, &doc) < 0)
return NULL;
Py_BEGIN_ALLOW_THREADS
status = dpiSodaColl_replaceOne(op->coll->handle, &op->options,
doc->handle, flags, NULL, &replacedDoc);
Py_END_ALLOW_THREADS
if (status < 0) {
cxoError_raiseAndReturnNull();
Py_DECREF(doc);
return NULL;
}
Py_DECREF(doc);
if (replacedDoc)
return (PyObject*) cxoSodaDoc_new(op->coll->db, replacedDoc);
Py_RETURN_NONE;
}

View File

@ -131,3 +131,73 @@ int cxoUtils_initializeDPI(void)
return 0;
}
//-----------------------------------------------------------------------------
// cxoUtils_processJsonArg()
// Process the argument which is expected to be either a string or bytes, or
// a dictionary or list which is converted to a string via the json.dumps()
// method. All strings are encoded to UTF-8 which is what SODA expects.
//-----------------------------------------------------------------------------
int cxoUtils_processJsonArg(PyObject *arg, cxoBuffer *buffer)
{
int converted = 0;
if (arg && (PyDict_Check(arg) || PyList_Check(arg))) {
arg = PyObject_CallFunctionObjArgs(cxoJsonDumpFunction, arg, NULL);
if (!arg)
return -1;
converted = 1;
}
if (cxoBuffer_fromObject(buffer, arg, "UTF-8") < 0)
return -1;
if (converted)
Py_DECREF(arg);
return 0;
}
//-----------------------------------------------------------------------------
// cxoUtils_processSodaDocArg()
// Process a SODA document argument. This is expectd to be an actual SODA
// document object or a dictionary. If the argument refers to a dictionary or
// list, a new SODA document will be created with the given content and without
// a key or media type specified.
//-----------------------------------------------------------------------------
int cxoUtils_processSodaDocArg(cxoSodaDatabase *db, PyObject *arg,
cxoSodaDoc **doc)
{
dpiSodaDoc *handle;
cxoBuffer buffer;
if (PyObject_TypeCheck(arg, &cxoPyTypeSodaDoc)) {
Py_INCREF(arg);
*doc = (cxoSodaDoc*) arg;
} else if (PyDict_Check(arg) || PyList_Check(arg)) {
arg = PyObject_CallFunctionObjArgs(cxoJsonDumpFunction, arg, NULL);
if (!arg)
return -1;
if (cxoBuffer_fromObject(&buffer, arg, "UTF-8") < 0) {
Py_DECREF(arg);
return -1;
}
Py_DECREF(arg);
if (dpiSodaDb_createDocument(db->handle, NULL, 0, buffer.ptr,
buffer.size, NULL, 0, DPI_SODA_FLAGS_DEFAULT, &handle) < 0) {
cxoError_raiseAndReturnNull();
cxoBuffer_clear(&buffer);
return -1;
}
cxoBuffer_clear(&buffer);
*doc = cxoSodaDoc_new(db, handle);
if (!*doc)
return -1;
} else {
PyErr_SetString(PyExc_TypeError,
"value must be a SODA document or dictionary");
return -1;
}
return 0;
}

194
test/SodaCollection.py Normal file
View File

@ -0,0 +1,194 @@
#------------------------------------------------------------------------------
# Copyright 2018, Oracle and/or its affiliates. All rights reserved.
#------------------------------------------------------------------------------
"""Module for testing Simple Oracle Document Access (SODA) Collections"""
class TestSodaDocuments(BaseTestCase):
def testInvalidJson(self):
"test inserting invalid JSON value into SODA collection"
invalidJson = "{testKey:testValue}"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoInvalidJSON")
doc = sodaDatabase.createDocument(invalidJson)
self.assertRaises(cx_Oracle.IntegrityError, coll.insertOne, doc)
coll.drop()
def testInsertDocuments(self):
"test inserting documents into a SODA collection"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoInsertDocs")
coll.find().remove()
valuesToInsert = [
{ "name" : "George", "age" : 47 },
{ "name" : "Susan", "age" : 39 },
{ "name" : "John", "age" : 50 },
{ "name" : "Jill", "age" : 54 }
]
insertedKeys = []
for value in valuesToInsert:
doc = coll.insertOneAndGet(value)
insertedKeys.append(doc.key)
self.connection.commit()
self.assertEqual(coll.find().count(), len(valuesToInsert))
for key, value in zip(insertedKeys, valuesToInsert):
doc = coll.find().key(key).getOne()
self.assertEqual(doc.getContent(), value)
coll.drop()
def testSkipDocuments(self):
"test skip documents from SODA collection"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoSkipDocs")
coll.find().remove()
valuesToInsert = [
{ "name" : "Matthew", "age" : 28 },
{ "name" : "Martha", "age" : 43 },
{ "name" : "Mark", "age" : 37 },
{ "name" : "Anna", "age" : 62 }
]
for value in valuesToInsert:
coll.insertOne(value)
self.connection.commit()
self.assertEqual(coll.find().skip(1).getOne().getContent(),
valuesToInsert[1])
self.assertEqual(coll.find().skip(3).getOne().getContent(),
valuesToInsert[3])
self.assertEqual(coll.find().skip(4).getOne(), None)
self.assertEqual(coll.find().skip(125).getOne(), None)
def testReplaceDocument(self):
"test replace documents in SODA collection"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoReplaceDoc")
coll.find().remove()
content = {'name': 'John', 'address': {'city': 'Sydney'}}
doc = coll.insertOneAndGet(content)
newContent = {'name': 'John', 'address': {'city':'Melbourne'}}
coll.find().key(doc.key).replaceOne(newContent)
self.connection.commit()
self.assertEqual(coll.find().key(doc.key).getOne().getContent(),
newContent)
coll.drop()
def testSearchDocumentsWithContent(self):
"test search documents with content using $like and $regex"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoSearchDocContent")
coll.find().remove()
data = [
{'name': 'John', 'address': {'city': 'Bangalore'}},
{'name': 'Johnson', 'address': {'city': 'Banaras'}},
{'name': 'Joseph', 'address': {'city': 'Bangalore'}},
{'name': 'Jibin', 'address': {'city': 'Secunderabad'}},
{'name': 'Andrew', 'address': {'city': 'Hyderabad'}},
{'name': 'Matthew', 'address': {'city': 'Mumbai'}}
]
for value in data:
coll.insertOne(value)
self.connection.commit()
filterSpecs = [
({'name': {'$like': 'And%'}}, 1),
({'name': {'$like': 'J%n'}}, 3),
({'name': {'$like': '%hn%'}}, 2),
({'address.city': {'$like': 'Ban%'}}, 3),
({'address.city': {'$like': '%bad'}}, 2),
({'address.city': {'$like': 'Hyderabad'}}, 1),
({'address.city': {'$like': 'China%'}}, 0),
({'name': {'$regex': 'Jo.*'}}, 3),
({'name': {'$regex': '.*[ho]n'}}, 2),
({'name': {'$regex': 'J.*h'}}, 1),
({'address.city': {'$regex': 'Ba.*'}}, 3),
({'address.city': {'$regex': '.*bad'}}, 2),
({'address.city': {'$regex': 'Hyderabad'}}, 1),
({'name': {'$regex': 'Js.*n'}}, 0)
]
for filterSpec, expectedCount in filterSpecs:
self.assertEqual(coll.find().filter(filterSpec).count(),
expectedCount, filterSpec)
coll.drop()
def testDocumentRemove(self):
"test removing documents"
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoRemoveDocs")
coll.find().remove()
data = [
{'name': 'John', 'address': {'city': 'Bangalore'}},
{'name': 'Johnson', 'address': {'city': 'Banaras'}},
{'name': 'Joseph', 'address': {'city': 'Mangalore'}},
{'name': 'Jibin', 'address': {'city': 'Secunderabad'}},
{'name': 'Andrew', 'address': {'city': 'Hyderabad'}},
{'name': 'Matthew', 'address': {'city': 'Mumbai'}}
]
docs = [coll.insertOneAndGet(v) for v in data]
coll.find().key(docs[3].key).remove()
self.assertEqual(coll.find().count(), len(data) - 1)
searchResults = coll.find().filter({'name': {'$like': 'Jibin'}})
self.assertEqual(searchResults.count(), 0)
coll.find().filter({'name': {'$like': 'John%'}}).remove()
self.assertEqual(coll.find().count(), len(data) - 3)
coll.find().filter({'name': {'$regex': 'J.*'}}).remove()
self.assertEqual(coll.find().count(), len(data) - 4)
self.connection.commit()
coll.drop()
def testCreateAndDropIndex(self):
"test create and drop Index"
indexName = "cxoTestIndexes_ix_1"
indexSpec = {
'name': indexName,
'fields': [
{
'path': 'address.city',
'datatype': 'string',
'order': 'asc'
}
]
}
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoTestIndexes")
coll.find().remove()
self.connection.commit()
coll.dropIndex(indexName)
coll.createIndex(indexSpec)
self.assertRaises(cx_Oracle.DatabaseError, coll.createIndex, indexSpec)
self.assertEqual(coll.dropIndex(indexName), True)
self.assertEqual(coll.dropIndex(indexName), False)
coll.drop()
def testGetDocuments(self):
"test getting documents from Collection"
self.connection.autocommit = True
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoTestGetDocs")
coll.find().remove()
data = [
{'name': 'John', 'address': {'city': 'Bangalore'}},
{'name': 'Johnson', 'address': {'city': 'Banaras'}},
{'name': 'Joseph', 'address': {'city': 'Mangalore'}},
{'name': 'Jibin', 'address': {'city': 'Secunderabad'}},
{'name': 'Andrew', 'address': {'city': 'Hyderabad'}}
]
insertedKeys = list(sorted(coll.insertOneAndGet(v).key for v in data))
fetchedKeys = list(sorted(d.key for d in coll.find().getDocuments()))
self.assertEqual(fetchedKeys, insertedKeys)
coll.drop()
def testCursor(self):
"test fetching documents from a cursor"
self.connection.autocommit = True
sodaDatabase = self.connection.getSodaDatabase()
coll = sodaDatabase.createCollection("cxoFindViaCursor")
coll.find().remove()
data = [
{'name': 'John', 'address': {'city': 'Bangalore'}},
{'name': 'Johnson', 'address': {'city': 'Banaras'}},
{'name': 'Joseph', 'address': {'city': 'Mangalore'}},
]
insertedKeys = list(sorted(coll.insertOneAndGet(v).key for v in data))
fetchedKeys = list(sorted(d.key for d in coll.find().getCursor()))
self.assertEqual(fetchedKeys, insertedKeys)
coll.drop()

101
test/SodaDatabase.py Normal file
View File

@ -0,0 +1,101 @@
#------------------------------------------------------------------------------
# Copyright 2018, Oracle and/or its affiliates. All rights reserved.
#------------------------------------------------------------------------------
"""Module for testing Simple Oracle Document Access (SODA) Database"""
import json
class TestSodaCollection(BaseTestCase):
def __dropExistingCollections(self, sodaDatabase):
for name in sodaDatabase.getCollectionNames():
sodaDatabase.openCollection(name).drop()
def __verifyDocument(self, doc, rawContent, strContent=None, content=None,
key=None, mediaType='application/json'):
self.assertEqual(doc.getContentAsBytes(), rawContent)
if strContent is not None:
self.assertEqual(doc.getContentAsString(), strContent)
if content is not None:
self.assertEqual(doc.getContent(), content)
self.assertEqual(doc.key, key)
self.assertEqual(doc.mediaType, mediaType)
def testCreateDocumentWithJson(self):
"test creating documents with JSON data"
sodaDatabase = self.connection.getSodaDatabase()
val = {"testKey1" : "testValue1", "testKey2" : "testValue2" }
strVal = json.dumps(val)
bytesVal = strVal.encode("UTF-8")
key = "MyKey"
mediaType = "text/plain"
doc = sodaDatabase.createDocument(val)
self.__verifyDocument(doc, bytesVal, strVal, val)
doc = sodaDatabase.createDocument(strVal, key)
self.__verifyDocument(doc, bytesVal, strVal, val, key)
doc = sodaDatabase.createDocument(bytesVal, key, mediaType)
self.__verifyDocument(doc, bytesVal, strVal, val, key, mediaType)
def testCreateDocumentWithRaw(self):
"test creating documents with raw data"
sodaDatabase = self.connection.getSodaDatabase()
val = b"<html/>"
key = "MyRawKey"
mediaType = "text/html"
doc = sodaDatabase.createDocument(val)
self.__verifyDocument(doc, val)
doc = sodaDatabase.createDocument(val, key)
self.__verifyDocument(doc, val, key=key)
doc = sodaDatabase.createDocument(val, key, mediaType)
self.__verifyDocument(doc, val, key=key, mediaType=mediaType)
def testGetCollectionNames(self):
"test getting collection names from the database"
sodaDatabase = self.connection.getSodaDatabase()
self.__dropExistingCollections(sodaDatabase)
self.assertEqual(sodaDatabase.getCollectionNames(), [])
names = ["zCol", "dCol", "sCol", "aCol", "gCol"]
sortedNames = list(sorted(names))
for name in names:
sodaDatabase.createCollection(name)
self.assertEqual(sodaDatabase.getCollectionNames(), sortedNames)
self.assertEqual(sodaDatabase.getCollectionNames(limit=2),
sortedNames[:2])
self.assertEqual(sodaDatabase.getCollectionNames("a"), sortedNames)
self.assertEqual(sodaDatabase.getCollectionNames("C"), sortedNames)
self.assertEqual(sodaDatabase.getCollectionNames("b", limit=3),
sortedNames[1:4])
self.assertEqual(sodaDatabase.getCollectionNames("z"),
sortedNames[-1:])
def testOpenCollection(self):
"test opening a collection"
sodaDatabase = self.connection.getSodaDatabase()
self.__dropExistingCollections(sodaDatabase)
coll = sodaDatabase.openCollection("CollectionThatDoesNotExist")
self.assertEqual(coll, None)
createdColl = sodaDatabase.createCollection("cxoTestOpenCollection")
coll = sodaDatabase.openCollection(createdColl.name)
self.assertEqual(coll.name, createdColl.name)
coll.drop()
def testRepr(self):
"test SodaDatabase representation"
con1 = self.connection
con2 = self.getConnection()
sodaDatabase1 = con1.getSodaDatabase()
sodaDatabase2 = con1.getSodaDatabase()
sodaDatabase3 = con2.getSodaDatabase()
self.assertEqual(str(sodaDatabase1), str(sodaDatabase2))
self.assertEqual(str(sodaDatabase2), str(sodaDatabase3))
def testNegative(self):
"test negative cases for SODA database methods"
sodaDatabase = self.connection.getSodaDatabase()
self.assertRaises(TypeError, sodaDatabase.createCollection)
self.assertRaises(TypeError, sodaDatabase.createCollection, 1)
self.assertRaises(cx_Oracle.DatabaseError,
sodaDatabase.createCollection, None)
self.assertRaises(TypeError, sodaDatabase.getCollectionNames, 1)

View File

@ -49,6 +49,19 @@ grant execute on dbms_aqadm to &main_user;
grant execute on dbms_transform to &main_user;
begin
for r in
( select role
from dba_roles
where role in ('SODA_APP')
) loop
execute immediate 'grant ' || r.role || ' to &main_user';
end loop;
end;
/
-- create types
create type &main_user..udt_SubObject as object (
SubNumberValue number,

View File

@ -60,6 +60,9 @@ else:
if clientVersion[:2] >= (12, 1):
moduleNames.append("BooleanVar")
moduleNames.append("Features12_1")
if clientVersion[:2] >= (18, 3):
moduleNames.append("SodaDatabase")
moduleNames.append("SodaCollection")
class BaseTestCase(unittest.TestCase):