mirror of
https://github.com/beekeeper-studio/beekeeper-studio.git
synced 2026-03-13 10:12:54 +08:00
Merge pull request #2946 from beekeeper-studio/feat/mongo-sql-editor
Feat/mongo sql editor
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
"@mongosh/browser-runtime-electron": "^3.8.0",
|
||||
"@mongosh/service-provider-node-driver": "^3.6.0",
|
||||
"@pdanpdan/vue-keyboard-trap": "^1.0.19",
|
||||
"@queryleaf/lib": "^0.2.2",
|
||||
"@types/ini": "^4.1.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"axios": "^1.8.2",
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface IConnectionHandlers {
|
||||
'conn/getTableReferences': ({ table, schema, sId }: { table: string, schema?: string, sId: string }) => Promise<string[]>,
|
||||
'conn/getTableKeys': ({ table, schema, sId }: { table: string, schema?: string, sId: string }) => Promise<TableKey[]>,
|
||||
'conn/listTablePartitions': ({ table, schema, sId }: { table: string, schema?: string, sId: string }) => Promise<TablePartition[]>,
|
||||
'conn/executeCommand': ({ commandText, sId }: { commandText: string, sId: string }) => Promise<NgQueryResult[]>,
|
||||
'conn/query': ({ queryText, options, sId }: { queryText: string, options?: any, sId: string }) => Promise<string>,
|
||||
'conn/getCompletions': ({ cmd, sId }: { cmd: string, sId: string }) => Promise<string[]>,
|
||||
'conn/getShellPrompt': ({ sId }: { sId: string }) => Promise<string>,
|
||||
@@ -272,6 +273,11 @@ export const ConnHandlers: IConnectionHandlers = {
|
||||
return await state(sId).connection.listTablePartitions(table, schema);
|
||||
},
|
||||
|
||||
'conn/executeCommand': async function({ commandText, sId }: { commandText: string, sId: string }) {
|
||||
checkConnection(sId);
|
||||
return await state(sId).connection.executeCommand(commandText);
|
||||
},
|
||||
|
||||
'conn/query': async function({ queryText, options, sId }: { queryText: string, options?: any, sId: string }) {
|
||||
checkConnection(sId);
|
||||
const query = await state(sId).connection.query(queryText, options);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IDbConnectionServer } from "@/lib/db/backendTypes";
|
||||
import { BasicDatabaseClient, ExecutionContext, QueryLogOptions } from "@/lib/db/clients/BasicDatabaseClient";
|
||||
import { DatabaseElement, IDbConnectionDatabase } from "@/lib/db/types";
|
||||
import { Collection, Db, Document, MongoClient, ObjectId } from 'mongodb';
|
||||
import { AggregationCursor, Collection, Db, Document, MongoClient, ObjectId } from 'mongodb';
|
||||
import { identify } from 'sql-query-identifier';
|
||||
import rawLog from '@bksLogger';
|
||||
import { BksField, BksFieldType, CancelableQuery, ExtendedTableColumn, NgQueryResult, OrderBy, PrimaryKeyColumn, Routine, SchemaFilterOptions, StreamResults, SupportedFeatures, TableChanges, TableColumn, TableDelete, TableFilter, TableIndex, TableInsert, TableOrView, TableProperties, TableResult, TableTrigger, TableUpdate, TableUpdateResult } from "@/lib/db/models";
|
||||
import { CreateTableSpec, IndexAlterations, TableKey } from "@/shared/lib/dialects/models";
|
||||
@@ -13,6 +14,12 @@ import { createCancelablePromise } from "@/common/utils";
|
||||
import { errors } from "@/lib/errors";
|
||||
import EventEmitter from "events";
|
||||
import { ChangeBuilderBase } from "@/shared/lib/sql/change_builder/ChangeBuilderBase";
|
||||
import { QueryLeaf } from '@queryleaf/lib'
|
||||
import { MongoDBCursor } from './mongodb/MongoDBCursor';
|
||||
import { wrapIdentifier } from "@/lib/db/clients/postgresql";
|
||||
import knexlib from 'knex'
|
||||
|
||||
const knex = knexlib({ client: 'pg' })
|
||||
|
||||
const log = rawLog.scope('mongodb');
|
||||
|
||||
@@ -34,10 +41,11 @@ const mongoContext = {
|
||||
export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
conn: MongoClient;
|
||||
runtime: MongoRuntime;
|
||||
queryLeaf: QueryLeaf;
|
||||
transcoders = [MongoDBObjectIdTranscoder];
|
||||
|
||||
constructor(server: IDbConnectionServer, database: IDbConnectionDatabase) {
|
||||
super(null, mongoContext, server, database);
|
||||
super(knex, mongoContext, server, database);
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
@@ -60,6 +68,8 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
const service = new NodeDriverServiceProvider(this.conn, new EventEmitter(), { productDocsLink: '', productName: 'BeekeeperStudio' });
|
||||
this.runtime = new MongoRuntime(service);
|
||||
this.runtime.evaluate(`use ${this.db}`)
|
||||
|
||||
this.queryLeaf = new QueryLeaf(this.conn, this.db);
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
@@ -239,12 +249,63 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
}
|
||||
}
|
||||
|
||||
async selectTopSql(_table: string, _offset: number, _limit: number, _orderBy: OrderBy[], _filters: string | TableFilter[], _schema?: string, _selects?: string[]): Promise<string> {
|
||||
log.error("MongoDB does not support generating SQL scripts");
|
||||
return '';
|
||||
async selectTopSql(table: string, offset: number, limit: number, orderBy: OrderBy[], filters: string | TableFilter[], _schema?: string, selects?: string[] = ['*']): Promise<string> {
|
||||
let orderByString = ""
|
||||
let filterString = ""
|
||||
let params: (string | string[])[] = []
|
||||
|
||||
if (orderBy && orderBy.length > 0) {
|
||||
orderByString = "ORDER BY " + (orderBy.map((item) => {
|
||||
if (_.isObject(item)) {
|
||||
return `${wrapIdentifier(item.field)} ${item.dir.toUpperCase()}`
|
||||
} else {
|
||||
return wrapIdentifier(item)
|
||||
}
|
||||
})).join(",")
|
||||
}
|
||||
|
||||
if (_.isString(filters)) {
|
||||
filterString = `WHERE ${filters}`
|
||||
} else if (filters && filters.length > 0) {
|
||||
let paramIdx = 1
|
||||
const allFilters = filters.map((item) => {
|
||||
if (item.type === 'in' && _.isArray(item.value)) {
|
||||
const values = item.value.map((v, idx) => {
|
||||
return knex.raw('?', [v]).toQuery();
|
||||
})
|
||||
paramIdx += values.length
|
||||
return `${wrapIdentifier(item.field)} ${item.type.toUpperCase()} (${values.join(',')})`
|
||||
} else if (item.type.includes('is')) {
|
||||
return `${wrapIdentifier(item.field)} ${item.type.toUpperCase()} NULL`
|
||||
}
|
||||
const value = knex.raw('?', [item.value]);
|
||||
paramIdx += 1
|
||||
return `${wrapIdentifier(item.field)} ${item.type.toUpperCase()} ${value}`
|
||||
})
|
||||
filterString = "WHERE " + joinFilters(allFilters, filters)
|
||||
|
||||
params = filters.filter((item) => !!item.value).flatMap((item) => {
|
||||
return _.isArray(item.value) ? item.value : [item.value]
|
||||
})
|
||||
}
|
||||
|
||||
const selectSQL = `SELECT ${selects.join(', ')}`
|
||||
const baseSQL = `
|
||||
FROM ${wrapIdentifier(table)}
|
||||
${filterString}
|
||||
`
|
||||
|
||||
const query = `
|
||||
${selectSQL} ${baseSQL}
|
||||
${orderByString}
|
||||
${_.isNumber(limit) ? `LIMIT ${limit}` : ''}
|
||||
${_.isNumber(offset) ? `OFFSET ${offset}` : ''}
|
||||
`;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
async selectTop(table: string, offset: number, limit: number, orderBy: OrderBy[], filters: string | TableFilter[], _schema?: string, selects?: string[]): Promise<TableResult> {
|
||||
private buildSelectTopCursor(table: string, offset: number, limit: number, orderBy: OrderBy[], filters: string | TableFilter[], selects?: string[], batchSize?: number): AggregationCursor {
|
||||
const collection = this.conn.db(this.db).collection(table);
|
||||
|
||||
const convertedOrders = orderBy.length > 0 ? orderBy.reduce((all, ord) => ({
|
||||
@@ -267,24 +328,45 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
}), init);
|
||||
}
|
||||
|
||||
let skip = null;
|
||||
if (offset) {
|
||||
skip = {
|
||||
"$skip": offset
|
||||
};
|
||||
}
|
||||
|
||||
const result = await collection.aggregate([
|
||||
let limitObj = null;
|
||||
if (limit) {
|
||||
limitObj = {
|
||||
"$limit": limit
|
||||
};
|
||||
}
|
||||
|
||||
const options: any = {};
|
||||
|
||||
if (batchSize) {
|
||||
options.batchSize = batchSize;
|
||||
}
|
||||
|
||||
const result = collection.aggregate([
|
||||
{
|
||||
"$match": convertedFilters
|
||||
},
|
||||
convertedOrders ? {
|
||||
"$sort": convertedOrders
|
||||
} : null,
|
||||
{
|
||||
"$skip": offset
|
||||
},
|
||||
{
|
||||
"$limit": limit
|
||||
},
|
||||
skip,
|
||||
limitObj,
|
||||
convertedSelects ? {
|
||||
"$project": convertedSelects
|
||||
} : null
|
||||
].filter((v) => !!v)).toArray();
|
||||
].filter((v) => !!v), options);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async selectTop(table: string, offset: number, limit: number, orderBy: OrderBy[], filters: string | TableFilter[], _schema?: string, selects?: string[]): Promise<TableResult> {
|
||||
const result = await this.buildSelectTopCursor(table, offset, limit, orderBy, filters, selects).toArray();
|
||||
|
||||
const fields = this.parseQueryResultColumns(result[0] || {});
|
||||
const rows = await this.serializeQueryResult({ rows: result, columns: [], arrayMode: false }, fields)
|
||||
@@ -612,6 +694,58 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
}
|
||||
|
||||
async executeQuery(queryText: string, _options?: any): Promise<NgQueryResult[]> {
|
||||
const queries = this.identifyCommands(queryText).map((q) => q.text);
|
||||
let results = [];
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i];
|
||||
const r = await this.queryLeaf.execute(query);
|
||||
let fields = [];
|
||||
if (r) {
|
||||
let f = [];
|
||||
if (_.isArray(r)) {
|
||||
f = _.uniq(_.flatten(_.takeRight(r, 10).map((obj) => Object.keys(obj))))
|
||||
} else {
|
||||
f = Object.keys(r);
|
||||
}
|
||||
fields = f.map((k) => ({
|
||||
name: k,
|
||||
id: k
|
||||
}));
|
||||
}
|
||||
|
||||
let returnResult = true;
|
||||
|
||||
let affectedRows = 0
|
||||
if (r?.acknowledged) {
|
||||
affectedRows = r?.insertedCount ?? r?.deletedCount ?? r?.modifiedCount;
|
||||
returnResult = false;
|
||||
}
|
||||
|
||||
let result = _.isArray(r) ? r : [r];
|
||||
if (!returnResult) result = [];
|
||||
|
||||
results.push({
|
||||
rows: result,
|
||||
rowCount: r?.length ?? 0,
|
||||
affectedRows,
|
||||
fields,
|
||||
command: query
|
||||
})
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private identifyCommands(queryText: string) {
|
||||
try {
|
||||
return identify(queryText, { strict: false, dialect: 'psql' });
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async executeCommand(commandText: string, _options?: any): Promise<NgQueryResult[]> {
|
||||
let results: NgQueryResult[] = [];
|
||||
|
||||
const listener = {
|
||||
@@ -626,7 +760,7 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
|
||||
this.runtime.setEvaluationListener(listener);
|
||||
|
||||
const ev = await this.runtime.evaluate(queryText);
|
||||
const ev = await this.runtime.evaluate(commandText);
|
||||
|
||||
let fields = [];
|
||||
if (ev.type === 'Cursor' || ev.type === 'AggregationCursor') {
|
||||
@@ -641,7 +775,7 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
rowCount: ev.printable?.documents?.length,
|
||||
fields,
|
||||
tableName: ev.source?.namespace?.collection ?? 'mycollection',
|
||||
command: queryText
|
||||
command: commandText
|
||||
})
|
||||
} else if (ev.type === 'Document') {
|
||||
if (ev.printable) {
|
||||
@@ -655,7 +789,7 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
rowCount: ev.printable ? 1 : 0,
|
||||
fields,
|
||||
tableName: ev.source?.namespace?.collection ?? 'mycollection',
|
||||
command: queryText
|
||||
command: commandText
|
||||
})
|
||||
} else if (ev.type === null && ev.printable) {
|
||||
if (typeof ev.printable === 'number') {
|
||||
@@ -663,7 +797,7 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
rows: [{ count: ev.printable }],
|
||||
rowCount: 1,
|
||||
fields: [{ name: 'count', id: 'count' }],
|
||||
command: queryText
|
||||
command: commandText
|
||||
});
|
||||
} else {
|
||||
if (ev.printable?.length > 0) {
|
||||
@@ -677,7 +811,7 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
rowCount: ev.printable?.length,
|
||||
fields,
|
||||
tableName: ev.source?.namespace?.collection ?? 'mycollection',
|
||||
command: queryText
|
||||
command: commandText
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -749,12 +883,37 @@ export class MongoDBClient extends BasicDatabaseClient<QueryResult> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async selectTopStream(_table: string, _orderBy: OrderBy[], _filters: string | TableFilter[], _chunkSize: number, _schema?: string): Promise<StreamResults> {
|
||||
log.error('MongoDB does not currently support streaming results');
|
||||
return null;
|
||||
async selectTopStream(table: string, orderBy: OrderBy[], filters: string | TableFilter[], chunkSize: number, _schema?: string): Promise<StreamResults> {
|
||||
const cursor = this.buildSelectTopCursor(table, null, null, orderBy, filters, null, chunkSize);
|
||||
|
||||
const columns = await this.listTableColumns(table);
|
||||
|
||||
const cursorOpts = {
|
||||
cursor,
|
||||
chunkSize
|
||||
};
|
||||
|
||||
return {
|
||||
totalRows: 0,// need to figure this out
|
||||
columns,
|
||||
cursor: new MongoDBCursor(cursorOpts)
|
||||
};
|
||||
}
|
||||
|
||||
async queryStream(_query: string, _chunkSize: number): Promise<StreamResults> {
|
||||
async queryStream(query: string, chunkSize: number): Promise<StreamResults> {
|
||||
const cursorOpts = {
|
||||
query: query,
|
||||
queryLeaf: this.queryLeaf,
|
||||
chunkSize
|
||||
};
|
||||
|
||||
const { columns, totalRows } = await this.getColumnsAndTotalRows(query);
|
||||
|
||||
return {
|
||||
totalRows,
|
||||
columns,
|
||||
cursor: new MongoDBCursor(cursorOpts)
|
||||
}
|
||||
log.error('MongoDB does not support querying');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { BeeCursor } from "@/lib/db/models";
|
||||
import { CursorResult, QueryLeaf } from "@queryleaf/lib";
|
||||
import { AggregationCursor } from "mongodb";
|
||||
|
||||
interface CursorOptions {
|
||||
query?: string,
|
||||
queryLeaf?: QueryLeaf,
|
||||
cursor?: AggregationCursor,
|
||||
chunkSize: number
|
||||
}
|
||||
|
||||
export class MongoDBCursor extends BeeCursor {
|
||||
private readonly options: CursorOptions;
|
||||
private cursor: CursorResult;
|
||||
private error?: Error;
|
||||
|
||||
constructor(options: CursorOptions) {
|
||||
super(options.chunkSize);
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.options.queryLeaf) {
|
||||
this.cursor = await this.options.queryLeaf.executeCursor(this.options.query, { batchSize: this.chunkSize });
|
||||
} else if (this.options.cursor) {
|
||||
// @ts-expect-error stupid peer dependencies
|
||||
this.cursor = this.options.cursor;
|
||||
} else {
|
||||
throw new Error('You need either a cursor or a queryleaf instance to be passed to the cursor');
|
||||
}
|
||||
this.cursor.on('error', this.handleError.bind(this));
|
||||
}
|
||||
|
||||
async read(): Promise<any[][]> {
|
||||
if (this.error) throw this.error;
|
||||
if (!this.cursor) {
|
||||
throw new Error("You need to call start first");
|
||||
}
|
||||
|
||||
if (await this.cursor.hasNext()) {
|
||||
// we have to call this to trigger mongo to fetch the next batch
|
||||
const firstDoc = await this.cursor.next();
|
||||
const rest = this.cursor.readBufferedDocuments();
|
||||
|
||||
return [firstDoc, ...rest].map((v) => Object.values(v));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
async cancel(): Promise<void> {
|
||||
await this.cursor.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,21 @@
|
||||
class="btn-fab add-query"
|
||||
><i class=" material-icons">add_circle</i></a>
|
||||
<!-- TODO (@day): when we have SQL queries for mongo, add an action dropdown here for shell/query tab -->
|
||||
<x-button
|
||||
class="add-tab-dropdown"
|
||||
menu
|
||||
>
|
||||
<i class="material-icons">arrow_drop_down</i>
|
||||
<x-menu>
|
||||
<x-menuitem @click.prevent="createQuery(null)">
|
||||
<x-label>New Query</x-label>
|
||||
<x-shortcut value="Control+T"/>
|
||||
</x-menuitem>
|
||||
<x-menuitem @click.prevent="createShell">
|
||||
<x-label>New Shell</x-label>
|
||||
</x-menuitem>
|
||||
</x-menu>
|
||||
</x-button>
|
||||
</span>
|
||||
<a
|
||||
@click.prevent="showUpgradeModal"
|
||||
@@ -348,7 +363,7 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
computed: {
|
||||
...mapState(['selectedSidebarItem']),
|
||||
...mapState('tabs', { 'activeTab': 'active', 'tabs': 'tabs' }),
|
||||
...mapState(['connection']),
|
||||
...mapState(['connection', 'connectionType']),
|
||||
...mapGetters({ 'dialect': 'dialect', 'dialectData': 'dialectData', 'dialectTitle': 'dialectTitle' }),
|
||||
tabIcon() {
|
||||
return {
|
||||
@@ -631,9 +646,6 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
await this.addTab(result);
|
||||
},
|
||||
async createQuery(optionalText, queryTitle?) {
|
||||
if (this.dialect === 'mongodb') {
|
||||
return await this.createShell();
|
||||
}
|
||||
// const text = optionalText ? optionalText : ""
|
||||
console.log("Creating tab")
|
||||
let qNum = 0
|
||||
@@ -906,7 +918,7 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
this.$store.dispatch('settings/save', { key: 'keymap', value: value });
|
||||
},
|
||||
openTableBuilder() {
|
||||
if (this.dialect === 'mongodb') {
|
||||
if (this.connectionType === 'mongodb') {
|
||||
this.$root.$emit(AppEvent.openCreateCollectionModal);
|
||||
return;
|
||||
}
|
||||
@@ -1100,3 +1112,9 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.add-tab-dropdown {
|
||||
padding: 0 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -458,6 +458,7 @@
|
||||
'mariadb': 'mysql',
|
||||
'tidb': 'mysql',
|
||||
'redshift': 'psql',
|
||||
'mongodb': 'psql'
|
||||
}
|
||||
return mappings[this.connectionType] || 'generic'
|
||||
},
|
||||
@@ -596,11 +597,14 @@
|
||||
entities() {
|
||||
return this.tables.map((t: TableOrView) => ({ schema: t.schema, name: t.name }))
|
||||
},
|
||||
queryDialect() {
|
||||
return this.dialectData.queryDialectOverride ?? this.connectionType;
|
||||
},
|
||||
formatterDialect() {
|
||||
return FormatterDialect(dialectFor(this.connectionType))
|
||||
return FormatterDialect(dialectFor(this.queryDialect))
|
||||
},
|
||||
identifierDialect() {
|
||||
return findSqlQueryIdentifierDialect(this.connectionType)
|
||||
return findSqlQueryIdentifierDialect(this.queryDialect)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
ref="bottomPanel"
|
||||
>
|
||||
<progress-bar
|
||||
@cancel="cancelQuery"
|
||||
:canCancel="false"
|
||||
:message="runningText"
|
||||
v-if="running"
|
||||
/>
|
||||
@@ -83,6 +83,7 @@
|
||||
<!-- STATUS BAR -->
|
||||
<query-editor-status-bar
|
||||
v-model="selectedResult"
|
||||
:hideWrapText="true"
|
||||
:results="results"
|
||||
:running="running"
|
||||
@download="download"
|
||||
@@ -139,7 +140,6 @@ export default Vue.extend({
|
||||
initialized: false,
|
||||
},
|
||||
mongoOutputResult: null,
|
||||
runningQuery: null,
|
||||
error: null,
|
||||
errorMarker: null,
|
||||
saveError: null,
|
||||
@@ -321,14 +321,6 @@ export default Vue.extend({
|
||||
close() {
|
||||
this.$root.$emit(AppEvent.closeTab)
|
||||
},
|
||||
async cancelQuery() {
|
||||
if (this.running && this.runningQuery) {
|
||||
this.running = false
|
||||
this.info = 'Command Execution Cancelled'
|
||||
await this.runningQuery.cancel();
|
||||
this.runningQuery = null;
|
||||
}
|
||||
},
|
||||
download(format) {
|
||||
this.$refs.table.download(format)
|
||||
},
|
||||
@@ -357,6 +349,7 @@ export default Vue.extend({
|
||||
clear() {
|
||||
this.selectedResult = 0;
|
||||
this.results = [];
|
||||
this.selectEditor();
|
||||
},
|
||||
async submitMongoCommand(command) {
|
||||
this.tab.isRunning = true
|
||||
@@ -365,9 +358,8 @@ export default Vue.extend({
|
||||
this.selectedResult = 0
|
||||
|
||||
try {
|
||||
this.runningQuery = await this.connection.query(command);
|
||||
const cmdStartTime = new Date();
|
||||
const results = await this.runningQuery.execute();
|
||||
const results = await this.connection.executeCommand(command);
|
||||
const cmdEndTime = new Date();
|
||||
|
||||
// eslint-disable-next-line
|
||||
|
||||
@@ -55,7 +55,6 @@ export default {
|
||||
return {
|
||||
shell: null,
|
||||
promptSymbol: "mongo> ",
|
||||
commandBuffer: "",
|
||||
commandHistory: [],
|
||||
historyIndex: -1,
|
||||
promptLine: 0, // where current prompt starts
|
||||
@@ -67,7 +66,7 @@ export default {
|
||||
...mapGetters({ 'userKeymap': 'settings/userKeymap' }),
|
||||
...mapState(['connection']),
|
||||
prompt() {
|
||||
const maxLength = 50;
|
||||
const maxLength = 30;
|
||||
if (this.promptSymbol.length <= maxLength) return this.promptSymbol;
|
||||
|
||||
const startLength = Math.floor((maxLength - 3) / 2);
|
||||
@@ -170,6 +169,8 @@ export default {
|
||||
await this.$nextTick();
|
||||
|
||||
this.destroyShell();
|
||||
await this.$nextTick();
|
||||
this.promptLine = 0;
|
||||
|
||||
const indicatorOpen = document.createElement("span");
|
||||
indicatorOpen.classList.add("foldgutter", "btn-fab", "open-close");
|
||||
@@ -274,9 +275,6 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// set value again cause sometimes it doesn't set
|
||||
//this.setInitialValue(cm);
|
||||
|
||||
if (this.firstInitialization && this.focus) {
|
||||
cm.focus();
|
||||
}
|
||||
@@ -299,6 +297,7 @@ export default {
|
||||
this.historyIndex = this.commandHistory.length;
|
||||
|
||||
if (userCommand === "clear") {
|
||||
this.firstInitialization = true;
|
||||
this.initialize({ userKeymap: this.userKeymap });
|
||||
this.$emit('clear');
|
||||
return;
|
||||
|
||||
@@ -81,17 +81,20 @@ export default Vue.extend({
|
||||
const editorPlugins = [
|
||||
plugins.autoquote,
|
||||
plugins.autoComplete,
|
||||
plugins.autoRemoveQueryQuotes(this.connectionType),
|
||||
plugins.autoRemoveQueryQuotes(this.queryDialect),
|
||||
plugins.queryMagic(() => this.defaultSchema, () => this.tables)
|
||||
];
|
||||
|
||||
return editorPlugins;
|
||||
},
|
||||
queryDialect() {
|
||||
return this.dialectData.queryDialectOverride ?? this.connectionType
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatSql() {
|
||||
const formatted = format(this.value, {
|
||||
language: FormatterDialect(dialectFor(this.connectionType)),
|
||||
language: FormatterDialect(dialectFor(this.queryDialect)),
|
||||
});
|
||||
this.$emit("input", formatted);
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div class="flex-col layout-center">
|
||||
<span>{{ message || 'loading...' }}</span>
|
||||
<a
|
||||
v-if="canCancel"
|
||||
@click.prevent="$emit('cancel')"
|
||||
title="Cancel Query Execution (Esc)"
|
||||
class="cancel-query btn btn-flat"
|
||||
@@ -12,6 +13,17 @@
|
||||
</template>
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
props: ['message']
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
canCancel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
},
|
||||
selectedDatabase() {
|
||||
// mongodb doesn't actually create the db until a collection has been added
|
||||
if (this.selectedDatabase != this.currentDatabase && (this.dbs.includes(this.selectedDatabase) || this.dialect === 'mongodb')) {
|
||||
if (this.selectedDatabase != this.currentDatabase && (this.dbs.includes(this.selectedDatabase) || this.connectionType === 'mongodb')) {
|
||||
this.$emit('databaseSelected', this.selectedDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<!-- FIXME (@day): we don't want to have per-db testing in the UI -->
|
||||
<button
|
||||
@click.prevent="newTable"
|
||||
:title="`New ${this.dialect === 'mongodb' ? 'Collection' : 'Table'}`"
|
||||
:title="`New ${this.connectionType === 'mongodb' ? 'Collection' : 'Table'}`"
|
||||
class="create-table"
|
||||
:disabled="tablesLoading"
|
||||
v-if="canCreateTable"
|
||||
@@ -261,7 +261,7 @@ import { matches } from '@/common/transport/TransportPinnedEntity'
|
||||
{ event: AppEvent.togglePinTableList, handler: this.togglePinTableList },
|
||||
]
|
||||
},
|
||||
...mapState(['selectedSidebarItem', 'tables', 'routines', 'database', 'tablesLoading', 'supportedFeatures']),
|
||||
...mapState(['selectedSidebarItem', 'tables', 'routines', 'database', 'tablesLoading', 'supportedFeatures', 'connectionType']),
|
||||
...mapGetters(['filteredTables', 'filteredRoutines', 'dialectData']),
|
||||
...mapGetters({
|
||||
pinnedEntities: 'pins/pinnedEntities',
|
||||
|
||||
@@ -166,7 +166,7 @@ export default Vue.extend({
|
||||
...mapGetters(['dialect', 'dialectData']),
|
||||
hasSql() {
|
||||
// FIXME (@day): no per db testing
|
||||
return this.dialect !== 'mongodb';
|
||||
return this.connectionType !== 'mongodb';
|
||||
},
|
||||
enabled() {
|
||||
return !this.dialectData.disabledFeatures?.alter?.everything && !this.dialectData.disabledFeatures.indexes;
|
||||
@@ -192,7 +192,7 @@ export default Vue.extend({
|
||||
|
||||
let additional = [];
|
||||
// FIXME (@day): no per-db testing
|
||||
if (this.dialect === 'mongodb') {
|
||||
if (this.connectionType === 'mongodb') {
|
||||
AdditionalMongoOrders.forEach((o) => {
|
||||
const add = this.table.columns.map((c) => `${escapeHtml(c.columnName)} ${o.toUpperCase()}`);
|
||||
additional.push(...add)
|
||||
@@ -231,14 +231,14 @@ export default Vue.extend({
|
||||
// FIXME (@day): no per-db testing
|
||||
const editableName = (cell) => this.newRows.includes(cell.getRow()) && !this.loading && this.dialect != 'mongodb'
|
||||
const result = [
|
||||
(this.dialectData?.disabledFeatures?.index?.id ? null : {title: 'Id', field: 'id', widthGrow: 0.5, cellDblClick: (e, cell) => this.handleCellDoubleClick(cell)}),
|
||||
(this.dialectData?.disabledFeatures?.index?.id ? null : {title: 'Id', field: 'id', widthGrow: 0.5, cellDblClick: (_e, cell) => this.handleCellDoubleClick(cell)}),
|
||||
{
|
||||
title:'Name',
|
||||
field: 'name',
|
||||
editable: editableName,
|
||||
editor: vueEditor(NullableInputEditorVue),
|
||||
formatter: this.cellFormatter,
|
||||
cellDblClick: (e, cell) => this.handleCellDoubleClick(cell),
|
||||
cellDblClick: (_e, cell) => this.handleCellDoubleClick(cell),
|
||||
},
|
||||
{
|
||||
title: 'Unique',
|
||||
@@ -271,7 +271,7 @@ export default Vue.extend({
|
||||
listOnEmpty: true,
|
||||
freetext: true,
|
||||
},
|
||||
cellDblClick: (e, cell) => this.handleCellDoubleClick(cell)
|
||||
cellDblClick: (_e, cell) => this.handleCellDoubleClick(cell)
|
||||
},
|
||||
trashButton(this.removeRow)
|
||||
]
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
>stars</i>
|
||||
</x-label>
|
||||
</x-menuitem>
|
||||
<x-menuitem @click="openQueryTab" :disabled="dialect === 'mongodb'">
|
||||
<x-menuitem @click="openQueryTab">
|
||||
<x-label>Copy view to SQL</x-label>
|
||||
</x-menuitem>
|
||||
</x-menu>
|
||||
@@ -391,6 +391,9 @@ export default Vue.extend({
|
||||
isCassandra() {
|
||||
return this.connectionType === 'cassandra'
|
||||
},
|
||||
queryDialect() {
|
||||
return this.dialectData?.queryDialectOverride ?? this.dialect;
|
||||
},
|
||||
columnsWithFilterAndOrder() {
|
||||
if (!this.tabulator || !this.table) return []
|
||||
const cols = this.tabulator.getColumns()
|
||||
@@ -1434,7 +1437,7 @@ export default Vue.extend({
|
||||
deletes: this.buildPendingDeletes()
|
||||
}
|
||||
const sql = await this.connection.applyChangesSql(changes);
|
||||
const formatted = format(sql, { language: FormatterDialect(this.dialect) })
|
||||
const formatted = format(sql, { language: FormatterDialect(this.queryDialect) })
|
||||
this.$root.$emit(AppEvent.newTab, formatted)
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
@@ -1569,7 +1572,7 @@ export default Vue.extend({
|
||||
this.table.schema,
|
||||
selects
|
||||
).then((query: string) => {
|
||||
const language = FormatterDialect(this.dialect);
|
||||
const language = FormatterDialect(this.queryDialect);
|
||||
const formatted = safeSqlFormat(query, { language });
|
||||
this.$root.$emit(AppEvent.newTab, formatted);
|
||||
}).catch((e: unknown) => {
|
||||
|
||||
@@ -174,6 +174,10 @@ export abstract class BasicDatabaseClient<RawResultType extends BaseQueryResult>
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
executeCommand(_commandText: string): Promise<NgQueryResult[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
abstract query(queryText: string, options?: any): Promise<CancelableQuery>;
|
||||
abstract executeQuery(queryText: string, options?: any): Promise<NgQueryResult[]>;
|
||||
abstract listDatabases(filter?: DatabaseFilterOptions): Promise<string[]>;
|
||||
|
||||
@@ -187,6 +187,7 @@ export interface IBasicDatabaseClient {
|
||||
getTableReferences(table: string, schema?: string): Promise<string[]>,
|
||||
getTableKeys(table: string, schema?: string): Promise<TableKey[]>,
|
||||
listTablePartitions(table: string, schema?: string): Promise<TablePartition[]>,
|
||||
executeCommand(commandText: string): Promise<NgQueryResult[]>,
|
||||
query(queryText: string, options?: any): Promise<CancelableQuery>,
|
||||
executeQuery(queryText: string, options?: any): Promise<NgQueryResult[]>,
|
||||
listDatabases(filter?: DatabaseFilterOptions): Promise<string[]>,
|
||||
|
||||
@@ -82,6 +82,10 @@ export class ElectronUtilityConnectionClient implements IBasicDatabaseClient {
|
||||
return await Vue.prototype.$util.send('conn/listTablePartitions', { table, schema });
|
||||
}
|
||||
|
||||
async executeCommand(commandText: string): Promise<NgQueryResult[]> {
|
||||
return await Vue.prototype.$util.send('conn/executeCommand', { commandText });
|
||||
}
|
||||
|
||||
async query(queryText: string, options?: any): Promise<CancelableQuery> {
|
||||
const id = await Vue.prototype.$util.send('conn/query', { queryText, options });
|
||||
return {
|
||||
|
||||
@@ -89,6 +89,7 @@ export class ColumnType {
|
||||
}
|
||||
|
||||
export interface DialectData {
|
||||
queryDialectOverride?: string,
|
||||
columnTypes?: ColumnType[],
|
||||
constraintActions?: string[]
|
||||
wrapIdentifier?: (s: string) => string
|
||||
|
||||
@@ -9,13 +9,12 @@ const types = [
|
||||
export const MongoDBData: DialectData = {
|
||||
columnTypes: types.map((t) => new ColumnType(t)),
|
||||
usesOffsetPagination: true,
|
||||
textEditorMode: 'javascript',
|
||||
queryDialectOverride: 'postgresql',
|
||||
textEditorMode: 'text/x-pgsql',
|
||||
disabledFeatures: {
|
||||
queryEditor: true,
|
||||
truncateElement: true,
|
||||
sqlCreate: true,
|
||||
importFromFile: true,
|
||||
exportTable: true,
|
||||
nullable: true,
|
||||
defaultValue: true,
|
||||
primary: true,
|
||||
|
||||
@@ -634,14 +634,14 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("executeQuery Function", () => {
|
||||
describe("executeCommand Function", () => {
|
||||
// Helper function to generate unique collection names
|
||||
const getTestCollectionName = (testName: string) => {
|
||||
return `query_test_${testName}_${Date.now()}`;
|
||||
};
|
||||
|
||||
it("should execute basic find command", async () => {
|
||||
const result = await connection.executeQuery("db.users.find({})");
|
||||
const result = await connection.executeCommand("db.users.find({})");
|
||||
|
||||
// Check that we have results
|
||||
expect(result).toBeDefined();
|
||||
@@ -662,7 +662,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should execute find command with filter", async () => {
|
||||
const result = await connection.executeQuery("db.users.find({ age: 30 })");
|
||||
const result = await connection.executeCommand("db.users.find({ age: 30 })");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -677,7 +677,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should execute findOne command", async () => {
|
||||
const result = await connection.executeQuery("db.users.findOne({ name: 'Alice' })");
|
||||
const result = await connection.executeCommand("db.users.findOne({ name: 'Alice' })");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -691,7 +691,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should execute projection in find command", async () => {
|
||||
const result = await connection.executeQuery("db.users.find({}, { name: 1, _id: 0 })");
|
||||
const result = await connection.executeCommand("db.users.find({}, { name: 1, _id: 0 })");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -708,7 +708,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should handle sort in find command", async () => {
|
||||
const result = await connection.executeQuery("db.users.find({}).sort({ age: -1 })");
|
||||
const result = await connection.executeCommand("db.users.find({}).sort({ age: -1 })");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -722,7 +722,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should handle limit in find command", async () => {
|
||||
const result = await connection.executeQuery("db.users.find({}).limit(2)");
|
||||
const result = await connection.executeCommand("db.users.find({}).limit(2)");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -733,11 +733,11 @@ describe(`MongoDB`, () => {
|
||||
|
||||
it("should handle skip in find command", async () => {
|
||||
// Get all users sorted by age ascending for reference
|
||||
const allUsersResult = await connection.executeQuery("db.users.find({}).sort({ age: 1 })");
|
||||
const allUsersResult = await connection.executeCommand("db.users.find({}).sort({ age: 1 })");
|
||||
const allUsers = allUsersResult[0].rows;
|
||||
|
||||
// Now get users with skip
|
||||
const result = await connection.executeQuery("db.users.find({}).sort({ age: 1 }).skip(2)");
|
||||
const result = await connection.executeCommand("db.users.find({}).sort({ age: 1 }).skip(2)");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -750,7 +750,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should execute count command", async () => {
|
||||
const result = await connection.executeQuery("db.users.countDocuments({})");
|
||||
const result = await connection.executeCommand("db.users.countDocuments({})");
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
@@ -766,7 +766,7 @@ describe(`MongoDB`, () => {
|
||||
});
|
||||
|
||||
it("should handle complex aggregation pipeline", async () => {
|
||||
const result = await connection.executeQuery(`
|
||||
const result = await connection.executeCommand(`
|
||||
db.jobs.aggregate([
|
||||
{ $match: { salary: { $gte: 100000 } } },
|
||||
{ $group: { _id: "$type", averageSalary: { $avg: "$salary" }, count: { $sum: 1 } } },
|
||||
@@ -794,7 +794,7 @@ describe(`MongoDB`, () => {
|
||||
|
||||
try {
|
||||
// Create a test collection and insert data using multi-statement command
|
||||
const result = await connection.executeQuery(`
|
||||
const result = await connection.executeCommand(`
|
||||
db.createCollection("${testCollection}");
|
||||
db.${testCollection}.insertOne({ test: "multi-statement" });
|
||||
db.${testCollection}.find({});
|
||||
@@ -824,7 +824,7 @@ describe(`MongoDB`, () => {
|
||||
await connection.createTable({ table: testCollection });
|
||||
|
||||
// Insert test documents
|
||||
await connection.executeQuery(`
|
||||
await connection.executeCommand(`
|
||||
db.${testCollection}.insertMany([
|
||||
{ value: "initial1" },
|
||||
{ value: "initial2" }
|
||||
@@ -832,7 +832,7 @@ describe(`MongoDB`, () => {
|
||||
`);
|
||||
|
||||
// Execute update
|
||||
const result = await connection.executeQuery(`
|
||||
const result = await connection.executeCommand(`
|
||||
db.${testCollection}.updateMany(
|
||||
{ value: /initial/ },
|
||||
{ $set: { value: "updated", updated: true } }
|
||||
@@ -842,7 +842,7 @@ describe(`MongoDB`, () => {
|
||||
expect(result).toBeDefined();
|
||||
|
||||
// Verify the update worked by checking the collection
|
||||
const verifyResult = await connection.executeQuery(`db.${testCollection}.find({})`);
|
||||
const verifyResult = await connection.executeCommand(`db.${testCollection}.find({})`);
|
||||
const updatedDocs = verifyResult[0].rows;
|
||||
|
||||
expect(updatedDocs.length).toBe(2);
|
||||
@@ -863,7 +863,7 @@ describe(`MongoDB`, () => {
|
||||
it("should handle errors gracefully", async () => {
|
||||
try {
|
||||
// Intentionally invalid query
|
||||
await connection.executeQuery("db.invalidOperation()");
|
||||
await connection.executeCommand("db.invalidOperation()");
|
||||
fail("Should have thrown an error");
|
||||
} catch (error) {
|
||||
// This is expected - the function should reject with an error
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@@ -3213,6 +3213,14 @@
|
||||
dependencies:
|
||||
playwright "1.50.1"
|
||||
|
||||
"@queryleaf/lib@^0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@queryleaf/lib/-/lib-0.2.2.tgz#87200730afb8aa6a0ae785e7b739622372a89e94"
|
||||
integrity sha512-HwIQ/2Zeyj+loXc0k8i7Js/PXyNHVseJdXCH6/d5EHoEqHnPHvqEMeYYCI1s7ocYe+1YIgKxg76LWpoLOPsK1g==
|
||||
dependencies:
|
||||
debug "^4.4.0"
|
||||
node-sql-parser "^4.11.0"
|
||||
|
||||
"@rollup/pluginutils@^4.2.1":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||
@@ -5296,7 +5304,7 @@ better-sqlite3@~11.1.2:
|
||||
bindings "^1.5.0"
|
||||
prebuild-install "^7.1.1"
|
||||
|
||||
big-integer@^1.6.44, big-integer@^1.6.51:
|
||||
big-integer@^1.6.44, big-integer@^1.6.48, big-integer@^1.6.51:
|
||||
version "1.6.52"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
||||
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
||||
@@ -5476,6 +5484,11 @@ buffer@^6.0.3:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
buildcheck@~0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
|
||||
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
|
||||
|
||||
builder-util-runtime@9.2.10, builder-util-runtime@^9.1.1:
|
||||
version "9.2.10"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz#a0f7d9e214158402e78b74a745c8d9f870c604bc"
|
||||
@@ -6094,8 +6107,13 @@ core-util-is@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
"cpu-features@file:./.yarn/packages/empty-package", cpu-features@~0.0.10:
|
||||
version "1.0.0"
|
||||
cpu-features@~0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5"
|
||||
integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==
|
||||
dependencies:
|
||||
buildcheck "~0.0.6"
|
||||
nan "^2.19.0"
|
||||
|
||||
crc-32@^1.2.0:
|
||||
version "1.2.2"
|
||||
@@ -9970,6 +9988,11 @@ named-placeholders@^1.1.3:
|
||||
dependencies:
|
||||
lru-cache "^7.14.1"
|
||||
|
||||
nan@^2.19.0:
|
||||
version "2.22.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
|
||||
integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==
|
||||
|
||||
nan@^2.20.0:
|
||||
version "2.22.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3"
|
||||
@@ -10167,6 +10190,13 @@ node-releases@^2.0.19:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
|
||||
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
|
||||
|
||||
node-sql-parser@^4.11.0:
|
||||
version "4.18.0"
|
||||
resolved "https://registry.yarnpkg.com/node-sql-parser/-/node-sql-parser-4.18.0.tgz#516b6e633c55c5abbba1ca588ab372db81ae9318"
|
||||
integrity sha512-2YEOR5qlI1zUFbGMLKNfsrR5JUvFg9LxIRVE+xJe962pfVLH0rnItqLzv96XVs1Y1UIR8FxsXAuvX/lYAWZ2BQ==
|
||||
dependencies:
|
||||
big-integer "^1.6.48"
|
||||
|
||||
nopt@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
|
||||
|
||||
Reference in New Issue
Block a user