SDK 3.4.1 raise Exception of type 'Couchbase.Core.Exceptions.KeyValue.SubdocExceptionException' was thrown. when path not found

Hi,
New SDK 3.4.1 raise exception Exception of type ‘Couchbase.Core.Exceptions.KeyValue.SubdocExceptionException’ was thrown. if path not found even if I just check if this path exists. This exception don’t clarify that is happen. More clear will be with exception Couchbase.Core.Exceptions.KeyValue.PathNotFoundException . It’s new behavior, and SDK will raise this exception in case if path not found?

@ytatarynov

There are no expected changes in behavior in 3.4.1. Can you post your code?

Jeff

Hi, @jmorris
my code work for SDK 2.7.3 and couchbase community edition 7.1.1

public async Task<(bool, TSubDocument)> TryGetSubDocumentAsync<TSubDocument>(string topDocumentKey, string subDocumentPath)
{
    var value = default(TSubDocument);
    if (!(await _bucket.ExistsAsync(topDocumentKey)))
        return (false, value);

    var contents = await _bucket.LookupIn<dynamic>(topDocumentKey).Exists(subDocumentPath).ExecuteAsync();
    var exists = contents.Exists(subDocumentPath);

    if (exists)
    {
        value = (await _bucket.LookupIn<dynamic>(topDocumentKey).Get(subDocumentPath).ExecuteAsync()).Content<TSubDocument>(0);
        return (true, value);
    }

    return (false, value);
}

I try migrate it on 3.4.1

public bool TryGetSubDocument<TSubDocument>(string topDocumentKey, string subDocumentPath, out TSubDocument value)
{
    value = default(TSubDocument);
        
    if (!_collection.ExistsAsync(topDocumentKey).Result.Exists)
        return false;

    var exists = _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Exists(subDocumentPath)).Result.Exists(0);

    if (exists)
    {
        value = _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Get(subDocumentPath)).Result.ContentAs<TSubDocument>(0);
        return true;
    }

    return false;
}

it’s raised exception on row

 var exists = _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Exists(subDocumentPath)).Result.Exists(0);

Can your provide a sample JSON doc and the path you are checking? A unit test would be nice. Once I have that I can tell you exactly what is going on.

I noticed in your migration to 3.4.1 code, you should be awaiting the operations instead of blocking on them via Result.

Jeff

thank for replay @jmorris
Totally agree with you, async / await preferable, but I need test both approaches with blocking and not blocking calls.

I prepare unit test to reproduce my issue. Please, replace in it with appropriate username, password, host and backet name.

using Couchbase;
using Couchbase.KeyValue;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;

namespace Common.IntegrationTests
{
    [TestClass]
    public class SampleUnitTest
    {
        private ICouchbaseCollection _collection;

        [DataTestMethod]
        [DataRow("strfield0", "test28")]
        [DataRow("strfield1", "test29")]
        [DataRow("strfield3", "test27")]
        [DataRow("strfield4", "test9")]
        public async Task TryGetSubDocumentAsyncShouldReturnTrue_IfDocumentDoesAndPathAreExistsAndStringValue(string path, string expectedValue)
        {
            var testKeyGetAsync = "test-doc-key";
            // Arrange
            GenerateTestCouchDBData(testKeyGetAsync);
            // Act 
            var (result, value) = await TryGetSubDocumentAsync<string>(testKeyGetAsync, path);

            // Assert
            Assert.IsTrue(result);
            Assert.IsNotNull(value);
            Assert.AreEqual(expectedValue, value);
        }


        public async Task<(bool, TSubDocument)> TryGetSubDocumentAsync<TSubDocument>(string topDocumentKey, string subDocumentPath)
        {

            var options = new ClusterOptions
            {
                UserName = "user_name",
                Password = "password",
            };

            var cluster = Cluster.ConnectAsync("couchbase://127.0.0.1", options).Result;
            cluster.WaitUntilReadyAsync(TimeSpan.FromSeconds(10000)).Wait();
            var bucket = cluster.BucketAsync("backetName").Result;

            _collection = bucket.DefaultCollection();

            var value = default(TSubDocument);

            if (!(await _collection.ExistsAsync(topDocumentKey)).Exists)
                return (false, value);

            var result = (await _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Exists(subDocumentPath)));
            var exists = result.ContentAs<bool>(0);

            if (exists)
            {
                value = (await _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Get(subDocumentPath))).ContentAs<TSubDocument>(0);
                return (true, value);
            }

            return (false, value);
        }

        public void GenerateTestCouchDBData(string key)
        {
            Store(key,
               new
               {
                   intArray = new[]
                   {
                        1000,
                        2000,
                        3000,
                        4000,
                        5000,
                        6000
                   },
                   strfield0 = "test28",
                   strfield1 = "test29",
                   strfield3 = "test27",
                   strfield4 = "test9",
                   strfield2 = "test19",
                   customObject = new
                   {
                       intProp = 1,
                       stringProp = "MyStringProp-1"
                   },
                   customObjectList = new[]
                   {
                        new
                        {
                            intProp = 1,
                            stringProp = "MyStringProp-1"
                        },
                        new
                        {
                            intProp = 2,
                            stringProp = "MyStringProp-2"
                        },
                        new
                        {
                           intProp = 3,
                           stringProp = "MyStringProp-3"
                        },
                        new
                        {
                            intProp = 4,
                            stringProp = "MyStringProp-4"
                        }
                   }
               },
               100000);
        }

        public bool Store<T>(string key, T value, int timeInMinutes)
        {
             var options = new UpsertOptions();
             options.Expiry(TimeSpan.FromMinutes(timeInMinutes));
             _collection.UpsertAsync(key, value, options).Wait();
             return true;
        }
    }
}

@ytatarynov

After a few changes your code works as expected. The changes I made were removing the blocking calls and importantly changing:

var exists = result.ContentAs<bool>(0);

To this:

var exists = result.Exists(0);

The call to check for the existence of the path is not done via ContentAs() but by using Exists().

1 Like

thanks @jmiraglia for positive cases it’s work fine.
Now I try negative test , when path not exists

[DataRow("not-exist-path", default(string))]

test raise Exception of type ‘Couchbase.Core.Exceptions.KeyValue.SubdocExceptionException’ was thrown.
in line

var result = (await _collection.LookupInAsync<dynamic>(topDocumentKey, spec => spec.Exists(subDocumentPath)));

if path not exists I need use something else ?

@ytatarynov -

I see now. Yes, I do believe this is a bug; I added a ticket for resolving in the future. The problem here is that it’s a breaking change to fix this as anybody else using the SDK might be catching the exception and handling it that way. If were to fix it in a minor release, that might cause problems.

Jeff

Thanks @jmorris I’ll follow the ticket