Initial BigQuery Client Support

Initial BigQuery Client Support

Upstream update ConnectionInterface.vue

Multiple changes, cleanup and vscode

- Cleanup code for upstream PR
- Finally a working vscode launch with proper sourceMap for Renderer

Add listViews and logs cleanup

Vscode Launch config

Adding BigQuery Client

Update Bigqueryform.vue

fix build issues

Bigquery, bugfixes and changes
This commit is contained in:
Sergio Aguilar
2023-06-29 20:07:07 -07:00
parent 1a1f383dab
commit 4ffdc29b57
24 changed files with 28994 additions and 226 deletions

45
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,45 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"type": "node",
"request": "launch",
"cwd":"${workspaceFolder}/apps/studio",
"runtimeExecutable": "${workspaceFolder}/apps/studio/node_modules/.bin/electron",
"preLaunchTask": "electron-debug",
"args": [
"--remote-debugging-port=9223",
"./dist_electron",
"--enable-logging"
],
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/apps/studio/src/*"
},
"sourceMaps": true,
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"urlFilter": "http://localhost:*",
"timeout": 50000,
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/apps/studio/src/*"
},
"sourceMaps": true,
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": [
"Electron: Main",
"Electron: Renderer"
]
}
]
}

32
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,32 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "electron-debug",
"type": "process",
"echoCommand": true,
"options": {
"cwd": "apps/studio"
},
"command": "./node_modules/.bin/vue-cli-service",
"isBackground": true,
"args": [
"electron:serve",
"--remote-debugging-port=9223",
"--debug",
],
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": ""
},
"background": {
"beginsPattern": "Starting development server\\.\\.\\.",
"endsPattern": "Not launching electron as debug argument was passed\\."
}
}
}
]
}

View File

@@ -48,6 +48,9 @@
.sqlite-form {
margin-bottom: $gutter-h;
}
.bigquery-form {
margin-bottom: $gutter-h;
}
.text-connect {
padding-top: 0.25rem;
}

View File

@@ -101,6 +101,12 @@ app.on('activate', async (_event, hasVisibleWindows) => {
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Need to explicitly disable CORS when running in dev mode because
// we can't connect to bigquery-emulator on localhost.
// See: https://github.com/electron/electron/issues/23664
console.log("Dev mode detected, disabling CORS")
app.commandLine.appendSwitch('disable-web-security');
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
// Install Vue Devtools
try {
console.log("installing vue devtools")

View File

@@ -22,7 +22,8 @@ export const ConnectionTypes = [
{ name: 'Amazon Redshift', value: 'redshift' },
{ name: 'CockroachDB', value: 'cockroachdb' },
{ name: 'Oracle', value: 'other' },
{ name: 'Cassandra', value: 'other' }
{ name: 'Cassandra', value: 'other' },
{ name: 'BigQuery', value: 'bigquery' },
]
export const keymapTypes = [
@@ -40,6 +41,12 @@ export interface RedshiftOptions {
tokenDurationSeconds?: number;
}
export interface BigQueryOptions {
iamAuthenticationEnabled?: boolean
keyFilename?: string;
projectId?: string;
}
export interface ConnectionOptions {
cluster?: string
}
@@ -99,6 +106,8 @@ export class DbConnectionBase extends ApplicationEntity {
return 1433
} else if (this.connectionType === 'cockroachdb') {
return 26257
} else if (this._connectionType === 'bigquery') {
return 443
}
return null
}
@@ -185,6 +194,9 @@ export class DbConnectionBase extends ApplicationEntity {
@Column({ type: 'simple-json', nullable: false })
redshiftOptions: RedshiftOptions = {}
@Column({ type: 'simple-json', nullable: false })
bigQueryOptions: BigQueryOptions = {}
// this is only for SQL Server.
@Column({ type: 'boolean', nullable: false })
trustServerCertificate = false

View File

@@ -30,6 +30,7 @@ export class UsedConnection extends DbConnectionBase implements ISimpleConnectio
this.options = other.options
this.trustServerCertificate = other.trustServerCertificate
this.redshiftOptions = other.redshiftOptions
this.bigQueryOptions = other.bigQueryOptions
}
}

View File

@@ -1,6 +1,7 @@
import { RedshiftOptions } from "../appdb/models/saved_connection"
import { BigQueryOptions } from "../appdb/models/saved_connection"
export type ConnectionType = 'sqlite' | 'sqlserver' | 'redshift' | 'cockroachdb' | 'mysql' | 'postgresql' | 'mariadb' | 'cassandra'
export type ConnectionType = 'sqlite' | 'sqlserver' | 'redshift' | 'cockroachdb' | 'mysql' | 'postgresql' | 'mariadb' | 'cassandra' | 'bigquery'
export type SshMode = null | 'agent' | 'userpass' | 'keyfile'
export interface ISimpleConnection {
@@ -31,6 +32,7 @@ export interface ISimpleConnection {
trustServerCertificate?: boolean
options?: any
redshiftOptions?: RedshiftOptions
bigQueryOptions?: BigQueryOptions
}
export interface IConnection extends ISimpleConnection {

View File

@@ -109,6 +109,11 @@
:config="config"
:testing="testing"
/>
<big-query-form
v-if="config.connectionType === 'bigquery'"
:config="config"
:testing="testing"
/>
<other-database-notice v-if="config.connectionType === 'other'" />
<!-- TEST AND CONNECT -->
@@ -161,239 +166,240 @@
class="pitch"
v-if="!config.connectionType"
>
🧚 Hey! If you love the app, consider buying the full version of Beekeeper Studio to support us (and get more features too!). <a
🧚 Hey! If you love the app, consider buying the full version of Beekeeper Studio to support us (and get more
features too!). <a
href="https://docs.beekeeperstudio.io/docs/upgrading-from-the-community-edition"
class=""
>Learn more ></a>.
</div>
</div>
<small class="app-version"><a href="https://www.beekeeperstudio.io/releases/latest">Beekeeper Studio {{ version }}</a></small>
<small class="app-version"><a href="https://www.beekeeperstudio.io/releases/latest">Beekeeper Studio {{ version
}}</a></small>
</div>
</div>
</div>
</template>
<script lang="ts">
import os from 'os'
import {SavedConnection} from '../common/appdb/models/saved_connection'
import ConnectionSidebar from './sidebar/ConnectionSidebar.vue'
import MysqlForm from './connection/MysqlForm.vue'
import PostgresForm from './connection/PostgresForm.vue'
import RedshiftForm from './connection/RedshiftForm.vue'
import Sidebar from './common/Sidebar.vue'
import SqliteForm from './connection/SqliteForm.vue'
import SqlServerForm from './connection/SqlServerForm.vue'
import SaveConnectionForm from './connection/SaveConnectionForm.vue'
import Split from 'split.js'
import ImportButton from './connection/ImportButton.vue'
import _ from 'lodash'
import platformInfo from '@/common/platform_info'
import ErrorAlert from './common/ErrorAlert.vue'
import rawLog from 'electron-log'
import { mapState } from 'vuex'
import { dialectFor } from '@shared/lib/dialects/models'
import { findClient } from '@/lib/db/clients'
import OtherDatabaseNotice from './connection/OtherDatabaseNotice.vue'
import Vue from 'vue'
import os from 'os'
import { SavedConnection } from '../common/appdb/models/saved_connection'
import ConnectionSidebar from './sidebar/ConnectionSidebar.vue'
import MysqlForm from './connection/MysqlForm.vue'
import PostgresForm from './connection/PostgresForm.vue'
import RedshiftForm from './connection/RedshiftForm.vue'
import Sidebar from './common/Sidebar.vue'
import SqliteForm from './connection/SqliteForm.vue'
import SqlServerForm from './connection/SqlServerForm.vue'
import SaveConnectionForm from './connection/SaveConnectionForm.vue'
import BigQueryForm from './connection/BigQueryForm.vue'
import Split from 'split.js'
import ImportButton from './connection/ImportButton.vue'
import _ from 'lodash'
import platformInfo from '@/common/platform_info'
import ErrorAlert from './common/ErrorAlert.vue'
import rawLog from 'electron-log'
import { mapState } from 'vuex'
import { dialectFor } from '@shared/lib/dialects/models'
import { findClient } from '@/lib/db/clients'
import OtherDatabaseNotice from './connection/OtherDatabaseNotice.vue'
import Vue from 'vue'
const log = rawLog.scope('ConnectionInterface')
// import ImportUrlForm from './connection/ImportUrlForm';
const log = rawLog.scope('ConnectionInterface')
// import ImportUrlForm from './connection/ImportUrlForm';
export default Vue.extend({
components: { ConnectionSidebar, MysqlForm, PostgresForm, RedshiftForm, Sidebar, SqliteForm, SqlServerForm, SaveConnectionForm, ImportButton, ErrorAlert, OtherDatabaseNotice, BigQueryForm, },
export default Vue.extend({
components: { ConnectionSidebar, MysqlForm, PostgresForm, RedshiftForm, Sidebar, SqliteForm, SqlServerForm, SaveConnectionForm, ImportButton, ErrorAlert, OtherDatabaseNotice, },
data() {
return {
config: new SavedConnection(),
errors: null,
connectionError: null,
errorHelp: null,
testing: false,
split: null,
url: null,
importError: null,
sidebarShown: true,
version: platformInfo.appVersion
data() {
return {
config: new SavedConnection(),
errors: null,
connectionError: null,
errorHelp: null,
testing: false,
split: null,
url: null,
importError: null,
sidebarShown: true,
version: platformInfo.appVersion
}
},
computed: {
...mapState(['workspaceId']),
...mapState('data/connections', { 'connections': 'items' }),
connectionTypes() {
return this.$config.defaults.connectionTypes
},
pageTitle() {
if (_.isNull(this.config) || _.isUndefined(this.config.id)) {
return "New Connection"
} else {
return this.config.name
}
},
computed: {
...mapState(['workspaceId']),
...mapState('data/connections', {'connections': 'items'}),
connectionTypes() {
return this.$config.defaults.connectionTypes
},
pageTitle() {
if(_.isNull(this.config) || _.isUndefined(this.config.id)) {
return "New Connection"
} else {
return this.config.name
}
},
dialect() {
return dialectFor(this.config.connectionType)
dialect() {
return dialectFor(this.config.connectionType)
}
},
watch: {
workspaceId() {
this.config = new SavedConnection()
},
config: {
deep: true,
handler() {
this.connectionError = null
}
},
watch: {
workspaceId() {
this.config = new SavedConnection()
},
config: {
deep: true,
handler() {
this.connectionError = null
}
},
'config.connectionType'(newConnectionType) {
if(!findClient(newConnectionType)?.supportsSocketPath) {
this.config.socketPathEnabled = false
}
},
connectionError() {
console.log("error watch", this.connectionError, this.dialect)
if (this.connectionError &&
this.dialect == 'sqlserver' &&
this.connectionError.message &&
this.connectionError.message.includes('self signed certificate')
) {
this.errorHelp = `You might need to check 'Trust Server Certificate'`
} else {
'config.connectionType'(newConnectionType) {
if (!findClient(newConnectionType)?.supportsSocketPath) {
this.config.socketPathEnabled = false
}
},
connectionError() {
console.log("error watch", this.connectionError, this.dialect)
if (this.connectionError &&
this.dialect == 'sqlserver' &&
this.connectionError.message &&
this.connectionError.message.includes('self signed certificate')
) {
this.errorHelp = `You might need to check 'Trust Server Certificate'`
} else {
this.errorHelp = null
}
}
},
async mounted() {
if (!this.$store.getters.workspace) {
await this.$store.commit('workspace', this.$store.state.localWorkspace)
}
},
async mounted() {
if (!this.$store.getters.workspace) {
await this.$store.commit('workspace', this.$store.state.localWorkspace)
}
await this.$store.dispatch('loadUsedConfigs')
await this.$store.dispatch('pinnedConnections/loadPins')
await this.$store.dispatch('pinnedConnections/reorder', this.$store.state.pinnedConnections.pins)
this.config.sshUsername = os.userInfo().username
this.$nextTick(() => {
const components = [
this.$refs.sidebar.$refs.sidebar,
this.$refs.content
]
this.split = Split(components, {
elementStyle: (_dimension, size) => ({
'flex-basis': `calc(${size}%)`,
}),
sizes: [300, 500],
gutterize: 8,
minSize: [300, 300],
expandToMin: true,
} as Split.Options)
})
},
beforeDestroy() {
if (this.split) {
this.split.destroy()
}
},
methods: {
maybeLoadSqlite(e) {
// cast to an array
const files = [...e.dataTransfer.files || []]
if (!files || !files.length) return
if (!this.config) return;
// we only load the first
const file = files[0]
const allGood = this.config.parse(file.path)
if (!allGood) {
this.$noty.error(`Unable to open '${file.name}'. It is not a valid SQLite file.`);
return
} else {
this.submit()
}
await this.$store.dispatch('loadUsedConfigs')
await this.$store.dispatch('pinnedConnections/loadPins')
await this.$store.dispatch('pinnedConnections/reorder', this.$store.state.pinnedConnections.pins)
this.config.sshUsername = os.userInfo().username
this.$nextTick(() => {
const components = [
this.$refs.sidebar.$refs.sidebar,
this.$refs.content
]
this.split = Split(components, {
elementStyle: (_dimension, size) => ({
'flex-basis': `calc(${size}%)`,
}),
sizes: [300,500],
gutterize: 8,
minSize: [300, 300],
expandToMin: true,
} as Split.Options)
})
},
beforeDestroy() {
if(this.split) {
this.split.destroy()
}
},
methods: {
maybeLoadSqlite(e) {
// cast to an array
const files = [...e.dataTransfer.files || []]
if (!files || !files.length) return
if (!this.config) return;
// we only load the first
const file = files[0]
const allGood = this.config.parse(file.path)
if (!allGood) {
this.$noty.error(`Unable to open '${file.name}'. It is not a valid SQLite file.`);
return
} else {
this.submit()
}
},
create() {
},
create() {
this.config = new SavedConnection()
},
edit(config) {
this.config = config
this.errors = null
this.connectionError = null
},
async remove(config) {
if (this.config === config) {
this.config = new SavedConnection()
},
edit(config) {
this.config = config
}
await this.$store.dispatch('pinnedConnections/remove', config)
await this.$store.dispatch('data/connections/remove', config)
this.$noty.success(`${config.name} deleted`)
},
async duplicate(config) {
// Duplicates ES 6 class of the connection, without any reference to the old one.
const duplicateConfig = await this.$store.dispatch('data/connections/clone', config)
duplicateConfig.name = 'Copy of ' + duplicateConfig.name
try {
const id = await this.$store.dispatch('data/connections/save', duplicateConfig)
this.$noty.success(`The connection was successfully duplicated!`)
this.config = this.connections.find((c) => c.id === id) || this.config
} catch (ex) {
this.$noty.error(`Could not duplicate Connection: ${ex.message}`)
}
},
async submit() {
this.connectionError = null
try {
await this.$store.dispatch('connect', this.config)
} catch (ex) {
this.connectionError = ex
this.$noty.error("Error establishing a connection")
log.error(ex)
}
},
async handleConnect(config) {
this.config = config
await this.submit()
},
async testConnection() {
try {
this.testing = true
this.connectionError = null
await this.$store.dispatch('test', this.config)
this.$noty.success("Connection looks good!")
return true
} catch (ex) {
this.connectionError = ex
this.$noty.error("Error establishing a connection")
} finally {
this.testing = false
}
},
async save() {
try {
this.errors = null
this.connectionError = null
},
async remove(config) {
if (this.config === config) {
this.config = new SavedConnection()
}
await this.$store.dispatch('pinnedConnections/remove', config)
await this.$store.dispatch('data/connections/remove', config)
this.$noty.success(`${config.name} deleted`)
},
async duplicate(config) {
// Duplicates ES 6 class of the connection, without any reference to the old one.
const duplicateConfig = await this.$store.dispatch('data/connections/clone', config)
duplicateConfig.name = 'Copy of ' + duplicateConfig.name
try {
const id = await this.$store.dispatch('data/connections/save', duplicateConfig)
this.$noty.success(`The connection was successfully duplicated!`)
this.config = this.connections.find((c) => c.id === id) || this.config
} catch (ex) {
this.$noty.error(`Could not duplicate Connection: ${ex.message}`)
}
},
async submit() {
this.connectionError = null
try {
await this.$store.dispatch('connect', this.config)
} catch(ex) {
this.connectionError = ex
this.$noty.error("Error establishing a connection")
log.error(ex)
}
},
async handleConnect(config) {
this.config = config
await this.submit()
},
async testConnection(){
try {
this.testing = true
this.connectionError = null
await this.$store.dispatch('test', this.config)
this.$noty.success("Connection looks good!")
return true
} catch(ex) {
this.connectionError = ex
this.$noty.error("Error establishing a connection")
} finally {
this.testing = false
}
},
async save() {
try {
this.errors = null
this.connectionError = null
if (!this.config.name) {
throw new Error("Name is required")
}
await this.$store.dispatch('data/connections/save', this.config)
this.$noty.success("Connection Saved")
} catch (ex) {
console.error(ex)
this.errors = [ex.message]
this.$noty.error("Could not save connection information")
}
},
handleErrorMessage(message){
if (message){
this.errors = [message]
this.$noty.error("Could not parse connection URL.")
}else{
this.errors = null
if (!this.config.name) {
throw new Error("Name is required")
}
await this.$store.dispatch('data/connections/save', this.config)
this.$noty.success("Connection Saved")
} catch (ex) {
console.error(ex)
this.errors = [ex.message]
this.$noty.error("Could not save connection information")
}
},
})
handleErrorMessage(message) {
if (message) {
this.errors = [message]
this.$noty.error("Could not parse connection URL.")
} else {
this.errors = null
}
}
},
})
</script>
<style>
</style>
<style></style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="with-connection-type">
<div class="form-group">
<label for="Project Id">ProjectId</label>
<input
type="text"
class="form-control"
v-model="config.bigQueryOptions.projectId"
>
</div>
<div class="advanced-connection-settings">
<h4
class="advanced-heading flex"
:class="{ enabled: iamAuthenticationEnabled }"
>
<span class="expand">IAM Authentication</span>
<x-switch
@click.prevent="toggleIAMAuthentication"
:toggled="iamAuthenticationEnabled"
/>
</h4>
<div
class="advanced-body"
v-show="iamAuthenticationEnabled"
>
<div class="row gutter">
<div class="alert alert-info">
<i class="material-icons-outlined">info</i>
<div>
Create a service account key. <a
href="https://cloud.google.com/iam/docs/keys-create-delete#creating"
>Read
More</a>
</div>
</div>
</div>
<div class="form-group">
<label for="KeyFilename">
Key file
</label>
<file-picker v-model="config.bigQueryOptions.keyFilename" />
</div>
</div>
</div>
<common-server-inputs :config="config" />
<common-advanced :config="config" />
</div>
</template>
<script>
import FilePicker from '@/components/common/form/FilePicker'
import CommonAdvanced from './CommonAdvanced'
import CommonServerInputs from './CommonServerInputs'
export default {
components: { CommonAdvanced, CommonServerInputs, FilePicker },
data() {
return {
iamAuthenticationEnabled: this.config.bigQueryOptions?.iamAuthenticationEnabled || false
}
},
methods: {
toggleIAMAuthentication() {
this.config.bigQueryOptions.iamAuthenticationEnabled = this.iamAuthenticationEnabled = !this.iamAuthenticationEnabled
}
},
props: ['config'],
}
</script>

View File

@@ -36,7 +36,8 @@ export default {
sslRejectUnauthorized: config.sslRejectUnauthorized,
trustServerCertificate: config.trustServerCertificate,
options: config.options,
redshiftOptions: config.redshiftOptions
redshiftOptions: config.redshiftOptions,
bigQueryOptions: config.bigQueryOptions,
}
},

View File

@@ -5,7 +5,7 @@ import createLogger from '../logger';
import { SSHConnection } from '@/vendor/node-ssh-forward/index';
import { SupportedFeatures, FilterOptions, TableOrView, Routine, TableColumn, SchemaFilterOptions, DatabaseFilterOptions, TableChanges, TableUpdateResult, OrderBy, TableFilter, TableResult, StreamResults, CancelableQuery, ExtendedTableColumn, PrimaryKeyColumn, TableProperties, TableIndex, TableTrigger, TableInsert, TablePartition } from './models';
import { AlterPartitionsSpec, AlterTableSpec, IndexAlterations, RelationAlterations } from '@shared/lib/dialects/models';
import type { RedshiftOptions } from '@/common/appdb/models/saved_connection';
import type { RedshiftOptions, BigQueryOptions } from '@/common/appdb/models/saved_connection';
const logger = createLogger('db');
@@ -128,6 +128,7 @@ export interface IDbConnectionServerConfig {
trustServerCertificate?: boolean
options?: any
redshiftOptions?: RedshiftOptions
bigQueryOptions?: BigQueryOptions
}
export interface IDbSshTunnel {
@@ -513,7 +514,7 @@ function getViewCreateScript(server: IDbConnectionServer, database: IDbConnectio
function getMaterializedViewCreateScript(server: IDbConnectionServer, database: IDbConnectionDatabase, view: string /* , schema */) {
checkIsConnected(server , database);
if(typeof database.connection?.getMaterializedViewCreateScript !== 'function') {
if (typeof database.connection?.getMaterializedViewCreateScript !== 'function') {
return null;
} else {
return database.connection?.getMaterializedViewCreateScript(view);

View File

@@ -0,0 +1,290 @@
import { errors } from '../../errors';
import * as bq from '@google-cloud/bigquery';
import { DatabaseClient, IDbConnectionServerConfig, DatabaseElement } from '../client'
import { FilterOptions, OrderBy, TableFilter, TableUpdateResult, TableResult, Routine, TableChanges, TableInsert, TableUpdate, TableDelete, DatabaseFilterOptions, SchemaFilterOptions, NgQueryResult, StreamResults, ExtendedTableColumn, PrimaryKeyColumn, TableIndex, IndexedColumn, } from "../models";
import rawLog from 'electron-log'
import { createCancelablePromise } from '@/common/utils';
import { BigQueryOptions } from '@/common/appdb/models/saved_connection';
import { table, time } from 'console';
import { data } from 'jquery';
import { buildDeleteQueries, buildInsertQueries, buildUpdateQueries, buildInsertQuery, genericSelectTop, buildSelectTopQuery, escapeString, joinQueries, escapeLiteral, applyChangesSql } from './utils';
const log = rawLog.scope('bigquery')
const logger = () => log
/**
* To keep compatibility with the other clients we treat dataset as database.
*/
export default async function (server, database) {
let client = null
logger().debug(`bigquery client creating `, server, ` database:`, database, ` config:`, server.config)
const dbConfig = configDatabase(server, database)
client = new bq.BigQuery(dbConfig)
logger().debug('bigquery client created ', client)
// light solution to test connection with with a simple query
const results = await executeQuery(client, { query: 'SELECT CURRENT_TIMESTAMP()' });
logger().debug("bigquery client connected")
return {
supportedFeatures: () => ({ customRoutines: false, comments: false, properties: true, partitions: false, editPartitions: false }),
disconnect: () => disconnect(client),
listTables: (db) => listTables(client, db),
listViews: (filter) => listViews(client, database.database, filter),
listMaterializedViews: () => Promise.resolve([]),
listRoutines: () => Promise.resolve([]),
listTableColumns: (db, table) => listTableColumns(client, db, table),
getTableLength: (table) => getTableLength(client, database.database, table),
selectTop: (table, offset, limit, orderBy, filters, schema, selects) => selectTop(client, database.database, table, offset, limit, orderBy, filters, selects),
getTableKeys: (db, table) => Promise.resolve([]),
getPrimaryKeys: (db, table) => Promise.resolve([]),
query: (queryText) => query(client, queryText),
executeQuery: (queryText) => executeQuery(client, queryText),
listDatabases: () => listDatasets(client),
getTableProperties: (table) => getTableProperties(client, database.database, table),
versionString: () => getVersionString(),
// db creation
// TODO: determine if bigquery has different charsets
listCharsets: () => [],
getDefaultCharset: () => null,
listCollations: (charset) => [],
createDatabase: (databaseName) => createDatabase(client, databaseName),
};
}
function configDatabase(server, database) {
const host = server.config.host
const port = server.config.port
// For BigQuery Only -- IAM authentication and credential exchange
const bigQueryOptions = server.config.bigQueryOptions
const config = {}
config.projectId = bigQueryOptions.projectId || server.config.projectId
if (server.config.client === 'bigquery' && bigQueryOptions?.iamAuthenticationEnabled) {
config.keyFilename = bigQueryOptions.keyFilename
}
// For testing purposes
if (host !== "" && port !== "") {
config.apiEndpoint = "http://" + host + ":" + port
logger().debug(`configDatabase host: ${host} port: ${port} setting apiEndpoint: ${config.apiEndpoint}`)
} // use default otherwise
logger().debug("configDatabase config: ", config)
return config
}
function query(client, queryText) {
logger().debug('bigQuery query: ' + queryText)
let job = null
let canceling = false
const cancelable = createCancelablePromise({
...errors.CANCELED_BY_USER,
sqlectronError: 'CANCELED_BY_USER',
})
return {
async execute() {
// Get a query job first
[job] = await client.createQueryJob({ query: queryText })
logger().debug("created job: ", job.id)
try {
logger().debug("wait for executeQuery job.id: ", job.id)
const data = await Promise.race([
cancelable.wait(),
executeQuery(client, queryText, job),
])
return data
} catch (err) {
log.error('executeQuery error: ', err)
throw err
}
},
async cancel() {
if (!job) {
throw new Error('Query not ready to be canceled')
}
canceling = true
try {
const [jobCancelResponse] = await job.cancel()
logger().debug("query jobCancelResponse: ", jobCancelResponse)
cancelable.cancel()
} catch (err) {
canceling = false
throw err
} finally {
canceling = false
cancelable.discard()
}
},
}
}
function getVersionString() {
return null
}
function parseRowData(data) {
// BigQuery can return nested objects with custom types in the results
// look for the value string property.
// https://github.com/googleapis/nodejs-bigquery/blob/71dbed2140893677f7af254f5a7713a7f50bae92/src/bigquery.ts#L2191
return data.map((row) => {
const parsedRow = {}
Object.keys(row).forEach((key) => {
let strValue = row[key]
if (strValue != null && (Object.prototype.hasOwnProperty.call(strValue, 'value'))) {
strValue = row[key].value
}
parsedRow[key] = strValue
})
return parsedRow
})
}
function parseRowQueryResult(data) {
// Fallback in case the identifier could not reconize the command
const isSelect = Array.isArray(data)
const rows = parseRowData(data) || []
const fields = Object.keys(rows[0] || {}).map((name) => ({ name, id: name }))
logger().debug("parseRowQueryResult data length: ", data.length)
return {
command: isSelect ? 'SELECT' : 'UNKNOWN',
rows,
fields: fields,
rowCount: data && data.length,
affectedRows: 0,
}
}
async function executeQuery(client, queries, job) {
// Support passing a single query string and an object with params
if (queries instanceof String) {
queries = { query: queries }
}
if (!job) {
[job] = await client.createQueryJob(queries)
}
// Wait for the query to finish
const results = await job.getQueryResults()
return results.map(parseRowQueryResult)
}
export async function disconnect(client) {
return Promise.resolve()
}
function ensureDbFullName(client, db) {
if ((db.includes(`${client.projectId}.`)) || db.includes(`.${db}`)) {
return db
} else {
return `${client.projectId}.${db}`
}
}
async function listTablesOrViews(client, db, type) {
// Lists all tables or views in the dataset
const [tables] = await client.dataset(db).getTables()
var data = tables.map((table) => ({ name: table.id, entityType: table.metadata.type, metadata: table.metadata, table: table }))
data = data.filter((table) => table.metadata.type === type)
logger().debug(`listTablesAndViews for type:${type} data: `, data)
return data
}
export async function listTables(client, db) {
// Lists all tables in the dataset
const data = await listTablesOrViews(client, db, 'TABLE')
return data
}
export async function listTableColumns(client, db, table) {
// Lists all columns in a table
const [metadata] = await client.dataset(db).table(table).getMetadata()
const data = metadata.schema.fields.map((field) => ({ columnName: field.name, dataType: field.type }))
return data
}
export async function getTableProperties(client, db, table) {
logger().debug("getTableProperties: ", table)
const [
length,
indexes,
triggers, // BigQuery has no triggers
relations // BigQuery has no relations
] = await Promise.all([
getTableLength(client, db, table),
null,
null,
null,
])
return {
length, indexes, relations, triggers
}
}
export async function getTableLength(client, db, table) {
// Returns the number of rows in the table
const [metadata] = await client.dataset(db).table(table).getMetadata()
return Number(metadata.numRows)
}
export async function listTableIndexes(client, db, table) {
return []
}
export async function listDatasets(client) {
// Lists all datasets in current GCP project.
const [datasets] = await client.getDatasets()
const data = datasets.map((dataset) => dataset.id)
return data
}
export async function selectTop(client, db, table, offset, limit, orderBy, filters, selects) {
const columns = await listTableColumns(client, db, table)
const bqTable = db + "." + table
const queries = buildSelectTopQuery(bqTable, offset, limit, orderBy, filters, 'total', columns, selects)
const queriesResult = await executeQuery(client, queries)
const data = queriesResult[0]
const rowCount = Number(data.rowCount)
const fields = Object.keys(data.rows[0] || {})
const result = {
totalRows: rowCount,
result: data.rows,
fields: { fields }
}
return result
}
export async function listViews(client, db, filter) {
// Lists all views in the dataset
const data = await listTablesOrViews(client, db, 'VIEW')
return data
}
export async function createDatabase(client, databaseName) {
// Create a new dataset/database
const options = {}
const [dataset] = await client.createDataset(databaseName, options);
logger().debug(`Dataset ${dataset.id} created.`);
}

View File

@@ -5,6 +5,7 @@ import postgresql from './postgresql';
import sqlserver from './sqlserver';
import sqlite from './sqlite';
import cassandra from './cassandra';
import bigquery from './bigquery.js';
export function findClient(key: string): Client | undefined {
@@ -124,6 +125,21 @@ export const CLIENTS: ClientConfig[] = [
'cancelQuery',
],
},
{
key: 'bigquery',
name: 'BigQuery',
defaultPort: 443,
disabledFeatures: [
'server:ssl',
'server:socketPath',
'server:user',
'server:password',
'server:schema',
'server:domain',
'server:ssh',
'scriptCreateTable',
],
},
];
@@ -135,5 +151,6 @@ export default {
cassandra,
redshift: postgresql,
mariadb: mysql,
cockroachdb: postgresql
cockroachdb: postgresql,
bigquery,
};

View File

@@ -0,0 +1,13 @@
export default {
name: "20230426-add-bigqqery_options",
async run(runner) {
const queries = [
`ALTER TABLE saved_connection ADD COLUMN bigQueryOptions text not null default '{}'`,
`ALTER TABLE used_connection ADD COLUMN bigQueryOptions text not null default '{}'`,
];
for (let i = 0; i < queries.length; i++) {
const query = queries[i];
await runner.query(query);
}
}
};

View File

@@ -44,6 +44,14 @@ export default {
password: 'Example@1',
defaultDatabase: 'sakila'
},
{
name: "[DEV] Docker Bigquery",
connectionType: 'bigquery',
port: 9050,
host: 'localhost',
projectId: 'bks',
defaultDatabase: 'world',
},
{
name: "Beekeeper's Database",
connectionType: "sqlite",

View File

@@ -31,6 +31,7 @@ import createHiddenSchemas from './20220908_create_hidden_schemas'
import redshiftOptions from './20220817_add_redshift_options'
import connectionPins from './20230308_create_connection_pins'
import fixKeymapType from './20230619_fix_keymap_type'
import bigQueryOptions from './20230426_add_bigquery_options'
const logger = createLogger('migrations')()
@@ -45,7 +46,7 @@ const realMigrations = [
addSc, sslFiles, sslReject, pinned, addSort,
createCreds, workspaceScoping, workspace2, addTabs, scWorkspace, systemTheme,
serverCerts, socketPath, connectionOptions, keepaliveInterval, redshiftOptions,
createHiddenEntities, createHiddenSchemas, connectionPins, fixKeymapType
createHiddenEntities, createHiddenSchemas, connectionPins, fixKeymapType, bigQueryOptions
]
// fixtures require the models
@@ -84,7 +85,7 @@ export default class {
console.log("running migrations")
const runner = this.connection.connection.createQueryRunner()
await runner.query(setupSQL)
for(let i = 0; i < migrations.length; i++){
for(let i = 0; i < migrations.length; i++) {
const migration = migrations[i]
logger.debug(`Checking migration ${migration.name}`)
if(migration.env && migration.env !== this.env) {

View File

@@ -31,8 +31,13 @@
"../../shared/src/**/*.ts",
"../../shared/src/**/*.tsx",
"../../shared/src/**/*.vue",
"src/*.js",
"src/**/*.js",
],
"exclude": [
"node_modules", "dist", "dist_electron"
]
],
"sourceMapPathOverrides": {
"src/*": "${webRoot}/apps/studio/src/*"
},
}

View File

@@ -173,6 +173,7 @@ module.exports = {
}
},
configureWebpack: {
devtool: 'source-map',
plugins: [
new webpack.IgnorePlugin(/pg-native/, /pg/),
new webpack.IgnorePlugin(/kerberos/, /cassandra-driver/),

21
dev/docker_bigquery/data.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -euxo pipefail
# Install curl
# apt update
# apt install -y axel procps htop
# # Change directory to /work
# cd /work
# # Download and install Google cloud sdk
# export FILENAME=google-cloud-cli-430.0.0-linux-x86_64.tar.gz
# [ ! -f $FILENAME ] && axel -k https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/$FILENAME
# tar -xvzf $FILENAME
# /work/google-cloud-sdk/install.sh -q
# # Add gcloud to PATH
# source /work/google-cloud-sdk/path.bash.inc
# echo "source /work/google-cloud-sdk/path.bash.inc" >> ~/.bashrc
# Start BigQuery emulator
/bin/bigquery-emulator --log-level=debug --project=bks --data-from-yaml=/data/world.yaml

28004
dev/docker_bigquery/world.yaml Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ volumes:
psql13:
mariadb:
cockroachdb:
bigquery:
services:
@@ -101,5 +102,12 @@ services:
ports:
- 26257:26257
command: start-single-node --insecure
bigquery:
image: ghcr.io/goccy/bigquery-emulator:latest
volumes:
- ./dev/docker_bigquery:/data
- ./dev/docker_bigquery:/docker_init
ports:
- 9050:9050
- 9060:9060
entrypoint: sh -c 'chmod +x /docker_init/data.sh; /docker_init/data.sh'

View File

@@ -24,16 +24,19 @@
"all:lint": "yarn workspace beekeeper-studio lint && yarn workspace sqltools lint && npx eslint 'shared/**' --fix"
},
"devDependencies": {
"@google-cloud/bigquery": "^6.2.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/eslint-config-typescript": "^11.0.2",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"npm-run-all": "^4.1.5"
"npm-run-all": "^4.1.5",
"vue-cli-plugin-electron-builder": "latest",
"log-timestamp": "latest"
},
"resolutions": {
"nan": "2.17.0",
"node-abi": "^3.34.0",
"cpu-features": "./.yarn/packages/empty-package"
"nan": "2.17.0",
"node-abi": "^3.34.0",
"cpu-features": "./.yarn/packages/empty-package"
}
}

View File

@@ -30,4 +30,4 @@
"dist_electron",
"dist"
]
}
}

229
yarn.lock
View File

@@ -1816,6 +1816,61 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
"@google-cloud/bigquery@^6.2.0":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@google-cloud/bigquery/-/bigquery-6.2.1.tgz#29907fa6050b457de18c5ee5fd32b7c6207bd9f8"
integrity sha512-C/tcM3jQ3RU8pKHHxj702ouIfGZ9GAQ5U+ZpvS/o4B3yWtqmnG3TITL5oRnzDjEKeMTNu5C6z3/nFtix3GKlqA==
dependencies:
"@google-cloud/common" "^4.0.0"
"@google-cloud/paginator" "^4.0.0"
"@google-cloud/precise-date" "^3.0.1"
"@google-cloud/promisify" "^3.0.0"
arrify "^2.0.1"
big.js "^6.0.0"
duplexify "^4.0.0"
extend "^3.0.2"
is "^3.3.0"
stream-events "^1.0.5"
uuid "^9.0.0"
"@google-cloud/common@^4.0.0":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-4.0.3.tgz#d4324ac83087385d727593f7e1b6d81ee66442cf"
integrity sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==
dependencies:
"@google-cloud/projectify" "^3.0.0"
"@google-cloud/promisify" "^3.0.0"
arrify "^2.0.1"
duplexify "^4.1.1"
ent "^2.2.0"
extend "^3.0.2"
google-auth-library "^8.0.2"
retry-request "^5.0.0"
teeny-request "^8.0.0"
"@google-cloud/paginator@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-4.0.1.tgz#5fb8793d4f84d18c50a6f2fad3dadab8d2c533ef"
integrity sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==
dependencies:
arrify "^2.0.0"
extend "^3.0.2"
"@google-cloud/precise-date@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@google-cloud/precise-date/-/precise-date-3.0.1.tgz#1e6659a14af662442037b8f4d20dbc82bf1a78bd"
integrity sha512-crK2rgNFfvLoSgcKJY7ZBOLW91IimVNmPfi1CL+kMTf78pTJYd29XqEVedAeBu4DwCJc0EDIp1MpctLgoPq+Uw==
"@google-cloud/projectify@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-3.0.0.tgz#302b25f55f674854dce65c2532d98919b118a408"
integrity sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==
"@google-cloud/promisify@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-3.0.1.tgz#8d724fb280f47d1ff99953aee0c1669b25238c2e"
integrity sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -3924,6 +3979,11 @@ arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==
arrify@^2.0.0, arrify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
asar@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221"
@@ -4278,7 +4338,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1:
base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -4341,6 +4401,16 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
big.js@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f"
integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==
bignumber.js@^9.0.0:
version "9.1.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6"
integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==
binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -6461,7 +6531,7 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
duplexify@^4.1.1:
duplexify@^4.0.0, duplexify@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"
integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==
@@ -6484,7 +6554,7 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
@@ -6685,6 +6755,11 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
ent@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==
entities@2.2.0, entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -7315,7 +7390,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.2:
extend@^3.0.2, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -7408,6 +7483,11 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-text-encoding@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867"
integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==
fast-xml-parser@3.19.0:
version "3.19.0"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01"
@@ -7848,6 +7928,16 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
gaxios@^5.0.0, gaxios@^5.0.1:
version "5.1.3"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.1.3.tgz#f7fa92da0fe197c846441e5ead2573d4979e9013"
integrity sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==
dependencies:
extend "^3.0.2"
https-proxy-agent "^5.0.0"
is-stream "^2.0.0"
node-fetch "^2.6.9"
gaze@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
@@ -7855,6 +7945,14 @@ gaze@^1.0.0:
dependencies:
globule "^1.0.0"
gcp-metadata@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.3.0.tgz#6f45eb473d0cb47d15001476b48b663744d25408"
integrity sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==
dependencies:
gaxios "^5.0.0"
json-bigint "^1.0.0"
generate-function@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
@@ -8138,6 +8236,28 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
google-auth-library@^8.0.2:
version "8.9.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-8.9.0.tgz#15a271eb2ec35d43b81deb72211bd61b1ef14dd0"
integrity sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==
dependencies:
arrify "^2.0.0"
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
fast-text-encoding "^1.0.0"
gaxios "^5.0.0"
gcp-metadata "^5.3.0"
gtoken "^6.1.0"
jws "^4.0.0"
lru-cache "^6.0.0"
google-p12-pem@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-4.0.1.tgz#82841798253c65b7dc2a4e5fe9df141db670172a"
integrity sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==
dependencies:
node-forge "^1.3.1"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@@ -8185,6 +8305,15 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==
gtoken@^6.1.0:
version "6.1.2"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-6.1.2.tgz#aeb7bdb019ff4c3ba3ac100bbe7b6e74dce0e8bc"
integrity sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==
dependencies:
gaxios "^5.0.1"
google-p12-pem "^4.0.0"
jws "^4.0.0"
gzip-size@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
@@ -9249,6 +9378,11 @@ is-yarn-global@^0.3.0:
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
is@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79"
integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -9909,6 +10043,13 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
@@ -10423,6 +10564,11 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.1
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log-prefix@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/log-prefix/-/log-prefix-0.1.1.tgz#3ec492138c8044c9f9732298492dca87850cac90"
integrity sha512-aP1Lst8OCdZKATqzXDN0JBissNVZuiKLyo6hOXDBxaQ1jHDsaxh2J1i5Pp0zMy6ayTKDWfUlLMXyLaQe1PJ48g==
log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -10430,6 +10576,13 @@ log-symbols@^2.2.0:
dependencies:
chalk "^2.0.1"
log-timestamp@latest:
version "0.3.0"
resolved "https://registry.yarnpkg.com/log-timestamp/-/log-timestamp-0.3.0.tgz#2e10f1f0db872674ef3ecf53d6a312840acae45f"
integrity sha512-luRz6soxijd1aJh0GkLXFjKABihxthvTfWTzu3XhCgg5EivG2bsTpSd63QFbUgS+/KmFtL+0RfSpeaD2QvOV8Q==
dependencies:
log-prefix "0.1.1"
loglevel@^1.6.8:
version "1.8.0"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114"
@@ -11172,11 +11325,23 @@ node-cache@^4.1.1:
clone "2.x"
lodash "^4.17.15"
node-fetch@^2.6.1, node-fetch@^2.6.9:
version "2.6.12"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
dependencies:
whatwg-url "^5.0.0"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
node-forge@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
node-gyp@^7.1.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae"
@@ -13247,6 +13412,14 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
retry-request@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-5.0.2.tgz#143d85f90c755af407fcc46b7166a4ba520e44da"
integrity sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==
dependencies:
debug "^4.1.1"
extend "^3.0.2"
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
@@ -14178,6 +14351,13 @@ stream-each@^1.1.0:
end-of-stream "^1.1.0"
stream-shift "^1.0.0"
stream-events@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
dependencies:
stubs "^3.0.0"
stream-http@^2.7.2:
version "2.8.3"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
@@ -14366,6 +14546,11 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
stubs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==
stylehacks@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
@@ -14552,6 +14737,17 @@ tedious@^14.0.0:
punycode "^2.1.0"
sprintf-js "^1.1.2"
teeny-request@^8.0.0:
version "8.0.3"
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-8.0.3.tgz#5cb9c471ef5e59f2fca8280dc3c5909595e6ca24"
integrity sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==
dependencies:
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.1"
stream-events "^1.0.5"
uuid "^9.0.0"
temp-file@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
@@ -14848,6 +15044,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
trim-newlines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
@@ -15438,6 +15639,11 @@ uuid@^8.3.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v-hotkey@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/v-hotkey/-/v-hotkey-0.8.0.tgz#134d64b30a3eeeb53bcb00eba81fdf74e21a032a"
@@ -15506,7 +15712,7 @@ vue-class-component@^7.1.0, vue-class-component@^7.2.3:
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.6.tgz#8471e037b8e4762f5a464686e19e5afc708502e4"
integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==
vue-cli-plugin-electron-builder@~2.1.1:
vue-cli-plugin-electron-builder@latest, vue-cli-plugin-electron-builder@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/vue-cli-plugin-electron-builder/-/vue-cli-plugin-electron-builder-2.1.1.tgz#de8bed25b32e73e28dd08061dd2a3c6bfff73227"
integrity sha512-ZrxFZ2uxgpwyFUE8LtguYqaTzSfZ1osME1uFlIj/Iz7GtLrATqs23n/BkKEpyEf5nYNAylzIM8ykGAfH/0QdmA==
@@ -15802,6 +16008,11 @@ wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -15984,6 +16195,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^6.4.1:
version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"