libpq
Version:
Low-level native bindings to PostgreSQL libpq
240 lines (194 loc) • 7.23 kB
JavaScript
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/);
});
});
});