TypeError: First argument needs to be a ViewQuery, SpatialQuery or N1qlQuery


#1

This is going to be quite a story, strap in. :slight_smile:

The Error

Start with a stack trace:

TypeError: First argument needs to be a ViewQuery, SpatialQuery or N1qlQuery.
    at Bucket.query (/Users/erik/aurora/dev_watch/ow-back/node_modules/couchbase/lib/bucket.js:955:11)
    at CbStoreAdapter._ensureGsiIndices (/Users/erik/aurora/dev_watch/ow-back/node_modules/ottoman/lib/cbstoreadapter.js:484:22)
    at /Users/erik/aurora/dev_watch/ow-back/node_modules/ottoman/lib/cbstoreadapter.js:507:10
    at handler (/Users/erik/aurora/dev_watch/ow-back/node_modules/ottoman/lib/cbstoreadapter.js:285:7)
    at /Users/erik/aurora/dev_watch/ow-back/node_modules/couchbase/lib/bucketmgr.js:152:7
    at IncomingMessage.<anonymous> (/Users/erik/aurora/dev_watch/ow-back/node_modules/couchbase/lib/bucketmgr.js:13:7)
    at emitNone (events.js:85:20)

This is complaining that Ottoman is trying to run something that isn’t an N1qlQuery object.

This problem has been posted about before. Google found a separate tale of woe about it, with no resolution.

This was a major blocker for us, so we dove deep, and found some bizarre stuff.

Research

First observation - nobody runs N1qlQueries, you actually run an N1qlStringQuery, which is a subtype of N1qlQuery defined by node_modules/couchbase/lib/n1qlquery.js.

That file itself is a little bit strange, because it defines this subtype AFTER doing module.exports (just an odd side note, probably not related). Using util.inherits, it states that an N1qlStringQuery is extended from N1qlQuery. So far, so good.

Inside of the couchbase node SDK in bucket.js, here’s where the error stems from:

  if (query instanceof ViewQuery) {
    return this._view(
        '_view', query.ddoc, query.name, query.options, callback);
  } else if (query instanceof SpatialQuery) {
    return this._view(
        '_spatial', query.ddoc, query.name, query.options, callback);
  } else if (query instanceof N1qlQuery) {
    return this._n1ql(
        query, params, callback
    );
  } else if (query instanceof SearchQuery) {
    return this._fts(
        query, callback
    );
  } else {
    throw new TypeError(
        'First argument needs to be a ViewQuery, SpatialQuery or N1qlQuery.');
  }

So this part is clear – this error is cropping up because the N1qlStringQuery that is being passed by Ottoman’s index creation methods is failing the test. “query” is an instanceof N1qlStringQuery (also known as couchbase.N1qlQuery.Direct, odd naming there, but again just a side note.

The Big WTF Moment

So…reducing this to simplest terms, in some runtime environments:

var q = couchbase.N1qlQuery.fromString('select * from default');
console.log(q instanceof N1qlQuery);

Yup, that’s false, at least some times, in some places.

Wait…WTF? This doesn’t make sense for several reasons. Primarily because this works for most people, ottoman passes its build tests, but then also if you look at the definition, this error really doesn’t make any sense.

function N1qlStringQuery(str) {
  this.options = {
    statement: str
  };
  this.isAdhoc = true;
}
util.inherits(N1qlStringQuery, N1qlQuery);
N1qlQuery.Direct = N1qlStringQuery;

Further deepening the mystery, if you take this condition:

  } else if (query instanceof N1qlQuery) {
    return this._n1ql(
        query, params, callback
    );

And you hack it to be this:

  } else if (query instanceof N1qlQuery || query instanceof N1qlQuery.Direct) {
    return this._n1ql(
        query, params, callback
    );

It still doesn’t work.

Something deep and ugly is happening here.

Work-Around

Hi random internet person! I bet you got here by googling! Welcome welcome, unlike those other posts you found that didn’t help you, this will actually fix your issue!

But it’s an ugly hack. Here goes – that line in bucket.js, where it checks the type of the argument passed to the query function, we changed it to this:

else if (query instanceof N1qlQuery || typeof query.consistency === 'function')

This is an ugly form of duck typing, basically we’re claiming that if you have a consistency function then you “walk like an N1qlQuery” and you “talk like an N1qlQuery”. I think I’m totally going to programmer hell for that line of code, but whatever.

Further weirdness

Most people don’t seem to have this issue. Even on a dev team of many people, only one of us had it, and we can’t identify any config difference between those that do and don’t. Same codebase, same version of node, etc. etc.

Hints & Tips – things I’m not sure are related but might be:

The hack isn’t a solution, it just gets us past this. Deeper question is, why is this happening? Well – I’m not sure. But here are some observations that suggest it’s a deeper issue.

  1. Most of our app is written in es6, we don’t use prototypal inheritance ever, except when working with external libs like ottoman & couchbase.
  2. The use of util.inherits is actively discouraged in node. https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
  3. es6 style class inherits and util.inherts / use of node “typeof” are incompatible, other people who use util.inherits have reported similar problems in other contexts: https://github.com/nodejs/node/issues/4179
  4. Plenty of other people recommend using extends specifically to avoid breaking “instanceof” https://github.com/airbnb/javascript#constructors--extends

#2

Very interesting findings. Thanks for sharing in case others see the same and I’m sure @brett19 and I will want to look at it further.


#3

Hey @moxious,

The reason your seeing this error is actually a little deeper than you might have noticed at first glance. The issue is not so much that we are using inheritance at the query class level, but rather that NPM manages dependancies on a per-module basis. In effect, what this means is that when you require the couchbase module into your application, and then we require couchbase into Ottoman.js, if the versions do not match, different modules are included. This means that the bucket you pass to ottoman has one idea of what N1qlQuery is, but the N1qlQuery that Ottoman.js is using to create queries is different (from its own version of the couchbase module).

Cheers, Brett