Skip to content

Commit f7c60d0

Browse files
authored
fix: revert breaking change in results creation (#2591)
* fix: revert breaking change in results creation * refactor: simplify prototype validation * ci: fix typo * chore: improve performance * chore: fix lint * chore: change from `nativeObjectProps` to `privateObjectProps` * chore: improve error message
1 parent 7f5b395 commit f7c60d0

9 files changed

+189
-167
lines changed

lib/helpers.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
function srcEscape(str) {
1818
return JSON.stringify({
19-
[str]: 1
19+
[str]: 1,
2020
}).slice(1, -3);
2121
}
2222

@@ -29,7 +29,7 @@ try {
2929
const REQUIRE_TERMINATOR = '';
3030
highlightFn = require(`cardinal${REQUIRE_TERMINATOR}`).highlight;
3131
} catch (err) {
32-
highlightFn = text => {
32+
highlightFn = (text) => {
3333
if (!cardinalRecommended) {
3434
// eslint-disable-next-line no-console
3535
console.log('For nicer debug output consider install cardinal@^2.0.0');
@@ -56,10 +56,20 @@ exports.printDebugWithCode = printDebugWithCode;
5656
*/
5757
function typeMatch(type, list, Types) {
5858
if (Array.isArray(list)) {
59-
return list.some(t => type === Types[t]);
59+
return list.some((t) => type === Types[t]);
6060
}
6161

6262
return !!list;
6363
}
6464

6565
exports.typeMatch = typeMatch;
66+
67+
const privateObjectProps = new Set([
68+
'__defineGetter__',
69+
'__defineSetter__',
70+
'__lookupGetter__',
71+
'__lookupSetter__',
72+
'__proto__',
73+
]);
74+
75+
exports.privateObjectProps = privateObjectProps;

lib/parsers/binary_parser.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,7 @@ function compile(fields, options, config) {
122122
if (options.rowsAsArray) {
123123
parserFn(`const result = new Array(${fields.length});`);
124124
} else {
125-
parserFn('const result = Object.create(null);');
126-
parserFn(`Object.defineProperty(result, "constructor", {
127-
value: Object.create(null),
128-
writable: false,
129-
configurable: false,
130-
enumerable: false
131-
});`);
125+
parserFn('const result = {};');
132126
}
133127

134128
// Global typeCast
@@ -152,6 +146,13 @@ function compile(fields, options, config) {
152146

153147
for (let i = 0; i < fields.length; i++) {
154148
fieldName = helpers.srcEscape(fields[i].name);
149+
150+
if (helpers.privateObjectProps.has(fields[i].name)) {
151+
throw new Error(
152+
`The field name (${fieldName}) can't be the same as an object's private property.`,
153+
);
154+
}
155+
155156
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
156157

157158
if (typeof options.nestTables === 'string') {
@@ -160,9 +161,7 @@ function compile(fields, options, config) {
160161
)}]`;
161162
} else if (options.nestTables === true) {
162163
tableName = helpers.srcEscape(fields[i].table);
163-
parserFn(
164-
`if (!result[${tableName}]) result[${tableName}] = Object.create(null);`,
165-
);
164+
parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
166165
lvalue = `result[${tableName}][${fieldName}]`;
167166
} else if (options.rowsAsArray) {
168167
lvalue = `result[${i.toString(10)}]`;

lib/parsers/text_parser.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,7 @@ function compile(fields, options, config) {
131131
if (options.rowsAsArray) {
132132
parserFn(`const result = new Array(${fields.length});`);
133133
} else {
134-
parserFn('const result = Object.create(null);');
135-
parserFn(`Object.defineProperty(result, "constructor", {
136-
value: Object.create(null),
137-
writable: false,
138-
configurable: false,
139-
enumerable: false
140-
});`);
134+
parserFn('const result = {};');
141135
}
142136

143137
const resultTables = {};
@@ -149,16 +143,21 @@ function compile(fields, options, config) {
149143
}
150144
resultTablesArray = Object.keys(resultTables);
151145
for (let i = 0; i < resultTablesArray.length; i++) {
152-
parserFn(
153-
`result[${helpers.srcEscape(resultTablesArray[i])}] = Object.create(null);`,
154-
);
146+
parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
155147
}
156148
}
157149

158150
let lvalue = '';
159151
let fieldName = '';
160152
for (let i = 0; i < fields.length; i++) {
161153
fieldName = helpers.srcEscape(fields[i].name);
154+
155+
if (helpers.privateObjectProps.has(fields[i].name)) {
156+
throw new Error(
157+
`The field name (${fieldName}) can't be the same as an object's private property.`,
158+
);
159+
}
160+
162161
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
163162
if (typeof options.nestTables === 'string') {
164163
lvalue = `result[${helpers.srcEscape(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { test, describe, assert } from 'poku';
2+
import { createConnection, describeOptions } from '../../../common.test.cjs';
3+
4+
const connection = createConnection().promise();
5+
6+
describe('Execute: Results Creation', describeOptions);
7+
8+
Promise.all([
9+
test(async () => {
10+
const expected = [
11+
{
12+
test: 2,
13+
},
14+
];
15+
const emptyObject = {};
16+
const proto = Object.getPrototypeOf(emptyObject);
17+
const privateObjectProps = Object.getOwnPropertyNames(proto);
18+
19+
const [results] = await connection.execute('SELECT 1+1 AS `test`');
20+
21+
assert.deepStrictEqual(results, expected, 'Ensure exact object "results"');
22+
assert.deepStrictEqual(
23+
Object.getOwnPropertyNames(results[0]),
24+
Object.getOwnPropertyNames(expected[0]),
25+
'Deep ensure exact object "results"',
26+
);
27+
assert.deepStrictEqual(
28+
Object.getPrototypeOf(results[0]),
29+
Object.getPrototypeOf({}),
30+
'Ensure clean properties in results items',
31+
);
32+
33+
privateObjectProps.forEach((prop) => {
34+
assert(prop in results[0], `Ensure ${prop} exists`);
35+
});
36+
37+
results[0].customProp = true;
38+
assert.strictEqual(
39+
results[0].customProp,
40+
true,
41+
'Ensure that the end-user is able to use custom props',
42+
);
43+
}),
44+
test(async () => {
45+
const [result] = await connection.execute('SET @1 = 1;');
46+
47+
assert.strictEqual(
48+
result.constructor.name,
49+
'ResultSetHeader',
50+
'Ensure constructor name in result object',
51+
);
52+
}),
53+
]).then(async () => {
54+
await connection.end();
55+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { test, describe, assert } from 'poku';
2+
import { createConnection, describeOptions } from '../../../common.test.cjs';
3+
4+
const connection = createConnection().promise();
5+
6+
describe('Query: Results Creation', describeOptions);
7+
8+
Promise.all([
9+
test(async () => {
10+
const expected = [
11+
{
12+
test: 2,
13+
},
14+
];
15+
const emptyObject = {};
16+
const proto = Object.getPrototypeOf(emptyObject);
17+
const privateObjectProps = Object.getOwnPropertyNames(proto);
18+
19+
const [results] = await connection.query('SELECT 1+1 AS `test`');
20+
21+
assert.deepStrictEqual(results, expected, 'Ensure exact object "results"');
22+
assert.deepStrictEqual(
23+
Object.getOwnPropertyNames(results[0]),
24+
Object.getOwnPropertyNames(expected[0]),
25+
'Deep ensure exact object "results"',
26+
);
27+
assert.deepStrictEqual(
28+
Object.getPrototypeOf(results[0]),
29+
Object.getPrototypeOf({}),
30+
'Ensure clean properties in results items',
31+
);
32+
33+
privateObjectProps.forEach((prop) => {
34+
assert(prop in results[0], `Ensure ${prop} exists`);
35+
});
36+
37+
results[0].customProp = true;
38+
assert.strictEqual(
39+
results[0].customProp,
40+
true,
41+
'Ensure that the end-user is able to use custom props',
42+
);
43+
}),
44+
test(async () => {
45+
const [result] = await connection.query('SET @1 = 1;');
46+
47+
assert.strictEqual(
48+
result.constructor.name,
49+
'ResultSetHeader',
50+
'Ensure constructor name in result object',
51+
);
52+
}),
53+
]).then(async () => {
54+
await connection.end();
55+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, assert } from 'poku';
2+
import { describeOptions } from '../../../common.test.cjs';
3+
import getBinaryParser from '../../../../lib/parsers/binary_parser.js';
4+
import { srcEscape } from '../../../../lib/helpers.js';
5+
import { privateObjectProps } from '../../../../lib/helpers.js';
6+
7+
describe('Binary Parser: Block Native Object Props', describeOptions);
8+
9+
const blockedFields = Array.from(privateObjectProps).map((prop) => [
10+
{ name: prop },
11+
]);
12+
13+
blockedFields.forEach((fields) => {
14+
try {
15+
getBinaryParser(fields, {}, {});
16+
assert.fail('An error was expected');
17+
} catch (error) {
18+
assert.strictEqual(
19+
error.message,
20+
`The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,
21+
`Ensure safe ${fields[0].name}`,
22+
);
23+
}
24+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, assert } from 'poku';
2+
import { describeOptions } from '../../../common.test.cjs';
3+
import TextRowParser from '../../../../lib/parsers/text_parser.js';
4+
import { srcEscape } from '../../../../lib/helpers.js';
5+
import { privateObjectProps } from '../../../../lib/helpers.js';
6+
7+
describe('Text Parser: Block Native Object Props', describeOptions);
8+
9+
const blockedFields = Array.from(privateObjectProps).map((prop) => [
10+
{ name: prop },
11+
]);
12+
13+
blockedFields.forEach((fields) => {
14+
try {
15+
TextRowParser(fields, {}, {});
16+
assert.fail('An error was expected');
17+
} catch (error) {
18+
assert.strictEqual(
19+
error.message,
20+
`The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,
21+
`Ensure safe ${fields[0].name}`,
22+
);
23+
}
24+
});

test/esm/unit/parsers/prototype-binary-results.test.mjs

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)