Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cube.js MongoDB Database Driver #9062

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion packages/cubejs-docker/dev.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ENV CI=0
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends libssl3 curl \
cmake python3.11 libpython3.11-dev gcc g++ make cmake openjdk-17-jdk-headless \
cmake python3 python3.11 libpython3.11-dev gcc g++ make cmake openjdk-17-jdk-headless \
&& rm -rf /var/lib/apt/lists/*

ENV RUSTUP_HOME=/usr/local/rustup
Expand Down Expand Up @@ -73,6 +73,7 @@ COPY packages/cubejs-sqlite-driver/package.json packages/cubejs-sqlite-driver/pa
COPY packages/cubejs-ksql-driver/package.json packages/cubejs-ksql-driver/package.json
COPY packages/cubejs-dbt-schema-extension/package.json packages/cubejs-dbt-schema-extension/package.json
COPY packages/cubejs-jdbc-driver/package.json packages/cubejs-jdbc-driver/package.json
COPY packages/cubejs-mongodb-jdbc-driver/package.json packages/cubejs-mongodb-jdbc-driver/package.json
# Skip
# COPY packages/cubejs-testing/package.json packages/cubejs-testing/package.json
# COPY packages/cubejs-docker/package.json packages/cubejs-docker/package.json
Expand Down Expand Up @@ -150,6 +151,7 @@ COPY packages/cubejs-ksql-driver/ packages/cubejs-ksql-driver/
COPY packages/cubejs-dbt-schema-extension/ packages/cubejs-dbt-schema-extension/
COPY packages/cubejs-jdbc-driver/ packages/cubejs-jdbc-driver/
COPY packages/cubejs-databricks-jdbc-driver/ packages/cubejs-databricks-jdbc-driver/
COPY packages/cubejs-mongodb-jdbc-driver/ packages/cubejs-mongodb-jdbc-driver/
# Skip
# COPY packages/cubejs-testing/ packages/cubejs-testing/
# COPY packages/cubejs-docker/ packages/cubejs-docker/
Expand Down
4 changes: 4 additions & 0 deletions packages/cubejs-jdbc-driver/src/JDBCDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { nextFn } from './QueryStream';
const DriverManager = require('@cubejs-backend/jdbc/lib/drivermanager');
const Connection = require('@cubejs-backend/jdbc/lib/connection');
const DatabaseMetaData = require('@cubejs-backend/jdbc/lib/databasemetadata');
const ResultSet = require('@cubejs-backend/jdbc/lib/resultset');
const jinst = require('@cubejs-backend/jdbc/lib/jinst');
const mvn = require('node-java-maven');

Expand Down Expand Up @@ -71,6 +72,9 @@ Connection.prototype.getMetaDataAsync = promisify(Connection.prototype.getMetaDa
// promisify DatabaseMetaData methods
DatabaseMetaData.prototype.getSchemasAsync = promisify(DatabaseMetaData.prototype.getSchemas);
DatabaseMetaData.prototype.getTablesAsync = promisify(DatabaseMetaData.prototype.getTables);
DatabaseMetaData.prototype.getColumnsAsync = promisify(DatabaseMetaData.prototype.getColumns);
// promisify ResultSet methods
ResultSet.prototype.toObjArrayAsync = promisify(ResultSet.prototype.toObjArray);

interface ExtendedPool extends Pool<any> {
_factory: Factory<any>;
Expand Down
2 changes: 2 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
16 changes: 16 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<p align="center"><a href="https://cube.dev"><img src="https://i.imgur.com/zYHXm4o.png" alt="Cube.js" width="300px"></a></p>

[Website](https://cube.dev) • [Docs](https://cube.dev/docs) • [Blog](https://cube.dev/blog) • [Slack](https://slack.cube.dev) • [Twitter](https://twitter.com/the_cube_dev)

[![npm version](https://badge.fury.io/js/%40cubejs-backend%2Fserver.svg)](https://badge.fury.io/js/%40cubejs-backend%2Fserver)
[![GitHub Actions](https://github.com/cube-js/cube.js/workflows/Build/badge.svg)](https://github.com/cube-js/cube.js/actions?query=workflow%3ABuild+branch%3Amaster)

# Cube.js MongoDB Database Driver

Cube.js MongoDB driver.

[Learn more](https://github.com/cube-js/cube.js#getting-started)

# License

Cube.js MongoDB Database Driver is [Apache 2.0 licensed](./LICENSE).
49 changes: 49 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@cubejs-backend/mongodb-jdbc-driver",
"description": "Cube.js MongoDB database driver",
"version": "1.1.9",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/cube-js/cube.git",
"directory": "packages/cubejs-mongodb-jdbc-driver"
},
"engines": {
"node": "^14.0.0 || ^16.0.0 || >=17.0.0"
},
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"scripts": {
"build": "rm -rf dist && npm run tsc",
"tsc": "tsc",
"watch": "tsc -w",
"lint": "eslint src/* --ext .ts",
"lint:fix": "eslint --fix src/* --ext .ts"
},
"files": [
"README.md",
"dist/src/*"
],
"dependencies": {
"@cubejs-backend/jdbc-driver": "1.1.10",
"@cubejs-backend/schema-compiler": "1.1.10",
"@cubejs-backend/shared": "1.1.10",
"moment-timezone": "^0.5.46",
"node-fetch": "2"
},
"devDependencies": {
"@cubejs-backend/linter": "^1.0.0",
"@types/node": "^18",
"@types/node-fetch": "2",
"typescript": "~5.2.2"
},
"publishConfig": {
"access": "public"
},
"eslintConfig": {
"extends": "../cubejs-linter"
},
"jest": {
"testEnvironment": "node"
}
}
80 changes: 80 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/src/MongoDBDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { assertDataSource, getEnv } from '@cubejs-backend/shared';
import { JDBCDriver, JDBCDriverConfiguration } from '@cubejs-backend/jdbc-driver';
import fs from 'fs';
import path from 'path';

import { downloadJDBCDriver } from './installer';
import { MongoDBDriverQuery } from './MongoDBDriverQuery';

export class MongoDBDriver extends JDBCDriver {
public constructor(
conf: Partial<JDBCDriverConfiguration> & {
/**
* Data source name.
*/
dataSource?: string,

/**
* Max pool size value for the [cube]<-->[db] pool.
*/
maxPoolSize?: number,

/**
* Time to wait for a response from a connection after validation
* request before determining it as not valid. Default - 60000 ms.
*/
testConnectionTimeout?: number,
} = {}
) {
const dataSource = conf.dataSource || assertDataSource('default');

const config: JDBCDriverConfiguration = {
database: getEnv('dbName', { dataSource }),
dbType: 'mongodb',
url: getEnv('jdbcUrl', { dataSource }),
drivername: 'com.mongodb.jdbc.MongoDriver',
properties: {
user: getEnv('dbUser', { dataSource }),
password: getEnv('dbPass', { dataSource }),
},
...conf,
};

super(config);
}

protected async getCustomClassPath() {
const customClassPath = path.join(__dirname, '..', 'mongodb-jdbc-2.2.0-all.jar');

if (!fs.existsSync(customClassPath)) {
await downloadJDBCDriver(customClassPath);
}

return customClassPath;
}

public readOnly() {
return true;
}

public async tablesSchema() {
const data = await this.withConnection(async (connection: any) => {
const metaData = await connection.getMetaDataAsync();
const columnsResult = await metaData.getColumnsAsync(null, null, null, null);
const columns = await columnsResult.toObjArrayAsync();

return columns.map((column: any) => ({
column_name: column.COLUMN_NAME,
table_name: column.TABLE_NAME,
table_schema: column.TABLE_CAT,
data_type: column.TYPE_NAME,
}));
});

return data.reduce(this.informationColumnsSchemaReducer, {});
}

public static dialectClass() {
return MongoDBDriverQuery;
}
}
63 changes: 63 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/src/MongoDBDriverQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BaseQuery } from '@cubejs-backend/schema-compiler';
import moment from 'moment-timezone';

const GRANULARITY_TO_INTERVAL: Record<string, (date: string) => string> = {
second: (date) => `DATEADD(second, DATEDIFF(second, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
minute: (date) => `DATEADD(minute, DATEDIFF(minute, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
hour: (date) => `DATEADD(hour, DATEDIFF(hour, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
day: (date) => `DATEADD(day, DATEDIFF(day, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
week: (date) => `DATEADD(week, DATEDIFF(week, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
month: (date) => `DATEADD(month, DATEDIFF(month, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
quarter: (date) => `DATEADD(quarter, DATEDIFF(quarter, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
year: (date) => `DATEADD(year, DATEDIFF(year, CAST('1900-01-01' as TIMESTAMP), ${date}), CAST('1900-01-01' as TIMESTAMP))`,
};

export class MongoDBDriverQuery extends BaseQuery {
public timeStampCast(value: string) {
return `CAST(${value} as TIMESTAMP)`;
}

public dateTimeCast(value: string) {
return `CAST(${value} AS TIMESTAMP)`;
}

public groupByClause() {
if (this.ungrouped) {
return '';
}

const names = this.dimensionAliasNames();

return names.length ? ` GROUP BY ${names.join(', ')}` : '';
}

public timeGroupedColumn(granularity: string, dimension: string) {
return GRANULARITY_TO_INTERVAL[granularity](dimension);
}

public convertTz(field: string) {
const offset = moment().tz(this.timezone).utcOffset();
const hours = Math.sign(offset) * Math.floor(Math.abs(offset) / 60);
const minutes = offset % 60;

let result = field;

if (hours !== 0) {
result = `TIMESTAMPADD(HOUR, ${hours}, ${result})`;
}

if (minutes !== 0) {
result = `TIMESTAMPADD(MINUTE, ${minutes}, ${result})`;
}

return result;
}

public nowTimestampSql() {
return 'CURRENT_TIMESTAMP';
}

public unixTimestampSql() {
return 'DATEDIFF(second, CAST(\'1970-01-01\'), CURRENT_TIMESTAMP)';
}
}
4 changes: 4 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { MongoDBDriver } from './MongoDBDriver';

export { MongoDBDriver };
export default MongoDBDriver;
20 changes: 20 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/src/installer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createWriteStream } from 'fs';
import { getHttpAgentForProxySettings } from '@cubejs-backend/shared';
import { pipeline } from 'node:stream/promises';
import fetch from 'node-fetch';

const URL = 'https://repo1.maven.org/maven2/org/mongodb/mongodb-jdbc/2.2.0/mongodb-jdbc-2.2.0-all.jar';

export async function downloadJDBCDriver(path: string) {
const response = await fetch(URL, {
agent: await getHttpAgentForProxySettings(),
});

if (!response.ok) {
throw new Error(`Failed to download MongoDB JDBC driver (status=${response.status})`);
}

const writeStream = createWriteStream(path);

await pipeline(response.body, writeStream);
}
12 changes: 12 additions & 0 deletions packages/cubejs-mongodb-jdbc-driver/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"src",
"test"
],
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"baseUrl": "."
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ module.exports = {
pinot: '@cubejs-backend/pinot-driver',
// List for JDBC drivers
'databricks-jdbc': '@cubejs-backend/databricks-jdbc-driver',
'mongodb-jdbc': '@cubejs-backend/mongodb-jdbc-driver',
};
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
{
"path": "packages/cubejs-databricks-jdbc-driver"
},
{
"path": "packages/cubejs-mongodb-jdbc-driver"
},
{
"path": "packages/cubejs-prestodb-driver"
},
Expand Down
Loading
Loading