UNPKG

libpq

Version:

Low-level native bindings to PostgreSQL libpq

240 lines (194 loc) 7.23 kB
var PQ = require('../'); var assert = require('assert'); describe('pipeline mode', function () { this.timeout(10000); var pq; beforeEach(function () { pq = new PQ(); pq.connectSync(); }); afterEach(function () { if (pq.connected) { pq.finish(); } }); describe('pipelineModeSupported', function () { it('returns a boolean', function () { var supported = pq.pipelineModeSupported(); assert.strictEqual(typeof supported, 'boolean'); }); }); // Skip pipeline tests if not supported // Pipeline mode requires BOTH: // 1. Client library compiled with PostgreSQL 14+ (pipelineModeSupported) // 2. Server version 14+ (serverVersion >= 140000) describe('pipeline operations', function () { before(function () { // Check if pipeline mode is supported using a temporary connection var testPq = new PQ(); testPq.connectSync(); var clientSupported = testPq.pipelineModeSupported(); var serverVersion = testPq.serverVersion(); testPq.finish(); // Pipeline mode requires PostgreSQL 14+ on both client and server var serverSupported = serverVersion >= 140000; if (!clientSupported) { console.log('Pipeline mode not supported by client library. Skipping pipeline tests.'); this.skip(); } if (!serverSupported) { console.log('Pipeline mode not supported by server (version: ' + serverVersion + ', requires 140000+). Skipping pipeline tests.'); this.skip(); } }); it('starts in non-pipeline mode', function () { assert.strictEqual(pq.pipelineStatus(), PQ.PIPELINE_OFF); }); it('can enter pipeline mode', function () { var result = pq.enterPipelineMode(); assert.strictEqual(result, true); assert.strictEqual(pq.pipelineStatus(), PQ.PIPELINE_ON); }); it('can exit pipeline mode', function () { pq.enterPipelineMode(); var result = pq.exitPipelineMode(); assert.strictEqual(result, true); assert.strictEqual(pq.pipelineStatus(), PQ.PIPELINE_OFF); }); it('can send multiple queries in pipeline mode', function (done) { pq.enterPipelineMode(); pq.setNonBlocking(true); // Send multiple queries assert.strictEqual(pq.sendQueryParams('SELECT $1::int as num', ['1']), true); assert.strictEqual(pq.sendQueryParams('SELECT $1::int as num', ['2']), true); assert.strictEqual(pq.sendQueryParams('SELECT $1::int as num', ['3']), true); // Send sync to mark end of pipeline assert.strictEqual(pq.pipelineSync(), true); // Flush the queries pq.flush(); // Read results using polling approach var results = []; var readResults = function() { // Consume any available input pq.consumeInput(); // Process all available results while (!pq.isBusy()) { var hasResult = pq.getResult(); if (!hasResult) { break; } var status = pq.resultStatus(); if (status === 'PGRES_TUPLES_OK') { results.push(parseInt(pq.getvalue(0, 0), 10)); } else if (status === 'PGRES_PIPELINE_SYNC') { return true; } } return false; }; // Poll for results var attempts = 0; var maxAttempts = 100; var poll = function() { attempts++; if (readResults()) { assert.deepStrictEqual(results, [1, 2, 3]); pq.exitPipelineMode(); done(); return; } if (attempts >= maxAttempts) { done(new Error('Timeout waiting for pipeline results. Got: ' + JSON.stringify(results))); return; } setTimeout(poll, 50); }; poll(); }); it('handles errors in pipeline mode', function (done) { pq.enterPipelineMode(); pq.setNonBlocking(true); // Send a valid query pq.sendQueryParams('SELECT $1::int as num', ['1']); // Send an invalid query - division by zero error pq.sendQueryParams('SELECT 1/0', []); // Send another valid query (will be skipped due to error) pq.sendQueryParams('SELECT $1::int as num', ['3']); // Send sync pq.pipelineSync(); pq.flush(); var gotError = false; var statuses = []; var readResults = function() { pq.consumeInput(); while (!pq.isBusy()) { var hasResult = pq.getResult(); if (!hasResult) { break; } var status = pq.resultStatus(); statuses.push(status); // Check for error status or pipeline aborted state if (status === 'PGRES_FATAL_ERROR') { gotError = true; } // Also check if pipeline became aborted (error occurred) if (pq.pipelineStatus() === PQ.PIPELINE_ABORTED) { gotError = true; } if (status === 'PGRES_PIPELINE_SYNC') { return true; } } return false; }; var attempts = 0; var maxAttempts = 100; var poll = function() { attempts++; if (readResults()) { // Either we got an explicit error or the pipeline was aborted assert.strictEqual(gotError, true, 'Should have received an error. Statuses: ' + statuses.join(', ')); pq.exitPipelineMode(); done(); return; } if (attempts >= maxAttempts) { done(new Error('Timeout waiting for pipeline error results. Statuses: ' + statuses.join(', '))); return; } setTimeout(poll, 50); }; poll(); }); it('sendFlushRequest works', function () { pq.enterPipelineMode(); pq.setNonBlocking(true); pq.sendQueryParams('SELECT 1 as num', []); var result = pq.sendFlushRequest(); assert.strictEqual(result, true); // Just verify the function works, don't need to read results pq.pipelineSync(); }); }); describe('pipeline constants', function () { it('exports pipeline status constants', function () { assert.strictEqual(PQ.PIPELINE_OFF, 0); assert.strictEqual(PQ.PIPELINE_ON, 1); assert.strictEqual(PQ.PIPELINE_ABORTED, 2); }); }); describe('error handling when not supported', function () { it('throws helpful error when pipeline mode not supported', function () { // Create a mock PQ without pipeline methods var mockPQ = { $enterPipelineMode: undefined }; // Bind the prototype method to our mock var enterFn = PQ.prototype.enterPipelineMode.bind(mockPQ); assert.throws(function () { enterFn(); }, /Pipeline mode is not supported/); }); }); });