Delete files unrelated to graph
|
@ -1,72 +0,0 @@
|
|||
// This stub is a subset of the code from files.ts in vscode-languageserver
|
||||
// (https://github.com/Microsoft/vscode-languageserver-node/blob/master/server/src/files.ts), which is used
|
||||
// by https://www.npmjs.com/package/dockerfile-language-server-nodejs. It contains some dynamic imports that
|
||||
// can't be webpack'ed. Since dockerfile-language-server-node only uses the uriToFilePath utility from this
|
||||
// file and that function doesn't have issues, the easiest solution is to copy just that function here.
|
||||
//
|
||||
// The original files.js file gets replaced by this file during webpack
|
||||
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const url = require("url");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const child_process_1 = require("child_process");
|
||||
/**
|
||||
* @deprecated Use the `vscode-uri` npm module which provides a more
|
||||
* complete implementation of handling VS Code URIs.
|
||||
*/
|
||||
function uriToFilePath(uri) {
|
||||
let parsed = url.parse(uri);
|
||||
if (parsed.protocol !== 'file:' || !parsed.path) {
|
||||
return undefined;
|
||||
}
|
||||
let segments = parsed.path.split('/');
|
||||
for (var i = 0, len = segments.length; i < len; i++) {
|
||||
segments[i] = decodeURIComponent(segments[i]);
|
||||
}
|
||||
if (process.platform === 'win32' && segments.length > 1) {
|
||||
let first = segments[0];
|
||||
let second = segments[1];
|
||||
// Do we have a drive letter and we started with a / which is the
|
||||
// case if the first segement is empty (see split above)
|
||||
if (first.length === 0 && second.length > 1 && second[1] === ':') {
|
||||
// Remove first slash
|
||||
segments.shift();
|
||||
}
|
||||
}
|
||||
return path.normalize(segments.join('/'));
|
||||
}
|
||||
exports.uriToFilePath = uriToFilePath;
|
||||
|
||||
// END OF ORIGINAL CODE
|
||||
|
||||
// Throw NYI if any of the other functions are ever called (they shouldn't be currently)
|
||||
function resolveModule(workspaceRoot, moduleName) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolveModule = resolveModule;
|
||||
function resolve(moduleName, nodePath, cwd, tracer) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolve = resolve;
|
||||
function resolveGlobalNodePath(tracer) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolveGlobalNodePath = resolveGlobalNodePath;
|
||||
function resolveGlobalYarnPath(tracer) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolveGlobalYarnPath = resolveGlobalYarnPath;
|
||||
function resolveModulePath(workspaceRoot, moduleName, nodePath, tracer) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolveModulePath = resolveModulePath;
|
||||
function resolveModule2(workspaceRoot, moduleName, nodePath, tracer) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
exports.resolveModule2 = resolveModule2;
|
|
@ -1,10 +0,0 @@
|
|||
Note: The file `JavaScript.tmLanguage.json` is derived from [TypeScriptReact.tmLanguage](https://github.com/Microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage).
|
||||
|
||||
# To update the grammar after making changes:
|
||||
|
||||
1. npm run update-grammar
|
||||
2. Re-comment imports in mongoParser.ts that are not used and cause compile errors
|
||||
|
||||
# Debugging the grammar
|
||||
|
||||
See instructions in launch.json. Be sure to explicitly save the mongo.g4 file to generate the debug info before trying to launch.
|
|
@ -1,240 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>fileTypes</key>
|
||||
<array/>
|
||||
<key>hideFromUser</key>
|
||||
<true/>
|
||||
<key>name</key>
|
||||
<!-- changed -->
|
||||
<string>Mongo Scrapbooks Regular Expressions (JavaScript)</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#regexp</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>repository</key>
|
||||
<dict>
|
||||
<key>regex-character-class</key>
|
||||
<dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\[wWsSdD]|\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.character-class.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\([0-7]{3}|x\h\h|u\h\h\h\h)</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.numeric.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\c[A-Z]</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.control.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.backslash.regexp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>regexp</key>
|
||||
<dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\[bB]|\^|\$</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.anchor.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\[1-9]\d*</string>
|
||||
<key>name</key>
|
||||
<string>keyword.other.back-reference.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>[?+*]|\{(\d+,\d+|\d+,|,\d+|\d+)\}\??</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.quantifier.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\|</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.or.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(\()((\?=)|(\?!))</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.group.regexp</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>meta.assertion.look-ahead.regexp</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>meta.assertion.negative-look-ahead.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(\))</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.group.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>meta.group.assertion.regexp</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#regexp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>\((\?:)?</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.group.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>\)</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.group.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>meta.group.regexp</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#regexp</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(\[)(\^)?</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.character-class.regexp</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.negation.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(\])</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.character-class.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>constant.other.character-class.set.regexp</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.numeric.regexp</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.control.regexp</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.backslash.regexp</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.numeric.regexp</string>
|
||||
</dict>
|
||||
<key>5</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.control.regexp</string>
|
||||
</dict>
|
||||
<key>6</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.backslash.regexp</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>(?:.|(\\(?:[0-7]{3}|x\h\h|u\h\h\h\h))|(\\c[A-Z])|(\\.))\-(?:[^\]\\]|(\\(?:[0-7]{3}|x\h\h|u\h\h\h\h))|(\\c[A-Z])|(\\.))</string>
|
||||
<key>name</key>
|
||||
<string>constant.other.character-class.range.regexp</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#regex-character-class</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#regex-character-class</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>scopeName</key>
|
||||
<!-- changed -->
|
||||
<string>source.mongo.js.regexp</string>
|
||||
<key>uuid</key>
|
||||
<!-- changed -->
|
||||
<string>c362a36f-6fd7-49c1-b7fb-90f53cdb7ee1</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"comments": {
|
||||
"lineComment": "//",
|
||||
"blockComment": [
|
||||
"/*",
|
||||
"*/"
|
||||
]
|
||||
},
|
||||
"brackets": [
|
||||
[
|
||||
"{",
|
||||
"}"
|
||||
],
|
||||
[
|
||||
"[",
|
||||
"]"
|
||||
]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{
|
||||
"open": "{",
|
||||
"close": "}"
|
||||
},
|
||||
{
|
||||
"open": "[",
|
||||
"close": "]"
|
||||
},
|
||||
{
|
||||
"open": "(",
|
||||
"close": ")"
|
||||
},
|
||||
{
|
||||
"open": "'",
|
||||
"close": "'",
|
||||
"notIn": [
|
||||
"string",
|
||||
"comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"open": "\"",
|
||||
"close": "\"",
|
||||
"notIn": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"open": "/**",
|
||||
"close": " */",
|
||||
"notIn": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
111
grammar/mongo.g4
|
@ -1,111 +0,0 @@
|
|||
grammar mongo;
|
||||
|
||||
@header {
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*tslint:disable */
|
||||
}
|
||||
|
||||
@lexer::members {
|
||||
private isExternalIdentifierText(text) {
|
||||
return text === 'db';
|
||||
}
|
||||
}
|
||||
|
||||
mongoCommands: commands EOF;
|
||||
|
||||
commands: ( command | emptyCommand | comment)*;
|
||||
|
||||
command: DB (DOT collection)? (DOT functionCall)+ SEMICOLON?;
|
||||
|
||||
emptyCommand: SEMICOLON;
|
||||
|
||||
collection: IDENTIFIER (DOT IDENTIFIER)*;
|
||||
|
||||
functionCall: FUNCTION_NAME = IDENTIFIER arguments;
|
||||
|
||||
arguments:
|
||||
OPEN_PARENTHESIS = '(' (argument ( ',' argument)*)? CLOSED_PARENTHESIS = ')';
|
||||
|
||||
argument: literal | objectLiteral | arrayLiteral;
|
||||
|
||||
objectLiteral: '{' propertyNameAndValueList? ','? '}';
|
||||
|
||||
arrayLiteral: '[' elementList? ']';
|
||||
|
||||
elementList: propertyValue ( ',' propertyValue)*;
|
||||
|
||||
propertyNameAndValueList:
|
||||
propertyAssignment (',' propertyAssignment)*;
|
||||
|
||||
propertyAssignment: propertyName ':' propertyValue;
|
||||
|
||||
propertyValue:
|
||||
literal
|
||||
| objectLiteral
|
||||
| arrayLiteral
|
||||
| functionCall;
|
||||
|
||||
literal: (NullLiteral | BooleanLiteral | StringLiteral)
|
||||
| RegexLiteral
|
||||
| NumericLiteral;
|
||||
|
||||
propertyName: StringLiteral | IDENTIFIER;
|
||||
|
||||
comment: SingleLineComment | MultiLineComment;
|
||||
|
||||
RegexLiteral:
|
||||
'/' (~[/\n\r*] | '\\/') (~[/\n\r] | '\\/')* '/' (RegexFlag)*;
|
||||
// Disallow '*' to succeed the opening '/'. This ensures we don't wrongly parse multi-line comments.
|
||||
// Disallow carriage returns too.
|
||||
|
||||
fragment RegexFlag: [gimuy];
|
||||
|
||||
SingleLineComment:
|
||||
'//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN);
|
||||
|
||||
MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN);
|
||||
|
||||
StringLiteral:
|
||||
SINGLE_QUOTED_STRING_LITERAL
|
||||
| DOUBLE_QUOTED_STRING_LITERAL;
|
||||
|
||||
NullLiteral: 'null';
|
||||
|
||||
BooleanLiteral: 'true' | 'false';
|
||||
|
||||
NumericLiteral: '-'? DecimalLiteral;
|
||||
|
||||
DecimalLiteral:
|
||||
DecimalIntegerLiteral '.' DecimalDigit+ ExponentPart?
|
||||
| '.' DecimalDigit+ ExponentPart?
|
||||
| DecimalIntegerLiteral ExponentPart?;
|
||||
|
||||
LineTerminator: [\r\n\u2028\u2029] -> channel(HIDDEN);
|
||||
|
||||
SEMICOLON: ';';
|
||||
DOT: '.';
|
||||
DB: 'db';
|
||||
|
||||
// Don't declare LR/CRLF tokens - they'll interfere with matching against LineTerminator LF: '\n';
|
||||
// CRLF: '\r\n';
|
||||
|
||||
IDENTIFIER: ((~[[\]"',\\ \t\n\r:.;(){}\-]) | STRING_ESCAPE)+ {!this.isExternalIdentifierText(this.text)
|
||||
}?;
|
||||
DOUBLE_QUOTED_STRING_LITERAL:
|
||||
'"' ((~["\\]) | STRING_ESCAPE)* '"';
|
||||
SINGLE_QUOTED_STRING_LITERAL:
|
||||
'\'' ((~['\\]) | STRING_ESCAPE)* '\'';
|
||||
|
||||
fragment STRING_ESCAPE: '\\' [\\"\\'];
|
||||
|
||||
fragment DecimalIntegerLiteral: '0' | [1-9] DecimalDigit*;
|
||||
|
||||
fragment ExponentPart: [eE] [+-]? DecimalDigit+;
|
||||
|
||||
fragment DecimalDigit: [0-9];
|
||||
|
||||
WHITESPACE: [ \t] -> skip;
|
Двоичные данные
resources/Browse.png
До Ширина: | Высота: | Размер: 43 KiB |
Двоичные данные
resources/Scrapbook.gif
До Ширина: | Высота: | Размер: 465 KiB |
Двоичные данные
resources/SelectSubscriptions.gif
До Ширина: | Высота: | Размер: 31 KiB |
Двоичные данные
resources/SignIn.gif
До Ширина: | Высота: | Размер: 764 KiB |
Двоичные данные
resources/attachEmulator.png
До Ширина: | Высота: | Размер: 23 KiB |
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="28" width="28" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill="#0072C6" d="M11.423,44.326l23.623-4.156L22.894,25.748l6.328-17.346L50,44.33L11.423,44.326z M27.566,5.67L11.469,40.109v-0.034H0l12.717-21.975L27.566,5.67z" />
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 337 B |
Двоичные данные
resources/cosmos.png
До Ширина: | Высота: | Размер: 21 KiB |
Двоичные данные
resources/create.gif
До Ширина: | Высота: | Размер: 1.1 MiB |
Двоичные данные
resources/features.png
До Ширина: | Высота: | Размер: 83 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M13.879 0l-2.214 2.214a4.162 4.162 0 0 0-1.554-.302c-1.066 0-2.094.415-2.818 1.139L4.465 5.878a3.976 3.976 0 0 0-1.172 2.829c0 .54.109 1.068.32 1.56l-2.331 2.33 2.121 2.121 2.331-2.331a3.94 3.94 0 0 0 1.559.32 3.973 3.973 0 0 0 2.828-1.172l2.828-2.828a3.981 3.981 0 0 0 .85-4.385L16 2.122v-.264L14.142 0h-.263z" id="outline"/><path class="icon-vs-fg" d="M8.707 10.121c-.756.756-2.072.756-2.828 0a1.986 1.986 0 0 1-.586-1.414c0-.534.208-1.036.586-1.414L6.94 6.232 9.768 9.06l-1.061 1.061zm2.828-2.828l-1.061 1.061-2.828-2.829 1.061-1.061c.756-.755 2.072-.755 2.828 0 .78.78.78 2.049 0 2.829z" id="iconFg"/><path class="icon-vs-bg" d="M14.718 1.99l-.707-.707-2.16 2.159c-1.155-.798-2.837-.698-3.851.315L5.172 6.586a2.978 2.978 0 0 0-.319 3.854l-2.156 2.156.707.707 2.156-2.157a2.982 2.982 0 0 0 3.854-.319L12.242 8a2.997 2.997 0 0 0 .318-3.853l2.158-2.157zm-6.011 8.131c-.756.756-2.072.756-2.828 0a1.986 1.986 0 0 1-.586-1.414c0-.534.208-1.036.586-1.414L6.94 6.232 9.768 9.06l-1.061 1.061zm2.828-2.828l-1.061 1.061-2.828-2.829 1.061-1.061c.756-.755 2.072-.755 2.828 0 .78.78.78 2.049 0 2.829z" id="iconBg"/></svg>
|
До Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30}.icon-canvas-transparent{opacity:0}.icon-vs-bg{fill:#c5c5c5}</style></defs><title>Process_16x</title><path class="icon-canvas-transparent" d="M16 0v16H0V0z"/><path class="icon-vs-out" d="M16 4.586l-1.018.6L16 5.743v.72l-.031.122a5.462 5.462 0 0 1-.5 1.264l-.376.681-1.306-.34.373 1.283-.657.4a4.885 4.885 0 0 1-.6.333 4.055 4.055 0 0 1-.549.183l-.851.244-.681-1.161-.65 1.185-.2-.051A3.58 3.58 0 0 1 10 11a3.852 3.852 0 0 1-.046.535l-.1.77-.782.191-.354.044.7.9-.476.614a5.038 5.038 0 0 1-.89.891l-.614.477-.9-.7-.138 1.12-.77.1A4.623 4.623 0 0 1 5 16a3.882 3.882 0 0 1-.536-.046l-.769-.1-.195-.782-.044-.353-.9.7-.614-.477a5.038 5.038 0 0 1-.89-.891l-.475-.614.7-.9L.158 12.4l-.1-.769A4.553 4.553 0 0 1 0 11a3.853 3.853 0 0 1 .046-.535l.1-.769.781-.196.354-.044-.7-.9.475-.614a5.022 5.022 0 0 1 .89-.891l.614-.477.9.7.14-1.116.77-.1A4.548 4.548 0 0 1 5 6a3.153 3.153 0 0 1 .387.033l-.014-.049L6.532 5.3l-1.181-.643.194-.757a5.422 5.422 0 0 1 .5-1.262l.375-.683 1.307.34-.376-1.287.764-.452a4.069 4.069 0 0 1 .506-.271A4.077 4.077 0 0 1 9.159.1l.363-.1h.578l.6 1.021L11.257 0h.719l.124.032a5.426 5.426 0 0 1 1.262.5l.681.376-.34 1.3L15 1.838l.452.769a4.289 4.289 0 0 1 .267.5 4.07 4.07 0 0 1 .185.556l.1.354z"/><path class="icon-vs-bg" d="M13.8 5.675a3.058 3.058 0 0 0-.012-.947l1.181-.693A4.266 4.266 0 0 0 14.8 3.5a4.563 4.563 0 0 0-.271-.487l-1.318.387a3.015 3.015 0 0 0-.68-.658l.347-1.329A4.421 4.421 0 0 0 11.849 1l-.661 1.2a3.075 3.075 0 0 0-.948.012l-.694-1.177a4.36 4.36 0 0 0-.53.168 4.32 4.32 0 0 0-.487.271l.383 1.315a3.044 3.044 0 0 0-.659.681l-1.33-.347a4.436 4.436 0 0 0-.409 1.03l1.2.66a3.039 3.039 0 0 0 .011.947l-1.181.693a4.294 4.294 0 0 0 .168.531 4.244 4.244 0 0 0 .271.487l1.317-.38a3.019 3.019 0 0 0 .681.658l-.346 1.329a4.454 4.454 0 0 0 1.03.41l.661-1.2a3.076 3.076 0 0 0 .948-.012l.693 1.181a4.272 4.272 0 0 0 .53-.168 4.108 4.108 0 0 0 .486-.271L12.6 7.7a3.041 3.041 0 0 0 .659-.681l1.33.347A4.392 4.392 0 0 0 15 6.335zm-2.169 1.59a2.2 2.2 0 1 1 1.149-2.891 2.2 2.2 0 0 1-1.152 2.891zm-3.917 3.077a2.744 2.744 0 0 0-.331-.795l.768-.984a4 4 0 0 0-.712-.713l-.985.768a2.783 2.783 0 0 0-.8-.331L5.5 7.051A3.9 3.9 0 0 0 5 7a3.9 3.9 0 0 0-.5.051l-.157 1.235a2.783 2.783 0 0 0-.8.331l-.981-.767a4 4 0 0 0-.712.713l.768.984a2.744 2.744 0 0 0-.331.795l-1.236.158A3.9 3.9 0 0 0 1 11a3.9 3.9 0 0 0 .051.5l1.235.154a2.744 2.744 0 0 0 .331.795l-.768.984a4 4 0 0 0 .712.713l.985-.768a2.783 2.783 0 0 0 .8.331l.154 1.24A3.886 3.886 0 0 0 5 15a3.886 3.886 0 0 0 .5-.051l.153-1.235a2.783 2.783 0 0 0 .8-.331l.985.768a4 4 0 0 0 .712-.713l-.768-.984a2.744 2.744 0 0 0 .331-.795l1.236-.159A3.9 3.9 0 0 0 9 11a3.9 3.9 0 0 0-.051-.5zM5 13a2 2 0 1 1 2-2 2 2 0 0 1-2 2zm1-2a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm5.767-6.191a1.1 1.1 0 1 1-1.445-.575 1.1 1.1 0 0 1 1.445.575z"/></svg>
|
До Ширина: | Высота: | Размер: 2.8 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 7V8H8V14H7V8H1V7H7V1H8V7H14Z" fill="#C5C5C5"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 163 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 0v3.043l5 6V16h6V9.043l5-6V0H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M7 14h2V8.319l5-6V2H2v.319l5 6V14z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 15H6V8.681l-5-6V1h14v1.681l-5 6V15zm-3-1h2V8.319l5-6V2H2v.319l5 6V14z" id="iconBg"/></svg>
|
До Ширина: | Высота: | Размер: 596 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 7H3V4H0V3H3V0H4V3H7V4H4V7ZM10.5 1.09998L13.9 4.59998L14 5V13.5L13.5 14H3.5L3 13.5V8H4V13H13V6H9V2H5V1H10.2L10.5 1.09998ZM10 2V5H12.9L10 2Z" fill="#C5C5C5"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 312 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H6V4H4.5L4 4.5V8C4 9.06087 4.42143 10.0783 5.17157 10.8284C5.80289 11.4597 6.6235 11.8582 7.5 11.9686V15H8.5V11.9686C9.3765 11.8582 10.1971 11.4597 10.8284 10.8284C11.5786 10.0783 12 9.06087 12 8V4.5L11.5 4H10V1H9V4H7V1ZM10.1213 10.1213C9.55871 10.6839 8.79565 11 8 11C7.20435 11 6.44129 10.6839 5.87868 10.1213C5.31607 9.55871 5 8.79565 5 8V5H11V8C11 8.79565 10.6839 9.55871 10.1213 10.1213Z" fill="#C5C5C5"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 567 B |
|
@ -1,4 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.56253 2.5158C3.46348 3.45013 2 5.55417 2 8.00002C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8.00002C14 5.32522 12.2497 3.05922 9.83199 2.28485L9.52968 3.23835C11.5429 3.88457 13 5.77213 13 8.00002C13 10.7614 10.7614 13 8 13C5.23858 13 3 10.7614 3 8.00002C3 6.31107 3.83742 4.8177 5.11969 3.91248L5.56253 2.5158Z" fill="#C5C5C5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3H2V2H5.5L6 2.5V6H5V3Z" fill="#C5C5C5"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 586 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M13.879 0l-2.214 2.214a4.162 4.162 0 0 0-1.554-.302c-1.066 0-2.094.415-2.818 1.139L4.465 5.878a3.976 3.976 0 0 0-1.172 2.829c0 .54.109 1.068.32 1.56l-2.331 2.33 2.121 2.121 2.331-2.331a3.94 3.94 0 0 0 1.559.32 3.973 3.973 0 0 0 2.828-1.172l2.828-2.828a3.981 3.981 0 0 0 .85-4.385L16 2.122v-.264L14.142 0h-.263z" id="outline"/><path class="icon-vs-fg" d="M8.707 10.121c-.756.756-2.072.756-2.828 0a1.986 1.986 0 0 1-.586-1.414c0-.534.208-1.036.586-1.414L6.94 6.232 9.768 9.06l-1.061 1.061zm2.828-2.828l-1.061 1.061-2.828-2.829 1.061-1.061c.756-.755 2.072-.755 2.828 0 .78.78.78 2.049 0 2.829z" id="iconFg"/><path class="icon-vs-bg" d="M14.718 1.99l-.707-.707-2.16 2.159c-1.155-.798-2.837-.698-3.851.315L5.172 6.586a2.978 2.978 0 0 0-.319 3.854l-2.156 2.156.707.707 2.156-2.157a2.982 2.982 0 0 0 3.854-.319L12.242 8a2.997 2.997 0 0 0 .318-3.853l2.158-2.157zm-6.011 8.131c-.756.756-2.072.756-2.828 0a1.986 1.986 0 0 1-.586-1.414c0-.534.208-1.036.586-1.414L6.94 6.232 9.768 9.06l-1.061 1.061zm2.828-2.828l-1.061 1.061-2.828-2.829 1.061-1.061c.756-.755 2.072-.755 2.828 0 .78.78.78 2.049 0 2.829z" id="iconBg"/></svg>
|
До Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6}.icon-canvas-transparent{opacity:0}.icon-vs-bg{fill:#424242}</style></defs><title>Process_16x</title><path class="icon-canvas-transparent" d="M16 0v16H0V0z"/><path class="icon-vs-out" d="M16 4.586l-1.018.6L16 5.743v.72l-.031.122a5.462 5.462 0 0 1-.5 1.264l-.376.681-1.306-.34.373 1.283-.657.4a4.885 4.885 0 0 1-.6.333 4.055 4.055 0 0 1-.549.183l-.851.244-.681-1.161-.65 1.185-.2-.051A3.58 3.58 0 0 1 10 11a3.852 3.852 0 0 1-.046.535l-.1.77-.782.191-.354.044.7.9-.476.614a5.038 5.038 0 0 1-.89.891l-.614.477-.9-.7-.138 1.12-.77.1A4.623 4.623 0 0 1 5 16a3.882 3.882 0 0 1-.536-.046l-.769-.1-.195-.782-.044-.353-.9.7-.614-.477a5.038 5.038 0 0 1-.89-.891l-.475-.614.7-.9L.158 12.4l-.1-.769A4.553 4.553 0 0 1 0 11a3.853 3.853 0 0 1 .046-.535l.1-.769.781-.196.354-.044-.7-.9.475-.614a5.022 5.022 0 0 1 .89-.891l.614-.477.9.7.14-1.116.77-.1A4.548 4.548 0 0 1 5 6a3.153 3.153 0 0 1 .387.033l-.014-.049L6.532 5.3l-1.181-.643.194-.757a5.422 5.422 0 0 1 .5-1.262l.375-.683 1.307.34-.376-1.287.764-.452a4.069 4.069 0 0 1 .506-.271A4.077 4.077 0 0 1 9.159.1l.363-.1h.578l.6 1.021L11.257 0h.719l.124.032a5.426 5.426 0 0 1 1.262.5l.681.376-.34 1.3L15 1.838l.452.769a4.289 4.289 0 0 1 .267.5 4.07 4.07 0 0 1 .185.556l.1.354z" style="display: none;"/><path class="icon-vs-bg" d="M13.8 5.675a3.058 3.058 0 0 0-.012-.947l1.181-.693A4.266 4.266 0 0 0 14.8 3.5a4.563 4.563 0 0 0-.271-.487l-1.318.387a3.015 3.015 0 0 0-.68-.658l.347-1.329A4.421 4.421 0 0 0 11.849 1l-.661 1.2a3.075 3.075 0 0 0-.948.012l-.694-1.177a4.36 4.36 0 0 0-.53.168 4.32 4.32 0 0 0-.487.271l.383 1.315a3.044 3.044 0 0 0-.659.681l-1.33-.347a4.436 4.436 0 0 0-.409 1.03l1.2.66a3.039 3.039 0 0 0 .011.947l-1.181.693a4.294 4.294 0 0 0 .168.531 4.244 4.244 0 0 0 .271.487l1.317-.38a3.019 3.019 0 0 0 .681.658l-.346 1.329a4.454 4.454 0 0 0 1.03.41l.661-1.2a3.076 3.076 0 0 0 .948-.012l.693 1.181a4.272 4.272 0 0 0 .53-.168 4.108 4.108 0 0 0 .486-.271L12.6 7.7a3.041 3.041 0 0 0 .659-.681l1.33.347A4.392 4.392 0 0 0 15 6.335zm-2.169 1.59a2.2 2.2 0 1 1 1.149-2.891 2.2 2.2 0 0 1-1.152 2.891zm-3.917 3.077a2.744 2.744 0 0 0-.331-.795l.768-.984a4 4 0 0 0-.712-.713l-.985.768a2.783 2.783 0 0 0-.8-.331L5.5 7.051A3.9 3.9 0 0 0 5 7a3.9 3.9 0 0 0-.5.051l-.157 1.235a2.783 2.783 0 0 0-.8.331l-.981-.767a4 4 0 0 0-.712.713l.768.984a2.744 2.744 0 0 0-.331.795l-1.236.158A3.9 3.9 0 0 0 1 11a3.9 3.9 0 0 0 .051.5l1.235.154a2.744 2.744 0 0 0 .331.795l-.768.984a4 4 0 0 0 .712.713l.985-.768a2.783 2.783 0 0 0 .8.331l.154 1.24A3.886 3.886 0 0 0 5 15a3.886 3.886 0 0 0 .5-.051l.153-1.235a2.783 2.783 0 0 0 .8-.331l.985.768a4 4 0 0 0 .712-.713l-.768-.984a2.744 2.744 0 0 0 .331-.795l1.236-.159A3.9 3.9 0 0 0 9 11a3.9 3.9 0 0 0-.051-.5zM5 13a2 2 0 1 1 2-2 2 2 0 0 1-2 2zm1-2a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm5.767-6.191a1.1 1.1 0 1 1-1.445-.575 1.1 1.1 0 0 1 1.445.575z"/></svg>
|
До Ширина: | Высота: | Размер: 2.9 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9999 7V8H7.99988V14H6.99988V8H0.999878V7H6.99988V1H7.99988V7H13.9999Z" fill="#424242"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 204 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 0v3.043l5 6V16h6V9.043l5-6V0H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M7 14h2V8.319l5-6V2H2v.319l5 6V14z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 15H6V8.681l-5-6V1h14v1.681l-5 6V15zm-3-1h2V8.319l5-6V2H2v.319l5 6V14z" id="iconBg"/></svg>
|
До Ширина: | Высота: | Размер: 596 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 7H3V4H0V3H3V0H4V3H7V4H4V7ZM10.5 1.09998L13.9 4.59998L14 5V13.5L13.5 14H3.5L3 13.5V8H4V13H13V6H9V2H5V1H10.2L10.5 1.09998ZM10 2V5H12.9L10 2Z" fill="#424242"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 312 B |
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H6V4H4.5L4 4.5V8C4 9.06087 4.42143 10.0783 5.17157 10.8284C5.80289 11.4597 6.6235 11.8582 7.5 11.9686V15H8.5V11.9686C9.3765 11.8582 10.1971 11.4597 10.8284 10.8284C11.5786 10.0783 12 9.06087 12 8V4.5L11.5 4H10V1H9V4H7V1ZM10.1213 10.1213C9.55871 10.6839 8.79565 11 8 11C7.20435 11 6.44129 10.6839 5.87868 10.1213C5.31607 9.55871 5 8.79565 5 8V5H11V8C11 8.79565 10.6839 9.55871 10.1213 10.1213Z" fill="#424242"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 567 B |
|
@ -1,4 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.56253 2.5158C3.46348 3.45013 2 5.55417 2 8.00002C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8.00002C14 5.32522 12.2497 3.05922 9.83199 2.28485L9.52968 3.23835C11.5429 3.88457 13 5.77213 13 8.00002C13 10.7614 10.7614 13 8 13C5.23858 13 3 10.7614 3 8.00002C3 6.31107 3.83742 4.8177 5.11969 3.91248L5.56253 2.5158Z" fill="#424242"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3H2V2H5.5L6 2.5V6H5V3Z" fill="#424242"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 586 B |
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0078D7;}
|
||||
.st1{fill:#F6F6F6;}
|
||||
.st2{fill:#FFFFFF;enable-background:new ;}
|
||||
</style>
|
||||
<polygon class="st0" points="14.1,2.1 12.8,0.8 12.2,0.3 12.2,0.3 2.5,0.3 2.5,14 14.7,14 14.7,2.7 14.7,2.7 "/>
|
||||
<polygon class="st1" points="12.2,0.8 3.1,0.8 3.1,13.5 14.1,13.5 14.1,2.7 13.4,2.7 12.2,2.7 "/>
|
||||
<polygon class="st0" points="12.4,3.8 11.1,2.5 10.5,2 10.5,2 0.9,2 0.9,15.7 13,15.7 13,4.5 13,4.4 "/>
|
||||
<polygon class="st2" points="10.5,2.5 1.4,2.5 1.4,15.2 12.4,15.2 12.4,4.5 11.7,4.5 10.5,4.5 "/>
|
||||
<path class="st0" d="M10.6,9.5c-0.5,0-0.7,0.3-0.7,0.7v0.9c0,0.5-0.1,0.9-0.3,1.1s-0.7,0.3-1.3,0.3v-0.7c0.3,0,0.4-0.1,0.5-0.1
|
||||
c0.1-0.1,0.1-0.3,0.1-0.5v-0.9c0-0.7,0.3-1.1,0.7-1.2l0,0C9.2,9,8.9,8.6,8.9,7.9V7c0-0.5-0.3-0.7-0.7-0.7V5.5c0.1,0,0.3,0,0.3,0
|
||||
c0.4,0,0.7,0.1,0.9,0.3c0.3,0.3,0.3,0.7,0.3,1.2v0.9c0,0.5,0.3,0.7,0.7,0.7L10.6,9.5L10.6,9.5z M5.4,12.5c-0.5,0-1.1-0.1-1.3-0.3
|
||||
c-0.3-0.3-0.3-0.5-0.3-1.1v-0.9C3.8,9.7,3.6,9.4,3.2,9.4V8.7c0.4,0,0.5-0.3,0.5-0.8V7.1c0-0.5,0.1-0.9,0.4-1.1
|
||||
c0.3-0.3,0.7-0.4,1.3-0.4v0.8C5,6.3,4.7,6.6,4.7,7.1v0.8c0,0.7-0.3,1.1-0.7,1.2l0,0c0.4,0.1,0.7,0.5,0.7,1.2v0.8
|
||||
c0,0.3,0.1,0.5,0.1,0.5c0.1,0.3,0.3,0.3,0.5,0.3v0.7H5.4z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -1,22 +0,0 @@
|
|||
<svg id="b089cfca-0de1-451c-a1ca-6680ea50cb4f" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<radialGradient id="b25d0836-964a-4c84-8c20-855f66e8345e" cx="-105.006" cy="-10.409" r="5.954" gradientTransform="translate(117.739 19.644) scale(1.036 1.027)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.183" stop-color="#5ea0ef"/>
|
||||
<stop offset="1" stop-color="#0078d4"/>
|
||||
</radialGradient>
|
||||
<clipPath id="b36c7f5d-2ef1-4760-8a25-eeb9661f4e47">
|
||||
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="none"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<title>Icon-databases-121</title>
|
||||
<path d="M2.954,5.266a.175.175,0,0,1-.176-.176h0A2.012,2.012,0,0,0,.769,3.081a.176.176,0,0,1-.176-.175h0a.176.176,0,0,1,.176-.176A2.012,2.012,0,0,0,2.778.72.175.175,0,0,1,2.954.544h0A.175.175,0,0,1,3.13.72h0A2.012,2.012,0,0,0,5.139,2.729a.175.175,0,0,1,.176.176h0a.175.175,0,0,1-.176.176h0A2.011,2.011,0,0,0,3.13,5.09.177.177,0,0,1,2.954,5.266Z" fill="#50e6ff"/>
|
||||
<path d="M15.611,17.456a.141.141,0,0,1-.141-.141h0a1.609,1.609,0,0,0-1.607-1.607.141.141,0,0,1-.141-.14h0a.141.141,0,0,1,.141-.141h0a1.608,1.608,0,0,0,1.607-1.607.141.141,0,0,1,.141-.141h0a.141.141,0,0,1,.141.141h0a1.608,1.608,0,0,0,1.607,1.607.141.141,0,1,1,0,.282h0a1.609,1.609,0,0,0-1.607,1.607A.141.141,0,0,1,15.611,17.456Z" fill="#50e6ff"/>
|
||||
<g>
|
||||
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="url(#b25d0836-964a-4c84-8c20-855f66e8345e)"/>
|
||||
<g clip-path="url(#b36c7f5d-2ef1-4760-8a25-eeb9661f4e47)">
|
||||
<path d="M5.709,13.115A1.638,1.638,0,1,0,5.714,9.84,1.307,1.307,0,0,0,5.721,9.7,1.651,1.651,0,0,0,4.06,8.064H2.832a6.251,6.251,0,0,0,1.595,5.051Z" fill="#f2f2f2"/>
|
||||
<path d="M15.045,7.815c0-.015,0-.03-.007-.044a5.978,5.978,0,0,0-1.406-2.88,1.825,1.825,0,0,0-.289-.09,1.806,1.806,0,0,0-2.3,1.663,2,2,0,0,0-.2-.013,1.737,1.737,0,0,0-.581,3.374,1.451,1.451,0,0,0,.541.1h2.03A13.453,13.453,0,0,0,15.045,7.815Z" fill="#f2f2f2"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M17.191,3.832c-.629-1.047-2.1-1.455-4.155-1.149a14.606,14.606,0,0,0-2.082.452,6.456,6.456,0,0,1,1.528.767c.241-.053.483-.116.715-.151A7.49,7.49,0,0,1,14.3,3.662a2.188,2.188,0,0,1,1.959.725h0c.383.638.06,1.729-.886,3a16.723,16.723,0,0,1-4.749,4.051A16.758,16.758,0,0,1,4.8,13.7c-1.564.234-2.682,0-3.065-.636s-.06-1.73.886-2.995c.117-.157.146-.234.279-.392a6.252,6.252,0,0,1,.026-1.63A11.552,11.552,0,0,0,1.756,9.419C.517,11.076.181,12.566.809,13.613a3.165,3.165,0,0,0,2.9,1.249,8.434,8.434,0,0,0,1.251-.1,17.855,17.855,0,0,0,6.219-2.4,17.808,17.808,0,0,0,5.061-4.332C17.483,6.369,17.819,4.88,17.191,3.832Z" fill="#50e6ff"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 2.6 KiB |
|
@ -1,28 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#0078D7;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="canvas_5_">
|
||||
<path class="st0" d="M16.5,16.3H-0.5V-0.5h17.1V16.3z"/>
|
||||
</g>
|
||||
<g id="iconBg_8_">
|
||||
<g>
|
||||
<rect x="7.9" y="7.9" class="st1" width="1.1" height="3.2"/>
|
||||
<path class="st1" d="M7.7,0.5c-3.3,0-6.1,1.3-6.1,2.4V13c0,1.3,3.5,2.4,6.7,2.4s5.9-1.1,5.9-2.3V2.9C14.2,1.8,10.9,0.5,7.7,0.5z
|
||||
M7.9,1.5c2.3,0,4.2,0.5,4.2,1.2s-1.9,1.2-4.2,1.2S3.7,3.4,3.7,2.6S5.6,1.5,7.9,1.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st2" d="M12.3,9.7c-0.4,0-0.6,0.2-0.6,0.8v0.9c0,0.6-0.1,0.9-0.4,1.3C11,12.9,10.5,13,9.9,13v-0.8
|
||||
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5v-0.9c0-0.6,0.2-1.2,0.6-1.3l0,0c-0.4-0.2-0.6-0.6-0.6-1.4V6.8
|
||||
c0-0.5-0.2-0.7-0.6-0.7V5.2c0.1,0,0.2,0,0.3,0c0.4,0,0.8,0.2,1.1,0.4c0.2,0.2,0.4,0.6,0.4,1.3v0.9c0,0.5,0.2,0.8,0.6,0.8L12.3,9.7
|
||||
L12.3,9.7z M5.8,12.9c-0.6,0-1.1-0.1-1.4-0.3C4.2,12.4,4,12,4,11.3v-1.1c0-0.5-0.2-0.8-0.6-0.8V8.7C3.8,8.7,4,8.4,4,7.9V6.9
|
||||
C4,6.3,4.1,6,4.4,5.7c0.3-0.2,0.7-0.3,1.4-0.3v0.8c-0.4,0-0.6,0.2-0.6,0.7v0.9c0,0.6-0.2,1.2-0.6,1.4l0,0c0.4,0.2,0.6,0.6,0.6,1.4
|
||||
v0.9c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.5,0.2L5.8,12.9L5.8,12.9z"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0078D7;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="iconBg_7_">
|
||||
<g>
|
||||
<rect x="6.9" y="7.8" class="st0" width="1.1" height="3.2"/>
|
||||
<path class="st0" d="M6.7,0.4c-3.3,0-6.1,1.3-6.1,2.4v10.1c0,1.3,3.5,2.4,6.7,2.4s5.9-1.1,5.9-2.3V2.9C13.2,1.7,10,0.4,6.7,0.4z
|
||||
M6.9,1.4c2.3,0,4.2,0.5,4.2,1.2S9.2,3.7,6.9,3.7S2.7,3.3,2.7,2.5S4.6,1.4,6.9,1.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M11.3,9.6c-0.4,0-0.6,0.2-0.6,0.8v0.9c0,0.6-0.1,0.9-0.4,1.3c-0.2,0.2-0.7,0.3-1.4,0.3v-0.8
|
||||
c0.2,0,0.4-0.1,0.5-0.2s0.2-0.3,0.2-0.5v-0.9c0-0.6,0.2-1.2,0.6-1.3l0,0C9.9,9,9.7,8.5,9.7,7.8V6.8C9.7,6.2,9.4,6,9,6V5.2
|
||||
c0.1,0,0.2,0,0.3,0c0.4,0,0.8,0.2,1.1,0.4c0.2,0.2,0.4,0.6,0.4,1.3v0.9c0,0.5,0.2,0.8,0.6,0.8L11.3,9.6L11.3,9.6z M4.8,12.8
|
||||
c-0.6,0-1.1-0.1-1.4-0.3C3.2,12.3,3,11.9,3,11.3v-1.1c0-0.5-0.2-0.8-0.6-0.8V8.6C2.8,8.6,3,8.3,3,7.8V6.9c0-0.6,0.1-0.9,0.4-1.3
|
||||
c0.3-0.2,0.7-0.3,1.4-0.3v0.8c-0.4,0-0.6,0.2-0.6,0.7v0.9C4.2,8.4,4,9,3.6,9.2l0,0C4,9.4,4.2,9.8,4.2,10.5v0.9
|
||||
c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.5,0.2L4.8,12.8L4.8,12.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M14.7,12.4c-0.1-0.9-0.8-1.6-1.7-1.6c-0.2,0-0.4,0.1-0.5,0.1c-0.4-0.5-0.9-0.7-1.5-0.7C9.9,10.2,9,11,8.9,12
|
||||
c-0.7,0.3-1.2,1-1.2,1.8c0,1.1,0.8,1.9,1.8,1.9H14c0.1,0,0.1,0,0.1,0c0.9-0.1,1.5-0.9,1.5-1.7C15.6,13.4,15.3,12.8,14.7,12.4z"/>
|
||||
<path class="st0" d="M14.1,12.6c-0.1-0.7-0.7-1.3-1.4-1.3c-0.1,0-0.3,0-0.5,0.1c-0.3-0.4-0.8-0.6-1.3-0.6c-0.9,0-1.6,0.7-1.7,1.5
|
||||
c-0.6,0.2-1,0.8-1,1.5c0,0.9,0.7,1.6,1.5,1.6h3.8c0.1,0,0.1,0,0.1,0c0.7-0.1,1.3-0.7,1.3-1.5C14.9,13.4,14.6,12.9,14.1,12.6z"/>
|
||||
<path class="st1" d="M13.5,13.2c-0.1,0-0.1,0-0.2,0c0.1-0.1,0.1-0.3,0.1-0.5c0-0.5-0.3-0.8-0.7-0.8c-0.3,0-0.5,0.2-0.7,0.5
|
||||
c-0.1-0.6-0.5-1-1.1-1c-0.6,0-1.1,0.5-1.1,1.2c0,0.1,0,0.2,0.1,0.3c-0.1,0-0.1,0-0.2,0c-0.5,0-0.9,0.4-0.9,0.9
|
||||
c0,0.5,0.4,0.9,0.8,0.9l0,0h3.8l0,0c0.4,0,0.7-0.4,0.7-0.8C14.3,13.5,13.9,13.2,13.5,13.2z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 2.2 KiB |
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0078D7;}
|
||||
.st1{fill:#FFFFFF;enable-background:new ;}
|
||||
</style>
|
||||
<polygon class="st0" points="13.8,2.7 12.4,1.3 11.8,0.7 11.8,0.7 1.6,0.7 1.6,15.3 14.4,15.3 14.4,3.3 14.4,3.3 "/>
|
||||
<polygon class="st1" points="11.8,1.3 2.2,1.3 2.2,14.7 13.8,14.7 13.8,3.3 13.1,3.3 11.8,3.3 "/>
|
||||
<path class="st0" d="M11.9,8.7c-0.4,0-0.6,0.2-0.6,0.7v0.8c0,0.5-0.1,0.8-0.3,1c-0.2,0.2-0.7,0.3-1.2,0.3v-0.6
|
||||
c0.2,0,0.4-0.1,0.4-0.1c0.1-0.1,0.1-0.3,0.1-0.5V9.5c0-0.6,0.2-1,0.6-1.1l0,0c-0.4-0.1-0.6-0.5-0.6-1.2V6.4c0-0.4-0.2-0.7-0.6-0.7V5
|
||||
c0.1,0,0.2,0,0.3,0c0.4,0,0.7,0.1,0.9,0.3c0.2,0.2,0.3,0.6,0.3,1.1v0.8c0,0.4,0.2,0.7,0.6,0.7L11.9,8.7L11.9,8.7z M6.4,11.5
|
||||
c-0.5,0-1-0.1-1.2-0.3c-0.2-0.2-0.3-0.5-0.3-1V9.3C4.8,8.9,4.6,8.6,4.3,8.6V7.9c0.4,0,0.5-0.2,0.5-0.7V6.5c0-0.5,0.1-0.8,0.4-1
|
||||
C5.4,5.1,5.8,5,6.3,5v0.7C5.9,5.8,5.7,6,5.7,6.5v0.7c0,0.6-0.2,1-0.6,1.1l0,0c0.4,0.1,0.6,0.5,0.6,1.1v0.7c0,0.2,0.1,0.4,0.1,0.5
|
||||
c0.1,0.2,0.2,0.2,0.4,0.2v0.6H6.4z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 12 12" enable-background="new 0 0 12 12" xml:space="preserve">
|
||||
<path fill="#0072C6" d="M6,11.5C3,11.5,0.5,9,0.5,6C0.5,3,3,0.5,6,0.5c3,0,5.5,2.5,5.5,5.5C11.5,9,9,11.5,6,11.5z"/>
|
||||
<path fill="#FFFFFF" d="M6,1c2.8,0,5,2.2,5,5s-2.2,5-5,5S1,8.8,1,6S3.2,1,6,1 M6,0C2.7,0,0,2.7,0,6s2.7,6,6,6s6-2.7,6-6S9.3,0,6,0
|
||||
L6,0z"/>
|
||||
<polygon fill="#FFFFFF" points="6,3 3,5.7 5.2,5.7 5.2,9 6.8,9 6.8,5.7 9,5.7 "/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 775 B |
|
@ -1,39 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0078D7;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M3.5,2.2C3.3,2,3.1,1.8,2.9,1.6c-0.5-0.3-1-0.5-1-0.5L1.2,0.9C1.1,1.1,1,1.2,1,1.5c0,0.9,0.3,1.1,0.3,1.1h2.4
|
||||
C3.6,2.5,3.5,2.4,3.5,2.2z"/>
|
||||
<path class="st0" d="M3.9,14.6c0.2-0.2,0.3-0.5,0.3-0.9v-0.5h0.5H12c0-0.2,0-0.4,0-0.5c0-2.6,2.3-6.4,2.3-9.4
|
||||
c0-2.4-2.3-2.7-2.3-2.7H3.8H2c0,0,1.8,0.4,2.2,2C4.3,2.8,4.3,3,4.3,3.3c0,3-2.3,6.8-2.3,9.4c0,0.3,0,0.7,0.1,1
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.1,0.1,0.2,0.2,0.4,0.3c0.1,0.1,0.3,0.2,0.4,0.2C3.5,14.9,3.7,14.8,3.9,14.6z"/>
|
||||
<path class="st0" d="M3.4,15.4"/>
|
||||
<path class="st0" d="M4.7,13.8c0,0.5-0.2,1-0.4,1.2c-0.3,0.3-0.9,0.4-0.9,0.4l9.8,0h0.3c0,0,0,0,0,0c0.3,0,0.6-0.2,0.8-0.5
|
||||
c0.2-0.3,0.3-0.7,0.3-1.2h-2.5L4.7,13.8L4.7,13.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M9,7.2c0,0-0.1-0.1-0.2-0.1L8.6,7.2c-0.1,0-0.1,0-0.2,0L8.3,7c0,0-0.1-0.1,0-0.2l0.1-0.2c0-0.1,0-0.1-0.1-0.1
|
||||
L7.8,6.3c-0.1,0-0.1,0-0.1,0.1L7.5,6.6c0,0.1-0.1,0.1-0.1,0.1l-0.2,0C7.1,6.7,7,6.6,7,6.6L6.8,6.4c0-0.1-0.1-0.1-0.1-0.1
|
||||
c0,0-0.1,0-0.2,0.1C6.3,6.4,6.2,6.5,6.2,6.5c0,0-0.1,0.1-0.1,0.2l0.1,0.2c0,0.1,0,0.1,0,0.2L6.1,7.3c0,0-0.1,0.1-0.2,0L5.6,7.2
|
||||
c-0.1,0-0.1,0-0.1,0.1L5.3,7.8c0,0.1,0,0.1,0.1,0.1L5.6,8c0.1,0,0.1,0.1,0.1,0.1l0,0.2c0,0.1,0,0.1-0.1,0.2L5.4,8.7
|
||||
c-0.1,0-0.1,0.1-0.1,0.1c0,0,0,0.1,0.1,0.2c0.1,0.1,0.1,0.2,0.1,0.2c0,0,0.1,0.1,0.2,0.1l0.2-0.1c0.1,0,0.1,0,0.2,0l0.2,0.2
|
||||
c0,0,0.1,0.1,0,0.2L6.3,9.9c0,0.1,0,0.1,0.1,0.1l0.5,0.2c0.1,0,0.1,0,0.1-0.1l0.1-0.2c0-0.1,0.1-0.1,0.1-0.1l0.2,0
|
||||
c0.1,0,0.1,0,0.2,0.1l0.1,0.2c0,0.1,0.1,0.1,0.1,0.1c0,0,0.1,0,0.2-0.1C8.2,10.1,8.3,10,8.3,10c0,0,0.1-0.1,0.1-0.2L8.3,9.6
|
||||
c0-0.1,0-0.1,0-0.2l0.2-0.2c0,0,0.1-0.1,0.2,0l0.2,0.1c0.1,0,0.1,0,0.1-0.1l0.2-0.5c0-0.1,0-0.1-0.1-0.1L9,8.5
|
||||
c-0.1,0-0.1-0.1-0.1-0.1l0-0.2c0-0.1,0-0.1,0.1-0.2l0.2-0.1c0.1,0,0.1-0.1,0.1-0.1c0,0,0-0.1-0.1-0.2C9.1,7.3,9,7.2,9,7.2z
|
||||
M7.5,8.7c-0.3,0.1-0.6,0-0.7-0.3c-0.1-0.3,0-0.6,0.3-0.7c0.3-0.1,0.6,0,0.7,0.3C7.9,8.3,7.8,8.6,7.5,8.7z"/>
|
||||
<path class="st1" d="M11.6,5.4c0,0-0.1-0.1-0.1-0.1l-0.2,0c-0.1,0-0.1,0-0.1-0.1L11,5c0,0,0-0.1,0-0.1l0.1-0.2c0,0,0-0.1,0-0.1
|
||||
l-0.3-0.3c0,0-0.1,0-0.1,0l-0.2,0.1c0,0-0.1,0-0.1,0l-0.2-0.1c0,0-0.1-0.1-0.1-0.1l0-0.2c0-0.1,0-0.1-0.1-0.1c0,0-0.1,0-0.2,0
|
||||
c-0.1,0-0.2,0-0.2,0c0,0-0.1,0.1-0.1,0.1l0,0.2c0,0.1,0,0.1-0.1,0.1L9.2,4.4c0,0-0.1,0-0.1,0L8.9,4.2c0,0-0.1,0-0.1,0L8.4,4.6
|
||||
c0,0,0,0.1,0,0.1l0.1,0.2c0,0,0,0.1,0,0.1L8.5,5.2c0,0-0.1,0.1-0.1,0.1l-0.2,0c-0.1,0-0.1,0-0.1,0.1c0,0,0,0.1,0,0.2
|
||||
c0,0.1,0,0.2,0,0.2c0,0,0.1,0.1,0.1,0.1l0.2,0c0.1,0,0.1,0,0.1,0.1l0.1,0.2c0,0,0,0.1,0,0.1L8.4,6.6c0,0,0,0.1,0,0.1L8.7,7
|
||||
c0,0,0.1,0,0.1,0L9,6.9c0,0,0.1,0,0.1,0l0.2,0.1c0,0,0.1,0.1,0.1,0.1l0,0.2c0,0.1,0,0.1,0.1,0.1c0,0,0.1,0,0.2,0c0.1,0,0.2,0,0.2,0
|
||||
c0,0,0.1-0.1,0.1-0.1l0-0.2c0-0.1,0-0.1,0.1-0.1l0.2-0.1c0,0,0.1,0,0.1,0L10.8,7c0,0,0.1,0,0.1,0l0.3-0.3c0,0,0-0.1,0-0.1l-0.1-0.2
|
||||
c0,0,0-0.1,0-0.1L11.1,6c0,0,0.1-0.1,0.1-0.1l0.2,0c0.1,0,0.1,0,0.1-0.1c0,0,0-0.1,0-0.2C11.6,5.5,11.6,5.4,11.6,5.4z M9.8,6.1
|
||||
c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5c0.3,0,0.5,0.2,0.5,0.5C10.3,5.9,10.1,6.1,9.8,6.1z"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 3.3 KiB |
Двоичные данные
resources/import_documents.gif
До Ширина: | Высота: | Размер: 3.8 MiB |
|
@ -1,173 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fse from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { MessageItem, ViewColumn } from 'vscode';
|
||||
import { AzureTreeItem, DialogResponses, IActionContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { DocDBDocumentNodeEditor } from './docdb/editors/DocDBDocumentNodeEditor';
|
||||
import { DocDBStoredProcedureNodeEditor } from './docdb/editors/DocDBStoredProcedureNodeEditor';
|
||||
import { DocDBDocumentTreeItem } from './docdb/tree/DocDBDocumentTreeItem';
|
||||
import { DocDBStoredProcedureTreeItem } from './docdb/tree/DocDBStoredProcedureTreeItem';
|
||||
import { ext } from './extensionVariables';
|
||||
import { MongoCollectionNodeEditor } from './mongo/editors/MongoCollectionNodeEditor';
|
||||
import { MongoDocumentNodeEditor } from './mongo/editors/MongoDocumentNodeEditor';
|
||||
import { MongoCollectionTreeItem } from './mongo/tree/MongoCollectionTreeItem';
|
||||
import { MongoDocumentTreeItem } from './mongo/tree/MongoDocumentTreeItem';
|
||||
import * as vscodeUtils from './utils/vscodeUtils';
|
||||
|
||||
export interface ICosmosEditor<T = {}> {
|
||||
label: string;
|
||||
id: string;
|
||||
getData(context: IActionContext): Promise<T>;
|
||||
update(data: T, context: IActionContext): Promise<T>;
|
||||
convertFromString(data: string): T;
|
||||
convertToString(data: T): string;
|
||||
}
|
||||
|
||||
export interface ShowEditorDocumentOptions {
|
||||
/**
|
||||
* Shows the document to the right of the current editor, and keeps focus on the active document
|
||||
*/
|
||||
showInNextColumn?: boolean;
|
||||
}
|
||||
|
||||
export class CosmosEditorManager {
|
||||
private fileMap: { [key: string]: ICosmosEditor } = {};
|
||||
private ignoreSave: boolean = false;
|
||||
|
||||
private readonly showSavePromptKey: string = 'cosmosDB.showSavePrompt';
|
||||
private _globalState: vscode.Memento;
|
||||
private readonly _persistedEditorsKey: string = "ms-azuretools.vscode-cosmosdb.editors";
|
||||
|
||||
constructor(globalState: vscode.Memento) {
|
||||
this._globalState = globalState;
|
||||
}
|
||||
|
||||
public async showDocument(context: IActionContext, editor: ICosmosEditor, fileName: string, options?: ShowEditorDocumentOptions): Promise<void> {
|
||||
let column: vscode.ViewColumn = vscode.ViewColumn.Active;
|
||||
let preserveFocus: boolean = false;
|
||||
if (options && options.showInNextColumn) {
|
||||
preserveFocus = true;
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (activeEditor && activeEditor.viewColumn >= vscode.ViewColumn.One) {
|
||||
column = activeEditor.viewColumn < ViewColumn.Three ? activeEditor.viewColumn + 1 : ViewColumn.One;
|
||||
}
|
||||
}
|
||||
|
||||
const localFilename = fileName.replace(/[<>:"\/\\|?*]/g, "-");
|
||||
const localDocPath = path.join(os.tmpdir(), 'vscode-cosmosdb-editor', localFilename);
|
||||
await fse.ensureFile(localDocPath);
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(localDocPath);
|
||||
if (document.isDirty) {
|
||||
const overwriteFlag = await vscode.window.showWarningMessage(`You are about to overwrite "${fileName}", which has unsaved changes. Do you want to continue?`, { modal: true }, DialogResponses.yes, DialogResponses.cancel);
|
||||
if (overwriteFlag !== DialogResponses.yes) {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
this.fileMap[localDocPath] = editor;
|
||||
const fileMapLabels = this._globalState.get(this._persistedEditorsKey, {});
|
||||
Object.keys(this.fileMap).forEach((key) => fileMapLabels[key] = (this.fileMap[key]).id);
|
||||
this._globalState.update(this._persistedEditorsKey, fileMapLabels);
|
||||
|
||||
const data = await editor.getData(context);
|
||||
const textEditor = await vscode.window.showTextDocument(document, column, preserveFocus);
|
||||
await this.updateEditor(data, textEditor, editor);
|
||||
}
|
||||
|
||||
public async updateMatchingNode(context: IActionContext, documentUri: vscode.Uri): Promise<void> {
|
||||
let filePath: string = Object.keys(this.fileMap).find((fp) => path.relative(documentUri.fsPath, fp) === '');
|
||||
if (!filePath) {
|
||||
filePath = await this.loadPersistedEditor(documentUri, context);
|
||||
}
|
||||
const document = await vscode.workspace.openTextDocument(documentUri.fsPath);
|
||||
await this.updateToCloud(this.fileMap[filePath], document, context);
|
||||
}
|
||||
|
||||
public async onDidSaveTextDocument(context: IActionContext, doc: vscode.TextDocument): Promise<void> {
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
let filePath = Object.keys(this.fileMap).find((fp) => path.relative(doc.uri.fsPath, fp) === '');
|
||||
if (!filePath) {
|
||||
filePath = await this.loadPersistedEditor(doc.uri, context);
|
||||
}
|
||||
if (!this.ignoreSave && filePath) {
|
||||
context.telemetry.suppressIfSuccessful = false;
|
||||
const editor: ICosmosEditor = this.fileMap[filePath];
|
||||
const showSaveWarning: boolean | undefined = vscode.workspace.getConfiguration().get(this.showSavePromptKey);
|
||||
if (showSaveWarning !== false) {
|
||||
const message: string = `Saving '${path.parse(doc.fileName).base}' will update the entity "${editor.label}" to the Cloud.`;
|
||||
const result: MessageItem | undefined = await vscode.window.showWarningMessage(message, DialogResponses.upload, DialogResponses.alwaysUpload, DialogResponses.cancel);
|
||||
|
||||
if (result === DialogResponses.alwaysUpload) {
|
||||
await vscode.workspace.getConfiguration().update(this.showSavePromptKey, false, vscode.ConfigurationTarget.Global);
|
||||
} else if (result !== DialogResponses.upload) {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateToCloud(editor, doc, context);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateToCloud(editor: ICosmosEditor, doc: vscode.TextDocument, context: IActionContext): Promise<void> {
|
||||
const newContent = editor.convertFromString(doc.getText());
|
||||
const updatedContent: {} = await editor.update(newContent, context);
|
||||
ext.outputChannel.appendLog(`Updated entity "${editor.label}"`);
|
||||
ext.outputChannel.show();
|
||||
if (doc.isClosed !== true) {
|
||||
const firstRelatedEditor = vscode.window.visibleTextEditors.find((ed) => ed.document === doc);
|
||||
if (firstRelatedEditor) {
|
||||
await this.updateEditor(updatedContent, firstRelatedEditor, editor);
|
||||
//all visible editors for that doc will be updated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async updateEditor(data: {}, textEditor: vscode.TextEditor, editor: ICosmosEditor): Promise<void> {
|
||||
const updatedText = editor.convertToString(data);
|
||||
await vscodeUtils.writeToEditor(textEditor, updatedText);
|
||||
this.ignoreSave = true;
|
||||
try {
|
||||
await textEditor.document.save();
|
||||
} finally {
|
||||
this.ignoreSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPersistedEditor(documentUri: vscode.Uri, context: IActionContext): Promise<string> {
|
||||
const persistedEditors = this._globalState.get(this._persistedEditorsKey);
|
||||
//Based on the documentUri, split just the appropriate key's value on '/'
|
||||
if (persistedEditors) {
|
||||
const editorFilePath = Object.keys(persistedEditors).find((label) => path.relative(documentUri.fsPath, label) === '');
|
||||
if (editorFilePath) {
|
||||
const editorNode: AzureTreeItem | undefined = await ext.tree.findTreeItem(persistedEditors[editorFilePath], context);
|
||||
let editor: ICosmosEditor;
|
||||
if (editorNode) {
|
||||
if (editorNode instanceof MongoCollectionTreeItem) {
|
||||
editor = new MongoCollectionNodeEditor(editorNode);
|
||||
} else if (editorNode instanceof DocDBDocumentTreeItem) {
|
||||
editor = new DocDBDocumentNodeEditor(editorNode);
|
||||
} else if (editorNode instanceof MongoDocumentTreeItem) {
|
||||
editor = new MongoDocumentNodeEditor(editorNode);
|
||||
} else if (editorNode instanceof DocDBStoredProcedureTreeItem) {
|
||||
editor = new DocDBStoredProcedureNodeEditor(editorNode);
|
||||
} else {
|
||||
throw new Error("Unexpected type of Editor treeItem");
|
||||
}
|
||||
this.fileMap[editorFilePath] = editor;
|
||||
} else {
|
||||
throw new Error("Failed to find entity on the tree. Please check the explorer to confirm that the entity exists, and that permissions are intact.");
|
||||
}
|
||||
}
|
||||
return editorFilePath;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export abstract class ParsedConnectionString {
|
||||
public abstract readonly hostName: string;
|
||||
public abstract readonly port: string;
|
||||
|
||||
/**
|
||||
* databaseName may be undefined if this is an account-level connection string
|
||||
*/
|
||||
public readonly databaseName: string | undefined;
|
||||
public readonly connectionString: string;
|
||||
|
||||
constructor(connectionString: string, databaseName: string | undefined) {
|
||||
this.connectionString = connectionString;
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
public get accountId(): string {
|
||||
return `${this.hostName}:${this.port}`;
|
||||
}
|
||||
|
||||
public get fullId(): string {
|
||||
return `${this.accountId}${this.databaseName ? '/' + this.databaseName : ''}`;
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocDBAccountTreeItemBase } from '../../docdb/tree/DocDBAccountTreeItemBase';
|
||||
import { API } from '../../experiences';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { ParsedMongoConnectionString } from '../../mongo/mongoConnectionStrings';
|
||||
import { MongoAccountTreeItem } from '../../mongo/tree/MongoAccountTreeItem';
|
||||
import { ParsedConnectionString } from '../../ParsedConnectionString';
|
||||
import { DatabaseAccountTreeItem } from '../../vscode-cosmosdb.api';
|
||||
|
||||
export class DatabaseAccountTreeItemInternal implements DatabaseAccountTreeItem {
|
||||
protected _parsedCS: ParsedConnectionString;
|
||||
private _accountNode: MongoAccountTreeItem | DocDBAccountTreeItemBase | undefined;
|
||||
|
||||
constructor(parsedCS: ParsedConnectionString, accountNode?: MongoAccountTreeItem | DocDBAccountTreeItemBase) {
|
||||
this._parsedCS = parsedCS;
|
||||
this._accountNode = accountNode;
|
||||
}
|
||||
|
||||
public get connectionString(): string {
|
||||
return this._parsedCS.connectionString;
|
||||
}
|
||||
|
||||
public get hostName(): string {
|
||||
return this._parsedCS.hostName;
|
||||
}
|
||||
|
||||
public get port(): string {
|
||||
return this._parsedCS.port;
|
||||
}
|
||||
|
||||
public get azureData(): { accountName: string; } | undefined {
|
||||
if (this._accountNode && this._accountNode.databaseAccount) {
|
||||
return {
|
||||
accountName: this._accountNode.databaseAccount.name
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public get docDBData(): { masterKey: string; documentEndpoint: string; } | undefined {
|
||||
if (this._accountNode instanceof DocDBAccountTreeItemBase) {
|
||||
return {
|
||||
documentEndpoint: this._accountNode.root.documentEndpoint,
|
||||
masterKey: this._accountNode.root.masterKey
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async reveal(): Promise<void> {
|
||||
ext.treeView.reveal(await this.getAccountNode());
|
||||
}
|
||||
|
||||
protected async getAccountNode(): Promise<MongoAccountTreeItem | DocDBAccountTreeItemBase> {
|
||||
// If this._accountNode is undefined, attach a new node based on connection string
|
||||
if (!this._accountNode) {
|
||||
const apiType = this._parsedCS instanceof ParsedMongoConnectionString ? API.MongoDB : API.Core;
|
||||
this._accountNode = await ext.attachedAccountsNode.attachConnectionString(this.connectionString, apiType);
|
||||
}
|
||||
|
||||
return this._accountNode;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureTreeItem, callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
|
||||
import { DocDBAccountTreeItemBase } from '../../docdb/tree/DocDBAccountTreeItemBase';
|
||||
import { DocDBDatabaseTreeItemBase } from '../../docdb/tree/DocDBDatabaseTreeItemBase';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { MongoAccountTreeItem } from '../../mongo/tree/MongoAccountTreeItem';
|
||||
import { MongoDatabaseTreeItem } from '../../mongo/tree/MongoDatabaseTreeItem';
|
||||
import { ParsedConnectionString } from '../../ParsedConnectionString';
|
||||
import { DatabaseTreeItem } from '../../vscode-cosmosdb.api';
|
||||
import { DatabaseAccountTreeItemInternal } from './DatabaseAccountTreeItemInternal';
|
||||
|
||||
export class DatabaseTreeItemInternal extends DatabaseAccountTreeItemInternal implements DatabaseTreeItem {
|
||||
private _dbNode: AzureTreeItem | undefined;
|
||||
|
||||
constructor(parsedCS: ParsedConnectionString & { databaseName: string }, accountNode?: MongoAccountTreeItem | DocDBAccountTreeItemBase, dbNode?: MongoDatabaseTreeItem | DocDBDatabaseTreeItemBase) {
|
||||
super(parsedCS, accountNode);
|
||||
this._dbNode = dbNode;
|
||||
}
|
||||
|
||||
public get databaseName(): string {
|
||||
return this._parsedCS.databaseName;
|
||||
}
|
||||
|
||||
public async reveal(): Promise<void> {
|
||||
await callWithTelemetryAndErrorHandling('api.db.reveal', async (context: IActionContext) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.errorHandling.rethrow = true;
|
||||
|
||||
const accountNode: MongoAccountTreeItem | DocDBAccountTreeItemBase = await this.getAccountNode();
|
||||
if (!this._dbNode) {
|
||||
const databaseId = `${accountNode.fullId}/${this.databaseName}`;
|
||||
this._dbNode = await ext.tree.findTreeItem(databaseId, context);
|
||||
}
|
||||
|
||||
ext.treeView.reveal(this._dbNode || accountNode);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ParsedMongoConnectionString } from '../../mongo/mongoConnectionStrings';
|
||||
import { ParsedConnectionString } from '../../ParsedConnectionString';
|
||||
import { DatabaseAccountTreeItem, DatabaseTreeItem } from '../../vscode-cosmosdb.api';
|
||||
|
||||
/**
|
||||
* This cache is used to speed up api calls from other extensions to the Cosmos DB extension
|
||||
* For now, it only helps on a per-session basis
|
||||
*/
|
||||
const sessionCache: Map<string, DatabaseAccountTreeItem | DatabaseTreeItem> = new Map();
|
||||
|
||||
export function cacheTreeItem(parsedCS: ParsedConnectionString, treeItem: DatabaseAccountTreeItem | DatabaseTreeItem): void {
|
||||
sessionCache.set(parsedCS.fullId, treeItem);
|
||||
}
|
||||
|
||||
export function tryGetTreeItemFromCache(parsedCS: ParsedConnectionString): DatabaseAccountTreeItem | DatabaseTreeItem | undefined {
|
||||
return sessionCache.get(parsedCS.fullId);
|
||||
}
|
||||
|
||||
export function removeTreeItemFromCache(expected: ParsedConnectionString): void {
|
||||
if (!expected.databaseName) {
|
||||
// If parsedCS represents an account, remove the account and any databases that match that account
|
||||
for (const [key, value] of sessionCache.entries()) {
|
||||
const actual = new ParsedMongoConnectionString(value.connectionString, value.hostName, value.port, undefined);
|
||||
if (actual.accountId === expected.accountId) {
|
||||
sessionCache.delete(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sessionCache.delete(expected.fullId);
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtTreeItem, callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
|
||||
import { parseDocDBConnectionString } from '../../docdb/docDBConnectionStrings';
|
||||
import { DocDBAccountTreeItemBase } from '../../docdb/tree/DocDBAccountTreeItemBase';
|
||||
import { DocDBDatabaseTreeItemBase } from '../../docdb/tree/DocDBDatabaseTreeItemBase';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { parseMongoConnectionString } from '../../mongo/mongoConnectionStrings';
|
||||
import { MongoAccountTreeItem } from '../../mongo/tree/MongoAccountTreeItem';
|
||||
import { MongoDatabaseTreeItem } from '../../mongo/tree/MongoDatabaseTreeItem';
|
||||
import { ParsedConnectionString } from '../../ParsedConnectionString';
|
||||
import { SubscriptionTreeItem } from '../../tree/SubscriptionTreeItem';
|
||||
import { DatabaseAccountTreeItem, DatabaseTreeItem, TreeItemQuery } from '../../vscode-cosmosdb.api';
|
||||
import { cacheTreeItem, tryGetTreeItemFromCache } from './apiCache';
|
||||
import { DatabaseAccountTreeItemInternal } from './DatabaseAccountTreeItemInternal';
|
||||
import { DatabaseTreeItemInternal } from './DatabaseTreeItemInternal';
|
||||
|
||||
export async function findTreeItem(query: TreeItemQuery): Promise<DatabaseAccountTreeItem | DatabaseTreeItem | undefined> {
|
||||
return await callWithTelemetryAndErrorHandling('api.findTreeItem', async (context: IActionContext) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.errorHandling.rethrow = true;
|
||||
|
||||
const connectionString = query.connectionString;
|
||||
let parsedCS: ParsedConnectionString;
|
||||
if (/^mongodb[^:]*:\/\//i.test(connectionString)) {
|
||||
parsedCS = await parseMongoConnectionString(connectionString);
|
||||
} else {
|
||||
parsedCS = parseDocDBConnectionString(connectionString);
|
||||
}
|
||||
|
||||
const maxTime = Date.now() + 10 * 1000; // Give up searching subscriptions after 10 seconds and just attach the account
|
||||
|
||||
// 1. Get result from cache if possible
|
||||
let result: DatabaseAccountTreeItem | DatabaseTreeItem | undefined = tryGetTreeItemFromCache(parsedCS);
|
||||
|
||||
// 2. Search attached accounts (do this before subscriptions because it's faster)
|
||||
if (!result) {
|
||||
const attachedDbAccounts = await ext.attachedAccountsNode.getCachedChildren(context);
|
||||
result = await searchDbAccounts(attachedDbAccounts, parsedCS, context, maxTime);
|
||||
}
|
||||
|
||||
// 3. Search subscriptions
|
||||
if (!result) {
|
||||
const rootNodes = await ext.tree.getChildren();
|
||||
for (const rootNode of rootNodes) {
|
||||
if (Date.now() > maxTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rootNode instanceof SubscriptionTreeItem) {
|
||||
const dbAccounts = await rootNode.getCachedChildren(context);
|
||||
result = await searchDbAccounts(dbAccounts, parsedCS, context, maxTime);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. If all else fails, just attach a new node
|
||||
if (!result) {
|
||||
result = new DatabaseTreeItemInternal(parsedCS);
|
||||
}
|
||||
|
||||
cacheTreeItem(parsedCS, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
async function searchDbAccounts(dbAccounts: AzExtTreeItem[], expected: ParsedConnectionString, context: IActionContext, maxTime: number): Promise<DatabaseAccountTreeItem | DatabaseTreeItem | undefined> {
|
||||
for (const dbAccount of dbAccounts) {
|
||||
if (Date.now() > maxTime) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let actual: ParsedConnectionString;
|
||||
if (dbAccount instanceof MongoAccountTreeItem) {
|
||||
actual = await parseMongoConnectionString(dbAccount.connectionString);
|
||||
} else if (dbAccount instanceof DocDBAccountTreeItemBase) {
|
||||
actual = parseDocDBConnectionString(dbAccount.connectionString);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (expected.accountId === actual.accountId) {
|
||||
if (expected.databaseName) {
|
||||
const dbs = await dbAccount.getCachedChildren(context);
|
||||
for (const db of dbs) {
|
||||
if ((db instanceof MongoDatabaseTreeItem || db instanceof DocDBDatabaseTreeItemBase) && expected.databaseName === db.databaseName) {
|
||||
return new DatabaseTreeItemInternal(expected, dbAccount, db);
|
||||
}
|
||||
}
|
||||
|
||||
// We found the right account - just not the db. In this case we can still 'reveal' the account
|
||||
return new DatabaseTreeItemInternal(expected, dbAccount);
|
||||
}
|
||||
|
||||
return new DatabaseAccountTreeItemInternal(expected, dbAccount);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
|
||||
import { parseDocDBConnectionString } from '../../docdb/docDBConnectionStrings';
|
||||
import { DocDBAccountTreeItem } from '../../docdb/tree/DocDBAccountTreeItem';
|
||||
import { DocDBAccountTreeItemBase } from '../../docdb/tree/DocDBAccountTreeItemBase';
|
||||
import { DocDBDatabaseTreeItem } from '../../docdb/tree/DocDBDatabaseTreeItem';
|
||||
import { DocDBDatabaseTreeItemBase } from '../../docdb/tree/DocDBDatabaseTreeItemBase';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { GraphAccountTreeItem } from '../../graph/tree/GraphAccountTreeItem';
|
||||
import { GraphDatabaseTreeItem } from '../../graph/tree/GraphDatabaseTreeItem';
|
||||
import { parseMongoConnectionString } from '../../mongo/mongoConnectionStrings';
|
||||
import { MongoAccountTreeItem } from '../../mongo/tree/MongoAccountTreeItem';
|
||||
import { MongoDatabaseTreeItem } from '../../mongo/tree/MongoDatabaseTreeItem';
|
||||
import { ParsedConnectionString } from '../../ParsedConnectionString';
|
||||
import { TableAccountTreeItem } from '../../table/tree/TableAccountTreeItem';
|
||||
import { AttachedAccountSuffix } from '../../tree/AttachedAccountsTreeItem';
|
||||
import { CosmosDBApiType, DatabaseAccountTreeItem, DatabaseTreeItem, PickTreeItemOptions } from '../../vscode-cosmosdb.api';
|
||||
import { cacheTreeItem } from './apiCache';
|
||||
import { DatabaseAccountTreeItemInternal } from './DatabaseAccountTreeItemInternal';
|
||||
import { DatabaseTreeItemInternal } from './DatabaseTreeItemInternal';
|
||||
|
||||
const databaseContextValues = [MongoDatabaseTreeItem.contextValue, DocDBDatabaseTreeItem.contextValue, GraphDatabaseTreeItem.contextValue];
|
||||
const accountContextValues = [GraphAccountTreeItem.contextValue, DocDBAccountTreeItem.contextValue, TableAccountTreeItem.contextValue, MongoAccountTreeItem.contextValue];
|
||||
|
||||
function getDatabaseContextValue(apiType: CosmosDBApiType): string {
|
||||
switch (apiType) {
|
||||
case 'Mongo':
|
||||
return MongoDatabaseTreeItem.contextValue;
|
||||
case 'SQL':
|
||||
return DocDBDatabaseTreeItem.contextValue;
|
||||
case 'Graph':
|
||||
return GraphDatabaseTreeItem.contextValue;
|
||||
default:
|
||||
throw new RangeError(`Unsupported api type "${apiType}".`);
|
||||
}
|
||||
}
|
||||
|
||||
function getAccountContextValue(apiType: CosmosDBApiType): string {
|
||||
switch (apiType) {
|
||||
case 'Mongo':
|
||||
return MongoAccountTreeItem.contextValue;
|
||||
case 'SQL':
|
||||
return DocDBAccountTreeItem.contextValue;
|
||||
case 'Graph':
|
||||
return GraphAccountTreeItem.contextValue;
|
||||
case 'Table':
|
||||
return TableAccountTreeItem.contextValue;
|
||||
default:
|
||||
throw new RangeError(`Unsupported api type "${apiType}".`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function pickTreeItem(options: PickTreeItemOptions): Promise<DatabaseTreeItem | DatabaseAccountTreeItem | undefined> {
|
||||
return await callWithTelemetryAndErrorHandling('api.pickTreeItem', async (context: IActionContext) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.errorHandling.rethrow = true;
|
||||
|
||||
let contextValuesToFind;
|
||||
switch (options.resourceType) {
|
||||
case 'Database':
|
||||
contextValuesToFind = options.apiType ? options.apiType.map(getDatabaseContextValue) : databaseContextValues;
|
||||
break;
|
||||
case 'DatabaseAccount':
|
||||
contextValuesToFind = options.apiType ? options.apiType.map(getAccountContextValue) : accountContextValues;
|
||||
contextValuesToFind = contextValuesToFind.concat(contextValuesToFind.map((val: string) => val + AttachedAccountSuffix));
|
||||
break;
|
||||
default:
|
||||
throw new RangeError(`Unsupported resource type "${options.resourceType}".`);
|
||||
}
|
||||
|
||||
const pickedItem = await ext.tree.showTreeItemPicker(contextValuesToFind, context);
|
||||
|
||||
let parsedCS: ParsedConnectionString;
|
||||
let accountNode: MongoAccountTreeItem | DocDBAccountTreeItemBase;
|
||||
let databaseNode: MongoDatabaseTreeItem | DocDBDatabaseTreeItemBase | undefined;
|
||||
if (pickedItem instanceof MongoAccountTreeItem) {
|
||||
parsedCS = await parseMongoConnectionString(pickedItem.connectionString);
|
||||
accountNode = pickedItem;
|
||||
} else if (pickedItem instanceof DocDBAccountTreeItemBase) {
|
||||
parsedCS = parseDocDBConnectionString(pickedItem.connectionString);
|
||||
accountNode = pickedItem;
|
||||
} else if (pickedItem instanceof MongoDatabaseTreeItem) {
|
||||
parsedCS = await parseMongoConnectionString(pickedItem.connectionString);
|
||||
accountNode = pickedItem.parent;
|
||||
databaseNode = pickedItem;
|
||||
} else if (pickedItem instanceof DocDBDatabaseTreeItemBase) {
|
||||
parsedCS = parseDocDBConnectionString(pickedItem.connectionString);
|
||||
accountNode = pickedItem.parent;
|
||||
databaseNode = pickedItem;
|
||||
}
|
||||
|
||||
const result = databaseNode ?
|
||||
new DatabaseTreeItemInternal(parsedCS, accountNode, databaseNode) :
|
||||
new DatabaseAccountTreeItemInternal(parsedCS, accountNode);
|
||||
cacheTreeItem(parsedCS, result);
|
||||
return result;
|
||||
});
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtTreeItem, callWithTelemetryAndErrorHandling, IActionContext } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
|
||||
export async function revealTreeItem(resourceId: string): Promise<void> {
|
||||
return await callWithTelemetryAndErrorHandling('api.revealTreeItem', async (context: IActionContext) => {
|
||||
const node: AzExtTreeItem | undefined = await ext.tree.findTreeItem(resourceId, { ...context, loadAll: true });
|
||||
if (node) {
|
||||
await ext.treeView.reveal(node, { select: true, focus: true, expand: true });
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CosmosDBManagementClient } from 'azure-arm-cosmosdb';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureTreeItem, createAzureClient, DialogResponses } from 'vscode-azureextensionui';
|
||||
import { UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { azureUtils } from '../utils/azureUtils';
|
||||
|
||||
export async function deleteCosmosDBAccount(node: AzureTreeItem): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete account '${node.label}' and its contents?`;
|
||||
const result = await ext.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const client: CosmosDBManagementClient = createAzureClient(node.root, CosmosDBManagementClient);
|
||||
const resourceGroup: string = azureUtils.getResourceGroupFromId(node.fullId);
|
||||
const accountName: string = azureUtils.getAccountNameFromId(node.fullId);
|
||||
const deletingMessage: string = `Deleting account "${accountName}"...`;
|
||||
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: deletingMessage }, async () => {
|
||||
await client.databaseAccounts.deleteMethod(resourceGroup, accountName);
|
||||
});
|
||||
// don't wait
|
||||
vscode.window.showInformationMessage(`Successfully deleted account "${accountName}".`);
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NewDocument } from 'documentdb';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as vscode from 'vscode';
|
||||
import { IActionContext, parseError } from 'vscode-azureextensionui';
|
||||
import { DocDBCollectionTreeItem } from '../docdb/tree/DocDBCollectionTreeItem';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { MongoCollectionTreeItem } from '../mongo/tree/MongoCollectionTreeItem';
|
||||
import { getRootPath } from '../utils/workspacUtils';
|
||||
|
||||
export async function importDocuments(actionContext: IActionContext, uris: vscode.Uri[] | undefined, collectionNode: MongoCollectionTreeItem | DocDBCollectionTreeItem | undefined): Promise<void> {
|
||||
if (!uris) {
|
||||
uris = await askForDocuments();
|
||||
}
|
||||
const ignoredUris: vscode.Uri[] = []; //account for https://github.com/Microsoft/vscode/issues/59782
|
||||
uris = uris.filter((uri) => {
|
||||
if (uri.fsPath.toLocaleLowerCase().endsWith('.json')) {
|
||||
return true;
|
||||
} else {
|
||||
ignoredUris.push(uri);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (ignoredUris.length) {
|
||||
ext.outputChannel.appendLog(`Ignoring the following files which are not json:`);
|
||||
ignoredUris.forEach(uri => ext.outputChannel.appendLine(`${uri.fsPath}`));
|
||||
ext.outputChannel.show();
|
||||
}
|
||||
if (!collectionNode) {
|
||||
collectionNode = <MongoCollectionTreeItem | DocDBCollectionTreeItem>await ext.tree.showTreeItemPicker([MongoCollectionTreeItem.contextValue, DocDBCollectionTreeItem.contextValue], actionContext);
|
||||
}
|
||||
let result: string;
|
||||
result = await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Importing documents..."
|
||||
},
|
||||
async (progress) => {
|
||||
progress.report({ increment: 20, message: "Parsing documents for errors" });
|
||||
const documents = await parseDocuments(uris);
|
||||
progress.report({ increment: 30, message: "Parsed documents. Importing" });
|
||||
if (collectionNode instanceof MongoCollectionTreeItem) {
|
||||
result = await insertDocumentsIntoMongo(collectionNode, documents);
|
||||
} else {
|
||||
result = await insertDocumentsIntoDocdb(collectionNode, documents, uris);
|
||||
}
|
||||
progress.report({ increment: 50, message: "Finished importing" });
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
await collectionNode.refresh();
|
||||
await vscode.window.showInformationMessage(result);
|
||||
}
|
||||
|
||||
async function askForDocuments(): Promise<vscode.Uri[]> {
|
||||
const openDialogOptions: vscode.OpenDialogOptions = {
|
||||
canSelectMany: true,
|
||||
openLabel: "Import",
|
||||
filters: {
|
||||
JSON: ["json"]
|
||||
}
|
||||
};
|
||||
const rootPath: string | undefined = getRootPath();
|
||||
if (rootPath) {
|
||||
openDialogOptions.defaultUri = vscode.Uri.file(rootPath);
|
||||
}
|
||||
return await ext.ui.showOpenDialog(openDialogOptions);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
async function parseDocuments(uris: vscode.Uri[]): Promise<any[]> {
|
||||
let documents = [];
|
||||
let errorFoundFlag: boolean = false;
|
||||
for (const uri of uris) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = await fse.readJSON(uri.fsPath);
|
||||
} catch (e) {
|
||||
if (!errorFoundFlag) {
|
||||
errorFoundFlag = true;
|
||||
ext.outputChannel.appendLog("Errors found in documents listed below. Please fix these.");
|
||||
ext.outputChannel.show();
|
||||
}
|
||||
const err = parseError(e);
|
||||
ext.outputChannel.appendLine(`${uri.path}:\n${err.message}`);
|
||||
}
|
||||
if (parsed) {
|
||||
if (Array.isArray(parsed)) {
|
||||
documents = documents.concat(parsed);
|
||||
} else {
|
||||
documents.push(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errorFoundFlag) {
|
||||
throw new Error(`Errors found in some documents. Please see the output, fix these and try again.`);
|
||||
}
|
||||
|
||||
return documents;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
async function insertDocumentsIntoDocdb(collectionNode: DocDBCollectionTreeItem, documents: any[], uris: vscode.Uri[]): Promise<string> {
|
||||
let result;
|
||||
const ids = [];
|
||||
let i = 0;
|
||||
const erroneousFiles: vscode.Uri[] = [];
|
||||
for (i = 0; i < documents.length; i++) {
|
||||
const document: NewDocument = documents[i];
|
||||
if (!collectionNode.documentsTreeItem.documentHasPartitionKey(document)) {
|
||||
erroneousFiles.push(uris[i]);
|
||||
}
|
||||
}
|
||||
if (erroneousFiles.length) {
|
||||
ext.outputChannel.appendLog(`The following documents do not contain the required partition key:`);
|
||||
erroneousFiles.forEach(file => ext.outputChannel.appendLine(file.path));
|
||||
ext.outputChannel.show();
|
||||
throw new Error(`See output for list of documents that do not contain the partition key '${collectionNode.partitionKey.paths[0]}' required by collection '${collectionNode.label}'`);
|
||||
}
|
||||
for (const document of documents) {
|
||||
const retrieved = await collectionNode.documentsTreeItem.createDocument(document);
|
||||
ids.push(retrieved.id);
|
||||
}
|
||||
result = `Import into SQL successful. Inserted ${ids.length} document(s). See output for more details.`;
|
||||
for (const id of ids) {
|
||||
ext.outputChannel.appendLine(`Inserted document: ${id}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
async function insertDocumentsIntoMongo(node: MongoCollectionTreeItem, documents: any[]): Promise<string> {
|
||||
let output = "";
|
||||
const parsed = await node.collection.insertMany(documents);
|
||||
if (parsed.result && parsed.result.ok) {
|
||||
output = `Import into mongo successful. Inserted ${parsed.insertedCount} document(s). See output for more details.`;
|
||||
for (const inserted of Object.values(parsed.insertedIds)) {
|
||||
ext.outputChannel.appendLine(`Inserted document: ${inserted}`);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as url from 'url';
|
||||
import { ParsedConnectionString } from '../ParsedConnectionString';
|
||||
|
||||
export function parseDocDBConnectionString(connectionString: string): ParsedDocDBConnectionString {
|
||||
const endpoint = getPropertyFromConnectionString(connectionString, 'AccountEndpoint');
|
||||
const masterKey = getPropertyFromConnectionString(connectionString, 'AccountKey');
|
||||
const databaseName = getPropertyFromConnectionString(connectionString, 'Database');
|
||||
if (!endpoint || !masterKey) {
|
||||
throw new Error('Invalid Document DB connection string.');
|
||||
}
|
||||
return new ParsedDocDBConnectionString(connectionString, endpoint, masterKey, databaseName);
|
||||
}
|
||||
|
||||
function getPropertyFromConnectionString(connectionString: string, property: string): string | undefined {
|
||||
const regexp = new RegExp(`(?:^|;)\\s*${property}=([^;]+)(?:;|$)`, 'i');
|
||||
const match = connectionString.match(regexp);
|
||||
return match && match[1];
|
||||
}
|
||||
|
||||
export class ParsedDocDBConnectionString extends ParsedConnectionString {
|
||||
public readonly hostName: string;
|
||||
public readonly port: string;
|
||||
|
||||
public readonly documentEndpoint: string;
|
||||
public readonly masterKey: string;
|
||||
|
||||
constructor(connectionString: string, endpoint: string, masterKey: string, databaseName: string | undefined) {
|
||||
super(connectionString, databaseName);
|
||||
this.documentEndpoint = endpoint;
|
||||
this.masterKey = masterKey;
|
||||
|
||||
const parsedEndpoint = url.parse(endpoint);
|
||||
this.hostName = parsedEndpoint.hostname;
|
||||
this.port = parsedEndpoint.port;
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RetrievedDocument } from "documentdb";
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { getNodeEditorLabel } from '../../utils/vscodeUtils';
|
||||
import { DocDBDocumentTreeItem } from "../tree/DocDBDocumentTreeItem";
|
||||
|
||||
export class DocDBDocumentNodeEditor implements ICosmosEditor<RetrievedDocument> {
|
||||
private _documentNode: DocDBDocumentTreeItem;
|
||||
constructor(documentNode: DocDBDocumentTreeItem) {
|
||||
this._documentNode = documentNode;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return getNodeEditorLabel(this._documentNode);
|
||||
}
|
||||
|
||||
public async getData(): Promise<RetrievedDocument> {
|
||||
return this._documentNode.document;
|
||||
}
|
||||
|
||||
public async update(document: RetrievedDocument): Promise<RetrievedDocument> {
|
||||
const updatedDoc = await this._documentNode.update(document);
|
||||
await this._documentNode.refresh();
|
||||
return updatedDoc;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._documentNode.fullId;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): RetrievedDocument {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
public convertToString(data: RetrievedDocument): string {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { getNodeEditorLabel } from '../../utils/vscodeUtils';
|
||||
import { DocDBStoredProcedureTreeItem } from "../tree/DocDBStoredProcedureTreeItem";
|
||||
|
||||
export class DocDBStoredProcedureNodeEditor implements ICosmosEditor<string> {
|
||||
private _spNode: DocDBStoredProcedureTreeItem;
|
||||
constructor(spNode: DocDBStoredProcedureTreeItem) {
|
||||
this._spNode = spNode;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return getNodeEditorLabel(this._spNode);
|
||||
}
|
||||
|
||||
public async getData(): Promise<string> {
|
||||
return this._spNode.procedure.body;
|
||||
}
|
||||
|
||||
public async update(document: string): Promise<string> {
|
||||
return await this._spNode.update(document);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._spNode.fullId;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): string {
|
||||
return data;
|
||||
}
|
||||
|
||||
public convertToString(data: string): string {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient } from "documentdb";
|
||||
import * as DocDBLib from 'documentdb/lib';
|
||||
import * as vscode from "vscode";
|
||||
import { appendExtensionUserAgent } from "vscode-azureextensionui";
|
||||
import { ext } from "../extensionVariables";
|
||||
|
||||
export function getDocumentClient(documentEndpoint: string, masterKey: string, isEmulator: boolean): DocumentClient {
|
||||
const documentBase = DocDBLib.DocumentBase;
|
||||
const connectionPolicy = new documentBase.ConnectionPolicy();
|
||||
|
||||
const vscodeStrictSSL: boolean | undefined = vscode.workspace.getConfiguration().get<boolean>(ext.settingsKeys.vsCode.proxyStrictSSL);
|
||||
const strictSSL = !isEmulator && vscodeStrictSSL;
|
||||
connectionPolicy.DisableSSLVerification = !strictSSL;
|
||||
const client = new DocumentClient(documentEndpoint, { masterKey: masterKey }, connectionPolicy);
|
||||
|
||||
// User agent isn't formally exposed on the client (https://github.com/Azure/azure-documentdb-node/issues/244) but nevertheless can be accessed via defaultHeaders
|
||||
// tslint:disable-next-line:no-any
|
||||
const defaultHeaders = (<{ defaultHeaders: { "User-Agent"?: string } }><any>client).defaultHeaders;
|
||||
if (defaultHeaders) {
|
||||
const userAgent = appendExtensionUserAgent(defaultHeaders['User-Agent']);
|
||||
defaultHeaders['User-Agent'] = userAgent;
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { commands } from "vscode";
|
||||
import { IActionContext, registerCommand } from "vscode-azureextensionui";
|
||||
import { doubleClickDebounceDelay } from "../constants";
|
||||
import { CosmosEditorManager } from "../CosmosEditorManager";
|
||||
import { ext } from "../extensionVariables";
|
||||
import { DocDBStoredProcedureNodeEditor } from "./editors/DocDBStoredProcedureNodeEditor";
|
||||
import { DocDBAccountTreeItem } from "./tree/DocDBAccountTreeItem";
|
||||
import { DocDBCollectionTreeItem } from "./tree/DocDBCollectionTreeItem";
|
||||
import { DocDBDatabaseTreeItem } from "./tree/DocDBDatabaseTreeItem";
|
||||
import { DocDBDocumentsTreeItem } from "./tree/DocDBDocumentsTreeItem";
|
||||
import { DocDBDocumentTreeItem } from "./tree/DocDBDocumentTreeItem";
|
||||
import { DocDBStoredProceduresTreeItem } from "./tree/DocDBStoredProceduresTreeItem";
|
||||
import { DocDBStoredProcedureTreeItem } from "./tree/DocDBStoredProcedureTreeItem";
|
||||
|
||||
export function registerDocDBCommands(editorManager: CosmosEditorManager): void {
|
||||
registerCommand('cosmosDB.createDocDBDatabase', async (context: IActionContext, node?: DocDBAccountTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBAccountTreeItem>await ext.tree.showTreeItemPicker(DocDBAccountTreeItem.contextValue, context);
|
||||
}
|
||||
const databaseNode: DocDBDatabaseTreeItem = <DocDBDatabaseTreeItem>await node.createChild(context);
|
||||
await ext.treeView.reveal(databaseNode, { focus: false });
|
||||
const collectionNode: DocDBCollectionTreeItem = <DocDBCollectionTreeItem>await databaseNode.createChild(context);
|
||||
await ext.treeView.reveal(collectionNode);
|
||||
});
|
||||
registerCommand('cosmosDB.createDocDBCollection', async (context: IActionContext, node?: DocDBDatabaseTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBDatabaseTreeItem>await ext.tree.showTreeItemPicker(DocDBDatabaseTreeItem.contextValue, context);
|
||||
}
|
||||
const collectionNode: DocDBCollectionTreeItem = <DocDBCollectionTreeItem>await node.createChild(context);
|
||||
await ext.treeView.reveal(collectionNode);
|
||||
});
|
||||
registerCommand('cosmosDB.createDocDBDocument', async (context: IActionContext, node?: DocDBDocumentsTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBDocumentsTreeItem>await ext.tree.showTreeItemPicker(DocDBDocumentsTreeItem.contextValue, context);
|
||||
}
|
||||
const documentNode = <DocDBDocumentTreeItem>await node.createChild(context);
|
||||
await ext.treeView.reveal(documentNode);
|
||||
await commands.executeCommand("cosmosDB.openDocument", documentNode);
|
||||
|
||||
});
|
||||
registerCommand('cosmosDB.createDocDBStoredProcedure', async (context: IActionContext, node?: DocDBStoredProceduresTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBStoredProceduresTreeItem>await ext.tree.showTreeItemPicker(DocDBStoredProceduresTreeItem.contextValue, context);
|
||||
}
|
||||
const childNode = await node.createChild(context);
|
||||
await commands.executeCommand("cosmosDB.openStoredProcedure", childNode);
|
||||
|
||||
});
|
||||
registerCommand('cosmosDB.deleteDocDBDatabase', async (context: IActionContext, node?: DocDBDatabaseTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBDatabaseTreeItem>await ext.tree.showTreeItemPicker(DocDBDatabaseTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
registerCommand('cosmosDB.deleteDocDBCollection', async (context: IActionContext, node?: DocDBCollectionTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBCollectionTreeItem>await ext.tree.showTreeItemPicker(DocDBCollectionTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
registerCommand('cosmosDB.openStoredProcedure', async (context: IActionContext, node?: DocDBStoredProcedureTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBStoredProcedureTreeItem>await ext.tree.showTreeItemPicker([DocDBStoredProcedureTreeItem.contextValue], context);
|
||||
}
|
||||
await editorManager.showDocument(context, new DocDBStoredProcedureNodeEditor(node), node.label + '-cosmos-stored-procedure.js');
|
||||
// tslint:disable-next-line:align
|
||||
}, doubleClickDebounceDelay);
|
||||
registerCommand('cosmosDB.deleteDocDBDocument', async (context: IActionContext, node?: DocDBDocumentTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBDocumentTreeItem>await ext.tree.showTreeItemPicker(DocDBDocumentTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
registerCommand('cosmosDB.deleteDocDBStoredProcedure', async (context: IActionContext, node?: DocDBStoredProcedureTreeItem) => {
|
||||
if (!node) {
|
||||
node = <DocDBStoredProcedureTreeItem>await ext.tree.showTreeItemPicker(DocDBStoredProcedureTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DatabaseMeta } from 'documentdb';
|
||||
import { DocDBAccountTreeItemBase } from './DocDBAccountTreeItemBase';
|
||||
import { DocDBCollectionTreeItem } from './DocDBCollectionTreeItem';
|
||||
import { DocDBDatabaseTreeItem } from './DocDBDatabaseTreeItem';
|
||||
import { DocDBDocumentsTreeItem } from './DocDBDocumentsTreeItem';
|
||||
import { DocDBDocumentTreeItem } from './DocDBDocumentTreeItem';
|
||||
import { DocDBStoredProceduresTreeItem } from './DocDBStoredProceduresTreeItem';
|
||||
import { DocDBStoredProcedureTreeItem } from './DocDBStoredProcedureTreeItem';
|
||||
|
||||
export class DocDBAccountTreeItem extends DocDBAccountTreeItemBase {
|
||||
public static contextValue: string = "cosmosDBDocumentServer";
|
||||
public contextValue: string = DocDBAccountTreeItem.contextValue;
|
||||
|
||||
public initChild(database: DatabaseMeta): DocDBDatabaseTreeItem {
|
||||
return new DocDBDatabaseTreeItem(this, database);
|
||||
}
|
||||
|
||||
public isAncestorOfImpl(contextValue: string): boolean {
|
||||
switch (contextValue) {
|
||||
case DocDBDatabaseTreeItem.contextValue:
|
||||
case DocDBCollectionTreeItem.contextValue:
|
||||
case DocDBDocumentTreeItem.contextValue:
|
||||
case DocDBStoredProcedureTreeItem.contextValue:
|
||||
case DocDBDocumentsTreeItem.contextValue:
|
||||
case DocDBStoredProceduresTreeItem.contextValue:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DatabaseAccount } from 'azure-arm-cosmosdb/lib/models';
|
||||
import { DatabaseMeta, DocumentClient, FeedOptions, QueryIterator } from 'documentdb';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzExtTreeItem, AzureParentTreeItem, AzureTreeItem, ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { deleteCosmosDBAccount } from '../../commands/deleteCosmosDBAccount';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { rejectOnTimeout } from '../../utils/timeout';
|
||||
import { getDocumentClient } from '../getDocumentClient';
|
||||
import { DocDBTreeItemBase } from './DocDBTreeItemBase';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
/**
|
||||
* This class provides common logic for DocumentDB, Graph, and Table accounts
|
||||
* (DocumentDB is the base type for all Cosmos DB accounts)
|
||||
*/
|
||||
export abstract class DocDBAccountTreeItemBase extends DocDBTreeItemBase<DatabaseMeta> {
|
||||
public readonly id: string;
|
||||
public readonly label: string;
|
||||
public readonly childTypeLabel: string = "Database";
|
||||
|
||||
private _root: IDocDBTreeRoot;
|
||||
|
||||
constructor(parent: AzureParentTreeItem, id: string, label: string, documentEndpoint: string, masterKey: string, isEmulator: boolean, readonly databaseAccount?: DatabaseAccount) {
|
||||
super(parent);
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this._root = Object.assign({}, parent.root, {
|
||||
documentEndpoint,
|
||||
masterKey,
|
||||
isEmulator,
|
||||
getDocumentClient: () => getDocumentClient(documentEndpoint, masterKey, isEmulator)
|
||||
});
|
||||
}
|
||||
|
||||
// overrides ISubscriptionContext with an object that also has DocDB info
|
||||
public get root(): IDocDBTreeRoot {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
public get connectionString(): string {
|
||||
return `AccountEndpoint=${this.root.documentEndpoint};AccountKey=${this.root.masterKey}`;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('CosmosDBAccount.svg');
|
||||
}
|
||||
|
||||
public async getIterator(client: DocumentClient, feedOptions: FeedOptions): Promise<QueryIterator<DatabaseMeta>> {
|
||||
return client.readDatabases(feedOptions);
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<AzureTreeItem<IDocDBTreeRoot>> {
|
||||
const databaseName = await vscode.window.showInputBox({
|
||||
placeHolder: 'Database Name',
|
||||
validateInput: validateDatabaseName,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (databaseName) {
|
||||
context.showCreatingTreeItem(databaseName);
|
||||
const client = this.root.getDocumentClient();
|
||||
const database: DatabaseMeta = await new Promise<DatabaseMeta>((resolve, reject) => {
|
||||
client.createDatabase({ id: databaseName }, (err, db: DatabaseMeta) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(db);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.initChild(database);
|
||||
}
|
||||
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
if (this._root.isEmulator) {
|
||||
const unableToReachEmulatorMessage: string = "Unable to reach emulator. Please ensure it is started and connected to the port specified by the 'cosmosDB.emulator.port' setting, then try again.";
|
||||
return await rejectOnTimeout(2000, () => super.loadMoreChildrenImpl(clearCache), unableToReachEmulatorMessage);
|
||||
} else {
|
||||
return await super.loadMoreChildrenImpl(clearCache);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await deleteCosmosDBAccount(this);
|
||||
}
|
||||
}
|
||||
|
||||
function validateDatabaseName(name: string): string | undefined | null {
|
||||
if (!name || name.length < 1 || name.length > 255) {
|
||||
return "Name has to be between 1 and 255 chars long";
|
||||
}
|
||||
if (name.endsWith(" ")) {
|
||||
return "Database name cannot end with space";
|
||||
}
|
||||
if (/[/\\?#]/.test(name)) {
|
||||
return `Database name cannot contain the characters '\\', '/', '#', '?'`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CollectionMeta, CollectionPartitionKey } from 'documentdb';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureParentTreeItem, AzureTreeItem, DialogResponses, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { DocDBDatabaseTreeItem } from './DocDBDatabaseTreeItem';
|
||||
import { DocDBDocumentsTreeItem } from './DocDBDocumentsTreeItem';
|
||||
import { DocDBDocumentTreeItem } from './DocDBDocumentTreeItem';
|
||||
import { DocDBStoredProceduresTreeItem } from './DocDBStoredProceduresTreeItem';
|
||||
import { DocDBStoredProcedureTreeItem } from './DocDBStoredProcedureTreeItem';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
/**
|
||||
* Represents a DocumentDB collection
|
||||
*/
|
||||
export class DocDBCollectionTreeItem extends AzureParentTreeItem<IDocDBTreeRoot> {
|
||||
public static contextValue: string = "cosmosDBDocumentCollection";
|
||||
public readonly contextValue: string = DocDBCollectionTreeItem.contextValue;
|
||||
public readonly documentsTreeItem: DocDBDocumentsTreeItem;
|
||||
|
||||
private readonly _storedProceduresTreeItem: DocDBStoredProceduresTreeItem;
|
||||
|
||||
constructor(parent: DocDBDatabaseTreeItem, private _collection: CollectionMeta) {
|
||||
super(parent);
|
||||
this.documentsTreeItem = new DocDBDocumentsTreeItem(this);
|
||||
this._storedProceduresTreeItem = new DocDBStoredProceduresTreeItem(this);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._collection.id;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._collection.id;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Collection.svg');
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this._collection._self;
|
||||
}
|
||||
|
||||
public get partitionKey(): CollectionPartitionKey | undefined {
|
||||
return this._collection.partitionKey;
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete collection '${this.label}' and its contents?`;
|
||||
const result = await vscode.window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const client = this.root.getDocumentClient();
|
||||
await new Promise((resolve, reject) => {
|
||||
client.deleteCollection(this.link, err => err ? reject(err) : resolve());
|
||||
});
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzureTreeItem<IDocDBTreeRoot>[]> {
|
||||
return [this.documentsTreeItem, this._storedProceduresTreeItem];
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): AzureTreeItem<IDocDBTreeRoot> | undefined {
|
||||
for (const expectedContextValue of expectedContextValues) {
|
||||
switch (expectedContextValue) {
|
||||
case DocDBDocumentsTreeItem.contextValue:
|
||||
case DocDBDocumentTreeItem.contextValue:
|
||||
return this.documentsTreeItem;
|
||||
case DocDBStoredProceduresTreeItem.contextValue:
|
||||
case DocDBStoredProcedureTreeItem.contextValue:
|
||||
return this._storedProceduresTreeItem;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CollectionMeta } from 'documentdb';
|
||||
import { DocDBCollectionTreeItem } from './DocDBCollectionTreeItem';
|
||||
import { DocDBDatabaseTreeItemBase } from './DocDBDatabaseTreeItemBase';
|
||||
|
||||
export class DocDBDatabaseTreeItem extends DocDBDatabaseTreeItemBase {
|
||||
public static contextValue: string = "cosmosDBDocumentDatabase";
|
||||
public readonly contextValue: string = DocDBDatabaseTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = 'Collection';
|
||||
|
||||
public initChild(collection: CollectionMeta): DocDBCollectionTreeItem {
|
||||
return new DocDBCollectionTreeItem(this, collection);
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Collection, CollectionMeta, DatabaseMeta, DocumentClient, FeedOptions, QueryIterator } from 'documentdb';
|
||||
import { DocumentBase } from 'documentdb/lib';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureTreeItem, DialogResponses, ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { DocDBAccountTreeItemBase } from './DocDBAccountTreeItemBase';
|
||||
import { DocDBTreeItemBase } from './DocDBTreeItemBase';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
const minThroughputFixed = 400;
|
||||
const minThroughputPartitioned = 1000;
|
||||
const maxThroughput: number = 100000;
|
||||
|
||||
/**
|
||||
* This class provides common logic for DocumentDB, Graph, and Table databases
|
||||
* (DocumentDB is the base type for all Cosmos DB accounts)
|
||||
*/
|
||||
export abstract class DocDBDatabaseTreeItemBase extends DocDBTreeItemBase<CollectionMeta> {
|
||||
public readonly parent: DocDBAccountTreeItemBase;
|
||||
private readonly _database: DatabaseMeta;
|
||||
|
||||
constructor(parent: DocDBAccountTreeItemBase, database: DatabaseMeta) {
|
||||
super(parent);
|
||||
this._database = database;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Database.svg');
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._database.id;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._database.id;
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this._database._self;
|
||||
}
|
||||
|
||||
public get connectionString(): string {
|
||||
return this.parent.connectionString.concat(`;Database=${this._database.id}`);
|
||||
}
|
||||
|
||||
public get databaseName(): string {
|
||||
return this._database.id;
|
||||
}
|
||||
|
||||
public async getIterator(client: DocumentClient, feedOptions: FeedOptions): Promise<QueryIterator<CollectionMeta>> {
|
||||
return client.readCollections(this.link, feedOptions);
|
||||
}
|
||||
|
||||
// Delete the database
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete database '${this.label}' and its contents?`;
|
||||
const result = await vscode.window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const client = this.root.getDocumentClient();
|
||||
await new Promise((resolve, reject) => {
|
||||
client.deleteDatabase(this.link, err => err ? reject(err) : resolve());
|
||||
});
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a DB collection
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<AzureTreeItem<IDocDBTreeRoot>> {
|
||||
const collectionName = await ext.ui.showInputBox({
|
||||
placeHolder: `Enter an id for your ${this.childTypeLabel}`,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: validateCollectionName
|
||||
});
|
||||
|
||||
const collectionDef: Collection = {
|
||||
id: collectionName
|
||||
};
|
||||
|
||||
let partitionKey: string | undefined = await ext.ui.showInputBox({
|
||||
prompt: 'Enter the partition key for the collection, or leave blank for fixed size.',
|
||||
ignoreFocusOut: true,
|
||||
validateInput: validatePartitionKey
|
||||
});
|
||||
|
||||
if (partitionKey && partitionKey.length && partitionKey[0] !== '/') {
|
||||
partitionKey = '/' + partitionKey;
|
||||
}
|
||||
if (!!partitionKey) {
|
||||
collectionDef.partitionKey = {
|
||||
paths: [partitionKey],
|
||||
kind: DocumentBase.PartitionKind.Hash
|
||||
};
|
||||
}
|
||||
const isFixed: boolean = !(collectionDef.partitionKey);
|
||||
const minThroughput = isFixed ? minThroughputFixed : minThroughputPartitioned;
|
||||
const throughput: number = Number(await ext.ui.showInputBox({
|
||||
value: minThroughput.toString(),
|
||||
ignoreFocusOut: true,
|
||||
prompt: `Initial throughput capacity, between ${minThroughput} and ${maxThroughput}`,
|
||||
validateInput: (input: string) => validateThroughput(isFixed, input)
|
||||
}));
|
||||
|
||||
const options = { offerThroughput: throughput };
|
||||
|
||||
context.showCreatingTreeItem(collectionName);
|
||||
const client = this.root.getDocumentClient();
|
||||
const collection: CollectionMeta = await new Promise<CollectionMeta>((resolve, reject) => {
|
||||
client.createCollection(this.link, collectionDef, options, (err, result) => {
|
||||
err ? reject(err) : resolve(result);
|
||||
});
|
||||
});
|
||||
|
||||
return this.initChild(collection);
|
||||
}
|
||||
}
|
||||
|
||||
function validatePartitionKey(key: string): string | undefined | null {
|
||||
if (/[#?\\]/.test(key)) {
|
||||
return "Cannot contain these characters: ?,#,\\, etc.";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateThroughput(isFixed: boolean, input: string): string | undefined | null {
|
||||
try {
|
||||
const minThroughput = isFixed ? minThroughputFixed : minThroughputPartitioned;
|
||||
const value = Number(input);
|
||||
if (value < minThroughput || value > maxThroughput) {
|
||||
return `Value must be between ${minThroughput} and ${maxThroughput}`;
|
||||
}
|
||||
} catch (err) {
|
||||
return "Input must be a number";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateCollectionName(name: string): string | undefined | null {
|
||||
if (!name) {
|
||||
return "Collection name cannot be empty";
|
||||
}
|
||||
if (name.endsWith(" ")) {
|
||||
return "Collection name cannot end with space";
|
||||
}
|
||||
if (/[/\\?#]/.test(name)) {
|
||||
return `Collection name cannot contain the characters '\\', '/', '#', '?'`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient, RetrievedDocument } from 'documentdb';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureTreeItem, DialogResponses, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { emptyPartitionKeyValue, getThemeAgnosticIconPath } from '../../constants';
|
||||
import { getDocumentTreeItemLabel } from '../../utils/vscodeUtils';
|
||||
import { DocDBDocumentsTreeItem } from './DocDBDocumentsTreeItem';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
/**
|
||||
* Represents a Cosmos DB DocumentDB (SQL) document
|
||||
*/
|
||||
export class DocDBDocumentTreeItem extends AzureTreeItem<IDocDBTreeRoot> {
|
||||
public static contextValue: string = "cosmosDBDocument";
|
||||
public readonly contextValue: string = DocDBDocumentTreeItem.contextValue;
|
||||
public readonly commandId: string = 'cosmosDB.openDocument';
|
||||
public readonly parent: DocDBDocumentsTreeItem;
|
||||
|
||||
private readonly _partitionKeyValue: string | undefined | Object;
|
||||
private _label: string;
|
||||
private _document: RetrievedDocument;
|
||||
|
||||
constructor(parent: DocDBDocumentsTreeItem, document: RetrievedDocument) {
|
||||
super(parent);
|
||||
this._document = document;
|
||||
this._partitionKeyValue = this.getPartitionKeyValue();
|
||||
this._label = getDocumentTreeItemLabel(this._document);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.document._rid || `${this.document.id}:${this.getPartitionKeyValue()}`;
|
||||
// Every document has an _rid field, even though the type definitions call it optional. The second clause is fallback.
|
||||
// The toString implicit conversion handles undefined and {} as expected. toString satisfies the uniqueness criterion.
|
||||
}
|
||||
|
||||
public async refreshImpl(): Promise<void> {
|
||||
this._label = getDocumentTreeItemLabel(this._document);
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this.document._self;
|
||||
}
|
||||
|
||||
get document(): RetrievedDocument {
|
||||
return this._document;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Document.svg');
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete document '${this.label}'?`;
|
||||
const result = await vscode.window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const client = this.root.getDocumentClient();
|
||||
const options = { partitionKey: this._partitionKeyValue };
|
||||
await new Promise((resolve, reject) => {
|
||||
// Disabling type check in the next line. This helps ensure documents having no partition key value
|
||||
// can still pass an empty object when required. It looks like a disparity between the type settings outlined here
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/01e0ffdbab16b15c702d5b8c87bb122cc6215a59/types/documentdb/index.d.ts#L72
|
||||
// vs. the workaround outlined at https://github.com/Azure/azure-documentdb-node/issues/222#issuecomment-364286027
|
||||
// tslint:disable-next-line:no-any
|
||||
client.deleteDocument(this.link, <any>options, err => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
public async update(newData: RetrievedDocument): Promise<RetrievedDocument> {
|
||||
const client: DocumentClient = this.root.getDocumentClient();
|
||||
const _self: string = this.document._self;
|
||||
if (["_self", "_etag"].some((element) => !newData[element])) {
|
||||
throw new Error(`The "_self" and "_etag" fields are required to update a document`);
|
||||
} else {
|
||||
const options = { accessCondition: { type: 'IfMatch', condition: newData._etag }, partitionKey: this._partitionKeyValue };
|
||||
this._document = await new Promise<RetrievedDocument>((resolve, reject) => {
|
||||
client.replaceDocument(
|
||||
_self,
|
||||
newData,
|
||||
//tslint:disable-next-line:no-any
|
||||
<any>options,
|
||||
(err, updated: RetrievedDocument) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(updated);
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.document;
|
||||
}
|
||||
}
|
||||
|
||||
private getPartitionKeyValue(): string | undefined | Object {
|
||||
const partitionKey = this.parent.parent.partitionKey;
|
||||
if (!partitionKey) { //Fixed collections -> no partitionKeyValue
|
||||
return undefined;
|
||||
}
|
||||
const fields = partitionKey.paths[0].split('/');
|
||||
if (fields[0] === '') {
|
||||
fields.shift();
|
||||
}
|
||||
let value;
|
||||
for (const field of fields) {
|
||||
value = value ? value[field] : this.document[field];
|
||||
if (!value) { //Partition Key exists, but this document doesn't have a value
|
||||
return emptyPartitionKeyValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient, FeedOptions, NewDocument, QueryIterator, RetrievedDocument } from 'documentdb';
|
||||
import * as vscode from 'vscode';
|
||||
import { ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { DocDBCollectionTreeItem } from './DocDBCollectionTreeItem';
|
||||
import { DocDBDocumentTreeItem } from './DocDBDocumentTreeItem';
|
||||
import { DocDBTreeItemBase } from './DocDBTreeItemBase';
|
||||
|
||||
/**
|
||||
* This class provides logic for DocumentDB collections
|
||||
*/
|
||||
export class DocDBDocumentsTreeItem extends DocDBTreeItemBase<RetrievedDocument> {
|
||||
public static contextValue: string = "cosmosDBDocumentsGroup";
|
||||
public readonly contextValue: string = DocDBDocumentsTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = "Documents";
|
||||
public readonly parent: DocDBCollectionTreeItem;
|
||||
|
||||
constructor(parent: DocDBCollectionTreeItem) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Collection.svg');
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return "$Documents";
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return "Documents";
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this.parent.link;
|
||||
}
|
||||
|
||||
public async getIterator(client: DocumentClient, feedOptions: FeedOptions): Promise<QueryIterator<RetrievedDocument>> {
|
||||
return client.readDocuments(this.link, feedOptions);
|
||||
}
|
||||
|
||||
public initChild(document: RetrievedDocument): DocDBDocumentTreeItem {
|
||||
return new DocDBDocumentTreeItem(this, document);
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<DocDBDocumentTreeItem> {
|
||||
let docID = await vscode.window.showInputBox({
|
||||
prompt: "Enter a document ID or leave blank for a generated ID",
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (docID || docID === "") {
|
||||
docID = docID.trim();
|
||||
let body = { id: docID };
|
||||
body = <NewDocument>(await this.promptForPartitionKey(body));
|
||||
context.showCreatingTreeItem(docID);
|
||||
const document = await this.createDocument(body);
|
||||
|
||||
return this.initChild(document);
|
||||
}
|
||||
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
|
||||
public async createDocument(body: NewDocument): Promise<RetrievedDocument> {
|
||||
return await new Promise<RetrievedDocument>((resolve, reject) => {
|
||||
this.root.getDocumentClient().createDocument(this.link, body, (err, result: RetrievedDocument) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public documentHasPartitionKey(doc: Object): boolean {
|
||||
let interim = doc;
|
||||
let partitionKey: string | undefined = this.parent.partitionKey && this.parent.partitionKey.paths[0];
|
||||
if (!partitionKey) {
|
||||
return true;
|
||||
}
|
||||
if (partitionKey[0] === '/') {
|
||||
partitionKey = partitionKey.slice(1);
|
||||
}
|
||||
const keyPath = partitionKey.split('/');
|
||||
let i: number;
|
||||
for (i = 0; i < keyPath.length - 1; i++) {
|
||||
if (interim.hasOwnProperty(keyPath[i])) {
|
||||
interim = interim[keyPath[i]];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async promptForPartitionKey(body: Object): Promise<Object> {
|
||||
const partitionKey: string | undefined = this.parent.partitionKey && this.parent.partitionKey.paths[0];
|
||||
if (partitionKey) {
|
||||
const partitionKeyValue: string = await vscode.window.showInputBox({
|
||||
prompt: `Enter a value for the partition key ("${partitionKey}")`,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (partitionKeyValue) {
|
||||
// Unlike delete/replace, createDocument does not accept a partition key value via an options parameter.
|
||||
// We need to present the partitionKey value as part of the document contents
|
||||
Object.assign(body, this.createPartitionPathObject(partitionKey, partitionKeyValue));
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
// Create a nested Object given the partition key path and value
|
||||
private createPartitionPathObject(partitionKey: string, partitionKeyValue: string): Object {
|
||||
//remove leading slash
|
||||
if (partitionKey[0] === '/') {
|
||||
partitionKey = partitionKey.slice(1);
|
||||
}
|
||||
const keyPath = partitionKey.split('/');
|
||||
const PartitionPath: Object = {};
|
||||
let interim: Object = PartitionPath;
|
||||
let i: number;
|
||||
for (i = 0; i < keyPath.length - 1; i++) {
|
||||
interim[keyPath[i]] = {};
|
||||
interim = interim[keyPath[i]];
|
||||
}
|
||||
interim[keyPath[i]] = partitionKeyValue;
|
||||
return PartitionPath;
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProcedureMeta } from 'documentdb';
|
||||
import * as vscode from "vscode";
|
||||
import { AzureTreeItem, DialogResponses, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemedIconPath } from '../../constants';
|
||||
import { DocDBStoredProceduresTreeItem } from './DocDBStoredProceduresTreeItem';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
/**
|
||||
* Represents a Cosmos DB DocumentDB (SQL) stored procedure
|
||||
*/
|
||||
export class DocDBStoredProcedureTreeItem extends AzureTreeItem<IDocDBTreeRoot> {
|
||||
public static contextValue: string = "cosmosDBStoredProcedure";
|
||||
public readonly contextValue: string = DocDBStoredProcedureTreeItem.contextValue;
|
||||
public readonly commandId: string = 'cosmosDB.openStoredProcedure';
|
||||
|
||||
constructor(parent: DocDBStoredProceduresTreeItem, public procedure: ProcedureMeta) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.procedure.id;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.procedure.id;
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this.procedure._self;
|
||||
}
|
||||
|
||||
public async update(newProcBody: string): Promise<string> {
|
||||
const client = this.root.getDocumentClient();
|
||||
this.procedure = await new Promise<ProcedureMeta>((resolve, reject) => client.replaceStoredProcedure(
|
||||
this.link,
|
||||
{ body: newProcBody, id: this.procedure.id },
|
||||
(err, updated: ProcedureMeta) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(updated);
|
||||
}
|
||||
})
|
||||
);
|
||||
return newProcBody;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemedIconPath('Process_16x.svg');
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete stored procedure '${this.label}'?`;
|
||||
const result = await vscode.window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const client = this.root.getDocumentClient();
|
||||
await new Promise((resolve, reject) => {
|
||||
client.deleteStoredProcedure(this.link, err => err ? reject(err) : resolve());
|
||||
});
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient, FeedOptions, ProcedureMeta, QueryIterator } from 'documentdb';
|
||||
import * as vscode from "vscode";
|
||||
import { ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { defaultStoredProcedure, getThemeAgnosticIconPath } from '../../constants';
|
||||
import { GraphCollectionTreeItem } from '../../graph/tree/GraphCollectionTreeItem';
|
||||
import { DocDBCollectionTreeItem } from './DocDBCollectionTreeItem';
|
||||
import { DocDBStoredProcedureTreeItem } from './DocDBStoredProcedureTreeItem';
|
||||
import { DocDBTreeItemBase } from './DocDBTreeItemBase';
|
||||
|
||||
/**
|
||||
* This class represents the DocumentDB "Stored Procedures" node in the tree
|
||||
*/
|
||||
export class DocDBStoredProceduresTreeItem extends DocDBTreeItemBase<ProcedureMeta> {
|
||||
public static contextValue: string = "cosmosDBStoredProceduresGroup";
|
||||
public readonly contextValue: string = DocDBStoredProceduresTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = "Stored Procedure";
|
||||
public readonly parent: DocDBCollectionTreeItem | GraphCollectionTreeItem;
|
||||
|
||||
constructor(parent: DocDBCollectionTreeItem | GraphCollectionTreeItem) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public initChild(resource: ProcedureMeta): DocDBStoredProcedureTreeItem {
|
||||
return new DocDBStoredProcedureTreeItem(this, resource);
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('stored procedures.svg');
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<DocDBStoredProcedureTreeItem> {
|
||||
const client = this.root.getDocumentClient();
|
||||
let spID = await vscode.window.showInputBox({
|
||||
prompt: "Enter a unique stored procedure ID",
|
||||
validateInput: this.validateName,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (spID || spID === "") {
|
||||
spID = spID.trim();
|
||||
context.showCreatingTreeItem(spID);
|
||||
const sproc: ProcedureMeta = await new Promise<ProcedureMeta>((resolve, reject) => {
|
||||
client.createStoredProcedure(this.link, { id: spID, body: defaultStoredProcedure }, (err, result: ProcedureMeta) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this.initChild(sproc);
|
||||
}
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return "$StoredProcedures";
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return "Stored Procedures";
|
||||
}
|
||||
|
||||
public get link(): string {
|
||||
return this.parent.link;
|
||||
}
|
||||
|
||||
public async getIterator(client: DocumentClient, feedOptions: FeedOptions): Promise<QueryIterator<ProcedureMeta>> {
|
||||
return client.readStoredProcedures(this.link, feedOptions);
|
||||
}
|
||||
|
||||
private validateName(name: string): string | null | undefined {
|
||||
if (name) {
|
||||
if (name.indexOf("/") !== -1 || name.indexOf("\\") !== -1 || name.indexOf("?") !== -1 || name.indexOf("#") !== -1) {
|
||||
return "Id contains illegal chars: /,\\,?,#";
|
||||
}
|
||||
if (name[name.length - 1] === " ") {
|
||||
return "Id ends with a space.";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient, FeedOptions, QueryError, QueryIterator } from 'documentdb';
|
||||
import { AzExtTreeItem, AzureParentTreeItem, AzureTreeItem } from 'vscode-azureextensionui';
|
||||
import { defaultBatchSize } from '../../constants';
|
||||
import { IDocDBTreeRoot } from './IDocDBTreeRoot';
|
||||
|
||||
/**
|
||||
* This class provides common iteration logic for DocumentDB accounts, databases, and collections
|
||||
*/
|
||||
export abstract class DocDBTreeItemBase<T> extends AzureParentTreeItem<IDocDBTreeRoot> {
|
||||
public abstract readonly id: string;
|
||||
public abstract readonly label: string;
|
||||
public abstract readonly contextValue: string;
|
||||
public abstract readonly childTypeLabel: string;
|
||||
|
||||
private _hasMoreChildren: boolean = true;
|
||||
private _iterator: QueryIterator<T> | undefined;
|
||||
private _batchSize: number = defaultBatchSize;
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return this._hasMoreChildren;
|
||||
}
|
||||
|
||||
public abstract initChild(resource: T): AzureTreeItem<IDocDBTreeRoot>;
|
||||
|
||||
public abstract getIterator(client: DocumentClient, feedOptions: FeedOptions): Promise<QueryIterator<T>>;
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
if (clearCache || this._iterator === undefined) {
|
||||
this._hasMoreChildren = true;
|
||||
const client = this.root.getDocumentClient();
|
||||
this._iterator = await this.getIterator(client, { maxItemCount: defaultBatchSize });
|
||||
this._batchSize = defaultBatchSize;
|
||||
}
|
||||
|
||||
const resources: T[] = [];
|
||||
let count: number = 0;
|
||||
while (count < this._batchSize) {
|
||||
const resource: T | undefined = await new Promise<T | undefined>((resolve, reject) => {
|
||||
this._iterator.nextItem((error: QueryError, rsrc: T | undefined) => {
|
||||
error ? reject(error) : resolve(rsrc);
|
||||
});
|
||||
});
|
||||
if (resource === undefined) {
|
||||
this._hasMoreChildren = false;
|
||||
break;
|
||||
} else {
|
||||
resources.push(resource);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
this._batchSize *= 2;
|
||||
|
||||
return resources.map((resource: T) => this.initChild(resource));
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentClient } from "documentdb";
|
||||
import { ISubscriptionContext } from "vscode-azureextensionui";
|
||||
|
||||
export interface IDocDBTreeRoot extends ISubscriptionContext {
|
||||
documentEndpoint: string;
|
||||
masterKey: string;
|
||||
isEmulator: boolean;
|
||||
getDocumentClient(): DocumentClient;
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/* ------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DatabaseAccount } from 'azure-arm-cosmosdb/lib/models';
|
||||
import { IAzureQuickPickItem } from 'vscode-azureextensionui';
|
||||
|
||||
export enum API {
|
||||
MongoDB = 'MongoDB',
|
||||
Graph = 'Graph',
|
||||
Table = 'Table',
|
||||
Core = 'Core'
|
||||
}
|
||||
|
||||
export enum DBAccountKind {
|
||||
MongoDB = 'MongoDB',
|
||||
GlobalDocumentDB = 'GlobalDocumentDB'
|
||||
}
|
||||
|
||||
export type CapabilityName = 'EnableGremlin' | 'EnableTable';
|
||||
|
||||
export function getExperienceFromApi(api: API): Experience {
|
||||
let info = experiencesMap.get(api);
|
||||
if (!info) {
|
||||
info = { api: api, shortName: api, longName: api, kind: DBAccountKind.GlobalDocumentDB, tag: api };
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
export function getExperienceLabel(account: DatabaseAccount): string {
|
||||
const experience: Experience | undefined = tryGetExperience(account);
|
||||
if (experience) {
|
||||
return experience.shortName;
|
||||
}
|
||||
|
||||
// Must be some new kind of account that we aren't aware of. Try to get a decent label
|
||||
const defaultExperience: string = <API>(account && account.tags && account.tags.defaultExperience);
|
||||
const firstCapability = account.capabilities && account.capabilities[0];
|
||||
const firstCapabilityName = firstCapability && firstCapability.name.replace(/^Enable/, '');
|
||||
return defaultExperience || firstCapabilityName || account.kind;
|
||||
}
|
||||
|
||||
export function tryGetExperience(account: DatabaseAccount): Experience | undefined {
|
||||
// defaultExperience in the account doesn't really mean anything, we can't depend on its value for determining account type
|
||||
if (account.kind === DBAccountKind.MongoDB) {
|
||||
return MongoExperience;
|
||||
} else if (account.capabilities.find(cap => cap.name === 'EnableGremlin')) {
|
||||
return GremlinExperience;
|
||||
} else if (account.capabilities.find(cap => cap.name === 'EnableTable')) {
|
||||
return TableExperience;
|
||||
} else if (account.capabilities.length === 0) {
|
||||
return CoreExperience;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export interface Experience {
|
||||
/**
|
||||
* Programmatic name used internally by us for historical reasons. Doesn't actually affect anything in Azure (maybe UI?)
|
||||
*/
|
||||
api: API;
|
||||
|
||||
longName: string;
|
||||
shortName: string;
|
||||
description?: string;
|
||||
|
||||
// These properties are what the portal actually looks at to determine the difference between APIs
|
||||
kind: DBAccountKind;
|
||||
capability?: CapabilityName;
|
||||
|
||||
// The defaultExperience tag to place into the resource (has no actual effect in Azure, just imitating the portal)
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export function getExperienceQuickPicks(): IAzureQuickPickItem<Experience>[] {
|
||||
return experiencesArray.map(exp => getExperienceQuickPick(exp.api));
|
||||
}
|
||||
|
||||
export function getExperienceQuickPick(api: API): IAzureQuickPickItem<Experience> {
|
||||
const exp = getExperienceFromApi(api);
|
||||
return { label: exp.longName, description: exp.description, data: exp };
|
||||
}
|
||||
|
||||
// Mongo is distinguished by having kind="MongoDB". All others have kind="GlobalDocumentDB"
|
||||
// Table and Gremlin are distinguished from SQL by their capabilities
|
||||
const CoreExperience: Experience = { api: API.Core, longName: "Core", description: "(SQL)", shortName: "SQL", kind: DBAccountKind.GlobalDocumentDB, tag: "Core (SQL)" };
|
||||
const MongoExperience: Experience = { api: API.MongoDB, longName: "Azure Cosmos DB for MongoDB API", shortName: "MongoDB", kind: DBAccountKind.MongoDB, tag: "Azure Cosmos DB for MongoDB API" };
|
||||
const TableExperience: Experience = { api: API.Table, longName: "Azure Table", shortName: "Table", kind: DBAccountKind.GlobalDocumentDB, capability: 'EnableTable', tag: "Azure Table" };
|
||||
const GremlinExperience: Experience = { api: API.Graph, longName: "Gremlin", description: "(graph)", shortName: "Gremlin", kind: DBAccountKind.GlobalDocumentDB, capability: 'EnableGremlin', tag: "Gremlin (graph)" };
|
||||
|
||||
const experiencesArray: Experience[] = [CoreExperience, MongoExperience, TableExperience, GremlinExperience];
|
||||
const experiencesMap = new Map<API, Experience>(experiencesArray.map((info: Experience): [API, Experience] => [info.api, info]));
|
|
@ -1,25 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RecognitionException } from 'antlr4ts';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface MongoCommand {
|
||||
range: vscode.Range;
|
||||
text: string;
|
||||
collection?: string;
|
||||
name?: string;
|
||||
// tslint:disable-next-line:no-banned-terms
|
||||
arguments?: string[];
|
||||
argumentObjects?: Object[];
|
||||
errors?: ErrorDescription[];
|
||||
chained?: boolean;
|
||||
}
|
||||
|
||||
export interface ErrorDescription {
|
||||
range: vscode.Range;
|
||||
message: string;
|
||||
exception?: RecognitionException;
|
||||
}
|
|
@ -1,472 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ANTLRInputStream as InputStream } from 'antlr4ts/ANTLRInputStream';
|
||||
import { CommonTokenStream } from 'antlr4ts/CommonTokenStream';
|
||||
import { ErrorNode } from 'antlr4ts/tree/ErrorNode';
|
||||
import { ParseTree } from 'antlr4ts/tree/ParseTree';
|
||||
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';
|
||||
import { ObjectID } from 'bson';
|
||||
import * as vscode from 'vscode';
|
||||
import { IActionContext, IParsedError, parseError } from 'vscode-azureextensionui';
|
||||
import { CosmosEditorManager } from '../CosmosEditorManager';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { filterType, findType } from '../utils/array';
|
||||
import * as vscodeUtil from './../utils/vscodeUtils';
|
||||
import { MongoFindOneResultEditor } from './editors/MongoFindOneResultEditor';
|
||||
import { MongoFindResultEditor } from './editors/MongoFindResultEditor';
|
||||
import { LexerErrorListener, ParserErrorListener } from './errorListeners';
|
||||
import { mongoLexer } from './grammar/mongoLexer';
|
||||
import * as mongoParser from './grammar/mongoParser';
|
||||
import { MongoVisitor } from './grammar/visitors';
|
||||
import { ErrorDescription, MongoCommand } from './MongoCommand';
|
||||
import { MongoDatabaseTreeItem, stripQuotes } from './tree/MongoDatabaseTreeItem';
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
const notInScrapbookMessage = "You must have a MongoDB scrapbook (*.mongo) open to run a MongoDB command.";
|
||||
|
||||
export function getAllErrorsFromTextDocument(document: vscode.TextDocument): vscode.Diagnostic[] {
|
||||
const commands = getAllCommandsFromTextDocument(document);
|
||||
const errors: vscode.Diagnostic[] = [];
|
||||
for (const command of commands) {
|
||||
for (const error of (command.errors || [])) {
|
||||
const diagnostic = new vscode.Diagnostic(error.range, error.message);
|
||||
errors.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export async function executeAllCommandsFromActiveEditor(database: MongoDatabaseTreeItem, editorManager: CosmosEditorManager, context: IActionContext): Promise<void> {
|
||||
ext.outputChannel.appendLog("Executing all commands in scrapbook...");
|
||||
const commands = getAllCommandsFromActiveEditor();
|
||||
await executeCommands(vscode.window.activeTextEditor, database, editorManager, context, commands);
|
||||
}
|
||||
|
||||
export async function executeCommandFromActiveEditor(database: MongoDatabaseTreeItem, editorManager: CosmosEditorManager, context: IActionContext): Promise<void> {
|
||||
const commands = getAllCommandsFromActiveEditor();
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
const selection = activeEditor.selection;
|
||||
const command = findCommandAtPosition(commands, selection.start);
|
||||
return await executeCommand(activeEditor, database, editorManager, context, command);
|
||||
}
|
||||
|
||||
export async function executeCommandFromText(database: MongoDatabaseTreeItem, editorManager: CosmosEditorManager, context: IActionContext, commandText: string): Promise<void> {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
const command = getCommandFromTextAtLocation(commandText, new vscode.Position(0, 0));
|
||||
return await executeCommand(activeEditor, database, editorManager, context, command);
|
||||
}
|
||||
|
||||
function getAllCommandsFromActiveEditor(): MongoCommand[] {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (activeEditor) {
|
||||
return getAllCommandsFromTextDocument(activeEditor.document);
|
||||
} else {
|
||||
// Shouldn't be able to reach this
|
||||
throw new Error(notInScrapbookMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllCommandsFromTextDocument(document: vscode.TextDocument): MongoCommand[] {
|
||||
return getAllCommandsFromText(document.getText());
|
||||
}
|
||||
|
||||
async function executeCommands(activeEditor: vscode.TextEditor, database: MongoDatabaseTreeItem, editorManager: CosmosEditorManager, context: IActionContext, commands: MongoCommand[]): Promise<void> {
|
||||
for (const command of commands) {
|
||||
try {
|
||||
await executeCommand(activeEditor, database, editorManager, context, command);
|
||||
} catch (e) {
|
||||
const err = parseError(e);
|
||||
if (err.isUserCancelledError) {
|
||||
throw e;
|
||||
} else {
|
||||
const message = `${command.text.split('(')[0]} at ${command.range.start.line + 1}:${command.range.start.character + 1}: ${err.message}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function executeCommand(activeEditor: vscode.TextEditor, database: MongoDatabaseTreeItem, editorManager: CosmosEditorManager, context: IActionContext, command: MongoCommand): Promise<void> {
|
||||
if (command) {
|
||||
try {
|
||||
context.telemetry.properties.command = command.name;
|
||||
context.telemetry.properties.argsCount = String(command.arguments ? command.arguments.length : 0);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (!database) {
|
||||
throw new Error('Please select a MongoDB database to run against by selecting it in the explorer and selecting the "Connect" context menu item');
|
||||
}
|
||||
if (command.errors && command.errors.length > 0) {
|
||||
//Currently, we take the first error pushed. Tests correlate that the parser visits errors in left-to-right, top-to-bottom.
|
||||
const err = command.errors[0];
|
||||
throw new Error(`Error near line ${err.range.start.line}, column ${err.range.start.character}: '${err.message}'. Please check syntax.`);
|
||||
}
|
||||
|
||||
// we don't handle chained commands so we can only handle "find" if isn't chained
|
||||
if (command.name === 'find' && !command.chained) {
|
||||
await editorManager.showDocument(context, new MongoFindResultEditor(database, command), 'cosmos-result.json', { showInNextColumn: true });
|
||||
} else {
|
||||
const result = await database.executeCommand(command, context);
|
||||
if (command.name === 'findOne') {
|
||||
if (result === "null") {
|
||||
throw new Error(`Could not find any documents`);
|
||||
}
|
||||
await editorManager.showDocument(context, new MongoFindOneResultEditor(database, command.collection, result), 'cosmos-result.json', { showInNextColumn: true });
|
||||
} else {
|
||||
await vscodeUtil.showNewFile(result, 'result', '.json', activeEditor.viewColumn + 1);
|
||||
await refreshTreeAfterCommand(database, command, context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('No MongoDB command found at the current cursor location.');
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshTreeAfterCommand(database: MongoDatabaseTreeItem, command: MongoCommand, context: IActionContext): Promise<void> {
|
||||
if (command.name === 'drop') {
|
||||
await database.refresh();
|
||||
} else if (command.collection && /^(insert|update|delete|replace|remove|write|bulkWrite)/i.test(command.name)) {
|
||||
const collectionNode = await ext.tree.findTreeItem(database.fullId + "/" + command.collection, context);
|
||||
if (collectionNode) {
|
||||
await collectionNode.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCommandFromTextAtLocation(content: string, position?: vscode.Position): MongoCommand {
|
||||
const commands = getAllCommandsFromText(content);
|
||||
return findCommandAtPosition(commands, position);
|
||||
}
|
||||
|
||||
export function getAllCommandsFromText(content: string): MongoCommand[] {
|
||||
const lexer = new mongoLexer(new InputStream(content));
|
||||
const lexerListener = new LexerErrorListener();
|
||||
lexer.removeErrorListeners(); // Default listener outputs to the console
|
||||
lexer.addErrorListener(lexerListener);
|
||||
const tokens: CommonTokenStream = new CommonTokenStream(lexer);
|
||||
|
||||
const parser = new mongoParser.mongoParser(tokens);
|
||||
const parserListener = new ParserErrorListener();
|
||||
parser.removeErrorListeners(); // Default listener outputs to the console
|
||||
parser.addErrorListener(parserListener);
|
||||
|
||||
const commandsContext: mongoParser.MongoCommandsContext = parser.mongoCommands();
|
||||
const commands = new FindMongoCommandsVisitor().visit(commandsContext);
|
||||
|
||||
// Match errors with commands based on location
|
||||
const errors = lexerListener.errors.concat(parserListener.errors);
|
||||
errors.sort((a, b) => {
|
||||
const linediff = a.range.start.line - b.range.start.line;
|
||||
const chardiff = a.range.start.character - b.range.start.character;
|
||||
return linediff || chardiff;
|
||||
});
|
||||
for (const err of errors) {
|
||||
const associatedCommand = findCommandAtPosition(commands, err.range.start);
|
||||
if (associatedCommand) {
|
||||
associatedCommand.errors = associatedCommand.errors || [];
|
||||
associatedCommand.errors.push(err);
|
||||
} else {
|
||||
// Create a new command to hook this up to
|
||||
const emptyCommand: MongoCommand = {
|
||||
collection: undefined,
|
||||
name: undefined,
|
||||
range: err.range,
|
||||
text: ""
|
||||
};
|
||||
emptyCommand.errors = [err];
|
||||
commands.push(emptyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
function findCommandAtPosition(commands: MongoCommand[], position?: vscode.Position): MongoCommand {
|
||||
let lastCommandOnSameLine = null;
|
||||
let lastCommandBeforePosition = null;
|
||||
if (position) {
|
||||
for (const command of commands) {
|
||||
if (command.range.contains(position)) {
|
||||
return command;
|
||||
}
|
||||
if (command.range.end.line === position.line) {
|
||||
lastCommandOnSameLine = command;
|
||||
}
|
||||
if (command.range.end.isBefore(position)) {
|
||||
lastCommandBeforePosition = command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastCommandOnSameLine || lastCommandBeforePosition || commands[commands.length - 1];
|
||||
}
|
||||
|
||||
class FindMongoCommandsVisitor extends MongoVisitor<MongoCommand[]> {
|
||||
private commands: MongoCommand[] = [];
|
||||
|
||||
public visitCommand(ctx: mongoParser.CommandContext): MongoCommand[] {
|
||||
const funcCallCount: number = filterType(ctx.children, mongoParser.FunctionCallContext).length;
|
||||
this.commands.push({
|
||||
range: new vscode.Range(ctx.start.line - 1, ctx.start.charPositionInLine, ctx.stop.line - 1, ctx.stop.charPositionInLine),
|
||||
text: ctx.text,
|
||||
name: '',
|
||||
arguments: [],
|
||||
argumentObjects: [],
|
||||
chained: funcCallCount > 1 ? true : false
|
||||
});
|
||||
return super.visitCommand(ctx);
|
||||
}
|
||||
|
||||
public visitCollection(ctx: mongoParser.CollectionContext): MongoCommand[] {
|
||||
this.commands[this.commands.length - 1].collection = ctx.text;
|
||||
return super.visitCollection(ctx);
|
||||
}
|
||||
|
||||
public visitFunctionCall(ctx: mongoParser.FunctionCallContext): MongoCommand[] {
|
||||
if (ctx.parent instanceof mongoParser.CommandContext) {
|
||||
this.commands[this.commands.length - 1].name = (ctx._FUNCTION_NAME && ctx._FUNCTION_NAME.text) || "";
|
||||
}
|
||||
return super.visitFunctionCall(ctx);
|
||||
}
|
||||
|
||||
public visitArgument(ctx: mongoParser.ArgumentContext): MongoCommand[] {
|
||||
try {
|
||||
const argumentsContext = ctx.parent;
|
||||
if (argumentsContext) {
|
||||
const functionCallContext = argumentsContext.parent;
|
||||
if (functionCallContext && functionCallContext.parent instanceof mongoParser.CommandContext) {
|
||||
const lastCommand = this.commands[this.commands.length - 1];
|
||||
const argAsObject = this.contextToObject(ctx);
|
||||
const argText = EJSON.stringify(argAsObject);
|
||||
lastCommand.arguments.push(argText);
|
||||
const escapeHandled = this.deduplicateEscapesForRegex(argText);
|
||||
let ejsonParsed = {};
|
||||
try {
|
||||
ejsonParsed = EJSON.parse(escapeHandled);
|
||||
} catch (err) { //EJSON parse failed due to a wrong flag, etc.
|
||||
this.addErrorToCommand(parseError(err), ctx);
|
||||
}
|
||||
lastCommand.argumentObjects.push(ejsonParsed);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.addErrorToCommand(parseError(error), ctx);
|
||||
}
|
||||
return super.visitArgument(ctx);
|
||||
}
|
||||
|
||||
protected defaultResult(_node: ParseTree): MongoCommand[] {
|
||||
return this.commands;
|
||||
}
|
||||
|
||||
private contextToObject(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext): Object {
|
||||
if (!ctx || ctx.childCount === 0) { //Base case and malformed statements
|
||||
return {};
|
||||
}
|
||||
// In a well formed expression, Argument and propertyValue tokens should have exactly one child, from their definitions in mongo.g4
|
||||
const child: ParseTree = ctx.children[0];
|
||||
if (child instanceof mongoParser.LiteralContext) {
|
||||
return this.literalContextToObject(child, ctx);
|
||||
} else if (child instanceof mongoParser.ObjectLiteralContext) {
|
||||
return this.objectLiteralContextToObject(child);
|
||||
} else if (child instanceof mongoParser.ArrayLiteralContext) {
|
||||
return this.arrayLiteralContextToObject(child);
|
||||
} else if (child instanceof mongoParser.FunctionCallContext) {
|
||||
return this.functionCallContextToObject(child, ctx);
|
||||
} else if (child instanceof ErrorNode) {
|
||||
return {};
|
||||
} else {
|
||||
const err: IParsedError = parseError(`Unrecognized node type encountered. We could not parse ${child.text}`);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private literalContextToObject(child: mongoParser.LiteralContext, ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext): Object {
|
||||
const text = child.text;
|
||||
const tokenType = child.start.type;
|
||||
const nonStringLiterals = [mongoParser.mongoParser.NullLiteral, mongoParser.mongoParser.BooleanLiteral, mongoParser.mongoParser.NumericLiteral];
|
||||
if (tokenType === mongoParser.mongoParser.StringLiteral) {
|
||||
return stripQuotes(text);
|
||||
} else if (tokenType === mongoParser.mongoParser.RegexLiteral) {
|
||||
return this.regexLiteralContextToObject(ctx, text);
|
||||
} else if (nonStringLiterals.indexOf(tokenType) > -1) {
|
||||
return JSON.parse(text);
|
||||
} else {
|
||||
const err: IParsedError = parseError(`Unrecognized token. Token text: ${text}`);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private objectLiteralContextToObject(child: mongoParser.ObjectLiteralContext): Object {
|
||||
const propertyNameAndValue = findType(child.children, mongoParser.PropertyNameAndValueListContext);
|
||||
if (!propertyNameAndValue) { // Argument is {}
|
||||
return {};
|
||||
} else {
|
||||
const parsedObject: Object = {};
|
||||
//tslint:disable:no-non-null-assertion
|
||||
const propertyAssignments = filterType(propertyNameAndValue.children, mongoParser.PropertyAssignmentContext);
|
||||
for (const propertyAssignment of propertyAssignments) {
|
||||
const propertyName = <mongoParser.PropertyNameContext>propertyAssignment.children[0];
|
||||
const propertyValue = <mongoParser.PropertyValueContext>propertyAssignment.children[2];
|
||||
parsedObject[stripQuotes(propertyName.text)] = this.contextToObject(propertyValue);
|
||||
}
|
||||
return parsedObject;
|
||||
}
|
||||
}
|
||||
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: typedef
|
||||
private arrayLiteralContextToObject(child: mongoParser.ArrayLiteralContext) {
|
||||
const elementList = findType(child.children, mongoParser.ElementListContext);
|
||||
if (elementList) {
|
||||
const elementItems = filterType(elementList.children, mongoParser.PropertyValueContext);
|
||||
return elementItems.map(this.contextToObject.bind(this));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private functionCallContextToObject(child: mongoParser.FunctionCallContext, ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext): Object {
|
||||
const functionTokens = child.children;
|
||||
const constructorCall: TerminalNode = findType(functionTokens, TerminalNode);
|
||||
const argumentsToken: mongoParser.ArgumentsContext = findType(functionTokens, mongoParser.ArgumentsContext);
|
||||
if (!(argumentsToken._CLOSED_PARENTHESIS && argumentsToken._OPEN_PARENTHESIS)) { //argumentsToken does not have '(' or ')'
|
||||
const err: IParsedError = parseError(`Expecting parentheses or quotes at '${constructorCall.text}'`);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
|
||||
const argumentContextArray: mongoParser.ArgumentContext[] = filterType(argumentsToken.children, mongoParser.ArgumentContext);
|
||||
if (argumentContextArray.length > 1) {
|
||||
const err: IParsedError = parseError(`Too many arguments. Expecting 0 or 1 argument(s) to ${constructorCall}`);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
|
||||
const tokenText: string | undefined = argumentContextArray.length ? argumentContextArray[0].text : undefined;
|
||||
switch (constructorCall.text) {
|
||||
case 'ObjectId':
|
||||
return this.objectIdToObject(ctx, tokenText);
|
||||
case 'ISODate':
|
||||
return this.isodateToObject(ctx, tokenText);
|
||||
case 'Date':
|
||||
return this.dateToObject(ctx, tokenText);
|
||||
default:
|
||||
const unrecognizedNodeErr: IParsedError = parseError(`Unrecognized node type encountered. Could not parse ${constructorCall.text} as part of ${child.text}`);
|
||||
this.addErrorToCommand(unrecognizedNodeErr, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private dateToObject(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext, tokenText?: string): { $date: string } | {} {
|
||||
const date: Date | {} = this.tryToConstructDate(ctx, tokenText);
|
||||
if (date instanceof Date) {
|
||||
return { $date: date.toString() };
|
||||
} else {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
private isodateToObject(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext, tokenText?: string): { $date: string } | {} {
|
||||
const date: Date | {} = this.tryToConstructDate(ctx, tokenText, true);
|
||||
|
||||
if (date instanceof Date) {
|
||||
return { $date: date.toISOString() };
|
||||
} else {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
private tryToConstructDate(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext, tokenText?: string, isIsodate: boolean = false): Date | {} {
|
||||
if (!tokenText) { // usage : ObjectID()
|
||||
return new Date();
|
||||
} else {
|
||||
try {
|
||||
tokenText = stripQuotes(tokenText);
|
||||
|
||||
// if the tokenText was an isodate, the last char must be Z
|
||||
if (isIsodate) {
|
||||
if (tokenText[tokenText.length - 1] !== 'Z') {
|
||||
tokenText += 'Z';
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(tokenText);
|
||||
} catch (error) {
|
||||
const err: IParsedError = parseError(error);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private objectIdToObject(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext, tokenText?: string): Object {
|
||||
let hexID: string;
|
||||
let constructedObject: ObjectID;
|
||||
if (!tokenText) { // usage : ObjectID()
|
||||
constructedObject = new ObjectID();
|
||||
} else {
|
||||
hexID = stripQuotes(<string>tokenText);
|
||||
try {
|
||||
constructedObject = new ObjectID(hexID);
|
||||
} catch (error) {
|
||||
const err: IParsedError = parseError(error);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return { $oid: constructedObject.toString() };
|
||||
}
|
||||
|
||||
private regexLiteralContextToObject(ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext, text: string): Object {
|
||||
const separator = text.lastIndexOf('/');
|
||||
const flags = separator !== text.length - 1 ? text.substring(separator + 1) : "";
|
||||
const pattern = text.substring(1, separator);
|
||||
try {
|
||||
// validate the pattern and flags.
|
||||
// It is intended for the errors thrown here to be handled by the catch block.
|
||||
let tokenObject = new RegExp(pattern, flags);
|
||||
tokenObject = tokenObject;
|
||||
// we are passing back a $regex annotation, hence we ensure parity wit the $regex syntax
|
||||
return { $regex: this.regexToStringNotation(pattern), $options: flags };
|
||||
} catch (error) { //User may not have finished typing
|
||||
const err: IParsedError = parseError(error);
|
||||
this.addErrorToCommand(err, ctx);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private addErrorToCommand(error: { message: string }, ctx: mongoParser.ArgumentContext | mongoParser.PropertyValueContext): void {
|
||||
const command = this.commands[this.commands.length - 1];
|
||||
command.errors = command.errors || [];
|
||||
const currentErrorDesc: ErrorDescription = { message: error.message, range: new vscode.Range(ctx.start.line - 1, ctx.start.charPositionInLine, ctx.stop.line - 1, ctx.stop.charPositionInLine) };
|
||||
command.errors.push(currentErrorDesc);
|
||||
}
|
||||
|
||||
private regexToStringNotation(pattern: string): string {
|
||||
// The equivalence:
|
||||
// /ker\b/ <=> $regex: "ker\\b", /ker\\b/ <=> "ker\\\\b"
|
||||
return pattern.replace(/\\([0-9a-z.*])/i, '\\\\$1');
|
||||
}
|
||||
|
||||
private deduplicateEscapesForRegex(argAsString: string): string {
|
||||
const removeDuplicatedBackslash = /\\{4}([0-9a-z.*])/gi;
|
||||
/*
|
||||
We remove duplicate backslashes due the behavior of '\b' - \b in a regex denotes word boundary, while \b in a string denotes backspace.
|
||||
$regex syntax uses a string. Strings require slashes to be escaped, while /regex/ does not. Eg. /abc+\b/ is equivalent to {$regex: "abc+\\b"}.
|
||||
{$regex: "abc+\b"} with an unescaped slash gets parsed as {$regex: <EOF>}. The user can only type '\\b' (which is encoded as '\\\\b').
|
||||
We need to convert this appropriately. Other special characters (\n, \t, \r) don't carry significance in regexes - we don't handle those
|
||||
What the regex does: '\\{4}' looks for the escaped slash 4 times. Lookahead checks if the character being escaped has a special meaning.
|
||||
*/
|
||||
return argAsString.replace(removeDuplicatedBackslash, `\\\\$1`);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import { parseError } from 'vscode-azureextensionui';
|
||||
import { InteractiveChildProcess } from '../utils/InteractiveChildProcess';
|
||||
import { randomUtils } from '../utils/randomUtils';
|
||||
import { wrapError } from '../utils/wrapError';
|
||||
|
||||
const timeoutMessage = "Timed out trying to execute the Mongo script. To use a longer timeout, modify the VS Code 'mongo.shell.timeout' setting.";
|
||||
|
||||
const mongoShellMoreMessage = 'Type "it" for more';
|
||||
const extensionMoreMessage = '(More)';
|
||||
|
||||
const sentinelBase = 'EXECUTION COMPLETED';
|
||||
const sentinelRegex = /\"?EXECUTION COMPLETED [0-9a-fA-F]{10}\"?/;
|
||||
function createSentinel(): string { return `${sentinelBase} ${randomUtils.getRandomHexString(10)}`; }
|
||||
|
||||
export class MongoShell extends vscode.Disposable {
|
||||
|
||||
constructor(private _process: InteractiveChildProcess, private _timeoutSeconds: number) {
|
||||
super(() => this.dispose());
|
||||
}
|
||||
|
||||
public static async create(execPath: string, execArgs: string[], connectionString: string, isEmulator: boolean, outputChannel: vscode.OutputChannel, timeoutSeconds: number): Promise<MongoShell> {
|
||||
try {
|
||||
const args: string[] = execArgs.slice() || []; // Snapshot since we modify it
|
||||
args.push(connectionString);
|
||||
|
||||
if (isEmulator) {
|
||||
// Without these the connection will fail due to the self-signed DocDB certificate
|
||||
if (args.indexOf("--ssl") < 0) {
|
||||
args.push("--ssl");
|
||||
}
|
||||
if (args.indexOf("--sslAllowInvalidCertificates") < 0) {
|
||||
args.push("--sslAllowInvalidCertificates");
|
||||
}
|
||||
}
|
||||
|
||||
const process: InteractiveChildProcess = await InteractiveChildProcess.create({
|
||||
outputChannel: outputChannel,
|
||||
command: execPath,
|
||||
args,
|
||||
outputFilterSearch: sentinelRegex,
|
||||
outputFilterReplace: ''
|
||||
});
|
||||
const shell: MongoShell = new MongoShell(process, timeoutSeconds);
|
||||
|
||||
// Try writing an empty script to verify the process is running correctly and allow us
|
||||
// to catch any errors related to the start-up of the process before trying to write to it.
|
||||
await shell.executeScript("");
|
||||
|
||||
return shell;
|
||||
} catch (error) {
|
||||
throw wrapCheckOutputWindow(error);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._process.kill();
|
||||
}
|
||||
|
||||
public async useDatabase(database: string): Promise<string> {
|
||||
return await this.executeScript(`use ${database}`);
|
||||
}
|
||||
|
||||
public async executeScript(script: string): Promise<string> {
|
||||
script = convertToSingleLine(script);
|
||||
|
||||
let stdOut = "";
|
||||
const sentinel = createSentinel();
|
||||
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
try {
|
||||
const result = await new Promise<string>(async (resolve, reject) => {
|
||||
try {
|
||||
startScriptTimeout(this._timeoutSeconds, reject);
|
||||
|
||||
// Hook up events
|
||||
disposables.push(
|
||||
this._process.onStdOut(text => {
|
||||
stdOut += text;
|
||||
// tslint:disable-next-line: prefer-const
|
||||
let { text: stdOutNoSentinel, removed } = removeSentinel(stdOut, sentinel);
|
||||
if (removed) {
|
||||
// The sentinel was found, which means we are done.
|
||||
|
||||
// Change the "type 'it' for more" message to one that doesn't ask users to type anything,
|
||||
// since we're not currently interactive like that.
|
||||
// CONSIDER: Ideally we would allow users to click a button to iterate through more data,
|
||||
// or even just do it for them
|
||||
stdOutNoSentinel = stdOutNoSentinel.replace(mongoShellMoreMessage, extensionMoreMessage);
|
||||
|
||||
resolve(stdOutNoSentinel);
|
||||
}
|
||||
}));
|
||||
disposables.push(
|
||||
this._process.onStdErr(text => {
|
||||
// Mongo shell only writes to STDERR for errors relating to starting up. Script errors go to STDOUT.
|
||||
// So consider this an error.
|
||||
// (It's okay if we fire this multiple times, the first one wins.)
|
||||
reject(wrapCheckOutputWindow(text.trim()));
|
||||
}));
|
||||
disposables.push(
|
||||
this._process.onError(error => {
|
||||
reject(error);
|
||||
}));
|
||||
|
||||
// Write the script to STDIN
|
||||
if (script) {
|
||||
this._process.writeLine(script);
|
||||
}
|
||||
|
||||
// Mark end of result by sending the sentinel wrapped in quotes so the console will spit
|
||||
// it back out as a string value after it's done processing the script
|
||||
const quotedSentinel = `"${sentinel}"`;
|
||||
this._process.writeLine(quotedSentinel); // (Don't display the sentinel)
|
||||
|
||||
} catch (error) {
|
||||
// new Promise() doesn't seem to catch exceptions in an async function, we need to explicitly reject it
|
||||
|
||||
if ((<{ code?: string }>error).code === 'EPIPE') {
|
||||
// Give a chance for start-up errors to show up before rejecting with this more general error message
|
||||
await delay(500);
|
||||
error = new Error("The process exited prematurely.");
|
||||
}
|
||||
|
||||
reject(wrapCheckOutputWindow(error));
|
||||
}
|
||||
});
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
finally {
|
||||
// Dispose event handlers
|
||||
for (const d of disposables) {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startScriptTimeout(timeoutSeconds: number | 0, reject: (err: unknown) => void): void {
|
||||
if (timeoutSeconds > 0) {
|
||||
setTimeout(
|
||||
() => {
|
||||
reject(timeoutMessage);
|
||||
},
|
||||
timeoutSeconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function convertToSingleLine(script: string): string {
|
||||
return script.split(os.EOL)
|
||||
.map(line => line.trim())
|
||||
.join('');
|
||||
|
||||
}
|
||||
|
||||
function removeSentinel(text: string, sentinel: string): { text: string; removed: boolean } {
|
||||
const index = text.indexOf(sentinel);
|
||||
if (index >= 0) {
|
||||
return { text: text.slice(0, index), removed: true };
|
||||
} else {
|
||||
return { text, removed: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function delay(milliseconds: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
// tslint:disable-next-line:no-string-based-set-timeout // false positive
|
||||
setTimeout(resolve, milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
function wrapCheckOutputWindow(error: unknown): unknown {
|
||||
const checkOutputMsg = "The output window may contain additional information.";
|
||||
return parseError(error).message.includes(checkOutputMsg) ? error : wrapError(error, checkOutputMsg);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MongoClient, MongoClientOptions } from 'mongodb';
|
||||
import { Links } from '../constants';
|
||||
|
||||
export async function connectToMongoClient(connectionString: string, appName: string): Promise<MongoClient> {
|
||||
// appname appears to be the correct equivalent to user-agent for mongo
|
||||
const options: MongoClientOptions = <MongoClientOptions>{
|
||||
// appName should be wrapped in '@'s when trying to connect to a Mongo account, this doesn't effect the appendUserAgent string
|
||||
appName: `@${appName}@`,
|
||||
// https://github.com/lmammino/mongo-uri-builder/issues/2
|
||||
useNewUrlParser: true
|
||||
};
|
||||
|
||||
try {
|
||||
return await MongoClient.connect(connectionString, options);
|
||||
} catch (err) {
|
||||
const error = <{ message?: string, name?: string }>err;
|
||||
const name = error && error.name;
|
||||
const message = error && error.message;
|
||||
|
||||
// Example error: "failed to connect to server [localhost:10255] on first connect [MongoError: connect ECONNREFUSED 127.0.0.1:10255]"
|
||||
// Example error: "failed to connect to server [127.0.0.1:27017] on first connect [MongoError: connect ECONNREFUSED 127.0.0.1:27017]"
|
||||
if (name === 'MongoError' && /ECONNREFUSED/.test(message) && /(localhost|127\.0\.0\.1)/.test(message)) {
|
||||
throw new Error(`Unable to connect to local Mongo DB instance. Make sure it is started correctly. See ${Links.LocalConnectionDebuggingTips} for tips.\n${message}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { getNodeEditorLabel } from '../../utils/vscodeUtils';
|
||||
import { MongoCollectionTreeItem } from "../tree/MongoCollectionTreeItem";
|
||||
import { IMongoDocument, MongoDocumentTreeItem } from "../tree/MongoDocumentTreeItem";
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
export class MongoCollectionNodeEditor implements ICosmosEditor<IMongoDocument[]> {
|
||||
private _collectionNode: MongoCollectionTreeItem;
|
||||
constructor(collectionNode: MongoCollectionTreeItem) {
|
||||
this._collectionNode = collectionNode;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return getNodeEditorLabel(this._collectionNode);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._collectionNode.fullId;
|
||||
}
|
||||
|
||||
public static async updateCachedDocNodes(updatedDocs: IMongoDocument[], collectionNode: MongoCollectionTreeItem, context: IActionContext): Promise<void> {
|
||||
const documentNodes = <MongoDocumentTreeItem[]>await collectionNode.getCachedChildren(context);
|
||||
for (const updatedDoc of updatedDocs) {
|
||||
const documentNode = documentNodes.find((node) => node.document._id.toString() === updatedDoc._id.toString());
|
||||
if (documentNode) {
|
||||
documentNode.document = updatedDoc;
|
||||
await documentNode.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getData(context: IActionContext): Promise<IMongoDocument[]> {
|
||||
const children = <MongoDocumentTreeItem[]>await this._collectionNode.getCachedChildren(context);
|
||||
return children.map((child) => child.document);
|
||||
}
|
||||
|
||||
public async update(documents: IMongoDocument[], context: IActionContext): Promise<IMongoDocument[]> {
|
||||
const updatedDocs = await this._collectionNode.update(documents);
|
||||
await MongoCollectionNodeEditor.updateCachedDocNodes(updatedDocs, this._collectionNode, context);
|
||||
return updatedDocs;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): IMongoDocument[] {
|
||||
return EJSON.parse(data);
|
||||
}
|
||||
|
||||
public convertToString(data: IMongoDocument[]): string {
|
||||
return EJSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { getNodeEditorLabel } from '../../utils/vscodeUtils';
|
||||
import { IMongoDocument, MongoDocumentTreeItem } from "../tree/MongoDocumentTreeItem";
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
export class MongoDocumentNodeEditor implements ICosmosEditor<IMongoDocument> {
|
||||
private _documentNode: MongoDocumentTreeItem;
|
||||
constructor(documentNode: MongoDocumentTreeItem) {
|
||||
this._documentNode = documentNode;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return getNodeEditorLabel(this._documentNode);
|
||||
}
|
||||
|
||||
public async getData(): Promise<IMongoDocument> {
|
||||
return this._documentNode.document;
|
||||
}
|
||||
|
||||
public async update(document: IMongoDocument): Promise<IMongoDocument> {
|
||||
const updatedDoc = await this._documentNode.update(document);
|
||||
await this._documentNode.refresh();
|
||||
return updatedDoc;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._documentNode.fullId;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): IMongoDocument {
|
||||
return EJSON.parse(data);
|
||||
}
|
||||
|
||||
public convertToString(data: IMongoDocument): string {
|
||||
return EJSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { MongoDatabaseTreeItem } from "../tree/MongoDatabaseTreeItem";
|
||||
import { IMongoDocument, MongoDocumentTreeItem } from "../tree/MongoDocumentTreeItem";
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
export class MongoFindOneResultEditor implements ICosmosEditor<IMongoDocument> {
|
||||
private _databaseNode: MongoDatabaseTreeItem;
|
||||
private _collectionName: string;
|
||||
private _originalDocument: IMongoDocument;
|
||||
|
||||
constructor(databaseNode: MongoDatabaseTreeItem, collectionName: string, data: string) {
|
||||
this._databaseNode = databaseNode;
|
||||
this._collectionName = collectionName;
|
||||
this._originalDocument = EJSON.parse(data);
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
const accountNode = this._databaseNode.parent;
|
||||
return `${accountNode.label}/${this._databaseNode.label}/${this._collectionName}/${this._originalDocument._id}`;
|
||||
}
|
||||
|
||||
public async getData(): Promise<IMongoDocument> {
|
||||
return this._originalDocument;
|
||||
}
|
||||
|
||||
public async update(newDocument: IMongoDocument, context: IActionContext): Promise<IMongoDocument> {
|
||||
const node = <MongoDocumentTreeItem | undefined>await ext.tree.findTreeItem(this.id, context);
|
||||
let result: IMongoDocument;
|
||||
if (node) {
|
||||
result = await node.update(newDocument);
|
||||
await node.refresh();
|
||||
} else {
|
||||
// If the node isn't cached already, just update it to Mongo directly (without worrying about updating the tree)
|
||||
const db = await this._databaseNode.connectToDb();
|
||||
result = await MongoDocumentTreeItem.update(db.collection(this._collectionName), newDocument);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return `${this._databaseNode.fullId}/${this._collectionName}/${this._originalDocument._id.toString()}`;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): IMongoDocument {
|
||||
return EJSON.parse(data);
|
||||
}
|
||||
|
||||
public convertToString(data: IMongoDocument): string {
|
||||
return EJSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Collection } from "mongodb";
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ICosmosEditor } from "../../CosmosEditorManager";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { MongoCommand } from "../MongoCommand";
|
||||
import { MongoCollectionTreeItem } from "../tree/MongoCollectionTreeItem";
|
||||
import { MongoDatabaseTreeItem } from "../tree/MongoDatabaseTreeItem";
|
||||
import { IMongoDocument, MongoDocumentTreeItem } from "../tree/MongoDocumentTreeItem";
|
||||
import { MongoCollectionNodeEditor } from "./MongoCollectionNodeEditor";
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
export class MongoFindResultEditor implements ICosmosEditor<IMongoDocument[]> {
|
||||
private _databaseNode: MongoDatabaseTreeItem;
|
||||
private _command: MongoCommand;
|
||||
private _collectionTreeItem: MongoCollectionTreeItem;
|
||||
|
||||
constructor(databaseNode: MongoDatabaseTreeItem, command: MongoCommand) {
|
||||
this._databaseNode = databaseNode;
|
||||
this._command = command;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
const accountNode = this._databaseNode.parent;
|
||||
return `${accountNode.label}/${this._databaseNode.label}/${this._command.collection}`;
|
||||
}
|
||||
|
||||
public async getData(context: IActionContext): Promise<IMongoDocument[]> {
|
||||
const db = await this._databaseNode.connectToDb();
|
||||
const collection: Collection = db.collection(this._command.collection);
|
||||
// NOTE: Intentionally creating a _new_ tree item rather than searching for a cached node in the tree because
|
||||
// the executed 'find' command could have a filter or projection that is not handled by a cached tree node
|
||||
this._collectionTreeItem = new MongoCollectionTreeItem(this._databaseNode, collection, this._command.argumentObjects);
|
||||
const documents: MongoDocumentTreeItem[] = <MongoDocumentTreeItem[]>await this._collectionTreeItem.getCachedChildren(context);
|
||||
return documents.map((docTreeItem) => docTreeItem.document);
|
||||
}
|
||||
|
||||
public async update(documents: IMongoDocument[], context: IActionContext): Promise<IMongoDocument[]> {
|
||||
const updatedDocs = await this._collectionTreeItem.update(documents);
|
||||
const cachedCollectionNode = await ext.tree.findTreeItem(this.id, context);
|
||||
if (cachedCollectionNode) {
|
||||
await MongoCollectionNodeEditor.updateCachedDocNodes(updatedDocs, <MongoCollectionTreeItem>cachedCollectionNode, context);
|
||||
}
|
||||
return updatedDocs;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return `${this._databaseNode.fullId}/${this._command.collection}`;
|
||||
}
|
||||
|
||||
public convertFromString(data: string): IMongoDocument[] {
|
||||
return EJSON.parse(data);
|
||||
}
|
||||
|
||||
public convertToString(data: IMongoDocument[]): string {
|
||||
return EJSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import { ANTLRErrorListener } from '../../node_modules/antlr4ts/ANTLRErrorListener';
|
||||
import { RecognitionException } from '../../node_modules/antlr4ts/RecognitionException';
|
||||
import { Recognizer } from '../../node_modules/antlr4ts/Recognizer';
|
||||
import { Token } from '../../node_modules/antlr4ts/Token';
|
||||
import { ErrorDescription } from './MongoCommand';
|
||||
|
||||
export class ParserErrorListener implements ANTLRErrorListener<Token> {
|
||||
private _errors: ErrorDescription[] = [];
|
||||
|
||||
public get errors(): ErrorDescription[] {
|
||||
return this._errors;
|
||||
}
|
||||
|
||||
public syntaxError(
|
||||
// tslint:disable-next-line:no-any
|
||||
_recognizer: Recognizer<Token, any>,
|
||||
_offendingSymbol: Token | undefined,
|
||||
line: number,
|
||||
charPositionInLine: number,
|
||||
msg: string,
|
||||
e: RecognitionException | undefined): void {
|
||||
|
||||
const position = new vscode.Position(line - 1, charPositionInLine); // Symbol lines are 1-indexed. Position lines are 0-indexed
|
||||
const range = new vscode.Range(position, position);
|
||||
|
||||
const error: ErrorDescription = {
|
||||
message: msg,
|
||||
range: range,
|
||||
exception: e
|
||||
};
|
||||
this._errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
export class LexerErrorListener implements ANTLRErrorListener<number> {
|
||||
private _errors: ErrorDescription[] = [];
|
||||
|
||||
public get errors(): ErrorDescription[] {
|
||||
return this._errors;
|
||||
}
|
||||
|
||||
public syntaxError(
|
||||
// tslint:disable-next-line:no-any
|
||||
_recognizer: Recognizer<number, any>,
|
||||
_offendingSymbol: number | undefined,
|
||||
line: number,
|
||||
charPositionInLine: number,
|
||||
msg: string,
|
||||
e: RecognitionException | undefined): void {
|
||||
|
||||
const position = new vscode.Position(line - 1, charPositionInLine); // Symbol lines are 1-indexed. Position lines are 0-indexed
|
||||
const range = new vscode.Range(position, position);
|
||||
|
||||
const error: ErrorDescription = {
|
||||
message: msg,
|
||||
range: range,
|
||||
exception: e
|
||||
};
|
||||
this._errors.push(error);
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
T__3=4
|
||||
T__4=5
|
||||
T__5=6
|
||||
T__6=7
|
||||
T__7=8
|
||||
RegexLiteral=9
|
||||
SingleLineComment=10
|
||||
MultiLineComment=11
|
||||
StringLiteral=12
|
||||
NullLiteral=13
|
||||
BooleanLiteral=14
|
||||
NumericLiteral=15
|
||||
DecimalLiteral=16
|
||||
LineTerminator=17
|
||||
SEMICOLON=18
|
||||
DOT=19
|
||||
DB=20
|
||||
IDENTIFIER=21
|
||||
DOUBLE_QUOTED_STRING_LITERAL=22
|
||||
SINGLE_QUOTED_STRING_LITERAL=23
|
||||
WHITESPACE=24
|
||||
'('=1
|
||||
','=2
|
||||
')'=3
|
||||
'{'=4
|
||||
'}'=5
|
||||
'['=6
|
||||
']'=7
|
||||
':'=8
|
||||
'null'=13
|
||||
';'=18
|
||||
'.'=19
|
||||
'db'=20
|
|
@ -1,36 +0,0 @@
|
|||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
T__3=4
|
||||
T__4=5
|
||||
T__5=6
|
||||
T__6=7
|
||||
T__7=8
|
||||
RegexLiteral=9
|
||||
SingleLineComment=10
|
||||
MultiLineComment=11
|
||||
StringLiteral=12
|
||||
NullLiteral=13
|
||||
BooleanLiteral=14
|
||||
NumericLiteral=15
|
||||
DecimalLiteral=16
|
||||
LineTerminator=17
|
||||
SEMICOLON=18
|
||||
DOT=19
|
||||
DB=20
|
||||
IDENTIFIER=21
|
||||
DOUBLE_QUOTED_STRING_LITERAL=22
|
||||
SINGLE_QUOTED_STRING_LITERAL=23
|
||||
WHITESPACE=24
|
||||
'('=1
|
||||
','=2
|
||||
')'=3
|
||||
'{'=4
|
||||
'}'=5
|
||||
'['=6
|
||||
']'=7
|
||||
':'=8
|
||||
'null'=13
|
||||
';'=18
|
||||
'.'=19
|
||||
'db'=20
|
|
@ -1,248 +0,0 @@
|
|||
// Generated from ./grammar/mongo.g4 by ANTLR 4.6-SNAPSHOT
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*tslint:disable */
|
||||
|
||||
|
||||
import { ATN } from 'antlr4ts/atn/ATN';
|
||||
import { ATNDeserializer } from 'antlr4ts/atn/ATNDeserializer';
|
||||
import { CharStream } from 'antlr4ts/CharStream';
|
||||
import { Lexer } from 'antlr4ts/Lexer';
|
||||
import { LexerATNSimulator } from 'antlr4ts/atn/LexerATNSimulator';
|
||||
import { NotNull } from 'antlr4ts/Decorators';
|
||||
import { Override } from 'antlr4ts/Decorators';
|
||||
import { RuleContext } from 'antlr4ts/RuleContext';
|
||||
import { Vocabulary } from 'antlr4ts/Vocabulary';
|
||||
import { VocabularyImpl } from 'antlr4ts/VocabularyImpl';
|
||||
|
||||
import * as Utils from 'antlr4ts/misc/Utils';
|
||||
|
||||
|
||||
export class mongoLexer extends Lexer {
|
||||
public static readonly T__0=1;
|
||||
public static readonly T__1=2;
|
||||
public static readonly T__2=3;
|
||||
public static readonly T__3=4;
|
||||
public static readonly T__4=5;
|
||||
public static readonly T__5=6;
|
||||
public static readonly T__6=7;
|
||||
public static readonly T__7=8;
|
||||
public static readonly RegexLiteral=9;
|
||||
public static readonly SingleLineComment=10;
|
||||
public static readonly MultiLineComment=11;
|
||||
public static readonly StringLiteral=12;
|
||||
public static readonly NullLiteral=13;
|
||||
public static readonly BooleanLiteral=14;
|
||||
public static readonly NumericLiteral=15;
|
||||
public static readonly DecimalLiteral=16;
|
||||
public static readonly LineTerminator=17;
|
||||
public static readonly SEMICOLON=18;
|
||||
public static readonly DOT=19;
|
||||
public static readonly DB=20;
|
||||
public static readonly IDENTIFIER=21;
|
||||
public static readonly DOUBLE_QUOTED_STRING_LITERAL=22;
|
||||
public static readonly SINGLE_QUOTED_STRING_LITERAL=23;
|
||||
public static readonly WHITESPACE=24;
|
||||
public static readonly modeNames: string[] = [
|
||||
"DEFAULT_MODE"
|
||||
];
|
||||
|
||||
public static readonly ruleNames: string[] = [
|
||||
"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "RegexLiteral",
|
||||
"RegexFlag", "SingleLineComment", "MultiLineComment", "StringLiteral",
|
||||
"NullLiteral", "BooleanLiteral", "NumericLiteral", "DecimalLiteral", "LineTerminator",
|
||||
"SEMICOLON", "DOT", "DB", "IDENTIFIER", "DOUBLE_QUOTED_STRING_LITERAL",
|
||||
"SINGLE_QUOTED_STRING_LITERAL", "STRING_ESCAPE", "DecimalIntegerLiteral",
|
||||
"ExponentPart", "DecimalDigit", "WHITESPACE"
|
||||
];
|
||||
|
||||
private static readonly _LITERAL_NAMES: (string | undefined)[] = [
|
||||
undefined, "'('", "','", "')'", "'{'", "'}'", "'['", "']'", "':'", undefined,
|
||||
undefined, undefined, undefined, "'null'", undefined, undefined, undefined,
|
||||
undefined, "';'", "'.'", "'db'"
|
||||
];
|
||||
private static readonly _SYMBOLIC_NAMES: (string | undefined)[] = [
|
||||
undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, "RegexLiteral", "SingleLineComment", "MultiLineComment",
|
||||
"StringLiteral", "NullLiteral", "BooleanLiteral", "NumericLiteral", "DecimalLiteral",
|
||||
"LineTerminator", "SEMICOLON", "DOT", "DB", "IDENTIFIER", "DOUBLE_QUOTED_STRING_LITERAL",
|
||||
"SINGLE_QUOTED_STRING_LITERAL", "WHITESPACE"
|
||||
];
|
||||
public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(mongoLexer._LITERAL_NAMES, mongoLexer._SYMBOLIC_NAMES, []);
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public get vocabulary(): Vocabulary {
|
||||
return mongoLexer.VOCABULARY;
|
||||
}
|
||||
|
||||
|
||||
private isExternalIdentifierText(text) {
|
||||
return text === 'db';
|
||||
}
|
||||
|
||||
|
||||
constructor(input: CharStream) {
|
||||
super(input);
|
||||
this._interp = new LexerATNSimulator(mongoLexer._ATN, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public get grammarFileName(): string { return "mongo.g4"; }
|
||||
|
||||
@Override
|
||||
public get ruleNames(): string[] { return mongoLexer.ruleNames; }
|
||||
|
||||
@Override
|
||||
public get serializedATN(): string { return mongoLexer._serializedATN; }
|
||||
|
||||
@Override
|
||||
public get modeNames(): string[] { return mongoLexer.modeNames; }
|
||||
|
||||
@Override
|
||||
public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean {
|
||||
switch (ruleIndex) {
|
||||
case 21:
|
||||
return this.IDENTIFIER_sempred(_localctx, predIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private IDENTIFIER_sempred(_localctx: RuleContext, predIndex: number): boolean {
|
||||
switch (predIndex) {
|
||||
case 0:
|
||||
return !this.isExternalIdentifierText(this.text)
|
||||
;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static readonly _serializedATN: string =
|
||||
"\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x02\x1A\xF2\b\x01"+
|
||||
"\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06"+
|
||||
"\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r"+
|
||||
"\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t"+
|
||||
"\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t"+
|
||||
"\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t"+
|
||||
"\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x03\x02\x03\x02\x03\x03\x03\x03\x03\x04"+
|
||||
"\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\b\x03\b\x03"+
|
||||
"\t\x03\t\x03\n\x03\n\x03\n\x03\n\x05\nR\n\n\x03\n\x03\n\x03\n\x07\nW\n"+
|
||||
"\n\f\n\x0E\nZ\v\n\x03\n\x03\n\x07\n^\n\n\f\n\x0E\na\v\n\x03\v\x03\v\x03"+
|
||||
"\f\x03\f\x03\f\x03\f\x07\fi\n\f\f\f\x0E\fl\v\f\x03\f\x03\f\x03\r\x03\r"+
|
||||
"\x03\r\x03\r\x07\rt\n\r\f\r\x0E\rw\v\r\x03\r\x03\r\x03\r\x03\r\x03\r\x03"+
|
||||
"\x0E\x03\x0E\x05\x0E\x80\n\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x0F\x03\x0F"+
|
||||
"\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10\x03\x10"+
|
||||
"\x05\x10\x90\n\x10\x03\x11\x05\x11\x93\n\x11\x03\x11\x03\x11\x03\x12\x03"+
|
||||
"\x12\x03\x12\x06\x12\x9A\n\x12\r\x12\x0E\x12\x9B\x03\x12\x05\x12\x9F\n"+
|
||||
"\x12\x03\x12\x03\x12\x06\x12\xA3\n\x12\r\x12\x0E\x12\xA4\x03\x12\x05\x12"+
|
||||
"\xA8\n\x12\x03\x12\x03\x12\x05\x12\xAC\n\x12\x05\x12\xAE\n\x12\x03\x13"+
|
||||
"\x03\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16"+
|
||||
"\x03\x16\x03\x17\x03\x17\x06\x17\xBD\n\x17\r\x17\x0E\x17\xBE\x03\x17\x03"+
|
||||
"\x17\x03\x18\x03\x18\x03\x18\x07\x18\xC6\n\x18\f\x18\x0E\x18\xC9\v\x18"+
|
||||
"\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x07\x19\xD0\n\x19\f\x19\x0E\x19"+
|
||||
"\xD3\v\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03"+
|
||||
"\x1B\x07\x1B\xDD\n\x1B\f\x1B\x0E\x1B\xE0\v\x1B\x05\x1B\xE2\n\x1B\x03\x1C"+
|
||||
"\x03\x1C\x05\x1C\xE6\n\x1C\x03\x1C\x06\x1C\xE9\n\x1C\r\x1C\x0E\x1C\xEA"+
|
||||
"\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x03u\x02\x02\x1F\x03"+
|
||||
"\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06\v\x02\x07\r\x02\b\x0F\x02\t"+
|
||||
"\x11\x02\n\x13\x02\v\x15\x02\x02\x17\x02\f\x19\x02\r\x1B\x02\x0E\x1D\x02"+
|
||||
"\x0F\x1F\x02\x10!\x02\x11#\x02\x12%\x02\x13\'\x02\x14)\x02\x15+\x02\x16"+
|
||||
"-\x02\x17/\x02\x181\x02\x193\x02\x025\x02\x027\x02\x029\x02\x02;\x02\x1A"+
|
||||
"\x03\x02\x0F\x06\x02\f\f\x0F\x0F,,11\x05\x02\f\f\x0F\x0F11\x07\x02iik"+
|
||||
"kooww{{\x05\x02\f\f\x0F\x0F\u202A\u202B\f\x02\v\f\x0F\x0F\"\"$$)+.0<="+
|
||||
"]_}}\x7F\x7F\x04\x02$$^^\x04\x02))^^\x05\x02$$))^^\x03\x023;\x04\x02G"+
|
||||
"Ggg\x04\x02--//\x03\x022;\x04\x02\v\v\"\"\u0106\x02\x03\x03\x02\x02\x02"+
|
||||
"\x02\x05\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02"+
|
||||
"\x02\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02"+
|
||||
"\x11\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x17\x03\x02\x02\x02\x02"+
|
||||
"\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03\x02\x02\x02\x02"+
|
||||
"\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02\x02\x02\x02%\x03"+
|
||||
"\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02\x02+\x03\x02\x02"+
|
||||
"\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03\x02\x02\x02\x02"+
|
||||
";\x03\x02\x02\x02\x03=\x03\x02\x02\x02\x05?\x03\x02\x02\x02\x07A\x03\x02"+
|
||||
"\x02\x02\tC\x03\x02\x02\x02\vE\x03\x02\x02\x02\rG\x03\x02\x02\x02\x0F"+
|
||||
"I\x03\x02\x02\x02\x11K\x03\x02\x02\x02\x13M\x03\x02\x02\x02\x15b\x03\x02"+
|
||||
"\x02\x02\x17d\x03\x02\x02\x02\x19o\x03\x02\x02\x02\x1B\x7F\x03\x02\x02"+
|
||||
"\x02\x1D\x81\x03\x02\x02\x02\x1F\x8F\x03\x02\x02\x02!\x92\x03\x02\x02"+
|
||||
"\x02#\xAD\x03\x02\x02\x02%\xAF\x03\x02\x02\x02\'\xB3\x03\x02\x02\x02)"+
|
||||
"\xB5\x03\x02\x02\x02+\xB7\x03\x02\x02\x02-\xBC\x03\x02\x02\x02/\xC2\x03"+
|
||||
"\x02\x02\x021\xCC\x03\x02\x02\x023\xD6\x03\x02\x02\x025\xE1\x03\x02\x02"+
|
||||
"\x027\xE3\x03\x02\x02\x029\xEC\x03\x02\x02\x02;\xEE\x03\x02\x02\x02=>"+
|
||||
"\x07*\x02\x02>\x04\x03\x02\x02\x02?@\x07.\x02\x02@\x06\x03\x02\x02\x02"+
|
||||
"AB\x07+\x02\x02B\b\x03\x02\x02\x02CD\x07}\x02\x02D\n\x03\x02\x02\x02E"+
|
||||
"F\x07\x7F\x02\x02F\f\x03\x02\x02\x02GH\x07]\x02\x02H\x0E\x03\x02\x02\x02"+
|
||||
"IJ\x07_\x02\x02J\x10\x03\x02\x02\x02KL\x07<\x02\x02L\x12\x03\x02\x02\x02"+
|
||||
"MQ\x071\x02\x02NR\n\x02\x02\x02OP\x07^\x02\x02PR\x071\x02\x02QN\x03\x02"+
|
||||
"\x02\x02QO\x03\x02\x02\x02RX\x03\x02\x02\x02SW\n\x03\x02\x02TU\x07^\x02"+
|
||||
"\x02UW\x071\x02\x02VS\x03\x02\x02\x02VT\x03\x02\x02\x02WZ\x03\x02\x02"+
|
||||
"\x02XV\x03\x02\x02\x02XY\x03\x02\x02\x02Y[\x03\x02\x02\x02ZX\x03\x02\x02"+
|
||||
"\x02[_\x071\x02\x02\\^\x05\x15\v\x02]\\\x03\x02\x02\x02^a\x03\x02\x02"+
|
||||
"\x02_]\x03\x02\x02\x02_`\x03\x02\x02\x02`\x14\x03\x02\x02\x02a_\x03\x02"+
|
||||
"\x02\x02bc\t\x04\x02\x02c\x16\x03\x02\x02\x02de\x071\x02\x02ef\x071\x02"+
|
||||
"\x02fj\x03\x02\x02\x02gi\n\x05\x02\x02hg\x03\x02\x02\x02il\x03\x02\x02"+
|
||||
"\x02jh\x03\x02\x02\x02jk\x03\x02\x02\x02km\x03\x02\x02\x02lj\x03\x02\x02"+
|
||||
"\x02mn\b\f\x02\x02n\x18\x03\x02\x02\x02op\x071\x02\x02pq\x07,\x02\x02"+
|
||||
"qu\x03\x02\x02\x02rt\v\x02\x02\x02sr\x03\x02\x02\x02tw\x03\x02\x02\x02"+
|
||||
"uv\x03\x02\x02\x02us\x03\x02\x02\x02vx\x03\x02\x02\x02wu\x03\x02\x02\x02"+
|
||||
"xy\x07,\x02\x02yz\x071\x02\x02z{\x03\x02\x02\x02{|\b\r\x02\x02|\x1A\x03"+
|
||||
"\x02\x02\x02}\x80\x051\x19\x02~\x80\x05/\x18\x02\x7F}\x03\x02\x02\x02"+
|
||||
"\x7F~\x03\x02\x02\x02\x80\x1C\x03\x02\x02\x02\x81\x82\x07p\x02\x02\x82"+
|
||||
"\x83\x07w\x02\x02\x83\x84\x07n\x02\x02\x84\x85\x07n\x02\x02\x85\x1E\x03"+
|
||||
"\x02\x02\x02\x86\x87\x07v\x02\x02\x87\x88\x07t\x02\x02\x88\x89\x07w\x02"+
|
||||
"\x02\x89\x90\x07g\x02\x02\x8A\x8B\x07h\x02\x02\x8B\x8C\x07c\x02\x02\x8C"+
|
||||
"\x8D\x07n\x02\x02\x8D\x8E\x07u\x02\x02\x8E\x90\x07g\x02\x02\x8F\x86\x03"+
|
||||
"\x02\x02\x02\x8F\x8A\x03\x02\x02\x02\x90 \x03\x02\x02\x02\x91\x93\x07"+
|
||||
"/\x02\x02\x92\x91\x03\x02\x02\x02\x92\x93\x03\x02\x02\x02\x93\x94\x03"+
|
||||
"\x02\x02\x02\x94\x95\x05#\x12\x02\x95\"\x03\x02\x02\x02\x96\x97\x055\x1B"+
|
||||
"\x02\x97\x99\x070\x02\x02\x98\x9A\x059\x1D\x02\x99\x98\x03\x02\x02\x02"+
|
||||
"\x9A\x9B\x03\x02\x02\x02\x9B\x99\x03\x02\x02\x02\x9B\x9C\x03\x02\x02\x02"+
|
||||
"\x9C\x9E\x03\x02\x02\x02\x9D\x9F\x057\x1C\x02\x9E\x9D\x03\x02\x02\x02"+
|
||||
"\x9E\x9F\x03\x02\x02\x02\x9F\xAE\x03\x02\x02\x02\xA0\xA2\x070\x02\x02"+
|
||||
"\xA1\xA3\x059\x1D\x02\xA2\xA1\x03\x02\x02\x02\xA3\xA4\x03\x02\x02\x02"+
|
||||
"\xA4\xA2\x03\x02\x02\x02\xA4\xA5\x03\x02\x02\x02\xA5\xA7\x03\x02\x02\x02"+
|
||||
"\xA6\xA8\x057\x1C\x02\xA7\xA6\x03\x02\x02\x02\xA7\xA8\x03\x02\x02\x02"+
|
||||
"\xA8\xAE\x03\x02\x02\x02\xA9\xAB\x055\x1B\x02\xAA\xAC\x057\x1C\x02\xAB"+
|
||||
"\xAA\x03\x02\x02\x02\xAB\xAC\x03\x02\x02\x02\xAC\xAE\x03\x02\x02\x02\xAD"+
|
||||
"\x96\x03\x02\x02\x02\xAD\xA0\x03\x02\x02\x02\xAD\xA9\x03\x02\x02\x02\xAE"+
|
||||
"$\x03\x02\x02\x02\xAF\xB0\t\x05\x02\x02\xB0\xB1\x03\x02\x02\x02\xB1\xB2"+
|
||||
"\b\x13\x02\x02\xB2&\x03\x02\x02\x02\xB3\xB4\x07=\x02\x02\xB4(\x03\x02"+
|
||||
"\x02\x02\xB5\xB6\x070\x02\x02\xB6*\x03\x02\x02\x02\xB7\xB8\x07f\x02\x02"+
|
||||
"\xB8\xB9\x07d\x02\x02\xB9,\x03\x02\x02\x02\xBA\xBD\n\x06\x02\x02\xBB\xBD"+
|
||||
"\x053\x1A\x02\xBC\xBA\x03\x02\x02\x02\xBC\xBB\x03\x02\x02\x02\xBD\xBE"+
|
||||
"\x03\x02\x02\x02\xBE\xBC\x03\x02\x02\x02\xBE\xBF\x03\x02\x02\x02\xBF\xC0"+
|
||||
"\x03\x02\x02\x02\xC0\xC1\x06\x17\x02\x02\xC1.\x03\x02\x02\x02\xC2\xC7"+
|
||||
"\x07$\x02\x02\xC3\xC6\n\x07\x02\x02\xC4\xC6\x053\x1A\x02\xC5\xC3\x03\x02"+
|
||||
"\x02\x02\xC5\xC4\x03\x02\x02\x02\xC6\xC9\x03\x02\x02\x02\xC7\xC5\x03\x02"+
|
||||
"\x02\x02\xC7\xC8\x03\x02\x02\x02\xC8\xCA\x03\x02\x02\x02\xC9\xC7\x03\x02"+
|
||||
"\x02\x02\xCA\xCB\x07$\x02\x02\xCB0\x03\x02\x02\x02\xCC\xD1\x07)\x02\x02"+
|
||||
"\xCD\xD0\n\b\x02\x02\xCE\xD0\x053\x1A\x02\xCF\xCD\x03\x02\x02\x02\xCF"+
|
||||
"\xCE\x03\x02\x02\x02\xD0\xD3\x03\x02\x02\x02\xD1\xCF\x03\x02\x02\x02\xD1"+
|
||||
"\xD2\x03\x02\x02\x02\xD2\xD4\x03\x02\x02\x02\xD3\xD1\x03\x02\x02\x02\xD4"+
|
||||
"\xD5\x07)\x02\x02\xD52\x03\x02\x02\x02\xD6\xD7\x07^\x02\x02\xD7\xD8\t"+
|
||||
"\t\x02\x02\xD84\x03\x02\x02\x02\xD9\xE2\x072\x02\x02\xDA\xDE\t\n\x02\x02"+
|
||||
"\xDB\xDD\x059\x1D\x02\xDC\xDB\x03\x02\x02\x02\xDD\xE0\x03\x02\x02\x02"+
|
||||
"\xDE\xDC\x03\x02\x02\x02\xDE\xDF\x03\x02\x02\x02\xDF\xE2\x03\x02\x02\x02"+
|
||||
"\xE0\xDE\x03\x02\x02\x02\xE1\xD9\x03\x02\x02\x02\xE1\xDA\x03\x02\x02\x02"+
|
||||
"\xE26\x03\x02\x02\x02\xE3\xE5\t\v\x02\x02\xE4\xE6\t\f\x02\x02\xE5\xE4"+
|
||||
"\x03\x02\x02\x02\xE5\xE6\x03\x02\x02\x02\xE6\xE8\x03\x02\x02\x02\xE7\xE9"+
|
||||
"\x059\x1D\x02\xE8\xE7\x03\x02\x02\x02\xE9\xEA\x03\x02\x02\x02\xEA\xE8"+
|
||||
"\x03\x02\x02\x02\xEA\xEB\x03\x02\x02\x02\xEB8\x03\x02\x02\x02\xEC\xED"+
|
||||
"\t\r\x02\x02\xED:\x03\x02\x02\x02\xEE\xEF\t\x0E\x02\x02\xEF\xF0\x03\x02"+
|
||||
"\x02\x02\xF0\xF1\b\x1E\x03\x02\xF1<\x03\x02\x02\x02\x1C\x02QVX_ju\x7F"+
|
||||
"\x8F\x92\x9B\x9E\xA4\xA7\xAB\xAD\xBC\xBE\xC5\xC7\xCF\xD1\xDE\xE1\xE5\xEA"+
|
||||
"\x04\x02\x03\x02\b\x02\x02";
|
||||
public static __ATN: ATN;
|
||||
public static get _ATN(): ATN {
|
||||
if (!mongoLexer.__ATN) {
|
||||
mongoLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(mongoLexer._serializedATN));
|
||||
}
|
||||
|
||||
return mongoLexer.__ATN;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
// Generated from ./grammar/mongo.g4 by ANTLR 4.6-SNAPSHOT
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*tslint:disable */
|
||||
|
||||
|
||||
import { ParseTreeListener } from 'antlr4ts/tree/ParseTreeListener';
|
||||
|
||||
import { MongoCommandsContext } from './mongoParser';
|
||||
import { CommandsContext } from './mongoParser';
|
||||
import { CommandContext } from './mongoParser';
|
||||
import { EmptyCommandContext } from './mongoParser';
|
||||
import { CollectionContext } from './mongoParser';
|
||||
import { FunctionCallContext } from './mongoParser';
|
||||
import { ArgumentsContext } from './mongoParser';
|
||||
import { ArgumentContext } from './mongoParser';
|
||||
import { ObjectLiteralContext } from './mongoParser';
|
||||
import { ArrayLiteralContext } from './mongoParser';
|
||||
import { ElementListContext } from './mongoParser';
|
||||
import { PropertyNameAndValueListContext } from './mongoParser';
|
||||
import { PropertyAssignmentContext } from './mongoParser';
|
||||
import { PropertyValueContext } from './mongoParser';
|
||||
import { LiteralContext } from './mongoParser';
|
||||
import { PropertyNameContext } from './mongoParser';
|
||||
import { CommentContext } from './mongoParser';
|
||||
|
||||
|
||||
/**
|
||||
* This interface defines a complete listener for a parse tree produced by
|
||||
* `mongoParser`.
|
||||
*/
|
||||
export interface mongoListener extends ParseTreeListener {
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.mongoCommands`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterMongoCommands?: (ctx: MongoCommandsContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.mongoCommands`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitMongoCommands?: (ctx: MongoCommandsContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.commands`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterCommands?: (ctx: CommandsContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.commands`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitCommands?: (ctx: CommandsContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.command`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterCommand?: (ctx: CommandContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.command`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitCommand?: (ctx: CommandContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.emptyCommand`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterEmptyCommand?: (ctx: EmptyCommandContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.emptyCommand`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitEmptyCommand?: (ctx: EmptyCommandContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.collection`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterCollection?: (ctx: CollectionContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.collection`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitCollection?: (ctx: CollectionContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.functionCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterFunctionCall?: (ctx: FunctionCallContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.functionCall`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitFunctionCall?: (ctx: FunctionCallContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.arguments`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterArguments?: (ctx: ArgumentsContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.arguments`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitArguments?: (ctx: ArgumentsContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.argument`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterArgument?: (ctx: ArgumentContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.argument`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitArgument?: (ctx: ArgumentContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.objectLiteral`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterObjectLiteral?: (ctx: ObjectLiteralContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.objectLiteral`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitObjectLiteral?: (ctx: ObjectLiteralContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.arrayLiteral`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterArrayLiteral?: (ctx: ArrayLiteralContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.arrayLiteral`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitArrayLiteral?: (ctx: ArrayLiteralContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.elementList`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterElementList?: (ctx: ElementListContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.elementList`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitElementList?: (ctx: ElementListContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.propertyNameAndValueList`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterPropertyNameAndValueList?: (ctx: PropertyNameAndValueListContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.propertyNameAndValueList`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitPropertyNameAndValueList?: (ctx: PropertyNameAndValueListContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.propertyAssignment`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterPropertyAssignment?: (ctx: PropertyAssignmentContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.propertyAssignment`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitPropertyAssignment?: (ctx: PropertyAssignmentContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.propertyValue`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterPropertyValue?: (ctx: PropertyValueContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.propertyValue`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitPropertyValue?: (ctx: PropertyValueContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.literal`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterLiteral?: (ctx: LiteralContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.literal`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitLiteral?: (ctx: LiteralContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.propertyName`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterPropertyName?: (ctx: PropertyNameContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.propertyName`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitPropertyName?: (ctx: PropertyNameContext) => void;
|
||||
|
||||
/**
|
||||
* Enter a parse tree produced by `mongoParser.comment`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterComment?: (ctx: CommentContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `mongoParser.comment`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitComment?: (ctx: CommentContext) => void;
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
// Generated from ./grammar/mongo.g4 by ANTLR 4.6-SNAPSHOT
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*tslint:disable */
|
||||
|
||||
|
||||
import { ParseTreeVisitor } from 'antlr4ts/tree/ParseTreeVisitor';
|
||||
|
||||
import { MongoCommandsContext } from './mongoParser';
|
||||
import { CommandsContext } from './mongoParser';
|
||||
import { CommandContext } from './mongoParser';
|
||||
import { EmptyCommandContext } from './mongoParser';
|
||||
import { CollectionContext } from './mongoParser';
|
||||
import { FunctionCallContext } from './mongoParser';
|
||||
import { ArgumentsContext } from './mongoParser';
|
||||
import { ArgumentContext } from './mongoParser';
|
||||
import { ObjectLiteralContext } from './mongoParser';
|
||||
import { ArrayLiteralContext } from './mongoParser';
|
||||
import { ElementListContext } from './mongoParser';
|
||||
import { PropertyNameAndValueListContext } from './mongoParser';
|
||||
import { PropertyAssignmentContext } from './mongoParser';
|
||||
import { PropertyValueContext } from './mongoParser';
|
||||
import { LiteralContext } from './mongoParser';
|
||||
import { PropertyNameContext } from './mongoParser';
|
||||
import { CommentContext } from './mongoParser';
|
||||
|
||||
|
||||
/**
|
||||
* This interface defines a complete generic visitor for a parse tree produced
|
||||
* by `mongoParser`.
|
||||
*
|
||||
* @param <Result> The return type of the visit operation. Use `void` for
|
||||
* operations with no return type.
|
||||
*/
|
||||
export interface mongoVisitor<Result> extends ParseTreeVisitor<Result> {
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.mongoCommands`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitMongoCommands?: (ctx: MongoCommandsContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.commands`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitCommands?: (ctx: CommandsContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.command`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitCommand?: (ctx: CommandContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.emptyCommand`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitEmptyCommand?: (ctx: EmptyCommandContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.collection`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitCollection?: (ctx: CollectionContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.functionCall`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitFunctionCall?: (ctx: FunctionCallContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.arguments`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitArguments?: (ctx: ArgumentsContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.argument`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitArgument?: (ctx: ArgumentContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.objectLiteral`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitObjectLiteral?: (ctx: ObjectLiteralContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.arrayLiteral`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitArrayLiteral?: (ctx: ArrayLiteralContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.elementList`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitElementList?: (ctx: ElementListContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.propertyNameAndValueList`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitPropertyNameAndValueList?: (ctx: PropertyNameAndValueListContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.propertyAssignment`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitPropertyAssignment?: (ctx: PropertyAssignmentContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.propertyValue`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitPropertyValue?: (ctx: PropertyValueContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.literal`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitLiteral?: (ctx: LiteralContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.propertyName`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitPropertyName?: (ctx: PropertyNameContext) => Result;
|
||||
|
||||
/**
|
||||
* Visit a parse tree produced by `mongoParser.comment`.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
visitComment?: (ctx: CommentContext) => Result;
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ParseTree } from 'antlr4ts/tree/ParseTree';
|
||||
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';
|
||||
import { ErrorNode } from 'antlr4ts/tree/ErrorNode';
|
||||
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
|
||||
import { CommandsContext, CommandContext, FunctionCallContext, MongoCommandsContext, CollectionContext, ArgumentContext, ArgumentsContext } from './mongoParser';
|
||||
import { mongoVisitor } from './mongoVisitor';
|
||||
|
||||
export class MongoVisitor<T> implements mongoVisitor<T> {
|
||||
|
||||
visitMongoCommands(ctx: MongoCommandsContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitCommands(ctx: CommandsContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitCommand(ctx: CommandContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitCollection(ctx: CollectionContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitFunctionCall(ctx: FunctionCallContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitArgument(ctx: ArgumentContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visitArguments(ctx: ArgumentsContext): T {
|
||||
return this.visitChildren(ctx);
|
||||
}
|
||||
|
||||
visit(tree: ParseTree): T {
|
||||
return tree.accept(this);
|
||||
}
|
||||
|
||||
visitChildren(ctx: ParserRuleContext): T {
|
||||
var result = this.defaultResult(ctx);
|
||||
var n = ctx.childCount
|
||||
for (var i = 0; i < n; i++) {
|
||||
if (!this.shouldVisitNextChild(ctx, result)) {
|
||||
break;
|
||||
}
|
||||
|
||||
var childNode = ctx.getChild(i);
|
||||
var childResult = childNode.accept(this);
|
||||
result = this.aggregateResult(result, childResult);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
visitTerminal(node: TerminalNode): T {
|
||||
return this.defaultResult(node);
|
||||
}
|
||||
|
||||
visitErrorNode(node: ErrorNode): T {
|
||||
return this.defaultResult(node);
|
||||
}
|
||||
|
||||
protected defaultResult(_node: ParseTree): T {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected aggregateResult(aggregate: T, nextResult: T): T {
|
||||
return !nextResult ? aggregate : nextResult;
|
||||
}
|
||||
|
||||
shouldVisitNextChild(_node, _currentResult: T): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import { appendExtensionUserAgent } from 'vscode-azureextensionui';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ext } from '../extensionVariables';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
// tslint:disable-next-line: export-name
|
||||
export class MongoDBLanguageClient {
|
||||
|
||||
public client: LanguageClient;
|
||||
|
||||
constructor() {
|
||||
// The server is implemented in node
|
||||
const serverModule = ext.ignoreBundle ?
|
||||
ext.context.asAbsolutePath(path.join('out', 'src', 'mongo', 'languageServer.js')) :
|
||||
ext.context.asAbsolutePath(path.join('dist', 'mongo-languageServer.bundle.js'));
|
||||
// The debug options for the server
|
||||
const debugOptions = { execArgv: ['--nolazy', '--inspect=6005'] };
|
||||
|
||||
// If the extension is launch in debug mode the debug server options are use
|
||||
// Otherwise the run options are used
|
||||
const serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
// Register the server for mongo javascript documents
|
||||
documentSelector: [
|
||||
{ language: 'mongo', scheme: 'file' },
|
||||
{ language: 'mongo', scheme: 'untitled' }
|
||||
]
|
||||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
this.client = new LanguageClient('mongo', localize('mongo.server.name', 'Mongo Language Server'), serverOptions, clientOptions);
|
||||
const disposable = this.client.start();
|
||||
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
ext.context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
public async connect(connectionString: string, databaseName: string): Promise<void> {
|
||||
await this.client.sendRequest('connect', <IConnectionParams>{ connectionString: connectionString, databaseName: databaseName, extensionUserAgent: appendExtensionUserAgent() });
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this.client.sendRequest('disconnect');
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { createConnection, IConnection } from 'vscode-languageserver';
|
||||
import { LanguageService } from './services/languageService';
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
// HOW TO DEBUG THE LANGUAGE SERVER
|
||||
//
|
||||
//
|
||||
// 1. Start the extension via F5
|
||||
// 2. Under vscode Debug pane, switch to "Attach to Language Server"
|
||||
// 3. F5
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// Create a connection for the server
|
||||
const connection: IConnection = createConnection();
|
||||
console.log = connection.console.log.bind(connection.console);
|
||||
console.error = connection.console.error.bind(connection.console);
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
new LanguageService(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
|
@ -1,83 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MongoClient, Mongos, ReplSet, Server } from "mongodb";
|
||||
import { appendExtensionUserAgent } from "vscode-azureextensionui";
|
||||
import { testDb } from "../constants";
|
||||
import { ParsedConnectionString } from "../ParsedConnectionString";
|
||||
import { connectToMongoClient } from "./connectToMongoClient";
|
||||
|
||||
// Connection strings follow the following format (https://docs.mongodb.com/manual/reference/connection-string/):
|
||||
// mongodb[+srv]://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
|
||||
// Some example connection strings:
|
||||
// mongodb://dbuser:dbpassword@dbname.mlab.com:14118
|
||||
// mongodb+srv://db1.example.net:27017,db2.example.net:2500/?replicaSet=test
|
||||
// mongodb://router1.example.com:27017,router2.example2.com:27017,router3.example3.com:27017/database?ssh=true
|
||||
// Regex splits into three parts:
|
||||
// Full match
|
||||
// mongodb[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]]
|
||||
// [database]
|
||||
|
||||
const parsePrefix = '([a-zA-Z]+:\/\/[^\/]*)';
|
||||
const parseDatabaseName = '\/?([^/?]+)?';
|
||||
const mongoConnectionStringRegExp = new RegExp(parsePrefix + parseDatabaseName);
|
||||
|
||||
export function getDatabaseNameFromConnectionString(connectionString: string): string | undefined {
|
||||
try {
|
||||
const [, , databaseName] = connectionString.match(mongoConnectionStringRegExp);
|
||||
return databaseName;
|
||||
} catch (error) {
|
||||
// Shouldn't happen, but ignore if does
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function addDatabaseToAccountConnectionString(connectionString: string, databaseName: string): string | undefined {
|
||||
try {
|
||||
return connectionString.replace(mongoConnectionStringRegExp, `$1\/${databaseName}`);
|
||||
} catch (error) {
|
||||
// Shouldn't happen, but ignore if does
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function parseMongoConnectionString(connectionString: string): Promise<ParsedMongoConnectionString> {
|
||||
let host: string;
|
||||
let port: string;
|
||||
|
||||
const mongoClient: MongoClient = await connectToMongoClient(connectionString, appendExtensionUserAgent());
|
||||
const serverConfig: Server | ReplSet | Mongos = mongoClient.db(testDb).serverConfig;
|
||||
// Azure CosmosDB comes back as a ReplSet
|
||||
if (serverConfig instanceof ReplSet) {
|
||||
// get the first connection string from the servers for the ReplSet
|
||||
// this may not be best solution, but the connection (below) gives
|
||||
// the replicaset host name, which is different than what is in the connection string
|
||||
// "s" is not part of ReplSet static definition but can't find any official documentation on it. Yet it is definitely there at runtime. Grandfathering in.
|
||||
// tslint:disable-next-line:no-any
|
||||
const rs: any = serverConfig;
|
||||
host = rs.s.options.servers[0].host;
|
||||
port = rs.s.options.servers[0].port;
|
||||
} else {
|
||||
// tslint:disable-next-line: no-any
|
||||
host = (<any>serverConfig).host;
|
||||
// tslint:disable-next-line: no-any
|
||||
port = (<any>serverConfig).port;
|
||||
}
|
||||
|
||||
return new ParsedMongoConnectionString(connectionString, host, port, getDatabaseNameFromConnectionString(connectionString));
|
||||
}
|
||||
|
||||
export class ParsedMongoConnectionString extends ParsedConnectionString {
|
||||
public readonly hostName: string;
|
||||
public readonly port: string;
|
||||
|
||||
constructor(connectionString: string, hostName: string, port: string, databaseName: string | undefined) {
|
||||
super(connectionString, databaseName);
|
||||
this.hostName = hostName;
|
||||
this.port = port;
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureTreeItem, callWithTelemetryAndErrorHandling, IActionContext, registerCommand, registerEvent } from "vscode-azureextensionui";
|
||||
import { CosmosEditorManager } from "../CosmosEditorManager";
|
||||
import { ext } from "../extensionVariables";
|
||||
import { AttachedAccountSuffix } from '../tree/AttachedAccountsTreeItem';
|
||||
import * as vscodeUtil from '../utils/vscodeUtils';
|
||||
import { MongoCollectionNodeEditor } from "./editors/MongoCollectionNodeEditor";
|
||||
import { MongoDBLanguageClient } from "./languageClient";
|
||||
import { executeAllCommandsFromActiveEditor, executeCommandFromActiveEditor, executeCommandFromText, getAllErrorsFromTextDocument } from "./MongoScrapbook";
|
||||
import { MongoCodeLensProvider } from "./services/MongoCodeLensProvider";
|
||||
import { setConnectedNode } from "./setConnectedNode";
|
||||
import { MongoAccountTreeItem } from "./tree/MongoAccountTreeItem";
|
||||
import { MongoCollectionTreeItem } from "./tree/MongoCollectionTreeItem";
|
||||
import { MongoDatabaseTreeItem } from "./tree/MongoDatabaseTreeItem";
|
||||
import { MongoDocumentTreeItem } from "./tree/MongoDocumentTreeItem";
|
||||
|
||||
const connectedDBKey: string = 'ms-azuretools.vscode-cosmosdb.connectedDB';
|
||||
let diagnosticsCollection: vscode.DiagnosticCollection;
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
export function registerMongoCommands(editorManager: CosmosEditorManager): MongoCodeLensProvider {
|
||||
const languageClient: MongoDBLanguageClient = new MongoDBLanguageClient();
|
||||
|
||||
const codeLensProvider = new MongoCodeLensProvider();
|
||||
ext.context.subscriptions.push(vscode.languages.registerCodeLensProvider('mongo', codeLensProvider));
|
||||
|
||||
diagnosticsCollection = vscode.languages.createDiagnosticCollection('cosmosDB.mongo');
|
||||
ext.context.subscriptions.push(diagnosticsCollection);
|
||||
|
||||
setUpErrorReporting();
|
||||
|
||||
const loadPersistedMongoDBTask: Promise<void> = loadPersistedMongoDB(languageClient, codeLensProvider);
|
||||
|
||||
registerCommand('cosmosDB.createMongoDatabase', async (context: IActionContext, node?: MongoAccountTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoAccountTreeItem>await ext.tree.showTreeItemPicker([MongoAccountTreeItem.contextValue, MongoAccountTreeItem.contextValue + AttachedAccountSuffix], context);
|
||||
}
|
||||
const databaseNode = <MongoDatabaseTreeItem>await node.createChild(context);
|
||||
// reveal the database treeItem in case user cancels collection creation
|
||||
await ext.treeView.reveal(databaseNode, { focus: false });
|
||||
const collectionNode = <MongoCollectionTreeItem>await databaseNode.createChild(context);
|
||||
await ext.treeView.reveal(collectionNode, { focus: true });
|
||||
|
||||
await vscode.commands.executeCommand('cosmosDB.connectMongoDB', databaseNode);
|
||||
});
|
||||
registerCommand('cosmosDB.createMongoCollection', async (context: IActionContext, node?: MongoDatabaseTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoDatabaseTreeItem>await ext.tree.showTreeItemPicker(MongoDatabaseTreeItem.contextValue, context);
|
||||
}
|
||||
const collectionNode = await node.createChild(context);
|
||||
await ext.treeView.reveal(collectionNode);
|
||||
await vscode.commands.executeCommand('cosmosDB.connectMongoDB', collectionNode.parent);
|
||||
});
|
||||
registerCommand('cosmosDB.createMongoDocument', async (context: IActionContext, node?: MongoCollectionTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoCollectionTreeItem>await ext.tree.showTreeItemPicker(MongoCollectionTreeItem.contextValue, context);
|
||||
}
|
||||
const documentNode = await node.createChild(context);
|
||||
await ext.treeView.reveal(documentNode);
|
||||
await vscode.commands.executeCommand("cosmosDB.openDocument", documentNode);
|
||||
});
|
||||
registerCommand('cosmosDB.connectMongoDB', async (context: IActionContext, node?: MongoDatabaseTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoDatabaseTreeItem>await ext.tree.showTreeItemPicker(MongoDatabaseTreeItem.contextValue, context);
|
||||
}
|
||||
|
||||
const oldNodeId: string | undefined = ext.connectedMongoDB && ext.connectedMongoDB.fullId;
|
||||
await languageClient.connect(node.connectionString, node.databaseName);
|
||||
ext.context.globalState.update(connectedDBKey, node.fullId);
|
||||
setConnectedNode(node, codeLensProvider);
|
||||
await node.refresh();
|
||||
|
||||
if (oldNodeId) {
|
||||
// We have to use findTreeItem to get the instance of the old node that's being displayed in the ext.tree. Our specific instance might have been out-of-date
|
||||
const oldNode: AzureTreeItem | undefined = await ext.tree.findTreeItem(oldNodeId, context);
|
||||
if (oldNode) {
|
||||
await oldNode.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
registerCommand('cosmosDB.deleteMongoDB', async (context: IActionContext, node?: MongoDatabaseTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoDatabaseTreeItem>await ext.tree.showTreeItemPicker(MongoDatabaseTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
if (ext.connectedMongoDB && ext.connectedMongoDB.fullId === node.fullId) {
|
||||
setConnectedNode(undefined, codeLensProvider);
|
||||
ext.context.globalState.update(connectedDBKey, undefined);
|
||||
languageClient.disconnect();
|
||||
}
|
||||
});
|
||||
registerCommand('cosmosDB.deleteMongoCollection', async (context: IActionContext, node?: MongoCollectionTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoCollectionTreeItem>await ext.tree.showTreeItemPicker(MongoCollectionTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
registerCommand('cosmosDB.deleteMongoDocument', async (context: IActionContext, node?: MongoDocumentTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoDocumentTreeItem>await ext.tree.showTreeItemPicker(MongoDocumentTreeItem.contextValue, context);
|
||||
}
|
||||
await node.deleteTreeItem(context);
|
||||
});
|
||||
registerCommand('cosmosDB.openCollection', async (context: IActionContext, node?: MongoCollectionTreeItem) => {
|
||||
if (!node) {
|
||||
node = <MongoCollectionTreeItem>await ext.tree.showTreeItemPicker(MongoCollectionTreeItem.contextValue, context);
|
||||
}
|
||||
await editorManager.showDocument(context, new MongoCollectionNodeEditor(node), node.label + '-cosmos-collection.json');
|
||||
});
|
||||
registerCommand('cosmosDB.launchMongoShell', launchMongoShell);
|
||||
registerCommand('cosmosDB.newMongoScrapbook', async () => await vscodeUtil.showNewFile('', 'Scrapbook', '.mongo'));
|
||||
registerCommand('cosmosDB.executeMongoCommand', async (context: IActionContext, commandText: object) => {
|
||||
await loadPersistedMongoDBTask;
|
||||
if (typeof commandText === "string") {
|
||||
await executeCommandFromText(ext.connectedMongoDB, editorManager, context, <string>commandText);
|
||||
} else {
|
||||
await executeCommandFromActiveEditor(ext.connectedMongoDB, editorManager, context);
|
||||
}
|
||||
});
|
||||
registerCommand('cosmosDB.executeAllMongoCommands', async (context: IActionContext) => {
|
||||
await loadPersistedMongoDBTask;
|
||||
await executeAllCommandsFromActiveEditor(ext.connectedMongoDB, editorManager, context);
|
||||
});
|
||||
|
||||
return codeLensProvider;
|
||||
}
|
||||
|
||||
async function loadPersistedMongoDB(languageClient: MongoDBLanguageClient, codeLensProvider: MongoCodeLensProvider): Promise<void> {
|
||||
// NOTE: We want to make sure this function never throws or returns a rejected promise because it gets awaited multiple times
|
||||
await callWithTelemetryAndErrorHandling('cosmosDB.loadPersistedMongoDB', async (context: IActionContext) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
try {
|
||||
const persistedNodeId: string | undefined = ext.context.globalState.get(connectedDBKey);
|
||||
if (persistedNodeId) {
|
||||
const persistedNode = await ext.tree.findTreeItem(persistedNodeId, context);
|
||||
if (persistedNode) {
|
||||
await languageClient.client.onReady();
|
||||
await vscode.commands.executeCommand('cosmosDB.connectMongoDB', persistedNode);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Get code lens provider out of initializing state if there's no connected DB
|
||||
if (!ext.connectedMongoDB) {
|
||||
codeLensProvider.setConnectedDatabase(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function launchMongoShell(): void {
|
||||
const terminal: vscode.Terminal = vscode.window.createTerminal('Mongo Shell');
|
||||
terminal.sendText(`mongo`);
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
function setUpErrorReporting(): void {
|
||||
// Update errors immediately in case a scrapbook is already open
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
callWithTelemetryAndErrorHandling(
|
||||
"initialUpdateErrorsInActiveDocument",
|
||||
async (context: IActionContext) => {
|
||||
updateErrorsInScrapbook(context, vscode.window.activeTextEditor && vscode.window.activeTextEditor.document);
|
||||
});
|
||||
|
||||
// Update errors when document opened/changed
|
||||
registerEvent('vscode.workspace.onDidOpenTextDocument', vscode.workspace.onDidOpenTextDocument, updateErrorsInScrapbook);
|
||||
registerEvent(
|
||||
'vscode.workspace.onDidChangeTextDocument',
|
||||
vscode.workspace.onDidChangeTextDocument,
|
||||
async (context: IActionContext, event: vscode.TextDocumentChangeEvent) => {
|
||||
// Always suppress success telemetry - event happens on every keystroke
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
|
||||
updateErrorsInScrapbook(context, event.document);
|
||||
});
|
||||
registerEvent(
|
||||
'vscode.workspace.onDidCloseTextDocument',
|
||||
vscode.workspace.onDidCloseTextDocument,
|
||||
async (context: IActionContext, document: vscode.TextDocument) => {
|
||||
// Remove errors when closed
|
||||
if (isScrapbook(document)) {
|
||||
diagnosticsCollection.set(document.uri, []);
|
||||
} else {
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isScrapbook(document: vscode.TextDocument): boolean {
|
||||
return document && document.languageId === 'mongo';
|
||||
}
|
||||
|
||||
function updateErrorsInScrapbook(context: IActionContext, document: vscode.TextDocument): void {
|
||||
if (isScrapbook(document)) {
|
||||
const errors = getAllErrorsFromTextDocument(document);
|
||||
diagnosticsCollection.set(document.uri, errors);
|
||||
} else {
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
interface IConnectionParams {
|
||||
connectionString: string;
|
||||
databaseName: string;
|
||||
extensionUserAgent: string;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import { callWithTelemetryAndErrorHandling, IActionContext } from "vscode-azureextensionui";
|
||||
import { getAllCommandsFromTextDocument } from "../MongoScrapbook";
|
||||
|
||||
export class MongoCodeLensProvider implements vscode.CodeLensProvider {
|
||||
private _onDidChangeEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
private _connectedDatabase: string;
|
||||
private _connectedDatabaseInitialized: boolean;
|
||||
|
||||
public get onDidChangeCodeLenses(): vscode.Event<void> {
|
||||
return this._onDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
public setConnectedDatabase(database: string | undefined): void {
|
||||
this._connectedDatabase = database;
|
||||
this._connectedDatabaseInitialized = true;
|
||||
this._onDidChangeEmitter.fire();
|
||||
}
|
||||
|
||||
public provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
|
||||
return callWithTelemetryAndErrorHandling("mongo.provideCodeLenses", (context: IActionContext) => {
|
||||
// Suppress except for errors - this can fire on every keystroke
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
|
||||
const isInitialized = this._connectedDatabaseInitialized;
|
||||
const isConnected = !!this._connectedDatabase;
|
||||
const database = isConnected && this._connectedDatabase;
|
||||
const lenses: vscode.CodeLens[] = [];
|
||||
|
||||
// Allow displaying and changing connected database
|
||||
lenses.push(<vscode.CodeLens>{
|
||||
command: {
|
||||
title: !isInitialized ?
|
||||
'Initializing...' :
|
||||
isConnected ?
|
||||
`Connected to ${database}` :
|
||||
`Connect to a database`,
|
||||
command: isInitialized && 'cosmosDB.connectMongoDB'
|
||||
},
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))
|
||||
});
|
||||
|
||||
if (isConnected) {
|
||||
// Run all
|
||||
lenses.push(<vscode.CodeLens>{
|
||||
command: {
|
||||
title: "Execute All",
|
||||
command: 'cosmosDB.executeAllMongoCommands'
|
||||
},
|
||||
range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))
|
||||
});
|
||||
|
||||
const commands = getAllCommandsFromTextDocument(document);
|
||||
for (const cmd of commands) {
|
||||
// run individual
|
||||
lenses.push(<vscode.CodeLens>{
|
||||
command: {
|
||||
title: "Execute",
|
||||
command: 'cosmosDB.executeMongoCommand',
|
||||
arguments: [cmd.text]
|
||||
},
|
||||
range: cmd.range
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return lenses;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,436 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
|
||||
import { ErrorNode } from 'antlr4ts/tree/ErrorNode';
|
||||
import { ParseTree } from 'antlr4ts/tree/ParseTree';
|
||||
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';
|
||||
import { Db } from 'mongodb';
|
||||
import { LanguageService as JsonLanguageService } from 'vscode-json-languageservice';
|
||||
import { CompletionItem, CompletionItemKind, Position, Range, TextDocument } from 'vscode-languageserver';
|
||||
import { mongoLexer } from './../grammar/mongoLexer';
|
||||
import * as mongoParser from './../grammar/mongoParser';
|
||||
import { MongoVisitor } from './../grammar/visitors';
|
||||
import { SchemaService } from './schemaService';
|
||||
|
||||
// tslint:disable-next-line: export-name
|
||||
export class CompletionItemsVisitor extends MongoVisitor<Promise<CompletionItem[]>> {
|
||||
private at: Position;
|
||||
|
||||
constructor(
|
||||
private textDocument: TextDocument,
|
||||
private db: Db,
|
||||
private offset: number,
|
||||
private schemaService: SchemaService,
|
||||
private jsonLanguageService: JsonLanguageService
|
||||
) {
|
||||
super();
|
||||
this.at = this.textDocument.positionAt(this.offset);
|
||||
}
|
||||
|
||||
public visitCommands(ctx: mongoParser.CommandsContext): Promise<CompletionItem[]> {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRange(ctx)));
|
||||
}
|
||||
|
||||
public visitEmptyCommand(ctx: mongoParser.EmptyCommandContext): Promise<CompletionItem[]> {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRangeAfter(ctx)));
|
||||
}
|
||||
|
||||
public visitCommand(ctx: mongoParser.CommandContext): Promise<CompletionItem[]> {
|
||||
if (ctx.childCount === 0) {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRange(ctx)));
|
||||
}
|
||||
|
||||
const lastTerminalNode = this.getLastTerminalNode(ctx);
|
||||
if (lastTerminalNode) {
|
||||
return this.getCompletionItemsFromTerminalNode(lastTerminalNode);
|
||||
}
|
||||
return this.thenable();
|
||||
}
|
||||
|
||||
public visitCollection(ctx: mongoParser.CollectionContext): Promise<CompletionItem[]> {
|
||||
return Promise.all([this.createCollectionCompletions(this.createRange(ctx)), this.createDbFunctionCompletions(this.createRange(ctx))])
|
||||
.then(([collectionCompletions, dbFunctionCompletions]) => [...collectionCompletions, ...dbFunctionCompletions]);
|
||||
}
|
||||
|
||||
public visitFunctionCall(ctx: mongoParser.FunctionCallContext): Promise<CompletionItem[]> {
|
||||
const previousNode = this.getPreviousNode(ctx);
|
||||
if (previousNode instanceof TerminalNode) {
|
||||
return this.getCompletionItemsFromTerminalNode(previousNode);
|
||||
}
|
||||
return this.thenable();
|
||||
}
|
||||
|
||||
public visitArguments(ctx: mongoParser.ArgumentsContext): Promise<CompletionItem[]> {
|
||||
const terminalNode = this.getLastTerminalNode(ctx);
|
||||
if (terminalNode && terminalNode.symbol === ctx._CLOSED_PARENTHESIS) {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRangeAfter(terminalNode)));
|
||||
}
|
||||
return this.thenable();
|
||||
}
|
||||
|
||||
public visitArgument(ctx: mongoParser.ArgumentContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitObjectLiteral(ctx: mongoParser.ObjectLiteralContext): Thenable<CompletionItem[]> {
|
||||
const functionName = this.getFunctionName(ctx);
|
||||
const collectionName = this.getCollectionName(ctx);
|
||||
if (collectionName && functionName) {
|
||||
if (['find', 'findOne', 'findOneAndDelete', 'findOneAndUpdate', 'findOneAndReplace', 'deleteOne', 'deleteMany', 'remove'].indexOf(functionName) !== -1) {
|
||||
return this.getArgumentCompletionItems(this.schemaService.queryDocumentUri(collectionName), collectionName, ctx);
|
||||
}
|
||||
}
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitArrayLiteral(ctx: mongoParser.ArrayLiteralContext): Thenable<CompletionItem[]> {
|
||||
const functionName = this.getFunctionName(ctx);
|
||||
const collectionName = this.getCollectionName(ctx);
|
||||
if (collectionName && functionName) {
|
||||
if (['aggregate'].indexOf(functionName) !== -1) {
|
||||
return this.getArgumentCompletionItems(this.schemaService.aggregateDocumentUri(collectionName), collectionName, ctx);
|
||||
}
|
||||
}
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitElementList(ctx: mongoParser.ElementListContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitPropertyNameAndValueList(ctx: mongoParser.PropertyNameAndValueListContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitPropertyAssignment(ctx: mongoParser.PropertyAssignmentContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitPropertyValue(ctx: mongoParser.PropertyValueContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitPropertyName(ctx: mongoParser.PropertyNameContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitLiteral(ctx: mongoParser.LiteralContext): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitTerminal(ctx: TerminalNode): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
public visitErrorNode(ctx: ErrorNode): Promise<CompletionItem[]> {
|
||||
return ctx.parent.accept(this);
|
||||
}
|
||||
|
||||
private getArgumentCompletionItems(documentUri: string, _collectionName: string, ctx: ParserRuleContext): Thenable<CompletionItem[]> {
|
||||
const text = this.textDocument.getText();
|
||||
const document = TextDocument.create(documentUri, 'json', 1, text.substring(ctx.start.startIndex, ctx.stop.stopIndex + 1));
|
||||
const positionOffset = this.textDocument.offsetAt(this.at);
|
||||
const contextOffset = ctx.start.startIndex;
|
||||
const position = document.positionAt(positionOffset - contextOffset);
|
||||
return this.jsonLanguageService.doComplete(document, position, this.jsonLanguageService.parseJSONDocument(document))
|
||||
.then(list => {
|
||||
return list.items.map(item => {
|
||||
const startPositionOffset = document.offsetAt(item.textEdit.range.start);
|
||||
const endPositionOffset = document.offsetAt(item.textEdit.range.end);
|
||||
item.textEdit.range = Range.create(this.textDocument.positionAt(startPositionOffset + contextOffset), this.textDocument.positionAt(contextOffset + endPositionOffset));
|
||||
return item;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getFunctionName(ctx: ParseTree): string {
|
||||
let parent = ctx.parent;
|
||||
if (!(parent && parent instanceof mongoParser.ArgumentContext)) {
|
||||
return null;
|
||||
}
|
||||
parent = parent.parent;
|
||||
if (!(parent && parent instanceof mongoParser.ArgumentsContext)) {
|
||||
return null;
|
||||
}
|
||||
parent = parent.parent;
|
||||
if (!(parent && parent instanceof mongoParser.FunctionCallContext)) {
|
||||
return null;
|
||||
}
|
||||
return (<mongoParser.FunctionCallContext>parent)._FUNCTION_NAME.text;
|
||||
}
|
||||
|
||||
private getCollectionName(ctx: ParseTree): string {
|
||||
let parent = ctx.parent;
|
||||
if (!(parent && parent instanceof mongoParser.ArgumentContext)) {
|
||||
return null;
|
||||
}
|
||||
parent = parent.parent;
|
||||
if (!(parent && parent instanceof mongoParser.ArgumentsContext)) {
|
||||
return null;
|
||||
}
|
||||
parent = parent.parent;
|
||||
if (!(parent && parent instanceof mongoParser.FunctionCallContext)) {
|
||||
return null;
|
||||
}
|
||||
let previousNode = this.getPreviousNode(parent);
|
||||
if (previousNode && previousNode instanceof TerminalNode && previousNode.symbol.type === mongoLexer.DOT) {
|
||||
previousNode = this.getPreviousNode(previousNode);
|
||||
if (previousNode && previousNode instanceof mongoParser.CollectionContext) {
|
||||
return previousNode.text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getCompletionItemsFromTerminalNode(node: TerminalNode): Promise<CompletionItem[]> {
|
||||
if (node._symbol.type === mongoParser.mongoParser.DB) {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRange(node)));
|
||||
}
|
||||
if (node._symbol.type === mongoParser.mongoParser.SEMICOLON) {
|
||||
return this.thenable(this.createDbKeywordCompletion(this.createRangeAfter(node)));
|
||||
}
|
||||
if (node._symbol.type === mongoParser.mongoParser.DOT) {
|
||||
const previousNode = this.getPreviousNode(node);
|
||||
if (previousNode && previousNode instanceof TerminalNode) {
|
||||
if (previousNode._symbol.type === mongoParser.mongoParser.DB) {
|
||||
return Promise.all([this.createCollectionCompletions(this.createRangeAfter(node)), this.createDbFunctionCompletions(this.createRangeAfter(node))])
|
||||
.then(([collectionCompletions, dbFunctionCompletions]) => [...collectionCompletions, ...dbFunctionCompletions]);
|
||||
}
|
||||
}
|
||||
if (previousNode instanceof mongoParser.CollectionContext) {
|
||||
return this.createCollectionFunctionsCompletions(this.createRangeAfter(node));
|
||||
}
|
||||
}
|
||||
if (node instanceof ErrorNode) {
|
||||
const previousNode = this.getPreviousNode(node);
|
||||
if (previousNode) {
|
||||
if (previousNode instanceof TerminalNode) {
|
||||
return this.getCompletionItemsFromTerminalNode(previousNode);
|
||||
}
|
||||
return previousNode.accept(this);
|
||||
}
|
||||
}
|
||||
return this.thenable();
|
||||
}
|
||||
|
||||
private getLastTerminalNode(ctx: ParserRuleContext): TerminalNode {
|
||||
return ctx.children ? <TerminalNode>ctx.children.slice().reverse().filter(node => node instanceof TerminalNode && node.symbol.stopIndex > -1 && node.symbol.stopIndex < this.offset)[0] : null;
|
||||
}
|
||||
|
||||
private getPreviousNode(node: ParseTree): ParseTree {
|
||||
let previousNode = null;
|
||||
const parentNode = node.parent;
|
||||
for (let i = 0; i < parentNode.childCount; i++) {
|
||||
const currentNode = parentNode.getChild(i);
|
||||
if (currentNode === node) {
|
||||
break;
|
||||
}
|
||||
previousNode = currentNode;
|
||||
}
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
private createDbKeywordCompletion(range: Range): CompletionItem {
|
||||
return {
|
||||
textEdit: {
|
||||
newText: 'db',
|
||||
range
|
||||
},
|
||||
kind: CompletionItemKind.Keyword,
|
||||
label: 'db'
|
||||
};
|
||||
}
|
||||
|
||||
private createDbFunctionCompletions(range: Range): Promise<CompletionItem[]> {
|
||||
return this.thenable(
|
||||
this.createFunctionCompletion('adminCommand', range),
|
||||
this.createFunctionCompletion('auth', range),
|
||||
this.createFunctionCompletion('cloneDatabase', range),
|
||||
this.createFunctionCompletion('commandHelp', range),
|
||||
this.createFunctionCompletion('copyDatabase', range),
|
||||
this.createFunctionCompletion('createCollection', range),
|
||||
this.createFunctionCompletion('createView', range),
|
||||
this.createFunctionCompletion('createUser', range),
|
||||
this.createFunctionCompletion('currentOp', range),
|
||||
this.createFunctionCompletion('dropDatabase', range),
|
||||
this.createFunctionCompletion('eval', range),
|
||||
this.createFunctionCompletion('fsyncLock', range),
|
||||
this.createFunctionCompletion('fsyncUnLock', range),
|
||||
this.createFunctionCompletion('getCollection', range),
|
||||
this.createFunctionCompletion('getCollectionInfos', range),
|
||||
this.createFunctionCompletion('getCollectionNames', range),
|
||||
this.createFunctionCompletion('getLastError', range),
|
||||
this.createFunctionCompletion('getLastErrorObj', range),
|
||||
this.createFunctionCompletion('getLogComponents', range),
|
||||
this.createFunctionCompletion('getMongo', range),
|
||||
this.createFunctionCompletion('getName', range),
|
||||
this.createFunctionCompletion('getPrevError', range),
|
||||
this.createFunctionCompletion('getProfilingLevel', range),
|
||||
this.createFunctionCompletion('getProfilingStatus', range),
|
||||
this.createFunctionCompletion('getReplicationInfo', range),
|
||||
this.createFunctionCompletion('getSiblingDB', range),
|
||||
this.createFunctionCompletion('getWriteConcern', range),
|
||||
this.createFunctionCompletion('hostInfo', range),
|
||||
this.createFunctionCompletion('isMaster', range),
|
||||
this.createFunctionCompletion('killOp', range),
|
||||
this.createFunctionCompletion('listCommands', range),
|
||||
this.createFunctionCompletion('loadServerScripts', range),
|
||||
this.createFunctionCompletion('logout', range),
|
||||
this.createFunctionCompletion('printCollectionStats', range),
|
||||
this.createFunctionCompletion('printReplicationInfo', range),
|
||||
this.createFunctionCompletion('printShardingStatus', range),
|
||||
this.createFunctionCompletion('printSlaveReplicationInfo', range),
|
||||
this.createFunctionCompletion('dropUser', range),
|
||||
this.createFunctionCompletion('repairDatabase', range),
|
||||
this.createFunctionCompletion('runCommand', range),
|
||||
this.createFunctionCompletion('serverStatus', range),
|
||||
this.createFunctionCompletion('setLogLevel', range),
|
||||
this.createFunctionCompletion('setProfilingLevel', range),
|
||||
this.createFunctionCompletion('setWriteConcern', range),
|
||||
this.createFunctionCompletion('unsetWriteConcern', range),
|
||||
this.createFunctionCompletion('setVerboseShell', range),
|
||||
this.createFunctionCompletion('shotdownServer', range),
|
||||
this.createFunctionCompletion('stats', range),
|
||||
this.createFunctionCompletion('version', range)
|
||||
);
|
||||
}
|
||||
|
||||
private createCollectionCompletions(range: Range): Promise<CompletionItem[]> {
|
||||
if (this.db) {
|
||||
return <Promise<CompletionItem[]>>this.db.collections().then(collections => {
|
||||
return collections.map(collection => (<CompletionItem>{
|
||||
textEdit: {
|
||||
newText: collection.collectionName,
|
||||
range
|
||||
},
|
||||
label: collection.collectionName,
|
||||
kind: CompletionItemKind.Property,
|
||||
filterText: collection.collectionName,
|
||||
sortText: `1:${collection.collectionName}`
|
||||
}));
|
||||
});
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
private createCollectionFunctionsCompletions(range: Range): Promise<CompletionItem[]> {
|
||||
return this.thenable(
|
||||
this.createFunctionCompletion('bulkWrite', range),
|
||||
this.createFunctionCompletion('count', range),
|
||||
this.createFunctionCompletion('copyTo', range),
|
||||
this.createFunctionCompletion('convertToCapped', range),
|
||||
this.createFunctionCompletion('createIndex', range),
|
||||
this.createFunctionCompletion('createIndexes', range),
|
||||
this.createFunctionCompletion('dataSize', range),
|
||||
this.createFunctionCompletion('deleteOne', range),
|
||||
this.createFunctionCompletion('deleteMany', range),
|
||||
this.createFunctionCompletion('distinct', range),
|
||||
this.createFunctionCompletion('drop', range),
|
||||
this.createFunctionCompletion('dropIndex', range),
|
||||
this.createFunctionCompletion('dropIndexes', range),
|
||||
this.createFunctionCompletion('ensureIndex', range),
|
||||
this.createFunctionCompletion('explain', range),
|
||||
this.createFunctionCompletion('reIndex', range),
|
||||
this.createFunctionCompletion('find', range),
|
||||
this.createFunctionCompletion('findOne', range),
|
||||
this.createFunctionCompletion('findOneAndDelete', range),
|
||||
this.createFunctionCompletion('findOneAndReplace', range),
|
||||
this.createFunctionCompletion('findOneAndUpdate', range),
|
||||
this.createFunctionCompletion('getDB', range),
|
||||
this.createFunctionCompletion('getPlanCache', range),
|
||||
this.createFunctionCompletion('getIndexes', range),
|
||||
this.createFunctionCompletion('group', range),
|
||||
this.createFunctionCompletion('insert', range),
|
||||
this.createFunctionCompletion('insertOne', range),
|
||||
this.createFunctionCompletion('insertMany', range),
|
||||
this.createFunctionCompletion('mapReduce', range),
|
||||
this.createFunctionCompletion('aggregate', range),
|
||||
this.createFunctionCompletion('remove', range),
|
||||
this.createFunctionCompletion('replaceOne', range),
|
||||
this.createFunctionCompletion('renameCollection', range),
|
||||
this.createFunctionCompletion('runCommand', range),
|
||||
this.createFunctionCompletion('save', range),
|
||||
this.createFunctionCompletion('stats', range),
|
||||
this.createFunctionCompletion('storageSize', range),
|
||||
this.createFunctionCompletion('totalIndexSize', range),
|
||||
this.createFunctionCompletion('update', range),
|
||||
this.createFunctionCompletion('updateOne', range),
|
||||
this.createFunctionCompletion('updateMany', range),
|
||||
this.createFunctionCompletion('validate', range),
|
||||
this.createFunctionCompletion('getShardVersion', range),
|
||||
this.createFunctionCompletion('getShardDistribution', range),
|
||||
this.createFunctionCompletion('getSplitKeysForChunks', range),
|
||||
this.createFunctionCompletion('getWriteConcern', range),
|
||||
this.createFunctionCompletion('setWriteConcern', range),
|
||||
this.createFunctionCompletion('unsetWriteConcern', range),
|
||||
this.createFunctionCompletion('latencyStats', range)
|
||||
);
|
||||
}
|
||||
|
||||
private createFunctionCompletion(label: string, range: Range): CompletionItem {
|
||||
return {
|
||||
textEdit: {
|
||||
newText: label,
|
||||
range
|
||||
},
|
||||
kind: CompletionItemKind.Function,
|
||||
label,
|
||||
sortText: `2:${label}`
|
||||
};
|
||||
}
|
||||
|
||||
private createRange(parserRuleContext: ParseTree): Range {
|
||||
if (parserRuleContext instanceof ParserRuleContext) {
|
||||
const startToken = parserRuleContext.start;
|
||||
let stopToken = parserRuleContext.stop;
|
||||
if (!stopToken || startToken.type === mongoParser.mongoParser.EOF) {
|
||||
stopToken = startToken;
|
||||
}
|
||||
|
||||
const stop = stopToken.stopIndex;
|
||||
return this._createRange(startToken.startIndex, stop);
|
||||
}
|
||||
|
||||
if (parserRuleContext instanceof TerminalNode) {
|
||||
return this._createRange(parserRuleContext.symbol.startIndex, parserRuleContext.symbol.stopIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private createRangeAfter(parserRuleContext: ParseTree): Range {
|
||||
if (parserRuleContext instanceof ParserRuleContext) {
|
||||
let stopToken = parserRuleContext.stop;
|
||||
if (!stopToken) {
|
||||
stopToken = parserRuleContext.start;
|
||||
}
|
||||
|
||||
const stop = stopToken.stopIndex;
|
||||
return this._createRange(stop + 1, stop + 1);
|
||||
}
|
||||
|
||||
if (parserRuleContext instanceof TerminalNode) {
|
||||
return this._createRange(parserRuleContext.symbol.stopIndex + 1, parserRuleContext.symbol.stopIndex + 1);
|
||||
}
|
||||
|
||||
//currently returning an null for the sake of linting. Would prefer to throw an error, but don't want
|
||||
// to introduce a regression bug.
|
||||
return null;
|
||||
}
|
||||
|
||||
private _createRange(start: number, end: number): Range {
|
||||
const endPosition = this.textDocument.positionAt(end);
|
||||
if (endPosition.line < this.at.line) {
|
||||
return Range.create(Position.create(this.at.line, 0), this.at);
|
||||
}
|
||||
const startPosition = this.textDocument.positionAt(start);
|
||||
return Range.create(startPosition, endPosition);
|
||||
}
|
||||
|
||||
private thenable(...completionItems: CompletionItem[]): Promise<CompletionItem[]> {
|
||||
return Promise.resolve(completionItems || []);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// NOTE: This file may not take a dependencey on vscode or anything that takes a dependency on it (such as vscode-azureextensionui)
|
||||
|
||||
import { Db } from 'mongodb';
|
||||
import { getLanguageService, LanguageService as JsonLanguageService, SchemaConfiguration } from 'vscode-json-languageservice';
|
||||
import { CompletionItem, IConnection, InitializeParams, InitializeResult, TextDocumentPositionParams, TextDocuments } from 'vscode-languageserver';
|
||||
import { connectToMongoClient } from '../connectToMongoClient';
|
||||
import { MongoScriptDocumentManager } from './mongoScript';
|
||||
import { SchemaService } from './schemaService';
|
||||
|
||||
// tslint:disable-next-line: export-name
|
||||
export class LanguageService {
|
||||
|
||||
private textDocuments: TextDocuments = new TextDocuments();
|
||||
private readonly mongoDocumentsManager: MongoScriptDocumentManager;
|
||||
private db: Db;
|
||||
|
||||
private jsonLanguageService: JsonLanguageService;
|
||||
private schemaService: SchemaService;
|
||||
private schemas: SchemaConfiguration[];
|
||||
|
||||
constructor(connection: IConnection) {
|
||||
|
||||
this.schemaService = new SchemaService();
|
||||
|
||||
this.textDocuments.listen(connection);
|
||||
// After the server has started the client sends an initilize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilities.
|
||||
connection.onInitialize((_params: InitializeParams): InitializeResult => {
|
||||
return {
|
||||
capabilities: {
|
||||
textDocumentSync: this.textDocuments.syncKind, // Tell the client that the server works in FULL text document sync mode
|
||||
completionProvider: { triggerCharacters: ['.'] }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
connection.onCompletion(textDocumentPosition => {
|
||||
return this.provideCompletionItems(textDocumentPosition);
|
||||
});
|
||||
|
||||
connection.onRequest('connect', (connectionParams: IConnectionParams) => {
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
connectToMongoClient(connectionParams.connectionString, connectionParams.extensionUserAgent)
|
||||
.then(account => {
|
||||
this.db = account.db(connectionParams.databaseName);
|
||||
this.schemaService.registerSchemas(this.db)
|
||||
.then(schemas => {
|
||||
this.configureSchemas(schemas);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
connection.onRequest('disconnect', () => {
|
||||
this.db = null;
|
||||
for (const schema of this.schemas) {
|
||||
this.jsonLanguageService.resetSchema(schema.uri);
|
||||
}
|
||||
});
|
||||
|
||||
this.jsonLanguageService = getLanguageService({
|
||||
schemaRequestService: uri => this.schemaService.resolveSchema(uri),
|
||||
contributions: []
|
||||
});
|
||||
|
||||
this.mongoDocumentsManager = new MongoScriptDocumentManager(this.schemaService, this.jsonLanguageService);
|
||||
}
|
||||
|
||||
public provideCompletionItems(positionParams: TextDocumentPositionParams): Promise<CompletionItem[]> {
|
||||
const textDocument = this.textDocuments.get(positionParams.textDocument.uri);
|
||||
const mongoScriptDocument = this.mongoDocumentsManager.getDocument(textDocument, this.db);
|
||||
return mongoScriptDocument.provideCompletionItemsAt(positionParams.position);
|
||||
}
|
||||
|
||||
public resetSchema(uri: string): void {
|
||||
this.jsonLanguageService.resetSchema(uri);
|
||||
}
|
||||
|
||||
public configureSchemas(schemas: SchemaConfiguration[]): void {
|
||||
this.jsonLanguageService.configure({
|
||||
schemas
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { ANTLRInputStream as InputStream } from 'antlr4ts/ANTLRInputStream';
|
||||
import { CommonTokenStream } from 'antlr4ts/CommonTokenStream';
|
||||
import { Interval } from 'antlr4ts/misc/Interval';
|
||||
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
|
||||
import { ParseTree } from 'antlr4ts/tree/ParseTree';
|
||||
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';
|
||||
import { Db } from 'mongodb';
|
||||
import { LanguageService as JsonLanguageService } from 'vscode-json-languageservice';
|
||||
import { CompletionItem, Position, TextDocument } from 'vscode-languageserver';
|
||||
import { mongoLexer } from './../grammar/mongoLexer';
|
||||
import * as mongoParser from './../grammar/mongoParser';
|
||||
import { MongoVisitor } from './../grammar/visitors';
|
||||
import { CompletionItemsVisitor } from './completionItemProvider';
|
||||
import { SchemaService } from './schemaService';
|
||||
|
||||
export class MongoScriptDocumentManager {
|
||||
|
||||
constructor(
|
||||
private schemaService: SchemaService,
|
||||
private jsonLanguageService: JsonLanguageService
|
||||
) {
|
||||
}
|
||||
|
||||
public getDocument(textDocument: TextDocument, db: Db): MongoScriptDocument {
|
||||
return new MongoScriptDocument(textDocument, db, this.schemaService, this.jsonLanguageService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MongoScriptDocument {
|
||||
|
||||
private readonly _lexer: mongoLexer;
|
||||
|
||||
constructor(
|
||||
private textDocument: TextDocument,
|
||||
private db: Db,
|
||||
private schemaService: SchemaService,
|
||||
private jsonLanguageService: JsonLanguageService
|
||||
) {
|
||||
this._lexer = new mongoLexer(new InputStream(textDocument.getText()));
|
||||
this._lexer.removeErrorListeners();
|
||||
}
|
||||
|
||||
public provideCompletionItemsAt(position: Position): Promise<CompletionItem[]> {
|
||||
const parser = new mongoParser.mongoParser(new CommonTokenStream(this._lexer));
|
||||
parser.removeErrorListeners();
|
||||
|
||||
const offset = this.textDocument.offsetAt(position);
|
||||
const lastNode = new NodeFinder(offset).visit(parser.commands());
|
||||
if (lastNode) {
|
||||
return new CompletionItemsVisitor(this.textDocument, this.db, offset, this.schemaService, this.jsonLanguageService).visit(lastNode);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
class NodeFinder extends MongoVisitor<ParseTree> {
|
||||
|
||||
constructor(private offset: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected defaultResult(ctx: ParseTree): ParseTree {
|
||||
if (ctx instanceof ParserRuleContext) {
|
||||
const stop = ctx.stop ? ctx.stop.stopIndex : ctx.start.stopIndex;
|
||||
if (stop < this.offset) {
|
||||
return ctx;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (ctx instanceof TerminalNode) {
|
||||
if (ctx.symbol.stopIndex < this.offset) {
|
||||
return ctx;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected aggregateResult(aggregate: ParseTree, nextResult: ParseTree): ParseTree {
|
||||
if (aggregate && nextResult) {
|
||||
const aggregateStart = aggregate instanceof ParserRuleContext ? aggregate.start.startIndex : (<TerminalNode>aggregate).symbol.startIndex;
|
||||
const aggregateStop = aggregate instanceof ParserRuleContext ? aggregate.start.stopIndex : (<TerminalNode>aggregate).symbol.stopIndex;
|
||||
const nextResultStart = nextResult instanceof ParserRuleContext ? nextResult.start.startIndex : (<TerminalNode>nextResult).symbol.startIndex;
|
||||
const nextResultStop = nextResult instanceof ParserRuleContext ? nextResult.start.stopIndex : (<TerminalNode>nextResult).symbol.stopIndex;
|
||||
|
||||
if (Interval.of(aggregateStart, aggregateStop).properlyContains(Interval.of(nextResultStart, nextResultStop))) {
|
||||
return aggregate;
|
||||
}
|
||||
return nextResult;
|
||||
}
|
||||
return nextResult ? nextResult : aggregate;
|
||||
}
|
||||
}
|
|
@ -1,630 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Cursor, Db } from 'mongodb';
|
||||
import { SchemaConfiguration } from 'vscode-json-languageservice';
|
||||
import { JSONSchema } from 'vscode-json-languageservice/lib/umd/jsonSchema';
|
||||
|
||||
// tslint:disable:no-reserved-keywords // Grandfathered in ("type")
|
||||
// tslint:disable:no-any
|
||||
|
||||
// tslint:disable-next-line: export-name
|
||||
export class SchemaService {
|
||||
|
||||
private _db: Db;
|
||||
private _schemasCache: Map<string, string> = new Map<string, string>();
|
||||
|
||||
public registerSchemas(db: Db): Thenable<SchemaConfiguration[]> {
|
||||
this._db = db;
|
||||
this._schemasCache.clear();
|
||||
return this._db.collections()
|
||||
.then(collections => {
|
||||
const schemas: SchemaConfiguration[] = [];
|
||||
for (const collection of collections) {
|
||||
schemas.push(...[{
|
||||
uri: this.queryCollectionSchema(collection.collectionName),
|
||||
fileMatch: [this.queryDocumentUri(collection.collectionName)]
|
||||
}, {
|
||||
uri: this.aggregateCollectionSchema(collection.collectionName),
|
||||
fileMatch: [this.aggregateDocumentUri(collection.collectionName)]
|
||||
}]);
|
||||
}
|
||||
return schemas;
|
||||
});
|
||||
}
|
||||
|
||||
public queryCollectionSchema(collectionName: string): string {
|
||||
return 'mongo://query/' + collectionName + '.schema';
|
||||
}
|
||||
|
||||
public aggregateCollectionSchema(collectionName: string): string {
|
||||
return 'mongo://aggregate/' + collectionName + '.schema';
|
||||
}
|
||||
|
||||
public queryDocumentUri(collectionName: string): string {
|
||||
return 'mongo://query/' + collectionName + '.json';
|
||||
}
|
||||
|
||||
public aggregateDocumentUri(collectionName: string): string {
|
||||
return 'mongo://aggregate/' + collectionName + '.json';
|
||||
}
|
||||
|
||||
public resolveSchema(uri: string): Thenable<string> {
|
||||
const schema = this._schemasCache.get(uri);
|
||||
if (schema) {
|
||||
return Promise.resolve(schema);
|
||||
}
|
||||
if (uri.startsWith('mongo://query/')) {
|
||||
return this._resolveQueryCollectionSchema(uri.substring('mongo://query/'.length, uri.length - '.schema'.length), uri)
|
||||
.then(sch => {
|
||||
this._schemasCache.set(uri, sch);
|
||||
return sch;
|
||||
});
|
||||
}
|
||||
if (uri.startsWith('mongo://aggregate/')) {
|
||||
return this._resolveAggregateCollectionSchema(uri.substring('mongo://aggregate/'.length, uri.length - '.schema'.length))
|
||||
.then(sch => {
|
||||
this._schemasCache.set(uri, sch);
|
||||
return sch;
|
||||
});
|
||||
}
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
private _resolveQueryCollectionSchema(collectionName: string, schemaUri: string): Thenable<string> {
|
||||
const collection = this._db.collection(collectionName);
|
||||
const cursor = collection.find();
|
||||
return new Promise((resolve, _reject) => {
|
||||
this.readNext([], cursor, 10, (result) => {
|
||||
const schema: JSONSchema = {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
};
|
||||
for (const document of result) {
|
||||
this.setSchemaForDocument(null, document, schema);
|
||||
}
|
||||
this.setGlobalOperatorProperties(schema);
|
||||
this.setLogicalOperatorProperties(schema, schemaUri);
|
||||
resolve(JSON.stringify(schema));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _resolveAggregateCollectionSchema(collectionName: string): Thenable<string> {
|
||||
const collection = this._db.collection(collectionName);
|
||||
const cursor = collection.find();
|
||||
return new Promise((resolve, _reject) => {
|
||||
this.readNext([], cursor, 10, (_result) => {
|
||||
const schema: JSONSchema = {
|
||||
type: 'array',
|
||||
items: this.getAggregateStagePropertiesSchema(this.queryCollectionSchema(collectionName))
|
||||
};
|
||||
resolve(JSON.stringify(schema));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getMongoDocumentType(document: any): string {
|
||||
return Array.isArray(document) ? 'array' : (document === null ? 'null' : typeof document);
|
||||
}
|
||||
|
||||
private setSchemaForDocument(parent: string, document: any, schema: JSONSchema): void {
|
||||
if (this.getMongoDocumentType(document) === 'object') {
|
||||
for (const property of Object.keys(document)) {
|
||||
if (!parent &&
|
||||
['_id'].indexOf(property) !== -1) {
|
||||
continue;
|
||||
}
|
||||
this.setSchemaForDocumentProperty(parent, property, document, schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setSchemaForDocumentProperty(parent: string, property: string, document: any, schema: JSONSchema): void {
|
||||
const scopedProperty = parent ? `${parent}.${property}` : property;
|
||||
const value = document[property];
|
||||
const type = this.getMongoDocumentType(value);
|
||||
|
||||
const propertySchema: JSONSchema = {
|
||||
type: [type, 'object']
|
||||
};
|
||||
this.setOperatorProperties(type, propertySchema);
|
||||
schema.properties[scopedProperty] = propertySchema;
|
||||
|
||||
if (type === 'object') {
|
||||
this.setSchemaForDocument(scopedProperty, value, schema);
|
||||
}
|
||||
|
||||
if (type === 'array') {
|
||||
for (const v of value) {
|
||||
this.setSchemaForDocument(scopedProperty, v, schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setGlobalOperatorProperties(schema: JSONSchema): void {
|
||||
schema.properties.$text = <JSONSchema>{
|
||||
type: 'object',
|
||||
description: 'Performs text search',
|
||||
properties: {
|
||||
$search: <JSONSchema>{
|
||||
type: 'string',
|
||||
description: 'A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase'
|
||||
},
|
||||
$language: {
|
||||
type: 'string',
|
||||
description: 'Optional. The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index.\nIf you specify a language value of "none", then the text search uses simple tokenization with no list of stop words and no stemming'
|
||||
},
|
||||
$caseSensitive: {
|
||||
type: 'boolean',
|
||||
description: 'Optional. A boolean flag to enable or disable case sensitive search. Defaults to false; i.e. the search defers to the case insensitivity of the text index'
|
||||
},
|
||||
$diacriticSensitive: {
|
||||
type: 'boolean',
|
||||
description: `Optional. A boolean flag to enable or disable diacritic sensitive search against version 3 text indexes.Defaults to false; i.e.the search defers to the diacritic insensitivity of the text index
|
||||
Text searches against earlier versions of the text index are inherently diacritic sensitive and cannot be diacritic insensitive. As such, the $diacriticSensitive option has no effect with earlier versions of the text index`
|
||||
}
|
||||
},
|
||||
required: ['$search']
|
||||
};
|
||||
|
||||
schema.properties.$where = {
|
||||
type: 'string',
|
||||
description: `Matches documents that satisfy a JavaScript expression.
|
||||
Use the $where operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system`
|
||||
};
|
||||
schema.properties.$comment = {
|
||||
type: 'string',
|
||||
description: 'Adds a comment to a query predicate'
|
||||
};
|
||||
}
|
||||
|
||||
private setLogicalOperatorProperties(schema: JSONSchema, schemaUri: string): void {
|
||||
schema.properties.$or = {
|
||||
type: 'array',
|
||||
description: 'Joins query clauses with a logical OR returns all documents that match the conditions of either clause',
|
||||
items: <JSONSchema>{
|
||||
$ref: schemaUri
|
||||
}
|
||||
};
|
||||
schema.properties.$and = {
|
||||
type: 'array',
|
||||
description: 'Joins query clauses with a logical AND returns all documents that match the conditions of both clauses',
|
||||
items: <JSONSchema>{
|
||||
$ref: schemaUri
|
||||
}
|
||||
};
|
||||
schema.properties.$nor = {
|
||||
type: 'array',
|
||||
description: 'Joins query clauses with a logical NOR returns all documents that fail to match both clauses',
|
||||
items: <JSONSchema>{
|
||||
$ref: schemaUri
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
private setOperatorProperties(type: string, schema: JSONSchema): void {
|
||||
if (!schema.properties) {
|
||||
schema.properties = {};
|
||||
}
|
||||
|
||||
const expressionSchema = {
|
||||
properties: <any>{}
|
||||
};
|
||||
// Comparison operators
|
||||
expressionSchema.properties.$eq = {
|
||||
type: type,
|
||||
description: 'Matches values that are equal to a specified value'
|
||||
};
|
||||
expressionSchema.properties.$gt = {
|
||||
type: type,
|
||||
description: 'Matches values that are greater than a specified value'
|
||||
};
|
||||
expressionSchema.properties.$gte = {
|
||||
type: type,
|
||||
description: 'Matches values that are greater than or equal to a specified value'
|
||||
};
|
||||
expressionSchema.properties.$lt = {
|
||||
type: type,
|
||||
description: 'Matches values that are less than a specified value'
|
||||
};
|
||||
expressionSchema.properties.$lte = {
|
||||
type: type,
|
||||
description: 'Matches values that are less than or equal to a specified value'
|
||||
};
|
||||
expressionSchema.properties.$ne = {
|
||||
type: type,
|
||||
description: 'Matches all values that are not equal to a specified value'
|
||||
};
|
||||
expressionSchema.properties.$in = {
|
||||
type: 'array',
|
||||
description: 'Matches any of the values specified in an array'
|
||||
};
|
||||
expressionSchema.properties.$nin = {
|
||||
type: 'array',
|
||||
description: 'Matches none of the values specified in an array'
|
||||
};
|
||||
|
||||
// Element operators
|
||||
expressionSchema.properties.$exists = {
|
||||
type: 'boolean',
|
||||
description: 'Matches documents that have the specified field'
|
||||
};
|
||||
expressionSchema.properties.$type = {
|
||||
type: 'string',
|
||||
description: 'Selects documents if a field is of the specified type'
|
||||
};
|
||||
|
||||
// Evaluation operators
|
||||
expressionSchema.properties.$mod = {
|
||||
type: 'array',
|
||||
description: 'Performs a modulo operation on the value of a field and selects documents with a specified result',
|
||||
maxItems: 2,
|
||||
default: [2, 0]
|
||||
};
|
||||
expressionSchema.properties.$regex = {
|
||||
type: 'string',
|
||||
description: 'Selects documents where values match a specified regular expression'
|
||||
};
|
||||
|
||||
// Geospatial
|
||||
const geometryPropertySchema: JSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
default: 'GeoJSON object type'
|
||||
},
|
||||
coordinates: {
|
||||
type: 'array'
|
||||
},
|
||||
crs: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string'
|
||||
},
|
||||
properties: {
|
||||
type: 'object'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expressionSchema.properties.$geoWithin = {
|
||||
type: 'object',
|
||||
description: 'Selects geometries within a bounding GeoJSON geometry. The 2dsphere and 2d indexes support $geoWithin',
|
||||
properties: {
|
||||
$geometry: geometryPropertySchema,
|
||||
$box: {
|
||||
type: 'array'
|
||||
},
|
||||
$polygon: {
|
||||
type: 'array'
|
||||
},
|
||||
$center: {
|
||||
type: 'array'
|
||||
},
|
||||
$centerSphere: {
|
||||
type: 'array'
|
||||
}
|
||||
}
|
||||
};
|
||||
expressionSchema.properties.$geoIntersects = {
|
||||
type: 'object',
|
||||
description: 'Selects geometries that intersect with a GeoJSON geometry. The 2dsphere index supports $geoIntersects',
|
||||
properties: {
|
||||
$geometry: geometryPropertySchema
|
||||
}
|
||||
};
|
||||
expressionSchema.properties.$near = {
|
||||
type: 'object',
|
||||
description: 'Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near',
|
||||
properties: {
|
||||
$geometry: geometryPropertySchema,
|
||||
$maxDistance: {
|
||||
type: 'number'
|
||||
},
|
||||
$minDistance: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
};
|
||||
expressionSchema.properties.$nearSphere = {
|
||||
type: 'object',
|
||||
description: 'Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near',
|
||||
properties: {
|
||||
$geometry: geometryPropertySchema,
|
||||
$maxDistance: {
|
||||
type: 'number'
|
||||
},
|
||||
$minDistance: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Array operatos
|
||||
if (type === 'array') {
|
||||
expressionSchema.properties.$all = {
|
||||
type: 'array',
|
||||
description: 'Matches arrays that contain all elements specified in the query'
|
||||
};
|
||||
expressionSchema.properties.$size = {
|
||||
type: 'number',
|
||||
description: 'Selects documents if the array field is a specified size'
|
||||
};
|
||||
}
|
||||
|
||||
// Bit operators
|
||||
expressionSchema.properties.$bitsAllSet = {
|
||||
type: 'array',
|
||||
description: 'Matches numeric or binary values in which a set of bit positions all have a value of 1'
|
||||
};
|
||||
expressionSchema.properties.$bitsAnySet = {
|
||||
type: 'array',
|
||||
description: 'Matches numeric or binary values in which any bit from a set of bit positions has a value of 1'
|
||||
};
|
||||
expressionSchema.properties.$bitsAllClear = {
|
||||
type: 'array',
|
||||
description: 'Matches numeric or binary values in which a set of bit positions all have a value of 0'
|
||||
};
|
||||
expressionSchema.properties.$bitsAnyClear = {
|
||||
type: 'array',
|
||||
description: 'Matches numeric or binary values in which any bit from a set of bit positions has a value of 0'
|
||||
};
|
||||
|
||||
schema.properties = { ...expressionSchema.properties };
|
||||
schema.properties.$not = {
|
||||
type: 'object',
|
||||
description: 'Inverts the effect of a query expression and returns documents that do not match the query expression',
|
||||
properties: { ...expressionSchema.properties }
|
||||
};
|
||||
schema.properties.$elemMatch = {
|
||||
type: 'object'
|
||||
};
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
private getAggregateStagePropertiesSchema(querySchemaUri: string): JSONSchema {
|
||||
const schemas: JSONSchema[] = [];
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$collStats: {
|
||||
type: 'object',
|
||||
description: 'Returns statistics regarding a collection or view'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$project: {
|
||||
type: 'object',
|
||||
description: 'Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$match: {
|
||||
type: 'object',
|
||||
description: 'Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match)',
|
||||
$ref: querySchemaUri
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$redact: {
|
||||
type: 'object',
|
||||
description: 'Reshapes each document in the stream by restricting the content for each document based on information stored in the documents themselves. Incorporates the functionality of $project and $match. Can be used to implement field level redaction. For each input document, outputs either one or zero documents'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$limit: {
|
||||
type: 'object',
|
||||
description: 'Passes the first n documents unmodified to the pipeline where n is the specified limit. For each input document, outputs either one document (for the first n documents) or zero documents (after the first n documents).'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$skip: {
|
||||
type: 'object',
|
||||
description: 'Skips the first n documents where n is the specified skip number and passes the remaining documents unmodified to the pipeline. For each input document, outputs either zero documents (for the first n documents) or one document (if after the first n documents)'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$unwind: {
|
||||
type: 'object',
|
||||
description: 'Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements and can be zero for an empty array'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$group: {
|
||||
type: 'object',
|
||||
description: 'Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields.',
|
||||
properties: {
|
||||
_id: {
|
||||
type: ['string', 'object']
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
type: 'object'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$sample: {
|
||||
type: 'object',
|
||||
description: 'Randomly selects the specified number of documents from its input'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$sort: {
|
||||
type: 'object',
|
||||
description: 'Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document.'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$geoNear: {
|
||||
type: 'object',
|
||||
description: 'Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field.'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$lookup: {
|
||||
type: 'object',
|
||||
description: 'Performs a left outer join to another collection in the same database to filter in documents from the “joined” collection for processing'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$out: {
|
||||
type: 'object',
|
||||
description: 'Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$indexStats: {
|
||||
type: 'object',
|
||||
description: 'Returns statistics regarding the use of each index for the collection'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$facet: {
|
||||
type: 'object',
|
||||
description: 'Processes multiple aggregation pipelines within a single stage on the same set of input documents. Enables the creation of multi-faceted aggregations capable of characterizing data across multiple dimensions, or facets, in a single stage'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$bucket: {
|
||||
type: 'object',
|
||||
description: 'Categorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$bucketAuto: {
|
||||
type: 'object',
|
||||
description: 'Categorizes incoming documents into a specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically determined in an attempt to evenly distribute the documents into the specified number of buckets'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$sortByCount: {
|
||||
type: 'object',
|
||||
description: 'Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$addFields: {
|
||||
type: 'object',
|
||||
description: 'Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$replaceRoot: {
|
||||
type: 'object',
|
||||
description: 'Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$count: {
|
||||
type: 'object',
|
||||
description: 'Returns a count of the number of documents at this stage of the aggregation pipeline'
|
||||
}
|
||||
}
|
||||
});
|
||||
schemas.push({
|
||||
type: 'object',
|
||||
properties: {
|
||||
$graphLookup: {
|
||||
type: 'object',
|
||||
description: 'Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document'
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
type: 'object',
|
||||
oneOf: schemas
|
||||
};
|
||||
}
|
||||
|
||||
private readNext(result: any[], cursor: Cursor<any>, batchSize: number, callback: (result: any[]) => void): void {
|
||||
if (result.length === batchSize) {
|
||||
callback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
cursor.hasNext().then(hasNext => {
|
||||
if (!hasNext) {
|
||||
callback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
cursor.next().then(doc => {
|
||||
result.push(doc);
|
||||
this.readNext(result, cursor, batchSize, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ext } from "../extensionVariables";
|
||||
import { MongoCodeLensProvider } from "./services/MongoCodeLensProvider";
|
||||
import { MongoDatabaseTreeItem } from "./tree/MongoDatabaseTreeItem";
|
||||
|
||||
export function setConnectedNode(node: MongoDatabaseTreeItem | undefined, codeLensProvider: MongoCodeLensProvider): void {
|
||||
ext.connectedMongoDB = node;
|
||||
const dbName = node && node.label;
|
||||
if (codeLensProvider) {
|
||||
codeLensProvider.setConnectedDatabase(dbName);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISubscriptionContext } from "vscode-azureextensionui";
|
||||
|
||||
export interface IMongoTreeRoot extends ISubscriptionContext {
|
||||
isEmulator: boolean;
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DatabaseAccount } from 'azure-arm-cosmosdb/lib/models';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import * as vscode from 'vscode';
|
||||
import { appendExtensionUserAgent, AzureParentTreeItem, AzureTreeItem, ICreateChildImplContext, parseError } from 'vscode-azureextensionui';
|
||||
import { deleteCosmosDBAccount } from '../../commands/deleteCosmosDBAccount';
|
||||
import { getThemeAgnosticIconPath, Links, testDb } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { connectToMongoClient } from '../connectToMongoClient';
|
||||
import { getDatabaseNameFromConnectionString } from '../mongoConnectionStrings';
|
||||
import { IMongoTreeRoot } from './IMongoTreeRoot';
|
||||
import { MongoCollectionTreeItem } from './MongoCollectionTreeItem';
|
||||
import { MongoDatabaseTreeItem } from './MongoDatabaseTreeItem';
|
||||
import { MongoDocumentTreeItem } from './MongoDocumentTreeItem';
|
||||
|
||||
export class MongoAccountTreeItem extends AzureParentTreeItem<IMongoTreeRoot> {
|
||||
public static contextValue: string = "cosmosDBMongoServer";
|
||||
public readonly contextValue: string = MongoAccountTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = "Database";
|
||||
public readonly id: string;
|
||||
public readonly label: string;
|
||||
public readonly connectionString: string;
|
||||
|
||||
private _root: IMongoTreeRoot;
|
||||
|
||||
constructor(parent: AzureParentTreeItem, id: string, label: string, connectionString: string, isEmulator: boolean, readonly databaseAccount?: DatabaseAccount) {
|
||||
super(parent);
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.connectionString = connectionString;
|
||||
this._root = Object.assign({}, parent.root, { isEmulator });
|
||||
}
|
||||
|
||||
// overrides ISubscriptionContext with an object that also has Mongo info
|
||||
public get root(): IMongoTreeRoot {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('CosmosDBAccount.svg');
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzureTreeItem<IMongoTreeRoot>[]> {
|
||||
let mongoClient: MongoClient | undefined;
|
||||
try {
|
||||
let databases: IDatabaseInfo[];
|
||||
|
||||
if (!this.connectionString) {
|
||||
throw new Error('Missing connection string');
|
||||
}
|
||||
|
||||
// Azure MongoDB accounts need to have the name passed in for private endpoints
|
||||
mongoClient = await connectToMongoClient(this.connectionString, this.databaseAccount ? this.databaseAccount.name : appendExtensionUserAgent());
|
||||
|
||||
const databaseInConnectionString = getDatabaseNameFromConnectionString(this.connectionString);
|
||||
if (databaseInConnectionString && !this.root.isEmulator) { // emulator violates the connection string format
|
||||
// If the database is in the connection string, that's all we connect to (we might not even have permissions to list databases)
|
||||
databases = [{
|
||||
name: databaseInConnectionString,
|
||||
empty: false
|
||||
}];
|
||||
} else {
|
||||
// https://mongodb.github.io/node-mongodb-native/3.1/api/index.html
|
||||
const result: { databases: IDatabaseInfo[] } = await mongoClient.db(testDb).admin().listDatabases();
|
||||
databases = result.databases;
|
||||
}
|
||||
return databases
|
||||
.filter((database: IDatabaseInfo) => !(database.name && database.name.toLowerCase() === "admin" && database.empty)) // Filter out the 'admin' database if it's empty
|
||||
.map(database => new MongoDatabaseTreeItem(this, database.name, this.connectionString));
|
||||
} catch (error) {
|
||||
const message = parseError(error).message;
|
||||
if (this._root.isEmulator && message.includes("ECONNREFUSED")) {
|
||||
error.message = `Unable to reach emulator. See ${Links.LocalConnectionDebuggingTips} for debugging tips.\n${message}`;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
finally {
|
||||
if (mongoClient) {
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
mongoClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<MongoDatabaseTreeItem> {
|
||||
const databaseName = await ext.ui.showInputBox({
|
||||
placeHolder: "Database Name",
|
||||
prompt: "Enter the name of the database",
|
||||
validateInput: validateDatabaseName
|
||||
});
|
||||
context.showCreatingTreeItem(databaseName);
|
||||
|
||||
return new MongoDatabaseTreeItem(this, databaseName, this.connectionString);
|
||||
}
|
||||
|
||||
public isAncestorOfImpl(contextValue: string): boolean {
|
||||
switch (contextValue) {
|
||||
case MongoDatabaseTreeItem.contextValue:
|
||||
case MongoCollectionTreeItem.contextValue:
|
||||
case MongoDocumentTreeItem.contextValue:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await deleteCosmosDBAccount(this);
|
||||
}
|
||||
}
|
||||
|
||||
function validateDatabaseName(database: string): string | undefined | null {
|
||||
// https://docs.mongodb.com/manual/reference/limits/#naming-restrictions
|
||||
const min = 1;
|
||||
const max = 63;
|
||||
if (!database || database.length < min || database.length > max) {
|
||||
return `Database name must be between ${min} and ${max} characters.`;
|
||||
}
|
||||
if (/[/\\. "$]/.test(database)) {
|
||||
return "Database name cannot contain these characters - `/\\. \"$`";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export interface IDatabaseInfo {
|
||||
name?: string;
|
||||
empty?: boolean;
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { BulkWriteOpResultObject, Collection, CollectionInsertManyOptions, Cursor, DeleteWriteOpResultObject, InsertOneWriteOpResult, InsertWriteOpResult, MongoCountPreferences } from 'mongodb';
|
||||
import * as _ from 'underscore';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureParentTreeItem, DialogResponses, ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { defaultBatchSize, getThemeAgnosticIconPath } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { MongoCommand } from '../MongoCommand';
|
||||
import { IMongoTreeRoot } from './IMongoTreeRoot';
|
||||
import { IMongoDocument, MongoDocumentTreeItem } from './MongoDocumentTreeItem';
|
||||
// tslint:disable:no-var-requires no-require-imports
|
||||
const EJSON = require("mongodb-extended-json");
|
||||
|
||||
type MongoFunction = (...args: Object[]) => Thenable<string>;
|
||||
class FunctionDescriptor {
|
||||
public constructor(public mongoFunction: MongoFunction, public text: string, public minShellArgs: number, public maxShellArgs: number, public maxHandledArgs: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export class MongoCollectionTreeItem extends AzureParentTreeItem<IMongoTreeRoot> {
|
||||
public static contextValue: string = "MongoCollection";
|
||||
public readonly contextValue: string = MongoCollectionTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = "Document";
|
||||
public readonly collection: Collection;
|
||||
|
||||
private readonly _query: object | undefined;
|
||||
private readonly _projection: object | undefined;
|
||||
private _cursor: Cursor | undefined;
|
||||
private _hasMoreChildren: boolean = true;
|
||||
private _batchSize: number = defaultBatchSize;
|
||||
|
||||
constructor(parent: AzureParentTreeItem, collection: Collection, query?: Object[]) {
|
||||
super(parent);
|
||||
this.collection = collection;
|
||||
if (query && query.length) {
|
||||
this._query = query[0];
|
||||
this._projection = query.length > 1 && query[1];
|
||||
}
|
||||
}
|
||||
|
||||
public async update(documents: IMongoDocument[]): Promise<IMongoDocument[]> {
|
||||
const operations = documents.map((document) => {
|
||||
return {
|
||||
replaceOne: {
|
||||
filter: { _id: document._id },
|
||||
update: _.omit(document, '_id'),
|
||||
upsert: false
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const result: BulkWriteOpResultObject = await this.collection.bulkWrite(operations);
|
||||
ext.outputChannel.appendLog(`Successfully updated ${result.modifiedCount} document(s), inserted ${result.insertedCount} document(s)`);
|
||||
return documents;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.collection.collectionName;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.collection.collectionName;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Collection.svg');
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return this._hasMoreChildren;
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean): Promise<MongoDocumentTreeItem[]> {
|
||||
if (clearCache || this._cursor === undefined) {
|
||||
this._cursor = this.collection.find(this._query).batchSize(defaultBatchSize);
|
||||
if (this._projection) {
|
||||
this._cursor = this._cursor.project(this._projection);
|
||||
}
|
||||
this._batchSize = defaultBatchSize;
|
||||
}
|
||||
|
||||
const documents: IMongoDocument[] = [];
|
||||
let count: number = 0;
|
||||
while (count < this._batchSize) {
|
||||
this._hasMoreChildren = await this._cursor.hasNext();
|
||||
if (this._hasMoreChildren) {
|
||||
documents.push(<IMongoDocument>await this._cursor.next());
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._batchSize *= 2;
|
||||
|
||||
return documents.map((document: IMongoDocument) => new MongoDocumentTreeItem(this, document));
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<MongoDocumentTreeItem> {
|
||||
context.showCreatingTreeItem("");
|
||||
const result: InsertOneWriteOpResult = await this.collection.insertOne({});
|
||||
const newDocument: IMongoDocument = await this.collection.findOne({ _id: result.insertedId });
|
||||
return new MongoDocumentTreeItem(this, newDocument);
|
||||
}
|
||||
|
||||
public async tryExecuteCommandDirectly(command: Partial<MongoCommand>): Promise<{ deferToShell: true; result: undefined } | { deferToShell: false; result: string }> {
|
||||
// range and text are not neccessary properties for this function so partial should suffice
|
||||
const parameters = command.arguments ? command.arguments.map(parseJSContent) : undefined;
|
||||
|
||||
const functions = {
|
||||
drop: new FunctionDescriptor(this.drop, 'Dropping collection', 0, 0, 0),
|
||||
count: new FunctionDescriptor(this.count, 'Counting documents', 0, 2, 2),
|
||||
findOne: new FunctionDescriptor(this.findOne, 'Finding document', 0, 2, 2),
|
||||
insert: new FunctionDescriptor(this.insert, 'Inserting document', 1, 1, 1),
|
||||
insertMany: new FunctionDescriptor(this.insertMany, 'Inserting documents', 1, 2, 2),
|
||||
insertOne: new FunctionDescriptor(this.insertOne, 'Inserting document', 1, 2, 2),
|
||||
deleteMany: new FunctionDescriptor(this.deleteMany, 'Deleting documents', 1, 2, 1),
|
||||
deleteOne: new FunctionDescriptor(this.deleteOne, 'Deleting document', 1, 2, 1),
|
||||
remove: new FunctionDescriptor(this.remove, 'Deleting document(s)', 1, 2, 1)
|
||||
};
|
||||
|
||||
if (functions.hasOwnProperty(command.name)) {
|
||||
|
||||
// currently no logic to handle chained commands so just defer to the shell right away
|
||||
if (command.chained) {
|
||||
return { deferToShell: true, result: undefined };
|
||||
}
|
||||
const descriptor: FunctionDescriptor = functions[command.name];
|
||||
|
||||
if (parameters.length < descriptor.minShellArgs) {
|
||||
throw new Error(`Too few arguments passed to command ${command.name}.`);
|
||||
}
|
||||
if (parameters.length > descriptor.maxShellArgs) {
|
||||
throw new Error(`Too many arguments passed to command ${command.name}`);
|
||||
}
|
||||
if (parameters.length > descriptor.maxHandledArgs) { //this function won't handle these arguments, but the shell will
|
||||
return { deferToShell: true, result: undefined };
|
||||
}
|
||||
const result = await reportProgress<string>(descriptor.mongoFunction.apply(this, parameters), descriptor.text);
|
||||
return { deferToShell: false, result };
|
||||
}
|
||||
return { deferToShell: true, result: undefined };
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete collection '${this.label}'?`;
|
||||
const result = await ext.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
await this.drop();
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
private async drop(): Promise<string> {
|
||||
try {
|
||||
await this.collection.drop();
|
||||
return `Dropped collection '${this.collection.collectionName}'.`;
|
||||
} catch (e) {
|
||||
const error: { code?: number, name?: string } = e;
|
||||
const NamespaceNotFoundCode = 26;
|
||||
if (error.name === 'MongoError' && error.code === NamespaceNotFoundCode) {
|
||||
return `Collection '${this.collection.collectionName}' could not be dropped because it does not exist.`;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async findOne(query?: Object, fieldsOption?: Object): Promise<string> {
|
||||
const result = await this.collection.findOne(query || {}, { fields: fieldsOption });
|
||||
// findOne is the only command in this file whose output requires EJSON support.
|
||||
// Hence that's the only function which uses EJSON.stringify rather than this.stringify.
|
||||
return EJSON.stringify(result, null, '\t');
|
||||
}
|
||||
|
||||
private async insert(document: Object): Promise<string> {
|
||||
if (!document) {
|
||||
throw new Error("The insert command requires at least one argument");
|
||||
}
|
||||
|
||||
const insertResult = await this.collection.insert(document);
|
||||
return this.stringify(insertResult);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
private async insertOne(document: Object, options?: any): Promise<string> {
|
||||
const insertOneResult: InsertOneWriteOpResult = await this.collection.insertOne(document, { w: options && options.writeConcern });
|
||||
return this.stringify(insertOneResult);
|
||||
}
|
||||
|
||||
//tslint:disable:no-any
|
||||
private async insertMany(documents: any[], options?: any): Promise<string> {
|
||||
assert.notEqual(documents.length, 0, "Array of documents cannot be empty");
|
||||
const insertManyOptions: CollectionInsertManyOptions = {};
|
||||
if (options) {
|
||||
if (options.ordered) {
|
||||
insertManyOptions.ordered = options.ordered;
|
||||
}
|
||||
if (options.writeConcern) {
|
||||
insertManyOptions.w = options.writeConcern;
|
||||
}
|
||||
}
|
||||
|
||||
const insertManyResult: InsertWriteOpResult = await this.collection.insertMany(documents, insertManyOptions);
|
||||
return this.stringify(insertManyResult);
|
||||
}
|
||||
|
||||
private async remove(filter?: Object): Promise<string> {
|
||||
const removeResult = await this.collection.remove(filter);
|
||||
return this.stringify(removeResult);
|
||||
}
|
||||
|
||||
private async deleteOne(filter: Object): Promise<string> {
|
||||
const deleteOneResult: DeleteWriteOpResultObject = await this.collection.deleteOne(filter);
|
||||
return this.stringify(deleteOneResult);
|
||||
}
|
||||
|
||||
private async deleteMany(filter: Object): Promise<string> {
|
||||
const deleteOpResult: DeleteWriteOpResultObject = await this.collection.deleteMany(filter);
|
||||
return this.stringify(deleteOpResult);
|
||||
}
|
||||
|
||||
private async count(query?: Object[], options?: MongoCountPreferences): Promise<string> {
|
||||
const count = await this.collection.count(query, options);
|
||||
return this.stringify(count);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
private stringify(result: any): string {
|
||||
return JSON.stringify(result, null, '\t');
|
||||
}
|
||||
}
|
||||
|
||||
function reportProgress<T>(promise: Thenable<T>, title: string): Thenable<T> {
|
||||
return vscode.window.withProgress<T>(
|
||||
{
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: title
|
||||
},
|
||||
(_progress) => {
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
function parseJSContent(content: string): any {
|
||||
try {
|
||||
return EJSON.parse(content);
|
||||
} catch (error) {
|
||||
throw error.message;
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fse from 'fs-extra';
|
||||
import { Collection, Db, DbCollectionOptions } from 'mongodb';
|
||||
import * as path from 'path';
|
||||
import * as process from 'process';
|
||||
import * as vscode from 'vscode';
|
||||
import { appendExtensionUserAgent, AzureParentTreeItem, DialogResponses, IActionContext, ICreateChildImplContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import * as cpUtils from '../../utils/cp';
|
||||
import { getWorkspaceArrayConfiguration, getWorkspaceConfiguration } from '../../utils/getWorkspaceConfiguration';
|
||||
import { connectToMongoClient } from '../connectToMongoClient';
|
||||
import { MongoCommand } from '../MongoCommand';
|
||||
import { addDatabaseToAccountConnectionString } from '../mongoConnectionStrings';
|
||||
import { MongoShell } from '../MongoShell';
|
||||
import { IMongoTreeRoot } from './IMongoTreeRoot';
|
||||
import { MongoAccountTreeItem } from './MongoAccountTreeItem';
|
||||
import { MongoCollectionTreeItem } from './MongoCollectionTreeItem';
|
||||
|
||||
const mongoExecutableFileName = process.platform === 'win32' ? 'mongo.exe' : 'mongo';
|
||||
const executingInShellMsg = "Executing command in Mongo shell";
|
||||
|
||||
export class MongoDatabaseTreeItem extends AzureParentTreeItem<IMongoTreeRoot> {
|
||||
public static contextValue: string = "mongoDb";
|
||||
public readonly contextValue: string = MongoDatabaseTreeItem.contextValue;
|
||||
public readonly childTypeLabel: string = "Collection";
|
||||
public readonly connectionString: string;
|
||||
public readonly databaseName: string;
|
||||
public readonly parent: MongoAccountTreeItem;
|
||||
|
||||
private _previousShellPathSetting: string | undefined;
|
||||
private _cachedShellPathOrCmd: string | undefined;
|
||||
|
||||
constructor(parent: MongoAccountTreeItem, databaseName: string, connectionString: string) {
|
||||
super(parent);
|
||||
this.databaseName = databaseName;
|
||||
this.connectionString = addDatabaseToAccountConnectionString(connectionString, this.databaseName);
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.databaseName;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return ext.connectedMongoDB && ext.connectedMongoDB.fullId === this.fullId ? 'Connected' : '';
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.databaseName;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Database.svg');
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<MongoCollectionTreeItem[]> {
|
||||
const db: Db = await this.connectToDb();
|
||||
const collections: Collection[] = await db.collections();
|
||||
return collections.map(collection => new MongoCollectionTreeItem(this, collection));
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<MongoCollectionTreeItem> {
|
||||
const collectionName = await ext.ui.showInputBox({
|
||||
placeHolder: "Collection Name",
|
||||
prompt: "Enter the name of the collection",
|
||||
validateInput: validateMongoCollectionName,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
context.showCreatingTreeItem(collectionName);
|
||||
return await this.createCollection(collectionName);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete database '${this.label}'?`;
|
||||
const result = await ext.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (result === DialogResponses.deleteResponse) {
|
||||
const db = await this.connectToDb();
|
||||
await db.dropDatabase();
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
public async connectToDb(): Promise<Db> {
|
||||
const accountConnection = await connectToMongoClient(this.connectionString, appendExtensionUserAgent());
|
||||
return accountConnection.db(this.databaseName);
|
||||
}
|
||||
|
||||
public async executeCommand(command: MongoCommand, context: IActionContext): Promise<string> {
|
||||
if (command.collection) {
|
||||
const db = await this.connectToDb();
|
||||
const collection = db.collection(command.collection);
|
||||
if (collection) {
|
||||
const collectionTreeItem = new MongoCollectionTreeItem(this, collection, command.arguments);
|
||||
const result = await collectionTreeItem.tryExecuteCommandDirectly(command);
|
||||
if (!result.deferToShell) {
|
||||
return result.result;
|
||||
}
|
||||
}
|
||||
return withProgress(this.executeCommandInShell(command, context), executingInShellMsg);
|
||||
|
||||
}
|
||||
|
||||
if (command.name === 'createCollection') {
|
||||
// arguments are all strings so DbCollectionOptions is represented as a JSON string which is why we pass argumentObjects instead
|
||||
return withProgress(this.createCollection(stripQuotes(command.arguments[0]), command.argumentObjects[1]).then(() => JSON.stringify({ Created: 'Ok' })), 'Creating collection');
|
||||
} else {
|
||||
return withProgress(this.executeCommandInShell(command, context), executingInShellMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public async createCollection(collectionName: string, options?: DbCollectionOptions): Promise<MongoCollectionTreeItem> {
|
||||
const db: Db = await this.connectToDb();
|
||||
const newCollection: Collection = await db.createCollection(collectionName, options);
|
||||
// db.createCollection() doesn't create empty collections for some reason
|
||||
// However, we can 'insert' and then 'delete' a document, which has the side-effect of creating an empty collection
|
||||
const result = await newCollection.insertOne({});
|
||||
await newCollection.deleteOne({ _id: result.insertedId });
|
||||
return new MongoCollectionTreeItem(this, newCollection);
|
||||
}
|
||||
|
||||
private async executeCommandInShell(command: MongoCommand, context: IActionContext): Promise<string> {
|
||||
context.telemetry.properties.executeInShell = "true";
|
||||
|
||||
// CONSIDER: Re-using the shell instead of disposing it each time would allow us to keep state
|
||||
// (JavaScript variables, etc.), but we would need to deal with concurrent requests, or timed-out
|
||||
// requests.
|
||||
const shell = await this.createShell();
|
||||
try {
|
||||
await shell.useDatabase(this.databaseName);
|
||||
return await shell.executeScript(command.text);
|
||||
} finally {
|
||||
shell.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async createShell(): Promise<MongoShell> {
|
||||
let shellPath: string | undefined = getWorkspaceConfiguration(ext.settingsKeys.mongoShellPath, "string");
|
||||
const shellArgs: string[] = getWorkspaceArrayConfiguration(ext.settingsKeys.mongoShellArgs, "string", []);
|
||||
|
||||
if (!this._cachedShellPathOrCmd || this._previousShellPathSetting !== shellPath) {
|
||||
// Only do this if setting changed since last time
|
||||
shellPath = await this._determineShellPathOrCmd(shellPath);
|
||||
this._previousShellPathSetting = shellPath;
|
||||
}
|
||||
this._cachedShellPathOrCmd = shellPath;
|
||||
|
||||
const timeout = 1000 * vscode.workspace.getConfiguration().get<number>(ext.settingsKeys.mongoShellTimeout);
|
||||
return MongoShell.create(shellPath, shellArgs, this.connectionString, this.root.isEmulator, ext.outputChannel, timeout);
|
||||
}
|
||||
|
||||
private async _determineShellPathOrCmd(shellPathSetting: string): Promise<string> {
|
||||
if (!shellPathSetting) {
|
||||
// User hasn't specified the path
|
||||
if (await cpUtils.commandSucceeds('mongo', '--version')) {
|
||||
// If the user already has mongo in their system path, just use that
|
||||
return 'mongo';
|
||||
} else {
|
||||
// If all else fails, prompt the user for the mongo path
|
||||
|
||||
// tslint:disable-next-line:no-constant-condition
|
||||
const openFile: vscode.MessageItem = { title: `Browse to ${mongoExecutableFileName}` };
|
||||
const browse: vscode.MessageItem = { title: 'Open installation page' };
|
||||
const noMongoError: string = 'This functionality requires the Mongo DB shell, but we could not find it in the path or using the mongo.shell.path setting.';
|
||||
const response = await vscode.window.showErrorMessage(noMongoError, browse, openFile);
|
||||
if (response === openFile) {
|
||||
// tslint:disable-next-line:no-constant-condition
|
||||
while (true) {
|
||||
const newPath: vscode.Uri[] = await vscode.window.showOpenDialog({
|
||||
filters: { 'Executable Files': [process.platform === 'win32' ? 'exe' : ''] },
|
||||
openLabel: `Select ${mongoExecutableFileName}`
|
||||
});
|
||||
if (newPath && newPath.length) {
|
||||
const fsPath = newPath[0].fsPath;
|
||||
const baseName = path.basename(fsPath);
|
||||
if (baseName !== mongoExecutableFileName) {
|
||||
const useAnyway: vscode.MessageItem = { title: 'Use anyway' };
|
||||
const tryAgain: vscode.MessageItem = { title: 'Try again' };
|
||||
const response2 = await ext.ui.showWarningMessage(
|
||||
`Expected a file named "${mongoExecutableFileName}, but the selected filename is "${baseName}"`,
|
||||
useAnyway,
|
||||
tryAgain);
|
||||
if (response2 === tryAgain) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await vscode.workspace.getConfiguration().update(ext.settingsKeys.mongoShellPath, fsPath, vscode.ConfigurationTarget.Global);
|
||||
return fsPath;
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
} else if (response === browse) {
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://docs.mongodb.com/manual/installation/'));
|
||||
// default down to cancel error because MongoShell.create errors out if undefined is passed as the shellPath
|
||||
}
|
||||
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
} else {
|
||||
// User has specified the path or command. Sometimes they set the folder instead of a path to the file, let's check that and auto fix
|
||||
if (await fse.pathExists(shellPathSetting)) {
|
||||
const stat = await fse.stat(shellPathSetting);
|
||||
if (stat.isDirectory()) {
|
||||
return path.join(shellPathSetting, mongoExecutableFileName);
|
||||
}
|
||||
}
|
||||
|
||||
return shellPathSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateMongoCollectionName(collectionName: string): string | undefined | null {
|
||||
// https://docs.mongodb.com/manual/reference/limits/#Restriction-on-Collection-Names
|
||||
if (!collectionName) {
|
||||
return "Collection name cannot be empty";
|
||||
}
|
||||
const systemPrefix = "system.";
|
||||
if (collectionName.startsWith(systemPrefix)) {
|
||||
return `"${systemPrefix}" prefix is reserved for internal use`;
|
||||
}
|
||||
if (/[$]/.test(collectionName)) {
|
||||
return "Collection name cannot contain $";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function withProgress<T>(promise: Thenable<T>, title: string, location: vscode.ProgressLocation = vscode.ProgressLocation.Window): Thenable<T> {
|
||||
return vscode.window.withProgress<T>(
|
||||
{
|
||||
location: location,
|
||||
title: title
|
||||
},
|
||||
(_progress) => {
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
export function stripQuotes(term: string): string {
|
||||
if ((term.startsWith('\'') && term.endsWith('\''))
|
||||
|| (term.startsWith('"') && term.endsWith('"'))) {
|
||||
return term.substring(1, term.length - 1);
|
||||
}
|
||||
return term;
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Collection, DeleteWriteOpResultObject, ObjectID, UpdateWriteOpResult } from 'mongodb';
|
||||
import * as _ from 'underscore';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureTreeItem, DialogResponses, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { getThemeAgnosticIconPath } from '../../constants';
|
||||
import { getDocumentTreeItemLabel } from '../../utils/vscodeUtils';
|
||||
import { IMongoTreeRoot } from './IMongoTreeRoot';
|
||||
import { MongoCollectionTreeItem } from './MongoCollectionTreeItem';
|
||||
|
||||
export interface IMongoDocument {
|
||||
_id: string | ObjectID;
|
||||
|
||||
// custom properties
|
||||
// tslint:disable-next-line:no-any
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class MongoDocumentTreeItem extends AzureTreeItem<IMongoTreeRoot> {
|
||||
public static contextValue: string = "MongoDocument";
|
||||
public readonly contextValue: string = MongoDocumentTreeItem.contextValue;
|
||||
public readonly commandId: string = 'cosmosDB.openDocument';
|
||||
public document: IMongoDocument;
|
||||
public readonly parent: MongoCollectionTreeItem;
|
||||
|
||||
private _label: string;
|
||||
|
||||
constructor(parent: MongoCollectionTreeItem, document: IMongoDocument) {
|
||||
super(parent);
|
||||
this.document = document;
|
||||
this._label = getDocumentTreeItemLabel(this.document);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
return String(this.document!._id);
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemeAgnosticIconPath('Document.svg');
|
||||
}
|
||||
|
||||
public static async update(collection: Collection, newDocument: IMongoDocument): Promise<IMongoDocument> {
|
||||
if (!newDocument._id) {
|
||||
throw new Error(`The "_id" field is required to update a document.`);
|
||||
}
|
||||
const filter: object = { _id: newDocument._id };
|
||||
const result: UpdateWriteOpResult = await collection.replaceOne(filter, _.omit(newDocument, '_id'));
|
||||
if (result.modifiedCount !== 1) {
|
||||
throw new Error(`Failed to update document with _id '${newDocument._id}'.`);
|
||||
}
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
public async refreshImpl(): Promise<void> {
|
||||
this._label = getDocumentTreeItemLabel(this.document);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
const message: string = `Are you sure you want to delete document '${this._label}'?`;
|
||||
const dialogResult = await vscode.window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
|
||||
if (dialogResult === DialogResponses.deleteResponse) {
|
||||
const deleteResult: DeleteWriteOpResultObject = await this.parent.collection.deleteOne({ _id: this.document._id });
|
||||
if (deleteResult.deletedCount !== 1) {
|
||||
throw new Error(`Failed to delete document with _id '${this.document._id}'.`);
|
||||
}
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
public async update(newDocument: IMongoDocument): Promise<IMongoDocument> {
|
||||
this.document = await MongoDocumentTreeItem.update(this.parent.collection, newDocument);
|
||||
return this.document;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtTreeItem, AzureTreeItem, GenericTreeItem } from "vscode-azureextensionui";
|
||||
import { deleteCosmosDBAccount } from '../../commands/deleteCosmosDBAccount';
|
||||
import { DocDBAccountTreeItemBase } from "../../docdb/tree/DocDBAccountTreeItemBase";
|
||||
import { IDocDBTreeRoot } from "../../docdb/tree/IDocDBTreeRoot";
|
||||
|
||||
export class TableAccountTreeItem extends DocDBAccountTreeItemBase {
|
||||
public static contextValue: string = "cosmosDBTableAccount";
|
||||
public contextValue: string = TableAccountTreeItem.contextValue;
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public initChild(): AzureTreeItem<IDocDBTreeRoot> {
|
||||
throw new Error('Table Accounts are not supported yet.');
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
return [new GenericTreeItem(this, {
|
||||
contextValue: 'tableNotSupported',
|
||||
label: 'Table Accounts are not supported yet.'
|
||||
})];
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await deleteCosmosDBAccount(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,376 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { AzureEnvironment } from 'ms-rest-azure';
|
||||
import * as vscode from 'vscode';
|
||||
import { appendExtensionUserAgent, AzExtParentTreeItem, AzExtTreeItem, AzureParentTreeItem, AzureTreeItem, GenericTreeItem, ISubscriptionContext, UserCancelledError } from 'vscode-azureextensionui';
|
||||
import { removeTreeItemFromCache } from '../commands/api/apiCache';
|
||||
import { emulatorPassword, getThemedIconPath } from '../constants';
|
||||
import { parseDocDBConnectionString } from '../docdb/docDBConnectionStrings';
|
||||
import { DocDBAccountTreeItem } from '../docdb/tree/DocDBAccountTreeItem';
|
||||
import { DocDBAccountTreeItemBase } from '../docdb/tree/DocDBAccountTreeItemBase';
|
||||
import { API, getExperienceFromApi, getExperienceQuickPick, getExperienceQuickPicks } from '../experiences';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { GraphAccountTreeItem } from '../graph/tree/GraphAccountTreeItem';
|
||||
import { connectToMongoClient } from '../mongo/connectToMongoClient';
|
||||
import { parseMongoConnectionString } from '../mongo/mongoConnectionStrings';
|
||||
import { MongoAccountTreeItem } from '../mongo/tree/MongoAccountTreeItem';
|
||||
import { TableAccountTreeItem } from '../table/tree/TableAccountTreeItem';
|
||||
import { KeyTar, tryGetKeyTar } from '../utils/keytar';
|
||||
import { SubscriptionTreeItem } from './SubscriptionTreeItem';
|
||||
|
||||
interface IPersistedAccount {
|
||||
id: string;
|
||||
// defaultExperience is not the same as API but we can't change the name due to backwards compatibility
|
||||
defaultExperience: API;
|
||||
isEmulator: boolean;
|
||||
}
|
||||
|
||||
export const AttachedAccountSuffix: string = 'Attached';
|
||||
export const MONGO_CONNECTION_EXPECTED: string = 'Connection string must start with "mongodb://" or "mongodb+srv://"';
|
||||
|
||||
const localMongoConnectionString: string = 'mongodb://127.0.0.1:27017';
|
||||
|
||||
export class AttachedAccountsTreeItem extends AzureParentTreeItem {
|
||||
public static contextValue: string = 'cosmosDBAttachedAccounts' + (process.platform === 'win32' ? 'WithEmulator' : 'WithoutEmulator');
|
||||
public readonly contextValue: string = AttachedAccountsTreeItem.contextValue;
|
||||
public readonly id: string = 'cosmosDBAttachedAccounts';
|
||||
public readonly label: string = 'Attached Database Accounts';
|
||||
public childTypeLabel: string = 'Account';
|
||||
|
||||
private readonly _serviceName: string = "ms-azuretools.vscode-cosmosdb.connectionStrings";
|
||||
private _attachedAccounts: AzureTreeItem[] | undefined;
|
||||
private _keytar: KeyTar;
|
||||
|
||||
private _root: ISubscriptionContext;
|
||||
private _loadPersistedAccountsTask: Promise<AzureTreeItem[]>;
|
||||
|
||||
constructor(parent: AzExtParentTreeItem) {
|
||||
super(parent);
|
||||
this._keytar = tryGetKeyTar();
|
||||
this._root = new AttachedAccountRoot();
|
||||
this._loadPersistedAccountsTask = this.loadPersistedAccounts();
|
||||
}
|
||||
|
||||
public get root(): ISubscriptionContext {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
public get iconPath(): string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } {
|
||||
return getThemedIconPath('ConnectPlugged.svg');
|
||||
}
|
||||
|
||||
public static validateMongoConnectionString(value: string): string | undefined {
|
||||
if (value && value.match(/^mongodb(\+srv)?:\/\//)) {
|
||||
return undefined;
|
||||
}
|
||||
return MONGO_CONNECTION_EXPECTED;
|
||||
}
|
||||
|
||||
private static validateDocDBConnectionString(value: string): string | undefined {
|
||||
try {
|
||||
parseDocDBConnectionString(value);
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
return 'Connection string must be of the form "AccountEndpoint=...;AccountKey=..."';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
if (clearCache) {
|
||||
this._attachedAccounts = undefined;
|
||||
this._loadPersistedAccountsTask = this.loadPersistedAccounts();
|
||||
}
|
||||
|
||||
const attachedAccounts: AzureTreeItem[] = await this.getAttachedAccounts();
|
||||
|
||||
return attachedAccounts.length > 0 ? attachedAccounts : [new GenericTreeItem(this, {
|
||||
contextValue: 'cosmosDBAttachDatabaseAccount',
|
||||
label: 'Attach Database Account...',
|
||||
commandId: 'cosmosDB.attachDatabaseAccount',
|
||||
includeInTreeItemPicker: true
|
||||
})];
|
||||
}
|
||||
|
||||
public isAncestorOfImpl(contextValue: string): boolean {
|
||||
switch (contextValue) {
|
||||
// We have to make sure the Attached Accounts node is not shown for commands like
|
||||
// 'Open in Portal', which only work for the non-attached version
|
||||
case GraphAccountTreeItem.contextValue:
|
||||
case MongoAccountTreeItem.contextValue:
|
||||
case DocDBAccountTreeItem.contextValue:
|
||||
case TableAccountTreeItem.contextValue:
|
||||
case SubscriptionTreeItem.contextValue:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async attachNewAccount(): Promise<void> {
|
||||
const defaultExperiencePick = await vscode.window.showQuickPick(getExperienceQuickPicks(), { placeHolder: "Select a Database Account API...", ignoreFocusOut: true });
|
||||
if (defaultExperiencePick) {
|
||||
const defaultExperience = defaultExperiencePick.data;
|
||||
let placeholder: string;
|
||||
let defaultValue: string;
|
||||
let validateInput: (value: string) => string | undefined | null;
|
||||
if (defaultExperience.api === API.MongoDB) {
|
||||
placeholder = 'mongodb://host:port';
|
||||
if (await this.canConnectToLocalMongoDB()) {
|
||||
defaultValue = placeholder = localMongoConnectionString;
|
||||
}
|
||||
validateInput = AttachedAccountsTreeItem.validateMongoConnectionString;
|
||||
} else {
|
||||
placeholder = 'AccountEndpoint=...;AccountKey=...';
|
||||
validateInput = AttachedAccountsTreeItem.validateDocDBConnectionString;
|
||||
}
|
||||
|
||||
const connectionString = await vscode.window.showInputBox({
|
||||
placeHolder: placeholder,
|
||||
prompt: 'Enter the connection string for your database account',
|
||||
validateInput: validateInput,
|
||||
ignoreFocusOut: true,
|
||||
value: defaultValue
|
||||
});
|
||||
|
||||
if (connectionString) {
|
||||
const treeItem: AzureTreeItem = await this.createTreeItem(connectionString, defaultExperience.api);
|
||||
await this.attachAccount(treeItem, connectionString);
|
||||
}
|
||||
} else {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
}
|
||||
|
||||
public async attachConnectionString(connectionString: string, api: API.MongoDB | API.Core): Promise<MongoAccountTreeItem | DocDBAccountTreeItemBase> {
|
||||
const treeItem = <MongoAccountTreeItem | DocDBAccountTreeItemBase>await this.createTreeItem(connectionString, api);
|
||||
await this.attachAccount(treeItem, connectionString);
|
||||
await this.refresh();
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
public async attachEmulator(): Promise<void> {
|
||||
let connectionString: string;
|
||||
const defaultExperiencePick = await vscode.window.showQuickPick(
|
||||
[
|
||||
getExperienceQuickPick(API.MongoDB),
|
||||
getExperienceQuickPick(API.Core)
|
||||
],
|
||||
{
|
||||
placeHolder: "Select a Database Account API...",
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (defaultExperiencePick) {
|
||||
const defaultExperience = defaultExperiencePick.data;
|
||||
let port: number;
|
||||
if (defaultExperience.api === API.MongoDB) {
|
||||
port = vscode.workspace.getConfiguration().get<number>("cosmosDB.emulator.mongoPort");
|
||||
} else {
|
||||
port = vscode.workspace.getConfiguration().get<number>("cosmosDB.emulator.port");
|
||||
}
|
||||
if (port) {
|
||||
if (defaultExperience.api === API.MongoDB) {
|
||||
// Mongo shell doesn't parse passwords with slashes, so we need to URI encode it. The '/' before the options is required by mongo conventions
|
||||
connectionString = `mongodb://localhost:${encodeURIComponent(emulatorPassword)}@localhost:${port}/?ssl=true`;
|
||||
} else {
|
||||
connectionString = `AccountEndpoint=https://localhost:${port}/;AccountKey=${emulatorPassword};`;
|
||||
}
|
||||
const label = `${defaultExperience.shortName} Emulator`;
|
||||
const treeItem: AzureTreeItem = await this.createTreeItem(connectionString, defaultExperience.api, label);
|
||||
if (treeItem instanceof DocDBAccountTreeItem || treeItem instanceof GraphAccountTreeItem || treeItem instanceof TableAccountTreeItem || treeItem instanceof MongoAccountTreeItem) {
|
||||
// CONSIDER: Why isn't this passed in to createTreeItem above?
|
||||
treeItem.root.isEmulator = true;
|
||||
}
|
||||
await this.attachAccount(treeItem, connectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async detach(node: AzureTreeItem): Promise<void> {
|
||||
const attachedAccounts: AzureTreeItem[] = await this.getAttachedAccounts();
|
||||
|
||||
const index = attachedAccounts.findIndex((account) => account.fullId === node.fullId);
|
||||
if (index !== -1) {
|
||||
attachedAccounts.splice(index, 1);
|
||||
if (this._keytar) {
|
||||
await this._keytar.deletePassword(this._serviceName, node.id); // intentionally using 'id' instead of 'fullId' for the sake of backwards compatability
|
||||
await this.persistIds(attachedAccounts);
|
||||
}
|
||||
|
||||
if (node instanceof MongoAccountTreeItem) {
|
||||
const parsedCS = await parseMongoConnectionString(node.connectionString);
|
||||
removeTreeItemFromCache(parsedCS);
|
||||
} else if (node instanceof DocDBAccountTreeItemBase) {
|
||||
const parsedCS = parseDocDBConnectionString(node.connectionString);
|
||||
removeTreeItemFromCache(parsedCS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getAttachedAccounts(): Promise<AzureTreeItem[]> {
|
||||
if (!this._attachedAccounts) {
|
||||
try {
|
||||
this._attachedAccounts = await this._loadPersistedAccountsTask;
|
||||
} catch {
|
||||
this._attachedAccounts = [];
|
||||
throw new Error('Failed to load persisted Database Accounts. Reattach the accounts manually.');
|
||||
}
|
||||
}
|
||||
|
||||
return this._attachedAccounts;
|
||||
}
|
||||
|
||||
private async canConnectToLocalMongoDB(): Promise<boolean> {
|
||||
try {
|
||||
const db = await connectToMongoClient(localMongoConnectionString, appendExtensionUserAgent());
|
||||
// grandfathered in
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
db.close();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async attachAccount(treeItem: AzureTreeItem, connectionString: string): Promise<void> {
|
||||
const attachedAccounts: AzureTreeItem[] = await this.getAttachedAccounts();
|
||||
|
||||
if (attachedAccounts.find(s => s.id === treeItem.id)) {
|
||||
vscode.window.showWarningMessage(`Database Account '${treeItem.id}' is already attached.`);
|
||||
} else {
|
||||
attachedAccounts.push(treeItem);
|
||||
if (this._keytar) {
|
||||
await this._keytar.setPassword(this._serviceName, treeItem.id, connectionString);
|
||||
await this.persistIds(attachedAccounts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPersistedAccounts(): Promise<AzureTreeItem[]> {
|
||||
const persistedAccounts: AzureTreeItem[] = [];
|
||||
const value: string | undefined = ext.context.globalState.get(this._serviceName);
|
||||
if (value && this._keytar) {
|
||||
const accounts: (string | IPersistedAccount)[] = JSON.parse(value);
|
||||
await Promise.all(accounts.map(async account => {
|
||||
let id: string;
|
||||
let label: string;
|
||||
let api: API;
|
||||
let isEmulator: boolean;
|
||||
if (typeof (account) === 'string') {
|
||||
// Default to Mongo if the value is a string for the sake of backwards compatiblity
|
||||
// (Mongo was originally the only account type that could be attached)
|
||||
id = account;
|
||||
api = API.MongoDB;
|
||||
label = `${account} (${getExperienceFromApi(api).shortName})`;
|
||||
isEmulator = false;
|
||||
} else {
|
||||
id = (<IPersistedAccount>account).id;
|
||||
api = (<IPersistedAccount>account).defaultExperience;
|
||||
isEmulator = (<IPersistedAccount>account).isEmulator;
|
||||
label = isEmulator ? `${getExperienceFromApi(api).shortName} Emulator` : `${id} (${getExperienceFromApi(api).shortName})`;
|
||||
}
|
||||
const connectionString: string = await this._keytar.getPassword(this._serviceName, id);
|
||||
persistedAccounts.push(await this.createTreeItem(connectionString, api, label, id, isEmulator));
|
||||
}));
|
||||
}
|
||||
|
||||
return persistedAccounts;
|
||||
}
|
||||
|
||||
private async createTreeItem(connectionString: string, api: API, label?: string, id?: string, isEmulator?: boolean): Promise<AzureTreeItem> {
|
||||
let treeItem: AzureTreeItem;
|
||||
// tslint:disable-next-line:possible-timing-attack // not security related
|
||||
if (api === API.MongoDB) {
|
||||
if (id === undefined) {
|
||||
const parsedCS = await parseMongoConnectionString(connectionString);
|
||||
id = parsedCS.fullId;
|
||||
}
|
||||
|
||||
label = label || `${id} (${getExperienceFromApi(api).shortName})`;
|
||||
treeItem = new MongoAccountTreeItem(this, id, label, connectionString, isEmulator);
|
||||
} else {
|
||||
const parsedCS = parseDocDBConnectionString(connectionString);
|
||||
|
||||
label = label || `${parsedCS.accountId} (${getExperienceFromApi(api).shortName})`;
|
||||
switch (api) {
|
||||
case API.Table:
|
||||
treeItem = new TableAccountTreeItem(this, parsedCS.accountId, label, parsedCS.documentEndpoint, parsedCS.masterKey, isEmulator);
|
||||
break;
|
||||
case API.Graph:
|
||||
treeItem = new GraphAccountTreeItem(this, parsedCS.accountId, label, parsedCS.documentEndpoint, undefined, parsedCS.masterKey, isEmulator);
|
||||
break;
|
||||
case API.Core:
|
||||
treeItem = new DocDBAccountTreeItem(this, parsedCS.accountId, label, parsedCS.documentEndpoint, parsedCS.masterKey, isEmulator);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected defaultExperience "${api}".`);
|
||||
}
|
||||
}
|
||||
|
||||
treeItem.contextValue += AttachedAccountSuffix;
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
private async persistIds(attachedAccounts: AzureTreeItem[]): Promise<void> {
|
||||
const value: IPersistedAccount[] = attachedAccounts.map((node: AzureTreeItem) => {
|
||||
let api: API;
|
||||
let isEmulator: boolean;
|
||||
if (node instanceof MongoAccountTreeItem || node instanceof DocDBAccountTreeItem || node instanceof GraphAccountTreeItem || node instanceof TableAccountTreeItem) {
|
||||
isEmulator = node.root.isEmulator;
|
||||
}
|
||||
if (node instanceof MongoAccountTreeItem) {
|
||||
api = API.MongoDB;
|
||||
} else if (node instanceof GraphAccountTreeItem) {
|
||||
api = API.Graph;
|
||||
} else if (node instanceof TableAccountTreeItem) {
|
||||
api = API.Table;
|
||||
} else if (node instanceof DocDBAccountTreeItem) {
|
||||
api = API.Core;
|
||||
} else {
|
||||
throw new Error(`Unexpected account node "${node.constructor.name}".`);
|
||||
}
|
||||
return { id: node.id, defaultExperience: api, isEmulator: isEmulator };
|
||||
});
|
||||
await ext.context.globalState.update(this._serviceName, JSON.stringify(value));
|
||||
}
|
||||
}
|
||||
|
||||
class AttachedAccountRoot implements ISubscriptionContext {
|
||||
private _error: Error = new Error('Cannot retrieve Azure subscription information for an attached account.');
|
||||
|
||||
public get credentials(): ServiceClientCredentials {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get subscriptionDisplayName(): string {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get subscriptionId(): string {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get subscriptionPath(): string {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get tenantId(): string {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get userId(): string {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
public get environment(): AzureEnvironment {
|
||||
throw this._error;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtTreeItem, AzureAccountTreeItemBase, IActionContext, ISubscriptionContext } from 'vscode-azureextensionui';
|
||||
import { ext } from '../extensionVariables';
|
||||
import { AttachedAccountsTreeItem } from './AttachedAccountsTreeItem';
|
||||
import { SubscriptionTreeItem } from './SubscriptionTreeItem';
|
||||
|
||||
export class AzureAccountTreeItemWithAttached extends AzureAccountTreeItemBase {
|
||||
public constructor(testAccount?: {}) {
|
||||
super(undefined, testAccount);
|
||||
ext.attachedAccountsNode = new AttachedAccountsTreeItem(this);
|
||||
}
|
||||
|
||||
public createSubscriptionTreeItem(root: ISubscriptionContext): SubscriptionTreeItem {
|
||||
return new SubscriptionTreeItem(this, root);
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
|
||||
const children: AzExtTreeItem[] = await super.loadMoreChildrenImpl(clearCache, context);
|
||||
return children.concat(ext.attachedAccountsNode);
|
||||
}
|
||||
|
||||
public compareChildrenImpl(item1: AzExtTreeItem, item2: AzExtTreeItem): number {
|
||||
if (item1 instanceof AttachedAccountsTreeItem) {
|
||||
return 1;
|
||||
} else if (item2 instanceof AttachedAccountsTreeItem) {
|
||||
return -1;
|
||||
} else {
|
||||
return super.compareChildrenImpl(item1, item2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureWizardPromptStep, IAzureQuickPickItem } from 'vscode-azureextensionui';
|
||||
import { Experience, getExperienceQuickPicks } from '../../experiences';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { ICosmosDBWizardContext } from './ICosmosDBWizardContext';
|
||||
|
||||
export class CosmosDBAccountApiStep extends AzureWizardPromptStep<ICosmosDBWizardContext> {
|
||||
public async prompt(wizardContext: ICosmosDBWizardContext): Promise<void> {
|
||||
const picks: IAzureQuickPickItem<Experience>[] = getExperienceQuickPicks();
|
||||
|
||||
const result: IAzureQuickPickItem<Experience> = await ext.ui.showQuickPick(picks, {
|
||||
placeHolder: "Select an API for your Cosmos DB account..."
|
||||
});
|
||||
|
||||
wizardContext.defaultExperience = result.data;
|
||||
}
|
||||
|
||||
public shouldPrompt(wizardContext: ICosmosDBWizardContext): boolean {
|
||||
return !wizardContext.defaultExperience;
|
||||
}
|
||||
}
|