Skip to content

Support sub-document and N1QL#6

Open
sublee wants to merge 26 commits intocouchbaselabs:masterfrom
what-studio:lcb-2.7
Open

Support sub-document and N1QL#6
sublee wants to merge 26 commits intocouchbaselabs:masterfrom
what-studio:lcb-2.7

Conversation

@sublee
Copy link

@sublee sublee commented Jun 6, 2017

Hi,

I've been trying to use a Couchbase client library in PyPy on gevent. When I started to try, there were many things broken. Especially, we couldn't use it with latest libcouchbase and the Python library. I already know it was an experimental project.

This patch makes couchbase_ffi work with the latest libcouchbase and the Python library again. The target version is couchbase/couchbase-python-client@afcfb29 over libcouchbase-2.7.5. And it implements the sub-document and N1QL API.

  • Fix the build problem with the latest libocuchbase.
  • Implement the sub-document API.
  • Implement the N1QL API.
  • Fix hang on persist_to or replicate_to option in async mode.
  • Fix hang on HTTP request in async mode. (To call Bucket.flush() or Bucket.design_create())
  • Fix hang on querying views in async mode.
  • Fix a ViewResult parameter mismatch bug.

I've tried to verify my work with some code snippets and the test cases under Couchbase Server 4.6.2-3905 Enterprise Edition (build-3905).

$ pytest -v tests

I don't know how to test gcouchbase with couchbase_ffi by the existing unit tests. If you know, let me know.

There are still many test failures so I'm comparing test results on both Python libaries. In my case, 16 failed, 197 passed, 24 skipped in couchbase-python-cffi; 4 failed, 215 passed, 18 skipped, 3 error in couchbase-python-client.

Some failures look like because of Couchbase Server. For example, SubdocTest::test_create_doc fails on both with LCB_0x4B (generated, catch: CouchbaseInternalError): <Key='create_doc1', RC=0x4B[A badly formatted packet was sent to the server. Please report this in a bug], Operational error, Results=1>.

I was not familiar with your code base. So it may have some problem. Please review my code or give some hint.

sublee added 26 commits June 1, 2017 19:29
The Couchbase Python client version is 2.2.4.
Don't implement mutate_in with mutiple values yet.
It looks like a convention.
Because GView fails with this error:

```
Traceback (most recent call last):
  ...
  File "gcouchbase/bucket.py", line 60, in __iter__
    for row in self._process_payload(rowset):
  File "couchbase/views/iterator.py", line 366, in _process_payload
    return self.row_processor.handle_rows(rows, self._parent, False)
AttributeError: 'GView' object has no attribute '_parent'
```
It's very dirty but works.
It shares similar code between ViewResult and N1QLResult.
But upsert_doc option is refused by the server.  This behavior is same
in CPython.
To pass test case: tests/test_compat.py::SubdocTest_FFI::test_counter_in
@ingenthr ingenthr requested a review from mnunberg June 6, 2017 15:25
@sublee
Copy link
Author

sublee commented Jun 15, 2017

@mnunberg asked me what tests failed at Gitter. Let me show you:

tests/test_compat.py::ViewTest_FFI::test_missing_view <- ../couchbase-python-client/couchbase/tests/cases/view_t.py FAILED
tests/test_compat.py::ViewTest_FFI::test_simple_view <- ../couchbase-python-client/couchbase/tests/cases/view_t.py FAILED
tests/test_compat.py::ViewTest_FFI::test_with_jparams <- ../couchbase-python-client/couchbase/tests/cases/view_t.py FAILED
tests/test_compat.py::ViewTest_FFI::test_with_params <- ../couchbase-python-client/couchbase/tests/cases/view_t.py FAILED
tests/test_compat.py::ViewTest_FFI::test_with_strparam <- ../couchbase-python-client/couchbase/tests/cases/view_t.py FAILED
tests/test_compat.py::TranscoderTest_FFI::test_transcoder_unhashable_keys <- ../couchbase-python-client/couchbase/tests/cases/transcoder_t.py FAILED
tests/test_compat.py::XattrTest_FFI::test_xattrs_basic <- ../couchbase-python-client/couchbase/tests/cases/xattr_t.py FAILED
tests/test_compat.py::SubdocTest_FFI::test_create_doc <- ../couchbase-python-client/couchbase/tests/cases/subdoc_t.py FAILED
tests/test_compat.py::SubdocTest_FFI::test_fulldoc <- ../couchbase-python-client/couchbase/tests/cases/subdoc_t.py FAILED
tests/test_compat.py::SubdocTest_FFI::test_get_count <- ../couchbase-python-client/couchbase/tests/cases/subdoc_t.py FAILED
tests/test_compat.py::BadArgsTest_FFI::test_bad_single <- ../couchbase-python-client/couchbase/tests/cases/badargs_t.py FAILED
tests/test_compat.py::ReplicaGetTest_FFI::test_get_ix <- ../couchbase-python-client/couchbase/tests/cases/rget_t.py FAILED
tests/test_compat.py::ReplicaGetTest_FFI::test_get_kw <- ../couchbase-python-client/couchbase/tests/cases/rget_t.py FAILED
tests/test_compat.py::MutationTokensTest_FFI::test_mutation_state <- ../couchbase-python-client/couchbase/tests/cases/mutationtokens_t.py FAILED
tests/test_compat.py::MutationTokensTest_FFI::test_mutinfo_disabled <- ../couchbase-python-client/couchbase/tests/cases/mutationtokens_t.py FAILED
tests/test_compat.py::MutationTokensTest_FFI::test_mutinfo_enabled <- ../couchbase-python-client/couchbase/tests/cases/mutationtokens_t.py FAILED
tests/test_compat.py::PipelineTest_FFI::test_multi_pipeline <- ../couchbase-python-client/couchbase/tests/cases/pipeline_t.py FAILED
tests/test_compat.py::PipelineTest_FFI::test_pipeline_argerrors <- ../couchbase-python-client/couchbase/tests/cases/pipeline_t.py FAILED
tests/test_compat.py::PipelineTest_FFI::test_pipeline_results <- ../couchbase-python-client/couchbase/tests/cases/pipeline_t.py FAILED
tests/test_compat.py::ConnectionObserveMasterTest_FFI::test_master_observe <- ../couchbase-python-client/couchbase/tests/cases/observe_t.py FAILED
tests/test_compat.py::ObserveTest_FFI::test_multi_observe <- ../couchbase-python-client/couchbase/tests/cases/observe_t.py FAILED
tests/test_compat.py::ClusterTest_FFI::test_query <- ../couchbase-python-client/couchbase/tests/cases/cluster_t.py FAILED
tests/test_compat.py::DesignDocManagementTest_FFI::test_design_headers <- ../couchbase-python-client/couchbase/tests/cases/design_t.py FAILED
tests/test_compat.py::DesignDocManagementTest_FFI::test_design_management <- ../couchbase-python-client/couchbase/tests/cases/design_t.py FAILED
tests/test_compat.py::N1QLTest_FFI::test_emptyrow <- ../couchbase-python-client/couchbase/tests/cases/n1ql_t.py FAILED
tests/test_compat.py::N1QLTest_FFI::test_meta <- ../couchbase-python-client/couchbase/tests/cases/n1ql_t.py FAILED
tests/test_compat.py::N1QLTest_FFI::test_onerow <- ../couchbase-python-client/couchbase/tests/cases/n1ql_t.py FAILED
tests/test_compat.py::EndureTest_FFI::test_embedded_endure_delete <- ../couchbase-python-client/couchbase/tests/cases/endure_t.py FAILED
tests/test_compat.py::EndureTest_FFI::test_embedded_endure_set <- ../couchbase-python-client/couchbase/tests/cases/endure_t.py FAILED
tests/test_compat.py::EndureTest_FFI::test_excessive <- ../couchbase-python-client/couchbase/tests/cases/endure_t.py FAILED
tests/test_compat.py::EndureTest_FFI::test_single_poll <- ../couchbase-python-client/couchbase/tests/cases/endure_t.py FAILED
tests/test_compat.py::SpatialTest_FFI::test_simple_spatial <- ../couchbase-python-client/couchbase/tests/cases/spatial_t.py FAILED
tests/test_compat.py::MiscTest_FFI::test_logging <- ../couchbase-python-client/couchbase/tests/cases/misc_t.py FAILED
tests/test_compat.py::MiscTest_FFI::test_multi_auth <- ../couchbase-python-client/couchbase/tests/cases/misc_t.py FAILED
tests/test_compat.py::StatsTest_FFI::test_stats_with_argument <- ../couchbase-python-client/couchbase/tests/cases/stats_t.py FAILED
tests/test_compat.py::StatsTest_FFI::test_stats_with_argument_list <- ../couchbase-python-client/couchbase/tests/cases/stats_t.py FAILED
tests/test_compat.py::StatsTest_FFI::test_trivial_stats_without_argument <- ../couchbase-python-client/couchbase/tests/cases/stats_t.py FAILED
tests/test_compat.py::ItemTest_FFI::test_invalid_item <- ../couchbase-python-client/couchbase/tests/cases/itmops_t.py FAILED
tests/test_compat.py::ItemTest_FFI::test_pycbc366 <- ../couchbase-python-client/couchbase/tests/cases/itmops_t.py FAILED
tests/test_compat.py::FlushTest_FFI::test_flush <- ../couchbase-python-client/couchbase/tests/cases/flush_t.py FAILED

There are still many failures but I think this patch can be a small step forward.


I tried to categorize failures simply. But these are not all.

Some failures are due to the difference of error handling interface between couchbase_ffi and couchbase:

_______________________________________________________________________________________________________________________________ ItemTest_FFI.test_pycbc366 _______________________________________________________________________________________________________________________________

self = <couchbase.tests.importer.ItemTest_FFI testMethod=test_pycbc366>

    def test_pycbc366(self):
        itcoll = ItemOptionDict()
        itcoll.create_and_add('foo', replica=True)
>       self.assertRaises(ArgumentError, self.cb.get_multi, itcoll)
E       AssertionError: ArgumentError not raised

../couchbase-python-client/couchbase/tests/cases/itmops_t.py:199: AssertionError

Due to the still existing API differences e.g., AsyncResult, _stats() function, or etc.:

_____________________________________________________________________________________________________________________________ ViewTest_FFI.test_missing_view _____________________________________________________________________________________________________________________________

self = <couchbase.tests.importer.ViewTest_FFI testMethod=test_missing_view>

    def setUp(self):
        super(ViewTest, self).setUp()
        self.skipIfMock()
        mgr = self.cb.bucket_manager()
        ret = mgr.design_create('blog', DESIGN_JSON, use_devmode=False)
>       self.assertTrue(ret.success)
E       AttributeError: 'AsyncResult' object has no attribute 'success'

../couchbase-python-client/couchbase/tests/cases/view_t.py:64: AttributeError
______________________________________________________________________________________________________________________ StatsTest_FFI.test_stats_with_argument_list _______________________________________________________________________________________________________________________

self = <couchbase.tests.importer.StatsTest_FFI testMethod=test_stats_with_argument_list>

    def test_stats_with_argument_list(self):
>       stats = self.cb.stats(['memory', 'tap'])

../couchbase-python-client/couchbase/tests/cases/stats_t.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../couchbase-python-client/gcouchbase/bucket.py:119: in ret
    return self._waitwrap(meth(self, *args, **kwargs))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <couchbase.bucket.Bucket bucket=default, nodes=['172.17.0.2:8091'] at 0x7f4e8d92ecd0>, keys = ['memory', 'tap'], keystats = False

    def stats(self, keys=None, keystats=False):
        """Request server statistics.

            Fetches stats from each node in the cluster. Without a key
            specified the server will respond with a default set of
            statistical information. It returns the a `dict` with stats keys
            and node-value pairs as a value.

            :param keys: One or several stats to query
            :type keys: string or list of string
            :raise: :exc:`.CouchbaseNetworkError`
            :return: `dict` where keys are stat keys and values are
                host-value pairs

            Find out how many items are in the bucket::

                total = 0
                for key, value in cb.stats()['total_items'].items():
                    total += value

            Get memory stats (works on couchbase buckets)::

                cb.stats('memory')
                # {'mem_used': {...}, ...}
            """
        if keys and not isinstance(keys, (tuple, list)):
            keys = (keys,)
>       return self._stats(keys, keystats=keystats)
E       TypeError: _stats() got an unexpected keyword argument 'keystats'

../couchbase-python-client/couchbase/bucket.py:908: TypeError

Due to wrong implementation for gevent:

___________________________________________________________________________________________________________________________ ObserveTest_FFI.test_multi_observe ___________________________________________________________________________________________________________________________

self = <couchbase.tests.importer.ObserveTest_FFI testMethod=test_multi_observe>

    def test_multi_observe(self):
        kexist = self.gen_key("test_multi_observe-exist")
        kmissing = self.gen_key("test_multi_observe-missing")
        self.cb.upsert(kexist, "value")
        self.cb.remove(kmissing, quiet=True)
        grv = self.cb.get(kexist)

>       mres = self.cb.observe_multi((kexist, kmissing))

../couchbase-python-client/couchbase/tests/cases/observe_t.py:59:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../couchbase-python-client/gcouchbase/bucket.py:119: in ret
    return self._waitwrap(meth(self, *args, **kwargs))
../couchbase-python-client/gcouchbase/bucket.py:111: in _waitwrap
    return get_hub().switch()
../../../env-pypy/site-packages/gevent/hub.py:630: in switch
    return RawGreenlet.switch(self)
/home/sub/src/pypy-5.7.1-linux_x86_64-portable/lib_pypy/greenlet.py:53: in switch
    return self.__switch('switch', (args, kwds))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

target = <Hub at 0x7f4e8d8abc70 epoll default pending=0 ref=0 fileno=11>, methodname = 'switch', baseargs = (((), {}),), current = <greenlet.greenlet object at 0x00007f4e8d8abc40>

    def __switch(target, methodname, *baseargs):
        current = getcurrent()
        #
        while not (target.__main or _continulet.is_pending(target)):
            # inlined __nonzero__ ^^^ in case it's overridden
            if not target.__started:
                if methodname == 'switch':
                    greenlet_func = _greenlet_start
                else:
                    greenlet_func = _greenlet_throw
                _continulet.__init__(target, greenlet_func, *baseargs)
                methodname = 'switch'
                baseargs = ()
                target.__started = True
                break
            # already done, go to the parent instead
            # (NB. infinite loop possible, but unlikely, unless you mess
            # up the 'parent' explicitly.  Good enough, because a Ctrl-C
            # will show that the program is caught in this loop here.)
            target = target.parent
            # convert a "raise GreenletExit" into "return GreenletExit"
            if methodname == 'throw':
                try:
                    raise baseargs[0], baseargs[1]
                except GreenletExit, e:
                    methodname = 'switch'
                    baseargs = (((e,), {}),)
                except:
                    baseargs = sys.exc_info()[:2] + baseargs[2:]
        #
        try:
            unbound_method = getattr(_continulet, methodname)
            _tls.leaving = current
>           args, kwds = unbound_method(current, *baseargs, to=target)
E           LoopExit: ('This operation would block forever', <Hub at 0x7f4e8d8abc70 epoll default pending=0 ref=0 fileno=11>)

/home/sub/src/pypy-5.7.1-linux_x86_64-portable/lib_pypy/greenlet.py:92: LoopExit

Due to the server's response:

_____________________________________________________________________________________________________________________________ SubdocTest_FFI.test_create_doc _____________________________________________________________________________________________________________________________

self = <couchbase.tests.importer.SubdocTest_FFI testMethod=test_create_doc>

    def test_create_doc(self):
        cb = self.cb
        key = self.gen_key('create_doc')
>       cb.mutate_in(key, SD.upsert('new.path', 'newval'), upsert_doc=True)

../couchbase-python-client/couchbase/tests/cases/subdoc_t.py:286:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../couchbase-python-client/gcouchbase/bucket.py:119: in ret
    return self._waitwrap(meth(self, *args, **kwargs))
../couchbase-python-client/gcouchbase/bucket.py:111: in _waitwrap
    return get_hub().switch()
../../../env-pypy/site-packages/gevent/hub.py:630: in switch
    return RawGreenlet.switch(self)
/home/sub/src/pypy-5.7.1-linux_x86_64-portable/lib_pypy/greenlet.py:53: in switch
    return self.__switch('switch', (args, kwds))
/home/sub/src/pypy-5.7.1-linux_x86_64-portable/lib_pypy/greenlet.py:92: in __switch
    args, kwds = unbound_method(current, *baseargs, to=target)
couchbase_ffi/result.py:215: in invoke
    self._maybe_throw()
couchbase_ffi/result.py:180: in _maybe_throw
    PyCBC.raise_helper(ex_cls, ex_obj, ex_bt)
couchbase_ffi/_rtconfig.py:60: in raise_helper
    exec('raise cls, obj, bt')
couchbase_ffi/result.py:155: in _add_bad_rc
    raise pycbc_exc_lcb(rc)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <couchbase_ffi._rtconfig._PyCBC_Class object at 0x00007f4e8df9dbb0>, rc = 75, msg = 'Operational error', obj = None

    def exc_lcb(self, rc, msg='Operational error', obj=None):
        try:
            cls = self.lcb_errno_map[rc]
        except KeyError:
            cls = self.default_exception.rc_to_exctype(rc)
        params = {'rc': rc, 'message': msg}
        if obj is not None:
            params['objextra'] = obj
>       raise cls(params)
E       LCB_0x4B (generated, catch: CouchbaseInternalError): <Key='create_doc1', RC=0x4B[A badly formatted packet was sent to the server. Please report this in a bug], Operational error, Results=1>

couchbase_ffi/_rtconfig.py:92: LCB_0x4B (generated, catch: CouchbaseInternalError)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant