Skip to content

Commit

Permalink
feat(redshift-driver): introspection for external schemas/tables (e.g…
Browse files Browse the repository at this point in the history
…. Spectrum) (#8849)

* use native Array.reduce instead of Ramda

* define types for TablesSchema query results

* extend redshift schema queries with external tables metadata

* fix types in databricks driver

* fix types in bigQuery driver

* fix types in Quest driver

* fix types in Materialize driver

* remove unneeded ramda

* fix typos in materialize

* updated @types/node version

* describe redshift fixtures config

* fix getSchemas in redshift

* add drivers-tests for redshift

* add yarn cmds for redshift testing

* implement missed getTablesForSpecificSchemas()

* fix redshift export bucket test

* add redshift fixtures description

* add redshift test snapshots

* add redshift to test matrix

* add externalSchemaTests

* remove unused
  • Loading branch information
KSDaemon authored Oct 24, 2024
1 parent bc582e5 commit fa4b3b8
Show file tree
Hide file tree
Showing 36 changed files with 31,758 additions and 130 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/drivers-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ jobs:
databricks-jdbc
databricks-jdbc-export-bucket-s3
databricks-jdbc-export-bucket-azure
redshift
redshift-export-bucket-s3
snowflake
snowflake-export-bucket-s3
snowflake-export-bucket-azure
Expand All @@ -221,6 +223,8 @@ jobs:
- mssql
- mysql
- postgres
- redshift
- redshift-export-bucket-s3
- snowflake
- snowflake-export-bucket-s3
- snowflake-export-bucket-azure
Expand Down Expand Up @@ -312,6 +316,11 @@ jobs:
DRIVERS_TESTS_CUBEJS_DB_EXPORT_BUCKET_AWS_KEY: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_EXPORT_BUCKET_AWS_KEY }}
DRIVERS_TESTS_CUBEJS_DB_EXPORT_BUCKET_AWS_SECRET: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_EXPORT_BUCKET_AWS_SECRET }}

# Redshift
DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_HOST: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_HOST }}
DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_USER: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_USER }}
DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_PASS: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_REDSHIFT_PASS }}

# Snowflake
DRIVERS_TESTS_CUBEJS_DB_SNOWFLAKE_USER: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_SNOWFLAKE_USER }}
DRIVERS_TESTS_CUBEJS_DB_SNOWFLAKE_PASS: ${{ secrets.DRIVERS_TESTS_CUBEJS_DB_SNOWFLAKE_PASS }}
Expand All @@ -324,3 +333,4 @@ jobs:
cd ./packages/cubejs-testing-drivers
export DEBUG=testcontainers
yarn ${{ matrix.database }}-full
2 changes: 1 addition & 1 deletion packages/cubejs-backend-maven/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"devDependencies": {
"@cubejs-backend/linter": "^1.0.0",
"@types/jest": "^27",
"@types/node": "^14",
"@types/node": "^18",
"jest": "^27",
"typescript": "~5.2.2"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-backend-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"devDependencies": {
"@cubejs-backend/linter": "^1.0.0",
"@types/jest": "^27",
"@types/node": "^14",
"@types/node": "^18",
"cargo-cp-artifact": "^0.1.9",
"jest": "^27",
"pg": "^8.11.3",
Expand Down
6 changes: 2 additions & 4 deletions packages/cubejs-base-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@
"@azure/identity": "^4.4.1",
"@azure/storage-blob": "^12.9.0",
"@cubejs-backend/shared": "1.0.3",
"@google-cloud/storage": "^7.13.0",
"ramda": "^0.27.0"
"@google-cloud/storage": "^7.13.0"
},
"devDependencies": {
"@cubejs-backend/linter": "^1.0.0",
"@types/jest": "^27",
"@types/node": "^14",
"@types/ramda": "^0.27.32",
"@types/node": "^18",
"jest": "^27",
"typescript": "~5.2.2"
},
Expand Down
16 changes: 8 additions & 8 deletions packages/cubejs-base-driver/src/BaseDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
isSslKey,
isSslCert,
} from '@cubejs-backend/shared';
import { reduce } from 'ramda';
import fs from 'fs';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { S3, GetObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3';
Expand Down Expand Up @@ -53,6 +52,7 @@ import {
TableMemoryData,
PrimaryKeysQueryResult,
ForeignKeysQueryResult,
DatabaseStructure,
} from './driver.interface';

/**
Expand Down Expand Up @@ -402,28 +402,28 @@ export abstract class BaseDriver implements DriverInterface {
return false;
}

protected informationColumnsSchemaReducer(result: any, i: any) {
protected informationColumnsSchemaReducer(result: any, i: any): DatabaseStructure {
let schema = (result[i.table_schema] || {});
const tables = (schema[i.table_name] || []);
const columns = (schema[i.table_name] || []);

tables.push({
columns.push({
name: i.column_name,
type: i.data_type,
attributes: i.key_type ? ['primaryKey'] : []
});

tables.sort();
schema[i.table_name] = tables;
columns.sort();
schema[i.table_name] = columns;
schema = sortByKeys(schema);
result[i.table_schema] = schema;

return sortByKeys(result);
}

public tablesSchema() {
public tablesSchema(): Promise<DatabaseStructure> {
const query = this.informationSchemaQuery();

return this.query(query).then(data => reduce(this.informationColumnsSchemaReducer, {}, data));
return this.query(query, []).then(data => data.reduce<DatabaseStructure>(this.informationColumnsSchemaReducer, {}));
}

// Extended version of tablesSchema containing primary and foreign keys
Expand Down
2 changes: 0 additions & 2 deletions packages/cubejs-base-driver/src/driver.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
export type GenericDataBaseType = string;

export interface TableColumn {
// eslint-disable-next-line camelcase
name: string;
// eslint-disable-next-line camelcase
type: GenericDataBaseType;
attributes?: string[]
}
Expand Down
5 changes: 3 additions & 2 deletions packages/cubejs-bigquery-driver/src/BigQueryDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { Bucket, Storage } from '@google-cloud/storage';
import {
BaseDriver,
DatabaseStructure,
DriverCapabilities,
DriverInterface,
QueryColumnsResult,
Expand Down Expand Up @@ -202,7 +203,7 @@ export class BigQueryDriver extends BaseDriver implements DriverInterface {
);
}

return [];
return {};
} catch (e) {
if ((<any>e).message.includes('Permission bigquery.tables.get denied on table')) {
return {};
Expand All @@ -212,7 +213,7 @@ export class BigQueryDriver extends BaseDriver implements DriverInterface {
}
}

public async tablesSchema() {
public async tablesSchema(): Promise<DatabaseStructure> {
const dataSets = await this.bigquery.getDatasets();
const dataSetsColumns = await Promise.all(
dataSets[0].map((dataSet) => this.loadTablesForDataset(dataSet))
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@types/inquirer": "^7.3.1",
"@types/jest": "^27",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^14",
"@types/node": "^18",
"@types/request-promise": "^4.1.46",
"@types/semver": "^7.3.4",
"husky": "^4.2.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-cubestore-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@cubejs-backend/linter": "^1.0.0",
"@types/csv-write-stream": "^2.0.0",
"@types/generic-pool": "^3.1.9",
"@types/node": "^14",
"@types/node": "^18",
"@types/ws": "^7.4.0",
"jest": "^27",
"typescript": "~5.2.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-databricks-jdbc-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@cubejs-backend/linter": "^1.0.0",
"@types/generic-pool": "^3.1.9",
"@types/jest": "^27",
"@types/node": "^14",
"@types/node": "^18",
"@types/ramda": "^0.27.34",
"@types/uuid": "^8.3.4",
"jest": "^27",
Expand Down
11 changes: 7 additions & 4 deletions packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import {
QuerySchemasResult,
QueryTablesResult,
UnloadOptions,
GenericDataBaseType
GenericDataBaseType,
TableColumn,
DatabaseStructure,
} from '@cubejs-backend/base-driver';
import {
JDBCDriver,
Expand Down Expand Up @@ -392,10 +394,10 @@ export class DatabricksDriver extends JDBCDriver {
/**
* Returns tables meta data object.
*/
public override async tablesSchema(): Promise<Record<string, Record<string, object>>> {
public override async tablesSchema(): Promise<DatabaseStructure> {
const tables = await this.getTables();

const metadata: Record<string, Record<string, object>> = {};
const metadata: DatabaseStructure = {};

await Promise.all(tables.map(async ({ database, tableName }) => {
if (!(database in metadata)) {
Expand Down Expand Up @@ -499,7 +501,7 @@ export class DatabricksDriver extends JDBCDriver {
/**
* Returns table columns types.
*/
public override async tableColumnTypes(table: string): Promise<{ name: any; type: string; }[]> {
public override async tableColumnTypes(table: string): Promise<TableColumn[]> {
let tableFullName = '';
const tableArray = table.split('.');

Expand Down Expand Up @@ -538,6 +540,7 @@ export class DatabricksDriver extends JDBCDriver {
result.push({
name: column.col_name,
type: this.toGenericType(column.data_type),
attributes: [],
});
}

Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-materialize-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"@cubejs-backend/shared": "1.0.3",
"@types/pg": "^8.6.0",
"pg": "^8.6.0",
"ramda": "0.27.2",
"semver": "7.3.7"
},
"license": "Apache-2.0",
Expand Down
30 changes: 14 additions & 16 deletions packages/cubejs-materialize-driver/src/MaterializeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
*/

import { PostgresDriver, PostgresDriverConfiguration } from '@cubejs-backend/postgres-driver';
import { BaseDriver, DownloadTableMemoryData, IndexesSQL, StreamOptions, StreamTableDataWithTypes, TableStructure } from '@cubejs-backend/base-driver';
import {
BaseDriver,
DatabaseStructure,
DownloadTableMemoryData,
IndexesSQL,
StreamOptions,
StreamTableDataWithTypes,
TableStructure
} from '@cubejs-backend/base-driver';
import { PoolClient, QueryResult } from 'pg';
import { reduce } from 'ramda';
import { Readable } from 'stream';
import semver from 'semver';

Expand All @@ -18,16 +25,6 @@ export type ReadableStreamTableDataWithTypes = StreamTableDataWithTypes & {
rowStream: Readable;
};

export type SchemaResponse = {
[schema: string]: {
[schemaObject: string]: {
name: string;
type: string;
attributes: any[];
}[];
}
};

/**
* Materialize driver class.
*/
Expand Down Expand Up @@ -73,6 +70,7 @@ export class MaterializeDriver extends PostgresDriver {
/**
* Application name to set for the connection.
*/
// eslint-disable-next-line camelcase
application_name?: string,
} = {},
) {
Expand Down Expand Up @@ -130,7 +128,7 @@ export class MaterializeDriver extends PostgresDriver {
}

/**
* Materialize quereable schema
* Materialize queryable schema
* Returns materialized sources, materialized views, and tables
* @returns {string} schemaQuery
*/
Expand Down Expand Up @@ -171,15 +169,15 @@ export class MaterializeDriver extends PostgresDriver {
public async getMaterializeVersion(): Promise<string> {
const [{ version }] = await this.query<{version: string}>('SELECT mz_version() as version;', []);

// Materialzie returns the version as follows: 'v0.24.3-alpha.5 (65778f520)'
// Materialize returns the version as follows: 'v0.24.3-alpha.5 (65778f520)'
return version.split(' ')[0];
}

public async tablesSchema(): Promise<SchemaResponse> {
public async tablesSchema(): Promise<DatabaseStructure> {
const version = await this.getMaterializeVersion();
const query = this.informationSchemaQueryWithFilter(version);

return this.query(query, []).then(data => reduce(this.informationColumnsSchemaReducer, {}, data));
return this.query(query, []).then(data => data.reduce<DatabaseStructure>(this.informationColumnsSchemaReducer, {}));
}

protected async* asyncFetcher<R extends unknown>(conn: PoolClient, cursorId: string): AsyncGenerator<R> {
Expand Down
7 changes: 4 additions & 3 deletions packages/cubejs-questdb-driver/src/QuestDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
BaseDriver, DownloadQueryResultsOptions,
DownloadTableMemoryData, DriverInterface,
IndexesSQL, TableStructure, QueryOptions,
DatabaseStructure,
} from '@cubejs-backend/base-driver';
import { QuestQuery } from './QuestQuery';

Expand Down Expand Up @@ -179,14 +180,14 @@ export class QuestDriver<Config extends QuestDriverConfiguration = QuestDriverCo
// no-op as there are no schemas in QuestDB
}

public async tablesSchema() {
public async tablesSchema(): Promise<DatabaseStructure> {
const tables = await this.getTablesQuery('');

// QuestDB doesn't have a notion of schema/logical database while the driver
// has to return a `{ 'schema_name': { 'table1': {...} } }` object. So, we use
// empty schema name ('') as a workaround to avoid the schema prefix
// ('schema_name.') being used for table names in the generated queries.
const metadata: Record<string, Record<string, object>> = { '': {} };
const metadata: DatabaseStructure = { '': {} };

// eslint-disable-next-line camelcase
await Promise.all(tables.map(async ({ table_name: tableName }) => {
Expand All @@ -209,7 +210,7 @@ export class QuestDriver<Config extends QuestDriverConfiguration = QuestDriverCo
return this.query('SHOW TABLES', []);
}

public async tableColumnTypes(table: string) {
public async tableColumnTypes(table: string): Promise<TableStructure> {
const response: any[] = await this.query(`SHOW COLUMNS FROM '${table}'`, []);

return response.map((row) => ({ name: row.column, type: this.toGenericType(row.type) }));
Expand Down
Loading

0 comments on commit fa4b3b8

Please sign in to comment.