From 6674b5cb1bd5c5424a69cd2a291baf3339f16625 Mon Sep 17 00:00:00 2001 From: "Jesus M. Gonzalez-Barahona" Date: Tue, 29 Sep 2015 19:33:28 +0200 Subject: [PATCH] Added "enforce" field to BuildQuery, for being able of enforcing returning lists. BuildQuery was returning a dictionary of lists if returned rows were larger than one, and a dictionary of elements if returned rows were exactly one. In some cases, this causes some trouble when a caller expects lists, but elements are obtained because there is only one row. This patch adds a new parameter, "enforce", which by default is None, and can take as well the value "list". If the value is None, the previous behavior is respected, for backwards compatibility. If it is "list", a dictionary of lists is returned always. In addition, an exception is raised if the sql string received is None, but only if enforce is "list", again for backwards compatibility. top_questions_qaforums.py has been touched to use this new parameter, mostly as a test. All calls that expect a list should use this parameter, to avoid trouble when resulting row is one... --- vizgrimoire/GrimoireSQL.py | 69 +++++++++++++++- .../analysis/top_questions_qaforums.py | 2 +- vizgrimoire/metrics/query_builder.py | 79 +++++++++++++++++-- 3 files changed, 140 insertions(+), 10 deletions(-) diff --git a/vizgrimoire/GrimoireSQL.py b/vizgrimoire/GrimoireSQL.py index fac40b5e..784a92f3 100644 --- a/vizgrimoire/GrimoireSQL.py +++ b/vizgrimoire/GrimoireSQL.py @@ -87,17 +87,78 @@ def SetDBChannel (user=None, password=None, database=None, cursor = db.cursor() cursor.execute("SET NAMES 'utf8'") -def ExecuteQuery (sql): - result = {} +def ExecuteQuery (sql, enforce = None): + """Execute query, return results. + + Returns the results of executing the specified SQL query, as a + dictionary. In this dictionary, keys are field names for the query + results, values are the corresponding value(s) for the corresponding + field in all rows. + + Depending on the value of the parameter 'enforce', results will + be enforced to be a list, or (default behavior, for compatibility), + a list if results are more than one row, or single values if the + result is only one row. If result is 0 rows, an empty list will always + be returned for each key. + + For example, if the following values are returned by the query + (assuming each line is a row, first line is the name of fields): + + "Field A", "Field B" + "String 1", 1 + "String 2", 2 + + the returning dictionary will be + + {"Field A": ["String 1", "String 2"], + "Field B": [1, 2] + } + + If query results is just one row, and enforce == "list", the result + is as follows: + + {"Field A": ["String 1"], + "Field B": [1] + } + + In the same case, enforce == None, the result is: + + {"Field A": "String 1", + "Field B": 1 + } + + If query results is 0 rows: + + {"Field A": [], + "Field B": [] + } + + If for some reason the the description of the fields cannot be obtained, + it returns None (if enforce =) "list") or empty directory + (if enforce == None). Probably it should raise an exception. + + :param sql: SQL string to execute + :param enforce: enforce some kind of result, "list" or None (default) + + :returns result of executing the SQL string + + """ + cursor.execute(sql) rows = cursor.rowcount columns = cursor.description - if columns is None: return result + if columns is None: + if enforce == "list": + return None + else: + return {} + + result = {} for column in columns: result[column[0]] = [] - if rows > 1: + if rows > 1 or ((rows == 1) and (enforce == "list")): for value in cursor.fetchall(): for (index,column) in enumerate(value): result[columns[index][0]].append(column) diff --git a/vizgrimoire/analysis/top_questions_qaforums.py b/vizgrimoire/analysis/top_questions_qaforums.py index 6c56bf15..7445f82c 100644 --- a/vizgrimoire/analysis/top_questions_qaforums.py +++ b/vizgrimoire/analysis/top_questions_qaforums.py @@ -63,7 +63,7 @@ def top_commented(self): group by q.question_identifier order by comments desc limit %s """ % (self.filters.startdate, self.filters.enddate, self.filters.npeople) - return self.db.ExecuteQuery(query) + return self.db.ExecuteQuery(query, enforce = "list") def top_visited(self): # The top visited questions are those questions with the diff --git a/vizgrimoire/metrics/query_builder.py b/vizgrimoire/metrics/query_builder.py index 5bb2a680..b208d921 100644 --- a/vizgrimoire/metrics/query_builder.py +++ b/vizgrimoire/metrics/query_builder.py @@ -222,19 +222,88 @@ def __SetDBChannel__ (self, user=None, password=None, database=None, return db - def ExecuteQuery (self, sql): - if sql is None: return {} + def ExecuteQuery (self, sql, enforce = None): + """Execute query, return results. + + Returns the results of executing the specified SQL query, as a + dictionary. In this dictionary, keys are field names for the query + results, values are the corresponding value(s) for the corresponding + field in all rows. + + Depending on the value of the parameter 'enforce', results will + be enforced to be a list, or (default behavior, for compatibility), + a list if results are more than one row, or single values if the + result is only one row. If result is 0 rows, an empty list will always + be returned for each key. + + For example, if the following values are returned by the query + (assuming each line is a row, first line is the name of fields): + + "Field A", "Field B" + "String 1", 1 + "String 2", 2 + + the returning dictionary will be + + {"Field A": ["String 1", "String 2"], + "Field B": [1, 2] + } + + If query results is just one row, and enforce == "list", the result + is as follows: + + {"Field A": ["String 1"], + "Field B": [1] + } + + In the same case, enforce == None, the result is: + + {"Field A": "String 1", + "Field B": 1 + } + + If query results is 0 rows: + + {"Field A": [], + "Field B": [] + } + + If for some reason the the description of the fields cannot be obtained, + it returns None (if enforce =) "list") or empty directory + (if enforce == None). Probably it should raise an exception. + + :param sql: SQL string to execute + :param enforce: enforce some kind of result, "list" or None (default) + + :returns result of executing the SQL string + + :raises ValueError: when sql is None (and enforce is not None) + + """ + + + if sql is None: + if enforce is not None: + raise ValueError ('sql string cannot be none') + else: + return {} # print sql - result = {} self.cursor.execute(sql) rows = self.cursor.rowcount columns = self.cursor.description - if columns is None: return result + if columns is None: + # Description failed + if enforce is not None: + return None + else: + return {} + + result = {} for column in columns: result[column[0]] = [] - if rows > 1: + if rows > 1 or ((rows == 1) and (enforce == "list")): for value in self.cursor.fetchall(): for (index,column) in enumerate(value): result[columns[index][0]].append(column)