Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### unreleased

- fix: #99 update value containing "where" cause exception.
- fix: #97 JSONField error in ClickHouse 24.8.
- fix: tuple function error in ClickHouse 24.8.

### 1.3.0

- fix #92 last_executed_query() when params is a mappinglast_executed_query() when params is a mapping.
Expand Down
2 changes: 1 addition & 1 deletion clickhouse_backend/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
"BigAutoField": "Int64",
"IPAddressField": "IPv4",
"GenericIPAddressField": "IPv6",
"JSONField": "JSON",
"JSONField": "Object('json')",
"BinaryField": "String",
"CharField": "FixedString(%(max_length)s)",
"DateField": "Date32",
Expand Down
4 changes: 2 additions & 2 deletions clickhouse_backend/backend/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
TableInfo = namedtuple("TableInfo", BaseTableInfo._fields + ("comment",))

constraint_pattern = re.compile(
r"CONSTRAINT (`)?((?(1)(?:[^\\`]|\\.)+|\S+))(?(1)`|) (CHECK .+?),?\n"
r"CONSTRAINT (`)?((?(1)(?:\\.|[^`])+|\S+))(?(1)`|) (CHECK .+?),?\n"
)
index_pattern = re.compile(
r"INDEX (`)?((?(1)(?:[^\\`]|\\.)+|\S+))(?(1)`|) (.+? TYPE ([a-zA-Z_][0-9a-zA-Z_]*)\(.+?\) GRANULARITY \d+)"
r"INDEX (`)?((?(1)(?:\\.|[^`])+|\S+))(?(1)`|) (.+? TYPE ([a-zA-Z_][0-9a-zA-Z_]*)\(.+?\) GRANULARITY \d+)"
)


Expand Down
27 changes: 18 additions & 9 deletions clickhouse_backend/driver/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from .escape import escape_params
from .pool import ClickhousePool

update_pattern = re.compile(
r"^\s*alter\s+table\s+(\S+)\s+.*?update.+?where\s+(.+?)(?:settings\s+.+)?$",
flags=re.IGNORECASE,
)
name_regex = r'"(?:[^"]|\\.)+"'
value_regex = r"(')?(?(1)(?:[^']|\\.)+|\S+)(?(1)'|)"
name_value_regex = f"{name_regex} = {value_regex}"
update_pattern = re.compile(f"^ALTER TABLE ({name_regex}) UPDATE ")


def send_query(self, query, query_id=None, params=None):
Expand Down Expand Up @@ -95,11 +95,20 @@ def execute(self, operation, parameters=None):
query = self._client.substitute_params(
operation, parameters, self._client.connection.context
)
table, where = update_pattern.match(query).groups()
super().execute(f"select count(*) from {table} where {where}")
(rowcount,) = self.fetchone()
self._reset_state()
self._rowcount = rowcount
m = update_pattern.match(query)
table = m.group(1)
query_upper = query.upper()
i = query_upper.rfind(" WHERE ")
if i > 0:
j = query_upper.rfind(" SETTINGS ", i + 7)
if j > 0:
where = query[i + 7 : j]
else:
where = query[i + 7 :]
super().execute(f"select count(*) from {table} where {where}")
(rowcount,) = self.fetchone()
self._reset_state()
self._rowcount = rowcount
super().execute(operation, parameters)


Expand Down
5 changes: 5 additions & 0 deletions clickhouse_backend/models/fields/tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ def _convert_type(self, value):
if value is None or isinstance(value, self.container_class):
return value
if self.is_named_tuple:
if isinstance(value, dict):
return self.container_class(**value)
return self.container_class(*value)
# From ClickHouse server 24.8 LTS, tuple("a", "b") returns NamedTuple.
if isinstance(value, dict):
return self.container_class(value.values())
return self.container_class(value)

@property
Expand Down
5 changes: 5 additions & 0 deletions docs/Fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,13 +639,18 @@ MapModel.objects.annotate(

### JSON

**Note:** Object('json') type [is not production-ready and is now deprecated](https://clickhouse.com/docs/en/sql-reference/data-types/object-data-type).

Field importing path: `clickhouse_backend.models.JSONField`.

Neither Nullable nor LowCardinality is supported.

When query from the database, JSONField get dict or list.

The JSON data type is an experimental feature. To use it, set `allow_experimental_object_type = 1` in the database settings.

**Note:** From [ClickHouse 24.8 LTS](https://clickhouse.com/blog/clickhouse-release-24-08), set `allow_experimental_json_type = 1` to use JSON type.

For example:

```python
Expand Down
2 changes: 1 addition & 1 deletion tests/clickhouse_fields/test_jsonfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .models import JSONModel


class MapFieldTests(TestCase):
class JsonFieldTests(TestCase):
def test_disallow_nullable(self):
field = models.JSONField(null=True, name="field")
self.assertEqual(
Expand Down
7 changes: 7 additions & 0 deletions tests/queries/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ def test_update(self):
self.a1.save()
with self.assertRaises(models.Author.MultipleObjectsReturned):
self.a1.refresh_from_db()

# regression test for https://github.com/jayvynl/django-clickhouse-backend/issues/99
def test_update_special_string_val(self):
self.a1.name = "where **"
self.a1.save()
self.a1.refresh_from_db()
self.assertEqual(self.a1.name, "where **")