+
Select your collection from the left sidebar to get started: {{ collectionName2 !== undefined ? ' ' + collectionName2 : '' }}
+
To generate your desired query, fill in the details in the appearing fields and use the graphical elements to fine-tune your selections. The query will be dynamically generated based on the information you provide, allowing you to customize the details to your exact specifications.
+
+
Choose what you want to do:
+
+
+
+
+
+
clear
+
+
+
+
+
+
Basics:
+
+ You can begin to build your query by filling in the input fields. Here's an example of a find() query:
+
+
+
db.yourCollection.find({{"{" }} "name": "Jane"})
+
{{ "{" }} "name": "Jane" }
specifies the criteria for selecting documents. In this case, the query will select documents where the value of the
name field is equal to
"Jane" .
+
+
+ To recreate this query, you can write
name
into the first input field. Next, select
equals
from the dropdown menu. Finally, enter
"Jane"
into the last input field. Don't forget the quotation marks if it is a string you're looking for.
+
+
+
Additional Features:
+
+ If you want to add more criterias to your search, you can use the additional features. Here's a step-by-step guide:
+
+
+ Adding new keys: You can add new keys by clicking on the plus-button. To delete a key, click on the trash-button.
+
+ Defining key-objects: You can define key-objects by creating a new key and adding key-objects to it through the plus-button. For example, if you want to add an address key containing objects street and number , you can create a new key address
, then add the key-object street
, and then the object number
to it.
+
+ Using Boolean Operators: You can use Boolean Operators (AND
, OR
) to create more complex conditions. Every Boolean Operator should be closed by an END
on the same depth level. You can do this through the plus-button. You can add multiple Boolean Operators in succession to create nested conditions. Every row between the condition and its END
is logically connected.
+
+ Drag and Drop: You can also drag and drop rows to transfer them inside the logical Operators or insert them into a different order. This can be useful if you made a mistake and need to correct the order of the criteria.
+
+
+
+
+
+
+
+
+
+
+
clear
+
+
+
+
+
+ An aggregation pipeline consists of one or more stages that process documents:
+
+
+
+ Each stage performs an operation on the input documents. For example, a stage can filter documents, group documents, and calculate values.
+ The documents that are the output from a stage are passed to the next stage.
+ An aggregation pipeline can return results for groups of documents. For example, return the total, average, maximum, and minimum values.
+
+
Basics:
+
+ This interface has per default an aggregation pipeline consisting of the stages $match, $group and $sort. The following aggregation pipeline example contains three stages and returns the total order quantity of medium size pizzas grouped by pizza name and sorted by totalQuantity:
+
+
+
+ db.pizzas.aggregate([{{"{" }}
+ $match:{{"{" }}size: "medium"}},{{"{" }}
+ $group:{{"{" }}_id: "$name", totalQuantity: {{"{" }}$sum: "$quantity"}}},{{"{" }}
+ $sort:{{"{" }}totalQuantity: -1}}])
+
+
+ The $match stage:
+
+
+ Filters the pizza documents to pizzas with a size of medium.
+ Passes the remaining documents to the $group stage.
+
+ The $group stage:
+
+ Groups the remaining documents by pizza name.
+ Uses $sum to calculate the total quantity for each pizza name. The total is stored in the totalQuantity field returned by the aggregation pipeline.
+
+ The $sort stage:
+
+ Sorts the totalQuantity in descending order
+
+ To recreate this query, you can follow these steps:
+
+
+ In the $match stage, write size
into the first input field, select equal
from the dropdown menu and write "medium"
in the last input field. This will match all documents where the "size" field is equal to "medium".
+
+ In the $group stage:
+
+ _id
should be written as default in the first input field. Then write in the last input field name
. This will group the documents by the "name" field.
+ add a new key and write totalQuantity
into the first input field, select sum
from the dropdown and write quantity
in the last input field. This will calculate the sum of the "quantity" field for each group
+
+
+
+ In the $sort stage, write totalQuantity
into the first input field and select descending
from the dropdown menu. This will sort the results in descending order based on the "totalQuantity" field.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Match stage
+
+
+
+
+
+
+ {{fieldNumber}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= this.mqlFieldsMATCH[i+1][5]">
+
+
+ equals
+ does not equal
+ is greater than
+ is less than
+ contains
+ does not contain
+ is type
+
+
+
+
+
+
= this.mqlFieldsMATCH[i+1][5]">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Group stage
+
+
+
+
+
+
+
+
= this.mqlFieldsGROUP[i+1][5]">
+
+
+
+
+
+
+
+
+
+
+
+
= this.mqlFieldsGROUP[i+1][5]">
+
+
+
+ average
+ count
+ max
+ min
+ sum
+
+
+
+
+
+
+
= this.mqlFieldsGROUP[i+1][5]">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sort stage
+
+
+
+
+
+
+
= this.mqlFieldsSORT[i+1][5]">
+
+
+ Sort ascending
+ Sort descending
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Find
+
+
+
+
+ {{fieldNumber}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= this.mqlFields[i+1][5]">
+
+
+ equals
+ does not equal
+ is greater than
+ is less than
+ contains
+ does not contain
+ is type
+
+
+
+
+
+
= this.mqlFields[i+1][5]">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Generated query
+
Edit your code if needed.
+
-
-
- Successfully executed
-
+
Execute
+
Loading
+
+
+
+ Query Result
-
-
-
+
+
+
+
+
+
+
Error:
+
{{resultSet.error}}
+
+
+
+
+ Successfully executed
+
+
+
+
-
-
diff --git a/src/app/views/querying/graphical-querying/graphical-querying.component.scss b/src/app/views/querying/graphical-querying/graphical-querying.component.scss
index eb077a81..6570cb8d 100644
--- a/src/app/views/querying/graphical-querying/graphical-querying.component.scss
+++ b/src/app/views/querying/graphical-querying/graphical-querying.component.scss
@@ -56,3 +56,59 @@
padding-right: 0;
}
+//Dropdown
+.json-dropdown {
+ display: block;
+ // center block
+ margin: 0 auto;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.show-error {
+ display: block !important;
+}
+
+.cypher-sidebar {
+ background-color: #f0f3f5;
+ border: 1px solid #c8ced3;
+}
+
+.mql-second-text-fields {
+ background-color: rgb(210, 210, 210, 0.9);
+ padding: 1em;
+ border: 1px rgba(0, 0, 0, 0.125) solid;
+}
+
+.text-fields {
+ background-color: #dcdcdc;
+ padding: 1em;
+ margin-bottom: 1.5em;
+ border: 1px rgba(0, 0, 0, 0.125) solid;
+}
+
+//Label Container
+.label-container {
+ display: flex;
+}
+
+.label-container p {
+ margin-right: 2.5em;
+}
+
+.label-node {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ border: 1px solid black;
+}
+
+.label-relation {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ border-radius: 1.5em;
+ border: 1px solid black;
+}
+
+
+
diff --git a/src/app/views/querying/graphical-querying/graphical-querying.component.ts b/src/app/views/querying/graphical-querying/graphical-querying.component.ts
index 686bfa0c..caff774b 100644
--- a/src/app/views/querying/graphical-querying/graphical-querying.component.ts
+++ b/src/app/views/querying/graphical-querying/graphical-querying.component.ts
@@ -16,6 +16,10 @@ import {WebuiSettingsService} from '../../../services/webui-settings.service';
import {WebSocket} from '../../../services/webSocket';
import {BsModalRef, BsModalService} from 'ngx-bootstrap/modal';
import {ViewInformation} from '../../../components/data-view/data-view.component';
+// new import added to extent graph
+import {TableConfig} from '../../../components/data-view/data-table/table-config';
+import * as d3 from 'd3';
+
@Component({
selector: 'app-graphical-querying',
@@ -61,14 +65,101 @@ export class GraphicalQueryingComponent implements OnInit, AfterViewInit, OnDest
this.initWebSocket();
}
- ngOnInit() {
- this._leftSidebar.open();
- this.initSchema();
+ // new additions:
+
+ nodeLabels: string[] = ['Movie', 'Person', 'Character']; //Hardcoded for the moment
+ relationLabels: string[] = ['DIRECTED_BY', 'HAS_ACTOR', 'PLAYS']; //Hardcoded for the moment
+ lang: string; // 'sql', 'cypher', 'mql'
+ tableId: string;
+ config: TableConfig;
+ // cypher input fields
+ cypherFields: string[][] = [['','','','','']]; // Matrix à [n,5] size -> [1] = Relationship, [2] = Node, [3] = 2.Relationship, [4] = 2.Node
+
+ cypherNode: string[][][] = [[['']]];
+
+ cypherNode2: string[][][] = [[['']]];
+
+ cypherNode3: string[][][] = [[['']]];
+
+ cypherRel: string[][][] = [[['']]];
+
+ cypherRel2: string[][][] = [[['']]];
+ cypherReturnDrop: string[] = [''];
+ cypherReturnProp: string[] = [''];
+ cypherReturn2: string[] = [''];
+
+ returnDropdown: string[] = [''];
+ cypherDropdown: string[] = [];
+ fieldListCypher: string[] = ['MATCH'];
+
+ fieldListCypherNode: string[][] = [['NODE']];
+
+ fieldListCypherNode2: string[][] = [['NODE']];
+
+ fieldListCypherNode3: string[][] = [['NODE']];
+
+ fieldListCypherRel2: string[][] = [['NODE']];
+
+ fieldListCypherRel: string[][] = [['NODE']];
+ fieldList: string[] = ['0'];
+ fieldListMATCH: string[] = ['0'];
+ fieldListGROUP: string[] = ['0'];
+ fieldListSORT: string[] = ['0'];
+ fieldDepthCounter = 0;
+ fieldDepthCounterMATCH = 0;
+ fieldDepthCounterGROUP = 0;
+ fieldDepthCounterSORT = 0;
+ logicalDepthCounter = 0;
+ logicalDepthCounterMATCH = 0;
+ // mql input fields
+ mqlFields: any[][] = [['','','','',0,0]]; //mqlText1 = 0, mqlDropdown = 1, mqlText2 = 3, mqlTextX = 4, fieldDepth = 5, logicalDepth = 6
+
+ mqlFieldsMATCH: any[][] = [['','','','',0,0]];
+ mqlFieldsGROUP: any[][] = [['_id','','','_id',0,0]];
+
+ mqlFieldsSORT: any[][] = [['','','','',0,0]];
+ activeNamespace: string; // same usage as console.components.ts
+ collectionName: string;
+ collectionName2: string;
+ graphName: string;
+
+ logicalOperatorStack: string[] = [];
+ logicalOperatorStackMATCH: string[] = [];
+
+ fieldCounter = 0;
+ fieldCounterMATCH = 0;
+ fieldCounterGROUP = 0;
+ fieldCounterSORT = 0;
+ private readonly LOCAL_STORAGE_NAMESPACE_KEY = 'polypheny-namespace'; // same usage as console.components.ts
+
+ show = false;
+ showFIND = false;
+ showCypherMatch = false;
+ showMATCH = false;
+ showGROUP = false;
+
+ showSORT = false;
+
+ showFIND2 = false;
+ showMATCH2 = false;
+ private debounce: any;
+ private debounceDelay = 200;
+
+ private initialrect: DOMRect;
+
+ mqlType: string;
+
+ activeMode: string;
+
+
+ ngOnInit() {
+ this._leftSidebar.open();
+ this.initSchema(this.lang);
this.initGraphicalQuerying();
const sub = this._crud.onReconnection().subscribe(
b => {
if (b) {
- this.initSchema();
+ this.initSchema(this.lang);
}
}
);
@@ -99,35 +190,78 @@ export class GraphicalQueryingComponent implements OnInit, AfterViewInit, OnDest
);
}
- initSchema() {
- this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 3, false, false, [DataModels.RELATIONAL])).subscribe(
- res => {
- const nodeAction = (tree, node, $event) => {
- if (!node.isActive && node.isLeaf) {
- this.addCol(node.data);
- node.setIsActive(true, true);
- } else if (node.isActive && node.isLeaf) {
- node.setIsActive(false, true);
- this.removeCol(node.data.id);
-
- //deletes the selection if nothing is choosen
- if (this.selectedColumn['column'].toString() === node.data.id) {
- this.selectedCol([]);
+ /**
+ * changes language and sidebar depending on chosen radiobutton
+ */
+ initSchema(lang:string) {
+ console.log(lang);
+ if (lang === 'sql') {
+ this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 3, false, false, true)).subscribe(
+ res => {
+ const nodeAction = (tree, node, $event) => {
+ if (!node.isActive && node.isLeaf) {
+ this.addCol(node.data);
+ node.setIsActive(true, true);
+ } else if (node.isActive && node.isLeaf) {
+ node.setIsActive(false, true);
+ this.removeCol(node.data.id);
+ //deletes the selection if nothing is choosen
+ if (this.selectedColumn['column'].toString() === node.data.id) {
+ this.selectedCol([]);
+ }
}
+ };
+ const schemaTemp =
res;
+ const schema = [];
+ for (const s of schemaTemp) {
+ const node = SidebarNode.fromJson(s, {allowRouting: false, autoActive: false, action: nodeAction});
+ schema.push(node);
}
- };
-
- const schemaTemp = res;
- const schema = [];
- for (const s of schemaTemp) {
- const node = SidebarNode.fromJson(s, {allowRouting: false, autoActive: false, action: nodeAction});
- schema.push(node);
+ this._leftSidebar.setNodes(schema);
+ this._leftSidebar.open();
}
-
- this._leftSidebar.setNodes(schema);
- this._leftSidebar.open();
- }
- );
+ );
+ }
+ else if (lang === 'cypher') {
+ this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 1, false, false, true)).subscribe(
+ res => {
+ const nodeAction = (tree, node, $event) => {
+ console.log(node.id);
+ this.graphName = node.id;
+ this.setDefaultDB(node.id); //changes the activeNamespace to the one chosen on the left side
+ };
+ const schemaTemp = res;
+ const schema = [];
+ for (const s of schemaTemp) {
+ const node = SidebarNode.fromJson(s, {allowRouting: false, autoActive: false, action: nodeAction});
+ schema.push(node);
+ }
+ this._leftSidebar.setNodes(schema);
+ this._leftSidebar.open();
+ }
+ );
+ }
+ else if (lang === 'mql') {
+ this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 2, false, false, true)).subscribe(
+ res => {
+ const nodeAction = (tree, node, $event) => {
+ if (!node.isActive && node.isLeaf) {
+ this.setDefaultDB(node.parent.id);
+ this.collectionName = node.displayField;
+ this.collectionName2 = node.id;
+ }
+ };
+ const schemaTemp = res;
+ const schema = [];
+ for (const s of schemaTemp) {
+ const node = SidebarNode.fromJson(s, {allowRouting: false, autoActive: false, action: nodeAction});
+ schema.push(node);
+ }
+ this._leftSidebar.setNodes(schema);
+ this._leftSidebar.open();
+ }
+ );
+ }
}
initGraphicalQuerying() {
@@ -329,6 +463,1156 @@ export class GraphicalQueryingComponent implements OnInit, AfterViewInit, OnDest
return '"' + k.split('.').join('"."') + '"';
}
+ onCloseModal() {
+ this.activeMode = undefined;
+ }
+
+ /**
+ * resets interface when language is changed by the radiobutton
+ */
+ setDefaultState(inputlang: string) { //sets the field values to zero if the user switches languages
+ switch (inputlang) {
+ case 'sql':
+ this.collectionName = undefined;
+ this.collectionName2 = undefined;
+ this.graphName = undefined;
+ this.clearInput('find');
+ this.clearInput('aggr');
+ this.clearInput('cypher');
+ break;
+ case 'cypher':
+ this.collectionName = undefined;
+ this.collectionName2 = undefined;
+ this.clearInput('find');
+ this.clearInput('aggr');
+ break;
+ case 'mql':
+ this.graphName = undefined;
+ this.clearInput('cypher');
+ break;
+ }
+ }
+ /**
+ * clears all fields with the clear-button
+ */
+ clearInput(type: string) {
+ if (type === 'cypher'){
+ this.cypherFields = [['','','','','']];
+ this.cypherNode = [[[]]];
+ this.cypherNode2 = [[[]]];
+ this.cypherNode3 = [[[]]];
+ this.cypherReturnProp = [''];
+ this.cypherReturnDrop = [''];
+ this.cypherReturn2 = [''];
+ this.fieldListCypher = ['MATCH'];
+ this.fieldListCypherNode = [['NODE']];
+ this.fieldListCypherNode2 = [['NODE']];
+ this.fieldListCypherNode3 = [['NODE']];
+ this.fieldListCypherRel2 = [['NODE']];
+ this.fieldListCypherRel = [['NODE']];
+ }
+ else if (type === 'find') {
+ this.mqlFields = [['','','','',0,0]];
+ this.fieldCounter = 0;
+ this.logicalOperatorStack = [];
+ this.fieldList = ['0'];
+ this.fieldDepthCounter = 0;
+ this.logicalDepthCounter = 0;
+ }
+ else {
+ this.mqlFieldsMATCH = [['','','','',0,0]];
+ this.mqlFieldsGROUP = [['_id','','','_id',0,0]];
+ this.mqlFieldsSORT = [['','','','',0,0]];
+ this.fieldCounterMATCH = 0;
+ this.logicalOperatorStackMATCH = [];
+ this.fieldListMATCH = ['0'];
+ this.fieldDepthCounterMATCH = 0;
+ this.logicalDepthCounterMATCH = 0;
+ this.fieldCounterGROUP = 0;
+ this.fieldListGROUP = ['0'];
+ this.fieldDepthCounterGROUP = 0;
+ this.fieldCounterSORT = 0;
+ this.fieldListSORT = ['0'];
+ this.fieldDepthCounterSORT = 0;
+ }
+ }
+
+ /**
+ * inspired by the same function in console.component.ts
+ */
+ private setDefaultDB(name: string) {
+ name = name.trim();
+ this.activeNamespace = name;
+ localStorage.setItem(this.LOCAL_STORAGE_NAMESPACE_KEY, name);
+ }
+
+ /**
+ * taken from json-editor.component.ts - showing the option menu
+ */
+ setMenuShow(doShow: boolean, instant = false, mqlCase: string) {
+ switch (mqlCase) {
+ case 'Cypher':
+ if (instant) {
+ this.show = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.show = false;
+ }, this.debounceDelay);
+ } else {
+ this.show = true;
+ }
+ break;
+ case 'CypherMatch':
+ if (instant) {
+ this.showCypherMatch = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showCypherMatch = false;
+ }, this.debounceDelay);
+ } else {
+ this.showCypherMatch = true;
+ }
+ break;
+ case 'Find':
+ if (instant) {
+ this.showFIND = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showFIND = false;
+ }, this.debounceDelay);
+ } else {
+ this.showFIND = true;
+ }
+ break;
+ case 'Aggr1':
+ if (instant) {
+ this.showMATCH = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showMATCH = false;
+ }, this.debounceDelay);
+ } else {
+ this.showMATCH = true;
+ }
+ break;
+ case 'Aggr2':
+ if (instant) {
+ this.showGROUP = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showGROUP = false;
+ }, this.debounceDelay);
+ } else {
+ this.showGROUP = true;
+ }
+ break;
+ case 'Aggr3':
+ if (instant) {
+ this.showSORT = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showSORT = false;
+ }, this.debounceDelay);
+ } else {
+ this.showSORT = true;
+ }
+ break;
+ }
+ }
+
+ /**
+ * taken from json-editor.component.ts
+ */
+ menuEnter(mqlCase: string) {
+ switch (mqlCase) {
+ case 'Cypher':
+ if (this.show) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ case 'Find':
+ if (this.showFIND) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ case 'Aggr1':
+ if (this.showMATCH) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ case 'Aggr2':
+ if (this.showGROUP) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ case 'Aggr3':
+ if (this.showSORT) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ }
+ }
+
+ setMenuShow2(doShow: boolean, instant = false, mqlCase: string) {
+ switch (mqlCase) {
+ case 'Find':
+ if (instant) {
+ this.showFIND2 = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showFIND2 = false;
+ }, this.debounceDelay);
+ } else {
+ this.showFIND2 = true;
+ }
+ break;
+ case 'Aggr1':
+ if (instant) {
+ this.showMATCH2 = doShow;
+ return;
+ }
+ if (!doShow) {
+ this.debounce = setTimeout(() => {
+ this.showMATCH2 = false;
+ }, this.debounceDelay);
+ } else {
+ this.showMATCH2 = true;
+ }
+ break;
+ }
+ }
+ menuEnter2(mqlCase: string) {
+ switch (mqlCase) {
+ case 'Find':
+ if (this.showFIND2) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ case 'Aggr1':
+ if (this.showMATCH2) {
+ clearTimeout(this.debounce);
+ }
+ break;
+ }
+
+ }
+
+ /**
+ * drag and drop for cypher labels
+ */
+ drag(event: any) {
+ event.dataTransfer.setData('text/plain', event.target.innerText);
+ }
+
+ /**
+ * /drag and drop for mql rows
+ */
+ onDragStart(event: DragEvent, str: string) {
+ event.dataTransfer?.setData('text/plain', str);
+ const targetElement = event.currentTarget as HTMLElement;
+ this.initialrect = targetElement.getBoundingClientRect();
+ }
+ onDragOver(event: DragEvent) {
+ event.preventDefault();
+ }
+ onDrop(event: DragEvent, mqlCase: string) {
+ event.preventDefault();
+ const data = event.dataTransfer?.getData('text/plain');
+ switch (mqlCase) {
+ case 'Find':
+ if (data) {
+ const index = this.fieldList.indexOf(data);
+ const targetElement = event.currentTarget as HTMLElement;
+ setTimeout(() => {
+ const rect = targetElement.getBoundingClientRect();
+ const mouseY = rect.top - this.initialrect.top;
+ const targetIndex = Math.round(mouseY / rect.height) + index;
+ if (index !== -1 && targetIndex !== -1 && index !== targetIndex) {
+ this.fieldList.splice(targetIndex, 0, this.fieldList.splice(index, 1)[0]);
+ this.mqlFields.splice(targetIndex, 0, this.mqlFields.splice(index, 1)[0]);
+ // Logical Operators
+ this.logicalOperatorStack.splice(targetIndex, 0, this.logicalOperatorStack.splice(index, 1)[0]);
+ }
+ this.generateMQL();
+ }, 0);
+ }
+ break;
+ case 'Aggr1':
+ if (data) {
+ const index = this.fieldListMATCH.indexOf(data);
+ const targetElement = event.currentTarget as HTMLElement;
+ setTimeout(() => {
+ const rect = targetElement.getBoundingClientRect();
+ const mouseY = rect.top - this.initialrect.top;
+ const targetIndex = Math.round(mouseY / rect.height) + index;
+ if (index !== -1 && targetIndex !== -1 && index !== targetIndex) {
+ this.fieldListMATCH.splice(targetIndex, 0, this.fieldListMATCH.splice(index, 1)[0]);
+ this.mqlFieldsMATCH.splice(targetIndex, 0, this.mqlFieldsMATCH.splice(index, 1)[0]);
+ // Logical Operators
+ this.logicalOperatorStackMATCH.splice(targetIndex, 0, this.logicalOperatorStackMATCH.splice(index, 1)[0]);
+ }
+ this.generateMQL();
+ }, 0);
+ }
+ break;
+ case 'Aggr2':
+ if (data) {
+ const index = this.fieldListGROUP.indexOf(data);
+ const targetElement = event.currentTarget as HTMLElement;
+ setTimeout(() => {
+ const rect = targetElement.getBoundingClientRect();
+ const mouseY = rect.top - this.initialrect.top;
+ const targetIndex = Math.round(mouseY / rect.height) + index;
+ if (index !== -1 && targetIndex !== -1 && index !== targetIndex) {
+ this.fieldListGROUP.splice(targetIndex, 0, this.fieldListGROUP.splice(index, 1)[0]);
+ this.mqlFieldsGROUP.splice(targetIndex, 0, this.mqlFieldsGROUP.splice(index, 1)[0]);
+ // Logical Operators
+ }
+ this.generateMQL();
+ }, 0);
+ }
+ break;
+ case 'Aggr3':
+ if (data) {
+ const index = this.fieldListSORT.indexOf(data);
+ const targetElement = event.currentTarget as HTMLElement;
+ setTimeout(() => {
+ const rect = targetElement.getBoundingClientRect();
+ const mouseY = rect.top - this.initialrect.top;
+ const targetIndex = Math.round(mouseY / rect.height) + index;
+ if (index !== -1 && targetIndex !== -1 && index !== targetIndex) {
+ this.fieldListSORT.splice(targetIndex, 0, this.fieldListSORT.splice(index, 1)[0]);
+ this.mqlFieldsSORT.splice(targetIndex, 0, this.mqlFieldsSORT.splice(index, 1)[0]);
+ // Logical Operators
+ }
+ this.generateMQL();
+ }, 0);
+ }
+ break;
+ }
+ }
+
+ /**
+ * similiar to data-graph.component.ts - generates the color of the node/relationship labels
+ */
+ generateColor(index: number): string {
+ const color = d3.interpolateSinebow;
+ const ratio = 1 / (this.nodeLabels.length + this.relationLabels.length);
+ return color(ratio * index);
+ }
+
+ /**
+ * adds new row
+ */
+ addCypherField(type: string) {
+ this.fieldListCypher.push(type);
+ this.cypherFields.push(['','','','','']);
+ this.fieldListCypherNode.push(['NODE']);
+ this.cypherNode.push([['']]);
+ this.fieldListCypherNode2.push(['NODE']);
+ this.cypherNode2.push([['']]);
+ this.fieldListCypherNode3.push(['NODE']);
+ this.cypherNode3.push([['']]);
+ this.fieldListCypherRel.push(['NODE']);
+ this.cypherRel.push([['']]);
+ this.fieldListCypherRel2.push(['NODE']);
+ this.cypherRel2.push([['']]);
+ }
+
+ /**
+ * add new property inside match input field
+ */
+ addCypherMatchField(index: number, field:number) {
+ if (field === 1){
+ this.fieldListCypherNode[index].push('PROP');
+ this.cypherNode[index].push([]);
+ }
+ if (field === 2){
+ this.fieldListCypherNode2[index].push('PROP');
+ this.cypherNode2[index].push([]);
+ }
+ if (field === 3){
+ this.fieldListCypherNode3[index].push('PROP');
+ this.cypherNode3[index].push([]);
+ }
+ if (field === 4){
+ this.fieldListCypherRel[index].push('PROP');
+ this.cypherRel[index].push([]);
+ }
+ if (field === 5){
+ this.fieldListCypherRel2[index].push('PROP');
+ this.cypherRel2[index].push([]);
+ }
+ }
+ /**
+ * changes between AND, OR, and NOR in the where-clause
+ */
+ changeCypherField(type: string, index: number) {
+ if (index + 1 > this.fieldListCypher.length-1) {
+ this.fieldListCypher.push(type);
+ this.cypherFields.push([]);
+ }
+ else {
+ this.fieldListCypher[index + 1] = type;
+ }
+ }
+ /**
+ * add a new mql field
+ */
+ addMQLField(mqlCase: string) { // 'normal new Field'
+ switch (mqlCase) {
+ case 'Find':
+ this.fieldCounter += 1;
+ this.fieldList.push(String(this.fieldCounter));
+ this.fieldDepthCounter = 0;
+ this.mqlFields.push([]);
+ this.mqlFields[this.fieldList.length-1][5] = 0; //Key-Object Depth
+ this.mqlFields[this.fieldList.length-1][6] = -1; //Logical Operator Depth
+ break;
+ case 'Aggr1':
+ this.fieldCounterMATCH += 1;
+ this.fieldListMATCH.push(String(this.fieldCounterMATCH));
+ this.fieldDepthCounterMATCH = 0;
+ this.mqlFieldsMATCH.push([]);
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][5] = 0;
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][6] = -1;
+ break;
+ case 'Aggr2':
+ this.fieldCounterGROUP += 1;
+ this.fieldListGROUP.push(String(this.fieldCounterGROUP));
+ this.fieldDepthCounterGROUP = 0;
+ this.mqlFieldsGROUP.push([]);
+ this.mqlFieldsGROUP[this.fieldListGROUP.length-1][5] = 0;
+ this.mqlFieldsGROUP[this.fieldListGROUP.length-1][6] = -1;
+ break;
+ case 'Aggr3':
+ this.fieldCounterSORT += 1;
+ this.fieldListSORT.push(String(this.fieldCounterSORT));
+ this.fieldDepthCounterSORT = 0;
+ this.mqlFieldsSORT.push([]);
+ this.mqlFieldsSORT[this.fieldListSORT.length-1][5] = 0;
+ this.mqlFieldsSORT[this.fieldListSORT.length-1][6] = -1;
+ break;
+ }
+ }
+ /**
+ * adds a new row with key-object field
+ */
+ addMQLFieldKeyObject(mqlCase: string) { // adding a property
+ switch (mqlCase) {
+ case 'Find':
+ this.mqlFields[this.fieldCounter][1] = undefined;// deleting equal from field before
+ this.fieldCounter += 1;
+ this.fieldDepthCounter += 1;
+ this.fieldList.push(String(this.fieldCounter)); //adding addition marker to fieldList
+ this.mqlFields.push([]);
+ this.mqlFields[this.fieldList.length-1][5] = this.fieldDepthCounter; //Key-Object Depth
+ this.mqlFields[this.fieldList.length-1][6] = -1; //Logical Operator Depth
+ break;
+ case 'Aggr1':
+ this.mqlFieldsMATCH[this.fieldCounterMATCH][1] = undefined;
+ this.fieldCounterMATCH += 1;
+ this.fieldDepthCounterMATCH += 1;
+ this.fieldListMATCH.push(String(this.fieldCounterMATCH));
+ this.mqlFieldsMATCH.push([]);
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][5] = this.fieldDepthCounterMATCH;
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][6] = -1;
+ break;
+ case 'Aggr2':
+ this.mqlFieldsGROUP[this.fieldCounterGROUP][1] = undefined;
+ this.fieldCounterGROUP += 1;
+ this.fieldDepthCounterGROUP += 1;
+ this.fieldListGROUP.push(String(this.fieldCounterGROUP));
+ this.mqlFieldsGROUP.push([]);
+ this.mqlFieldsGROUP[this.fieldListGROUP.length-1][5] = this.fieldDepthCounterGROUP;
+ this.mqlFieldsGROUP[this.fieldListGROUP.length-1][6] = -1;
+ break;
+ case 'Aggr3':
+ this.mqlFieldsSORT[this.fieldCounterSORT][1] = undefined;
+ this.fieldCounterSORT += 1;
+ this.fieldDepthCounterSORT += 1;
+ this.fieldListSORT.push(String(this.fieldCounterSORT));
+ this.mqlFieldsSORT.push([]);
+ this.mqlFieldsSORT[this.fieldListSORT.length-1][5] = this.fieldDepthCounterSORT;
+ this.mqlFieldsSORT[this.fieldListSORT.length-1][6] = -1;
+ break;
+ }
+ }
+ /**
+ * adds a new object field
+ */
+ addMQLFieldKeyObject2(mqlCase: string) { // staying on the same 'level' of property
+ switch (mqlCase) {
+ case 'Find':
+ this.fieldCounter += 1;
+ this.fieldList.push(String(this.fieldCounter)); //adding addition marker to fieldList
+ this.mqlFields.push([]);
+ this.mqlFields[this.fieldList.length - 1][5] = this.fieldDepthCounter; //Key-Object Depth
+ this.mqlFields[this.fieldList.length - 1][6] = -1; //Logical Operator Depth
+ break;
+ case 'Aggr1':
+ this.fieldCounterMATCH += 1;
+ this.fieldListMATCH.push(String(this.fieldCounterMATCH));
+ this.mqlFieldsMATCH.push([]);
+ this.mqlFieldsMATCH[this.fieldListMATCH.length - 1][5] = this.fieldDepthCounterMATCH;
+ this.mqlFieldsMATCH[this.fieldListMATCH.length - 1][6] = -1;
+ break;
+ case 'Aggr2':
+ this.fieldCounterGROUP += 1;
+ this.fieldListGROUP.push(String(this.fieldCounterGROUP));
+ this.mqlFieldsGROUP.push([]);
+ this.mqlFieldsGROUP[this.fieldListGROUP.length - 1][5] = this.fieldDepthCounterGROUP;
+ this.mqlFieldsGROUP[this.fieldListGROUP.length - 1][6] = -1;
+ break;
+ case 'Aggr3':
+ this.fieldCounterSORT += 1;
+ this.fieldListSORT.push(String(this.fieldCounterSORT));
+ this.mqlFieldsSORT.push([]);
+ this.mqlFieldsSORT[this.fieldListSORT.length - 1][5] = this.fieldDepthCounterSORT;
+ this.mqlFieldsSORT[this.fieldListSORT.length - 1][6] = -1;
+ break;
+ }
+ }
+ /**
+ * adds logical Operator AND or OR
+ */
+ addLogicalOperator(logical: string, mqlCase: string) { // adding Logical Operator AND or OR
+ switch (mqlCase) {
+ case 'Find':
+ this.fieldCounter += 1;
+ this.fieldList.push(logical);
+ this.logicalOperatorStack.push(logical);
+ this.mqlFields.push([]);
+ this.mqlFields[this.fieldList.length - 1][5] = -1; //Key-Object Depth
+ this.mqlFields[this.fieldList.length - 1][6] = this.logicalDepthCounter; //Logical Operator Depth
+ this.logicalDepthCounter += 1;
+ break;
+ case 'Aggr1':
+ this.fieldCounterMATCH += 1;
+ this.fieldListMATCH.push(logical);
+ this.logicalOperatorStackMATCH.push(logical);
+ this.mqlFieldsMATCH.push([]);
+ this.mqlFieldsMATCH[this.fieldListMATCH.length - 1][5] = -1;
+ this.mqlFieldsMATCH[this.fieldListMATCH.length - 1][6] = this.logicalDepthCounterMATCH;
+ this.logicalDepthCounterMATCH += 1;
+ break;
+ }
+ }
+ /**
+ * closes the Logical Operator with an END
+ */
+ endLogicalOperator(mqlCase: string) { // ending Logical Operator AND or OR
+ switch (mqlCase) {
+ case 'Find':
+ this.fieldCounter += 1;
+ this.logicalDepthCounter -= 1;
+ this.fieldList.push('END');
+ this.logicalOperatorStack.pop();
+ this.mqlFields.push([]);
+ this.mqlFields[this.fieldList.length-1][5] = -1; //Key-Object Depth
+ this.mqlFields[this.fieldList.length-1][6] = this.logicalDepthCounter; //Logical Operator Depth
+ break;
+ case 'Aggr1':
+ this.fieldCounterMATCH += 1;
+ this.logicalDepthCounterMATCH -= 1;
+ this.fieldListMATCH.push('END');
+ this.logicalOperatorStackMATCH.pop();
+ this.mqlFieldsMATCH.push([]);
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][5] = -1;
+ this.mqlFieldsMATCH[this.fieldListMATCH.length-1][6] = this.logicalDepthCounterMATCH;
+ break;
+ }
+ }
+ /**
+ * adds the fitting prepend text if object is part of the key-object
+ */
+ prependKeyObject(depth: number, index: number, mqlCase:string) { //creating the dot notation for objects
+ let prependText = '';
+ switch (mqlCase) {
+ case 'Find':
+ if (depth !== 0) { //does not have to iterarate through this if depth 0
+ for (let i = index; i >= 0; i--) { //searches elements in array with lower depth to append for dot-notation
+ if (this.mqlFields[i][5] < depth && this.mqlFields[i][5] !== -1) {
+ prependText += this.mqlFields[i][0] + '.';
+ break;
+ }
+ }
+ }
+ this.mqlFields[index][0] = prependText + this.mqlFields[index][3];
+ break;
+ case 'Aggr1':
+ if (depth !== 0) {
+ for (let i = this.mqlFieldsMATCH.length - 1; i >= 0; i--) {
+ if (this.mqlFieldsMATCH[i][5] < depth && this.mqlFields[i][5] !== -1) {
+ prependText += this.mqlFieldsMATCH[i][0] + '.';
+ break;
+ }
+ }
+ }
+ this.mqlFieldsMATCH[index][0] = prependText + this.mqlFieldsMATCH[index][3];
+ break;
+ case 'Aggr2':
+ if (depth !== 0) {
+ for (let i = this.mqlFieldsGROUP.length - 1; i >= 0; i--) {
+ if (this.mqlFieldsGROUP[i][5] < depth && this.mqlFields[i][5] !== -1) {
+ prependText += this.mqlFieldsGROUP[i][0] + '.';
+ break;
+ }
+ }
+ }
+ this.mqlFieldsGROUP[index][0] = prependText + this.mqlFieldsGROUP[index][3];
+ break;
+ case 'Aggr3':
+ if (depth !== 0) {
+ for (let i = this.mqlFieldsSORT.length - 1; i >= 0; i--) {
+ if (this.mqlFieldsSORT[i][5] < depth && this.mqlFields[i][5] !== -1) {
+ prependText += this.mqlFieldsSORT[i][0] + '.';
+ break;
+ }
+ }
+ }
+ this.mqlFieldsSORT[index][0] = prependText + this.mqlFieldsSORT[index][3];
+ break;
+ }
+ }
+ /**
+ * deleting a cypher field
+ */
+ deleteCypherField(x:number) {
+ this.fieldListCypher.splice(x, 1);
+ this.cypherFields.splice(x, 1);
+
+ this.fieldListCypherNode.splice(x, 1);
+ this.cypherNode.splice(x, 1);
+ this.fieldListCypherNode2.splice(x, 1);
+ this.cypherNode2.splice(x, 1);
+ this.fieldListCypherNode3.splice(x, 1);
+ this.cypherNode3.splice(x, 1);
+ this.fieldListCypherRel.splice(x, 1);
+ this.cypherRel.splice(x, 1);
+ this.fieldListCypherRel2.splice(x, 1);
+ this.cypherRel2.splice(x, 1);
+ this.generateCypher();
+ }
+ /**
+ * deleting a property field
+ */
+ deleteCypherMatchField(index: number, x:number, field: number) {
+ if (field === 1){
+ this.fieldListCypherNode[index].splice(x, 1);
+ this.cypherNode[index].splice(x, 1);
+ }
+ if (field === 2) {
+ this.fieldListCypherNode2[index].splice(x, 1);
+ this.cypherNode2[index].splice(x, 1);
+ }
+ if (field === 3) {
+ this.fieldListCypherNode3[index].splice(x, 1);
+ this.cypherNode3[index].splice(x, 1);
+ }
+ if (field === 4) {
+ this.fieldListCypherRel[index].splice(x, 1);
+ this.cypherRel[index].splice(x, 1);
+ }
+ if (field === 5) {
+ this.fieldListCypherRel2[index].splice(x, 1);
+ this.cypherRel2[index].splice(x, 1);
+ }
+ }
+ /**
+ * deleting a mql field
+ */
+ deleteMQLField(x:number, mqlCase:string) {
+ switch (mqlCase) {
+ case 'Find':
+ if (this.fieldList[x] === 'AND' || this.fieldList[x] === 'OR' || this.fieldList[x] === 'END') { //Deleting logical Operator and its END
+ const indicesToRemove = [];
+ for (let i = x; i < this.fieldList.length; i++) { // finds element and next element with same logical depth
+ if (this.mqlFields[i][6] === this.mqlFields[x][6]) { // comparing logical depths
+ indicesToRemove.push(i);
+ }
+ if (indicesToRemove.length === 2) {
+ break;
+ }
+ }
+ this.mqlFields = this.mqlFields.filter((_, index) => !indicesToRemove.includes(index));
+ this.fieldCounter = this.fieldCounter - indicesToRemove.length;
+ if (indicesToRemove[1] === this.fieldList.length && x !== 0) { //if last row of rows, we have to adapt fieldDepthCounter
+ this.fieldDepthCounter = this.mqlFields[x - 1][5];
+ }
+ this.fieldList = this.fieldList.filter((_, index) => !indicesToRemove.includes(index));
+ this.logicalOperatorStack = this.fieldList.filter((el) => el === 'AND' || el === 'OR' || el === 'END');
+ if (indicesToRemove.length === 1) { // there is no END, so the logical depth counter has to be substracted
+ this.logicalDepthCounter -= 1;
+ }
+ }
+ else { // normal field
+ this.fieldList.splice(x, 1);
+ this.mqlFields.splice(x, 1);
+ this.fieldCounter -= 1;
+ if (x === this.fieldList.length && x !== 0) { //if last row of rows, we have to adapt fieldDepthCounter
+ this.fieldDepthCounter = this.mqlFields[x-1][5];
+ }
+ }
+ this.generateMQL();
+ break;
+ case 'Aggr1':
+ if (this.fieldListMATCH[x] === 'AND' || this.fieldListMATCH[x] === 'OR' || this.fieldListMATCH[x] === 'END') {
+ const indicesToRemove = [];
+ for (let i = x; i < this.fieldListMATCH.length; i++) {
+ if (this.mqlFieldsMATCH[i][6] === this.mqlFieldsMATCH[x][6]) {
+ indicesToRemove.push(i);
+ }
+ if (indicesToRemove.length === 2) {
+ break;
+ }
+ }
+ this.mqlFieldsMATCH = this.mqlFieldsMATCH.filter((_, index) => !indicesToRemove.includes(index));
+ this.fieldCounterMATCH = this.fieldCounterMATCH - indicesToRemove.length;
+ if (indicesToRemove[1] === this.fieldListMATCH.length && x !== 0) {
+ this.fieldDepthCounterMATCH = this.mqlFieldsMATCH[x - 1][5];
+ }
+ this.fieldListMATCH = this.fieldListMATCH.filter((_, index) => !indicesToRemove.includes(index));
+ this.logicalOperatorStackMATCH = this.fieldListMATCH.filter((el) => el === 'AND' || el === 'OR' || el === 'END');
+ if (indicesToRemove.length === 1) {
+ this.logicalDepthCounterMATCH -= 1;
+ }
+ }
+ else { // normal field
+ this.fieldListMATCH.splice(x, 1);
+ this.mqlFieldsMATCH.splice(x, 1);
+ this.fieldCounterMATCH -= 1;
+ if (x === this.fieldListMATCH.length && x !== 0) {
+ this.fieldDepthCounterMATCH = this.mqlFieldsMATCH[x-1][5];
+ }
+ }
+ this.generateMQL();
+ break;
+ case 'Aggr2':
+ if (this.fieldListGROUP[x] === 'AND' || this.fieldListGROUP[x] === 'OR' || this.fieldListGROUP[x] === 'END') {
+ const indicesToRemove = [];
+ for (let i = x; i < this.fieldListGROUP.length; i++) {
+ if (this.mqlFieldsGROUP[i][6] === this.mqlFieldsGROUP[x][6]) {
+ indicesToRemove.push(i);
+ }
+ if (indicesToRemove.length === 2) {
+ break;
+ }
+ }
+ this.mqlFieldsGROUP = this.mqlFieldsGROUP.filter((_, index) => !indicesToRemove.includes(index));
+ this.fieldCounterGROUP = this.fieldCounterGROUP - indicesToRemove.length;
+ if (indicesToRemove[1] === this.fieldListGROUP.length && x !== 0) {
+ this.fieldDepthCounterGROUP = this.mqlFieldsGROUP[x - 1][5];
+ }
+ this.fieldListGROUP = this.fieldListGROUP.filter((_, index) => !indicesToRemove.includes(index));
+ }
+ else { // normal field
+ this.fieldListGROUP.splice(x, 1);
+ this.mqlFieldsGROUP.splice(x, 1);
+ this.fieldCounterGROUP -= 1;
+ if (x === this.fieldListGROUP.length && x !== 0) {
+ this.fieldDepthCounterGROUP = this.mqlFieldsGROUP[x-1][5];
+ }
+ }
+ this.generateMQL();
+ break;
+ case 'Aggr3':
+ if (this.fieldListSORT[x] === 'AND' || this.fieldListSORT[x] === 'OR' || this.fieldListSORT[x] === 'END') {
+ const indicesToRemove = [];
+ for (let i = x; i < this.fieldListSORT.length; i++) {
+ if (this.mqlFieldsSORT[i][6] === this.mqlFieldsSORT[x][6]) {
+ indicesToRemove.push(i);
+ }
+ if (indicesToRemove.length === 2) {
+ break;
+ }
+ }
+ this.mqlFieldsSORT = this.mqlFieldsSORT.filter((_, index) => !indicesToRemove.includes(index));
+ this.fieldCounterSORT = this.fieldCounterSORT - indicesToRemove.length;
+ if (indicesToRemove[1] === this.fieldListSORT.length && x !== 0) {
+ this.fieldDepthCounterSORT = this.mqlFieldsSORT[x - 1][5];
+ }
+ this.fieldListSORT = this.fieldListSORT.filter((_, index) => !indicesToRemove.includes(index));
+ }
+ else { // normal field
+ this.fieldListSORT.splice(x, 1);
+ this.mqlFieldsSORT.splice(x, 1);
+ this.fieldCounterSORT -= 1;
+ if (x === this.fieldListSORT.length && x !== 0) {
+ this.fieldDepthCounterSORT = this.mqlFieldsSORT[x-1][5];
+ }
+ }
+ this.generateMQL();
+ break;
+ }
+ }
+ /**
+ * generates the Dropdown options in the return value
+ */
+ generateDropDownArray() {
+ this.returnDropdown = [];
+ for (let i = 0; i <= this.fieldListCypherNode.length - 1; i++) {
+ if (this.cypherNode[i][0][0] !== '' && this.cypherNode[i][0][0] !== undefined) {
+ this.returnDropdown.push(this.cypherNode[i][0][0]);
+ }
+ if (this.cypherNode2[i][0][0] !== '' && this.cypherNode2[i][0][0] !== undefined && this.cypherDropdown[i] !== 'cypher-none') {
+ this.returnDropdown.push(this.cypherNode2[i][0][0]);
+ }
+ if (this.cypherNode3[i][0][0] !== '' && this.cypherNode3[i][0][0] !== undefined && this.cypherDropdown[i] === 'cypher-multiple') {
+ this.returnDropdown.push(this.cypherNode3[i][0][0]);
+ }
+ if (this.cypherRel[i][0][0] !== '' && this.cypherRel[i][0][0] !== undefined && this.cypherDropdown[i] !== 'cypher-none' && this.cypherDropdown[i] !== 'cypher-outgoing') {
+ this.returnDropdown.push(this.cypherRel[i][0][0]);
+ }
+ if (this.cypherRel2[i][0][0] !== '' && this.cypherRel2[i][0][0] !== undefined && this.cypherDropdown[i] === 'cypher-multiple') {
+ this.returnDropdown.push(this.cypherRel2[i][0][0]);
+ }
+ }
+ this.returnDropdown = this.returnDropdown.filter((value, index) => this.returnDropdown.indexOf(value) === index); //only show unique values as options
+ this.returnDropdown = this.returnDropdown.sort(); // sorts array alphabetically
+ }
+ /**
+ * generates the overall match-clause with all input fields
+ */
+ async generateMatch(index: number, field: number) {
+ // generate the content of the field
+ if (field === 1) {
+ if (this.cypherNode[index][0][1] === '*') { // dropdown is empty
+ this.cypherFields[index][0] = this.cypherNode[index][0][0];
+ }
+ else {
+ this.cypherFields[index][0] = this.cypherNode[index][0][0] + ': ' + this.cypherNode[index][0][1];
+ }
+ for (let i = 1; i < this.fieldListCypherNode[index].length; i++) {
+ if (i === 1) { // first property element
+ this.cypherFields[index][0] += ' {';
+ }
+ if (this.cypherNode[index][i][0] !== undefined && this.cypherNode[index][i][1] !== undefined) {
+ this.cypherFields[index][0] += this.cypherNode[index][i][0] + ': ' + this.checkInput(this.cypherNode[index][i][1]);
+ if (i < this.fieldListCypherNode[index].length - 1) {
+ this.cypherFields[index][0] += ', ';
+ }
+ }// last property element
+ if (i === this.fieldListCypherNode[index].length - 1) {
+ this.cypherFields[index][0] += '}';
+ }
+ }
+ }
+ if (field === 2) {
+ if (this.cypherNode2[index][0][1] === '*') { // dropdown is empty
+ this.cypherFields[index][2] = this.cypherNode2[index][0][0];
+ }
+ else {
+ this.cypherFields[index][2] = this.cypherNode2[index][0][0] + ': ' + this.cypherNode2[index][0][1];
+ }
+ for (let i = 1; i < this.fieldListCypherNode2[index].length; i++) {
+ if (i === 1) { // first property element
+ this.cypherFields[index][2] += ' {';
+ }
+ if (this.cypherNode2[index][i][0] !== undefined && this.cypherNode2[index][i][1] !== undefined) {
+ this.cypherFields[index][2] += this.cypherNode2[index][i][0] + ': ' + this.checkInput(this.cypherNode2[index][i][1]);
+ if (i < this.fieldListCypherNode2[index].length - 1) {
+ this.cypherFields[index][2] += ', ';
+ }
+ }
+ if (i === this.fieldListCypherNode2[index].length - 1) {
+ this.cypherFields[index][2] += '}';
+ }
+ }
+ }
+ if (field === 3) {
+ if (this.cypherNode3[index][0][1] === '*') { // dropdown is empty
+ this.cypherFields[index][4] = this.cypherNode3[index][0][0];
+ }
+ else {
+ this.cypherFields[index][4] = this.cypherNode3[index][0][0] + ': ' + this.cypherNode3[index][0][1];
+ }
+ for (let i = 1; i < this.fieldListCypherNode3[index].length; i++) {
+ if (i === 1) { // first property element
+ this.cypherFields[index][4] += ' {';
+ }
+ if (this.cypherNode3[index][i][0] !== undefined && this.cypherNode3[index][i][1] !== undefined) {
+ this.cypherFields[index][4] += this.cypherNode3[index][i][0] + ': ' + this.checkInput(this.cypherNode3[index][i][1]);
+ if (i < this.fieldListCypherNode3[index].length - 1) {
+ this.cypherFields[index][4] += ', ';
+ }
+ }
+ if (i === this.fieldListCypherNode3[index].length - 1) {
+ this.cypherFields[index][4] += '}';
+ }
+ }
+ }
+ if (field === 4) {
+ this.cypherFields[index][1] = this.cypherRel[index][0][0] + ': ' + this.cypherRel[index][0][1];
+ for (let i = 1; i < this.fieldListCypherRel[index].length; i++) {
+ if (i === 1) { // first property element
+ this.cypherFields[index][1] += ' {';
+ }
+ if (this.cypherRel[index][i][0] !== undefined && this.cypherRel[index][i][1] !== undefined) {
+ this.cypherFields[index][1] += this.cypherRel[index][i][0] + ': ' + this.checkInput(this.cypherRel[index][i][1]);
+ if (i < this.fieldListCypherRel[index].length - 1) {
+ this.cypherFields[index][1] += ', ';
+ }
+ }
+ if (i === this.fieldListCypherRel[index].length - 1) {
+ this.cypherFields[index][1] += '}';
+ }
+ }
+ }
+ if (field === 5) {
+ this.cypherFields[index][3] = this.cypherRel2[index][0][0] + ': ' + this.cypherRel2[index][0][1];
+ for (let i = 1; i < this.fieldListCypherRel2[index].length; i++) {
+ if (i === 1) { // first property element
+ this.cypherFields[index][3] += ' {';
+ }
+ if (this.cypherRel2[index][i][0] !== undefined && this.cypherRel2[index][i][1] !== undefined) {
+ this.cypherFields[index][3] += this.cypherRel2[index][i][0] + ': ' + this.checkInput(this.cypherRel2[index][i][1]);
+ if (i < this.fieldListCypherRel2[index].length - 1) {
+ this.cypherFields[index][3] += ', ';
+ }
+ }
+ if (i === this.fieldListCypherRel2[index].length - 1) {
+ this.cypherFields[index][3] += '}';
+ }
+ }
+ }
+ this.generateCypher();
+ }
+ /**
+ * checks if input is a valid number, and if not add quotiation (string)
+ */
+ checkInput(input: string): string {
+ // Check if input contains at most one dot and only numeric values
+ const regex = /^[0-9]*\.?[0-9]*$/; // only numeric values
+ const isValid = regex.test(input) && input.split('.').length <= 2; // at most one dot
+ if (!isValid) {
+ return `"${input}"`; // add quotation if it is not valid
+ }
+ return input;
+ }
+ /**
+ * generates the final cypher code
+ */
+ async generateCypher() {
+ let cypher = '';
+ for (let i = 0; i < this.fieldListCypher.length; i++) {
+ if (this.fieldListCypher[i] === 'MATCH') {
+ cypher += 'MATCH ' + '(' + this.cypherFields[i][0] + ')';
+ switch (this.cypherDropdown[i]) {
+ case 'cypher-related':
+ cypher += '--(' + this.cypherFields[i][2] + ')';
+ break;
+ case 'cypher-outgoing':
+ cypher += '-->(' + this.cypherFields[i][2] + ')';
+ break;
+ case 'cypher-directed':
+ cypher += '-[' + this.cypherFields[i][1] + ']->(' + this.cypherFields[i][2] + ')';
+ break;
+ case 'cypher-multiple':
+ cypher += '-[' + this.cypherFields[i][1] + ']->(' + this.cypherFields[i][2] + ')<-[' + this.cypherFields[i][3] + ']-(' + this.cypherFields[i][4] + ')';
+ break;
+ }
+ }
+ if (this.fieldListCypher[i] === 'WHERE') {
+ cypher += 'WHERE ' + this.cypherFields[i][0] + ' ' + this.cypherFields[i][1] + ' ' + this.checkInput(this.cypherFields[i][2]);
+ }
+ if (this.fieldListCypher[i] === 'OR' || this.fieldListCypher[i] === 'AND' || this.fieldListCypher[i] === 'NOR') {
+ cypher += this.fieldListCypher[i] + ' ';
+ if (this.cypherFields[i][0] !== undefined) {
+ cypher += this.cypherFields[i][0] + ' ';
+ }
+ if (this.cypherFields[i][1] !== undefined) {
+ cypher += this.cypherFields[i][1] + ' ';
+ }
+ if (this.cypherFields[i][2] !== undefined) {
+ cypher += this.checkInput(this.cypherFields[i][2]);
+ }
+ }
+ cypher += '\n';
+ }
+ cypher += 'RETURN ';
+ for (let i = 0; i < this.cypherReturnDrop.length; i++) {
+ cypher += this.cypherReturnDrop[i];
+ if (this.cypherReturnProp[i] !== undefined && this.cypherReturnProp[i] !== '') {
+ cypher += '.' + this.cypherReturnProp[i];
+ }
+ if (i < this.cypherReturnDrop.length - 1) {
+ cypher += ', ';
+ }
+ }
+ this.editorGenerated.setCode(cypher);
+ }
+
+ onDropdownClick(dropdown: any) { // is needed to change the cypherReturnDrop[0], by changes
+ const event = new Event('change');
+ dropdown.dispatchEvent(event);
+ }
+ /**
+ * generates final mql code
+ */
+ async generateMQL() {
+ let mql = '';
+ const isInsideLogicalCondition = [];
+ switch (this.mqlType) {
+ case 'FIND':
+ mql += 'db.' + this.collectionName + '.find({';
+ for (let i = 0; i < this.fieldList.length; i++) {
+ if (this.fieldList[i] === 'AND') {
+ mql += '$and: [';
+ isInsideLogicalCondition.push('AND');
+ }
+ if (this.fieldList[i] === 'OR') {
+ mql += '$or: [';
+ isInsideLogicalCondition.push('OR');
+ }
+ if (this.fieldList[i] !== 'AND' && this.fieldList[i] !== 'OR' && this.fieldList[i] !== 'END') {
+ if (isInsideLogicalCondition.length !== 0 && this.mqlFields[i][1] !== undefined){
+ mql += '{';
+ }
+ switch (this.mqlFields[i][1]) {
+ case 'equal':
+ mql += '"' + this.mqlFields[i][0] + '" : ' + this.checkInput(this.mqlFields[i][2]);
+ break;
+ case 'notequal':
+ mql += '"' + this.mqlFields[i][0] + '" : {"$ne" : ' + this.checkInput(this.mqlFields[i][2]) + '}';
+ break;
+ case 'greater':
+ mql += '"' + this.mqlFields[i][0] + '" : {"$gt" : ' + this.checkInput(this.mqlFields[i][2]) + '}';
+ break;
+ case 'lesser':
+ mql += '"' + this.mqlFields[i][0] + '" : {"$lt" : ' + this.checkInput(this.mqlFields[i][2]) + '}';
+ break;
+ case 'contains':
+ mql += '"' + this.mqlFields[i][0] + '" : {$regex: \'/.*' + this.checkInput(this.mqlFields[i][2]) + '.*/i\'}';
+ break;
+ case 'notcontains':
+ mql += '"' + this.mqlFields[i][0] + '" : {"$not" : /.*' + this.checkInput(this.mqlFields[i][2]) + '.*/i}';
+ break;
+ case 'type':
+ mql += '"' + this.mqlFields[i][0] + '" : {"$type" : ' + this.checkInput(this.mqlFields[i][2]) + '}';
+ break;
+ }
+ if (isInsideLogicalCondition.length !== 0 && this.mqlFields[i][1] !== undefined){
+ mql += '}';
+ }
+ const fieldListf = this.fieldList.filter((el) => el !== 'AND' && el !== 'OR' && el !== 'END');
+ let matchingIndex = -1;
+ for (let j = 0; j < fieldListf.length; j++) {
+ if (fieldListf[j] === this.fieldList[i]) {
+ matchingIndex = j;
+ }
+ }
+ if (matchingIndex + 1 <= fieldListf.length - 1 && matchingIndex !== -1 && this.mqlFields[i][1] !== undefined) {
+ mql += ', ';
+ }
+ }
+ if (this.fieldList[i] === 'END') {
+ mql += ']';
+ isInsideLogicalCondition.pop();
+ }
+ }
+ mql += '})';
+ this.editorGenerated.setCode(mql);
+ break;
+ case 'AGGR':
+ mql += 'db.' + this.collectionName + '.aggregate([{$match: {';
+ // Match-Loop
+ for (let i = 0; i < this.fieldListMATCH.length; i++) {
+ if (this.fieldListMATCH[i] === 'AND') {
+ mql += '$and: [';
+ isInsideLogicalCondition.push('AND');
+ }
+ if (this.fieldListMATCH[i] === 'OR') {
+ mql += '$or: [';
+ isInsideLogicalCondition.push('OR');
+ }
+ if (this.fieldListMATCH[i] !== 'AND' && this.fieldListMATCH[i] !== 'OR' && this.fieldListMATCH[i] !== 'END') {
+ if (isInsideLogicalCondition.length !== 0){
+ mql += '{';
+ }
+ switch (this.mqlFieldsMATCH[i][1]) {
+ case 'equal':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : ' + this.checkInput(this.mqlFieldsMATCH[i][2]);
+ break;
+ case 'notequal':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {"$ne" : ' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '}';
+ break;
+ case 'greater':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {"$gt" : ' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '}';
+ break;
+ case 'lesser':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {"$lt" : ' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '}';
+ break;
+ case 'contains':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {$regex: \'/.*' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '.*/i\'}';
+ break;
+ case 'notcontains':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {"$not" : /.*' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '.*/i}';
+ break;
+ case 'type':
+ mql += '"' + this.mqlFieldsMATCH[i][0] + '" : {"$type" : ' + this.checkInput(this.mqlFieldsMATCH[i][2]) + '}';
+ break;
+ }
+ if (isInsideLogicalCondition.length !== 0){
+ mql += '}';
+ }
+ const fieldListf = this.fieldListMATCH.filter((el) => el !== 'AND' && el !== 'OR' && el !== 'END');
+ let matchingIndex = -1;
+ for (let j = 0; j < fieldListf.length; j++) {
+ if (fieldListf[j] === this.fieldListMATCH[i]) {
+ matchingIndex = j;
+ }
+ }
+ if (matchingIndex + 1 <= fieldListf.length - 1 && matchingIndex !== -1 && this.mqlFieldsMATCH[i][1] !== undefined) {
+ mql += ', ';
+ }
+ }
+ if (this.fieldListMATCH[i] === 'END') {
+ mql += ']';
+ isInsideLogicalCondition.pop();
+ }
+ }
+ mql += '}}, {$group: {';
+ //Group-Loop
+ for (let i = 0; i < this.fieldListGROUP.length; i++) {
+ if (this.mqlFieldsGROUP[i][0] === '_id') {
+ mql += '"' + this.mqlFieldsGROUP[i][0] + '" : "$' + this.mqlFieldsGROUP[i][2] + '"';
+ }
+ else {
+ mql += '"' + this.mqlFieldsGROUP[i][0] + '" : { ' + this.mqlFieldsGROUP[i][1] + ' : "$' + this.mqlFieldsGROUP[i][2] + '"}';
+ }
+ if (i < this.mqlFieldsGROUP.length-1) {
+ mql += ', ';
+ }
+ }
+ mql += '}}, {$sort: {';
+ //Sort-Loop
+ for (let i = 0; i < this.fieldListSORT.length; i++) {
+ mql += '"' + this.mqlFieldsSORT[i][0] + '" : ' + this.mqlFieldsSORT[i][1];
+ if (i < this.mqlFieldsSORT.length-1) {
+ mql += ', ';
+ }
+ }
+ mql += '}}])';
+ this.editorGenerated.setCode(mql);
+ break;
+ }
+ }
+
async generateSQL() {
this.whereCounter = 0;
this.andCounter = 0;
@@ -336,7 +1620,7 @@ export class GraphicalQueryingComponent implements OnInit, AfterViewInit, OnDest
let filteredInfos = '';
if (this.columns.size === 0) {
- this.editorGenerated.setCode('');
+ //this.editorGenerated.setCode('');
return;
}
@@ -428,13 +1712,12 @@ export class GraphicalQueryingComponent implements OnInit, AfterViewInit, OnDest
executeQuery() {
this.loading = true;
const code = this.editorGenerated.getCode();
- if (!this._crud.anyQuery(this.webSocket, new QueryRequest(code, false, true, 'sql', null))) {
+ if (!this._crud.anyQuery(this.webSocket, new QueryRequest(code, false, true, this.lang, this.activeNamespace))) {
this.loading = false;
this.resultSet = new ResultSet('Could not establish a connection with the server.', code);
}
}
-
addCol(data) {
const treeElement = new SidebarNode(data.id, data.name, null, null);
@@ -515,3 +1798,4 @@ class JoinCondition {
this.active = !this.active;
}
}
+
diff --git a/src/app/views/uml/uml.component.ts b/src/app/views/uml/uml.component.ts
index 6adb9fce..e95bd3aa 100644
--- a/src/app/views/uml/uml.component.ts
+++ b/src/app/views/uml/uml.component.ts
@@ -82,12 +82,12 @@ export class UmlComponent implements OnInit, AfterViewInit, OnDestroy {
const sub = this._crud.onReconnection().subscribe(
b => {
if (b) {
- this._leftSidebar.setSchema(new SchemaRequest('/views/uml/', false, 1, true, false, [DataModels.RELATIONAL]), this._router);
+ this._leftSidebar.setSchema(new SchemaRequest('/views/uml/', false, 1, true, false, false, [DataModels.RELATIONAL]), this._router);
}
}
);
this.subscriptions.add(sub);
- this._leftSidebar.setSchema(new SchemaRequest('/views/uml/', true, 1, true, false, [DataModels.RELATIONAL]), this._router);
+ this._leftSidebar.setSchema(new SchemaRequest('/views/uml/', true, 1, true, false, false, [DataModels.RELATIONAL]), this._router);
}
ngAfterViewInit() {