Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
45d807e
1. add stddevPop func
Jan 18, 2021
e14de71
Support for adding a column to the beginning of a table
meanmail Mar 3, 2021
6a58892
Simplified
meanmail Mar 4, 2021
fbf2207
Bump jinja2 from 2.11.2 to 2.11.3 in /examples/db_explorer
dependabot[bot] Mar 20, 2021
0f4547b
Bump urllib3 from 1.25.9 to 1.26.5 in /examples/db_explorer
dependabot[bot] Jun 2, 2021
5abe39e
Fixes to make the tests pass on ClickHouse v21.9
Oct 16, 2021
76d432b
Merge pull request #181 from mangototango/ver_21_9
Oct 17, 2021
0787efc
ignore non-numeric parts of version string
Oct 21, 2021
3b0aaeb
fix precedence of ~ operator in Q objects
Oct 21, 2021
ed33c09
Merge pull request #183 from mangototango/small_issues
Oct 21, 2021
da591dc
Merge pull request #167 from meanmail/develop
Oct 21, 2021
1ae66f6
Merge pull request #164 from behldizh/add-stddev-funcs-develop
Oct 21, 2021
cb7d7ef
Merge pull request #172 from Infinidat/dependabot/pip/examples/db_exp…
Oct 21, 2021
17ab9c0
Merge pull request #169 from Infinidat/dependabot/pip/examples/db_exp…
Oct 21, 2021
4cbaf5e
Releasing v2.1.1
Oct 21, 2021
232a8d2
Finished Release v2.1.1
Oct 21, 2021
2727291
Add model to QuerySet to support django-rest-framework 3
Jul 18, 2022
359809e
Releasing v2.1.2
Jul 19, 2022
7b15567
Bump python version to 3.8.12
Jul 19, 2022
2777d30
Releasing v2.1.2
Jul 19, 2022
09aeddf
Finished Release v2.1.2
Jul 19, 2022
d7a26a8
Fix pagination for models with alias fields
Nov 29, 2022
070b2c3
Merge pull request #197 from Infinidat/fix_paginate_alias
Nov 29, 2022
623f3e7
releasing 2.1.3
Nov 29, 2022
45a9200
Finished Release v2.1.3
Nov 29, 2022
ee01e82
Merge remote-tracking branch 'upstream/develop' into merge-upstream
olliemath Jul 1, 2025
6c49afb
ci: update test matrix
olliemath Jul 1, 2025
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
8 changes: 4 additions & 4 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Upload to PIP

# Controls when the action will run.
# Controls when the action will run.
on:
# Triggers the workflow when a release is created
release:
release:
types: [created]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -18,10 +18,10 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v4

# Sets up python
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: 3.9

Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
fail-fast: false
matrix:
# Lint on smallest and largest active versions
python-version: [3.6, 3.9]
python-version: ["3.9", "3.10"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -45,12 +45,12 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, pypy3]
python-version: ["3.9", "3.10", "pypy3.10"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -72,12 +72,12 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: ["3.9"]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Change Log
==========

v2.3.0
------
Merges upstream changes:

- Fix pagination for models with alias columns
- Add `QuerySet.model` to support django-rest-framework 3
- Improve support of ClickHouse v21.9 (mangototango)
- Ignore non-numeric parts in ClickHouse version (mangototango)
- Fix precedence of ~ operator in Q objects (mangototango)
- Support for adding a column to the beginning of a table (meanmail)
- Add stddevPop and stddevSamp functions (k.peskov)

v2.2.2
------
- Unpined requirements to enhance compatability
Expand Down Expand Up @@ -216,5 +228,3 @@ v0.7.0
v0.6.3
------
- Python 3 support


71 changes: 57 additions & 14 deletions clickhouse_orm/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def __init__(self, message):
""",
re.VERBOSE | re.DOTALL,
),
# ClickHouse v21+
re.compile(
r"""
Code:\ (?P<code>\d+).
\ (?P<type1>[^ \n]+):\ (?P<msg>.+)
""",
re.VERBOSE | re.DOTALL,
),
)

@classmethod
Expand Down Expand Up @@ -124,14 +132,20 @@ def __init__(
self.db_exists = self._is_existing_database()
if readonly:
if not self.db_exists:
raise DatabaseException("Database does not exist, and cannot be created under readonly connection")
raise DatabaseException(
"Database does not exist, and cannot be created under readonly connection"
)
self.connection_readonly = self._is_connection_readonly()
self.readonly = True
elif autocreate and not self.db_exists:
self.create_database()
self.server_version = self._get_server_version()
# Versions 1.1.53981 and below don't have timezone function
self.server_timezone = self._get_server_timezone() if self.server_version > (1, 1, 53981) else pytz.utc
self.server_timezone = (
self._get_server_timezone()
if self.server_version > (1, 1, 53981)
else pytz.utc
)
# Versions 19.1.16 and above support codec compression
self.has_codec_support = self.server_version >= (19, 1, 16)
# Version 19.0 and above support LowCardinality
Expand All @@ -158,7 +172,9 @@ def create_table(self, model_class):
if model_class.is_system_model():
raise DatabaseException("You can't create system table")
if model_class.engine is None:
raise DatabaseException("%s class must define an engine" % model_class.__name__)
raise DatabaseException(
"%s class must define an engine" % model_class.__name__
)
self._send(model_class.create_table_sql(self))

def drop_table(self, model_class):
Expand Down Expand Up @@ -229,7 +245,9 @@ def insert(self, model_instances, batch_size=1000):
if first_instance.is_read_only() or first_instance.is_system_model():
raise DatabaseException("You can't insert into read only and system tables")

fields_list = ",".join(["`%s`" % name for name in first_instance.fields(writable=True)])
fields_list = ",".join(
["`%s`" % name for name in first_instance.fields(writable=True)]
)
fmt = "TSKV" if model_class.has_funcs_as_defaults() else "TabSeparated"
query = "INSERT INTO $table (%s) FORMAT %s\n" % (fields_list, fmt)

Expand Down Expand Up @@ -289,11 +307,15 @@ def select(self, query, model_class=None, settings=None):
lines = r.iter_lines()
field_names = parse_tsv(next(lines))
field_types = parse_tsv(next(lines))
model_class = model_class or ModelBase.create_ad_hoc_model(zip(field_names, field_types))
model_class = model_class or ModelBase.create_ad_hoc_model(
zip(field_names, field_types)
)
for line in lines:
# skip blank line left by WITH TOTALS modifier
if line:
yield model_class.from_tsv(line, field_names, self.server_timezone, self)
yield model_class.from_tsv(
line, field_names, self.server_timezone, self
)

def raw(self, query, settings=None, stream=False):
"""
Expand All @@ -306,7 +328,15 @@ def raw(self, query, settings=None, stream=False):
query = self._substitute(query, None)
return self._send(query, settings=settings, stream=stream).text

def paginate(self, model_class, order_by, page_num=1, page_size=100, conditions=None, settings=None):
def paginate(
self,
model_class,
order_by,
page_num=1,
page_size=100,
conditions=None,
settings=None,
):
"""
Selects records and returns a single page of model instances.

Expand All @@ -330,7 +360,8 @@ def paginate(self, model_class, order_by, page_num=1, page_size=100, conditions=
elif page_num < 1:
raise ValueError("Invalid page number: %d" % page_num)
offset = (page_num - 1) * page_size
query = "SELECT * FROM $table"
query = "SELECT {} FROM $table".format(", ".join(model_class.fields().keys()))

if conditions:
if isinstance(conditions, Q):
conditions = conditions.to_sql(model_class)
Expand Down Expand Up @@ -367,7 +398,9 @@ def migrate(self, migrations_package_name, up_to=9999):
self.insert(
[
MigrationHistory(
package_name=migrations_package_name, module_name=name, applied=datetime.date.today()
package_name=migrations_package_name,
module_name=name,
applied=datetime.date.today(),
)
]
)
Expand All @@ -378,7 +411,10 @@ def _get_applied_migrations(self, migrations_package_name):
from .migrations import MigrationHistory

self.create_table(MigrationHistory)
query = "SELECT module_name from $table WHERE package_name = '%s'" % migrations_package_name
query = (
"SELECT module_name from $table WHERE package_name = '%s'"
% migrations_package_name
)
query = self._substitute(query, MigrationHistory)
return set(obj.module_name for obj in self.select(query))

Expand All @@ -388,7 +424,9 @@ def _send(self, data, settings=None, stream=False):
if self.log_statements:
logger.info(data)
params = self._build_params(settings)
r = self.request_session.post(self.db_url, params=params, data=data, stream=stream, timeout=self.timeout)
r = self.request_session.post(
self.db_url, params=params, data=data, stream=stream, timeout=self.timeout
)
if r.status_code != 200:
raise ServerError(r.text)
return r
Expand All @@ -413,7 +451,10 @@ def _substitute(self, query, model_class=None):
if model_class.is_system_model():
mapping["table"] = "`system`.`%s`" % model_class.table_name()
else:
mapping["table"] = "`%s`.`%s`" % (self.db_name, model_class.table_name())
mapping["table"] = "`%s`.`%s`" % (
self.db_name,
model_class.table_name(),
)
query = Template(query).safe_substitute(mapping)
return query

Expand All @@ -432,10 +473,12 @@ def _get_server_version(self, as_tuple=True):
except ServerError as e:
logger.exception("Cannot determine server version (%s), assuming 1.1.0", e)
ver = "1.1.0"
return tuple(int(n) for n in ver.split(".")) if as_tuple else ver
return tuple(int(n) for n in ver.split(".") if n.isdigit()) if as_tuple else ver

def _is_existing_database(self):
r = self._send("SELECT count() FROM system.databases WHERE name = '%s'" % self.db_name)
r = self._send(
"SELECT count() FROM system.databases WHERE name = '%s'" % self.db_name
)
return r.text.strip() == "1"

def _is_connection_readonly(self):
Expand Down
Loading
Loading