Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit 2cd633d

Browse files
Google BigQuery (#526)
* Check in first commits * Check in code from original branch * Check in bigquery logo * Fix eslint issue * make database optional * tweaks * always show query for bigquery * revert
1 parent 07166b5 commit 2cd633d

File tree

10 files changed

+2225
-10
lines changed

10 files changed

+2225
-10
lines changed

app/components/Settings/Tabs/Tab.react.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export default class ConnectionTab extends Component {
4242
label = `${connectionObject.connectionString}`;
4343
} else if (connectionObject.dialect === DIALECTS.SQLITE) {
4444
label = connectionObject.storage;
45+
} else if (connectionObject.dialect === DIALECTS.BIGQUERY) {
46+
label = `Big Query ${connectionObject.database}`;
4547
} else if (connectionObject.dialect === DIALECTS.DATA_WORLD) {
4648
const pathNames = getPathNames(connectionObject.url);
4749
if (pathNames.length >= 3) {

app/constants/constants.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export const DIALECTS = {
1818
APACHE_DRILL: 'apache drill',
1919
DATA_WORLD: 'data.world',
2020
ATHENA: 'athena',
21-
CSV: 'csv'
21+
CSV: 'csv',
22+
BIGQUERY: 'bigquery'
2223
};
2324

2425
export const SQL_DIALECTS_USING_EDITOR = [
@@ -34,7 +35,8 @@ export const SQL_DIALECTS_USING_EDITOR = [
3435
'apache impala',
3536
'data.world',
3637
'athena',
37-
'csv'
38+
'csv',
39+
'bigquery'
3840
];
3941

4042
const commonSqlOptions = [
@@ -253,6 +255,21 @@ export const CONNECTION_CONFIG = {
253255
Note that this is just the connection between this app and your database; \
254256
connections to plot.ly or your plotly instance are always encrypted.'
255257
}
258+
],
259+
[DIALECTS.BIGQUERY]: [
260+
{
261+
'label': 'Google Project Id',
262+
'value': 'projectId',
263+
'type': 'text',
264+
'description': 'The Google Cloud Project Id'
265+
},
266+
{'label': 'Database', 'value': 'database', 'type': 'text'},
267+
{
268+
'label': 'Key File',
269+
'value': 'keyFilename',
270+
'type': 'filedrop',
271+
'description': 'The location of the Google Service Account Key File'
272+
}
256273
]
257274
};
258275

@@ -273,7 +290,8 @@ export const LOGOS = {
273290
[DIALECTS.S3]: 'images/s3-logo.png',
274291
[DIALECTS.APACHE_DRILL]: 'images/apache_drill-logo.png',
275292
[DIALECTS.DATA_WORLD]: 'images/dataworld-logo.png',
276-
[DIALECTS.ATHENA]: 'images/athena-logo.png'
293+
[DIALECTS.ATHENA]: 'images/athena-logo.png',
294+
[DIALECTS.BIGQUERY]: 'images/bigquery-logo.png'
277295
};
278296

279297
export function PREVIEW_QUERY(connection, table, elasticsearchIndex) {
@@ -307,6 +325,8 @@ export function PREVIEW_QUERY(connection, table, elasticsearchIndex) {
307325
size: 1000
308326
}
309327
});
328+
case DIALECTS.BIGQUERY:
329+
return 'SELECT \'connected\' as status';
310330
default:
311331
return '';
312332
}
@@ -484,6 +504,11 @@ export const SAMPLE_DBS = {
484504
},
485505
[DIALECTS.DATA_WORLD]: {
486506
url: 'https://data.world/rflprr/reported-lyme-disease-cases-by-state'
507+
},
508+
[DIALECTS.BIGQUERY]: {
509+
projectId: 'Plotly',
510+
database: 'plotly',
511+
keyFilename: '/home/plotly/falcon/google-credentials.json'
487512
}
488513
};
489514

app/images/bigquery-logo.LICENSE

Lines changed: 249 additions & 0 deletions
Large diffs are not rendered by default.

app/images/bigquery-logo.png

22.5 KB
Loading

backend/persistent/datastores/Datastores.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as ApacheImpala from './impala';
1414
import * as DataWorld from './dataworld';
1515
import * as DatastoreMock from './datastoremock';
1616
import * as Athena from './athena';
17+
import * as BigQuery from './bigquery';
1718

1819
const CSV = require('./csv');
1920
const Oracle = require('./oracle.js');
@@ -65,6 +66,8 @@ function getDatastoreClient(connection) {
6566
return Athena;
6667
} else if (dialect === 'oracle') {
6768
return Oracle;
69+
} else if (dialect === 'bigquery') {
70+
return BigQuery;
6871
}
6972
return Sql;
7073
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import {parseSQL} from '../../parse.js';
2+
3+
const BigQuery = require('@google-cloud/bigquery');
4+
const Pool = require('./pool.js');
5+
const pool = new Pool(newClient, sameConnection);
6+
7+
function newClient(connection) {
8+
return new BigQuery({
9+
keyFilename: connection.keyFilename,
10+
projectId: connection.projectId
11+
});
12+
}
13+
14+
function sameConnection(connection1, connection2) {
15+
return (
16+
connection1.projectId === connection2.projectId &&
17+
connection1.database === connection2.database &&
18+
connection1.keyFilename === connection2.keyFilename
19+
);
20+
}
21+
22+
/*
23+
* The connection function will validate the parameters and return the connection
24+
* parameters
25+
* @param {object} connection
26+
* @param {string} connection.projectId - Google Cloud Project Id
27+
* @param {string} connection.database - Google Big Query Database
28+
* @param {string} connection.keyFilename - Google Service Account Key File
29+
* @returns {Promise} that resolves connection
30+
*/
31+
export function connect(connection) {
32+
const client = pool.getClient(connection);
33+
return Promise.resolve(client);
34+
}
35+
36+
export function disconnect(connection) {
37+
return pool.remove(connection);
38+
}
39+
40+
/**
41+
* The following method will execute a query against the specified connection
42+
* @param {object} queryObject - The SQL to query against the connection
43+
* @param {object} connection - Connection parameters
44+
* @returns {Promise} that resolves to { columnnames, rows }
45+
*/
46+
export function query(queryObject, connection) {
47+
const client = pool.getClient(connection);
48+
const options = {
49+
query: queryObject,
50+
useLegacySql: false // Use standard SQL syntax for queries.
51+
};
52+
53+
let job;
54+
55+
return client
56+
.createQueryJob(options)
57+
.then(results => {
58+
job = results[0];
59+
return job.promise();
60+
})
61+
.then(() => {
62+
return job.getMetadata();
63+
})
64+
.then(metadata => {
65+
const errors = metadata[0].status.errors;
66+
if (errors && errors.length > 0) {
67+
throw new Error(errors.join(':'));
68+
}
69+
})
70+
.then(() => {
71+
return job.getQueryResults().then(rst => {
72+
return rst[0];
73+
});
74+
})
75+
.then(parseSQL);
76+
}
77+
78+
/**
79+
* Should return a list of tables and their columns that are defined within the database.
80+
* @param {object} connection - Connection parameters
81+
* @param {string} connection.projectId - Google Cloud Project Id
82+
* @param {string} connection.database - Google Big Query Database
83+
* @param {string} connection.keyFilename - Google Service Account Key File
84+
* @returns {Promise} that resolves to { columnnames, rows }
85+
*/
86+
export function schemas(connection) {
87+
if (!connection.database || connection.database === '') {
88+
const columnnames = ['table_name', 'column_name', 'data_type'];
89+
const rows = [];
90+
return {columnnames, rows};
91+
}
92+
93+
const client = pool.getClient(connection);
94+
95+
return client
96+
.dataset(connection.database)
97+
.getTables()
98+
.then(results => {
99+
const metadataPromises = results[0].map(table => table.getMetadata());
100+
return Promise.all(metadataPromises);
101+
}).
102+
then(results => {
103+
const columnnames = ['table_name', 'column_name', 'data_type'];
104+
const rows = [];
105+
106+
// iterate tables
107+
results.forEach(result => {
108+
const metadata = result[0];
109+
const tableName = metadata.tableReference.tableId;
110+
111+
// iterate fields
112+
if (metadata.schema && metadata.schema.fields)
113+
{
114+
metadata.schema.fields.forEach(({name, type}) => {
115+
rows.push([tableName, name, type]);
116+
});
117+
}
118+
});
119+
120+
return {columnnames, rows};
121+
});
122+
}
123+
124+
125+
/**
126+
* Should return a list of tables that are in the database
127+
* @param {object} connection - Connection Parameters
128+
* @param {string} connection.projectId - Google Cloud Project Id
129+
* @param {string} connection.database - Google Big Query Database
130+
* @param {string} connection.keyFilename - Google Service Account Key File
131+
* @returns {Promise} that resolves to an array of table names
132+
*/
133+
export function tables(connection) {
134+
if (!connection.database || connection.database === '') {
135+
return [];
136+
}
137+
138+
const client = pool.getClient(connection);
139+
140+
return client
141+
.dataset(connection.database)
142+
.getTables()
143+
.then(results => {
144+
return (results[0] || []).map(table => table.id);
145+
});
146+
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "falcon-sql-client",
3-
"version": "3.0.3",
3+
"version": "3.0.4",
44
"description": "Free, open-source SQL client for Windows, Mac and Linux",
55
"main": "./backend/main.js",
66
"scripts": {
@@ -145,6 +145,7 @@
145145
"fsevents": "*"
146146
},
147147
"devDependencies": {
148+
"@google-cloud/bigquery": "^1.3.0",
148149
"aws-sdk": "^2.156.0",
149150
"babel-core": "^6.26.0",
150151
"babel-eslint": "^8.0.2",
@@ -235,6 +236,7 @@
235236
"sinon": "^4.3.0",
236237
"style-loader": "^0.19.0",
237238
"tohash": "^1.0.2",
239+
"uglifyjs-webpack-plugin": "^1.2.7",
238240
"webpack": "^3.8.1",
239241
"yamljs": "^0.3.0"
240242
},

webpack.config.base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default {
2424
libraryTarget: 'commonjs2'
2525
},
2626
resolve: {
27-
extensions: ['.js', '.jsx']
27+
extensions: ['.js', '.jsx', '.json']
2828
},
2929
plugins: [
3030
],

webpack.config.electron.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import webpack from 'webpack';
22
import baseConfig from './webpack.config.base';
3+
import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
34

45
export default {
56
...baseConfig,
@@ -16,11 +17,8 @@ export default {
1617
plugins: [
1718
...baseConfig.plugins,
1819

19-
new webpack.optimize.UglifyJsPlugin({
20-
sourceMap: false,
21-
compressor: {
22-
warnings: false
23-
}
20+
new UglifyJsPlugin({
21+
sourceMap: false
2422
}),
2523
new webpack.BannerPlugin(
2624
{banner: 'require("source-map-support").install();',

0 commit comments

Comments
 (0)