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