This commit is contained in:
Salvo 'LtWorf' Tomaselli 2015-07-14 11:01:35 +02:00
parent 98ac364dc7
commit 73dd14d9dd
10 changed files with 209 additions and 193 deletions

View File

@ -38,7 +38,7 @@ tests_path = 'test/'
def readfile(fname): def readfile(fname):
'''Reads a file as string and returns its content''' '''Reads a file as string and returns its content'''
fd = open(fname,encoding='utf-8') fd = open(fname, encoding='utf-8')
expr = fd.read() expr = fd.read()
fd.close() fd.close()
return expr return expr

View File

@ -151,6 +151,3 @@ class user_interface (object):
str(e) str(e)
)) ))
return r return r

View File

@ -77,7 +77,9 @@ class TokenizerException (Exception):
class ParserException (Exception): class ParserException (Exception):
pass pass
class CallableString(str): class CallableString(str):
''' '''
This is a string. However it is also callable. This is a string. However it is also callable.
@ -94,7 +96,8 @@ class CallableString(str):
context is a dictionary where to context is a dictionary where to
each name is associated the relative relation each name is associated the relative relation
''' '''
return eval(self,context) return eval(self, context)
class node (object): class node (object):
@ -136,16 +139,16 @@ class node (object):
u"'%s' is not a valid relation name" % self.name) u"'%s' is not a valid relation name" % self.name)
return return
#Expression from right to left, searching for binary operators # Expression from right to left, searching for binary operators
#this means that binary operators have lesser priority than # this means that binary operators have lesser priority than
#unary operators. # unary operators.
#It finds the operator with lesser priority, uses it as root of this # It finds the operator with lesser priority, uses it as root of this
#(sub)tree using everything on its left as left parameter (so building # (sub)tree using everything on its left as left parameter (so building
#a left subtree with the part of the list located on left) and doing # a left subtree with the part of the list located on left) and doing
#the same on right. # the same on right.
#Since it searches for strings, and expressions into parenthesis are # Since it searches for strings, and expressions into parenthesis are
#within sub-lists, they won't be found here, ensuring that they will # within sub-lists, they won't be found here, ensuring that they will
#have highest priority. # have highest priority.
for i in range(len(expression) - 1, -1, -1): for i in range(len(expression) - 1, -1, -1):
if expression[i] in b_operators: # Binary operator if expression[i] in b_operators: # Binary operator
self.kind = BINARY self.kind = BINARY
@ -318,7 +321,7 @@ def tokenize(expression):
items = [] # List for the tokens items = [] # List for the tokens
#This is a state machine. Initial status is determined by the starting of the # This is a state machine. Initial status is determined by the starting of the
# expression. There are the following statuses: # expression. There are the following statuses:
# #
# relation: this is the status if the expressions begins with something else than an # relation: this is the status if the expressions begins with something else than an
@ -327,7 +330,8 @@ def tokenize(expression):
# unary operator: this status is more complex, since it will be followed by a parameter AND a # unary operator: this status is more complex, since it will be followed by a parameter AND a
# sub-expression. # sub-expression.
# sub-expression: this status is entered when finding a '(' and will be exited when finding a ')'. # sub-expression: this status is entered when finding a '(' and will be exited when finding a ')'.
# means that the others open must be counted to determine which close is the right one.''' # means that the others open must be counted to determine which close is
# the right one.
expression = expression.strip() # Removes initial and endind spaces expression = expression.strip() # Removes initial and endind spaces
state = 0 state = 0

View File

@ -101,7 +101,7 @@ class relation (object):
elif self.header.sharedAttributes(other.header) == len(self.header): elif self.header.sharedAttributes(other.header) == len(self.header):
return other.projection(self.header) return other.projection(self.header)
raise Exception('Relations differ: [%s] [%s]' % ( raise Exception('Relations differ: [%s] [%s]' % (
','.join(self.header) , ','.join(other.header) ','.join(self.header), ','.join(other.header)
)) ))
def selection(self, expr): def selection(self, expr):
@ -111,7 +111,9 @@ class relation (object):
newt.header = header(self.header) newt.header = header(self.header)
for i in self.content: for i in self.content:
# Fills the attributes dictionary with the values of the tuple # Fills the attributes dictionary with the values of the tuple
attributes = {attr:i[j].autocast() for j, attr in enumerate(self.header)} attributes = {attr: i[j].autocast()
for j, attr in enumerate(self.header)
}
try: try:
if eval(expr, attributes): if eval(expr, attributes):
@ -129,7 +131,8 @@ class relation (object):
if (self.__class__ != other.__class__)or(self.header.sharedAttributes(other.header) != 0): if (self.__class__ != other.__class__)or(self.header.sharedAttributes(other.header) != 0):
raise Exception( raise Exception(
'Unable to perform product on relations with colliding attributes') 'Unable to perform product on relations with colliding attributes'
)
newt = relation() newt = relation()
newt.header = header(self.header + other.header) newt.header = header(self.header + other.header)
@ -277,7 +280,7 @@ class relation (object):
# Creating the header with all the fields, done like that because order is # Creating the header with all the fields, done like that because order is
# needed # needed
h = (i for i in other.header if i not in shared) h = (i for i in other.header if i not in shared)
newt.header = header(chain(self.header,h)) newt.header = header(chain(self.header, h))
# Shared ids of self # Shared ids of self
sid = self.header.getAttributesId(shared) sid = self.header.getAttributesId(shared)
@ -296,13 +299,13 @@ class relation (object):
match = match and (i[sid[k]] == j[oid[k]]) match = match and (i[sid[k]] == j[oid[k]])
if match: if match:
item = chain(i,(j[l] for l in noid)) item = chain(i, (j[l] for l in noid))
newt.content.add(tuple(item)) newt.content.add(tuple(item))
added = True added = True
# If it didn't partecipate, adds it # If it didn't partecipate, adds it
if not added: if not added:
item = chain(i,repeat('---',len(noid))) item = chain(i, repeat('---', len(noid)))
newt.content.add(tuple(item)) newt.content.add(tuple(item))
return newt return newt
@ -319,7 +322,7 @@ class relation (object):
# Creating the header with all the fields, done like that because order is # Creating the header with all the fields, done like that because order is
# needed # needed
h = (i for i in other.header if i not in shared) h = (i for i in other.header if i not in shared)
newt.header = header(chain(self.header,h)) newt.header = header(chain(self.header, h))
# Shared ids of self # Shared ids of self
sid = self.header.getAttributesId(shared) sid = self.header.getAttributesId(shared)
@ -374,7 +377,7 @@ class relation (object):
col += 1 col += 1
res = "" res = ""
for f,attr in enumerate(self.header): for f, attr in enumerate(self.header):
res += "%s" % (attr.ljust(2 + m_len[f])) res += "%s" % (attr.ljust(2 + m_len[f]))
for r in self.content: for r in self.content:
@ -398,12 +401,11 @@ class relation (object):
affected = 0 affected = 0
attributes = {} attributes = {}
keys = dic.keys() # List of headers to modify keys = dic.keys() # List of headers to modify
f_ids = self.header.getAttributesId( f_ids = self.header.getAttributesId(keys)
keys) # List of indexes corresponding to keys
# new_content=[] #New content of the relation # new_content=[] #New content of the relation
for i in self.content: for i in self.content:
for j,attr in enumerate(self.header): for j, attr in enumerate(self.header):
attributes[attr] = i[j].autocast() attributes[attr] = i[j].autocast()
if eval(expr, attributes): # If expr is true, changing the tuple if eval(expr, attributes): # If expr is true, changing the tuple
@ -445,7 +447,7 @@ class relation (object):
deleting all the tuples that make expr true. deleting all the tuples that make expr true.
Returns the number of affected rows.''' Returns the number of affected rows.'''
#Not necessary self._make_writable() # Not necessary self._make_writable()
l = len(self.content) l = len(self.content)
self._readonly = False self._readonly = False
@ -459,7 +461,7 @@ class header(tuple):
It is used within relations to know if requested operations are accepted''' It is used within relations to know if requested operations are accepted'''
# Since relations are mutalbe we explicitly block hashing them # Since relations are mutalbe we explicitly block hashing them
def __new__ (cls, fields): def __new__(cls, fields):
return super(header, cls).__new__(cls, tuple(fields)) return super(header, cls).__new__(cls, tuple(fields))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -481,7 +483,7 @@ class header(tuple):
params is a dictionary of {old:new} names params is a dictionary of {old:new} names
''' '''
attrs = list(self) attrs = list(self)
for old,new in params.items(): for old, new in params.items():
if not is_valid_relation_name(new): if not is_valid_relation_name(new):
raise Exception('%s is not a valid attribute name' % new) raise Exception('%s is not a valid attribute name' % new)
try: try:

View File

@ -25,9 +25,11 @@ import re
RELATION_NAME_REGEXP = r'^[_a-zA-Z]+[_a-zA-Z0-9]*$' RELATION_NAME_REGEXP = r'^[_a-zA-Z]+[_a-zA-Z0-9]*$'
class rstring (str): class rstring (str):
'''String subclass with some custom methods''' '''String subclass with some custom methods'''
def autocast(self): def autocast(self):
''' '''
Returns the automatic cast for this Returns the automatic cast for this

View File

@ -87,7 +87,10 @@ if __name__ == "__main__":
try: try:
from relational_gui import maingui, guihandler, about, surveyForm from relational_gui import maingui, guihandler, about, surveyForm
except: except:
print ("Module relational_gui is missing.\nPlease install relational package.",file=sys.stderr) print (
"Module relational_gui is missing.\nPlease install relational package.",
file=sys.stderr
)
sys.exit(3) sys.exit(3)
about.version = version about.version = version
@ -103,7 +106,7 @@ if __name__ == "__main__":
form = guihandler.relForm(ui) form = guihandler.relForm(ui)
ui.setupUi(form) ui.setupUi(form)
f = QtGui.QFont() f = QtGui.QFont()
size = f.pointSize(); size = f.pointSize()
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
winFont = 'Cambria' winFont = 'Cambria'
symbolFont = 'Segoe UI Symbol' symbolFont = 'Segoe UI Symbol'
@ -113,19 +116,21 @@ if __name__ == "__main__":
symbolFont = f.family() symbolFont = f.family()
increment = 2 increment = 2
ui.lstHistory.setFont(QtGui.QFont(winFont,size+increment)) ui.lstHistory.setFont(QtGui.QFont(winFont, size + increment))
ui.txtMultiQuery.setFont(QtGui.QFont(winFont,size+increment)) ui.txtMultiQuery.setFont(QtGui.QFont(winFont, size + increment))
ui.txtQuery.setFont(QtGui.QFont(winFont,size+increment)) ui.txtQuery.setFont(QtGui.QFont(winFont, size + increment))
ui.groupOperators.setFont(QtGui.QFont(winFont,size+increment)) ui.groupOperators.setFont(QtGui.QFont(winFont, size + increment))
ui.cmdClearMultilineQuery.setFont(QtGui.QFont(symbolFont)) ui.cmdClearMultilineQuery.setFont(QtGui.QFont(symbolFont))
ui.cmdClearQuery.setFont(QtGui.QFont(symbolFont)) ui.cmdClearQuery.setFont(QtGui.QFont(symbolFont))
form.restore_settings() form.restore_settings()
m = enumerate(map(os.path.isfile, files)) m = enumerate(map(os.path.isfile, files))
invalid = ' '.join((files[i[0]] for i in (filter(lambda x: not x[1], m)))) invalid = ' '.join(
(files[i[0]] for i in (filter(lambda x: not x[1], m)))
)
if invalid: if invalid:
print ("%s: not a file" % invalid,file=sys.stderr) print ("%s: not a file" % invalid, file=sys.stderr)
printhelp(12) printhelp(12)
if len(files): if len(files):
form.loadRelation(files) form.loadRelation(files)
@ -137,7 +142,10 @@ if __name__ == "__main__":
try: try:
import relational_readline.linegui import relational_readline.linegui
except: except:
print ("Module relational_readline is missing.\nPlease install relational-cli package.",file=sys.stderr) print (
"Module relational_readline is missing.\nPlease install relational-cli package.",
file=sys.stderr
)
sys.exit(3) sys.exit(3)
relational_readline.linegui.version = version relational_readline.linegui.version = version
relational_readline.linegui.main(files) relational_readline.linegui.main(files)

View File

@ -80,7 +80,8 @@ class creatorForm(QtWidgets.QDialog):
self.table.setItem(1, 1, i11) self.table.setItem(1, 1, i11)
def create_relation(self): def create_relation(self):
h = (self.table.item(0, i).text() for i in range(self.table.columnCount())) h = (self.table.item(0, i).text()
for i in range(self.table.columnCount()))
try: try:
header = relation.header(h) header = relation.header(h)

View File

@ -79,7 +79,8 @@ class relForm(QtWidgets.QMainWindow):
query = self.ui.txtQuery.text() query = self.ui.txtQuery.text()
try: try:
result = optimizer.optimize_all(query, self.user_interface.relations) result = optimizer.optimize_all(
query, self.user_interface.relations)
self.ui.txtQuery.setText(result) self.ui.txtQuery.setText(result)
except Exception as e: except Exception as e:
self.error(e) self.error(e)
@ -106,7 +107,7 @@ class relForm(QtWidgets.QMainWindow):
if self.multiline: if self.multiline:
return self._run_multiline() return self._run_multiline()
#Single line query # Single line query
query = self.ui.txtQuery.text() query = self.ui.txtQuery.text()
res_rel = self.ui.txtResult.text() # result relation's name res_rel = self.ui.txtResult.text() # result relation's name
@ -149,7 +150,7 @@ class relForm(QtWidgets.QMainWindow):
self.ui.table.addTopLevelItem(item) self.ui.table.addTopLevelItem(item)
# Sets columns # Sets columns
for i,attr in enumerate(rel.header): for i, attr in enumerate(rel.header):
self.ui.table.headerItem().setText(i, attr) self.ui.table.headerItem().setText(i, attr)
self.ui.table.resizeColumnToContents( self.ui.table.resizeColumnToContents(
i) # Must be done in order to avoid too small columns i) # Must be done in order to avoid too small columns
@ -201,7 +202,7 @@ class relForm(QtWidgets.QMainWindow):
self.user_interface.set_relation(i.text(), result) self.user_interface.set_relation(i.text(), result)
self.updateRelations() self.updateRelations()
def error(self,exception): def error(self, exception):
print (exception) print (exception)
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
None, QtWidgets.QApplication.translate("Form", "Error"), None, QtWidgets.QApplication.translate("Form", "Error"),
@ -217,12 +218,13 @@ class relForm(QtWidgets.QMainWindow):
"Form", "Insert the name for the new relation"), "Form", "Insert the name for the new relation"),
QtWidgets.QLineEdit.Normal, '' QtWidgets.QLineEdit.Normal, ''
) )
if res[1] == False:# or len(res[0]) == 0: if res[1] == False: # or len(res[0]) == 0:
return None return None
name = res[0] name = res[0]
if not rtypes.is_valid_relation_name(name): if not rtypes.is_valid_relation_name(name):
r = QtWidgets.QApplication.translate( r = QtWidgets.QApplication.translate(
"Form", str("Wrong name for destination relation: %s." % name) "Form", str(
"Wrong name for destination relation: %s." % name)
) )
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
self, QtWidgets.QApplication.translate("Form", "Error"), r self, QtWidgets.QApplication.translate("Form", "Error"), r
@ -256,8 +258,9 @@ class relForm(QtWidgets.QMainWindow):
def restore_settings(self): def restore_settings(self):
# self.settings.value('session_name','default').toString() # self.settings.value('session_name','default').toString()
self.setMultiline(self.settings.value('multiline','false')=='true') self.setMultiline(self.settings.value('multiline', 'false') == 'true')
self.ui.txtMultiQuery.setPlainText(self.settings.value('multiline/query','')) self.ui.txtMultiQuery.setPlainText(
self.settings.value('multiline/query', ''))
try: try:
self.restoreGeometry(self.settings.value('maingui/geometry')) self.restoreGeometry(self.settings.value('maingui/geometry'))
self.restoreState(self.settings.value('maingui/windowState')) self.restoreState(self.settings.value('maingui/windowState'))
@ -299,7 +302,7 @@ class relForm(QtWidgets.QMainWindow):
continue continue
try: try:
self.user_interface.load(f,name) self.user_interface.load(f, name)
except Exception as e: except Exception as e:
self.error(e) self.error(e)
continue continue

View File

@ -24,7 +24,6 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from relational import maintenance from relational import maintenance
class surveyForm (QtWidgets.QWidget): class surveyForm (QtWidgets.QWidget):
'''This class is the form used for the survey, needed to intercept the events. '''This class is the form used for the survey, needed to intercept the events.