Skip to content

Commit 8c590d7

Browse files
Add polyfills to make apache-arrow work in Node@14 (#219)
* Add polyfills to make `apache-arrow` work in Node@14 Signed-off-by: Levko Kravets <levko.ne@gmail.com> * Add tests Signed-off-by: Levko Kravets <levko.ne@gmail.com> * Run unit tests on Node@14 Signed-off-by: Levko Kravets <levko.ne@gmail.com> --------- Signed-off-by: Levko Kravets <levko.ne@gmail.com>
1 parent 086f5bb commit 8c590d7

5 files changed

Lines changed: 132 additions & 4 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ jobs:
3535
strategy:
3636
matrix:
3737
# only LTS versions starting from the lowest we support
38-
# TODO: Include Nodejs@14
39-
node-version: ['16', '18', '20']
38+
node-version: ['14', '16', '18', '20']
4039
env:
4140
cache-name: cache-node-modules
4241
NYC_REPORT_DIR: coverage_unit_node${{ matrix.node-version }}

lib/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Don't move this import - it should be placed before any other
2+
import './polyfills';
3+
14
import { Thrift } from 'thrift';
25
import TCLIService from '../thrift/TCLIService';
36
import TCLIService_types from '../thrift/TCLIService_types';

lib/polyfills.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
// `Array.at` / `TypedArray.at` is supported only since Nodejs@16.6.0
4+
// These methods are massively used by `apache-arrow@13`, but we have
5+
// to use this version because older ones contain some other nasty bugs
6+
7+
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tointegerorinfinity
8+
function toIntegerOrInfinity(value: unknown): number {
9+
const result = Number(value);
10+
11+
// Return `0` for NaN; return `+Infinity` / `-Infinity` as is
12+
if (!Number.isFinite(result)) {
13+
return Number.isNaN(result) ? 0 : result;
14+
}
15+
16+
return Math.trunc(result);
17+
}
18+
19+
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tolength
20+
function toLength(value: unknown): number {
21+
const result = toIntegerOrInfinity(value);
22+
return result > 0 ? Math.min(result, Number.MAX_SAFE_INTEGER) : 0;
23+
}
24+
25+
// https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.at
26+
export function at<T>(this: Array<T>, index: number): T | undefined {
27+
const length = toLength(this.length);
28+
const relativeIndex = toIntegerOrInfinity(index);
29+
const absoluteIndex = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
30+
return absoluteIndex >= 0 && absoluteIndex < length ? this[absoluteIndex] : undefined;
31+
}
32+
33+
const ArrayConstructors = [
34+
global.Array,
35+
global.Int8Array,
36+
global.Uint8Array,
37+
global.Uint8ClampedArray,
38+
global.Int16Array,
39+
global.Uint16Array,
40+
global.Int32Array,
41+
global.Uint32Array,
42+
global.Float32Array,
43+
global.Float64Array,
44+
global.BigInt64Array,
45+
global.BigUint64Array,
46+
];
47+
48+
ArrayConstructors.forEach((ArrayConstructor) => {
49+
ArrayConstructor.prototype.at = ArrayConstructor.prototype.at ?? at;
50+
});

tests/e2e/arrow.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ async function deleteTable(session, tableName) {
4848
async function initializeTable(session, tableName) {
4949
await deleteTable(session, tableName);
5050

51-
const createTable = fixtures.createTableSql.replaceAll('${table_name}', tableName);
51+
const createTable = fixtures.createTableSql.replace(/\$\{table_name\}/g, tableName);
5252
await execute(session, createTable);
5353

54-
const insertData = fixtures.insertDataSql.replaceAll('${table_name}', tableName);
54+
const insertData = fixtures.insertDataSql.replace(/\$\{table_name\}/g, tableName);
5555
await execute(session, insertData);
5656
}
5757

tests/unit/polyfills.test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const { expect } = require('chai');
2+
const { at } = require('../../dist/polyfills');
3+
4+
const defaultArrayMock = {
5+
0: 'a',
6+
1: 'b',
7+
2: 'c',
8+
3: 'd',
9+
length: 4,
10+
at,
11+
};
12+
13+
describe('Array.at', () => {
14+
it('should handle zero index', () => {
15+
const obj = { ...defaultArrayMock };
16+
expect(obj.at(0)).to.eq('a');
17+
expect(obj.at(Number('+0'))).to.eq('a');
18+
expect(obj.at(Number('-0'))).to.eq('a');
19+
});
20+
21+
it('should handle positive index', () => {
22+
const obj = { ...defaultArrayMock };
23+
expect(obj.at(2)).to.eq('c');
24+
expect(obj.at(2.2)).to.eq('c');
25+
expect(obj.at(2.8)).to.eq('c');
26+
});
27+
28+
it('should handle negative index', () => {
29+
const obj = { ...defaultArrayMock };
30+
expect(obj.at(-2)).to.eq('c');
31+
expect(obj.at(-2.2)).to.eq('c');
32+
expect(obj.at(-2.8)).to.eq('c');
33+
});
34+
35+
it('should handle positive infinity index', () => {
36+
const obj = { ...defaultArrayMock };
37+
expect(obj.at(Number.POSITIVE_INFINITY)).to.be.undefined;
38+
});
39+
40+
it('should handle negative infinity index', () => {
41+
const obj = { ...defaultArrayMock };
42+
expect(obj.at(Number.NEGATIVE_INFINITY)).to.be.undefined;
43+
});
44+
45+
it('should handle non-numeric index', () => {
46+
const obj = { ...defaultArrayMock };
47+
expect(obj.at('2')).to.eq('c');
48+
});
49+
50+
it('should handle NaN index', () => {
51+
const obj = { ...defaultArrayMock };
52+
expect(obj.at(Number.NaN)).to.eq('a');
53+
expect(obj.at('invalid')).to.eq('a');
54+
});
55+
56+
it('should handle index out of bounds', () => {
57+
const obj = { ...defaultArrayMock };
58+
expect(obj.at(10)).to.be.undefined;
59+
expect(obj.at(-10)).to.be.undefined;
60+
});
61+
62+
it('should handle zero length', () => {
63+
const obj = { ...defaultArrayMock, length: 0 };
64+
expect(obj.at(2)).to.be.undefined;
65+
});
66+
67+
it('should handle negative length', () => {
68+
const obj = { ...defaultArrayMock, length: -4 };
69+
expect(obj.at(2)).to.be.undefined;
70+
});
71+
72+
it('should handle non-numeric length', () => {
73+
const obj = { ...defaultArrayMock, length: 'invalid' };
74+
expect(obj.at(2)).to.be.undefined;
75+
});
76+
});

0 commit comments

Comments
 (0)