Files
LinkScope_Client/Core/LQL.py

1281 lines
60 KiB
Python

#!/usr/bin/env python3
import contextlib
import csv
import statistics
import re
import networkx as nx
import string
from typing import Union, Optional, Any
from uuid import uuid4
from pathlib import Path
from PySide6 import QtWidgets, QtCore, QtCharts, QtGui
from Core.GlobalVariables import non_string_fields
from Core.ResourceHandler import resizePictureFromBuffer
from Core.PathHelper import is_path_exists_or_creatable_portable
class QueryBuilderWizard(QtWidgets.QDialog):
def __init__(self, mainWindowObject):
super(QueryBuilderWizard, self).__init__()
self.mainWindowObject = mainWindowObject
self.setModal(True)
self.setWindowTitle('LQL Query Wizard')
dialogLayout = QtWidgets.QGridLayout()
self.setLayout(dialogLayout)
# Add a tab to create a new query, and a tab to re-run old queries.
self.queryNewOrHistory = QtWidgets.QTabWidget()
self.queryTabbedPane = QtWidgets.QTabWidget()
self.queryNewOrHistory.addTab(self.queryTabbedPane, 'New Query')
dialogLayout.addWidget(self.queryNewOrHistory)
buttonsWidget = QtWidgets.QWidget()
buttonsWidgetLayout = QtWidgets.QHBoxLayout()
buttonsWidget.setLayout(buttonsWidgetLayout)
exitButton = QtWidgets.QPushButton('Close')
exitButton.clicked.connect(self.accept)
resetWizardButton = QtWidgets.QPushButton('Reset Wizard')
resetWizardButton.clicked.connect(self.updateValues)
self.runButton = QtWidgets.QPushButton('Run Query')
self.runButton.clicked.connect(self.runQuery)
buttonsWidgetLayout.addWidget(exitButton)
buttonsWidgetLayout.addWidget(resetWizardButton)
buttonsWidgetLayout.addWidget(self.runButton)
dialogLayout.addWidget(buttonsWidget)
self.runButton.setDefault(True)
#### SELECT
selectPane = QtWidgets.QWidget()
selectPaneLayout = QtWidgets.QGridLayout()
selectPane.setLayout(selectPaneLayout)
self.selectStatementPicker = QtWidgets.QComboBox()
self.selectStatementPicker.addItems(['SELECT', 'RSELECT'])
self.selectStatementPicker.setEditable(False) # Default, but it's nice to be explicit.
self.selectStatementPicker.currentIndexChanged.connect(
lambda newIndex: self.selectStatementValuePickerLayout.setCurrentIndex(newIndex))
selectStatementValuePickerWidget = QtWidgets.QWidget()
self.selectStatementValuePickerLayout = QtWidgets.QStackedLayout()
selectStatementValuePickerWidget.setLayout(self.selectStatementValuePickerLayout)
self.selectStatementList = QtWidgets.QListWidget()
self.selectStatementList.setSortingEnabled(True)
self.selectStatementList.setSelectionMode(QtWidgets.QListWidget.SelectionMode.ExtendedSelection)
self.selectStatementList.setMinimumHeight(125)
self.selectStatementList.setToolTip('Highlight all the fields you wish to select.')
self.selectStatementTextbox = QtWidgets.QLineEdit('')
self.selectStatementTextbox.setFixedHeight(26)
self.selectStatementTextbox.setToolTip('Type the regex you want to use to specify the fields to select.')
self.selectStatementValuePickerLayout.addWidget(self.selectStatementList)
self.selectStatementValuePickerLayout.addWidget(self.selectStatementTextbox)
selectPaneLayout.addWidget(QtWidgets.QLabel('Selection mode: '), 0, 0, 1, 1)
selectPaneLayout.addWidget(self.selectStatementPicker, 0, 1, 1, 1)
selectPaneLayout.addWidget(QtWidgets.QLabel('Field selection:'), 1, 0, 1, 2)
selectPaneLayout.addWidget(selectStatementValuePickerWidget, 2, 0, 2, 2)
self.queryTabbedPane.addTab(selectPane, 'Selection')
####
#### SOURCE
sourcePane = QtWidgets.QWidget()
sourcePaneLayout = QtWidgets.QGridLayout()
sourcePane.setLayout(sourcePaneLayout)
self.sourceStatementPicker = QtWidgets.QComboBox()
self.sourceStatementPicker.addItems(['FROMDB', 'FROM'])
self.sourceStatementPicker.setEditable(False)
self.sourceStatementPicker.currentTextChanged.connect(self.sourceModeSwitch)
self.sourceValues = []
self.sourceValuesArea = QtWidgets.QScrollArea()
self.sourceValuesArea.setWidgetResizable(True)
self.sourceValuesArea.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sourceValuesAreaWidget = QtWidgets.QWidget()
self.sourceValuesAreaWidgetLayout = QtWidgets.QVBoxLayout()
sourceValuesAreaWidget.setLayout(self.sourceValuesAreaWidgetLayout)
self.sourceValuesArea.setWidget(sourceValuesAreaWidget)
self.sourceValuesArea.setEnabled(False)
self.sourceValuesArea.setDisabled(True)
self.sourceValuesArea.setHidden(True)
sourceButtonsWidget = QtWidgets.QWidget()
sourceButtonsWidgetLayout = QtWidgets.QHBoxLayout()
sourceButtonsWidget.setLayout(sourceButtonsWidgetLayout)
sourceAddStatementButton = QtWidgets.QPushButton('Add Clause')
sourceRemoveStatementButton = QtWidgets.QPushButton('Remove Last Clause')
sourceAddStatementButton.clicked.connect(self.addSourceClause)
sourceRemoveStatementButton.clicked.connect(self.removeSourceClause)
sourceButtonsWidgetLayout.addWidget(sourceRemoveStatementButton)
sourceButtonsWidgetLayout.addWidget(sourceAddStatementButton)
self.sourceValuesAreaWidgetLayout.addWidget(sourceButtonsWidget)
sourcePaneLayout.addWidget(QtWidgets.QLabel('Source: '), 0, 0, 1, 1)
sourcePaneLayout.addWidget(self.sourceStatementPicker, 0, 1, 1, 1)
sourcePaneLayout.addWidget(self.sourceValuesArea, 1, 0, 2, 2)
self.queryTabbedPane.addTab(sourcePane, 'Source')
####
#### CONDITIONS
conditionsPane = QtWidgets.QWidget()
conditionsPaneLayout = QtWidgets.QGridLayout()
conditionsPane.setLayout(conditionsPaneLayout)
self.conditionValues = []
self.conditionValuesArea = QtWidgets.QScrollArea()
self.conditionValuesArea.setWidgetResizable(True)
conditionValuesAreaWidget = QtWidgets.QWidget()
self.conditionValuesAreaWidgetLayout = QtWidgets.QVBoxLayout()
conditionValuesAreaWidget.setLayout(self.conditionValuesAreaWidgetLayout)
self.conditionValuesArea.setWidget(conditionValuesAreaWidget)
self.conditionValuesArea.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Minimum)
conditionsButtonsWidget = QtWidgets.QWidget()
conditionsButtonsWidgetLayout = QtWidgets.QHBoxLayout()
conditionsButtonsWidget.setLayout(conditionsButtonsWidgetLayout)
conditionsAddStatementButton = QtWidgets.QPushButton('Add Condition')
conditionsRemoveStatementButton = QtWidgets.QPushButton('Remove Last Condition')
conditionsAddStatementButton.clicked.connect(self.addConditionClause)
conditionsRemoveStatementButton.clicked.connect(self.removeConditionClause)
conditionsButtonsWidgetLayout.addWidget(conditionsRemoveStatementButton)
conditionsButtonsWidgetLayout.addWidget(conditionsAddStatementButton)
self.conditionValuesAreaWidgetLayout.addWidget(conditionsButtonsWidget)
conditionsPaneLayout.addWidget(QtWidgets.QLabel('Conditions: '), 0, 0, 1, 1)
conditionsPaneLayout.addWidget(self.conditionValuesArea, 0, 0, 2, 2)
self.queryTabbedPane.addTab(conditionsPane, 'Conditions')
####
#### MODIFY
modificationsPane = QtWidgets.QWidget()
modificationsPaneLayout = QtWidgets.QGridLayout()
modificationsPane.setLayout(modificationsPaneLayout)
self.modificationValues = []
self.modificationValuesArea = QtWidgets.QScrollArea()
self.modificationValuesArea.setWidgetResizable(True)
self.modificationValuesArea.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Minimum)
modificationValuesAreaWidget = QtWidgets.QWidget()
self.modificationValuesAreaWidgetLayout = QtWidgets.QVBoxLayout()
modificationValuesAreaWidget.setLayout(self.modificationValuesAreaWidgetLayout)
self.modificationValuesArea.setWidget(modificationValuesAreaWidget)
modificationsButtonsWidget = QtWidgets.QWidget()
modificationsButtonsWidgetLayout = QtWidgets.QHBoxLayout()
modificationsButtonsWidget.setLayout(modificationsButtonsWidgetLayout)
modificationsAddStatementButton = QtWidgets.QPushButton('Add Modification')
modificationsRemoveStatementButton = QtWidgets.QPushButton('Remove Last Modification')
modificationsAddStatementButton.clicked.connect(self.addModificationClause)
modificationsRemoveStatementButton.clicked.connect(self.removeModificationClause)
modificationsButtonsWidgetLayout.addWidget(modificationsRemoveStatementButton)
modificationsButtonsWidgetLayout.addWidget(modificationsAddStatementButton)
self.modificationValuesAreaWidgetLayout.addWidget(modificationsButtonsWidget)
modificationsPaneLayout.addWidget(QtWidgets.QLabel('Modifications: '), 0, 0, 1, 1)
modificationsPaneLayout.addWidget(self.modificationValuesArea, 0, 0, 2, 2)
self.queryTabbedPane.addTab(modificationsPane, 'Modifications')
####
self.entityDropdownTriplets = []
self.historyTable = QtWidgets.QTableWidget(0, 7, self)
self.historyTable.setSelectionBehavior(self.historyTable.SelectionBehavior.SelectRows)
self.historyTable.setSelectionMode(self.historyTable.SelectionMode.SingleSelection)
self.historyTable.setAcceptDrops(False)
self.historyTable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.historyTable.verticalHeader().setCascadingSectionResizes(True)
self.historyTable.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch)
self.historyTable.setHorizontalHeaderLabels(['Query UID', 'Select Clause', 'Select Value(s)', 'Source Clause',
'Source Value(s)', 'Condition Clause(s)', 'Modification Values'])
self.queryNewOrHistory.addTab(self.historyTable, 'History')
self.updateValues()
self.resize(1000, 600)
def sourceModeSwitch(self, newText: str):
if newText == 'FROMDB':
self.sourceValuesArea.setEnabled(False)
self.sourceValuesArea.setDisabled(True)
self.sourceValuesArea.setHidden(True)
else:
self.sourceValuesArea.setEnabled(True)
self.sourceValuesArea.setDisabled(False)
self.sourceValuesArea.setHidden(False)
def addSourceClause(self):
clauseWidget = QtWidgets.QFrame()
clauseWidgetLayout = QtWidgets.QVBoxLayout()
clauseWidget.setLayout(clauseWidgetLayout)
clauseWidget.setFrameStyle(QtWidgets.QFrame.Shape.Panel | QtWidgets.QFrame.Shadow.Raised)
clauseWidget.setLineWidth(3)
andOrClause = QtWidgets.QComboBox()
andOrClause.addItems(['OR', 'AND'])
specifier = QtWidgets.QComboBox()
specifier.addItems(['CANVAS', 'RCANVAS'])
negation = QtWidgets.QComboBox()
negation.addItems(['MATCHES', 'DOES NOT MATCH'])
inputDropdown = QtWidgets.QListWidget()
inputDropdown.setSortingEnabled(True)
inputDropdown.setSelectionMode(QtWidgets.QListWidget.SelectionMode.SingleSelection)
inputDropdown.setMinimumHeight(125)
inputDropdown.addItems(self.mainWindowObject.LQLWIZARD.allCanvases)
inputText = QtWidgets.QLineEdit('')
inputText.setFixedHeight(26)
inputWidget = QtWidgets.QWidget()
inputWidgetLayout = QtWidgets.QStackedLayout()
inputWidget.setLayout(inputWidgetLayout)
inputWidgetLayout.addWidget(inputDropdown)
inputWidgetLayout.addWidget(inputText)
specifier.currentIndexChanged.connect(lambda newIndex: inputWidgetLayout.setCurrentIndex(newIndex))
clauseWidgetLayout.addWidget(andOrClause)
clauseWidgetLayout.addWidget(specifier)
clauseWidgetLayout.addWidget(negation)
clauseWidgetLayout.addWidget(inputWidget)
if self.sourceValuesAreaWidgetLayout.count() == 1:
andOrClause.setDisabled(True)
andOrClause.setToolTip('Cannot edit the set modifier of the first source clause.')
self.sourceValues.append(clauseWidget)
self.sourceValuesAreaWidgetLayout.insertWidget(self.sourceValuesAreaWidgetLayout.count() - 1, clauseWidget)
def removeSourceClause(self):
if self.sourceValuesAreaWidgetLayout.count() != 1:
# Remove clause that was added last.
itemToDel = self.sourceValuesAreaWidgetLayout.takeAt(self.sourceValuesAreaWidgetLayout.count() - 2)
itemToDel.widget().deleteLater()
widgetToDel = self.sourceValues.pop()
widgetToDel.deleteLater()
def addConditionClause(self):
clauseWidget = ConditionClauseWidget(self)
if self.conditionValuesAreaWidgetLayout.count() == 1:
clauseWidget.andOrClause.setDisabled(True)
clauseWidget.andOrClause.setToolTip('Cannot edit the set modifier of the first condition clause.')
self.conditionValues.append(clauseWidget)
self.conditionValuesAreaWidgetLayout.insertWidget(self.conditionValuesAreaWidgetLayout.count() - 1,
clauseWidget)
def removeConditionClause(self):
if self.conditionValuesAreaWidgetLayout.count() != 1:
# Remove clause that was added last.
itemToDel = self.conditionValuesAreaWidgetLayout.takeAt(self.conditionValuesAreaWidgetLayout.count() - 2)
itemToDel.widget().deleteLater()
widgetToDel = self.conditionValues.pop()
widgetToDel.deleteLater()
def addModificationClause(self):
clauseWidget = QtWidgets.QFrame()
clauseWidgetLayout = QtWidgets.QVBoxLayout()
clauseWidget.setLayout(clauseWidgetLayout)
clauseWidget.setFrameStyle(QtWidgets.QFrame.Shape.Panel | QtWidgets.QFrame.Shadow.Raised)
clauseWidget.setLineWidth(3)
andOrClause = QtWidgets.QLabel('AND')
specifier = QtWidgets.QComboBox()
specifier.addItems(['MODIFY', 'RMODIFY'])
modifyOption = QtWidgets.QComboBox()
modifyOption.addItems(['NUMIFY', 'UPPERCASE', 'LOWERCASE'])
inputWidget = QtWidgets.QWidget()
inputWidgetLayout = QtWidgets.QStackedLayout()
inputDropdown = QtWidgets.QListWidget()
inputDropdown.setSortingEnabled(True)
inputDropdown.setSelectionMode(QtWidgets.QListWidget.SelectionMode.SingleSelection)
inputDropdown.addItems(self.mainWindowObject.LQLWIZARD.allEntityFields)
inputDropdown.setMinimumHeight(125)
inputText = QtWidgets.QLineEdit('')
inputText.setFixedHeight(26)
inputWidget.setLayout(inputWidgetLayout)
inputWidgetLayout.addWidget(inputDropdown)
inputWidgetLayout.addWidget(inputText)
specifier.currentIndexChanged.connect(lambda newIndex: inputWidgetLayout.setCurrentIndex(newIndex))
clauseWidgetLayout.addWidget(andOrClause)
clauseWidgetLayout.addWidget(specifier)
clauseWidgetLayout.addWidget(inputWidget)
clauseWidgetLayout.addWidget(modifyOption)
if self.modificationValuesAreaWidgetLayout.count() == 1:
andOrClause.setDisabled(True)
andOrClause.setToolTip('Cannot edit the set modifier of the first source clause.')
self.modificationValues.append(clauseWidget)
self.modificationValuesAreaWidgetLayout.insertWidget(self.modificationValuesAreaWidgetLayout.count() - 1,
clauseWidget)
def removeModificationClause(self):
if self.modificationValuesAreaWidgetLayout.count() != 1:
# Remove clause that was added last.
itemToDel = self.modificationValuesAreaWidgetLayout.takeAt(
self.modificationValuesAreaWidgetLayout.count() - 2)
itemToDel.widget().deleteLater()
widgetToDel = self.modificationValues.pop()
widgetToDel.deleteLater()
def updateValues(self):
self.mainWindowObject.LQLWIZARD.takeSnapshot()
self.selectStatementList.clear()
self.selectStatementList.addItems(self.mainWindowObject.LQLWIZARD.allEntityFields)
self.selectStatementTextbox.setText('')
for _ in range(len(self.sourceValues)):
widgetToDel = self.sourceValues.pop()
widgetToDel.deleteLater()
for _ in range(self.sourceValuesAreaWidgetLayout.count() - 1):
itemToDel = self.sourceValuesAreaWidgetLayout.takeAt(0)
itemToDel.widget().deleteLater()
for _ in range(len(self.conditionValues)):
widgetToDel = self.conditionValues.pop()
widgetToDel.deleteLater()
for _ in range(self.conditionValuesAreaWidgetLayout.count() - 1):
itemToDel = self.conditionValuesAreaWidgetLayout.takeAt(0)
itemToDel.widget().deleteLater()
for _ in range(self.modificationValuesAreaWidgetLayout.count() - 1):
itemToDel = self.modificationValuesAreaWidgetLayout.takeAt(0)
itemToDel.widget().deleteLater()
for _ in range(len(self.modificationValues)):
widgetToDel = self.modificationValues.pop()
widgetToDel.deleteLater()
self.entityDropdownTriplets.clear()
for entityUID in self.mainWindowObject.LQLWIZARD.databaseSnapshot.nodes:
nodeDetails = self.mainWindowObject.LQLWIZARD.databaseSnapshot.nodes[entityUID]
pixmapIcon = QtGui.QPixmap()
resizedIcon = resizePictureFromBuffer(nodeDetails['Icon'], (40, 40))
pixmapIcon.loadFromData(resizedIcon)
self.entityDropdownTriplets.append((nodeDetails[list(nodeDetails)[1]], entityUID, pixmapIcon))
for _ in range(self.historyTable.rowCount()):
self.historyTable.removeRow(0)
for oldQueryUID in self.mainWindowObject.LQLWIZARD.QUERIES_HISTORY:
self.historyTable.insertRow(0)
self.historyTable.setItem(0, 0, QtWidgets.QTableWidgetItem(str(oldQueryUID)))
for valueIndex in range(6):
self.historyTable.setItem(0, valueIndex + 1, QtWidgets.QTableWidgetItem(
str(self.mainWindowObject.LQLWIZARD.QUERIES_HISTORY[oldQueryUID][valueIndex])))
def runQuery(self):
# Results
if self.queryNewOrHistory.currentIndex() == 0:
sourceResults = []
for sourceValue in self.sourceValues:
sourceResult = [
sourceValue.layout().itemAt(0).widget().currentText(),
sourceValue.layout().itemAt(1).widget().currentText(),
sourceValue.layout().itemAt(2).widget().currentText()
!= 'MATCHES',
]
if sourceValue.layout().itemAt(3).widget().layout().currentIndex() == 0:
try:
sourceResult.append(
sourceValue.layout().itemAt(3).widget().layout().itemAt(0).widget().selectedItems()[
0].text())
except IndexError:
continue
else:
sourceResult.append(sourceValue.layout().itemAt(3).widget().layout().itemAt(1).widget().text())
sourceResults.append(sourceResult)
conditionResults = []
for conditionValue in self.conditionValues:
conditionResult = conditionValue.getValue()
if conditionResult is not None:
conditionResults.append(conditionResult)
if not conditionResults:
conditionResults = None
modificationResults = []
for modificationValue in self.modificationValues:
specifierText = modificationValue.layout().itemAt(1).widget().currentText()
modificationResult = [specifierText]
if specifierText == 'MODIFY':
try:
modificationResult.append(
modificationValue.layout().itemAt(2).widget().layout().itemAt(0).widget().selectedItems()[
0].text())
except IndexError:
continue
else:
modificationResult.append(
modificationValue.layout().itemAt(2).widget().layout().itemAt(1).widget().text())
modificationResult.append(modificationValue.layout().itemAt(3).widget().currentText())
modificationResults.append(modificationResult)
if not modificationResults:
modificationResults = None
currentSelectStatement = self.selectStatementPicker.currentText()
if currentSelectStatement == 'SELECT':
selectedFields = [
item.text()
for item in self.selectStatementList.selectedItems()
]
else:
selectedFields = self.selectStatementTextbox.text()
sourceStatement = self.sourceStatementPicker.currentText()
sourceListOrNone = None if sourceStatement == 'FROMDB' else sourceResults
resultsSet, modificationsSet = self.mainWindowObject.LQLWIZARD.parseQuery(self.mainWindowObject,
currentSelectStatement,
selectedFields, sourceStatement,
sourceListOrNone,
conditionResults,
modificationResults)
else:
try:
selectedHistoryUID = self.historyTable.selectedItems()[0].text()
resultsSet, modificationsSet = self.mainWindowObject.LQLWIZARD.parseQuery(
self.mainWindowObject,
*self.mainWindowObject.LQLWIZARD.QUERIES_HISTORY[selectedHistoryUID])
except IndexError:
self.mainWindowObject.MESSAGEHANDLER.error('No Query selected from history.', popUp=True)
return
self.showResults(resultsSet, modificationsSet)
def showResults(self, resultsSet, modificationsSet):
if not resultsSet:
self.mainWindowObject.MESSAGEHANDLER.warning('Query returned no results.', popUp=True)
return
numified = modificationsSet[1] if modificationsSet else None
qResultsViewer = QueryResultsViewer(self.mainWindowObject, self.mainWindowObject.LQLWIZARD.allEntities,
resultsSet[0], resultsSet[1], numified)
qResultsViewer.exec()
class QueryResultsViewer(QtWidgets.QDialog):
def __init__(self, mainWindowObject, entitiesDict: dict, selectedUIDs: set, selectedFields: set,
numifiedFields: set):
super(QueryResultsViewer, self).__init__()
self.mainWindowObject = mainWindowObject
self.setModal(True)
self.setWindowTitle('Query Results')
dialogLayout = QtWidgets.QGridLayout()
self.setLayout(dialogLayout)
self.selectedUIDs = selectedUIDs
self.resultsTabbedPane = QtWidgets.QTabWidget(self)
dialogLayout.addWidget(self.resultsTabbedPane, 0, 0, 2, 2)
self.headerFields = list(selectedFields)
with contextlib.suppress(ValueError):
self.headerFields.remove('uid')
self.headerFields.insert(0, 'uid')
self.resultsTable = QtWidgets.QTableWidget(0, len(self.headerFields), self)
self.resultsTable.setHorizontalHeaderLabels(self.headerFields)
self.resultsTable.setAcceptDrops(False)
self.resultsTable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.resultsTable.verticalHeader().setCascadingSectionResizes(True)
for index in range(1, len(self.headerFields)):
self.resultsTable.horizontalHeader().setSectionResizeMode(index, QtWidgets.QHeaderView.ResizeMode.Stretch)
for count, uid in enumerate(selectedUIDs):
self.resultsTable.insertRow(count)
for index, field in enumerate(self.headerFields):
self.resultsTable.setItem(count, index, QtWidgets.QTableWidgetItem(
str(entitiesDict[uid].get(field, 'None'))))
self.resultsTabbedPane.addTab(self.resultsTable, 'Table')
self.charts = {}
for headerField in self.headerFields[1:]:
values = {}
for entity in entitiesDict:
entityValue = str(entitiesDict[entity].get(headerField))
if entityValue not in values:
values[entityValue] = 1
else:
values[entityValue] += 1
if not values:
# Do not make charts if there are no values to make charts out of.
continue
fieldChart = QtCharts.QChart()
chartTitle = f"{headerField} Chart"
fieldChart.setTitle(chartTitle)
fieldChart.setTheme(QtCharts.QChart.ChartTheme.ChartThemeBlueCerulean)
fieldChart.setMargins(QtCore.QMargins(0, 0, 0, 0))
chartView = QtCharts.QChartView(fieldChart)
chartView.setRubberBand(QtCharts.QChartView.RubberBand.NoRubberBand)
chartView.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
fieldChart.setAnimationOptions(QtCharts.QChart.AnimationOption.AllAnimations)
fieldChart.setAnimationDuration(250)
fieldChart.legend().setVisible(True)
fieldChart.legend().setAlignment(QtCore.Qt.AlignmentFlag.AlignBottom)
self.charts[headerField] = (fieldChart, chartView)
self.resultsTabbedPane.addTab(chartView, chartTitle)
barSeries = QtCharts.QBarSeries()
barSeries.setName(headerField)
for barValue, value in values.items():
barSet = QtCharts.QBarSet(barValue)
barSet.append(value)
barSeries.append(barSet)
fieldChart.addSeries(barSeries)
xAxis = QtCharts.QBarCategoryAxis()
xAxis.append([headerField])
fieldChart.addAxis(xAxis, QtCore.Qt.AlignmentFlag.AlignBottom)
barSeries.attachAxis(xAxis)
yAxis = QtCharts.QValueAxis()
yAxis.setRange(0, max(values.values()) + 1)
yAxis.applyNiceNumbers()
fieldChart.addAxis(yAxis, QtCore.Qt.AlignmentFlag.AlignLeft)
barSeries.attachAxis(yAxis)
if numifiedFields:
for field in numifiedFields:
fieldValues = []
for entity in entitiesDict:
try:
entityValue = float(entitiesDict[entity].get(field))
except TypeError:
continue
fieldValues.append(entityValue)
if not fieldValues:
continue
numValues = len(fieldValues)
maxValue = max(fieldValues)
minValue = min(fieldValues)
meanValue = statistics.fmean(fieldValues)
medianValue = statistics.median(fieldValues)
modeValue = statistics.mode(fieldValues)
sumValue = sum(fieldValues)
rangeValue = maxValue - minValue
varianceValue = statistics.pvariance(fieldValues)
standardDeviationValue = statistics.pstdev(fieldValues)
numifiedFieldWidget = QtWidgets.QWidget()
numifiedFieldWidgetLayout = QtWidgets.QVBoxLayout()
numifiedFieldWidget.setLayout(numifiedFieldWidgetLayout)
fieldLabel = QtWidgets.QLabel(f'Numerical Information for field: {field}')
numifiedValuesWidget = QtWidgets.QWidget()
numifiedValuesWidgetLayout = QtWidgets.QFormLayout()
numifiedValuesWidget.setLayout(numifiedValuesWidgetLayout)
numifiedValuesWidgetLayout.addRow('Number of Values: ', QtWidgets.QLabel(str(numValues)))
numifiedValuesWidgetLayout.addRow('Biggest Value: ', QtWidgets.QLabel(str(maxValue)))
numifiedValuesWidgetLayout.addRow('Smallest Value: ', QtWidgets.QLabel(str(minValue)))
numifiedValuesWidgetLayout.addRow('Mean Value: ', QtWidgets.QLabel(str(meanValue)))
numifiedValuesWidgetLayout.addRow('Median Value: ', QtWidgets.QLabel(str(medianValue)))
numifiedValuesWidgetLayout.addRow('Mode Value: ', QtWidgets.QLabel(str(modeValue)))
numifiedValuesWidgetLayout.addRow('Sum of Values: ', QtWidgets.QLabel(str(sumValue)))
numifiedValuesWidgetLayout.addRow('Range of Values: ', QtWidgets.QLabel(str(rangeValue)))
numifiedValuesWidgetLayout.addRow('Variance of Values: ', QtWidgets.QLabel(str(varianceValue)))
numifiedValuesWidgetLayout.addRow('Standard Deviation of Values: ',
QtWidgets.QLabel(str(standardDeviationValue)))
numifiedFieldWidgetLayout.addWidget(fieldLabel, 0)
numifiedFieldWidgetLayout.addWidget(numifiedValuesWidget, 1)
self.resultsTabbedPane.addTab(
numifiedFieldWidget, f'{field} Field Values Information'
)
closeButton = QtWidgets.QPushButton('Close')
closeButton.clicked.connect(self.accept)
exportButton = QtWidgets.QPushButton('Export Table')
exportButton.clicked.connect(self.exportData)
selectOnCurrentCanvasButton = QtWidgets.QPushButton('Select Result Entities on Current Canvas')
selectOnCurrentCanvasButton.clicked.connect(self.selectOnCurrentCanvas)
dialogLayout.addWidget(closeButton, 3, 0, 1, 1)
dialogLayout.addWidget(exportButton, 3, 1, 1, 1)
dialogLayout.addWidget(selectOnCurrentCanvasButton, 4, 0, 1, 2)
def selectOnCurrentCanvas(self):
self.mainWindowObject.centralWidget().tabbedPane.getCurrentScene().selectNodesFromList(self.selectedUIDs)
self.mainWindowObject.MESSAGEHANDLER.info('Query Result Entities Selected Successfully.', popUp=True)
def exportData(self):
exportDialog = QtWidgets.QFileDialog()
exportDialog.setOption(QtWidgets.QFileDialog.Option.DontUseNativeDialog, True)
exportDialog.setViewMode(QtWidgets.QFileDialog.ViewMode.List)
exportDialog.setFileMode(QtWidgets.QFileDialog.FileMode.AnyFile)
exportDialog.setAcceptMode(QtWidgets.QFileDialog.AcceptMode.AcceptSave)
exportDialog.setDirectory(str(Path.home()))
exportExec = exportDialog.exec()
if not exportExec:
self.mainWindowObject.setStatus('Export operation cancelled.')
return False
fileName = exportDialog.selectedFiles()[0]
exportFilePath = Path(fileName)
if not is_path_exists_or_creatable_portable(str(exportFilePath)):
self.mainWindowObject.MESSAGEHANDLER.error(
'Invalid export file name or path to save at.', popUp=True, exc_info=False)
return False
try:
with open(exportFilePath, 'w') as fileToWrite:
csvWriter = csv.writer(fileToWrite, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
csvWriter.writerow(self.headerFields)
for rowIndex in range(self.resultsTable.rowCount()):
currColumnValues = [
self.resultsTable.item(rowIndex, columnIndex).text()
for columnIndex in range(self.resultsTable.columnCount())
]
csvWriter.writerow(currColumnValues)
except FileNotFoundError:
self.mainWindowObject.MESSAGEHANDLER.error('Cannot write file into a non-existing parent directory. '
'Please create the required parent directories and try again.',
popUp=True, exc_info=False)
return False
self.mainWindowObject.MESSAGEHANDLER.info('Table exported successfully.', popUp=True, exc_info=False)
return True
class ConditionClauseWidget(QtWidgets.QFrame):
def __init__(self, parentWizard: QueryBuilderWizard):
super(ConditionClauseWidget, self).__init__()
clauseWidgetLayout = QtWidgets.QVBoxLayout()
self.setLayout(clauseWidgetLayout)
self.setFrameStyle(QtWidgets.QFrame.Shape.Panel | QtWidgets.QFrame.Shadow.Raised)
self.setLineWidth(3)
self.andOrClause = QtWidgets.QComboBox()
self.andOrClause.addItems(['OR', 'AND'])
self.specifier = QtWidgets.QComboBox()
self.specifier.addItems(['Value Condition', 'Graph Condition'])
self.negation = QtWidgets.QComboBox()
self.negation.addItems(['MATCHES', 'DOES NOT MATCH'])
inputWidget = QtWidgets.QWidget()
inputWidgetLayout = QtWidgets.QStackedLayout()
inputWidget.setLayout(inputWidgetLayout)
vcWidget = QtWidgets.QFrame()
vcWidgetLayout = QtWidgets.QVBoxLayout()
vcWidget.setLayout(vcWidgetLayout)
valueInputDropdownAttr = QtWidgets.QComboBox()
valueInputDropdownAttr.addItems(["ATTRIBUTE", "RATTRIBUTE"])
userInputDropdownAttr = QtWidgets.QLineEdit('')
userInputDropdownAttr.setFixedHeight(26)
valueInputDropdownCondition = QtWidgets.QComboBox()
valueInputDropdownCondition.addItems(["EQ", "CONTAINS", "STARTSWITH", "ENDSWITH", "RMATCH"])
userInputDropdownCondition = QtWidgets.QLineEdit('')
userInputDropdownCondition.setFixedHeight(26)
vcWidgetLayout.addWidget(valueInputDropdownAttr)
vcWidgetLayout.addWidget(userInputDropdownAttr)
vcWidgetLayout.addWidget(valueInputDropdownCondition)
vcWidgetLayout.addWidget(userInputDropdownCondition)
gcWidget = QtWidgets.QFrame()
gcWidgetLayout = QtWidgets.QVBoxLayout()
gcWidget.setLayout(gcWidgetLayout)
graphDropdownCondition = QtWidgets.QComboBox()
graphDropdownCondition.addItems(['CHILDOF', 'DESCENDANTOF', 'PARENTOF', 'ANCESTOROF', 'CONNECTEDTO',
'NUMCHILDREN', 'NUMPARENTS', 'NUMANCESTORS', 'NUMDESCENDANTS',
'NUMIFIED_PARENTS_TOTAL', 'NUMIFIED_CHILDREN_TOTAL',
'ISOLATED', 'ISROOT', 'ISLEAF'])
gcWidgetLayout.addWidget(graphDropdownCondition)
gcSecondaryInput = QtWidgets.QWidget()
self.gcSecondaryInputLayout = QtWidgets.QStackedLayout()
gcSecondaryInput.setLayout(self.gcSecondaryInputLayout)
graphEntitiesDropdown = QtWidgets.QTreeWidget()
graphEntitiesDropdown.setHeaderLabels(['Primary Field', 'UID', 'Icon'])
graphEntitiesDropdown.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
for entityDropdownTriplet in parentWizard.entityDropdownTriplets:
newItem = QtWidgets.QTreeWidgetItem()
newItem.setText(0, entityDropdownTriplet[0])
newItem.setText(1, entityDropdownTriplet[1])
newItem.setIcon(2, entityDropdownTriplet[2])
graphEntitiesDropdown.addTopLevelItem(newItem)
self.gcSecondaryInputLayout.addWidget(graphEntitiesDropdown)
graphNumComparisonsWidget = QtWidgets.QWidget()
graphNumComparisonsWidgetLayout = QtWidgets.QHBoxLayout()
graphNumComparisonsWidget.setLayout(graphNumComparisonsWidgetLayout)
graphNumComparisonDropdown = QtWidgets.QComboBox()
graphNumComparisonDropdown.addItems(['<', '<=', '>', '>=', '=='])
graphNumInput = QtWidgets.QDoubleSpinBox()
graphNumInput.setMinimum(0)
graphNumInput.setMaximum(1000000) # Can be adjusted higher if need be.
graphNumInput.setValue(0)
graphNumComparisonsWidgetLayout.addWidget(graphNumComparisonDropdown)
graphNumComparisonsWidgetLayout.addWidget(graphNumInput)
self.gcSecondaryInputLayout.addWidget(graphNumComparisonsWidget)
emptyWidget = QtWidgets.QWidget()
self.gcSecondaryInputLayout.addWidget(emptyWidget)
gcWidgetLayout.addWidget(gcSecondaryInput)
inputWidgetLayout.addWidget(vcWidget)
inputWidgetLayout.addWidget(gcWidget)
self.specifier.currentIndexChanged.connect(lambda newIndex: inputWidgetLayout.setCurrentIndex(newIndex))
graphDropdownCondition.currentIndexChanged.connect(self.determineSecondaryInput)
clauseWidgetLayout.addWidget(self.andOrClause)
clauseWidgetLayout.addWidget(self.specifier)
clauseWidgetLayout.addWidget(self.negation)
clauseWidgetLayout.addWidget(inputWidget)
def determineSecondaryInput(self, conditionIndex: int):
if conditionIndex < 5:
self.gcSecondaryInputLayout.setCurrentIndex(0)
elif conditionIndex < 11:
self.gcSecondaryInputLayout.setCurrentIndex(1)
else:
self.gcSecondaryInputLayout.setCurrentIndex(2)
def getValue(self):
specifierValue = self.specifier.currentText()
returnValues = [self.andOrClause.currentText(),
specifierValue]
if self.negation.currentText() == 'MATCHES':
returnValues.append(False)
else:
returnValues.append(True)
conditionValue = []
valueConditionLayout = self.layout().itemAt(3).widget().layout().itemAt(0).widget().layout()
if specifierValue == 'Value Condition':
conditionValue.extend(
(
valueConditionLayout.itemAt(0).widget().currentText(),
valueConditionLayout.itemAt(1).widget().text(),
valueConditionLayout.itemAt(2).widget().currentText(),
valueConditionLayout.itemAt(3).widget().text(),
)
)
else:
conditionValue.append(
self.layout().itemAt(3).widget().layout().itemAt(1).widget().layout().itemAt(0).widget().currentText())
if self.gcSecondaryInputLayout.currentIndex() == 0:
try:
conditionValue.append(self.gcSecondaryInputLayout.itemAt(0).widget().selectedItems()[0].text(1))
except IndexError:
return None
elif self.gcSecondaryInputLayout.currentIndex() == 1:
conditionValue.append(
self.gcSecondaryInputLayout.itemAt(1).widget().layout().itemAt(0).widget().currentText())
conditionValue.append(
self.gcSecondaryInputLayout.itemAt(1).widget().layout().itemAt(1).widget().value())
returnValues.append(conditionValue)
return returnValues
class LQLQueryBuilder:
QUERIES_HISTORY = {}
databaseSnapshot = None
databaseEntities = None
allCanvases = None
canvasesEntitiesDict = None
allEntityFields = None
allEntitiesInit = None
allEntities = None
def __init__(self, mainWindow):
self.mainWindow = mainWindow
def takeSnapshot(self):
with self.mainWindow.LENTDB.dbLock:
# Create a copy
self.databaseSnapshot = self.mainWindow.LENTDB.database.copy()
self.databaseEntities = set(self.databaseSnapshot.nodes)
self.allCanvases = self.getAllCanvasNames()
self.canvasesEntitiesDict = self.getCanvasesEntitiesDict(self.allCanvases)
self.allEntityFields, self.allEntitiesInit = self.getAllEntitiesAndFields()
# Re-define database entities to remove Group Entities
self.databaseEntities = set(self.allEntitiesInit.keys())
def getAllEntitiesAndFields(self) -> (set, dict):
entitiesSnapshot = {entity: self.databaseSnapshot.nodes[entity] for entity in self.databaseSnapshot.nodes
if self.databaseSnapshot.nodes[entity].get('Entity Type') != 'EntityGroup'}
entityFields = set()
for entityUID in entitiesSnapshot:
entityFields.update(entitiesSnapshot[entityUID].keys())
for field in non_string_fields:
try:
entityFields.remove(field)
except KeyError:
# This typically only happens if there are no entities in the database.
continue
return entityFields, entitiesSnapshot
def getAllCanvasNames(self) -> list:
canvasNames = list(self.mainWindow.centralWidget().tabbedPane.canvasTabs.keys())
canvasNames.append('*')
return canvasNames
def getEntitiesOnCanvas(self, canvasName: str):
try:
# Ensure that we don't have nodes here that are not present in our database snapshot
canvasNodes = set(
self.mainWindow.centralWidget().tabbedPane.canvasTabs[canvasName].scene().sceneGraph.nodes)
return canvasNodes.intersection(self.databaseEntities)
except KeyError:
return None
def getCanvasesEntitiesDict(self, allCanvasNames: list):
returnDict = {}
for canvas in allCanvasNames:
allEntitiesOnCanvas = self.getEntitiesOnCanvas(canvas)
if allEntitiesOnCanvas is not None:
returnDict[canvas] = allEntitiesOnCanvas
else:
returnDict[canvas] = set()
return returnDict
def parseSelect(self, selectClause: str, selectValue: Union[str, list]):
if selectClause == 'SELECT':
return self.allEntityFields if '*' in selectValue else \
{entityField for entityField in selectValue if entityField in self.allEntityFields}
try:
clauseValue = re.compile(selectValue)
return {entityField for entityField in self.allEntityFields if clauseValue.match(entityField)}
except re.error:
return set()
def parseSource(self, sourceClause: str, sourceValues: Union[None, list], fieldsToSelect: set) -> set:
"""
sourceValues:
[[("AND" | "OR" | None), ("CANVAS" | "RCANVAS"), (True | False), <User Input>], ...]
OR
None
if sourceClause == "FROMDB"
"""
if sourceClause == "FROMDB":
resultEntitySet = set(self.databaseEntities)
else:
resultEntitySet = set()
for sourceValue in sourceValues:
try:
if sourceValue[1] == "CANVAS":
if sourceValue[3] not in self.allCanvases:
raise ValueError('Reference to nonexistent canvas.')
matchingCanvases = [sourceValue[3]]
else:
canvasRegex = re.compile(sourceValue[3])
matchingCanvases = [canvasMatch for canvasMatch in self.allCanvases
if canvasRegex.match(canvasMatch)]
except (ValueError, re.error):
continue
for matchingCanvas in matchingCanvases:
if sourceValue[0] == 'AND':
resultEntitySet = self.canvasAndNot(resultEntitySet, self.canvasesEntitiesDict[matchingCanvas]) \
if sourceValue[2] is True else \
self.canvasAnd(resultEntitySet, self.canvasesEntitiesDict[matchingCanvas])
elif sourceValue[2] is True:
resultEntitySet = self.canvasOrNot(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas],
self.databaseEntities)
else:
resultEntitySet = self.canvasOr(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas])
# Filter out all entities that do not contain at least one of the selected fields.
for entity in list(resultEntitySet):
validEntity = any(field in self.allEntities[entity].keys() for field in fieldsToSelect)
if not validEntity:
resultEntitySet.remove(entity)
self.allEntities.pop(entity)
return resultEntitySet
def parseConditions(self, mainWindow, conditionClauses: Union[None, list], entitiesPool) -> set:
"""
conditionClauses:
[[("AND" | "OR" | None), ("Value Condition" | "Graph Condition"), (True | False), conditionValue], ...]
conditionValue:
if Value Condition:
[("ATTRIBUTE" | "RATTRIBUTE"), <User Input>,
("EQ" | "CONTAINS" | "STARTSWITH" | "ENDSWITH" | "RMATCH"), <User Input>]
if Graph Condition:
[("CHILDOF" <ENTITY> | "DESCENDANTOF " <ENTITY> |
"PARENTOF" <ENTITY> | "ANCESTOROF " <ENTITY> |
"NUMCHILDREN" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"NUMPARENTS" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"NUMANCESTORS" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"NUMDESCENDANTS" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"NUMIFIED_PARENTS_TOTAL" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"NUMIFIED_CHILDREN_TOTAL" (" < " | " <= " | " > " | " >= " | " == ") <DIGITS> |
"CONNECTEDTO" <ENTITY> | "ISOLATED" | "ISROOT" | "ISLEAF")]
"""
self.mainWindow = mainWindow
self.allEntities = {uid: self.allEntities[uid] for uid in self.allEntities if uid in entitiesPool}
uidsToSelect = set()
for conditionClause in conditionClauses:
isNot = conditionClause[2]
conditionValue = conditionClause[3]
try:
userInput1 = conditionValue[1]
userInput2 = conditionValue[3]
except IndexError:
# Not used in cases where a Graph Condition is specified
userInput1 = None
userInput2 = None
firstArgument = conditionValue[0]
if conditionClause[1] == "Value Condition":
matchingFields = []
if firstArgument == "ATTRIBUTE":
if userInput1 in self.allEntityFields:
matchingFields.append(userInput1)
else:
try:
attributeRegex = re.compile(userInput1)
except re.error:
continue
matchingFields.extend(field for field in self.allEntityFields if attributeRegex.match(field))
for matchingField in matchingFields:
entitiesToRemove = []
for entity in self.allEntities:
attributeKeyValue = str(self.allEntities[entity].get(matchingField))
if self.checkVCHelper(conditionValue[2], isNot, attributeKeyValue, userInput2):
uidsToSelect.add(entity)
elif conditionClause[0] == "AND":
entitiesToRemove.append(entity)
for entityToRemove in entitiesToRemove:
uidsToSelect.remove(entityToRemove)
elif conditionClause[1] == "Graph Condition":
entitiesToRemove = []
for entity in self.allEntities:
if self.checkGCHelper(firstArgument, isNot, [entity] + conditionValue[1:]):
uidsToSelect.add(entity)
elif conditionClause[0] == "AND":
entitiesToRemove.append(entity)
for entityToRemove in entitiesToRemove:
uidsToSelect.remove(entityToRemove)
uidsToRemove = set(self.allEntities).difference(uidsToSelect)
for entity in uidsToRemove:
self.allEntities.pop(entity, None)
return uidsToSelect
def canvasOr(self, canvasSetA: set, canvasSetB: set):
return canvasSetA.union(canvasSetB)
def canvasAnd(self, canvasSetA: set, canvasSetB: set):
return canvasSetA.intersection(canvasSetB)
def canvasAndNot(self, canvasSetA: set, canvasSetB: set):
return canvasSetA.difference(canvasSetB)
def canvasOrNot(self, canvasSetA: set, canvasSetB: set, allEntitiesSet: set):
return canvasSetA.union(allEntitiesSet.difference(canvasSetB))
def checkEQ(self, valueA: str, valueB: str):
return valueA == valueB
def checkContains(self, valueA: str, valueB: str):
return valueB in valueA
def checkStartsWith(self, valueA: str, valueB: str):
return valueA.startswith(valueB)
def checkEndsWith(self, valueA: str, valueB: str):
return valueA.endswith(valueB)
def checkRMatch(self, valueA: str, valueB: str):
with contextlib.suppress(re.error):
valueMatch = re.compile(valueB)
if valueMatch.match(valueA):
return True
return False
def checkVCHelper(self, checkType: str, isNot: bool, valueA: str, valueB: str):
returnVal = False
if checkType == "CONTAINS":
returnVal = self.checkContains(valueA, valueB)
elif checkType == "ENDSWITH":
returnVal = self.checkEndsWith(valueA, valueB)
elif checkType == "EQ":
returnVal = self.checkEQ(valueA, valueB)
elif checkType == "RMATCH":
returnVal = self.checkRMatch(valueA, valueB)
elif checkType == "STARTSWITH":
returnVal = self.checkStartsWith(valueA, valueB)
return not returnVal if isNot else returnVal
def checkParentOf(self, valueA: str, valueB: str):
return self.databaseSnapshot.has_successor(valueA, valueB)
def checkAncestorOf(self, valueA: str, valueB: str):
with contextlib.suppress(nx.NetworkXError):
if valueB in nx.descendants(self.databaseSnapshot, valueA):
return True
return False
def checkChildOf(self, valueA: str, valueB: str):
return self.databaseSnapshot.has_predecessor(valueA, valueB)
def checkDescendantOf(self, valueA: str, valueB: str):
with contextlib.suppress(nx.NetworkXError):
if valueB in nx.ancestors(self.databaseSnapshot, valueA):
return True
return False
def checkNumChildren(self, valueA: str, valueB: str, valueC: float):
numChildren = len(list(self.databaseSnapshot.successors(valueA)))
return (valueB == "<" and numChildren < valueC) or \
(valueB == "<=" and numChildren <= valueC) or \
(valueB == ">" and numChildren > valueC) or \
(valueB == ">=" and numChildren >= valueC) or \
(valueB == "==" and numChildren == valueC)
def checkNumParents(self, valueA: str, valueB: str, valueC: float):
numParents = len(list(self.databaseSnapshot.predecessors(valueA)))
return (valueB == "<" and numParents < valueC) or \
(valueB == "<=" and numParents <= valueC) or \
(valueB == ">" and numParents > valueC) or \
(valueB == ">=" and numParents >= valueC) or \
(valueB == "==" and numParents == valueC)
def checkNumAncestors(self, valueA: str, valueB: str, valueC: float):
numAncestors = len(list(nx.ancestors(self.databaseSnapshot, valueA)))
return (valueB == "<" and numAncestors < valueC) or \
(valueB == "<=" and numAncestors <= valueC) or \
(valueB == ">" and numAncestors > valueC) or \
(valueB == ">=" and numAncestors >= valueC) or \
(valueB == "==" and numAncestors == valueC)
def checkNumDescendants(self, valueA: str, valueB: str, valueC: float):
numDescendants = len(list(nx.descendants(self.databaseSnapshot, valueA)))
return (valueB == "<" and numDescendants < valueC) or \
(valueB == "<=" and numDescendants <= valueC) or \
(valueB == ">" and numDescendants > valueC) or \
(valueB == ">=" and numDescendants >= valueC) or \
(valueB == "==" and numDescendants == valueC)
def checkNumifiedParentsTotal(self, valueA: str, valueB: str, valueC: float):
parents = self.databaseSnapshot.predecessors(valueA)
total = 0.0
for item in parents:
primaryField = self.mainWindow.RESOURCEHANDLER.getPrimaryFieldForEntityType(
self.databaseSnapshot.nodes[item]['Entity Type'])
with contextlib.suppress(Exception):
total += self.modifyNumify(self.databaseSnapshot.nodes[item][primaryField])
return (valueB == "<" and total < valueC) or \
(valueB == "<=" and total <= valueC) or \
(valueB == ">" and total > valueC) or \
(valueB == ">=" and total >= valueC) or \
(valueB == "==" and total == valueC)
def checkNumifiedChildrenTotal(self, valueA: str, valueB: str, valueC: float):
children = self.databaseSnapshot.successors(valueA)
total = 0.0
for item in children:
primaryField = self.mainWindow.RESOURCEHANDLER.getPrimaryFieldForEntityType(
self.databaseSnapshot.nodes[item]['Entity Type'])
with contextlib.suppress(Exception):
total += self.modifyNumify(self.databaseSnapshot.nodes[item][primaryField])
return (valueB == "<" and total < valueC) or \
(valueB == "<=" and total <= valueC) or \
(valueB == ">" and total > valueC) or \
(valueB == ">=" and total >= valueC) or \
(valueB == "==" and total == valueC)
def checkConnectedTo(self, valueA: str, valueB: str):
with contextlib.suppress(nx.NetworkXError):
if nx.has_path(self.databaseSnapshot, valueA, valueB):
return True
return False
def checkIsolated(self, valueA: str):
with contextlib.suppress(nx.NetworkXError):
if valueA in self.databaseSnapshot.nodes and nx.is_isolate(self.databaseSnapshot, valueA):
return True
return False
def checkIsRoot(self, valueA: str):
with contextlib.suppress(nx.NetworkXError):
if len(self.databaseSnapshot.in_edges(valueA)) == 0:
return True
return False
def checkIsLeaf(self, valueA: str):
with contextlib.suppress(nx.NetworkXError):
if len(self.databaseSnapshot.out_edges(valueA)) == 0:
return True
return False
def checkGCHelper(self, checkType: str, isNot: bool, args: list):
returnVal = False
if checkType == "ANCESTOROF":
returnVal = self.checkAncestorOf(*args)
elif checkType == "CHILDOF":
returnVal = self.checkChildOf(*args)
elif checkType == "CONNECTEDTO":
returnVal = self.checkConnectedTo(*args)
elif checkType == "DESCENDANTOF":
returnVal = self.checkDescendantOf(*args)
elif checkType == "ISLEAF":
returnVal = self.checkIsLeaf(*args)
elif checkType == "ISOLATED":
returnVal = self.checkIsolated(*args)
elif checkType == "ISROOT":
returnVal = self.checkIsRoot(*args)
elif checkType == "NUMCHILDREN":
returnVal = self.checkNumChildren(*args)
elif checkType == "NUMPARENTS":
returnVal = self.checkNumParents(*args)
elif checkType == "NUMANCESTORS":
returnVal = self.checkNumAncestors(*args)
elif checkType == "NUMDESCENDANTS":
returnVal = self.checkNumDescendants(*args)
elif checkType == "NUMIFIED_PARENTS_TOTAL":
returnVal = self.checkNumifiedParentsTotal(*args)
elif checkType == "NUMIFIED_CHILDREN_TOTAL":
returnVal = self.checkNumifiedChildrenTotal(*args)
elif checkType == "PARENTOF":
returnVal = self.checkParentOf(*args)
return not returnVal if isNot else returnVal
def modifyNumify(self, valueA: str) -> float:
# Get the first number that shows up.
tempString = valueA.replace(',', '.') # Making sure that floats are expressed the right way.
count = 0
for c in tempString:
if c not in string.digits:
count += 1
else:
break
count2 = 0
for c in tempString[count:]:
if c in string.digits or c == '.':
count2 += 1
else:
break
# If there are no numbers in the string, its numeric value is 0.
try:
floatValue = float(tempString[count:count + count2])
except ValueError:
floatValue = 0.0
return floatValue
def modifyUpperCase(self, valueA: str):
return valueA.upper()
def modifyLowerCase(self, valueA: str):
return valueA.lower()
def parseModify(self, resultsToModify: (set, set), modifyQueries: list) -> (set, set):
"""
modifyQueries:
[[("MODIFY" | "RMODIFY"), <User Input>, ("NUMIFY" | "UPPERCASE" | "LOWERCASE")], ...]
"""
matchingFields = resultsToModify[1]
modifiedUIDs = set()
numifiedFields = set()
for modification in modifyQueries:
userInput1 = modification[1]
modificationType = modification[2]
modifyFields = []
if modification[0] == "MODIFY":
if userInput1 in resultsToModify[1]:
modifyFields.append(userInput1)
else:
try:
userInputRegex = re.compile(userInput1)
modifyFields = [fieldMatch for fieldMatch in matchingFields if userInputRegex.match(fieldMatch)]
except (ValueError, re.error):
continue
for entity in self.allEntities:
for modifyField in modifyFields:
entityFieldValue = self.allEntities[entity].get(modifyField)
if entityFieldValue is None or modificationType not in ["UPPERCASE", "LOWERCASE", "NUMIFY"]:
newFieldValue = None
elif modificationType == "UPPERCASE":
newFieldValue = self.modifyUpperCase(entityFieldValue)
elif modificationType == "LOWERCASE":
newFieldValue = self.modifyLowerCase(entityFieldValue)
else:
newFieldValue = str(self.modifyNumify(entityFieldValue))
numifiedFields.add(modifyField)
if newFieldValue is not None:
modifiedUIDs.add(entity)
self.allEntities[entity][modifyField] = newFieldValue
return modifiedUIDs, numifiedFields
def parseQuery(self, mainWindow, selectClause: str, selectValue: Union[str, list], sourceClause: str,
sourceValues: Union[None, list], conditionClauses: Union[None, list],
modifyQueries: Union[list, None] = None) -> Optional[
tuple[Optional[tuple[set, Union[set[Any], set[Union[str, Any]]]]],
Optional[tuple[set[Any], set[Any]]]]]:
if self.databaseSnapshot is None:
return None
self.allEntities = dict(self.allEntitiesInit)
returnValue = None
modifications = None
if fieldsToSelect := self.parseSelect(selectClause, selectValue):
if entitiesToConsider := self.parseSource(sourceClause, sourceValues, fieldsToSelect):
if conditionClauses:
entitiesToConsider = self.parseConditions(mainWindow, conditionClauses, entitiesToConsider)
returnValue = (entitiesToConsider, fieldsToSelect)
if modifyQueries:
modifications = self.parseModify(returnValue, modifyQueries)
queryUID = str(uuid4())
self.QUERIES_HISTORY[queryUID] = (selectClause, selectValue, sourceClause, sourceValues, conditionClauses,
modifyQueries)
return returnValue, modifications