X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
}
]
}
},
{
"files": ["*.test.js", "*.test.ts"],
"rules": {
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "off"
}
}
]
}
243 changes: 156 additions & 87 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"e2e": "nyc --reporter=lcov --report-dir=${NYC_REPORT_DIR:-coverage_e2e} mocha --config tests/e2e/.mocharc.js",
"test": "nyc --reporter=lcov --report-dir=${NYC_REPORT_DIR:-coverage_unit} mocha --config tests/unit/.mocharc.js",
"update-version": "node bin/update-version.js && prettier --write ./lib/version.ts",
"build": "npm run update-version && tsc",
"watch": "tsc -w",
"build": "npm run update-version && tsc --project tsconfig.build.json",
"watch": "tsc --project tsconfig.build.json --watch",
"type-check": "tsc --noEmit",
"prettier": "prettier . --check",
"prettier:fix": "prettier . --write",
"lint": "eslint lib/** --ext .js,.ts",
"lint": "eslint lib/** tests/e2e/** --ext .js,.ts",
"lint:fix": "eslint lib/** --ext .js,.ts --fix"
},
"repository": {
Expand All @@ -48,11 +48,13 @@
"license": "Apache 2.0",
"devDependencies": {
"@types/chai": "^4.3.14",
"@types/http-proxy": "^1.17.14",
"@types/lz4": "^0.6.4",
"@types/mocha": "^10.0.6",
"@types/node": "^18.11.9",
"@types/node-fetch": "^2.6.4",
"@types/node-int64": "^0.4.29",
"@types/sinon": "^17.0.3",
"@types/thrift": "^0.10.11",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.44.0",
Expand All @@ -70,7 +72,7 @@
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prettier": "^2.8.4",
"sinon": "^14.0.0",
"sinon": "^17.0.1",
"ts-node": "^10.9.2",
"typescript": "^4.9.3"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/.mocharc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const allSpecs = 'tests/e2e/**/*.test.js';
const allSpecs = 'tests/e2e/**/*.test.ts';

const argvSpecs = process.argv.slice(4);

Expand Down
47 changes: 29 additions & 18 deletions tests/e2e/arrow.test.js → tests/e2e/arrow.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
const { expect } = require('chai');
const sinon = require('sinon');
const config = require('./utils/config');
const logger = require('./utils/logger')(config.logger);
const { DBSQLClient } = require('../../lib');
const ArrowResultHandler = require('../../lib/result/ArrowResultHandler').default;
const ArrowResultConverter = require('../../lib/result/ArrowResultConverter').default;
const ResultSlicer = require('../../lib/result/ResultSlicer').default;
import { expect } from 'chai';
import sinon from 'sinon';
import { DBSQLClient } from '../../lib';
import { ClientConfig } from '../../lib/contracts/IClientContext';
import IDBSQLSession from '../../lib/contracts/IDBSQLSession';
import ArrowResultHandler from '../../lib/result/ArrowResultHandler';
import ArrowResultConverter from '../../lib/result/ArrowResultConverter';
import ResultSlicer from '../../lib/result/ResultSlicer';

import config from './utils/config';

const fixtures = require('../fixtures/compatibility');
const { expected: expectedColumn } = require('../fixtures/compatibility/column');
const { expected: expectedArrow } = require('../fixtures/compatibility/arrow');
const { expected: expectedArrowNativeTypes } = require('../fixtures/compatibility/arrow_native_types');

const { fixArrowResult } = fixtures;

async function openSession(customConfig) {
async function openSession(customConfig: Partial<ClientConfig> = {}) {
const client = new DBSQLClient();

const clientConfig = client.getConfig();
Expand All @@ -29,23 +32,23 @@ async function openSession(customConfig) {
});

return connection.openSession({
initialCatalog: config.database[0],
initialSchema: config.database[1],
initialCatalog: config.catalog,
initialSchema: config.schema,
});
}

async function execute(session, statement) {
async function execute(session: IDBSQLSession, statement: string) {
const operation = await session.executeStatement(statement);
const result = await operation.fetchAll();
await operation.close();
return result;
}

async function deleteTable(session, tableName) {
async function deleteTable(session: IDBSQLSession, tableName: string) {
await execute(session, `DROP TABLE IF EXISTS ${tableName}`);
}

async function initializeTable(session, tableName) {
async function initializeTable(session: IDBSQLSession, tableName: string) {
await deleteTable(session, tableName);

const createTable = fixtures.createTableSql.replace(/\$\{table_name\}/g, tableName);
Expand All @@ -58,15 +61,15 @@ async function initializeTable(session, tableName) {
describe('Arrow support', () => {
const tableName = `dbsql_nodejs_sdk_e2e_arrow_${config.tableSuffix}`;

function createTest(testBody, customConfig) {
function createTest(
testBody: (session: IDBSQLSession) => void | Promise<void>,
customConfig: Partial<ClientConfig> = {},
) {
return async () => {
const session = await openSession(customConfig);
try {
await initializeTable(session, tableName);
await testBody(session);
} catch (error) {
logger(error);
throw error;
} finally {
await deleteTable(session, tableName);
await session.close();
Expand All @@ -82,6 +85,7 @@ describe('Arrow support', () => {
const result = await operation.fetchAll();
expect(result).to.deep.equal(expectedColumn);

// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.not.instanceof(ArrowResultConverter);
Expand All @@ -103,6 +107,7 @@ describe('Arrow support', () => {
const result = await operation.fetchAll();
expect(fixArrowResult(result)).to.deep.equal(expectedArrow);

// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
Expand All @@ -126,6 +131,7 @@ describe('Arrow support', () => {
const result = await operation.fetchAll();
expect(fixArrowResult(result)).to.deep.equal(expectedArrowNativeTypes);

// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
Expand Down Expand Up @@ -155,16 +161,20 @@ describe('Arrow support', () => {
`);

// We use some internals here to check that server returned response with multiple batches
// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
expect(resultHandler.source.source).to.be.instanceof(ArrowResultHandler);

// @ts-expect-error TS2339: Property _data does not exist on type IOperation
sinon.spy(operation._data, 'fetchNext');

const result = await resultHandler.fetchNext({ limit: rowsCount });

// @ts-expect-error TS2339: Property _data does not exist on type IOperation
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning any additional PRs to resolve these type incompatibilities? I assume these comments are like compiler directives saying that we know this property is not part of the type def?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're totally correct. Fields marked as private in TS become a regular public fields in JS. And in tests we used this quite a lot to inspect internal state of objects. When convering to TS, I fixed what was easy to fix, and added these @ts-expect-error directives for non-trivial ones. After we have all the tests in TS, I'll do another iteration and hopefully clean these up

expect(operation._data.fetchNext.callCount).to.be.eq(1);
// @ts-expect-error TS2339: Property _data does not exist on type IOperation
const rawData = await operation._data.fetchNext.firstCall.returnValue;
// We don't know exact count of batches returned, it depends on server's configuration,
// but with much enough rows there should be more than one result batch
Expand All @@ -181,6 +191,7 @@ describe('Arrow support', () => {
const result = await operation.fetchAll();
expect(fixArrowResult(result)).to.deep.equal(expectedArrow);

// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
Expand Down
42 changes: 26 additions & 16 deletions tests/e2e/batched_fetch.test.js → tests/e2e/batched_fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const { expect } = require('chai');
const sinon = require('sinon');
const config = require('./utils/config');
const logger = require('./utils/logger')(config.logger);
const { DBSQLClient } = require('../../lib');
import { expect } from 'chai';
import sinon from 'sinon';
import { DBSQLClient } from '../../lib';
import { ClientConfig } from '../../lib/contracts/IClientContext';

async function openSession(customConfig) {
import config from './utils/config';

async function openSession(customConfig: Partial<ClientConfig> = {}) {
const client = new DBSQLClient();

const clientConfig = client.getConfig();
Expand All @@ -20,8 +21,8 @@ async function openSession(customConfig) {
});

return connection.openSession({
initialCatalog: config.database[0],
initialSchema: config.database[1],
initialCatalog: config.catalog,
initialSchema: config.schema,
});
}

Expand All @@ -34,15 +35,15 @@ describe('Data fetching', () => {

it('fetch chunks should return a max row set of chunkSize', async () => {
const session = await openSession({ arrowEnabled: false });
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
sinon.spy(session.context.driver, 'fetchResults');
try {
// set `maxRows` to null to disable direct results so all the data are fetched through `driver.fetchResults`
const operation = await session.executeStatement(query, { maxRows: null });
let chunkedOp = await operation
.fetchChunk({ maxRows: 10, disableBuffering: true })
.catch((error) => logger(error));
expect(chunkedOp.length).to.be.equal(10);
const chunk = await operation.fetchChunk({ maxRows: 10, disableBuffering: true });
expect(chunk.length).to.be.equal(10);
// we explicitly requested only one chunk
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
expect(session.context.driver.fetchResults.callCount).to.equal(1);
} finally {
await session.close();
Expand All @@ -62,8 +63,11 @@ describe('Data fetching', () => {
let chunkCount = 0;

while (hasMoreRows) {
let chunkedOp = await operation.fetchChunk({ maxRows: 300 });
// eslint-disable-next-line no-await-in-loop
const chunkedOp = await operation.fetchChunk({ maxRows: 300 });
chunkCount += 1;

// eslint-disable-next-line no-await-in-loop
hasMoreRows = await operation.hasMoreRows();

const isLastChunk = !hasMoreRows;
Expand All @@ -78,13 +82,15 @@ describe('Data fetching', () => {

it('fetch all should fetch all records', async () => {
const session = await openSession({ arrowEnabled: false });
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
sinon.spy(session.context.driver, 'fetchResults');
try {
// set `maxRows` to null to disable direct results so all the data are fetched through `driver.fetchResults`
const operation = await session.executeStatement(query, { maxRows: null });
let all = await operation.fetchAll({ maxRows: 200 });
const all = await operation.fetchAll({ maxRows: 200 });
expect(all.length).to.be.equal(1000);
// 1000/200 = 5 chunks + one extra request to ensure that there's no more data
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
expect(session.context.driver.fetchResults.callCount).to.equal(6);
} finally {
await session.close();
Expand All @@ -93,13 +99,15 @@ describe('Data fetching', () => {

it('should fetch all records if they fit within directResults response', async () => {
const session = await openSession({ arrowEnabled: false });
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
sinon.spy(session.context.driver, 'fetchResults');
try {
// here `maxRows` enables direct results with limit of the first batch
const operation = await session.executeStatement(query, { maxRows: 1000 });
let all = await operation.fetchAll();
const all = await operation.fetchAll();
expect(all.length).to.be.equal(1000);
// all the data returned immediately from direct results, so no additional requests
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
expect(session.context.driver.fetchResults.callCount).to.equal(0);
} finally {
await session.close();
Expand All @@ -108,15 +116,17 @@ describe('Data fetching', () => {

it('should fetch all records if only part of them fit within directResults response', async () => {
const session = await openSession({ arrowEnabled: false });
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
sinon.spy(session.context.driver, 'fetchResults');
try {
// here `maxRows` enables direct results with limit of the first batch
const operation = await session.executeStatement(query, { maxRows: 200 });
// here `maxRows` sets limit for `driver.fetchResults`
let all = await operation.fetchAll({ maxRows: 200 });
const all = await operation.fetchAll({ maxRows: 200 });
expect(all.length).to.be.equal(1000);
// 1 chunk returned immediately from direct results + 4 remaining chunks + one extra chunk to ensure
// that there's no more data
// @ts-expect-error TS2339: Property context does not exist on type IDBSQLSession
expect(session.context.driver.fetchResults.callCount).to.equal(5);
} finally {
await session.close();
Expand Down
34 changes: 21 additions & 13 deletions tests/e2e/cloudfetch.test.js → tests/e2e/cloudfetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const { expect } = require('chai');
const sinon = require('sinon');
const config = require('./utils/config');
const { DBSQLClient } = require('../../lib');
const CloudFetchResultHandler = require('../../lib/result/CloudFetchResultHandler').default;
const ArrowResultConverter = require('../../lib/result/ArrowResultConverter').default;
const ResultSlicer = require('../../lib/result/ResultSlicer').default;

async function openSession(customConfig) {
import { expect } from 'chai';
import sinon from 'sinon';
import { DBSQLClient } from '../../lib';
import { ClientConfig } from '../../lib/contracts/IClientContext';
import CloudFetchResultHandler from '../../lib/result/CloudFetchResultHandler';
import ArrowResultConverter from '../../lib/result/ArrowResultConverter';
import ResultSlicer from '../../lib/result/ResultSlicer';

import config from './utils/config';

async function openSession(customConfig: Partial<ClientConfig> = {}) {
const client = new DBSQLClient();

const clientConfig = client.getConfig();
Expand All @@ -22,8 +24,8 @@ async function openSession(customConfig) {
});

return connection.openSession({
initialCatalog: config.database[0],
initialSchema: config.database[1],
initialCatalog: config.catalog,
initialSchema: config.schema,
});
}

Expand Down Expand Up @@ -54,6 +56,7 @@ describe('CloudFetch', () => {
await operation.finished();

// Check if we're actually getting data via CloudFetch
// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
Expand All @@ -69,10 +72,12 @@ describe('CloudFetch', () => {
expect(cfResultHandler.pendingLinks.length).to.be.equal(0);
expect(cfResultHandler.downloadTasks.length).to.be.equal(0);

// @ts-expect-error TS2339: Property _data does not exist on type IOperation
sinon.spy(operation._data, 'fetchNext');

const chunk = await operation.fetchChunk({ maxRows: 100000, disableBuffering: true });
// Count links returned from server
// @ts-expect-error TS2339: Property _data does not exist on type IOperation
const resultSet = await operation._data.fetchNext.firstCall.returnValue;
const resultLinksCount = resultSet?.resultLinks?.length ?? 0;

Expand All @@ -82,9 +87,11 @@ describe('CloudFetch', () => {
expect(cfResultHandler.downloadTasks.length).to.be.equal(cloudFetchConcurrentDownloads - 1);

let fetchedRowCount = chunk.length;
// eslint-disable-next-line no-await-in-loop
while (await operation.hasMoreRows()) {
const chunk = await operation.fetchChunk({ maxRows: 100000, disableBuffering: true });
fetchedRowCount += chunk.length;
// eslint-disable-next-line no-await-in-loop
const ch = await operation.fetchChunk({ maxRows: 100000, disableBuffering: true });
fetchedRowCount += ch.length;
}

expect(fetchedRowCount).to.be.equal(queriedRowsCount);
Expand Down Expand Up @@ -114,6 +121,7 @@ describe('CloudFetch', () => {
await operation.finished();

// Check if we're actually getting data via CloudFetch
// @ts-expect-error TS2339: Property getResultHandler does not exist on type IOperation
const resultHandler = await operation.getResultHandler();
expect(resultHandler).to.be.instanceof(ResultSlicer);
expect(resultHandler.source).to.be.instanceof(ArrowResultConverter);
Expand Down
Loading
X Tutup