//-----------------------------------------------------------------------------
// Copyright (c) 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;
}