Skip to content

Commit

Permalink
feat(postgres-driver): Migrate driver to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
ovr committed May 20, 2021
1 parent d694c91 commit 38f4adb
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 200 deletions.
1 change: 1 addition & 0 deletions packages/cubejs-postgres-driver/.gitignore
@@ -0,0 +1 @@
dist
9 changes: 0 additions & 9 deletions packages/cubejs-postgres-driver/driver/index.d.ts

This file was deleted.

15 changes: 15 additions & 0 deletions packages/cubejs-postgres-driver/index.js
@@ -0,0 +1,15 @@
const fromExports = require('./dist/src');
const { PostgresDriver } = require('./dist/src/PostgresDriver');

/**
* After 5 years working with TypeScript, now I know
* that commonjs and nodejs require is not compatibility with using export default
*/
const toExport = PostgresDriver;

// eslint-disable-next-line no-restricted-syntax
for (const [key, module] of Object.entries(fromExports)) {
toExport[key] = module;
}

module.exports = toExport;
14 changes: 11 additions & 3 deletions packages/cubejs-postgres-driver/package.json
Expand Up @@ -11,9 +11,16 @@
"engines": {
"node": ">=10.8.0"
},
"main": "driver/PostgresDriver.js",
"typings": "driver/index.d.ts",
"files": [
"src",
"index.js"
],
"main": "index.js",
"typings": "dist/src/index.d.ts",
"scripts": {
"build": "rm -rf dist && npm run tsc",
"tsc": "tsc",
"watch": "tsc -w",
"lint": "eslint **/*.js"
},
"dependencies": {
Expand All @@ -24,7 +31,8 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@cubejs-backend/linter": "^0.27.0"
"@cubejs-backend/linter": "^0.27.0",
"typescript": "~4.1.5"
},
"eslintConfig": {
"extends": "../cubejs-linter"
Expand Down
@@ -1,36 +1,46 @@
const pg = require('pg');
const { types } = require('pg');
const moment = require('moment');
const { BaseDriver } = require('@cubejs-backend/query-orchestrator');

const { Pool } = pg;

const GenericTypeToPostgres = {
import { types, Pool, PoolConfig } from 'pg';
import moment from 'moment';
import {
BaseDriver,
DownloadQueryResults, DownloadTableMemoryData, DriverInterface,
GenericDataBaseType, IndexesSQL, TableStructure,
} from '@cubejs-backend/query-orchestrator';

const GenericTypeToPostgres: Record<GenericDataBaseType, string> = {
string: 'text',
double: 'decimal'
};

const DataTypeMapping = {};
const DataTypeMapping: Record<string, any> = {};

Object.entries(types.builtins).forEach(pair => {
const [key, value] = pair;
DataTypeMapping[value] = key;
});

const timestampDataTypes = [1114, 1184];
const timestampTypeParser = (val: any) => moment.utc(val).format(moment.HTML5_FMT.DATETIME_LOCAL_MS);

const timestampTypeParser = val => moment.utc(val).format(moment.HTML5_FMT.DATETIME_LOCAL_MS);
export type PostgresDriverConfiguration = Partial<PoolConfig> & {
storeTimezone?: string,
executionTimeout?: number,
readOnly?: number,
};

export class PostgresDriver extends BaseDriver implements DriverInterface {
protected readonly pool: Pool;

class PostgresDriver extends BaseDriver {
constructor(config) {
public constructor(
protected readonly config: Partial<PostgresDriverConfiguration> = {}
) {
super();
this.config = config || {};

this.pool = new Pool({
max: process.env.CUBEJS_DB_MAX_POOL && parseInt(process.env.CUBEJS_DB_MAX_POOL, 10) || 8,
idleTimeoutMillis: 30000,
host: process.env.CUBEJS_DB_HOST,
database: process.env.CUBEJS_DB_NAME,
port: process.env.CUBEJS_DB_PORT,
port: <any>process.env.CUBEJS_DB_PORT,
user: process.env.CUBEJS_DB_USER,
password: process.env.CUBEJS_DB_PASS,
ssl: this.getSslOptions(),
Expand All @@ -41,22 +51,26 @@ class PostgresDriver extends BaseDriver {
});
}

async testConnection() {
public async testConnection(): Promise<void> {
try {
return await this.pool.query('SELECT $1::int AS number', ['1']);
await this.pool.query('SELECT $1::int AS number', ['1']);
} catch (e) {
if (e.toString().indexOf('no pg_hba.conf entry for host') !== -1) {
throw new Error(`Please use CUBEJS_DB_SSL=true to connect: ${e.toString()}`);
}

throw e;
}
}

async queryResponse(query, values) {
protected async queryResponse(query: string, values: unknown[]) {
const client = await this.pool.connect();
try {
await client.query(`SET TIME ZONE '${this.config.storeTimezone || 'UTC'}'`);
await client.query(`set statement_timeout to ${(this.config.hasOwnProperty('executionTimeout')) ? this.config.executionTimeout * 1000 : 600000}`);

const statementTimeout: number = this.config.executionTimeout ? this.config.executionTimeout * 1000 : 600000;
await client.query(`set statement_timeout to ${statementTimeout}`);

const res = await client.query({
text: query,
values: values || [],
Expand All @@ -69,7 +83,7 @@ class PostgresDriver extends BaseDriver {
parser = timestampTypeParser;
}

return val => parser(val);
return (val: any) => parser(val);
},
},
});
Expand All @@ -79,11 +93,12 @@ class PostgresDriver extends BaseDriver {
}
}

async query(query, values) {
return (await this.queryResponse(query, values)).rows;
public async query(query: string, values: unknown[]) {
const result = await this.queryResponse(query, values);
return result.rows;
}

async downloadQueryResults(query, values) {
public async downloadQueryResults(query: string, values: unknown[], options: DownloadQueryResults) {
const res = await this.queryResponse(query, values);
return {
rows: res.rows,
Expand All @@ -94,22 +109,25 @@ class PostgresDriver extends BaseDriver {
};
}

readOnly() {
public readOnly() {
return !!this.config.readOnly;
}

async uploadTableWithIndexes(table, columns, tableData, indexesSql) {
public async uploadTableWithIndexes(table: string, columns: TableStructure, tableData: DownloadTableMemoryData, indexesSql: IndexesSQL) {
if (!tableData.rows) {
throw new Error(`${this.constructor} driver supports only rows upload`);
}

await this.createTable(table, columns);

try {
await this.query(
`INSERT INTO ${table}
(${columns.map(c => this.quoteIdentifier(c.name)).join(', ')})
SELECT * FROM UNNEST (${columns.map((c, columnIndex) => `${this.param(columnIndex)}::${this.fromGenericType(c.type)}[]`).join(', ')})`,
columns.map(c => tableData.rows.map(r => r[c.name]))
);

for (let i = 0; i < indexesSql.length; i++) {
const [query, p] = indexesSql[i].sql;
await this.query(query, p);
Expand All @@ -120,17 +138,15 @@ class PostgresDriver extends BaseDriver {
}
}

release() {
public release() {
return this.pool.end();
}

param(paramIndex) {
public param(paramIndex: number) {
return `$${paramIndex + 1}`;
}

fromGenericType(columnType) {
public fromGenericType(columnType: string) {
return GenericTypeToPostgres[columnType] || super.fromGenericType(columnType);
}
}

module.exports = PostgresDriver;
12 changes: 12 additions & 0 deletions packages/cubejs-postgres-driver/tsconfig.json
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"src",
"test"
],
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"baseUrl": ".",
}
}
Expand Up @@ -185,7 +185,7 @@ export class BaseDriver {
throw new Error('Not implemented');
}

async downloadQueryResults(query, values) {
async downloadQueryResults(query, values, options) {
const rows = await this.query(query, values);
if (rows.length === 0) {
throw new Error(
Expand Down
Expand Up @@ -2,13 +2,14 @@

export type GenericDataBaseType = string;

export interface TableStructure {
export interface TableColumn {
name: string;
type: GenericDataBaseType;
}
export type TableStructure = TableColumn[];

export interface DownloadTableMemoryData {
rows: object[];
rows: Record<string, unknown>[];
}

export interface DownloadTableCSVData {
Expand All @@ -29,9 +30,15 @@ export interface ExternalDriverCompatibilities {
csvImport?: true,
streamImport?: true,
}
export type DownloadQueryResults = ExternalDriverCompatibilities;

export type IndexesSQL = {
sql: [string, unknown[]];
}[];

export interface DriverInterface {
createSchemaIfNotExists(schemaName: string): Promise<any>;
uploadTableWithIndexes(table: string, columns: TableStructure, tableData: DownloadTableData, indexesSql: IndexesSQL): Promise<void>;
loadPreAggregationIntoTable: (preAggregationTableName: string, loadSql: string, params: any, options: any) => Promise<any>;
//
tableColumnTypes: (table: string) => Promise<TableStructure>;
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Expand Up @@ -28,6 +28,9 @@
{
"path": "packages/cubejs-druid-driver"
},
{
"path": "packages/cubejs-postgres-driver"
},
{
"path": "packages/cubejs-jdbc-driver"
},
Expand Down

0 comments on commit 38f4adb

Please sign in to comment.