diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index ca012418..f4f496c9 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -100,7 +100,8 @@ import {DatesPipeModule} from './data-view/shared-module'; InputComponent, JsonEditorComponent, EditorComponent, - DeleteConfirmComponent + DeleteConfirmComponent, + DataGraphComponent ] }) export class ComponentsModule { diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 6e214f60..7e6fbb36 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -224,9 +224,11 @@ export class SchemaRequest extends UIRequest { */ showTable: boolean; schemaEdit: boolean; + + isCrossModel: boolean; dataModels: DataModels[]; - constructor(routerLinkRoot: string, views: boolean, depth: number, showTable: boolean, schemaEdit?: boolean, dataModels: DataModels[] = [DataModels.RELATIONAL, DataModels.DOCUMENT, DataModels.GRAPH]) { + constructor(routerLinkRoot: string, views: boolean, depth: number, showTable: boolean, schemaEdit?: boolean, isCrossModel= false, dataModels: DataModels[] = [DataModels.RELATIONAL, DataModels.DOCUMENT, DataModels.GRAPH]) { super(); this.routerLinkRoot = routerLinkRoot; this.views = views; @@ -234,6 +236,7 @@ export class SchemaRequest extends UIRequest { this.showTable = showTable; this.schemaEdit = schemaEdit || false; this.dataModels = dataModels; + this.isCrossModel = isCrossModel; } } diff --git a/src/app/views/querying/console/console.component.ts b/src/app/views/querying/console/console.component.ts index e62a47be..eb0dec49 100644 --- a/src/app/views/querying/console/console.component.ts +++ b/src/app/views/querying/console/console.component.ts @@ -183,6 +183,8 @@ export class ConsoleComponent implements OnInit, OnDestroy { this.queryAnalysis = null; this.loading = true; + console.log(code); + console.log(this.activeNamespace); if (!this._crud.anyQuery(this.websocket, new QueryRequest(code, this.analyzeQuery, this.useCache, this.lang, this.activeNamespace))) { this.loading = false; this.resultSets = [new ResultSet('Could not establish a connection with the server.', code)]; diff --git a/src/app/views/querying/explore-by-example/explore-by-example.component.ts b/src/app/views/querying/explore-by-example/explore-by-example.component.ts index f7fe790d..369b4fa6 100644 --- a/src/app/views/querying/explore-by-example/explore-by-example.component.ts +++ b/src/app/views/querying/explore-by-example/explore-by-example.component.ts @@ -108,7 +108,7 @@ export class ExploreByExampleComponent implements OnInit, OnDestroy { } initSchema() { - this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 3, false, false, [DataModels.RELATIONAL])).subscribe( + this._crud.getSchema(new SchemaRequest('views/graphical-querying/', true, 3, false, false, false, [DataModels.RELATIONAL])).subscribe( res => { const nodeAction = (tree, node, $event) => { if (!node.isActive && node.isLeaf) { diff --git a/src/app/views/querying/graphical-querying/graphical-querying.component.html b/src/app/views/querying/graphical-querying/graphical-querying.component.html index ea471b02..9d5b595d 100644 --- a/src/app/views/querying/graphical-querying/graphical-querying.component.html +++ b/src/app/views/querying/graphical-querying/graphical-querying.component.html @@ -2,97 +2,1447 @@
Graphical Querying
-

Select columns from the left sidebar to get started. If needed, you can edit the generated SQL code.

-
-
- select -
-
+ + + +

Select your query language:

+
+ + +
-
-
- from -
-
- {{t.key}}test -
+ +
+

Select columns from the left sidebar to get started. If needed, you can edit the generated SQL code.

+ +
+
+ select
+
+
-
-
- join
conditions
-
-
- -
-
-
+
+
+ from +
+
+ {{t.key}}test
+
-
Generated query
-

Edit your code if needed. Your changes will be overwritten, as soon as you add or remove a column in the - select box or change any join condition.

-
- +
+
+ join
conditions
-
+
+ +
+
+
+
+ +
Generated query
+

Edit your code if needed. Your changes will be overwritten, as soon as you add or remove a column in the select box or change any join condition.

+
+ +
+
+ + - + + - - +
+
+
Query Result
+
-
-
-
Query Result
+
+ +
+ {{resultSet.generatedQuery}} + + {{resultSet.affectedRows}} + ! + +
+ +
+
+ Error: +

{{resultSet.error}}

+
+ +
+

+ Successfully executed +

+
+ +
+
+ +
+
+
+
+ + +
+

Select your graph from the left sidebar to get started: {{ graphName !== undefined ? ' ' + graphName : '' }}

+

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.

+
+
+ +
+ Node Labels: +
+
+

{{label}}

+
+ + +
+ Relationship Labels: +
+
+

{{label}}

+
+
+ + +
+ + + +
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+ + +
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+ +
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+ + +
+
+ + +
+
-
- {{resultSet.generatedQuery}} - - {{resultSet.affectedRows}} - ! - + +
+
+
+
+ +
+ + +
+
+
+ + +
+
+
+ +
Generated query
+

Edit your code if needed.

+
+ +
+ + + + +
+
+
Query Result
+
+ +
+ +
+ {{resultSet.generatedQuery}} + + {{resultSet.affectedRows}} + ! + +
+ +
+
+ Error: +

{{resultSet.error}}

+
+ +
+

+ Successfully executed +

+
+ +
+ +
+
+
+
-
-
- Error: -

{{resultSet.error}}

+ +
+

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:

+
+
+
+ + +
+
+
+ + + + +
+
+ + + + +
+
+ + + +
+
+ +
+ Match stage +
+
+
+ +
+
+ {{fieldNumber}} +
+
+ +
+
+
+ +
+
+ END +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+ + +
+ Group stage +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+ +
+ Sort stage +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+
+
+
+ + +
+
+ Find +
+ +
+
+ {{fieldNumber}} +
+
+ +
+
+
+ +
+
+ END +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
Generated query
+

Edit your code if needed.

+
+
-
-

- Successfully executed -

+ + + +
+
+
Query Result
-
-
- +
+ +
+ {{resultSet.generatedQuery}} + + {{resultSet.affectedRows}} + ! + +
+ +
+
+ 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() {