Skip to content

Commit 109b89b

Browse files
committed
fix #9
1 parent 5659546 commit 109b89b

File tree

5 files changed

+55
-24
lines changed

5 files changed

+55
-24
lines changed

clickhouse_backend/driver/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from .escape import escape_params
77

8-
insert_pattern = re.compile(r'\s*insert\s*into', flags=re.IGNORECASE)
8+
insert_pattern = re.compile(r'\s*insert\s+into', flags=re.IGNORECASE)
99

1010

1111
class Client(client.Client):

clickhouse_backend/driver/connection.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import re
2+
13
from clickhouse_driver.dbapi import connection
24
from clickhouse_driver.dbapi import cursor
35
from clickhouse_driver.dbapi import errors
46
from clickhouse_pool.pool import ChPoolError
57

68
from .pool import ClickhousePool
79

10+
update_pattern = re.compile(r'\s*alter\s+table\s+(.+)\s+update.+where\s+(.+)', flags=re.IGNORECASE)
11+
812

913
class Cursor(cursor.Cursor):
1014
def close(self):
@@ -28,6 +32,19 @@ def __del__(self):
2832
except ChPoolError:
2933
pass
3034

35+
def execute(self, operation, parameters=None):
36+
"""fix https://github.com/jayvynl/django-clickhouse-backend/issues/9"""
37+
if update_pattern.match(operation):
38+
query = self._client.substitute_params(
39+
operation, parameters, self._client.connection.context
40+
)
41+
table, where = update_pattern.match(query).groups()
42+
super().execute(f'select count(*) from {table} where {where}')
43+
rowcount, = self.fetchone()
44+
self._reset_state()
45+
self._rowcount = rowcount
46+
super().execute(operation, parameters)
47+
3148

3249
class Connection(connection.Connection):
3350
"""Connection class with support for connection pool."""

clickhouse_backend/models/base.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ def as_clickhouse(self, compiler, connection, **extra_context):
2525
functions.Random.as_clickhouse = as_clickhouse
2626

2727

28-
class ClickhouseManager(BaseManager.from_queryset(QuerySet)):
28+
class ClickhouseManager(models.Manager):
29+
_queryset_class = QuerySet
30+
2931
def get_queryset(self):
3032
"""
3133
User defined Query and QuerySet class that support clickhouse particular query.
@@ -46,9 +48,9 @@ class Meta:
4648

4749
def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
4850
"""
49-
Try to update the model. Return True if the model was updated (if an
50-
update query was done and a matching row was found in the DB).
51-
"""
51+
Try to update the model. Return True if the model was updated (if an
52+
update query was done and a matching row was found in the DB).
53+
"""
5254
filtered = base_qs.filter(pk=pk_val)
5355
if not values:
5456
# We can end up here when saving a model in inheritance chain where
@@ -57,15 +59,4 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
5759
# is a model with just PK - in that case check that the PK still
5860
# exists.
5961
return update_fields is not None or filtered.exists()
60-
return (
61-
filtered.exists()
62-
and
63-
# It may happen that the object is deleted from the DB right after
64-
# this check, causing the subsequent UPDATE to return zero matching
65-
# rows. The same result can occur in some rare cases when the
66-
# database returns zero despite the UPDATE being executed
67-
# successfully (a row is matched and updated). In order to
68-
# distinguish these two cases, the object's existence in the
69-
# database is again checked for if the UPDATE query returns 0.
70-
(filtered._update(values) > 0 or filtered.exists())
71-
)
62+
return filtered._update(values) > 0

tests/basic/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,14 @@ class PrimaryKeyWithDefault(ClickhouseModel):
5353

5454
class ChildPrimaryKeyWithDefault(PrimaryKeyWithDefault):
5555
pass
56+
57+
58+
class DjangoArticle(models.Model):
59+
headline = models.CharField(max_length=100, default="Default headline")
60+
pub_date = models.DateTimeField()
61+
62+
class Meta:
63+
ordering = ("pub_date", "headline")
64+
65+
def __str__(self):
66+
return self.headline

tests/basic/tests.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@
44

55
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
66
from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections, models
7-
from django.db.models.manager import BaseManager
87
from django.db.models.query import MAX_GET_RESULTS, EmptyQuerySet
98
from django.test import (
10-
SimpleTestCase,
119
TestCase,
1210
TransactionTestCase,
1311
skipUnlessDBFeature,
1412
)
1513
from django.utils.translation import gettext_lazy
1614

17-
from clickhouse_backend import compat
1815
from .models import (
1916
Article,
2017
ArticleSelectOnSave,
2118
ChildPrimaryKeyWithDefault,
2219
FeaturedArticle,
2320
PrimaryKeyWithDefault,
2421
SelfRef,
22+
DjangoArticle
2523
)
2624

2725

@@ -632,12 +630,12 @@ def deleter():
632630
class SelectOnSaveTests(TestCase):
633631
def test_select_on_save(self):
634632
a1 = Article.objects.create(pub_date=datetime.now())
635-
with self.assertNumQueries(3):
633+
with self.assertNumQueries(1):
636634
a1.save()
637635
asos = ArticleSelectOnSave.objects.create(pub_date=datetime.now())
638-
with self.assertNumQueries(3):
636+
with self.assertNumQueries(1):
639637
asos.save()
640-
with self.assertNumQueries(3):
638+
with self.assertNumQueries(1):
641639
asos.save(force_update=True)
642640
Article.objects.all().delete()
643641
with self.assertRaisesMessage(
@@ -671,7 +669,7 @@ def _update(self, *args, **kwargs):
671669
try:
672670
Article._base_manager._queryset_class = FakeQuerySet
673671
asos = ArticleSelectOnSave.objects.create(pub_date=datetime.now())
674-
with self.assertNumQueries(3):
672+
with self.assertNumQueries(2):
675673
asos.save()
676674
self.assertTrue(FakeQuerySet.called)
677675
finally:
@@ -808,3 +806,17 @@ def test_prefetched_cache_cleared(self):
808806
a2_prefetched.refresh_from_db(fields=["selfref_set"])
809807
# Cache was cleared and new results are available.
810808
self.assertCountEqual(a2_prefetched.selfref_set.all(), [s])
809+
810+
811+
class DjangoModelTests(TestCase):
812+
def test_saving_an_object_again_does_not_create_a_new_object(self):
813+
a = DjangoArticle(headline="original", pub_date=datetime(2014, 5, 16))
814+
a.save()
815+
current_id = a.id
816+
817+
a.save()
818+
self.assertEqual(a.id, current_id)
819+
820+
a.headline = "Updated headline"
821+
a.save()
822+
self.assertEqual(a.id, current_id)

0 commit comments

Comments
 (0)