Explorar o código

Added new pyparsing-based parser allowing for nested queries

Stefan Schmidt %!s(int64=6) %!d(string=hai) anos
pai
achega
7cf7274887
Modificáronse 5 ficheiros con 151 adicións e 146 borrados
  1. 4 1
      README.md
  2. 28 20
      code/Core/Controller.py
  3. 26 0
      code/Core/QueryParser.py
  4. 90 125
      code/Core/StatsDatabase.py
  5. 3 0
      code/Test/test_Queries.py

+ 4 - 1
README.md

@@ -150,13 +150,16 @@ There are also parameterizable selectors which take conditions as input. Followi
 	-> returns the MAC address matching the given criteria
 	Supports the field: ipAddress
 
+Parameterizable selectors also allow for specifying another query in the comparison instead of a specific value, like the following example demonstrates:
+	macAddress(ipAddress=most_used(ipAddress))
+
 __Extractors__ are to be used on the result of a named query. If the result is a list, applying an extractor reduces the result set to a single element. If the result is already a single element, the extractor is ignored.
 ```
 random(...)  -> returns a random element from a list
 first(...)   -> returns the first element from a list
 last(...)    -> returns the last element from a list
 ```
-Attention: Named queries are designed to be combined with extractors, like ``random(all(ipAddress))``. But it is currently NOT possible to encapsulate multiple named queries, like `` macAddress(ipAddress=most_used(ipAddress))``. This can be circumvented by first querying ``most_used(ipAddress)`` and then inserting the result as argument in ``macAddress(…)``.
+Named queries are designed to be combined with extractors, like ``random(all(ipAddress))``
 
 ## Versioning
 The [SemVer](http://semver.org/spec/v2.0.0.html) is used for versioning. For currently available versions of ID2T, see page [releases](https://git.tk.informatik.tu-darmstadt.de/SPIN/ID2T-toolkit/releases).

+ 28 - 20
code/Core/Controller.py

@@ -2,6 +2,7 @@ import os
 import readline
 import sys
 
+import pyparsing as pp
 import Core.AttackController as atkCtrl
 import Core.LabelManager as LabelManager
 import Core.Statistics as Statistics
@@ -236,28 +237,35 @@ class Controller:
             buffer += line
             import sqlite3
             if sqlite3.complete_statement(buffer):
-                try:
-                    buffer = buffer.strip()
-                    if buffer.lower().startswith('help'):
-                        buffer = buffer.strip(';')
-                        self.process_help(buffer.split(' ')[1:])
-                    elif buffer.lower().strip() == 'labels;':
-                        if not self.label_manager.labels:
-                            print("No labels found.")
-                        else:
-                            print("Attacks listed in the label file:")
-                            print()
-                            for label in self.label_manager.labels:
-                                print("Attack name:     " + str(label.attack_name))
-                                print("Attack note:     " + str(label.attack_note))
-                                print("Start timestamp: " + str(label.timestamp_start))
-                                print("End timestamp:   " + str(label.timestamp_end))
-                                print()
-                        print()
+                buffer = buffer.strip()
+                if buffer.lower().startswith('help'):
+                    buffer = buffer.strip(';')
+                    self.process_help(buffer.split(' ')[1:])
+                elif buffer.lower().strip() == 'labels;':
+                    if not self.label_manager.labels:
+                        print("No labels found.")
                     else:
+                        print("Attacks listed in the label file:")
+                        print()
+                        for label in self.label_manager.labels:
+                            print("Attack name:     " + str(label.attack_name))
+                            print("Attack note:     " + str(label.attack_note))
+                            print("Start timestamp: " + str(label.timestamp_start))
+                            print("End timestamp:   " + str(label.timestamp_end))
+                            print()
+                    print()
+                else:
+                    try:
                         self.statisticsDB.process_db_query(buffer, True)
-                except sqlite3.Error as e:
-                    print("An error occurred:", e.args[0])
+                    except sqlite3.Error as e:
+                        print("An error occurred:", e.args[0])
+                    except pp.ParseException as e:
+                        sys.stderr.write("Error in query:\n")
+                        sys.stderr.write(buffer)
+                        sys.stderr.write("\n")
+                        for i in range(1, e.col):
+                            sys.stderr.write(" ")
+                        sys.stderr.write("^\n\n")
                 buffer = ""
 
         readline.set_history_length(1000)

+ 26 - 0
code/Core/QueryParser.py

@@ -0,0 +1,26 @@
+import pyparsing as pp
+
+
+class QueryParser:
+    def __init__(self):
+        extractor = pp.Keyword("random") ^ pp.Keyword("first") ^ pp.Keyword("last")
+        selector = pp.Keyword("most_used") ^ pp.Keyword("least_used") ^ pp.Keyword("avg") ^ pp.Keyword("all")
+        attribute = pp.Keyword("ipaddress") ^ pp.Keyword("macaddress") ^ pp.Keyword("portnumber") ^ pp.Keyword("protocolname") ^ pp.Keyword("ttlvalue") ^ pp.Keyword("mssvalue") ^ pp.Keyword("winsize") ^ pp.Keyword("ipclass") ^ pp.Keyword("pktssent") ^ pp.Keyword("pktsreceived") ^ pp.Keyword("mss") ^ pp.Keyword("kbytesreceived") ^ pp.Keyword("kbytessent")
+        simple_selector_query = selector + pp.Suppress("(") + attribute + pp.Suppress(")")
+
+        param_selectors = pp.Keyword("ipaddress").setParseAction(pp.replaceWith("ipaddress_param")) ^ pp.Keyword("macaddress").setParseAction(pp.replaceWith("macaddress_param"))
+        operators = pp.Literal("=") ^ pp.Literal("<=") ^ pp.Literal("<") ^ pp.Literal(">=") ^ pp.Literal(">")
+        expr = pp.Forward()
+        comparison = pp.Group(attribute + operators + (pp.Word(pp.alphanums + ".:") ^ expr))
+        parameterized_query = param_selectors + pp.Suppress("(") + pp.Group(pp.delimitedList(comparison)) + pp.Suppress(")")
+        # parameterized_query = param_selectors + pp.Suppress("(") + comparison + pp.Suppress(")")
+
+        all_selector_queries = (simple_selector_query ^ parameterized_query)
+        extractor_selector_query = extractor + pp.Suppress("(") + all_selector_queries + pp.Suppress(")")
+
+        named_query = (extractor_selector_query ^ all_selector_queries)
+        expr << pp.Group(named_query)
+        self.full_query = named_query + pp.Suppress(";")
+
+    def parse_query(self, querystring):
+        return self.full_query.parseString(querystring)

+ 90 - 125
code/Core/StatsDatabase.py

@@ -7,6 +7,8 @@ import sys
 # TODO: double check this import
 # does it complain because libpcapreader is not a .py?
 import ID2TLib.libpcapreader as pr
+import Core.QueryParser as qp
+import pyparsing as pp
 
 
 def dict_gen(curs: sqlite3.Cursor):
@@ -30,6 +32,8 @@ class StatsDatabase:
 
         :param db_path: The path to the database file
         """
+        self.query_parser = qp.QueryParser()
+
         self.existing_db = os.path.exists(db_path)
         self.database = sqlite3.connect(db_path)
         self.cursor = self.database.cursor()
@@ -164,6 +168,9 @@ class StatsDatabase:
         field_types = self.get_field_types('ip_mac', 'ip_ttl', 'ip_ports', 'ip_protocols', 'ip_statistics', 'ip_mac')
         conditions = []
         for key, op, value in param_op_val:
+            if isinstance(value, pp.ParseResults):
+                # If we have another query instead of a direct value, execute and replace it
+                value = self._execute_query_list(value)[0][0]
             # this makes sure that TEXT fields are queried by strings,
             # e.g. ipAddress=192.168.178.1 --is-converted-to--> ipAddress='192.168.178.1'
             if field_types.get(key) == 'TEXT':
@@ -179,102 +186,85 @@ class StatsDatabase:
         self.cursor.execute(query)
         return self.cursor.fetchall()
 
-    def _process_named_query(self, query_param_list):
+    named_queries = {
+        "most_used.ipaddress": "SELECT ipAddress FROM ip_statistics WHERE (pktsSent+pktsReceived) == "
+                               "(SELECT MAX(pktsSent+pktsReceived) from ip_statistics) ORDER BY ipAddress ASC",
+        "most_used.macaddress": "SELECT macAddress FROM (SELECT macAddress, COUNT(*) as occ from ip_mac GROUP BY "
+                                "macAddress) WHERE occ=(SELECT COUNT(*) as occ from ip_mac GROUP BY macAddress "
+                                "ORDER BY occ DESC LIMIT 1) ORDER BY macAddress ASC",
+        "most_used.portnumber": "SELECT portNumber FROM ip_ports GROUP BY portNumber HAVING COUNT(portNumber)="
+                                "(SELECT MAX(cntPort) from (SELECT portNumber, COUNT(portNumber) as cntPort FROM "
+                                "ip_ports GROUP BY portNumber)) ORDER BY portNumber ASC",
+        "most_used.protocolname": "SELECT protocolName FROM ip_protocols GROUP BY protocolName HAVING "
+                                  "COUNT(protocolCount)=(SELECT COUNT(protocolCount) as cnt FROM ip_protocols "
+                                  "GROUP BY protocolName ORDER BY cnt DESC LIMIT 1) ORDER BY protocolName ASC",
+        "most_used.ttlvalue": "SELECT ttlValue FROM (SELECT ttlValue, SUM(ttlCount) as occ FROM ip_ttl GROUP BY "
+                              "ttlValue) WHERE occ=(SELECT SUM(ttlCount) as occ FROM ip_ttl GROUP BY ttlValue "
+                              "ORDER BY occ DESC LIMIT 1) ORDER BY ttlValue ASC",
+        "most_used.mssvalue": "SELECT mssValue FROM (SELECT mssValue, SUM(mssCount) as occ FROM tcp_mss GROUP BY "
+                              "mssValue) WHERE occ=(SELECT SUM(mssCount) as occ FROM tcp_mss GROUP BY mssValue "
+                              "ORDER BY occ DESC LIMIT 1) ORDER BY mssValue ASC",
+        "most_used.winsize": "SELECT winSize FROM (SELECT winSize, SUM(winCount) as occ FROM tcp_win GROUP BY "
+                             "winSize) WHERE occ=(SELECT SUM(winCount) as occ FROM tcp_win GROUP BY winSize ORDER "
+                             "BY occ DESC LIMIT 1) ORDER BY winSize ASC",
+        "most_used.ipclass": "SELECT ipClass FROM (SELECT ipClass, COUNT(*) as occ from ip_statistics GROUP BY "
+                             "ipClass ORDER BY occ DESC) WHERE occ=(SELECT COUNT(*) as occ from ip_statistics "
+                             "GROUP BY ipClass ORDER BY occ DESC LIMIT 1) ORDER BY ipClass ASC",
+        "least_used.ipaddress": "SELECT ipAddress FROM ip_statistics WHERE (pktsSent+pktsReceived) == (SELECT "
+                                "MIN(pktsSent+pktsReceived) from ip_statistics) ORDER BY ipAddress ASC",
+        "least_used.macaddress": "SELECT macAddress FROM (SELECT macAddress, COUNT(*) as occ from ip_mac GROUP "
+                                 "BY macAddress) WHERE occ=(SELECT COUNT(*) as occ from ip_mac GROUP BY macAddress "
+                                 "ORDER BY occ ASC LIMIT 1) ORDER BY macAddress ASC",
+        "least_used.portnumber": "SELECT portNumber FROM ip_ports GROUP BY portNumber HAVING COUNT(portNumber)="
+                                 "(SELECT MIN(cntPort) from (SELECT portNumber, COUNT(portNumber) as cntPort FROM "
+                                 "ip_ports GROUP BY portNumber)) ORDER BY portNumber ASC",
+        "least_used.protocolname": "SELECT protocolName FROM ip_protocols GROUP BY protocolName HAVING "
+                                   "COUNT(protocolCount)=(SELECT COUNT(protocolCount) as cnt FROM ip_protocols "
+                                   "GROUP BY protocolName ORDER BY cnt ASC LIMIT 1) ORDER BY protocolName ASC",
+        "least_used.ttlvalue": "SELECT ttlValue FROM (SELECT ttlValue, SUM(ttlCount) as occ FROM ip_ttl GROUP BY "
+                               "ttlValue) WHERE occ=(SELECT SUM(ttlCount) as occ FROM ip_ttl GROUP BY ttlValue "
+                               "ORDER BY occ ASC LIMIT 1) ORDER BY ttlValue ASC",
+        "least_used.mssvalue": "SELECT mssValue FROM (SELECT mssValue, SUM(mssCount) as occ FROM tcp_mss GROUP BY "
+                               "mssValue) WHERE occ=(SELECT SUM(mssCount) as occ FROM tcp_mss GROUP BY mssValue "
+                               "ORDER BY occ ASC LIMIT 1) ORDER BY mssValue ASC",
+        "least_used.winsize": "SELECT winSize FROM (SELECT winSize, SUM(winCount) as occ FROM tcp_win GROUP BY "
+                              "winSize) WHERE occ=(SELECT SUM(winCount) as occ FROM tcp_win GROUP BY winSize "
+                              "ORDER BY occ ASC LIMIT 1) ORDER BY winSize ASC",
+        "avg.pktsreceived": "SELECT avg(pktsReceived) from ip_statistics",
+        "avg.pktssent": "SELECT avg(pktsSent) from ip_statistics",
+        "avg.kbytesreceived": "SELECT avg(kbytesReceived) from ip_statistics",
+        "avg.kbytessent": "SELECT avg(kbytesSent) from ip_statistics",
+        "avg.ttlvalue": "SELECT avg(ttlValue) from ip_ttl",
+        "avg.mss": "SELECT avg(mssValue) from tcp_mss",
+        "all.ipaddress": "SELECT ipAddress from ip_statistics ORDER BY ipAddress ASC",
+        "all.ttlvalue": "SELECT DISTINCT ttlValue from ip_ttl ORDER BY ttlValue ASC",
+        "all.mss": "SELECT DISTINCT mssValue from tcp_mss ORDER BY mssValue ASC",
+        "all.macaddress": "SELECT DISTINCT macAddress from ip_mac ORDER BY macAddress ASC",
+        "all.portnumber": "SELECT DISTINCT portNumber from ip_ports ORDER BY portNumber ASC",
+        "all.protocolname": "SELECT DISTINCT protocolName from ip_protocols ORDER BY protocolName ASC"}
+
+    def _execute_query_list(self, query_list):
         """
-        Executes a named query.
-
-        :param query_param_list: A query list consisting of (keyword, params), e.g. [(most_used, ipAddress), (random,)]
-        :return: the result of the query
+        Recursively executes a list of named queries. They are of the following form:
+        ['macaddress_param', [['ipaddress', '=', ['most_used', 'ipaddress']]]]
+        :param query_list: The query statement list obtained from the query parser
+        :return: The result of the query (either a single result or a list).
         """
-        # Definition of SQL queries associated to named queries
-        named_queries = {
-            "most_used.ipaddress": "SELECT ipAddress FROM ip_statistics WHERE (pktsSent+pktsReceived) == "
-                                   "(SELECT MAX(pktsSent+pktsReceived) from ip_statistics) ORDER BY ipAddress ASC",
-            "most_used.macaddress": "SELECT macAddress FROM (SELECT macAddress, COUNT(*) as occ from ip_mac GROUP BY "
-                                    "macAddress) WHERE occ=(SELECT COUNT(*) as occ from ip_mac GROUP BY macAddress "
-                                    "ORDER BY occ DESC LIMIT 1) ORDER BY macAddress ASC",
-            "most_used.portnumber": "SELECT portNumber FROM ip_ports GROUP BY portNumber HAVING COUNT(portNumber)="
-                                    "(SELECT MAX(cntPort) from (SELECT portNumber, COUNT(portNumber) as cntPort FROM "
-                                    "ip_ports GROUP BY portNumber)) ORDER BY portNumber ASC",
-            "most_used.protocolname": "SELECT protocolName FROM ip_protocols GROUP BY protocolName HAVING "
-                                      "COUNT(protocolCount)=(SELECT COUNT(protocolCount) as cnt FROM ip_protocols "
-                                      "GROUP BY protocolName ORDER BY cnt DESC LIMIT 1) ORDER BY protocolName ASC",
-            "most_used.ttlvalue": "SELECT ttlValue FROM (SELECT ttlValue, SUM(ttlCount) as occ FROM ip_ttl GROUP BY "
-                                  "ttlValue) WHERE occ=(SELECT SUM(ttlCount) as occ FROM ip_ttl GROUP BY ttlValue "
-                                  "ORDER BY occ DESC LIMIT 1) ORDER BY ttlValue ASC",
-            "most_used.mssvalue": "SELECT mssValue FROM (SELECT mssValue, SUM(mssCount) as occ FROM tcp_mss GROUP BY "
-                                  "mssValue) WHERE occ=(SELECT SUM(mssCount) as occ FROM tcp_mss GROUP BY mssValue "
-                                  "ORDER BY occ DESC LIMIT 1) ORDER BY mssValue ASC",
-            "most_used.winsize": "SELECT winSize FROM (SELECT winSize, SUM(winCount) as occ FROM tcp_win GROUP BY "
-                                 "winSize) WHERE occ=(SELECT SUM(winCount) as occ FROM tcp_win GROUP BY winSize ORDER "
-                                 "BY occ DESC LIMIT 1) ORDER BY winSize ASC",
-            "most_used.ipclass": "SELECT ipClass FROM (SELECT ipClass, COUNT(*) as occ from ip_statistics GROUP BY "
-                                 "ipClass ORDER BY occ DESC) WHERE occ=(SELECT COUNT(*) as occ from ip_statistics "
-                                 "GROUP BY ipClass ORDER BY occ DESC LIMIT 1) ORDER BY ipClass ASC",
-            "least_used.ipaddress": "SELECT ipAddress FROM ip_statistics WHERE (pktsSent+pktsReceived) == (SELECT "
-                                    "MIN(pktsSent+pktsReceived) from ip_statistics) ORDER BY ipAddress ASC",
-            "least_used.macaddress": "SELECT macAddress FROM (SELECT macAddress, COUNT(*) as occ from ip_mac GROUP "
-                                     "BY macAddress) WHERE occ=(SELECT COUNT(*) as occ from ip_mac GROUP BY macAddress "
-                                     "ORDER BY occ ASC LIMIT 1) ORDER BY macAddress ASC",
-            "least_used.portnumber": "SELECT portNumber FROM ip_ports GROUP BY portNumber HAVING COUNT(portNumber)="
-                                     "(SELECT MIN(cntPort) from (SELECT portNumber, COUNT(portNumber) as cntPort FROM "
-                                     "ip_ports GROUP BY portNumber)) ORDER BY portNumber ASC",
-            "least_used.protocolname": "SELECT protocolName FROM ip_protocols GROUP BY protocolName HAVING "
-                                       "COUNT(protocolCount)=(SELECT COUNT(protocolCount) as cnt FROM ip_protocols "
-                                       "GROUP BY protocolName ORDER BY cnt ASC LIMIT 1) ORDER BY protocolName ASC",
-            "least_used.ttlvalue": "SELECT ttlValue FROM (SELECT ttlValue, SUM(ttlCount) as occ FROM ip_ttl GROUP BY "
-                                   "ttlValue) WHERE occ=(SELECT SUM(ttlCount) as occ FROM ip_ttl GROUP BY ttlValue "
-                                   "ORDER BY occ ASC LIMIT 1) ORDER BY ttlValue ASC",
-            "least_used.mssvalue": "SELECT mssValue FROM (SELECT mssValue, SUM(mssCount) as occ FROM tcp_mss GROUP BY "
-                                   "mssValue) WHERE occ=(SELECT SUM(mssCount) as occ FROM tcp_mss GROUP BY mssValue "
-                                   "ORDER BY occ ASC LIMIT 1) ORDER BY mssValue ASC",
-            "least_used.winsize": "SELECT winSize FROM (SELECT winSize, SUM(winCount) as occ FROM tcp_win GROUP BY "
-                                  "winSize) WHERE occ=(SELECT SUM(winCount) as occ FROM tcp_win GROUP BY winSize "
-                                  "ORDER BY occ ASC LIMIT 1) ORDER BY winSize ASC",
-            "avg.pktsreceived": "SELECT avg(pktsReceived) from ip_statistics",
-            "avg.pktssent": "SELECT avg(pktsSent) from ip_statistics",
-            "avg.kbytesreceived": "SELECT avg(kbytesReceived) from ip_statistics",
-            "avg.kbytessent": "SELECT avg(kbytesSent) from ip_statistics",
-            "avg.ttlvalue": "SELECT avg(ttlValue) from ip_ttl",
-            "avg.mss": "SELECT avg(mssValue) from tcp_mss",
-            "all.ipaddress": "SELECT ipAddress from ip_statistics ORDER BY ipAddress ASC",
-            "all.ttlvalue": "SELECT DISTINCT ttlValue from ip_ttl ORDER BY ttlValue ASC",
-            "all.mss": "SELECT DISTINCT mssValue from tcp_mss ORDER BY mssValue ASC",
-            "all.macaddress": "SELECT DISTINCT macAddress from ip_mac ORDER BY macAddress ASC",
-            "all.portnumber": "SELECT DISTINCT portNumber from ip_ports ORDER BY portNumber ASC",
-            "all.protocolname": "SELECT DISTINCT protocolName from ip_protocols ORDER BY protocolName ASC"}
-
-        # Retrieve values by selectors, if given, reduce results by extractor
-        last_result = 0
-        for q in query_param_list:
-            # if selector, like avg, ttl, is given
-            if any(e in q[0] for e in self._get_selector_keywords()):
-                (keyword, param) = q
-                query = named_queries.get(keyword + "." + param)
-                self.cursor.execute(str(query))
-                last_result = self.cursor.fetchall()
-            # if selector is parametrized, i.e. ipAddress(mac=AA:BB:CC:DD:EE) or macAddress(ipAddress=192.168.178.1)
-            elif any(e in q[0] for e in self._get_parametrized_selector_keywords()) and any(
-                            o in q[1] for o in ["<", "=", ">", "<=", ">="]):
-                (keyword, param) = q
-                # convert string into list of triples
-                # example string 'paramName1<operator1>paramValue1,paramName2<operator2>paramValue2,...'
-                param_op_val = [(key, op, value) for (key, op, value) in
-                                [re.split("(<=|>=|>|<|=)", x) for x in param.split(",")]]
-                last_result = self.named_query_parameterized(keyword, param_op_val)
-            # if extractor, like random, first, last, is given
-            elif any(e in q[0] for e in self._get_extractor_keywords()) and (
-                        isinstance(last_result, list) or isinstance(last_result, tuple)):
-                extractor = q[0]
-                if extractor == 'random':
-                    index = rnd.randint(a=0, b=len(last_result) - 1)
-                    last_result = last_result[index]
-                elif extractor == 'first':
-                    last_result = last_result[0]
-                elif extractor == 'last':
-                    last_result = last_result[-1]
-
-        return last_result
+        if query_list[0] == "random":
+            return rnd.choice(self._execute_query_list(query_list[1:]))
+        elif query_list[0] == "first":
+            return self._execute_query_list(query_list[1:])[0]
+        elif query_list[0] == "last":
+            return self._execute_query_list(query_list[1:])[-1]
+        elif query_list[0] == "macaddress_param":
+            return self.named_query_parameterized("macaddress", query_list[1])
+        elif query_list[0] == "ipaddress_param":
+            return self.named_query_parameterized("ipaddress", query_list[1])
+        else:
+            query = self.named_queries.get(query_list[0] + "." + query_list[1])
+            self.cursor.execute(str(query))
+            last_result = self.cursor.fetchall()
+            return last_result
 
     def process_db_query(self, query_string_in: str, print_results=False, sql_query_parameters: tuple = None):
         """
@@ -296,36 +286,11 @@ class StatsDatabase:
             result = self.process_user_defined_query(query_string, sql_query_parameters)
         # query string is a named query -> parse it and pass it to statisticsDB
         elif any(k in query_string for k in named_query_keywords) and all(k in query_string for k in ['(', ')']):
-            # Clean query_string
-            query_string = query_string.replace(" ", "")
-
-            # Validity check: Brackets
-            brackets_open, brackets_closed = query_string.count("("), query_string.count(")")
-            if not (brackets_open == brackets_closed):
-                sys.stderr.write("Bracketing of given query '" + query_string + "' is incorrect.")
-
-            # Parse query string into [ (query_keyword1, query_params1), ... ]
-            delimiter_start, delimiter_end = "(", ")"
-            kplist = []
-            current_word = ""
-            for char in query_string:  # process characters one-by-one
-                # if char is no delimiter, add char to current_word
-                if char != delimiter_end and char != delimiter_start:
-                    current_word += char
-                # if a start delimiter was found and the current_word so far is a keyword, add it to kplist
-                elif char == delimiter_start:
-                    if current_word in named_query_keywords:
-                        kplist.append((current_word,))
-                        current_word = ""
-                    else:
-                        print("ERROR: Unrecognized keyword '" + current_word + "' found. Ignoring query.")
-                        return
-                # else if characeter is end delimiter and there were no two directly following ending delimiters,
-                # the current_word must be the parameters of an earlier given keyword
-                elif char == delimiter_end and len(current_word) > 0:
-                    kplist[-1] += (current_word,)
-                    current_word = ""
-            result = self._process_named_query(kplist[::-1])
+            if query_string[-1] != ";":
+                query_string += ";"
+            query_list = self.query_parser.parse_query(query_string)
+            print(str(query_list))
+            result = self._execute_query_list(query_list)
         else:
             sys.stderr.write(
                 "Query invalid. Only named queries and SQL SELECT/INSERT allowed. Please check the query's syntax!\n")
@@ -342,7 +307,7 @@ class StatsDatabase:
                 requires_extraction = False
 
         # If tuple of tuples or list of tuples, each consisting of single element is returned,
-        # then convert it into list of values, because the returned colum is clearly specified by the given query
+        # then convert it into list of values, because the returned column is clearly specified by the given query
         if (isinstance(result, tuple) or isinstance(result, list)) and all(len(val) == 1 for val in result):
             result = [c for c in result for c in c]
 

+ 3 - 0
code/Test/test_Queries.py

@@ -227,3 +227,6 @@ class TestQueries(unittest.TestCase):
 
     def test_all_protocolname(self):
         self.assertEqual(controller.statistics.process_db_query('all(protocolname)'), ['IPv4', 'TCP', 'UDP'])
+
+    def test_nested_query(self):
+        self.assertEqual(controller.statistics.process_db_query('macaddress(ipaddress=most_used(ipaddress))'), '08:00:27:a3:83:43')