UNPKG

@mysql/xdevapi

Version:

MySQL Connector/Node.js - A Node.js driver for MySQL using the X Protocol and X DevAPI.

1,066 lines (871 loc) 50.7 kB
/* * Copyright (c) 2020, 2022, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is also distributed with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, * as designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an * additional permission to link the program and your derivative works * with the separately licensed software that they have included with * MySQL. * * Without limiting anything contained in the foregoing, this file, * which is part of MySQL Connector/Node.js, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ 'use strict'; /* eslint-env node, mocha */ const Level = require('../../../../lib/logger').Level; const config = require('../../../config'); const errors = require('../../../../lib/constants/errors'); const expect = require('chai').expect; const fixtures = require('../../../fixtures'); const mysqlx = require('../../../..'); const path = require('path'); const warnings = require('../../../../lib/constants/warnings'); describe('finding documents in collections using CRUD', () => { const baseConfig = { schema: config.schema || 'mysql-connector-nodejs_test' }; let schema, session, collection; beforeEach('create default schema', () => { return fixtures.createSchema(baseConfig.schema); }); beforeEach('create session using default schema', () => { const defaultConfig = Object.assign({}, config, baseConfig); return mysqlx.getSession(defaultConfig) .then(s => { session = s; }); }); beforeEach('load default schema', () => { schema = session.getDefaultSchema(); }); beforeEach('create collection', () => { return schema.createCollection('test') .then(c => { collection = c; }); }); afterEach('drop default schema', () => { return session.dropSchema(schema.getName()); }); afterEach('close session', () => { return session.close(); }); context('without criteria', () => { beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo' }) .add({ _id: '2', name: 'bar' }) .execute(); }); context('with a callback', () => { it('includes all documents in the collection', () => { const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }]; const actual = []; return collection.find() .execute(doc => { actual.push(doc); }) .then(() => { expect(actual).to.have.lengthOf(expected.length); expect(actual).to.deep.include.all.members(expected); }); }); it('does not fail when trying to use a pull-based cursor', () => { const noop = () => {}; return collection.find() .execute(noop) .then(result => { // eslint-disable-next-line no-unused-expressions expect(result.fetchOne()).to.not.exist; expect(result.fetchAll()).to.deep.equal([]); }); }); }); context('without a callback', () => { it('returns the first document in the resultset', () => { const expected = { _id: '1', name: 'foo' }; return collection.find() .sort('_id') .execute() .then(result => { let actual = result.fetchOne(); expect(actual).to.deep.equal(expected); actual = result.fetchAll(); expect(actual).to.have.lengthOf(1); }); }); it('returns all documents in the collection', () => { const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }]; return collection.find() .execute() .then(result => { let actual = result.fetchAll(); expect(actual).to.have.lengthOf(expected.length); expect(actual).to.deep.include.all.members(expected); actual = result.fetchAll(); return expect(actual).to.be.empty; }); }); it('returns the resultset as an array', () => { const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }]; return collection.find() .execute() .then(result => { let actual = result.toArray(); expect(actual).to.have.lengthOf(expected.length); expect(actual).to.deep.include.all.members(expected); actual = result.toArray(); expect(actual).to.have.lengthOf(expected.length); }); }); }); }); context('with binding query', () => { beforeEach('add fixtures', () => { return collection .add({ _id: 'foo', foo: 'bar', size: 42 }) .add({ _id: 'bar', bar: 'baz', size: 23 }) .execute(); }); it('returns documents that match a criteria specified with named parameter pairs', () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const actual = []; return collection .find('size = :size') .bind('size', 42) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns documents that match a criteria specified with a named parameter mapping', () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const actual = []; return collection .find('size = :size') .bind({ size: 42 }) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); context('with a list of placeholders', () => { it('returns documents that match a criteria specified as a string', async () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const got = await collection.find('size in [:s1, :s2, :s3]') .bind({ s1: 42, s2: 50, s3: 57 }) .execute(); expect(got.fetchAll()).to.deep.equal(expected); }); it('returns documents that match a criteria specified as an X DevAPI expression', async () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const got = await collection.find(mysqlx.expr('size in [:s1, :s2, :s3]')) .bind({ s1: 42, s2: 50, s3: 57 }) .execute(); expect(got.fetchAll()).to.deep.equal(expected); }); }); context('with a JSON object containing placeholders', () => { it('returns documents that match a criteria specified as a string', async () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const got = await collection.find('size in json_extract({ "keys": [:s1, :s2, :s3] }, "$.keys[*]")') .bind({ s1: 42, s2: 50, s3: 57 }) .execute(); expect(got.fetchAll()).to.deep.equal(expected); }); it('returns documents that match a criteria specified as an X DevAPI expression', async () => { const expected = [{ _id: 'foo', foo: 'bar', size: 42 }]; const got = await collection.find(mysqlx.expr('size in json_extract({ "keys": [:s1, :s2, :s3] }, "$.keys[*]")')) .bind({ s1: 42, s2: 50, s3: 57 }) .execute(); expect(got.fetchAll()).to.deep.equal(expected); }); }); context('BUG#33940584', () => { it('fails with the corresponding server error if the placeholder value is not supported', () => { return collection.find('size = :size') .bind('size', { size: 42 }) .execute() .then(() => { return expect.fail(); }) .catch(err => { expect(err.info).to.include.keys('code'); return expect(err.info.code).to.equal(errors.ER_X_EXPR_BAD_VALUE); }); }); }); }); context('with projection', () => { beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo', size: 42 }) .add({ _id: '2', name: 'bar', size: 23 }) .execute(); }); it('includes only columns provided as an expression array', () => { const expected = [{ name: 'foo', size: 42 }, { name: 'bar', size: 23 }]; const actual = []; return collection .find() .fields(['name', 'size']) .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('includes only columns provided as expression arguments', () => { const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }]; const actual = []; return collection .find() .fields('_id', 'name') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('performs computed projections', () => { const expected = [{ name: 'bar', newSize: 24 }, { name: 'foo', newSize: 43 }]; const actual = []; return collection .find() .fields(mysqlx.expr('{ "name": name, "newSize": size + 1 }')) .sort('size ASC') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('with aggregations', () => { beforeEach('add fixtures', () => { return collection .add({ name: 'foo', age: 23, active: true }) .add({ name: 'bar', age: 42, active: false }) .add({ name: 'baz', age: 50, active: true }) .add({ name: 'qux', age: 35, active: false }) .execute(); }); it('includes all the aggregation results', () => { const expected = [{ active: true, age: 36.5 }, { active: false, age: 38.5 }]; return collection.find() .fields('active', 'avg(age) as age') .groupBy('active') .execute() .then(res => { expect(res.fetchAll()).to.deep.equal(expected); }); }); it('includes only the aggregation results that match a given criteria', () => { const expected = [{ active: false, age: 38.5 }]; return collection.find() .fields('active', 'avg(age) as age') .groupBy('active') .having('avg(age) > 37') .execute() .then(res => { expect(res.fetchAll()).to.deep.equal(expected); }); }); }); context('with limit and/or offset', () => { beforeEach('add fixtures', () => { return collection .add({ _id: 1, name: 'foo' }) .add({ _id: 2, name: 'bar' }) .add({ _id: 3, name: 'baz' }) .add({ _id: 4, name: 'qux' }) .add({ _id: 5, name: 'quux' }) .execute(); }); it('returns a given number of documents specified with a JavaScript number', () => { const expected = [{ _id: 1, name: 'foo' }, { _id: 2, name: 'bar' }, { _id: 3, name: 'baz' }]; const actual = []; return collection .find() .limit(3) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns a given number of documents specified with a JavaScript string', () => { const expected = [{ _id: 1, name: 'foo' }, { _id: 2, name: 'bar' }, { _id: 3, name: 'baz' }]; const actual = []; return collection .find() .limit('3') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns a given number of documents specified with a JavaScript BigInt', () => { const expected = [{ _id: 1, name: 'foo' }, { _id: 2, name: 'bar' }, { _id: 3, name: 'baz' }]; const actual = []; return collection .find() .limit(3n) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns the documents after a given offset specified with a JavaScript number', () => { const expected = [{ _id: 3, name: 'baz' }, { _id: 4, name: 'qux' }]; const actual = []; return collection .find() .limit(2) .offset(2) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns the documents after a given offset specified with a JavaScript string', () => { const expected = [{ _id: 3, name: 'baz' }, { _id: 4, name: 'qux' }]; const actual = []; return collection .find() .limit(2) .offset('2') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns the documents after a given offset specified with a JavaScript BigInt', () => { const expected = [{ _id: 3, name: 'baz' }, { _id: 4, name: 'qux' }]; const actual = []; return collection .find() .limit(2) .offset(2n) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); context('when an offset is provided using the limit() method', () => { it('writes a deprecation warning to the log when debug mode is enabled', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-limit-offset.js'); return fixtures.collectLogs('api.limit', script, [schema.getName(), collection.getName(), 1, 1], { level: Level.WARNING }) .then(proc => { expect(proc.logs).to.have.lengthOf(1); return expect(proc.logs[0]).to.equal(warnings.MESSAGES.WARN_DEPRECATED_LIMIT_WITH_OFFSET); }); }); it('writes a deprecation warning to stdout when debug mode is not enabled', done => { process.on('warning', warning => { if ((!warning.name || warning.name !== warnings.TYPES.DEPRECATION) || (!warning.code || !warning.code.startsWith(warnings.CODES.DEPRECATION))) { return; } process.removeAllListeners('warning'); expect(warning.message).to.equal(warnings.MESSAGES.WARN_DEPRECATED_LIMIT_WITH_OFFSET); return done(); }); collection.find() .limit(2, 3) .execute(); }); }); context('when the setCount() method is used', () => { it('writes a deprecation warning to stdout', done => { process.on('warning', warning => { if ((!warning.name || warning.name !== warnings.TYPES.DEPRECATION) || (!warning.code || !warning.code.startsWith(warnings.CODES.DEPRECATION))) { return; } process.removeAllListeners('warning'); expect(warning.message).to.equal(warnings.MESSAGES.WARN_DEPRECATED_LIMIT_SET_COUNT); return done(); }); collection.find() .setCount(1) .execute(); }); }); }); context('single document retrieval', () => { beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo' }) .add({ _id: 2, name: 'bar' }) .execute(); }); context('when a document with a given id exists', () => { it('returns the document if _id is a String', () => { const expected = { _id: '1', name: 'foo' }; return collection.getOne('1') .then(doc => expect(doc).to.deep.equal(expected)); }); it('returns the document if _id is not a String', () => { const expected = { _id: 2, name: 'bar' }; return collection.getOne(2) .then(doc => expect(doc).to.deep.equal(expected)); }); }); context('when a document with a given id does not exist', () => { it('returns null if the given id is a non-empty String', () => { return collection.getOne('3') .then(doc => expect(doc).to.be.null); }); it('returns null if a document id is an empty String', () => { return collection.getOne('') .then(doc => expect(doc).to.be.null); }); it('returns null if a document id is null', () => { return collection.getOne(null) .then(doc => expect(doc).to.be.null); }); it('returns null if a document id is not defined', () => { return collection.getOne() .then(doc => expect(doc).to.be.null); }); }); }); context('predicate expressions', () => { beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo', list: [1, 4] }) .add({ _id: '2', name: 'bar', list: [4, 7] }) .add({ _id: '3', name: 'baz' }) .execute(); }); context('where the property value', () => { it('is part of a set of values being provided', () => { const expected = [{ _id: '1', name: 'foo' }, { _id: '3', name: 'baz' }]; const actual = []; return collection.find("_id in ('1', '3')") .fields('_id', 'name') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('is not part of a set of values being provided', () => { const expected = [{ _id: '2', name: 'bar' }]; const actual = []; return collection.find("_id not in ('1', '3')") .fields('_id', 'name') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('where a list of values being provided', () => { it('overlaps a list of values of a given document property', () => { const expected = [{ _id: '1' }]; const actual = []; return collection.find('[1, 2, 3] OVERLAPS list') .fields('_id') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('does not overlap a list of values of a given document property', () => { const expected = [{ _id: '1' }]; const actual = []; return collection.find('[6, 7] NOT OVERLAPS list') .fields('_id') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('where a list of values of a given document property', () => { it('overlaps a list of values being provided', () => { const expected = [{ _id: '1' }, { _id: '2' }]; const actual = []; return collection.find('list OVERLAPS [4]') .fields('_id') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('does not overlap a list of values being provided', () => { const expected = [{ _id: '1' }, { _id: '2' }]; const actual = []; return collection.find('list NOT OVERLAPS [8, 9]') .fields('_id') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); }); context('sorting results', () => { beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo', age: 23 }) .add({ _id: '2', name: 'bar', age: 42 }) .add({ _id: '3', name: 'baz', age: 23 }) .execute(); }); it('sorts the results according the order clauses provided as an expression array', () => { const expected = [{ _id: '3', name: 'baz', age: 23 }, { _id: '1', name: 'foo', age: 23 }, { _id: '2', name: 'bar', age: 42 }]; const actual = []; return collection.find() .sort(['age ASC', 'name ASC']) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); it('sorts the results according the order clauses provided as expression arguments', () => { const expected = [{ _id: '2', name: 'bar', age: 42 }, { _id: '3', name: 'baz', age: 23 }, { _id: '1', name: 'foo', age: 23 }]; const actual = []; return collection.find() .sort('age DESC', 'name ASC') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('BUG#29766014', () => { const now = new Date(); beforeEach('add fixtures', () => { return collection .add({ _id: '1', name: 'foo', updatedAt: now }) .add({ _id: '2', name: 'bar' }) .execute(); }); it('does not fail when a placeholder is assigned a JavaScript Date instance', () => { const updatedAt = now.toJSON().substring(0, now.toJSON().length - 1).concat('+00:00'); const expected = [{ _id: '1', name: 'foo', updatedAt }]; const actual = []; return collection.find('updatedAt = :date') .bind('date', updatedAt) .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('BUG#29807792', () => { beforeEach('add fixtures', () => { return collection .add({ name: 'foo', age: 42 }) .add({ name: 'bar', age: 23 }) .execute(); }); it('correctly encodes cast types', () => { const expected = [{ name: 'foo' }]; const actual = []; return collection.find("CAST('42' AS UNSIGNED INTEGER) = age") .fields('name') .execute(doc => actual.push(doc)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('BUG#34728259', () => { const signedBigInt = '-9223372036854775808'; const unsignedBigInt = '18446744073709551615'; beforeEach('populate collection', () => { return session.sql(`INSERT INTO \`${schema.getName()}\`.\`${collection.getName()}\` (doc) VALUES ('{ "_id": "1", "signedBigInt": ${signedBigInt}, "unsignedBigInt": ${unsignedBigInt} }')`) .execute(); }); it('returns unsafe numeric field values as strings', () => { return collection.find() .fields('signedBigInt', 'unsignedBigInt') .execute() .then(res => { return expect(res.fetchOne()).to.deep.equal({ signedBigInt, unsignedBigInt }); }); }); }); context('upstream numeric values specified with a JavaScript BigInt', () => { const unsafeNegative = '-9223372036854775808'; const unsafePositive = '18446744073709551615'; beforeEach('populate the collection', async () => { await collection.add({ unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }) .execute(); }); context('in the statements created by the application', () => { it('do not lose precision in filtering criteria placeholder assignments', async () => { const want = { unsafeNegative, unsafePositive }; const res = await collection.find('unsafeNegative = :negative AND unsafePositive = :positive') .bind('negative', BigInt(unsafeNegative)) .bind('positive', BigInt(unsafePositive)) .fields('unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); expect(got).to.deep.equal(want); }); }); }); context('integer values in a result set', () => { const safeNegative = Number.MIN_SAFE_INTEGER + 1; const safePositive = Number.MAX_SAFE_INTEGER - 1; const unsafeNegative = '-9223372036854775808'; const unsafePositive = '18446744073709551615'; beforeEach('populate the collection', async () => { await collection.add({ safeNegative, safePositive, unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }) .execute(); }); context('consumed using a pull-based cursor', () => { it('can always be decoded as a JavaScript string', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.STRING, schema: schema.getName() }; const session = await mysqlx.getSession(itConfig); const want = { safeNegative: `${safeNegative}`, safePositive: `${safePositive}`, unsafeNegative, unsafePositive }; const res = await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); it('can always be decoded as a JavaScript BigInt', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.BIGINT, schema: schema.getName() }; const session = await mysqlx.getSession(itConfig); const want = { safeNegative: BigInt(safeNegative), safePositive: BigInt(safePositive), unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }; const res = await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); it('can be decoded as a JavaScript string only when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.UNSAFE_STRING, schema: schema.getName() }; const session = await mysqlx.getSession(itConfig); const want = { safeNegative, safePositive, unsafeNegative: `${unsafeNegative}`, unsafePositive: `${unsafePositive}` }; const res = await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); it('can be decoded as a JavaScript BigInt only when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.UNSAFE_BIGINT, schema: schema.getName() }; const session = await mysqlx.getSession(itConfig); const want = { safeNegative, safePositive, unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }; const res = await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); it('are decoded by default as a JavaScript string when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, schema: schema.getName() }; const session = await mysqlx.getSession(itConfig); const want = { safeNegative, safePositive, unsafeNegative: `${unsafeNegative}`, unsafePositive: `${unsafePositive}` }; const res = await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); }); context('consumed using a push-based cursor', () => { it('can always be decoded as a JavaScript string', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.STRING, schema: schema.getName() }; const session = await mysqlx.getSession(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [{ safeNegative: `${safeNegative}`, safePositive: `${safePositive}`, unsafeNegative, unsafePositive }]; const got = []; await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(doc => got.push(doc)); await session.close(); expect(got).to.deep.equal(want); }); it('can always be decoded as a JavaScript BigInt', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.BIGINT, schema: schema.getName() }; const session = await mysqlx.getSession(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [{ safeNegative: BigInt(safeNegative), safePositive: BigInt(safePositive), unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }]; const got = []; await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(doc => got.push(doc)); await session.close(); expect(got).to.deep.equal(want); }); it('can be decoded as a JavaScript string only when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.UNSAFE_STRING, schema: schema.getName() }; const session = await mysqlx.getSession(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [{ safeNegative, safePositive, unsafeNegative: `${unsafeNegative}`, unsafePositive: `${unsafePositive}` }]; const got = []; await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(doc => got.push(doc)); await session.close(); expect(got).to.deep.equal(want); }); it('can be decoded as a JavaScript BigInt only when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.UNSAFE_BIGINT, schema: schema.getName() }; const session = await mysqlx.getSession(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [{ safeNegative, safePositive, unsafeNegative: BigInt(unsafeNegative), unsafePositive: BigInt(unsafePositive) }]; const got = []; await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(doc => got.push(doc)); await session.close(); expect(got).to.deep.equal(want); }); it('are decoded by default as a JavaScript string when they lose precision', async () => { const itConfig = { ...config, ...baseConfig, schema: schema.getName() }; const session = await mysqlx.getSession(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [{ safeNegative, safePositive, unsafeNegative: `${unsafeNegative}`, unsafePositive: `${unsafePositive}` }]; const got = []; await session.getDefaultSchema().getCollection(collection.getName()) .find() .fields('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(doc => got.push(doc)); await session.close(); expect(got).to.deep.equal(want); }); }); }); context('when debug mode is enabled', () => { beforeEach('populate collection', () => { return collection.add({ name: 'foo', count: 2 }) .add({ name: 'bar', count: 5 }) .add({ name: 'foo', count: 10 }) .execute(); }); it('logs the basic operation parameters', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName()]) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('collection', 'data_model'); expect(crudFind.collection).to.contain.keys('name', 'schema'); expect(crudFind.collection.name).to.equal(collection.getName()); expect(crudFind.collection.schema).to.equal(schema.getName()); expect(crudFind.data_model).to.equal('DOCUMENT'); }); }); it('logs the criteria statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-criteria.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 'name = :v', 'v', 'foo']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('criteria', 'args'); expect(crudFind.criteria).to.contain.keys('type', 'operator'); expect(crudFind.criteria.type).to.equal('OPERATOR'); expect(crudFind.criteria.operator).to.contain.keys('name', 'param'); expect(crudFind.criteria.operator.name).to.equal('=='); expect(crudFind.criteria.operator.param).to.be.an('array').and.have.lengthOf(2); expect(crudFind.criteria.operator.param[0]).to.contain.keys('type', 'identifier'); expect(crudFind.criteria.operator.param[0].type).to.equal('IDENT'); expect(crudFind.criteria.operator.param[0].identifier).contain.keys('document_path'); expect(crudFind.criteria.operator.param[0].identifier.document_path).to.be.an('array').and.have.lengthOf(1); expect(crudFind.criteria.operator.param[0].identifier.document_path[0]).to.contain.keys('type', 'value'); expect(crudFind.criteria.operator.param[0].identifier.document_path[0].type).to.equal('MEMBER'); expect(crudFind.criteria.operator.param[0].identifier.document_path[0].value).to.equal('name'); expect(crudFind.criteria.operator.param[1]).to.contain.keys('type', 'position'); expect(crudFind.criteria.operator.param[1].type).to.equal('PLACEHOLDER'); expect(crudFind.criteria.operator.param[1].position).to.equal(0); expect(crudFind.args).to.be.an('array').and.have.lengthOf(1); expect(crudFind.args[0]).to.contain.keys('type', 'v_string'); expect(crudFind.args[0].type).to.equal('V_STRING'); expect(crudFind.args[0].v_string).to.contain.keys('value'); expect(crudFind.args[0].v_string.value).to.equal('foo'); }); }); it('logs the projection statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-projection.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 'name AS col']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('projection'); expect(crudFind.projection).to.be.an('array').and.have.lengthOf(1); expect(crudFind.projection[0]).contain.keys('source', 'alias'); expect(crudFind.projection[0].source).to.contain.keys('type', 'identifier'); expect(crudFind.projection[0].source.type).to.equal('IDENT'); expect(crudFind.projection[0].source.identifier.document_path).to.be.an('array').and.have.lengthOf(1); expect(crudFind.projection[0].source.identifier.document_path[0]).to.contain.keys('type', 'value'); expect(crudFind.projection[0].source.identifier.document_path[0].type).to.equal('MEMBER'); expect(crudFind.projection[0].source.identifier.document_path[0].value).to.equal('name'); expect(crudFind.projection[0].alias).to.equal('col'); }); }); it('logs the order statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-order.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 'count DESC']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('order'); expect(crudFind.order).to.be.an('array').and.have.lengthOf(1); expect(crudFind.order[0]).to.contain.keys('expr', 'direction'); expect(crudFind.order[0].expr).to.contain.keys('type', 'identifier'); expect(crudFind.order[0].expr.type).to.equal('IDENT'); expect(crudFind.order[0].expr.identifier).to.contain.keys('document_path'); expect(crudFind.order[0].expr.identifier.document_path).to.be.an('array').and.have.lengthOf(1); expect(crudFind.order[0].expr.identifier.document_path[0]).to.contain.keys('type', 'value'); expect(crudFind.order[0].expr.identifier.document_path[0].type).to.equal('MEMBER'); expect(crudFind.order[0].expr.identifier.document_path[0].value).to.equal('count'); }); }); it('logs the grouping statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-grouping.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 'count']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('grouping'); expect(crudFind.grouping).to.be.an('array').and.have.lengthOf(1); expect(crudFind.grouping[0]).to.contain.keys('type', 'identifier'); expect(crudFind.grouping[0].type).equal('IDENT'); expect(crudFind.grouping[0].identifier).to.contain.keys('document_path'); expect(crudFind.grouping[0].identifier.document_path).to.be.an('array'); expect(crudFind.grouping[0].identifier.document_path[0]).to.contain.keys('type', 'value'); expect(crudFind.grouping[0].identifier.document_path[0].type).to.equal('MEMBER'); expect(crudFind.grouping[0].identifier.document_path[0].value).to.equal('count'); }); }); it('logs the grouping criteria statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-grouping-criteria.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 'SUM(count)', 'name', 'SUM(count) > 2']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('grouping_criteria'); expect(crudFind.grouping_criteria).to.contain.keys('type', 'operator'); expect(crudFind.grouping_criteria.type).equal('OPERATOR'); expect(crudFind.grouping_criteria.operator).to.contain.keys('name', 'param'); expect(crudFind.grouping_criteria.operator.name).to.equal('>'); expect(crudFind.grouping_criteria.operator.param).to.be.an('array').and.have.lengthOf(2); expect(crudFind.grouping_criteria.operator.param[0]).to.contain.keys('type', 'function_call'); expect(crudFind.grouping_criteria.operator.param[0].type).to.equal('FUNC_CALL'); expect(crudFind.grouping_criteria.operator.param[0].function_call).to.contain.keys('name', 'param'); expect(crudFind.grouping_criteria.operator.param[0].function_call.name).to.contain.keys('name'); expect(crudFind.grouping_criteria.operator.param[0].function_call.name.name).to.equal('SUM'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param).to.be.an('array').and.have.lengthOf(1); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0]).to.contain.keys('type', 'identifier'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].type).to.equal('IDENT'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].identifier).to.contain.keys('document_path'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].identifier.document_path).to.be.an('array').and.have.lengthOf(1); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].identifier.document_path[0]).to.contain.keys('type', 'value'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].identifier.document_path[0].type).to.equal('MEMBER'); expect(crudFind.grouping_criteria.operator.param[0].function_call.param[0].identifier.document_path[0].value).to.equal('count'); expect(crudFind.grouping_criteria.operator.param[1]).to.contain.keys('type', 'literal'); expect(crudFind.grouping_criteria.operator.param[1].type).to.equal('LITERAL'); expect(crudFind.grouping_criteria.operator.param[1].literal).to.contain.keys('type', 'v_unsigned_int'); expect(crudFind.grouping_criteria.operator.param[1].literal.type).to.equal('V_UINT'); expect(crudFind.grouping_criteria.operator.param[1].literal.v_unsigned_int).to.equal(2); }); }); it('logs the correct locking parameters', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-locking.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), mysqlx.LockContention.NOWAIT]) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('locking', 'locking_options'); expect(crudFind.locking).to.equal('EXCLUSIVE_LOCK'); expect(crudFind.locking_options).to.equal('NOWAIT'); }); }); it('logs limit parameters specified with a JavaScript number', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'document-store', 'find-with-limit.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), collection.getName(), 1, 1]) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const crudFind = proc.logs[0]; expect(crudFind).to.contain.keys('limit'); expect(crudFind.limit).to.contain.keys('row_count', 'offset'); expect(crudFind.limit.row_count).to.equal(1); expect(crudFind.limit.offset).to.equal(1); }); }); it('logs the result set row data sent by the server', () => { const script = path.join