UNPKG

@mysql/xdevapi

Version:

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

883 lines (719 loc) 40.8 kB
/* * Copyright (c) 2020, 2023, 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 config = require('../../../config'); const expect = require('chai').expect; const fixtures = require('../../../fixtures'); const mysqlx = require('../../../../'); const path = require('path'); describe('selecting rows from a table using CRUD', () => { const baseConfig = { schema: config.schema || 'mysql-connector-nodejs_test' }; let session, schema, table; 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 table', () => { return session.sql(`CREATE TABLE test ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)`).execute(); }); beforeEach('load table', () => { table = schema.getTable('test'); }); afterEach('drop default schema', () => { return session.dropSchema(schema.getName()); }); afterEach('close session', () => { return session.close(); }); context('without criteria and projection', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'bar', 23]) .values([2, 'foo', 42]) .execute(); }); context('with a callback', () => { it('includes all the rows in the table (and all the columns for each row)', () => { const expected = [[1, 'bar', 23], [2, 'foo', 42]]; const actual = []; return table.select() .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.include.members(expected)); }); it('does not fail when trying to use a pull-based cursor', () => { const noop = () => {}; return table.select() .execute(noop) .then(result => { // eslint-disable-next-line no-unused-expressions expect(result.fetchOne()).to.not.exist; expect(result.fetchAll()).to.deep.equal([]); }); }); it('returns the column metadata once', () => { let rowCount = 0; return table.select() .execute(() => {}, columns => { rowCount += 1; expect(columns).to.have.lengthOf(3); expect(columns[0].getColumnName()).to.equal('id'); expect(columns[0].getType()).to.equal('BIGINT'); expect(columns[1].getColumnName()).to.equal('name'); expect(columns[1].getType()).to.equal('STRING'); expect(columns[2].getColumnName()).to.equal('age'); expect(columns[2].getType()).to.equal('INT'); }) .then(() => { return expect(rowCount).to.equal(1); }); }); }); context('without a callback', () => { it('returns the first row in the resultset (and all its columns)', () => { const expected = [1, 'bar', 23]; return table.select() .orderBy('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 the existing rows in the table (and all the columns for each row)', () => { const expected = [[1, 'bar', 23], [2, 'foo', 42]]; return table.select() .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 = [[1, 'bar', 23], [2, 'foo', 42]]; return table.select() .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(); return expect(actual).to.have.lengthOf(expected.length); }); }); it('returns the column metadata for each row', () => { return table.select() .execute() .then(result => { const columns = result.getColumns(); expect(columns).to.have.lengthOf(3); expect(columns[0].getColumnName()).to.equal('id'); expect(columns[0].getType()).to.equal('BIGINT'); expect(columns[1].getColumnName()).to.equal('name'); expect(columns[1].getType()).to.equal('STRING'); expect(columns[2].getColumnName()).to.equal('age'); expect(columns[2].getType()).to.equal('INT'); }); }); }); }); context('with projection', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'bar', 23]) .values([2, 'foo', 42]) .execute(); }); it('includes only columns provided as an expression array', () => { const expected = [['bar', 23], ['foo', 42]]; const actual = []; return table.select(['name', 'age']) .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.include.members(expected)); }); it('includes only columns provided as expression arguments', () => { const expected = [['bar', 23], ['foo', 42]]; const actual = []; return table.select('name', 'age') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.include.members(expected)); }); it('includes columns with a given alias', async () => { const expected = [['bar', 23], ['foo', 42]]; const got = await table.select('name as theName', 'age as theAge') .orderBy('age') .execute(); const columns = got.getColumns(); expect(columns[0].getColumnLabel()).to.equal('theName'); expect(columns[1].getColumnLabel()).to.equal('theAge'); expect(got.fetchAll()).to.deep.equal(expected); }); it('transforms the colum format with a computed projection', async () => { const expected = [[{ theName: 'bar', theAge: 23 }], [{ theName: 'foo', theAge: 42 }]]; const got = await table.select(mysqlx.expr('{ "theName": name, "theAge": age }', { mode: mysqlx.Mode.TABLE })) .orderBy('age') .execute(); expect(got.fetchAll()).to.deep.equal(expected); }); }); context('with order', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'foo', 23]) .values([3, 'bar', 23]) .execute(); }); it('sorts by columns provided as an expression array', () => { const expected = [['foo', 23], ['foo', 42], ['bar', 23]]; const actual = []; return table.select('name', 'age') .orderBy(['name desc', 'age asc']) .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('sorts by columns provided as expression arguments', () => { const expected = [['foo', 42], ['foo', 23], ['bar', 23]]; const actual = []; return table.select('name', 'age') .orderBy('age desc', 'name desc') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('with grouping', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'bar', 23]) .values([3, 'foo', 42]) .values([4, 'foo', 23]) .values([5, 'bar', 23]) .values([6, 'bar', 42]) .execute(); }); it('groups columns provided as an expression array', () => { const expected = [['bar', 42], ['bar', 23], ['foo', 42], ['foo', 23]]; const actual = []; return table.select('name', 'age') .groupBy(['name', 'age']) // MySQL 8 does not ensure GROUP BY order .orderBy(['name ASC', 'age DESC']) .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('groups columns provided as expression arguments', () => { const expected = [['bar', 23], ['foo', 23], ['bar', 42], ['foo', 42]]; const actual = []; return table.select('name', 'age') .groupBy('age', 'name') // MySQL 8 does not ensure GROUP BY order .orderBy('age ASC', 'name ASC') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('with grouping criteria', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'bar', 23]) .values([3, 'foo', 42]) .values([4, 'foo', 23]) .values([5, 'bar', 23]) .values([6, 'bar', 42]) .execute(); }); it('groups columns provided as an expression array', () => { const expected = [['bar', 42], ['foo', 42]]; const actual = []; return table.select('name', 'age') .groupBy(['name', 'age']) .having('age > 23') // MySQL 8 does not ensure GROUP BY order .orderBy(['name ASC', 'age DESC']) .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('groups columns provided as expression arguments', () => { const expected = [['bar', 23], ['foo', 23]]; const actual = []; return table.select('name', 'age') .groupBy('age', 'name') .having('age = 23') // MySQL 8 does not ensure GROUP BY order .orderBy('age ASC', 'name ASC') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('with limit', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'bar', 23]) .values([3, 'baz', 42]) .values([4, 'qux', 23]) .values([5, 'quux', 23]) .execute(); }); it('returns a given number of row', () => { const expected = [[1, 'foo', 42], [2, 'bar', 23], [3, 'baz', 42]]; const actual = []; return table.select() .limit(3) .orderBy('id') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns the rows after a given offset', () => { const expected = [[3, 'baz', 42], [4, 'qux', 23]]; const actual = []; return table.select() .limit(2) .offset(2) .orderBy('id') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('multi-option expressions', () => { beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'bar', 23]) .values([3, 'baz', 42]) .execute(); }); it('returns all documents that match a criteria specified by a grouped expression', () => { const expected = [[1, 'foo', 42], [3, 'baz', 42]]; const actual = []; return table.select() .where("name in ('foo', 'baz')") .orderBy('id') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); it('returns all documents that do not match a criteria specified by a grouped expression', () => { const expected = [[2, 'bar', 23]]; const actual = []; return table.select() .where('age not in (50, 42)') .orderBy('id') .execute(row => actual.push(row)) .then(() => expect(actual).to.deep.equal(expected)); }); }); context('BUG#32687374 rounding for DECIMAL fields', () => { beforeEach('add a DECIMAL column to the existing table', () => { return session.sql(`ALTER TABLE \`${schema.getName()}\`.\`${table.getName()}\` ADD COLUMN fpn DECIMAL (16,2)`) .execute(); }); beforeEach('add fixtures', () => { return table.insert(['id', 'name', 'fpn']) .values([1, 'foo', -56565656.56]) .execute(); }); it('applies a filter using a floating point number expression literal without losing precision', () => { return table.select('name') .where('fpn = -56565656.56') .execute() .then(res => { return expect(res.fetchOne()).to.deep.equal(['foo']); }); }); it('applies a filter using a floating point number placeholder argument without losing precision', () => { return table.select('name') .where('fpn = :float') .bind('float', -56565656.56) .execute() .then(res => { return expect(res.fetchOne()).to.deep.equal(['foo']); }); }); }); 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('add relevant columns to the existing table', () => { return session.sql(`ALTER TABLE \`${schema.getName()}\`.\`${table.getName()}\` ADD COLUMN safeNegative BIGINT, ADD COLUMN safePositive BIGINT UNSIGNED, ADD COLUMN unsafeNegative BIGINT, ADD COLUMN unsafePositive BIGINT UNSIGNED`) .execute(); }); beforeEach('populate the table', async () => { return table.insert('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .values(safeNegative, safePositive, BigInt(unsafeNegative), 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(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [`${safeNegative}`, `${safePositive}`, unsafeNegative, unsafePositive]; const res = await session.getDefaultSchema().getTable(table.getName()) .select('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(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [BigInt(safeNegative), BigInt(safePositive), BigInt(unsafeNegative), BigInt(unsafePositive)]; const res = await session.getDefaultSchema().getTable(table.getName()) .select('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(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [safeNegative, safePositive, unsafeNegative, unsafePositive]; const res = await session.getDefaultSchema().getTable(table.getName()) .select('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(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [safeNegative, safePositive, BigInt(unsafeNegative), BigInt(unsafePositive)]; const res = await session.getDefaultSchema().getTable(table.getName()) .select('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(`mysqlx://${itConfig.user}:${itConfig.password}@${itConfig.host}:${itConfig.port}/${itConfig.schema}?integer-type=${itConfig.integerType}`); const want = [safeNegative, safePositive, unsafeNegative, unsafePositive]; const res = await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(); const got = res.fetchOne(); await session.close(); expect(got).to.deep.equal(want); }); }); context('consumed with 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(itConfig); const want = [[`${safeNegative}`, `${safePositive}`, unsafeNegative, unsafePositive]]; const got = []; await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(row => got.push(row)); 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 = [[BigInt(safeNegative), BigInt(safePositive), BigInt(unsafeNegative), BigInt(unsafePositive)]]; const got = []; await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(row => got.push(row)); 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, unsafePositive]]; const got = []; await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(row => got.push(row)); 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, BigInt(unsafeNegative), BigInt(unsafePositive)]]; const got = []; await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(row => got.push(row)); 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, unsafePositive]]; const got = []; await session.getDefaultSchema().getTable(table.getName()) .select('safeNegative', 'safePositive', 'unsafeNegative', 'unsafePositive') .execute(row => got.push(row)); await session.close(); expect(got).to.deep.equal(want); }); }); }); context('unsafe number of affected items', () => { it('returns the number of records as a JavaScript string', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.STRING, schema: schema.getName() }; const rows = [{ name: 'foo', age: 23 }, { name: 'bar', age: 42 }]; const want = rows.length.toString(); const session = await mysqlx.getSession(itConfig); const res = await session.getDefaultSchema().getTable(table.getName()).insert('name', 'age') .values(rows[0].name, rows[0].age) .values(rows[1].name, rows[1].age) .execute(); const got = res.getAffectedItemsCount(); await session.close(); expect(got).to.equal(want); }); it('returns the number of records as a JavaScript BigInt', async () => { const itConfig = { ...config, ...baseConfig, integerType: mysqlx.IntegerType.BIGINT, schema: schema.getName() }; const rows = [{ name: 'foo', age: 23 }, { name: 'bar', age: 42 }]; const want = BigInt(rows.length); const session = await mysqlx.getSession(itConfig); const res = await session.getDefaultSchema().getTable(table.getName()).insert('name', 'age') .values(rows[0].name, rows[0].age) .values(rows[1].name, rows[1].age) .execute(); const got = res.getAffectedItemsCount(); await session.close(); expect(got).to.equal(want); }); }); context('BUG#35707417', () => { beforeEach('add relevant columns to the existing table', () => { return session.sql(`ALTER TABLE \`${schema.getName()}\`.\`${table.getName()}\` ADD COLUMN de1 DECIMAL(18, 17), ADD COLUMN de2 DECIMAL(18, 13)`) .execute(); }); beforeEach('populate the table', async () => { return table.insert('de1', 'de2') .values('1.23456789012345678', '12345.6789012345678') .execute(); }); it('does not loose precision when retrieving values from fixed-point decimal columns', async () => { const want = ['1.23456789012345678', '12345.6789012345678']; const res = await table.select('de1', 'de2') .execute(); const got = res.fetchOne(); expect(got).to.deep.equal(want); }); }); context('when debug mode is enabled', () => { beforeEach('populate table', () => { return table.insert(['id', 'name', 'age']) .values([1, 'foo', 42]) .values([2, 'bar', 23]) .values([3, 'foo', 50]) .execute(); }); it('logs the basic operation parameters', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.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(table.getName()); expect(crudFind.collection.schema).to.equal(schema.getName()); expect(crudFind.data_model).to.equal('TABLE'); }); }); it('logs the criteria statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-criteria.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.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('name'); expect(crudFind.criteria.operator.param[0].identifier.name).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', 'relational-tables', 'select-with-projection.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.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).to.contain.keys('name'); expect(crudFind.projection[0].source.identifier.name).to.equal('name'); expect(crudFind.projection[0].alias).to.equal('col'); }); }); it('logs the order statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-order.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.getName(), 'age 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('name'); expect(crudFind.order[0].expr.identifier.name).to.equal('age'); expect(crudFind.order[0].direction).to.equal('DESC'); }); }); it('logs the grouping statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-grouping.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.getName(), 'name, AVG(age)', 'name']) .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('name'); expect(crudFind.grouping[0].identifier.name).to.equal('name'); }); }); it('logs the grouping criteria statement data', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-grouping-criteria.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.getName(), 'name, AVG(age) as age', 'name', 'age > 22']) .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', 'identifier'); expect(crudFind.grouping_criteria.operator.param[0].identifier).to.contain.keys('name'); expect(crudFind.grouping_criteria.operator.param[0].identifier.name).to.equal('age'); 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(22); }); }); it('logs the correct locking parameters', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-locking.js'); return fixtures.collectLogs('protocol:outbound:Mysqlx.Crud.Find', script, [schema.getName(), table.getName(), mysqlx.LockContention.SKIP_LOCKED]) .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('SHARED_LOCK'); expect(crudFind.locking_options).to.equal('SKIP_LOCKED'); }); }); it('logs the result set column metadata sent by the server', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select-with-projection.js'); return fixtures.collectLogs('protocol:inbound:Mysqlx.Resultset.ColumnMetaData', script, [schema.getName(), table.getName(), 'age as col']) .then(proc => { expect(proc.logs).to.have.lengthOf(1); const columnMetadata = proc.logs[0]; expect(columnMetadata).to.contain.keys('type', 'name', 'original_name', 'table', 'original_table', 'schema', 'catalog', 'collation', 'fractional_digits', 'length', 'flags'); expect(columnMetadata.type).to.equal('SINT'); // INT is SIGNED by default expect(columnMetadata.name).to.equal('col'); expect(columnMetadata.original_name).to.equal('age'); expect(columnMetadata.table).to.equal(table.getName()); expect(columnMetadata.original_table).to.equal(table.getName()); expect(columnMetadata.schema).to.equal(schema.getName()); expect(columnMetadata.catalog).to.equal('def'); // always "def" expect(columnMetadata.collation).to.equal(0); // no collation for INT expect(columnMetadata.fractional_digits).to.equal(0); expect(columnMetadata.length).to.equal(11); // sizeof INT expect(columnMetadata.flags).to.equal(0); }); }); it('logs the result set row data sent by the server', () => { const script = path.join(__dirname, '..', '..', '..', 'fixtures', 'scripts', 'relational-tables', 'select.js'); return fixtures.collectLogs('protocol:inbound:Mysqlx.Resultset.Row', script, [schema.getName(), table.getName()]) .then(proc => { expect(proc.logs).to.have.lengthOf(3); // number of rows const rows = proc.logs; rows.forEach(row => expect(row).to.contain.keys('fields')); expect(rows[0].fields).to.deep.equal([1, 'foo', 42]); expect(rows[1].fields).to.deep.equal([2, 'bar', 23]); expect(rows[2].fields).to.deep.equal([3, 'foo', 50]); }); }); }); });