Repository: kayak/pypika
Pull request #631 (2021-08-29 14:32:55)

fix: add _use/_force_indexes into __copy__ with shallow copy
By #334, #470 , pypika got `force_index`、`use_index` feature, but forgot to add "_force_indexes", "_use_indexes" into `QueryBuilder.__copy__()` with shallow copy, thus they both become mutable. For examples

```python
from pypika import Query

query = Query.from_('customers').select('id', 'fname')
q1 = query.use_index('idx1')
q2 = query
q3 = query.use_index('idx2')

if __name__ == "__main__":
    print(q1.get_sql())
    # SELECT "id","fname" FROM "customers" USE INDEX ("idx1","idx2")
    print(q2.get_sql())
    # SELECT "id","fname" FROM "customers" USE INDEX ("idx1","idx2")
    print(q3.get_sql())
    # SELECT "id","fname" FROM "customers" USE INDEX ("idx1","idx2")
```


Patch:
diff --git a/pypika/queries.py b/pypika/queries.py
index 974f6ae..2adc1b1 100644
--- a/pypika/queries.py
+++ b/pypika/queries.py
@@ -752,6 +752,8 @@ class QueryBuilder(Selectable, Term):
         newone._joins = copy(self._joins)
         newone._unions = copy(self._unions)
         newone._updates = copy(self._updates)
+        newone._force_indexes = copy(self._force_indexes)
+        newone._use_indexes = copy(self._use_indexes)
         return newone
 
     @builder


Test patch:
diff --git a/pypika/tests/test_immutability.py b/pypika/tests/test_immutability.py
index 0904a81..ee539bf 100644
--- a/pypika/tests/test_immutability.py
+++ b/pypika/tests/test_immutability.py
@@ -1,13 +1,14 @@
 import unittest
 
 from pypika import (
+    AliasedQuery,
     Query,
     Tables,
 )
 
 
 class ImmutabilityTests(unittest.TestCase):
-    table_a, table_b = Tables("a", "b")
+    table_a, table_b, table_c = Tables("a", "b", "c")
 
     def test_select_returns_new_query_instance(self):
         query_a = Query.from_(self.table_a).select(self.table_a.foo)
@@ -16,6 +17,72 @@ class ImmutabilityTests(unittest.TestCase):
         self.assertIsNot(query_a, query_b)
         self.assertNotEqual(str(query_a), str(query_b))
 
+    def test_groupby_returns_new_query_instance(self):
+        query_a = Query.from_(self.table_a).select(self.table_a.foo).groupby(self.table_a.foo)
+        query_b = query_a.groupby(self.table_a.bar)
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_orderby_return_new_query_instance(self):
+        query_a = Query.from_(self.table_a).select(self.table_a.foo).orderby(self.table_a.foo)
+        query_b = query_a.orderby(self.table_a.bar)
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_join_return_new_query_instance(self):
+        base = Query.from_(self.table_a).select(self.table_a.foo)
+        query_a = base.join(self.table_b).on(self.table_a.foo == self.table_b.bar).select(self.table_b.bar)
+        query_b = query_a.join(self.table_c).on(self.table_a.foo == self.table_c.baz).select(self.table_c.baz)
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_use_index_return_new_query_instance(self):
+        query_a = Query.from_(self.table_a).select(self.table_a.foo).use_index('idx1')
+        query_b = query_a.use_index('idx2')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_force_index_return_new_query_instance(self):
+        query_a = Query.from_(self.table_a).select(self.table_a.foo).force_index('idx1')
+        query_b = query_a.force_index('idx2')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_with_return_new_query_instance(self):
+        alias_1 = Query.from_(self.table_a).select(self.table_a.foo)
+        alias_2 = Query.from_(self.table_b).select(self.table_b.bar)
+        query_a = Query.from_(AliasedQuery('a1')).select('foo').with_(alias_1, 'a1')
+        query_b = query_a.with_(alias_2, 'a2')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_insert_into_return_new_query_instance(self):
+        query_a = Query.into(self.table_a).insert('foo1')
+        query_b = query_a.insert('foo2')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_replace_into_return_new_query_instance(self):
+        query_a = Query.into(self.table_a).replace('foo')
+        query_b = query_a.replace('bar')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
+    def test_update_set_return_new_query_instance(self):
+        query_a = Query.update(self.table_a).set(self.table_a.foo, 'foo')
+        query_b = query_a.set(self.table_a.bar, 'bar')
+
+        self.assertIsNot(query_a, query_b)
+        self.assertNotEqual(str(query_a), str(query_b))
+
     def test_queries_after_join(self):
         query1 = Query.from_(self.table_a).select(self.table_a.foo)
         query2 = query1.join(self.table_b).on(self.table_a.foo == self.table_b.bar).select(self.table_b.buz)