From 635deb7192b2b3c5303766a4a550d3857aa5d2f2 Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Sat, 28 Jan 2023 18:28:49 -0500 Subject: [PATCH] Significant progress updating WHERE clause builder to be more versatile --- py_dbcn/connectors/core/clauses.py | 213 +++++++--- tests/connectors/core/test_clauses.py | 564 +++++++++++++++++++++++++- 2 files changed, 714 insertions(+), 63 deletions(-) diff --git a/py_dbcn/connectors/core/clauses.py b/py_dbcn/connectors/core/clauses.py index d04bb4c..b550cd7 100644 --- a/py_dbcn/connectors/core/clauses.py +++ b/py_dbcn/connectors/core/clauses.py @@ -390,8 +390,14 @@ class WhereClauseBuilder(BaseClauseBuilder): def __str__(self): if len(self.array) > 0: # Non-empty clause. Format for str output. - to_str = ' AND '.join('({})' for x in range(len(self.array))) - to_str = to_str.format(*self.array) + to_str = '' + temp_array = self.array + for value in self._clause_connectors: + if value == []: + to_str += '({0})'.format(temp_array.pop(0)) + else: + to_str += ' {0} '.format(value) + to_str = '\n{0}{1}'.format(self._print_prefix, to_str) return to_str else: @@ -400,60 +406,58 @@ class WhereClauseBuilder(BaseClauseBuilder): def _to_array(self, value): """Converts clause to array format for initial parsing.""" + self._clause_connectors = [] + if self._clause_prefix is None: raise NotImplementedError('Query type {0} missing clause_prefix value.'.format(self.__class__)) if self._quote_format is None: raise NotImplementedError('Query type {0} missing quote_format value.'.format(self.__class__)) - if isinstance(value, list): - # Already list format. - clause = value - elif isinstance(value, tuple): - # Close to list format. Simply convert. - clause = list(value) - else: - # Attempt to parse as str for all other formats. - if value is None: - # None type defaults to empty. - clause = [] - else: - clause = str(value).strip() + # First parse initial expected types. + if isinstance(value, list) or isinstance(value, tuple): + # In list or tuple format. For consistent tokenization, convert to str. + new_value = '' + for item in value: - # Trim prefix, if present. - if len(self._clause_prefix) > 0: - # Check if starts with prefix, brackets, and space. - if ( - clause.upper().startswith('{0} ('.format(self._clause_prefix)) and clause.endswith(')') - or clause.upper().startswith('{0} ['.format(self._clause_prefix)) and clause.endswith(']') - ): - clause = clause[(len(self._clause_prefix) + 2):-1] + # # By default, combine all individual values with AND operators. + # # Skip this for the first index, as there's nothing to combine yet. + if len(new_value) > 0: + new_value += ' AND ' - # Check if starts with prefix, brackets, and no space. - elif ( - clause.upper().startswith('{0}('.format(self._clause_prefix)) and clause.endswith(')') - or clause.upper().startswith('{0}['.format(self._clause_prefix)) and clause.endswith(']') - ): - clause = clause[(len(self._clause_prefix) + 1):-1] + # Add current item to str. + new_value += item - # Check if starts with prefix and no brackets. - elif clause.upper().startswith('{0} '.format(self._clause_prefix)): - clause = clause[(len(self._clause_prefix) + 1):] + # Save formatted string. + value = new_value - # Split into subsections, based on AND + OR delimiters. - full_split = [] - # First separate by AND delimiters. - and_split = clause.split(' AND ') - for and_clause in and_split: - # For each inner section, also separate by OR delimiters. - or_split = and_clause.split(' OR ') - for or_clause in or_split: - # For each of these, strip spaces and add if non-empty. - or_clause = or_clause.strip() - if len(or_clause) > 0: - full_split.append(or_clause) - - # Use final result. - clause = full_split + # Parse + if value is None or value.strip() == '': + # None type and empty clauses default to empty. + clause = [] + else: + clause = str(value).strip() + + # Trim prefix, if present. + if len(self._clause_prefix) > 0: + # Check if starts with prefix, brackets, and space. + if ( + clause.upper().startswith('{0} ('.format(self._clause_prefix)) and clause.endswith(')') + or clause.upper().startswith('{0} ['.format(self._clause_prefix)) and clause.endswith(']') + ): + clause = clause[(len(self._clause_prefix) + 2):-1] + + # Check if starts with prefix, brackets, and no space. + elif ( + clause.upper().startswith('{0}('.format(self._clause_prefix)) and clause.endswith(')') + or clause.upper().startswith('{0}['.format(self._clause_prefix)) and clause.endswith(']') + ): + clause = clause[(len(self._clause_prefix) + 1):-1] + + # Check if starts with prefix and no brackets. + elif clause.upper().startswith('{0} '.format(self._clause_prefix)): + clause = clause[(len(self._clause_prefix) + 1):] + + clause = self.tokenize_value(clause) # Validate each item in clause, now that it's an array. if len(clause) > 0: @@ -480,6 +484,121 @@ class WhereClauseBuilder(BaseClauseBuilder): else: # Save empty clause. self._clause_array = [] + self._clause_connectors = [] + + def tokenize_value(self, value): + """""" + print('\n\n\n\n') + print('clause:') + print('{0}'.format(value)) + print('\n') + tokens = generate_tokens(StringIO(value).readline) + print('\nas tokens:') + + section_str = '' + sub_section_str = '' + token_set = [] + for token in tokens: + print(' {0}'.format(token)) + + # Process if handling a subsection. + if sub_section_str != '': + sub_section_str += token.string + + # Check if end of subsection. + if token == ')': + print(' Finishing subsection.') + # End of subsection. Recursively call to process. + # sub_clause = self._tokenize_value(sub_section_str) + + # Clear out subsection holder. + sub_section_str = '' + + # If inner parens exist, then we need to recursively call to process sub-section. + # First we start building our sub-string to process. + if token == '(': + print(' Handling subsection.') + sub_section_str += token.string + + # Not processing sub-section. + # Determine if token is AND or OR. + elif token.type == 1 and token.string.upper() in ['AND', 'OR']: + # Token is AND or OR combiner. Handle appropriately. + print(' Handling combiner.') + + # Trim trailing final paren, if present. + if len(section_str) > 1 and section_str[-1] in [')', ']']: + section_str = section_str[:-1] + + # Save our currently assembled section of tokens. + token_set.append(section_str.strip()) + + # Append our found combiner token. + self._clause_connectors.append([]) + self._clause_connectors.append(token.string.upper()) + + # Clear out saved section, for further processing. + section_str = '' + + # For all other token types, assume is part of current section. Append to existing section. + else: + + + # Certain types need string spacing. + if token.type in [1, 2, 3]: + # Standard string types. + print(' Handling string token.') + if len(section_str) > 0 and section_str[-1] not in [' ', '`', '"', "'", '(', '[']: + section_str += ' ' + section_str += '{0}'.format(token.string) + + elif token.type in [54]: + # Operator types, such as equals sign. + print(' Handling operator token.') + + # If actually equals sign, add spaces. + if token.string == '=': + if len(section_str) > 0 and section_str[-1] != ' ': + section_str += ' ' + + # Skip parens if first value in section. + if not (token.string in ['(', '['] and section_str == ''): + section_str += '{0}'.format(token.string) + + else: + # All other types. Append as-is. + print(' Handling generic token.') + section_str += token.string + + # Done with loops. Do final post-processing. + # Trim trailing final paren, if present. + if len(section_str) > 1 and section_str[-1] in [')', ']']: + section_str = section_str[:-1] + + # Save our last-handled section, if any. + if section_str.strip() != '': + self._clause_connectors.append([]) + token_set.append(section_str) + + print('\n') + print('final self._clause_connectors:') + print('{0}'.format(self._clause_connectors)) + print('') + print('final token_set:') + print('{0}'.format(token_set)) + print('\n\n\n\n') + + return token_set + + def _tokenize_value(self, value): + """Recursive inner call for "tokenize_value" function.""" + tokens = generate_tokens(StringIO(value).readline) + for token in tokens: + + # If inner parens exist, then we need to recursively call to process sub-section. + if token == '(': + pass + class ColumnsClauseBuilder(BaseClauseBuilder): diff --git a/tests/connectors/core/test_clauses.py b/tests/connectors/core/test_clauses.py index 4f1eced..b7ef15c 100644 --- a/tests/connectors/core/test_clauses.py +++ b/tests/connectors/core/test_clauses.py @@ -138,60 +138,592 @@ class CoreClauseTestMixin: with self.subTest('Basic WHERE clause - As str'): # With no quotes. clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, """id = 'test'""") + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) # With single quotes. clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, """'id' = 'test'""") + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) # With double quotes. clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, """"id" = 'test'""") + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) # With backtick quotes. clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, """`id` = 'test'""") + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) - clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, """id = 'test' AND code = 1234 AND name = 'Test User'""") - self.assertEqual([""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], clause_object.array) - self.assertText("""WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", str(clause_object)) + with self.subTest('WHERE clause - As str, using ANDs only'): + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """col_1 = 1 AND col_2 = 2 AND col_3 = 3 AND col_4 = 4""", + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """(col_1 = 1) AND (col_2 = 2) AND (col_3 = 3) AND (col_4 = 4)""", + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """[col_1 = 1] AND [col_2 = 2] AND [col_3 = 3] AND [col_4 = 4]""", + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """id = 'test' AND `code` = 1234 AND "name" = 'Test User'""", + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """(id = 'test') AND (`code` = 1234) AND ("name" = 'Test User')""", + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """[id = 'test'] AND [`code` = 1234] AND ["name" = 'Test User']""", + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + with self.subTest('Basic WHERE clause - As str, using ORs only'): + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """col_1 = 1 OR col_2 = 2 OR col_3 = 3 OR col_4 = 4""", + ) + self.assertEqual([[], 'OR', [], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) OR ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """(col_1 = 1) OR (col_2 = 2) OR (col_3 = 3) OR (col_4 = 4)""", + ) + self.assertEqual([[], 'OR', [], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) OR ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """[col_1 = 1] OR [col_2 = 2] OR [col_3 = 3] OR [col_4 = 4]""", + ) + self.assertEqual([[], 'OR', [], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) OR ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """id = 'test' OR `code` = 1234 OR "name" = 'Test User'""", + ) + self.assertEqual([[], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) OR ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """(id = 'test') OR (`code` = 1234) OR ("name" = 'Test User')""", + ) + self.assertEqual([[], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) OR ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + """[id = 'test'] OR [`code` = 1234] OR ["name" = 'Test User']""", + ) + self.assertEqual([[], 'OR', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) OR ("name" = 'Test User')""", + str(clause_object), + ) with self.subTest('Basic WHERE clause - As list'): clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, ["""id = 'test'"""]) + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) + with self.subTest('Where clause - As list, using ANDs only'): + # Note: Default combination of array assumes AND format. + + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""col_1 = 1 AND col_2 = 2""", """col_3 = 3 AND col_4 = 4"""], + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""(col_1 = 1) AND (col_2 = 2)""", """(col_3 = 3) AND (col_4 = 4)"""], + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""[col_1 = 1] AND [col_2 = 2]""", """[col_3 = 3] AND [col_4 = 4]"""], + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""id = 'test'""", """`code` = 1234""", """"name" = 'Test User'"""], + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""(id = 'test')""", """(`code` = 1234)""", """("name" = 'Test User')"""], + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""[id = 'test']""", """[`code` = 1234]""", """["name" = 'Test User']"""], + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + with self.subTest('Where clause - As list, using ORs only'): + # Note: Default combination of array assumes AND format. + + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""col_1 = 1 OR col_2 = 2""", """col_3 = 3 OR col_4 = 4"""], + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""(col_1 = 1) OR (col_2 = 2)""", """(col_3 = 3) OR (col_4 = 4)"""], + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""[col_1 = 1] OR [col_2 = 2]""", """[col_3 = 3] OR [col_4 = 4]"""], + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""id = 'test' OR `code` = 1234""", """"name" = 'Test User'"""], + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ["""(id = 'test') OR (`code` = 1234)""", """("name" = 'Test User')"""], + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. clause_object = self.connector.validate.clauses.WhereClauseBuilder( validation_class, - ["""id = 'test'""", """code = 1234""", """name = 'Test User'"""], + ["""[id = 'test'] OR [`code` = 1234]""", """["name" = 'Test User']"""], + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), ) - self.assertEqual([""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], clause_object.array) - self.assertText("""WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", str(clause_object)) with self.subTest('Basic WHERE clause - As tuple'): clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, ("""id = 'test'""",)) + self.assertEqual([[]], clause_object._clause_connectors) self.assertEqual([""""id" = 'test'"""], clause_object.array) self.assertText("""WHERE ("id" = 'test')""", str(clause_object)) + with self.subTest('Where clause - As tuple, using ANDs only'): + # Note: Default combination of array assumes AND format. + + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""col_1 = 1 AND col_2 = 2""", """col_3 = 3 AND col_4 = 4"""), + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. clause_object = self.connector.validate.clauses.WhereClauseBuilder( validation_class, - ("""id = 'test'""", """code = 1234""", """name = 'Test User'"""), + ("""(col_1 = 1) AND (col_2 = 2)""", """(col_3 = 3) AND (col_4 = 4)"""), + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), ) - self.assertEqual([""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], clause_object.array) - self.assertText("""WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", str(clause_object)) - with self.subTest('WHERE containing various quote types'): - clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, ("""name = '2" nail'""", """description = '2 inch nail'""""")) - self.assertEqual([""""name" = '2" nail'""", """"description" = '2 inch nail'"""], clause_object.array) - self.assertText("""WHERE ("name" = '2" nail') AND ("description" = '2 inch nail')""", str(clause_object)) + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""[col_1 = 1] AND [col_2 = 2]""", """[col_3 = 3] AND [col_4 = 4]"""), + ) + self.assertEqual([[], 'AND', [], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) AND ("col_2" = 2) AND ("col_3" = 3) AND ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""id = 'test'""", """`code` = 1234""", """"name" = 'Test User'"""), + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""(id = 'test')""", """(`code` = 1234)""", """("name" = 'Test User')"""), + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""[id = 'test']""", """[`code` = 1234]""", """["name" = 'Test User']"""), + ) + self.assertEqual([[], 'AND', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') AND ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + with self.subTest('Where clause - As list, using ORs only'): + # Note: Default combination of array assumes AND format. + + # Without paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""col_1 = 1 OR col_2 = 2""", """col_3 = 3 OR col_4 = 4"""), + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""(col_1 = 1) OR (col_2 = 2)""", """(col_3 = 3) OR (col_4 = 4)"""), + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # With bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""[col_1 = 1] OR [col_2 = 2]""", """[col_3 = 3] OR [col_4 = 4]"""), + ) + self.assertEqual([[], 'OR', [], 'AND', [], 'OR', []], clause_object._clause_connectors) + self.assertEqual( + [""""col_1" = 1""", """"col_2" = 2""", """"col_3" = 3""", """"col_4" = 4"""], + clause_object.array, + ) + self.assertText( + """WHERE ("col_1" = 1) OR ("col_2" = 2) AND ("col_3" = 3) OR ("col_4" = 4)""", + str(clause_object), + ) + + # Testing various formats, no separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""id = 'test' OR `code` = 1234""", """"name" = 'Test User'"""), + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with paren separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""(id = 'test') OR (`code` = 1234)""", """("name" = 'Test User')"""), + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) + + # Testing various formats, with bracket separators. + clause_object = self.connector.validate.clauses.WhereClauseBuilder( + validation_class, + ("""[id = 'test'] OR [`code` = 1234]""", """["name" = 'Test User']"""), + ) + self.assertEqual([[], 'OR', [], 'AND', []], clause_object._clause_connectors) + self.assertEqual( + [""""id" = 'test'""", """"code" = 1234""", """"name" = 'Test User'"""], + clause_object.array) + self.assertText( + """WHERE ("id" = 'test') OR ("code" = 1234) AND ("name" = 'Test User')""", + str(clause_object), + ) - clause_object = self.connector.validate.clauses.WhereClauseBuilder(validation_class, ("""name = '1\' ruler'""", """description = '1 foot ruler'""""")) - self.assertEqual([""""name" = '1\' ruler'""", """"description" = '1 foot ruler'"""], clause_object.array) - self.assertText("""WHERE ("name" = '1\' ruler') AND ("description" = '1 foot ruler')""", str(clause_object)) + # with self.subTest('WHERE containing various quote types'): + # clause_object = self.connector.validate.clauses.WhereClauseBuilder( + # validation_class, + # ("""name = '2" nail'""", """description = '2 inch nail'"""""), + # ) + # self.assertEqual([[], 'AND', []], clause_object._clause_connectors) + # self.assertEqual([""""name" = '2" nail'""", """"description" = '2 inch nail'"""], clause_object.array) + # self.assertText("""WHERE ("name" = '2" nail') AND ("description" = '2 inch nail')""", str(clause_object)) + # + # clause_object = self.connector.validate.clauses.WhereClauseBuilder( + # validation_class, + # ("""name = '1\' ruler'""", """description = '1 foot ruler'"""""), + # ) + # self.assertEqual([[], 'AND', []], clause_object._clause_connectors) + # self.assertEqual([""""name" = '1\' ruler'""", """"description" = '1 foot ruler'"""], clause_object.array) + # self.assertText("""WHERE ("name" = '1\' ruler') AND ("description" = '1 foot ruler')""", str(clause_object)) def test__clause__columns(self): """Test logic for parsing a COLUMNS clause.""" -- GitLab