SDK 3.0.7 BucketManager.getBucket(): "You must have one open bucket before you can perform queries."

NODE SDK 3.0.7
CB 6.6.0
Node 10.14

I am try to create buckets in a new Couchbase cluster using the Node SDK. This is a process I used to do successfully use SDK 2.6. The methods are slightly different in 3.0.7 but it looks like the equivalent calls exist and the procedure should be the same. So I first call getAllBuckets to find out what buckets are currently in the cluster and whether any need to be created.

const cluster = new couchbase.Cluster("couchbase://localhost", { username: ..., password: ... });
const manager = cluster.buckets();
const bucketInfo = await manager.getAllBuckets();

However, I get an error: “You must have one open bucket before you can perform queries.”. Obviously I don’t have an open bucket but nor should I need one for this. There may not even be any buckets on the system. I also get the same error if I try to get a single bucket with manager.getBucket("mybucket")

If I open a bucket first, with bucket = cluster.bucket("mybucket") then call manager.getBucket() or manager.getAllBuckets() it works. However, if the bucket that I opened with cluster.bucket doesn’t actually exist (e.g. cluster.bucket(“dummy”)) then the getAllBuckets don’t just return an error, they throw an uncatchable unhandledPromiseRejection inside the call. This means that my catch block is not reached - the CB library has failed to catch and pass on the rejection which causes the application to quit!

I think due to the unhandled reject getBucket and getAllBuckets are not usable, so the only way that I can think of to test whether a bucket exists is to try to open it with cluser.bucket, then get the default collection and call collection.get(“dummy”). This will fail with documentNotFound if the bucket is valid, or “cluster object was closed” if not. This error is also given if the password is not correct, so it’s not foolproof but I can’t think of a better way.

Moving on from this the next step is to create the bucket if it doesn’t exist. However, this also fails if there is not a bucket open, therefore I don’t think they can be used to create the first bucket in a new cluster. As an aside, the 3.0.7 API docs for createBucket are incorrect - they say that the only setting that can be passed is conflictResolutionType. They don’t even say how to pass the new bucket name! The function does accept “name” in settings though and does work, as long as you have opened an existing bucket first.

Thanks,
Giles

1 Like

Hello Giles,

This should get you setup and working:

const couchbase = require('couchbase')
async function getBuckets() {

  const cluster = await couchbase.connect('couchbase://localhost', { 
    username: "Administrator", password: "password" 
  })
  const manager = cluster.buckets();

  try {
    const bucketInfo = await manager.getAllBuckets()
    console.log(bucketInfo)
  } catch (e) {
    console.error(e.message)
  }

  cluster.close();
}

getBuckets()
  .catch(e => console.log(e))

Calling:

new couchbase.Cluster 

will not work as it is not connecting to the cluster at cluster-level. Also, ensure that you are running the latest version of couchbase *3.0.7 or you may still have issues.

This is not covered well in the docs and I am going to try and fix that. If this answers your question it would help if you can mark the post as resolved or if you have any more questions about this let me know.

2 Likes

Hi Eric,

Thanks for the reply and the solution. I can confirm that, as long as connection is valid, calling “couchbase,connect” rather than “new couchbase.Cluster” does work and I will mark this as solved. I have a few comments/questions:

  • If the username or password passed to couchbase.connect is incorrect, the call still resolves the promise but getBucket() returns the same “You mist have one open bucket before you can perform queries” error. This is misleading - the error should be related to authentication.
  • Considering the couchbase.connect call always passes even if the credentials are wrong, why does it return a promise? It seems like it could be similar to the normal “cluster = new Cluster()” call, since it doesn’t actually give an error until you try to use it (so I guess isn’t checking the credentials and making the connection before returning).
  • “it is not connecting to the cluster at cluster-level” - I don’t understand this. What do you mean by cluster level and what level is the “new couchbase.Cluster” connecting at? The cluster returned by couchbase.connect can still be used for cluster.bucket(), collection.get(), etc, so what’s the difference?

One final thing, related to this forum rather than CB: when you posted this solution you just edited your previous message where you said “I’m looking into this issue” rather than posting a new comment. I understand why you did this - to keep the thread clean and remove unnecessary comments. However, it meant I didn’t get a notification (either email or in-forum) for the solution. More trivially,it also meant I couldn’t “like” the solution as I had already liked the “I’m looking into it” comment!

Thanks very much for your work and this solution. I appreciate your efforts on Couchbase.

Giles

1 Like

Hey @giles,

I’ll try to respond to your questions/comments individually:

  • This is indeed a bit miss-leading, but it’s challenging to resolve due to the answer of your second question below.
  • It returns a promise because there are a few async things it does during connect, primarily related to the validation of the input parameters. For instance, if the SSL certificate you have passed is badly formatted. These kinds of errors which will never resolve themselves are thrown at connect time.
  • Fundamentally, Couchbase connects at a bucket level when talking to the memcached service (ie: a connection is bucket-specific). On top of this, we have a logical ‘cluster-level’ which allows you to perform operations which apply to the whole cluster, such as the creation of buckets or querying with a query which crosses buckets. Calling the connect method is what sets up these initial bucket-less connections which allow us to fetch the topology of the cluster. Due to the fact that we need to perform some async work during the initial setup of our Cluster object we needed to provide a method rather than a constructor such that we can return a promise. Note that the Cluster constructor is actually hidden and documented as not intended to be used directly, Cluster::connect is the correct way to instantiate a Cluster object. I should likely have masked it behind some proxy object when exposed by the SDK, but unfortunately we won’t be able to do this until 4.0.0.

Cheers, Brett

2 Likes

Hi @brett19 ,

Thanks for the explanation. Are you saying that we should always use “couchbase.connect” rather than “new couchbase.Cluster”, contrary to the “Getting Started” doc?

Cheers,
Giles

Hi @brett19,

Sorry to hassle you on this when you and @ericb have already resolved this issue, but I just want to clarify your previous message. Should I always use couchbase.connect rather than new couchbase.Cluster to connect, whether I’m doing bucket or cluster level operations?

Thanks,
Giles

1 Like