Skip to content

Commit bf40e41

Browse files
authored
fix #25 (#31)
* Change AutoFiled and SmallAutoField to clickhouse Int64, so that id worker can generate value for them. * `DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'` is no longer a required configuration item.
1 parent bc82d21 commit bf40e41

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2195
-125
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
### 1.1.0
2+
- Change `AutoFiled` and `SmallAutoField` to clickhouse `Int64`, so that id worker can generate value for them.
3+
This allows more compatibilities with existing apps such as `django.contrib.auth`.
4+
- `DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'` is no longer a required configuration item.
5+
16
### 1.0.3
27
- Fix reading settings in explain, pull request [#13](https://github.com/jayvynl/django-clickhouse-backend/pull/13) by [mahdi-jfri](https://github.com/mahdi-jfri).
38
- Add toYYYYMM[DD[hhmmss]] functions.

README.md

+3-7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Read [Documentation](https://github.com/jayvynl/django-clickhouse-backend/blob/m
2828
- In outer join, clickhouse will set missing columns to empty values (0 for number, empty string for text, unix epoch for date/datatime) instead of NULL.
2929
So Count("book") resolve to 1 in a missing LEFT OUTER JOIN match, not 0.
3030
In aggregation expression Avg("book__rating", default=2.5), default=2.5 have no effect in a missing match.
31+
- Clickhouse does not support unique constraint and foreignkey constraint. `ForeignKey`, `ManyToManyField` and `OneToOneField` can be used with clickhouse backend, but no database level constraints will be added, so there could be some consistency problems.
32+
- Clickhouse does not support transaction. If any exception occurs during migrating, then your clickhouse database will be in an untracked state. Any migration should be full tested in test environment before deployed to production environment.
3133

3234
**Requirements:**
3335

@@ -85,13 +87,9 @@ DATABASES = {
8587
'HOST': 'localhost',
8688
'USER': 'DB_USER',
8789
'PASSWORD': 'DB_PASSWORD',
88-
'TEST': {
89-
'fake_transaction': True
90-
}
9190
}
9291
}
9392
DATABASE_ROUTERS = ['dbrouters.ClickHouseRouter']
94-
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
9593
```
9694

9795
```python
@@ -143,8 +141,6 @@ automatically route your queries to the right database. In the preceding example
143141
queries from subclasses of `clickhouse_backend.models.ClickhouseModel` or custom migrations with a `clickhouse` hint key to clickhouse.
144142
All other queries are routed to the default database (postgresql).
145143

146-
`DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'` is required to working with django migration.
147-
More details will be covered in [DEFAULT_AUTO_FIELD](https://github.com/jayvynl/django-clickhouse-backend/blob/main/docs/Configurations.md#default_auto_field).
148144

149145
### Model Definition
150146

@@ -224,7 +220,7 @@ this operation will generate migration file under apps/migrations/
224220
then we mirgrate
225221

226222
```shell
227-
$ python manage.py migrate
223+
$ python manage.py migrate --database clickhouse
228224
```
229225

230226
for the first time run, this operation will generate django_migrations table with create table sql like this

clickhouse_backend/VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.3
1+
1.1.0

clickhouse_backend/backend/base.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
2323
# If a column type is set to None, it won't be included in the output.
2424
data_types = {
2525
# Django fields.
26-
"SmallAutoField": "Int16",
27-
"AutoField": "Int32",
26+
"SmallAutoField": "Int64",
27+
"AutoField": "Int64",
2828
"BigAutoField": "Int64",
2929
"IPAddressField": "IPv4",
3030
"GenericIPAddressField": "IPv6",

clickhouse_backend/backend/operations.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class DatabaseOperations(BaseDatabaseOperations):
2323
"PositiveBigIntegerField": (0, 18446744073709551615),
2424
"PositiveSmallIntegerField": (0, 65535),
2525
"PositiveIntegerField": (0, 4294967295),
26-
"SmallAutoField": (-32768, 32767),
27-
"AutoField": (-2147483648, 2147483647),
26+
"SmallAutoField": (-9223372036854775808, 9223372036854775807),
27+
"AutoField": (-9223372036854775808, 9223372036854775807),
2828
"BigAutoField": (-9223372036854775808, 9223372036854775807),
2929
# Clickhouse fields.
3030
"Int8Field": (-1 << 7, -1 ^ (-1 << 7)),

clickhouse_backend/backend/schema.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.db.backends.ddl_references import (
88
Expressions, IndexName, Statement, Table, Columns
99
)
10+
from django.db.models.constraints import CheckConstraint, UniqueConstraint
1011
from django.db.models.expressions import ExpressionList
1112
from django.db.models.indexes import IndexExpression
1213

@@ -82,9 +83,12 @@ def table_sql(self, model):
8283
))
8384
constraints.append(self._column_check_sql(field))
8485
for constraint in model._meta.constraints:
85-
constraints.append(
86-
constraint.constraint_sql(model, self)
87-
)
86+
if isinstance(constraint, CheckConstraint):
87+
constraints.append(
88+
constraint.constraint_sql(model, self)
89+
)
90+
if any(isinstance(c, UniqueConstraint) for c in model._meta.constraints):
91+
warnings.warn("Clickhouse does not support unique constraint.")
8892

8993
engine = self._get_engine(model)
9094
extra_parts = self._model_extra_sql(model, engine)

clickhouse_backend/compat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import django
22

3-
dj32 = (3, 2) <= django.VERSION < (4, )
3+
dj3 = (3, ) <= django.VERSION < (4, )
44
dj4 = (4, ) <= django.VERSION < (5, )
55
dj_ge4 = django.VERSION >= (4, )
66
dj_ge41 = django.VERSION >= (4, 1)

clickhouse_backend/models/sql/compiler.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db.models.fields import BigAutoField
1+
from django.db.models.fields import AutoFieldMixin
22
from django.db.models.sql import compiler
33

44
from clickhouse_backend import compat
@@ -64,8 +64,8 @@ def as_sql(self):
6464
insert_statement = self.connection.ops.insert_statement()
6565

6666
fields = self.query.fields
67-
# For compatible with BigAutoField, add value generated by snowflake algorithm if needed.
68-
absent_of_pk = isinstance(opts.pk, BigAutoField) and opts.pk not in fields
67+
# Generate value for AutoField when needed.
68+
absent_of_pk = isinstance(opts.pk, AutoFieldMixin) and opts.pk not in fields
6969
if absent_of_pk:
7070
fields = fields + [opts.pk]
7171
for obj in self.query.objs:

clickhouse_backend/patch/__init__.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
from .fields import patch_jsonfield
2-
from .functions import patch_functions
1+
from .fields import *
2+
from .fields import __all__ as fields_all
3+
from .functions import *
4+
from .functions import __all__ as functions_all
5+
6+
__all__ = [
7+
"patch_all",
8+
*fields_all,
9+
*functions_all,
10+
]
311

412

513
def patch_all():
6-
patch_jsonfield()
7-
patch_functions()
14+
patch_all_functions()
15+
patch_all_fields()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from django.db import models
2+
3+
from .json import patch_jsonfield
4+
5+
__all__ = [
6+
"patch_all_fields",
7+
"patch_auto_field",
8+
"patch_jsonfield",
9+
]
10+
11+
12+
def patch_all_fields():
13+
patch_auto_field()
14+
patch_jsonfield()
15+
16+
17+
def patch_auto_field():
18+
def rel_db_type_decorator(cls):
19+
old_func = cls.rel_db_type
20+
21+
def rel_db_type(self, connection):
22+
if connection.vendor == "clickhouse":
23+
return self.db_type(connection)
24+
return old_func(self, connection)
25+
26+
cls.rel_db_type = rel_db_type
27+
return cls
28+
rel_db_type_decorator(models.AutoField)
29+
rel_db_type_decorator(models.SmallAutoField)
30+
rel_db_type_decorator(models.BigAutoField)

clickhouse_backend/patch/functions.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
from django.db.models import functions
22

3+
__all__ = [
4+
"patch_all_functions",
5+
"patch_random",
6+
]
37

4-
def random_as_clickhouse(self, compiler, connection, **extra_context):
5-
return functions.Random.as_sql(
6-
self, compiler, connection, function="rand64", **extra_context
7-
)
88

9+
def patch_all_functions():
10+
patch_random()
911

10-
def patch_functions():
12+
13+
def patch_random():
14+
def random_as_clickhouse(self, compiler, connection, **extra_context):
15+
return functions.Random.as_sql(
16+
self, compiler, connection, function="rand64", **extra_context
17+
)
1118
functions.Random.as_clickhouse = random_as_clickhouse

compose.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
clickhouse:
3-
image: clickhouse/clickhouse-server:23.1.3.5
3+
image: clickhouse/clickhouse-server:23.6.2.18
44
container_name: test-clickhouse
55
restart: always
66
environment:

docs/Configurations.md

+6-18
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ Valid `TEST` keys:
5151
But this will have a side effect, that is, the clickhouse data of each test case will not be isolated. So in general it is not recommended to use this feature unless you know very well what is the impaction.
5252

5353

54-
### DEFAULT_AUTO_FIELD
55-
56-
`DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'` IS REQUIRED TO WORKING WITH DJANGO MIGRATION.
54+
### Auto Field
5755

5856
Django ORM depends heavily on single column primary key, this primary key is a unique identifier of an ORM object.
5957
All `get` `save` `delete` actions depend on primary key.
@@ -64,20 +62,10 @@ There is [no unique constraint](https://github.com/ClickHouse/ClickHouse/issues/
6462

6563
By default, django will add a field named `id` as auto increasing primary key.
6664

67-
- AutoField
68-
69-
Mapped to clickhouse Int32 data type. You should generate this unique id yourself.
70-
71-
- BigAutoField
72-
73-
Mapped to clickhouse Int64 data type. If primary key is not specified when insert data, then `clickhouse_driver.idworker.id_worker` is used to generate this unique id.
74-
75-
Default id_worker is an instance of `clickhouse.idworker.snowflake.SnowflakeIDWorker` which implement [twitter snowflake id](https://en.wikipedia.org/wiki/Snowflake_ID).
76-
If data insertions happen on multiple datacenter, server, process or thread, you should ensure uniqueness of (CLICKHOUSE_WORKER_ID, CLICKHOUSE_DATACENTER_ID) environment variable.
77-
Because work_id and datacenter_id are 5 bits, they should be an integer between 0 and 31. CLICKHOUSE_WORKER_ID default to 0, CLICKHOUSE_DATACENTER_ID will be generated randomly if not provided.
65+
Django `AutoField`, `SmallAutoField` and `BigAutoField` ar mapped to clickhouse Int64 data type. If primary key is not specified when insert data, then `clickhouse_driver.idworker.id_worker` is used to generate this unique id.
7866

79-
`clickhouse.idworker.snowflake.SnowflakeIDWorker` is not thread safe. You could inherit `clickhouse.idworker.base.BaseIDWorker` and implement one, then set `CLICKHOUSE_ID_WORKER` in `settings.py` to doted import path of your IDWorker instance.
67+
Default `id_worker` is an instance of `clickhouse.idworker.snowflake.SnowflakeIDWorker` which implement [twitter snowflake id](https://en.wikipedia.org/wiki/Snowflake_ID).
68+
If data insertions happen on multiple datacenter, server, process or thread, you should ensure uniqueness of (CLICKHOUSE_WORKER_ID, CLICKHOUSE_DATACENTER_ID) environment variable.
69+
Because work_id and datacenter_id are 5 bits, they should be an integer between 0 and 31. CLICKHOUSE_WORKER_ID default to 0, CLICKHOUSE_DATACENTER_ID will be generated randomly if not provided.
8070

81-
Django use a table named `django_migrations` to track migration files. ID field should be BigAutoField, so that IDWorker can generate unique id for you.
82-
After Django 3.2,a new [config `DEFAULT_AUTO_FIELD`](https://docs.djangoproject.com/en/4.1/releases/3.2/#customizing-type-of-auto-created-primary-keys) is introduced to control field type of default primary key.
83-
So `DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'` is required if you want to use migrations with django clickhouse backend.
71+
`clickhouse.idworker.snowflake.SnowflakeIDWorker` is not thread safe. You could inherit `clickhouse.idworker.base.BaseIDWorker` and implement one, then set `CLICKHOUSE_ID_WORKER` in `settings.py` to doted import path of your IDWorker instance.

0 commit comments

Comments
 (0)