sql-testing-tools 0.2.11__tar.gz → 0.2.12__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.
- {sql_testing_tools-0.2.11/sql_testing_tools.egg-info → sql_testing_tools-0.2.12}/PKG-INFO +5 -1
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/README.md +4 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/setup.py +1 -1
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools/BaseAccess.py +39 -35
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools/Helper.py +101 -87
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools/TokenProcessing.py +55 -95
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12/sql_testing_tools.egg-info}/PKG-INFO +5 -1
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/LICENSE +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/setup.cfg +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools/__init__.py +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/SOURCES.txt +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/dependency_links.txt +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/requires.txt +0 -0
- {sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql_testing_tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Dist: sqlparse>=0.5.1
|
|
@@ -135,6 +135,10 @@ res = checkEquality()
|
|
|
135
135
|
|
|
136
136
|
### Changelog
|
|
137
137
|
|
|
138
|
+
##### V 0.2.12
|
|
139
|
+
- fixed bug when having only one colum selected
|
|
140
|
+
- fixed bug with nested conditions
|
|
141
|
+
|
|
138
142
|
##### V 0.2.11
|
|
139
143
|
- fixed irrelevant equality order if both sides aren't literals
|
|
140
144
|
|
|
@@ -123,6 +123,10 @@ res = checkEquality()
|
|
|
123
123
|
|
|
124
124
|
### Changelog
|
|
125
125
|
|
|
126
|
+
##### V 0.2.12
|
|
127
|
+
- fixed bug when having only one colum selected
|
|
128
|
+
- fixed bug with nested conditions
|
|
129
|
+
|
|
126
130
|
##### V 0.2.11
|
|
127
131
|
- fixed irrelevant equality order if both sides aren't literals
|
|
128
132
|
|
|
@@ -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(
|
|
34
|
-
|
|
34
|
+
if isinstance(database, str) and database.startswith(dbiu):
|
|
35
|
+
db_version = dbMap.get(database)
|
|
35
36
|
elif isinstance(database, int):
|
|
36
|
-
|
|
37
|
-
database =
|
|
37
|
+
db_version = database
|
|
38
|
+
database = dbiu+str(database)
|
|
38
39
|
elif isinstance(database, str):
|
|
39
|
-
|
|
40
|
+
db_version = None
|
|
40
41
|
else:
|
|
41
42
|
raise Exception("Invalid database selected")
|
|
42
43
|
|
|
43
|
-
if(
|
|
44
|
-
os.system('pip install dbiu_databases=='+str(
|
|
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(
|
|
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
|
-
|
|
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,
|
|
91
|
+
def runAndGetStringTable_fromFile(path: str, count: int = 5, max_line_length: int = 85):
|
|
88
92
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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(
|
|
115
|
+
matrix[col][row] = str(matrix[col][row]).ljust(max_length)
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
normalized_rows = []
|
|
113
118
|
spacing = " "
|
|
114
|
-
|
|
119
|
+
spacing_length = len(spacing) * (len(matrix[0]) - 1)
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
line_length = sum([len(x[0]) for x in matrix]) + spacing_length
|
|
117
122
|
|
|
118
123
|
|
|
119
124
|
|
|
120
|
-
if
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
if
|
|
128
|
-
matrix[col][row] = matrix[col][row][:
|
|
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][:
|
|
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
|
-
|
|
135
|
-
|
|
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(
|
|
138
|
-
s += "\n" +
|
|
141
|
+
s = "\n".join(normalized_rows[:count+2])
|
|
142
|
+
s += "\n" + normalized_rows[1]
|
|
139
143
|
|
|
140
|
-
if (
|
|
141
|
-
|
|
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
|
-
|
|
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(
|
|
24
|
-
sql = normalizeSQLQuery(Ba.getSQLFromFile(
|
|
25
|
-
if(
|
|
26
|
-
sol = normalizeSQLQuery(Ba.getSQLFromFile(
|
|
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
|
|
39
|
+
raise AssertionError("\n\nSQL-Datei ist leer. Aufgabe wurde noch nicht bearbeitet.")
|
|
29
40
|
|
|
30
41
|
|
|
31
|
-
def normalizeSQLQuery(query,
|
|
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
|
|
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() ==
|
|
47
|
-
formatted_query.append(
|
|
48
|
-
|
|
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,
|
|
52
|
-
Tp._from(il, alias_map,
|
|
53
|
-
elif isinstance(token, IdentifierList) and formatted_query and formatted_query[-1] ==
|
|
54
|
-
formatted_query.append(Tp._from(token, alias_map,
|
|
55
|
-
Tp._from(token, alias_map,
|
|
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() ==
|
|
64
|
-
formatted_query.append(
|
|
65
|
-
elif token.ttype is Keyword and token.value.upper() ==
|
|
66
|
-
formatted_query.append(
|
|
67
|
-
elif token.ttype is Keyword and token.value.upper() ==
|
|
68
|
-
formatted_query.append(
|
|
69
|
-
elif token.ttype is Keyword and token.value.upper() ==
|
|
70
|
-
formatted_query.append(
|
|
71
|
-
elif token.ttype is Keyword and token.value.upper() ==
|
|
72
|
-
formatted_query.append(
|
|
73
|
-
elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] ==
|
|
74
|
-
formatted_query.append(Tp._from(token, alias_map,
|
|
75
|
-
elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] ==
|
|
76
|
-
formatted_query.append(Tp._groupby(token, alias_map,
|
|
77
|
-
elif (isinstance(token, IdentifierList) or isinstance(token, Identifier)) and formatted_query and formatted_query[-1] ==
|
|
78
|
-
formatted_query.append(Tp._orderby(token, alias_map,
|
|
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(
|
|
81
|
-
formatted_query.append(Tp._where(token, alias_map,
|
|
82
|
-
elif formatted_query and formatted_query[-1] ==
|
|
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,
|
|
86
|
-
elif formatted_query and formatted_query[-1] ==
|
|
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
|
|
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
|
|
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,
|
|
108
|
-
tab =
|
|
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(
|
|
131
|
+
def buildAndSendCosetteRequest(base_dict, sql, sol):
|
|
122
132
|
|
|
123
133
|
err = ""
|
|
124
|
-
for
|
|
134
|
+
for _ in range(2):
|
|
125
135
|
try:
|
|
126
|
-
|
|
136
|
+
api_key=getCosetteKeyFromFile()
|
|
127
137
|
|
|
128
138
|
|
|
129
139
|
schema = ""
|
|
130
|
-
for tab in
|
|
131
|
-
schema += f"schema sch{tab}{getTableScheme(tab,
|
|
132
|
-
for tab in
|
|
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":
|
|
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
|
|
153
|
-
|
|
160
|
+
def check_keywords(start_word:str, end_words:list):
|
|
161
|
+
start_word = start_word.lower()
|
|
154
162
|
|
|
155
|
-
|
|
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(
|
|
161
|
-
start = sql.lower().find(
|
|
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
|
|
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(
|
|
178
|
+
start = sol.lower().find(start_word) + len(start_word)
|
|
174
179
|
end = -1
|
|
175
180
|
|
|
176
|
-
for kw in
|
|
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 '"+
|
|
192
|
+
return "Der '"+start_word+"' Teil der SQL-Abfrage ist nicht korrekt (oder nicht automatisch überprüfbar)."
|
|
188
193
|
|
|
189
194
|
|
|
190
|
-
def checkColumns(
|
|
191
|
-
|
|
192
|
-
|
|
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(
|
|
197
|
-
|
|
198
|
-
|
|
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(
|
|
203
|
-
|
|
204
|
-
|
|
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(
|
|
209
|
-
|
|
210
|
-
|
|
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(
|
|
215
|
-
|
|
216
|
-
|
|
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(
|
|
221
|
-
|
|
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,
|
|
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.
|
|
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,29 @@ def _identifier(identifier, alias_map, baseDict: dict):
|
|
|
29
29
|
#####################################
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def _select(select, alias_map,
|
|
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,
|
|
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,
|
|
45
|
+
select_tokens.append(_identifier(token, alias_map, base_dict).lower())
|
|
41
46
|
else:
|
|
42
|
-
select_tokens.append(f"{_identifier(token, alias_map,
|
|
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,
|
|
49
|
+
#select_tokens.append(f"{token.get_name().lower()}({_identifier(token.get_parameters(), alias_map, base_dict).lower()})")
|
|
45
50
|
for par in token.tokens:
|
|
46
51
|
if isinstance(par, Identifier):
|
|
47
52
|
select_tokens.append(par.get_name().lower()+"(")
|
|
48
53
|
if isinstance(par, Parenthesis):
|
|
49
|
-
select_tokens[-1] += _select(par, alias_map,
|
|
54
|
+
select_tokens[-1] += _select(par, alias_map, base_dict, True)+") as funcResult"
|
|
50
55
|
elif token.ttype is Wildcard:
|
|
51
56
|
select_tokens.append("*")
|
|
52
57
|
else:
|
|
@@ -59,7 +64,7 @@ def _select(select, alias_map, baseDict: dict, insideFunction=False):
|
|
|
59
64
|
#####################################
|
|
60
65
|
|
|
61
66
|
|
|
62
|
-
def _from(from_, alias_map,
|
|
67
|
+
def _from(from_, alias_map, base_dict: dict):
|
|
63
68
|
from_tokens = []
|
|
64
69
|
if hasattr(from_, 'tokens'):
|
|
65
70
|
for token in from_.tokens:
|
|
@@ -83,32 +88,32 @@ def _from(from_, alias_map, baseDict: dict):
|
|
|
83
88
|
#####################################
|
|
84
89
|
|
|
85
90
|
|
|
86
|
-
def _groupby(groupby_, alias_map,
|
|
91
|
+
def _groupby(groupby_, alias_map, base_dict: dict):
|
|
87
92
|
groupby_tokens = []
|
|
88
93
|
if(isinstance(groupby_, Identifier)):
|
|
89
|
-
groupby_tokens.append(_identifier(groupby_, alias_map,
|
|
94
|
+
groupby_tokens.append(_identifier(groupby_, alias_map, base_dict).lower())
|
|
90
95
|
elif isinstance(groupby_, IdentifierList):
|
|
91
96
|
for identifier in groupby_.get_identifiers():
|
|
92
|
-
groupby_tokens.append(_identifier(identifier, alias_map,
|
|
97
|
+
groupby_tokens.append(_identifier(identifier, alias_map, base_dict).lower())
|
|
93
98
|
groupby_tokens.sort()
|
|
94
99
|
return ",".join(groupby_tokens)
|
|
95
100
|
|
|
96
|
-
def _orderby(orderby_, alias_map,
|
|
101
|
+
def _orderby(orderby_, alias_map, base_dict: dict):
|
|
97
102
|
orderby_tokens = []
|
|
98
103
|
if isinstance(orderby_, Identifier):
|
|
99
104
|
if len(orderby_.tokens) > 0 and orderby_.tokens[-1].ttype==Keyword.Order:
|
|
100
105
|
for tok in orderby_.tokens:
|
|
101
106
|
if isinstance(tok, Identifier):
|
|
102
|
-
orderby_tokens.append(_identifier(tok, alias_map,
|
|
107
|
+
orderby_tokens.append(_identifier(tok, alias_map, base_dict).lower())
|
|
103
108
|
if tok.ttype==Keyword.Order:
|
|
104
109
|
orderby_tokens[-1] += (" "+tok.value)
|
|
105
110
|
else:
|
|
106
|
-
orderby_tokens.append(_identifier(orderby_, alias_map,
|
|
111
|
+
orderby_tokens.append(_identifier(orderby_, alias_map, base_dict).lower())
|
|
107
112
|
orderby_tokens[-1] += " ASC"
|
|
108
113
|
elif isinstance(orderby_, IdentifierList):
|
|
109
114
|
for tok in orderby_.tokens:
|
|
110
115
|
if isinstance(tok, Identifier):
|
|
111
|
-
orderby_tokens.append(_identifier(tok, alias_map,
|
|
116
|
+
orderby_tokens.append(_identifier(tok, alias_map, base_dict).lower())
|
|
112
117
|
#elif isinstance(orderby_, Order):
|
|
113
118
|
# pass
|
|
114
119
|
# orderby_tokens.sort()
|
|
@@ -133,7 +138,7 @@ def replace_not_with_parenthesis(where_tokens):
|
|
|
133
138
|
i += 1
|
|
134
139
|
return new_tokens
|
|
135
140
|
|
|
136
|
-
def _condition(token, alias_map,
|
|
141
|
+
def _condition(token, alias_map, base_dict: dict):
|
|
137
142
|
left, operator, right = [t for t in token.tokens if not t.is_whitespace]
|
|
138
143
|
|
|
139
144
|
flipAllowed = True
|
|
@@ -158,8 +163,8 @@ def _condition(token, alias_map, baseDict: dict):
|
|
|
158
163
|
rightLiteral = True
|
|
159
164
|
flipAllowed = False
|
|
160
165
|
|
|
161
|
-
left = _identifier(left, alias_map,
|
|
162
|
-
right = _identifier(right, alias_map,
|
|
166
|
+
left = _identifier(left, alias_map, base_dict)
|
|
167
|
+
right = _identifier(right, alias_map, base_dict)
|
|
163
168
|
|
|
164
169
|
if operator.value.strip() in ("!=", "<>"):
|
|
165
170
|
return f"(NOT {left.lower()} LIKE {right})"
|
|
@@ -195,7 +200,7 @@ def is_value(token):
|
|
|
195
200
|
#####################################
|
|
196
201
|
|
|
197
202
|
|
|
198
|
-
def _paranthesis(parenthesis, alias_map,
|
|
203
|
+
def _paranthesis(parenthesis, alias_map, base_dict: dict):
|
|
199
204
|
toks = []
|
|
200
205
|
|
|
201
206
|
bracketsRequired = True
|
|
@@ -206,10 +211,10 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
|
|
|
206
211
|
|
|
207
212
|
|
|
208
213
|
if(and_count==1 and or_count == 0):
|
|
209
|
-
val = _2element_par(parenthesis, alias_map,
|
|
214
|
+
val = _2element_par(parenthesis, alias_map, base_dict, "AND")
|
|
210
215
|
return val
|
|
211
216
|
elif(and_count==0 and or_count == 1):
|
|
212
|
-
val = _2element_par(parenthesis, alias_map,
|
|
217
|
+
val = _2element_par(parenthesis, alias_map, base_dict, "OR")
|
|
213
218
|
return val
|
|
214
219
|
|
|
215
220
|
|
|
@@ -219,9 +224,9 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
|
|
|
219
224
|
|
|
220
225
|
for token in parenthesis.tokens:
|
|
221
226
|
if isinstance(token, Comparison):
|
|
222
|
-
toks.append(_condition(token, alias_map,
|
|
227
|
+
toks.append(_condition(token, alias_map, base_dict))
|
|
223
228
|
elif isinstance(token, Parenthesis):
|
|
224
|
-
toks.append(_paranthesis(token, alias_map,
|
|
229
|
+
toks.append(_paranthesis(token, alias_map, base_dict))
|
|
225
230
|
|
|
226
231
|
elif token.ttype is Keyword: #AND,OR
|
|
227
232
|
if token.value == "AND":
|
|
@@ -239,14 +244,14 @@ def _paranthesis(parenthesis, alias_map, baseDict: dict):
|
|
|
239
244
|
return " ".join(toks)
|
|
240
245
|
|
|
241
246
|
|
|
242
|
-
def _2element_par(parenthesis, alias_map,
|
|
247
|
+
def _2element_par(parenthesis, alias_map, base_dict: dict, keyword):
|
|
243
248
|
toks = []
|
|
244
249
|
|
|
245
250
|
for token in parenthesis.tokens:
|
|
246
251
|
if isinstance(token, Comparison):
|
|
247
|
-
toks.append(_condition(token, alias_map,
|
|
252
|
+
toks.append(_condition(token, alias_map, base_dict))
|
|
248
253
|
elif isinstance(token, Parenthesis):
|
|
249
|
-
toks.append(_paranthesis(token, alias_map,
|
|
254
|
+
toks.append(_paranthesis(token, alias_map, base_dict))
|
|
250
255
|
toks = toks.sort()
|
|
251
256
|
fill = (" "+keyword+" ")
|
|
252
257
|
return fill.join(toks)
|
|
@@ -263,13 +268,8 @@ def find_index_of_keyword(tokens, keyword):
|
|
|
263
268
|
return -1 # Wenn kein AND-Token gefunden wird
|
|
264
269
|
|
|
265
270
|
|
|
266
|
-
def _where(where, alias_map,
|
|
267
|
-
|
|
268
|
-
current_condition = []
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
isParanthesis = isinstance(where, Parenthesis)
|
|
271
|
+
def _where(where, alias_map, base_dict: dict):
|
|
272
|
+
is_paranthesis = isinstance(where, Parenthesis)
|
|
273
273
|
|
|
274
274
|
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
275
|
where_tokens = replace_not_with_parenthesis(where_tokens)
|
|
@@ -282,11 +282,11 @@ def _where(where, alias_map, baseDict: dict):
|
|
|
282
282
|
|
|
283
283
|
# No AND / OR
|
|
284
284
|
if and_count + or_count == 0:
|
|
285
|
-
return _where_simpleCondition(where, alias_map,
|
|
285
|
+
return _where_simpleCondition(where, alias_map, base_dict)
|
|
286
286
|
|
|
287
287
|
# only ANDs / only ORs
|
|
288
288
|
elif and_count == 0 or or_count == 0:
|
|
289
|
-
return _where_sameStrengthKeywords(where_tokens, alias_map,
|
|
289
|
+
return _where_sameStrengthKeywords(where_tokens, alias_map, base_dict, " AND " if and_count > 0 else " OR ")
|
|
290
290
|
|
|
291
291
|
# ANDs and ORs
|
|
292
292
|
|
|
@@ -298,96 +298,56 @@ def _where(where, alias_map, baseDict: dict):
|
|
|
298
298
|
# two conditions on top level
|
|
299
299
|
if top_and_count + top_or_count == 1:
|
|
300
300
|
keyword = " AND " if top_and_count == 1 else " OR "
|
|
301
|
-
return _where_twoConditionsOnTopLevel(where, alias_map,
|
|
301
|
+
return _where_twoConditionsOnTopLevel(where, alias_map, base_dict, keyword, is_paranthesis)
|
|
302
302
|
else:
|
|
303
|
-
where_tokens = _where_addBracketsAroundAND(where, alias_map,
|
|
304
|
-
return _where(Where(where_tokens), alias_map,
|
|
303
|
+
where_tokens = _where_addBracketsAroundAND(where, alias_map, base_dict)
|
|
304
|
+
return _where(Where(where_tokens), alias_map, base_dict)
|
|
305
305
|
|
|
306
|
-
def _where_addBracketsAroundAND(where, alias_map,
|
|
306
|
+
def _where_addBracketsAroundAND(where, alias_map, base_dict: dict):
|
|
307
307
|
index = find_index_of_keyword(where.tokens, "AND")
|
|
308
308
|
par = Parenthesis(where.tokens[index-2:index+3])
|
|
309
309
|
where_tokens = where.tokens[:index-2] + [par] + where.tokens[index+3:]
|
|
310
310
|
return where_tokens
|
|
311
311
|
|
|
312
312
|
|
|
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):
|
|
313
|
+
def _where_simpleCondition(where_tokens, alias_map, base_dict: dict):
|
|
354
314
|
cond = []
|
|
355
315
|
for tok in where_tokens:
|
|
356
316
|
if tok.ttype == Keyword and tok.value.upper() == "NOT":
|
|
357
317
|
cond.append("NOT ")
|
|
358
318
|
if isinstance(tok, Comparison):
|
|
359
|
-
cond.append(_condition(tok, alias_map,
|
|
319
|
+
cond.append(_condition(tok, alias_map, base_dict))
|
|
360
320
|
elif isinstance(tok, Parenthesis):
|
|
361
321
|
for _tok in tok.tokens:
|
|
362
322
|
if isinstance(_tok, Comparison):
|
|
363
|
-
cond.append(_condition(_tok, alias_map,
|
|
323
|
+
cond.append(_condition(_tok, alias_map, base_dict))
|
|
364
324
|
elif isinstance(_tok, Parenthesis):
|
|
365
325
|
tok = _tok
|
|
366
326
|
return "".join(cond)
|
|
367
327
|
|
|
368
|
-
def _where_sameStrengthKeywords(where_tokens, alias_map,
|
|
328
|
+
def _where_sameStrengthKeywords(where_tokens, alias_map, base_dict: dict, keyword: str):
|
|
369
329
|
cond = []
|
|
370
330
|
for tok in where_tokens:
|
|
371
331
|
if isinstance(tok, Comparison):
|
|
372
|
-
cond.append(_where_simpleCondition([tok], alias_map,
|
|
332
|
+
cond.append(_where_simpleCondition([tok], alias_map, base_dict))
|
|
373
333
|
elif isinstance(tok, Parenthesis):
|
|
374
|
-
|
|
375
|
-
cond.append(("(" if
|
|
334
|
+
is_not = tok.tokens[0].ttype == Keyword and tok.tokens[0].value.upper() == "NOT"
|
|
335
|
+
cond.append(("(" if is_not else "") + _where(tok, alias_map, base_dict) + (")" if is_not else ""))
|
|
376
336
|
cond.sort()
|
|
377
337
|
return keyword.join(cond)
|
|
378
338
|
|
|
379
|
-
def _where_twoConditionsOnTopLevel(where, alias_map,
|
|
339
|
+
def _where_twoConditionsOnTopLevel(where, alias_map, base_dict: dict, keyword, is_paranthesis):
|
|
380
340
|
current_condition = []
|
|
381
341
|
for tok in where.tokens:
|
|
382
342
|
if isinstance(tok, Comparison):
|
|
383
|
-
current_condition.append(_condition(tok, alias_map,
|
|
343
|
+
current_condition.append(_condition(tok, alias_map, base_dict))
|
|
384
344
|
elif isinstance(tok, Parenthesis):
|
|
385
|
-
current_condition.append(_where(tok, alias_map,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
345
|
+
current_condition.append('(' + _where(tok, alias_map, base_dict) + ')')
|
|
346
|
+
current_condition.sort()
|
|
347
|
+
if keyword == " AND " or is_paranthesis:
|
|
348
|
+
return "(" + keyword.join(current_condition) + ")"
|
|
349
|
+
else:
|
|
350
|
+
return keyword.join(current_condition)
|
|
391
351
|
|
|
392
352
|
|
|
393
353
|
def _limit(limit):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql_testing_tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Dist: sqlparse>=0.5.1
|
|
@@ -135,6 +135,10 @@ res = checkEquality()
|
|
|
135
135
|
|
|
136
136
|
### Changelog
|
|
137
137
|
|
|
138
|
+
##### V 0.2.12
|
|
139
|
+
- fixed bug when having only one colum selected
|
|
140
|
+
- fixed bug with nested conditions
|
|
141
|
+
|
|
138
142
|
##### V 0.2.11
|
|
139
143
|
- fixed irrelevant equality order if both sides aren't literals
|
|
140
144
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/requires.txt
RENAMED
|
File without changes
|
{sql_testing_tools-0.2.11 → sql_testing_tools-0.2.12}/sql_testing_tools.egg-info/top_level.txt
RENAMED
|
File without changes
|