sql-testing-tools 0.2.11__tar.gz → 0.2.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql_testing_tools
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Description-Content-Type: text/markdown
5
5
  License-File: LICENSE
6
6
  Requires-Dist: sqlparse>=0.5.1
@@ -135,6 +135,14 @@ res = checkEquality()
135
135
 
136
136
  ### Changelog
137
137
 
138
+ ##### V 0.2.13
139
+ - Acccept `COUNT(colname)`, too (instead of just `COUNT(*)`)
140
+
141
+ ##### V 0.2.12
142
+ - fixed bug when having only one colum selected
143
+ - fixed bug with nested conditions
144
+ - fixed memory problem with huge cross products (used to lead to build failed)
145
+
138
146
  ##### V 0.2.11
139
147
  - fixed irrelevant equality order if both sides aren't literals
140
148
 
@@ -123,6 +123,14 @@ res = checkEquality()
123
123
 
124
124
  ### Changelog
125
125
 
126
+ ##### V 0.2.13
127
+ - Acccept `COUNT(colname)`, too (instead of just `COUNT(*)`)
128
+
129
+ ##### V 0.2.12
130
+ - fixed bug when having only one colum selected
131
+ - fixed bug with nested conditions
132
+ - fixed memory problem with huge cross products (used to lead to build failed)
133
+
126
134
  ##### V 0.2.11
127
135
  - fixed irrelevant equality order if both sides aren't literals
128
136
 
@@ -6,7 +6,7 @@ with open("README.md", "r") as fh:
6
6
 
7
7
  setup(
8
8
  name='sql_testing_tools',
9
- version='0.2.11',
9
+ version='0.2.13',
10
10
  packages=find_packages(),
11
11
  install_requires=[
12
12
  'sqlparse>=0.5.1',
@@ -26,22 +26,23 @@ dbMap = {
26
26
  }
27
27
 
28
28
  __db=""
29
+ dbiu = 'dbiu.'
29
30
 
30
31
  def setDBName(database):
31
32
  global __db
32
33
 
33
- if isinstance(database, str) and database.startswith("dbiu."):
34
- dbVersion = dbMap.get(database)
34
+ if isinstance(database, str) and database.startswith(dbiu):
35
+ db_version = dbMap.get(database)
35
36
  elif isinstance(database, int):
36
- dbVersion = database
37
- database = "dbiu."+str(database)
37
+ db_version = database
38
+ database = dbiu+str(database)
38
39
  elif isinstance(database, str):
39
- dbVersion = None
40
+ db_version = None
40
41
  else:
41
42
  raise Exception("Invalid database selected")
42
43
 
43
- if(dbVersion is not None):
44
- os.system('pip install dbiu_databases=='+str(dbVersion))
44
+ if(db_version is not None):
45
+ os.system('pip install dbiu_databases=='+str(db_version))
45
46
  try:
46
47
  import dbiu_databases
47
48
  except ImportError:
@@ -51,7 +52,7 @@ def setDBName(database):
51
52
 
52
53
 
53
54
  def run(sql: str):
54
- if __db.startswith("dbiu.") and not __db.endswith(".db"):
55
+ if __db.startswith(dbiu) and not __db.endswith(".db"):
55
56
  with importlib.resources.path('dbiu_databases', "base.db") as db_path:
56
57
  return __run(sql, db_path)
57
58
  else:
@@ -64,7 +65,7 @@ def __run(sql: str, db: str):
64
65
  con.commit()
65
66
  return cur
66
67
 
67
- def runFromFile(path: str):
68
+ def runFromFile(path: str, limit: int = None):
68
69
  sql = getSQLFromFile(path)
69
70
  if not sql:
70
71
  raise Exception("\nSQL-Datei ist leer. Aufgabe wurde noch nicht bearbeitet.")
@@ -79,15 +80,19 @@ def runFromFile(path: str):
79
80
  raise Exception(f"\n\nSyntax-Fehler in der SQL-Abfrage:\n{str(e)}")
80
81
 
81
82
  headers = [h[0] for h in res.description] if res.description else []
82
- rows = res.fetchall() if res else []
83
+ if limit:
84
+ rows = res.fetchmany(limit)
85
+ else:
86
+ rows = res.fetchall() if res else []
83
87
 
84
88
  return headers, rows
85
89
 
86
90
 
87
- def runAndGetStringTable_fromFile(path: str, count: int = 5, maxLineLength: int = 85):
91
+ def runAndGetStringTable_fromFile(path: str, count: int = 5, max_line_length: int = 85):
88
92
  try:
89
- headers, rows = runFromFile(path)
90
- resultCount = len(rows)
93
+ limit = 1000
94
+ headers, rows = runFromFile(path, limit)
95
+ result_count = len(rows)
91
96
  rows = rows[:count]
92
97
  s = ""
93
98
 
@@ -104,52 +109,51 @@ def runAndGetStringTable_fromFile(path: str, count: int = 5, maxLineLength: int
104
109
  matrix[col].append(rows[row][col])
105
110
 
106
111
  for col in range(len(matrix)):
107
- maxLength = max([len(str(x)) for x in matrix[col]])
112
+ max_length = max([len(str(x)) for x in matrix[col]])
108
113
 
109
114
  for row in range(0, len(matrix[col])):
110
- matrix[col][row] = str(matrix[col][row]).ljust(maxLength)
115
+ matrix[col][row] = str(matrix[col][row]).ljust(max_length)
111
116
 
112
- normalizedRows = []
117
+ normalized_rows = []
113
118
  spacing = " "
114
- spacingLength = len(spacing) * (len(matrix[0]) - 1)
119
+ spacing_length = len(spacing) * (len(matrix[0]) - 1)
115
120
 
116
- lineLength = sum([len(x[0]) for x in matrix]) + spacingLength
121
+ line_length = sum([len(x[0]) for x in matrix]) + spacing_length
117
122
 
118
123
 
119
124
 
120
- if lineLength > maxLineLength:
121
- valLength = lineLength - spacingLength
122
- maxColLength = int(maxLineLength/len(matrix))
125
+ if line_length > max_line_length:
126
+ max_col_length = int(max_line_length/len(matrix))
123
127
 
124
128
  for col in range(len(matrix)):
125
129
  for row in range(0, len(matrix[col])):
126
- cutContent = len(matrix[col][row].strip()) > maxColLength
127
- if cutContent:
128
- matrix[col][row] = matrix[col][row][:maxColLength-2] + ".."
130
+ cut_content = len(matrix[col][row].strip()) > max_col_length
131
+ if cut_content:
132
+ matrix[col][row] = matrix[col][row][:max_col_length-2] + ".."
129
133
  else:
130
- matrix[col][row] = matrix[col][row][:maxColLength]
134
+ matrix[col][row] = matrix[col][row][:max_col_length]
131
135
 
132
136
 
133
137
  for row in range(min(count+1, len(matrix[0]))):
134
- normalizedRows.append(spacing.join([str(matrix[col][row]) for col in range(len(matrix))]))
135
- normalizedRows.insert(1, len(normalizedRows[0])*"-")
138
+ normalized_rows.append(spacing.join([str(matrix[col][row]) for col in range(len(matrix))]))
139
+ normalized_rows.insert(1, len(normalized_rows[0])*"-")
136
140
 
137
- s = "\n".join(normalizedRows[:count+2])
138
- s += "\n" + normalizedRows[1]
141
+ s = "\n".join(normalized_rows[:count+2])
142
+ s += "\n" + normalized_rows[1]
139
143
 
140
- if (resultCount > count):
141
- s += "\n... " + str(resultCount - count) + " weitere Zeilen"
144
+ if (result_count > count):
145
+ if result_count == limit:
146
+ s += "\n... mehr als " + str(limit) + " Zeilen"
147
+ else:
148
+ s += "\n... " + str(result_count - count) + " weitere Zeilen"
142
149
  else:
143
150
  s += "\n... keine weiteren Zeilen"
144
-
145
- #longestLineInS = 85 # max([len(x.replace("\t", " ")) for x in s.split("\n")])
146
-
147
- #s = "-" * longestLineInS + "\n" + s + "\n" + "-" * longestLineInS
148
151
  s = "\n\nDiese Meldung sagt nichts über die Korrektheit der Abgabe aus!\nDie ersten " + str(
149
152
  count) + " Zeilen des Ergebnisses der SQL-Abfrage:\n\n" + s
150
153
 
151
154
  return s.replace("None", "NULL")
152
155
  except Exception as ex:
156
+ print(ex)
153
157
  raise ex
154
158
 
155
159
  def getSQLFromFile(path: str):
@@ -15,26 +15,37 @@ from sqlparse.tokens import Keyword, DML, Name, Wildcard
15
15
  sql = ""
16
16
  sol = ""
17
17
  bd = None
18
+ kw_sel = 'SELECT'
19
+ kw_frm = 'FROM'
20
+ kw_by = ' BY'
21
+ kw_grp = 'GROUP' + kw_by
22
+ kw_ord = 'ORDER' + kw_by
23
+ kw_whr = 'WHERE'
24
+ kw_lim = 'LIMIT'
25
+ kw_hav = 'HAVING'
26
+ semcol = ';'
18
27
 
19
- def setup(sqlPath,solPath):
28
+
29
+
30
+ def setup(sql_path,sol_path):
20
31
  global sql, sol, bd
21
32
  if(bd == None):
22
33
  bd = Ba.getTableDict()
23
- if(sqlPath and sqlPath != ""):
24
- sql = normalizeSQLQuery(Ba.getSQLFromFile(sqlPath), bd)
25
- if(solPath and solPath != ""):
26
- sol = normalizeSQLQuery(Ba.getSQLFromFile(solPath), bd)
34
+ if(sql_path and sql_path != ""):
35
+ sql = normalizeSQLQuery(Ba.getSQLFromFile(sql_path), bd)
36
+ if(sol_path and sol_path != ""):
37
+ sol = normalizeSQLQuery(Ba.getSQLFromFile(sol_path), bd)
27
38
  if(sql=='' or sol==''):
28
- raise Exception("\n\nSQL-Datei ist leer. Aufgabe wurde noch nicht bearbeitet.")
39
+ raise AssertionError("\n\nSQL-Datei ist leer. Aufgabe wurde noch nicht bearbeitet.")
29
40
 
30
41
 
31
- def normalizeSQLQuery(query, baseDict):
42
+ def normalizeSQLQuery(query, base_dict):
32
43
  try:
33
- query = query.replace("\"", "'")
44
+ query = query.replace("\"", "'").replace("\n", " ").replace("\r", " ").replace("\t", " ").replace(" ", " ").strip()
34
45
  parsed = sqlparse.parse(query)[0]
35
46
  parsed.tokens = [token for token in parsed.tokens if not token.is_whitespace]
36
47
  except Exception as e:
37
- raise Exception(f"\nSyntax-Fehler in der SQL-Abfrage.")
48
+ raise AssertionError(f"\nFehler beim Parsen der SQL-Abfrage: {str(e)}")
38
49
 
39
50
  formatted_query = []
40
51
  alias_map = {}
@@ -43,16 +54,15 @@ def normalizeSQLQuery(query, baseDict):
43
54
  for token in parsed.tokens:
44
55
  if token.is_whitespace:
45
56
  continue
46
- elif token.ttype is Keyword and token.value.upper() == 'FROM':
47
- formatted_query.append('FROM')
48
- pass
49
- elif isinstance(token, Identifier) and formatted_query and formatted_query[-1] == 'FROM':
57
+ elif token.ttype is Keyword and token.value.upper() == kw_frm:
58
+ formatted_query.append(kw_frm)
59
+ elif isinstance(token, Identifier) and formatted_query and formatted_query[-1] == kw_frm:
50
60
  il = IdentifierList([token])
51
- formatted_query.append(Tp._from(il, alias_map, baseDict))
52
- Tp._from(il, alias_map, baseDict)
53
- elif isinstance(token, IdentifierList) and formatted_query and formatted_query[-1] == 'FROM':
54
- formatted_query.append(Tp._from(token, alias_map, baseDict))
55
- Tp._from(token, alias_map, baseDict)
61
+ formatted_query.append(Tp._from(il, alias_map, base_dict))
62
+ Tp._from(il, alias_map, base_dict)
63
+ elif isinstance(token, IdentifierList) and formatted_query and formatted_query[-1] == kw_frm:
64
+ formatted_query.append(Tp._from(token, alias_map, base_dict))
65
+ Tp._from(token, alias_map, base_dict)
56
66
 
57
67
  formatted_query = []
58
68
 
@@ -60,40 +70,40 @@ def normalizeSQLQuery(query, baseDict):
60
70
  for token in parsed.tokens:
61
71
  if token.is_whitespace:
62
72
  continue
63
- elif token.ttype is DML and token.value.upper() == 'SELECT':
64
- formatted_query.append('SELECT')
65
- elif token.ttype is Keyword and token.value.upper() == 'FROM':
66
- formatted_query.append('FROM')
67
- elif token.ttype is Keyword and token.value.upper() == 'GROUP BY':
68
- formatted_query.append('GROUP BY')
69
- elif token.ttype is Keyword and token.value.upper() == 'ORDER BY':
70
- formatted_query.append('ORDER BY')
71
- elif token.ttype is Keyword and token.value.upper() == 'LIMIT':
72
- formatted_query.append('LIMIT')
73
- elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == 'FROM':
74
- formatted_query.append(Tp._from(token, alias_map, baseDict))
75
- elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == 'GROUP BY':
76
- formatted_query.append(Tp._groupby(token, alias_map, baseDict))
77
- elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == 'ORDER BY':
78
- formatted_query.append(Tp._orderby(token, alias_map, baseDict))
73
+ elif token.ttype is DML and token.value.upper() == kw_sel:
74
+ formatted_query.append(kw_sel)
75
+ elif token.ttype is Keyword and token.value.upper() == kw_frm:
76
+ formatted_query.append(kw_frm)
77
+ elif token.ttype is Keyword and token.value.upper() == kw_grp:
78
+ formatted_query.append(kw_grp)
79
+ elif token.ttype is Keyword and token.value.upper() == kw_ord:
80
+ formatted_query.append(kw_ord)
81
+ elif token.ttype is Keyword and token.value.upper() == kw_lim:
82
+ formatted_query.append(kw_lim)
83
+ elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == kw_frm:
84
+ formatted_query.append(Tp._from(token, alias_map, base_dict))
85
+ elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == kw_grp:
86
+ formatted_query.append(Tp._groupby(token, alias_map, base_dict))
87
+ elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] == kw_ord:
88
+ formatted_query.append(Tp._orderby(token, alias_map, base_dict))
79
89
  elif isinstance(token, Where):
80
- formatted_query.append('WHERE')
81
- formatted_query.append(Tp._where(token, alias_map, baseDict))
82
- elif formatted_query and formatted_query[-1] == 'SELECT' and (isinstance(token, IdentifierList) or isinstance(token, Function) or isinstance(token, Identifier)):
90
+ formatted_query.append(kw_whr)
91
+ formatted_query.append(Tp._where(token, alias_map, base_dict))
92
+ elif formatted_query and formatted_query[-1] == kw_sel and (isinstance(token, IdentifierList) or isinstance(token, Function) or isinstance(token, Identifier)):
83
93
  if isinstance(token, Function):
84
94
  token = IdentifierList([token])
85
- formatted_query.append(Tp._select(token, alias_map, baseDict))
86
- elif formatted_query and formatted_query[-1] == 'LIMIT':
95
+ formatted_query.append(Tp._select(token, alias_map, base_dict))
96
+ elif formatted_query and formatted_query[-1] == kw_lim:
87
97
  formatted_query.append(Tp._limit(token))
88
98
  else:
89
99
  formatted_query.append(str(token))
90
100
 
91
101
  return " ".join(formatted_query)
92
102
 
93
- def findTableForColumn(data_dict, target_value, relevantTables):
103
+ def find_table_for_column(data_dict, target_value, relevant_tables):
94
104
  l = []
95
105
  for key, value_list in data_dict.items():
96
- if key.lower() in relevantTables:
106
+ if key.lower() in relevant_tables:
97
107
  for sublist in value_list:
98
108
  if sublist and sublist[0].lower() == target_value.lower():
99
109
  l.append(key)
@@ -104,8 +114,8 @@ def findTableForColumn(data_dict, target_value, relevantTables):
104
114
  l.append(key)
105
115
  return l
106
116
 
107
- def getTableScheme(table_name: str, tableDict: dict):
108
- tab = tableDict[table_name]
117
+ def getTableScheme(table_name: str, table_dict: dict):
118
+ tab = table_dict[table_name]
109
119
 
110
120
  # Format the schema
111
121
  schema = "(" + ",".join([f"{col[0]}:{col[1]}" for col in tab]) + ")"
@@ -118,18 +128,18 @@ def getCosetteKeyFromFile():
118
128
  except FileNotFoundError:
119
129
  return "NOKEY"
120
130
 
121
- def buildAndSendCosetteRequest(baseDict, sql, sol):
131
+ def buildAndSendCosetteRequest(base_dict, sql, sol):
122
132
 
123
133
  err = ""
124
- for i in range(2):
134
+ for _ in range(2):
125
135
  try:
126
- apiKey=getCosetteKeyFromFile()
136
+ api_key=getCosetteKeyFromFile()
127
137
 
128
138
 
129
139
  schema = ""
130
- for tab in baseDict.keys():
131
- schema += f"schema sch{tab}{getTableScheme(tab, baseDict)};\n"
132
- for tab in baseDict.keys():
140
+ for tab in base_dict.keys():
141
+ schema += f"schema sch{tab}{getTableScheme(tab, base_dict)};\n"
142
+ for tab in base_dict.keys():
133
143
  schema += f"table {tab}(sch{tab});\n"
134
144
 
135
145
  q1 = "query q1\n`"+sql+"`;\n"
@@ -138,29 +148,25 @@ def buildAndSendCosetteRequest(baseDict, sql, sol):
138
148
  cosette = "-- random Kommentar\n" + schema + q1 + q2 + "verify q1 q2;\n"
139
149
  print(cosette)
140
150
 
141
- r = requests.post("https://demo.cosette.cs.washington.edu/solve",data={"api_key": apiKey, "query": cosette}, verify=False)
151
+ r = requests.post("https://demo.cosette.cs.washington.edu/solve",data={"api_key": api_key, "query": cosette}, verify=False)
142
152
 
143
153
  print(r.text)
144
154
  return (r.json()['result'],r.text)
145
- #return r.json()['result']
146
-
147
155
  except Exception as e:
148
156
  err = str(e)
149
157
  return ("ERR", err)
150
158
 
151
159
 
152
- def checkKeywords(startWord:str, endWords:list):
153
- #global sql,sol
160
+ def check_keywords(start_word:str, end_words:list):
161
+ start_word = start_word.lower()
154
162
 
155
- startWord = startWord.lower()
156
-
157
- if(startWord not in sol.lower() and startWord not in sql.lower()):
163
+ if(start_word not in sol.lower() and start_word not in sql.lower()):
158
164
  return ""
159
165
 
160
- if(startWord in sql.lower()):
161
- start = sql.lower().find(startWord) + len(startWord)
166
+ if(start_word in sql.lower()):
167
+ start = sql.lower().find(start_word) + len(start_word)
162
168
  end = -1
163
- for kw in endWords:
169
+ for kw in end_words:
164
170
  index = sql.lower().find(kw.lower(), start)
165
171
  if -1 < index < end or end == -1:
166
172
  end = index
@@ -168,12 +174,11 @@ def checkKeywords(startWord:str, endWords:list):
168
174
  end = len(sql)
169
175
 
170
176
  submission = str.strip(sql[start:end])
171
- #print("'"+submission+"'")
172
177
 
173
- start = sol.lower().find(startWord) + len(startWord)
178
+ start = sol.lower().find(start_word) + len(start_word)
174
179
  end = -1
175
180
 
176
- for kw in endWords:
181
+ for kw in end_words:
177
182
  index = sol.lower().find(kw.lower(), start)
178
183
  if -1 < index < end or end == -1:
179
184
  end = index
@@ -184,45 +189,54 @@ def checkKeywords(startWord:str, endWords:list):
184
189
 
185
190
  if submission == solution:
186
191
  return ""
187
- return "Der '"+startWord+"' Teil der SQL-Abfrage ist nicht korrekt (oder nicht automatisch überprüfbar)."
192
+ return "Der '"+start_word+"' Teil der SQL-Abfrage ist nicht korrekt (oder nicht automatisch überprüfbar)."
188
193
 
189
194
 
190
- def checkColumns(sqlPath="", solPath=""):
191
- #global sql,sol
192
- setup(sqlPath, solPath)
193
- return checkKeywords("SELECT ", ["FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", ";", "HAVING"])
195
+ def checkColumns(sql_path="", sol_path=""):
196
+ setup(sql_path, sol_path)
197
+ return check_keywords(kw_sel+" ", [kw_frm, kw_whr, kw_grp, kw_ord, kw_lim, semcol, kw_hav])
194
198
 
195
199
 
196
- def checkTables(sqlPath="", solPath=""):
197
- #global sql,sol
198
- setup(sqlPath, solPath)
199
- return checkKeywords("FROM ", ["SELECT", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", ";", "HAVING"])
200
+ def checkTables(sql_path="", sol_path=""):
201
+ setup(sql_path, sol_path)
202
+ return check_keywords(kw_frm+" ", [kw_sel, kw_whr, kw_grp, kw_ord, kw_lim, semcol, kw_hav])
200
203
 
201
204
 
202
- def checkCondition(sqlPath="", solPath=""):
203
- #global sql,sol
204
- setup(sqlPath, solPath)
205
- return checkKeywords("WHERE ", ["SELECT", "FROM", "GROUP BY", "ORDER BY", "LIMIT", ";", "HAVING"])
205
+ def checkCondition(sql_path="", sol_path=""):
206
+ setup(sql_path, sol_path)
207
+ return check_keywords(kw_whr+" ", [kw_sel, kw_frm, kw_grp, kw_ord, kw_lim, semcol, kw_hav])
206
208
 
207
209
 
208
- def checkOrder(sqlPath="", solPath=""):
209
- #global sql,sol
210
- setup(sqlPath, solPath)
211
- return checkKeywords("ORDER BY ", ["SELECT", "WHERE", "GROUP BY", "FROM", "LIMIT", ";", "HAVING"])
210
+ def checkOrder(sql_path="", sol_path=""):
211
+ setup(sql_path, sol_path)
212
+ return check_keywords(kw_ord+" ", [kw_sel, kw_whr, kw_grp, kw_frm, kw_lim, semcol, kw_hav])
212
213
 
213
214
 
214
- def checkGroup(sqlPath="", solPath=""):
215
- #global sql, sol
216
- setup(sqlPath, solPath)
217
- return checkKeywords("GROUP BY ", ["SELECT", "WHERE", "FROM BY", "ORDER BY", "LIMIT", ";", "HAVING"])
215
+ def checkGroup(sql_path="", sol_path=""):
216
+ setup(sql_path, sol_path)
217
+ return check_keywords(kw_grp+" ", [kw_sel, kw_whr, kw_grp, kw_ord, kw_lim, semcol, kw_hav])
218
218
 
219
219
 
220
- def checkEquality(sqlPath="", solPath=""):
221
- #global sql, sol
222
- setup(sqlPath, solPath)
220
+ def checkEquality(sql_path="", sol_path=""):
221
+ setup(sql_path, sol_path)
223
222
 
224
223
  if sql == sol:
225
224
  return ""
225
+
226
+
227
+ sql_build_fail = ""
228
+ sol_build_fail = ""
229
+ try:
230
+ Ba.runAndGetStringTable_fromFile(sql_path)
231
+ except Exception as e:
232
+ sql_build_fail = str(e)
233
+ try:
234
+ Ba.runAndGetStringTable_fromFile(sol_path)
235
+ except Exception as e:
236
+ sol_build_fail = str(e)
237
+
238
+ if(sql_build_fail != "" or sol_build_fail != ""):
239
+ return "\n\nFehler beim Ausführen der Abfrage. Vermutlich enthält die Abfrage Syntaxfehler:\n" + sql_build_fail + "\n" + sol_build_fail
226
240
 
227
241
  result = buildAndSendCosetteRequest(bd, sql, sol)
228
242
 
@@ -11,12 +11,12 @@ except ImportError:
11
11
  #####################################
12
12
  #####################################
13
13
 
14
- def _identifier(identifier, alias_map, baseDict: dict):
14
+ def _identifier(identifier, alias_map, base_dict: dict):
15
15
  if isinstance(identifier, Identifier):
16
16
  if identifier.get_real_name() and identifier.get_parent_name() and identifier.get_parent_name().lower() in alias_map.keys():
17
17
  return f"{alias_map[identifier.get_parent_name().lower()].lower()}.{identifier.get_real_name().lower()}"
18
18
  elif identifier.get_real_name():
19
- tables = He.findTableForColumn(baseDict, identifier.get_real_name(), alias_map.keys())
19
+ tables = He.find_table_for_column(base_dict, identifier.get_real_name(), alias_map.keys())
20
20
  if len(tables) == 1:
21
21
  alias_map[tables[0].lower()] = tables[0]
22
22
  return f"{tables[0].lower()}.{identifier.get_real_name().lower()}"
@@ -29,24 +29,33 @@ def _identifier(identifier, alias_map, baseDict: dict):
29
29
  #####################################
30
30
 
31
31
 
32
- def _select(select, alias_map, baseDict: dict, insideFunction=False):
32
+ def _select(select, alias_map, base_dict: dict, insideFunction=False):
33
33
  select_tokens = []
34
+ if isinstance(select, Identifier):
35
+ if insideFunction:
36
+ select_tokens.append(_identifier(select, alias_map, base_dict).lower())
37
+ else:
38
+ select_tokens.append(f"{_identifier(select, alias_map, base_dict).lower()} as {select.get_real_name().lower()}")
34
39
  for token in select.tokens:
35
40
  if isinstance(token, IdentifierList):
36
41
  for identifier in token.get_identifiers():
37
- select_tokens.append(f"{_identifier(identifier, alias_map, baseDict).lower()} as {identifier.get_real_name().lower()}")
42
+ select_tokens.append(f"{_identifier(identifier, alias_map, base_dict).lower()} as {identifier.get_real_name().lower()}")
38
43
  elif isinstance(token, Identifier):
39
44
  if insideFunction:
40
- select_tokens.append(_identifier(token, alias_map, baseDict).lower())
45
+ select_tokens.append(_identifier(token, alias_map, base_dict).lower())
41
46
  else:
42
- select_tokens.append(f"{_identifier(token, alias_map, baseDict).lower()} as {token.get_real_name().lower()}")
47
+ select_tokens.append(f"{_identifier(token, alias_map, base_dict).lower()} as {token.get_real_name().lower()}")
43
48
  elif isinstance(token, Function):
44
- #select_tokens.append(f"{token.get_name().lower()}({_identifier(token.get_parameters(), alias_map, baseDict).lower()})")
49
+ is_count = False
45
50
  for par in token.tokens:
46
51
  if isinstance(par, Identifier):
47
52
  select_tokens.append(par.get_name().lower()+"(")
53
+ is_count = par.get_name().lower() == "count"
48
54
  if isinstance(par, Parenthesis):
49
- select_tokens[-1] += _select(par, alias_map, baseDict, True)+") as funcResult"
55
+ select_ident = _select(par, alias_map, base_dict, True)
56
+ if is_count and not any(tok.value.upper() == "DISTINCT" for tok in par.tokens):
57
+ select_ident = "*"
58
+ select_tokens[-1] += select_ident +") as funcResult"
50
59
  elif token.ttype is Wildcard:
51
60
  select_tokens.append("*")
52
61
  else:
@@ -59,7 +68,7 @@ def _select(select, alias_map, baseDict: dict, insideFunction=False):
59
68
  #####################################
60
69
 
61
70
 
62
- def _from(from_, alias_map, baseDict: dict):
71
+ def _from(from_, alias_map, base_dict: dict):
63
72
  from_tokens = []
64
73
  if hasattr(from_, 'tokens'):
65
74
  for token in from_.tokens:
@@ -83,32 +92,32 @@ def _from(from_, alias_map, baseDict: dict):
83
92
  #####################################
84
93
 
85
94
 
86
- def _groupby(groupby_, alias_map, baseDict: dict):
95
+ def _groupby(groupby_, alias_map, base_dict: dict):
87
96
  groupby_tokens = []
88
97
  if(isinstance(groupby_, Identifier)):
89
- groupby_tokens.append(_identifier(groupby_, alias_map, baseDict).lower())
98
+ groupby_tokens.append(_identifier(groupby_, alias_map, base_dict).lower())
90
99
  elif isinstance(groupby_, IdentifierList):
91
100
  for identifier in groupby_.get_identifiers():
92
- groupby_tokens.append(_identifier(identifier, alias_map, baseDict).lower())
101
+ groupby_tokens.append(_identifier(identifier, alias_map, base_dict).lower())
93
102
  groupby_tokens.sort()
94
103
  return ",".join(groupby_tokens)
95
104
 
96
- def _orderby(orderby_, alias_map, baseDict: dict):
105
+ def _orderby(orderby_, alias_map, base_dict: dict):
97
106
  orderby_tokens = []
98
107
  if isinstance(orderby_, Identifier):
99
108
  if len(orderby_.tokens) > 0 and orderby_.tokens[-1].ttype==Keyword.Order:
100
109
  for tok in orderby_.tokens:
101
110
  if isinstance(tok, Identifier):
102
- orderby_tokens.append(_identifier(tok, alias_map, baseDict).lower())
111
+ orderby_tokens.append(_identifier(tok, alias_map, base_dict).lower())
103
112
  if tok.ttype==Keyword.Order:
104
113
  orderby_tokens[-1] += (" "+tok.value)
105
114
  else:
106
- orderby_tokens.append(_identifier(orderby_, alias_map, baseDict).lower())
115
+ orderby_tokens.append(_identifier(orderby_, alias_map, base_dict).lower())
107
116
  orderby_tokens[-1] += " ASC"
108
117
  elif isinstance(orderby_, IdentifierList):
109
118
  for tok in orderby_.tokens:
110
119
  if isinstance(tok, Identifier):
111
- orderby_tokens.append(_identifier(tok, alias_map, baseDict).lower())
120
+ orderby_tokens.append(_identifier(tok, alias_map, base_dict).lower())
112
121
  #elif isinstance(orderby_, Order):
113
122
  # pass
114
123
  # orderby_tokens.sort()
@@ -133,7 +142,7 @@ def replace_not_with_parenthesis(where_tokens):
133
142
  i += 1
134
143
  return new_tokens
135
144
 
136
- def _condition(token, alias_map, baseDict: dict):
145
+ def _condition(token, alias_map, base_dict: dict):
137
146
  left, operator, right = [t for t in token.tokens if not t.is_whitespace]
138
147
 
139
148
  flipAllowed = True
@@ -158,8 +167,8 @@ def _condition(token, alias_map, baseDict: dict):
158
167
  rightLiteral = True
159
168
  flipAllowed = False
160
169
 
161
- left = _identifier(left, alias_map, baseDict)
162
- right = _identifier(right, alias_map, baseDict)
170
+ left = _identifier(left, alias_map, base_dict)
171
+ right = _identifier(right, alias_map, base_dict)
163
172
 
164
173
  if operator.value.strip() in ("!=", "<>"):
165
174
  return f"(NOT {left.lower()} LIKE {right})"
@@ -195,7 +204,7 @@ def is_value(token):
195
204
  #####################################
196
205
 
197
206
 
198
- def _paranthesis(parenthesis, alias_map, baseDict: dict):
207
+ def _paranthesis(parenthesis, alias_map, base_dict: dict):
199
208
  toks = []
200
209
 
201
210
  bracketsRequired = True
@@ -206,10 +215,10 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
206
215
 
207
216
 
208
217
  if(and_count==1 and or_count == 0):
209
- val = _2element_par(parenthesis, alias_map, baseDict, "AND")
218
+ val = _2element_par(parenthesis, alias_map, base_dict, "AND")
210
219
  return val
211
220
  elif(and_count==0 and or_count == 1):
212
- val = _2element_par(parenthesis, alias_map, baseDict, "OR")
221
+ val = _2element_par(parenthesis, alias_map, base_dict, "OR")
213
222
  return val
214
223
 
215
224
 
@@ -219,9 +228,9 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
219
228
 
220
229
  for token in parenthesis.tokens:
221
230
  if isinstance(token, Comparison):
222
- toks.append(_condition(token, alias_map, baseDict))
231
+ toks.append(_condition(token, alias_map, base_dict))
223
232
  elif isinstance(token, Parenthesis):
224
- toks.append(_paranthesis(token, alias_map, baseDict))
233
+ toks.append(_paranthesis(token, alias_map, base_dict))
225
234
 
226
235
  elif token.ttype is Keyword: #AND,OR
227
236
  if token.value == "AND":
@@ -239,14 +248,14 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
239
248
  return " ".join(toks)
240
249
 
241
250
 
242
- def _2element_par(parenthesis, alias_map, baseDict: dict, keyword):
251
+ def _2element_par(parenthesis, alias_map, base_dict: dict, keyword):
243
252
  toks = []
244
253
 
245
254
  for token in parenthesis.tokens:
246
255
  if isinstance(token, Comparison):
247
- toks.append(_condition(token, alias_map, baseDict))
256
+ toks.append(_condition(token, alias_map, base_dict))
248
257
  elif isinstance(token, Parenthesis):
249
- toks.append(_paranthesis(token, alias_map, baseDict))
258
+ toks.append(_paranthesis(token, alias_map, base_dict))
250
259
  toks = toks.sort()
251
260
  fill = (" "+keyword+" ")
252
261
  return fill.join(toks)
@@ -263,13 +272,8 @@ def find_index_of_keyword(tokens, keyword):
263
272
  return -1 # Wenn kein AND-Token gefunden wird
264
273
 
265
274
 
266
- def _where(where, alias_map, baseDict: dict):
267
- conditions = []
268
- current_condition = []
269
-
270
-
271
-
272
- isParanthesis = isinstance(where, Parenthesis)
275
+ def _where(where, alias_map, base_dict: dict):
276
+ is_paranthesis = isinstance(where, Parenthesis)
273
277
 
274
278
  where_tokens = [token for token in where.tokens if (token.ttype is not Whitespace and token.ttype is not Newline) and token.value != "WHERE"]
275
279
  where_tokens = replace_not_with_parenthesis(where_tokens)
@@ -282,11 +286,11 @@ def _where(where, alias_map, baseDict: dict):
282
286
 
283
287
  # No AND / OR
284
288
  if and_count + or_count == 0:
285
- return _where_simpleCondition(where, alias_map, baseDict)
289
+ return _where_simpleCondition(where, alias_map, base_dict)
286
290
 
287
291
  # only ANDs / only ORs
288
292
  elif and_count == 0 or or_count == 0:
289
- return _where_sameStrengthKeywords(where_tokens, alias_map, baseDict, " AND " if and_count > 0 else " OR ")
293
+ return _where_sameStrengthKeywords(where_tokens, alias_map, base_dict, " AND " if and_count > 0 else " OR ")
290
294
 
291
295
  # ANDs and ORs
292
296
 
@@ -298,96 +302,56 @@ def _where(where, alias_map, baseDict: dict):
298
302
  # two conditions on top level
299
303
  if top_and_count + top_or_count == 1:
300
304
  keyword = " AND " if top_and_count == 1 else " OR "
301
- return _where_twoConditionsOnTopLevel(where, alias_map, baseDict, keyword, isParanthesis)
305
+ return _where_twoConditionsOnTopLevel(where, alias_map, base_dict, keyword, is_paranthesis)
302
306
  else:
303
- where_tokens = _where_addBracketsAroundAND(where, alias_map, baseDict)
304
- return _where(Where(where_tokens), alias_map, baseDict)
307
+ where_tokens = _where_addBracketsAroundAND(where, alias_map, base_dict)
308
+ return _where(Where(where_tokens), alias_map, base_dict)
305
309
 
306
- def _where_addBracketsAroundAND(where, alias_map, baseDict: dict):
310
+ def _where_addBracketsAroundAND(where, alias_map, base_dict: dict):
307
311
  index = find_index_of_keyword(where.tokens, "AND")
308
312
  par = Parenthesis(where.tokens[index-2:index+3])
309
313
  where_tokens = where.tokens[:index-2] + [par] + where.tokens[index+3:]
310
314
  return where_tokens
311
315
 
312
316
 
313
-
314
- # for token in where.tokens:
315
- # if token.is_whitespace or (token.ttype is Keyword and token.value.upper() == "WHERE"):
316
- # continue
317
-
318
- # if token.ttype is Keyword and token.value.upper() in ('AND', 'OR'):
319
- # if current_condition:
320
- # conditions.append(''.join(str(t) for t in current_condition).strip())
321
- # current_condition = []
322
- # conditions.append(token.value.upper())
323
- # else:
324
- # current_condition.append(token)
325
-
326
- # if current_condition:
327
- # conditions.append(''.join(str(t) for t in current_condition).strip())
328
-
329
- # sorted_conditions = []
330
- # current_group = []
331
- # last_connector = ""
332
-
333
- # if "OR" not in conditions:
334
- # for condition in conditions:
335
- # if condition == 'AND':
336
- # pass
337
- # else:
338
- # current_group.append(condition)
339
-
340
- # if current_group:
341
- # sorted_conditions.extend(sorted(current_group))
342
-
343
- # return " AND ".join(sorted_conditions)
344
- # else:
345
- # return " ".join(conditions)
346
-
347
- # where_index = query.upper().find('WHERE')
348
-
349
- # normalized_query = query[:where_index + 5] + ' ' + ' '.join(sorted_conditions)
350
-
351
-
352
-
353
- def _where_simpleCondition(where_tokens, alias_map, baseDict: dict):
317
+ def _where_simpleCondition(where_tokens, alias_map, base_dict: dict):
354
318
  cond = []
355
319
  for tok in where_tokens:
356
320
  if tok.ttype == Keyword and tok.value.upper() == "NOT":
357
321
  cond.append("NOT ")
358
322
  if isinstance(tok, Comparison):
359
- cond.append(_condition(tok, alias_map, baseDict))
323
+ cond.append(_condition(tok, alias_map, base_dict))
360
324
  elif isinstance(tok, Parenthesis):
361
325
  for _tok in tok.tokens:
362
326
  if isinstance(_tok, Comparison):
363
- cond.append(_condition(_tok, alias_map, baseDict))
327
+ cond.append(_condition(_tok, alias_map, base_dict))
364
328
  elif isinstance(_tok, Parenthesis):
365
329
  tok = _tok
366
330
  return "".join(cond)
367
331
 
368
- def _where_sameStrengthKeywords(where_tokens, alias_map, baseDict: dict, keyword: str):
332
+ def _where_sameStrengthKeywords(where_tokens, alias_map, base_dict: dict, keyword: str):
369
333
  cond = []
370
334
  for tok in where_tokens:
371
335
  if isinstance(tok, Comparison):
372
- cond.append(_where_simpleCondition([tok], alias_map, baseDict))
336
+ cond.append(_where_simpleCondition([tok], alias_map, base_dict))
373
337
  elif isinstance(tok, Parenthesis):
374
- isNot = tok.tokens[0].ttype == Keyword and tok.tokens[0].value.upper() == "NOT"
375
- cond.append(("(" if isNot else "") + _where(tok, alias_map, baseDict) + (")" if isNot else ""))
338
+ is_not = tok.tokens[0].ttype == Keyword and tok.tokens[0].value.upper() == "NOT"
339
+ cond.append(("(" if is_not else "") + _where(tok, alias_map, base_dict) + (")" if is_not else ""))
376
340
  cond.sort()
377
341
  return keyword.join(cond)
378
342
 
379
- def _where_twoConditionsOnTopLevel(where, alias_map, baseDict: dict, keyword, isParanthesis):
343
+ def _where_twoConditionsOnTopLevel(where, alias_map, base_dict: dict, keyword, is_paranthesis):
380
344
  current_condition = []
381
345
  for tok in where.tokens:
382
346
  if isinstance(tok, Comparison):
383
- current_condition.append(_condition(tok, alias_map, baseDict))
347
+ current_condition.append(_condition(tok, alias_map, base_dict))
384
348
  elif isinstance(tok, Parenthesis):
385
- current_condition.append(_where(tok, alias_map, baseDict))
386
- current_condition.sort()
387
- if keyword == " AND " or isParanthesis:
388
- return "(" + keyword.join(current_condition) + ")"
389
- else:
390
- return keyword.join(current_condition)
349
+ current_condition.append('(' + _where(tok, alias_map, base_dict) + ')')
350
+ current_condition.sort()
351
+ if keyword == " AND " or is_paranthesis:
352
+ return "(" + keyword.join(current_condition) + ")"
353
+ else:
354
+ return keyword.join(current_condition)
391
355
 
392
356
 
393
357
  def _limit(limit):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql_testing_tools
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Description-Content-Type: text/markdown
5
5
  License-File: LICENSE
6
6
  Requires-Dist: sqlparse>=0.5.1
@@ -135,6 +135,14 @@ res = checkEquality()
135
135
 
136
136
  ### Changelog
137
137
 
138
+ ##### V 0.2.13
139
+ - Acccept `COUNT(colname)`, too (instead of just `COUNT(*)`)
140
+
141
+ ##### V 0.2.12
142
+ - fixed bug when having only one colum selected
143
+ - fixed bug with nested conditions
144
+ - fixed memory problem with huge cross products (used to lead to build failed)
145
+
138
146
  ##### V 0.2.11
139
147
  - fixed irrelevant equality order if both sides aren't literals
140
148