Skip to content

Commit c355ada

Browse files
committed
chore: add tests for native
1 parent f78be28 commit c355ada

File tree

3 files changed

+118
-24
lines changed

3 files changed

+118
-24
lines changed

packages/pg/lib/native/client.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ var Client = (module.exports = function (config) {
2626
types: this._types,
2727
})
2828

29+
// Store maxResultSize configuration
30+
this._maxResultSize = config.maxResultSize
31+
2932
this._queryQueue = []
3033
this._ending = false
3134
this._connecting = false
@@ -100,6 +103,9 @@ Client.prototype._connect = function (cb) {
100103
// set internal states to connected
101104
self._connected = true
102105

106+
// Add a reference to the client for error bubbling
107+
self.native.connection = self;
108+
103109
// handle connection errors from the native layer
104110
self.native.on('error', function (err) {
105111
self._queryable = false
@@ -196,7 +202,7 @@ Client.prototype.query = function (config, values, callback) {
196202

197203
// we already returned an error,
198204
// just do nothing if query completes
199-
query.callback = () => {}
205+
query.callback = () => { }
200206

201207
// Remove from queue
202208
var index = this._queryQueue.indexOf(query)
@@ -289,14 +295,14 @@ Client.prototype._pulseQueryQueue = function (initialConnection) {
289295
// attempt to cancel an in-progress query
290296
Client.prototype.cancel = function (query) {
291297
if (this._activeQuery === query) {
292-
this.native.cancel(function () {})
298+
this.native.cancel(function () { })
293299
} else if (this._queryQueue.indexOf(query) !== -1) {
294300
this._queryQueue.splice(this._queryQueue.indexOf(query), 1)
295301
}
296302
}
297303

298-
Client.prototype.ref = function () {}
299-
Client.prototype.unref = function () {}
304+
Client.prototype.ref = function () { }
305+
Client.prototype.unref = function () { }
300306

301307
Client.prototype.setTypeParser = function (oid, format, parseFn) {
302308
return this._types.setTypeParser(oid, format, parseFn)

packages/pg/lib/native/query.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ NativeQuery.prototype.handleError = function (err) {
5656
err[normalizedFieldName] = fields[key]
5757
}
5858
}
59+
60+
// For maxResultSize exceeded errors, make sure we emit the error to the client too
61+
if (err.code === 'RESULT_SIZE_EXCEEDED') {
62+
if (this.native && this.native.connection) {
63+
// Need to emit the error on the client/connection level too
64+
process.nextTick(() => {
65+
this.native.connection.emit('error', err);
66+
});
67+
}
68+
}
69+
5970
if (this.callback) {
6071
this.callback(err)
6172
} else {
@@ -89,6 +100,9 @@ NativeQuery.prototype.submit = function (client) {
89100
this.native = client.native
90101
client.native.arrayMode = this._arrayMode
91102

103+
// Get the maxResultSize from the client if it's set
104+
this._maxResultSize = client._maxResultSize
105+
92106
var after = function (err, rows, results) {
93107
client.native.arrayMode = false
94108
setImmediate(function () {
@@ -100,6 +114,30 @@ NativeQuery.prototype.submit = function (client) {
100114
return self.handleError(err)
101115
}
102116

117+
// Check the result size if maxResultSize is configured
118+
if (self._maxResultSize) {
119+
// Calculate result size (rough approximation)
120+
let resultSize = 0;
121+
122+
// For multiple result sets
123+
if (results.length > 1) {
124+
for (let i = 0; i < rows.length; i++) {
125+
resultSize += self._calculateResultSize(rows[i]);
126+
}
127+
} else if (rows.length > 0) {
128+
resultSize = self._calculateResultSize(rows);
129+
}
130+
131+
// If the size limit is exceeded, generate an error
132+
if (resultSize > self._maxResultSize) {
133+
const error = new Error('Query result size exceeded the configured limit');
134+
error.code = 'RESULT_SIZE_EXCEEDED';
135+
error.resultSize = resultSize;
136+
error.maxResultSize = self._maxResultSize;
137+
return self.handleError(error);
138+
}
139+
}
140+
103141
// emit row events for each row in the result
104142
if (self._emitRowEvents) {
105143
if (results.length > 1) {
@@ -166,3 +204,59 @@ NativeQuery.prototype.submit = function (client) {
166204
client.native.query(this.text, after)
167205
}
168206
}
207+
208+
// Helper method to estimate the size of a result set
209+
NativeQuery.prototype._calculateResultSize = function (rows) {
210+
let size = 0;
211+
212+
// For empty results, return 0
213+
if (!rows || rows.length === 0) {
214+
return 0;
215+
}
216+
217+
// For array mode, calculate differently
218+
if (this._arrayMode) {
219+
// Just use a rough approximation based on number of rows
220+
return rows.length * 100;
221+
}
222+
223+
// For each row, approximate its size
224+
for (let i = 0; i < rows.length; i++) {
225+
const row = rows[i];
226+
227+
// Add base row size
228+
size += 24; // Overhead per row
229+
230+
// Add size of each column
231+
for (const key in row) {
232+
if (Object.prototype.hasOwnProperty.call(row, key)) {
233+
const value = row[key];
234+
235+
// Add key size
236+
size += key.length * 2; // Assume 2 bytes per character
237+
238+
// Add value size based on type
239+
if (value === null || value === undefined) {
240+
size += 8;
241+
} else if (typeof value === 'string') {
242+
size += value.length * 2; // Assume 2 bytes per character
243+
} else if (typeof value === 'number') {
244+
size += 8;
245+
} else if (typeof value === 'boolean') {
246+
size += 4;
247+
} else if (value instanceof Date) {
248+
size += 8;
249+
} else if (Buffer.isBuffer(value)) {
250+
size += value.length;
251+
} else if (Array.isArray(value)) {
252+
size += 16 + value.length * 8;
253+
} else {
254+
// For objects, use a rough estimate
255+
size += 32 + JSON.stringify(value).length * 2;
256+
}
257+
}
258+
}
259+
}
260+
261+
return size;
262+
}

packages/pg/test/integration/client/max-result-size-tests.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ process.on('unhandledRejection', function (e) {
1010

1111
const suite = new helper.Suite()
1212

13-
// Test that result size exceeding maxResultSize properly raises an error
1413
suite.test('maxResultSize limit triggers error', (cb) => {
14+
// Check if we're running with the native client
15+
const isNative = helper.args.native
16+
console.log(isNative ? 'Testing with native client' : 'Testing with JavaScript client')
17+
1518
// Create a pool with a very small result size limit
1619
const pool = new pg.Pool({
1720
maxResultSize: 100, // Very small limit (100 bytes)
1821
...helper.args,
1922
})
2023

21-
// Track if we've seen the size exceeded error
2224
let sizeExceededErrorSeen = false
2325

24-
// Set up error handler on pool
2526
pool.on('error', (err) => {
2627
console.log('Pool error:', err.message, err.code)
2728
})
@@ -33,21 +34,20 @@ suite.test('maxResultSize limit triggers error', (cb) => {
3334
client.on('error', (err) => {
3435
console.log('Client error event:', err.message, err.code)
3536

36-
// If we get the expected error, mark it
37-
if (err.message === 'Query result size exceeded the configured limit') {
38-
assert.equal(err.code, 'RESULT_SIZE_EXCEEDED', 'Error should have RESULT_SIZE_EXCEEDED code')
37+
// If we get any size exceeded error, mark it
38+
if (err.code === 'RESULT_SIZE_EXCEEDED' ||
39+
err.message === 'Query result size exceeded the configured limit') {
3940
sizeExceededErrorSeen = true
4041
}
4142
})
4243

43-
// Create a temp table
4444
return client
4545
.query('CREATE TEMP TABLE large_result_test(id SERIAL, data TEXT)')
4646
.then(() => {
47-
// Insert data that will exceed the size limit when selected
47+
// Insert rows that will exceed the size limit when queried
4848
const insertPromises = []
4949
for (let i = 0; i < 20; i++) {
50-
// Each row will have 50 bytes of data
50+
// Each row will have enough data to eventually exceed our limit
5151
const data = 'x'.repeat(50)
5252
insertPromises.push(client.query('INSERT INTO large_result_test(data) VALUES($1)', [data]))
5353
}
@@ -56,7 +56,6 @@ suite.test('maxResultSize limit triggers error', (cb) => {
5656
.then(() => {
5757
console.log('Running query that should exceed size limit...')
5858

59-
// This query should fail due to exceeding size limit
6059
return client
6160
.query('SELECT * FROM large_result_test')
6261
.then(() => {
@@ -65,15 +64,14 @@ suite.test('maxResultSize limit triggers error', (cb) => {
6564
.catch((err) => {
6665
console.log('Query error caught:', err.message, err.code)
6766

68-
// The error should have the correct code
67+
// Both implementations should throw an error with this code
6968
assert.equal(err.code, 'RESULT_SIZE_EXCEEDED', 'Error should have RESULT_SIZE_EXCEEDED code')
7069

71-
// Give a little time for error events to be processed
70+
// Give time for error events to propagate
7271
return new Promise((resolve) => setTimeout(resolve, 100)).then(() => {
73-
// Verify we saw the expected error event
72+
// Verify we saw the error event
7473
assert(sizeExceededErrorSeen, 'Should have seen the size exceeded error event')
7574

76-
// Attempt cleanup but don't fail if it errors
7775
return client.query('DROP TABLE IF EXISTS large_result_test').catch(() => {
7876
/* ignore cleanup errors */
7977
})
@@ -85,16 +83,17 @@ suite.test('maxResultSize limit triggers error', (cb) => {
8583
pool.end(cb)
8684
})
8785
.catch((err) => {
86+
console.error('Test error:', err.message)
8887
client.release()
8988
pool.end(() => cb(err))
9089
})
9190
})
9291
.catch((err) => {
92+
console.error('Connection error:', err.message)
9393
pool.end(() => cb(err))
9494
})
9595
})
9696

97-
// Test that results under the maxResultSize limit work normally
9897
suite.test('results under maxResultSize limit work correctly', (cb) => {
9998
// Create a pool with a reasonably large limit
10099
const pool = new pg.Pool({
@@ -105,21 +104,16 @@ suite.test('results under maxResultSize limit work correctly', (cb) => {
105104
pool
106105
.connect()
107106
.then((client) => {
108-
// Create a temp table
109107
return client
110108
.query('CREATE TEMP TABLE small_result_test(id SERIAL, data TEXT)')
111109
.then(() => {
112-
// Insert a small amount of data
113110
return client.query('INSERT INTO small_result_test(data) VALUES($1)', ['small_data'])
114111
})
115112
.then(() => {
116-
// This query should succeed
117113
return client.query('SELECT * FROM small_result_test').then((result) => {
118-
// Verify the result
119114
assert.equal(result.rows.length, 1, 'Should get 1 row')
120115
assert.equal(result.rows[0].data, 'small_data', 'Data should match')
121116

122-
// Clean up
123117
return client.query('DROP TABLE small_result_test')
124118
})
125119
})

0 commit comments

Comments
 (0)