| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| 'use strict'; |
| |
| const { assert } = require('chai'); |
| const events = require('events'); |
| const helper = require('../test-helper'); |
| const PrepareHandler = require('../../lib/prepare-handler'); |
| const defaultOptions = require('../../lib/client-options').defaultOptions; |
| const types = require('../../lib/types'); |
| const utils = require('../../lib/utils'); |
| |
| describe('PrepareHandler', function () { |
| |
| describe('getPrepared()', function () { |
| |
| it('should make request when not already prepared', async () => { |
| const client = getClient({ prepareOnAllHosts: false }); |
| const lbp = helper.getLoadBalancingPolicyFake([ { isUp: false }, { ignored: true }, {}, {} ]); |
| await PrepareHandler.getPrepared(client, lbp, 'SELECT QUERY', null); |
| const hosts = lbp.getFixedQueryPlan(); |
| assert.strictEqual(hosts[2].prepareCalled, 1); |
| assert.strictEqual(hosts[3].prepareCalled, 0); |
| }); |
| |
| it('should make the same prepare request once and queue the rest', async () => { |
| const client = getClient(); |
| const lbp = helper.getLoadBalancingPolicyFake([ { } ]); |
| await Promise.all( |
| Array(100).fill(0).map(() => PrepareHandler.getPrepared(client, lbp, 'SELECT QUERY', null))); |
| |
| const hosts = lbp.getFixedQueryPlan(); |
| assert.strictEqual(hosts[0].prepareCalled, 1); |
| }); |
| |
| it('should callback in error if request send fails', async () => { |
| const client = getClient(); |
| const lbp = helper.getLoadBalancingPolicyFake([ {} ], function (q, h, cb) { |
| cb(new Error('Test prepare error')); |
| }); |
| |
| let err; |
| |
| try { |
| await PrepareHandler.getPrepared(client, lbp, 'SELECT QUERY', null); |
| } catch (e) { |
| err = e; |
| } |
| |
| assert.instanceOf(err, Error); |
| const host = lbp.getFixedQueryPlan()[0]; |
| assert.strictEqual(host.prepareCalled, 1); |
| }); |
| |
| it('should retry on next host if request send fails due to socket error', async () => { |
| const client = getClient(); |
| const lbp = helper.getLoadBalancingPolicyFake([ {}, {} ], function (q, h, cb) { |
| if (h.address === '0') { |
| const err = new Error('Test prepare error'); |
| err.isSocketError = true; |
| return cb(err); |
| } |
| cb(null, { id: 100, meta: {} }); |
| }); |
| |
| await PrepareHandler.getPrepared(client, lbp, 'SELECT QUERY', null); |
| |
| const hosts = lbp.getFixedQueryPlan(); |
| assert.strictEqual(hosts[0].prepareCalled, 1); |
| assert.strictEqual(hosts[1].prepareCalled, 1); |
| }); |
| |
| it('should prepare on all UP hosts not ignored', async () => { |
| const client = getClient({ prepareOnAllHosts: true }); |
| const lbp = helper.getLoadBalancingPolicyFake([ { isUp: false }, {}, { isUp: false }, { ignored: true }, {} ]); |
| |
| await PrepareHandler.getPrepared(client, lbp, 'SELECT QUERY', null); |
| |
| const hosts = lbp.getFixedQueryPlan(); |
| |
| const consideredHosts = [ hosts[1], hosts[4] ]; |
| const avoidedHosts = [ hosts[0], hosts[2], hosts[3] ]; |
| |
| consideredHosts.forEach(h => { |
| assert.strictEqual(h.prepareCalled, 1); |
| assert.strictEqual(h.borrowConnection.callCount, 1); |
| }); |
| |
| avoidedHosts.forEach(h => { |
| assert.strictEqual(h.prepareCalled, 0); |
| assert.strictEqual(h.borrowConnection.callCount, 0); |
| }); |
| }); |
| }); |
| |
| describe('prepareAllQueries', function () { |
| it('should switch keyspace per each keyspace and execute', async () => { |
| const host = helper.getHostsMock([ {} ])[0]; |
| const preparedInfoArray = [ |
| { keyspace: 'system', query: 'query1' }, |
| { keyspace: 'system_schema', query: 'query2' }, |
| { keyspace: null, query: 'query3' }, |
| { keyspace: 'userks', query: 'query4' }, |
| { keyspace: 'system', query: 'query5' }, |
| ]; |
| |
| await PrepareHandler.prepareAllQueries(host, preparedInfoArray); |
| assert.deepStrictEqual(host.connectionKeyspace, [ 'system', 'system_schema', 'userks' ]); |
| assert.strictEqual(host.prepareCalled, 5); |
| }); |
| |
| it('should callback when there are no queries to prepare', async () => { |
| await PrepareHandler.prepareAllQueries({}, []); |
| }); |
| |
| it('should callback in error when there is an error borrowing a connection', async () => { |
| const host = helper.getHostsMock([ {} ])[0]; |
| host.borrowConnection = () => Promise.reject(new Error('Test error')); |
| |
| let err; |
| try { |
| await PrepareHandler.prepareAllQueries(host, [{ query: 'query1' }]); |
| } catch (e) { |
| err = e; |
| } |
| helper.assertInstanceOf(err, Error); |
| }); |
| |
| it('should callback in error when there is an error preparing any of the queries', async () => { |
| function prepareOnce(q, h, cb) { |
| if (q === 'query3') { |
| return cb(new Error('Test error')); |
| } |
| cb(); |
| } |
| const host = helper.getHostsMock([ {} ], prepareOnce)[0]; |
| const preparedInfoArray = [ |
| { keyspace: 'system', query: 'query1' }, |
| { keyspace: null, query: 'query2' }, |
| { keyspace: 'system', query: 'query3' } |
| ]; |
| |
| let err; |
| try { |
| await PrepareHandler.prepareAllQueries(host, preparedInfoArray); |
| } catch (e) { |
| err = e; |
| } |
| |
| helper.assertInstanceOf(err, Error); |
| assert.deepStrictEqual(host.connectionKeyspace, ['system']); |
| assert.strictEqual(host.prepareCalled, 2); |
| }); |
| }); |
| }); |
| |
| function getClient(options) { |
| return { |
| metadata: { |
| _infos: {}, |
| getPreparedInfo: function (ks, q) { |
| let info = this._infos[ks + '.' + q]; |
| if (!info) { |
| info = this._infos[ks + '.' + q] = new events.EventEmitter().setMaxListeners(1000); |
| } |
| return info; |
| }, |
| setPreparedById: utils.noop |
| }, |
| options: utils.extend({ logEmitter: () => {}}, defaultOptions(), options), |
| profileManager: { |
| getDistance: function (h) { |
| return h.shouldBeIgnored ? types.distance.ignored : types.distance.local; |
| } |
| } |
| }; |
| } |