diff --git a/CHANGELOG.md b/CHANGELOG.md
index adda7f1..5401934 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
### 1.4.0
- feat: #119 Allow query results returned in columns and deserialized to `numpy` objects
+- feat: #125 Add database functions `toStartOfMinute`, `toStartOfFiveMinutes`, `toStartOfTenMinutes`, `toStartOfFifteenMinutes` and `toStartofHour`
### 1.3.2
diff --git a/clickhouse-config/node1/remote-servers.xml b/clickhouse-config/node1/remote-servers.xml
index 710591f..7bbf2d5 100644
--- a/clickhouse-config/node1/remote-servers.xml
+++ b/clickhouse-config/node1/remote-servers.xml
@@ -7,10 +7,12 @@
node1
9000
+ clickhouse_password
node2
9000
+ clickhouse_password
@@ -18,12 +20,14 @@
node3
9000
+ clickhouse_password
node4
9000
+ clickhouse_password
-
\ No newline at end of file
+
diff --git a/clickhouse-config/node2/remote-servers.xml b/clickhouse-config/node2/remote-servers.xml
index 710591f..7bbf2d5 100644
--- a/clickhouse-config/node2/remote-servers.xml
+++ b/clickhouse-config/node2/remote-servers.xml
@@ -7,10 +7,12 @@
node1
9000
+ clickhouse_password
node2
9000
+ clickhouse_password
@@ -18,12 +20,14 @@
node3
9000
+ clickhouse_password
node4
9000
+ clickhouse_password
-
\ No newline at end of file
+
diff --git a/clickhouse-config/node3/remote-servers.xml b/clickhouse-config/node3/remote-servers.xml
index 710591f..7bbf2d5 100644
--- a/clickhouse-config/node3/remote-servers.xml
+++ b/clickhouse-config/node3/remote-servers.xml
@@ -7,10 +7,12 @@
node1
9000
+ clickhouse_password
node2
9000
+ clickhouse_password
@@ -18,12 +20,14 @@
node3
9000
+ clickhouse_password
node4
9000
+ clickhouse_password
-
\ No newline at end of file
+
diff --git a/clickhouse-config/node4/remote-servers.xml b/clickhouse-config/node4/remote-servers.xml
index 710591f..7bbf2d5 100644
--- a/clickhouse-config/node4/remote-servers.xml
+++ b/clickhouse-config/node4/remote-servers.xml
@@ -7,10 +7,12 @@
node1
9000
+ clickhouse_password
node2
9000
+ clickhouse_password
@@ -18,12 +20,14 @@
node3
9000
+ clickhouse_password
node4
9000
+ clickhouse_password
-
\ No newline at end of file
+
diff --git a/clickhouse_backend/models/functions/datetime.py b/clickhouse_backend/models/functions/datetime.py
index 4929b11..9dd204a 100644
--- a/clickhouse_backend/models/functions/datetime.py
+++ b/clickhouse_backend/models/functions/datetime.py
@@ -6,6 +6,11 @@
from .base import Func
__all__ = [
+ "toStartOfMinute",
+ "toStartOfFiveMinutes",
+ "toStartOfTenMinutes",
+ "toStartOfFifteenMinutes",
+ "toStartOfHour",
"toYYYYMM",
"toYYYYMMDD",
"toYYYYMMDDhhmmss",
@@ -39,3 +44,36 @@ class toYYYYMMDD(toYYYYMM):
class toYYYYMMDDhhmmss(toYYYYMM):
output_field = fields.UInt64Field()
+
+
+class toStartOfMinute(Func):
+ output_field = models.fields.DateTimeField()
+
+ def __init__(self, *expressions):
+ arity = len(expressions)
+ if arity < 1 or arity > 1:
+ raise TypeError(
+ "'%s' takes 1 argument (%s given)"
+ % (
+ self.__class__.__name__,
+ len(expressions),
+ )
+ )
+
+ super().__init__(*expressions)
+
+
+class toStartOfFiveMinutes(toStartOfMinute):
+ pass
+
+
+class toStartOfTenMinutes(toStartOfMinute):
+ pass
+
+
+class toStartOfFifteenMinutes(toStartOfMinute):
+ pass
+
+
+class toStartOfHour(toStartOfMinute):
+ pass
diff --git a/compose.yaml b/compose.yaml
index 0c79a8d..9e3c6d4 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,5 +1,7 @@
x-base-service: &base-service
image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-23.6.2.18}
+ environment:
+ CLICKHOUSE_PASSWORD: "clickhouse_password"
restart: always
ulimits:
nofile:
diff --git a/tests/clickhouse_functions/test_datetime.py b/tests/clickhouse_functions/test_datetime.py
index 6e598b0..36e6cda 100644
--- a/tests/clickhouse_functions/test_datetime.py
+++ b/tests/clickhouse_functions/test_datetime.py
@@ -17,13 +17,15 @@ def setUpTestData(cls):
alias="smithj",
# https://stackoverflow.com/a/18862958
birthday=pytz.timezone(get_timezone()).localize(
- datetime(2023, 11, 30, 16), is_dst=False
+ datetime(2023, 11, 30, hour=16, minute=12, second=15), is_dst=False
),
)
cls.elena = Author.objects.create(
name="Élena Jordan",
alias="elena",
- birthday=pytz.utc.localize(datetime(2023, 11, 30, 16), is_dst=False),
+ birthday=pytz.utc.localize(
+ datetime(2023, 11, 30, hour=16, minute=59, second=59), is_dst=False
+ ),
)
def test_yyyymm(self):
@@ -50,8 +52,128 @@ def test_yyyymmddhhmmss(self):
john = Author.objects.annotate(v=models.toYYYYMMDDhhmmss("birthday")).get(
id=self.john.id
)
- self.assertEqual(john.v, 20231130160000)
+ self.assertEqual(john.v, 20231130161215)
elena = Author.objects.annotate(
v=models.toYYYYMMDDhhmmss("birthday", "Asia/Shanghai")
).get(id=self.elena.id)
- self.assertEqual(elena.v, 20231201000000)
+ self.assertEqual(elena.v, 20231201005959)
+
+ def test_tostartofminute(self):
+ john = Author.objects.annotate(v=models.toStartOfMinute("birthday")).get(
+ id=self.john.id
+ )
+ self.assertEqual(
+ john.v,
+ datetime(
+ 2023,
+ 11,
+ 30,
+ hour=16,
+ minute=12,
+ second=00,
+ ),
+ )
+
+ elena = Author.objects.annotate(v=models.toStartOfMinute("birthday")).get(
+ id=self.elena.id
+ )
+ self.assertEqual(
+ elena.v,
+ datetime(2023, 11, 30, hour=10, minute=59, second=00),
+ )
+
+ def test_tostartoffiveminutes(self):
+ john = Author.objects.annotate(v=models.toStartOfFiveMinutes("birthday")).get(
+ id=self.john.id
+ )
+ self.assertEqual(
+ john.v,
+ datetime(
+ 2023,
+ 11,
+ 30,
+ hour=16,
+ minute=10,
+ second=00,
+ ),
+ )
+
+ elena = Author.objects.annotate(v=models.toStartOfFiveMinutes("birthday")).get(
+ id=self.elena.id
+ )
+ self.assertEqual(
+ elena.v,
+ datetime(2023, 11, 30, hour=10, minute=55, second=00),
+ )
+
+ def test_tostartoftenminutes(self):
+ john = Author.objects.annotate(v=models.toStartOfTenMinutes("birthday")).get(
+ id=self.john.id
+ )
+ self.assertEqual(
+ john.v,
+ datetime(
+ 2023,
+ 11,
+ 30,
+ hour=16,
+ minute=10,
+ second=00,
+ ),
+ )
+
+ elena = Author.objects.annotate(v=models.toStartOfTenMinutes("birthday")).get(
+ id=self.elena.id
+ )
+ self.assertEqual(
+ elena.v,
+ datetime(2023, 11, 30, hour=10, minute=50, second=00),
+ )
+
+ def test_tostartoffifteenminutes(self):
+ john = Author.objects.annotate(
+ v=models.toStartOfFifteenMinutes("birthday")
+ ).get(id=self.john.id)
+ self.assertEqual(
+ john.v,
+ datetime(
+ 2023,
+ 11,
+ 30,
+ hour=16,
+ minute=00,
+ second=00,
+ ),
+ )
+
+ elena = Author.objects.annotate(
+ v=models.toStartOfFifteenMinutes("birthday")
+ ).get(id=self.elena.id)
+ self.assertEqual(
+ elena.v,
+ datetime(2023, 11, 30, hour=10, minute=45, second=00),
+ )
+
+ def test_tostartofhour(self):
+ john = Author.objects.annotate(v=models.toStartOfHour("birthday")).get(
+ id=self.john.id
+ )
+ self.assertEqual(
+ john.v,
+ datetime(
+ 2023,
+ 11,
+ 30,
+ hour=16,
+ minute=00,
+ second=00,
+ ),
+ )
+
+ elena = Author.objects.annotate(v=models.toStartOfHour("birthday")).get(
+ id=self.elena.id
+ )
+ self.assertEqual(
+ elena.v,
+ datetime(2023, 11, 30, hour=10, minute=00, second=00),
+ )
diff --git a/tests/settings.py b/tests/settings.py
index d8b60c1..ae5fde7 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -19,6 +19,7 @@
DATABASES = {
"default": {
"ENGINE": "clickhouse_backend.backend",
+ "PASSWORD": "clickhouse_password",
"OPTIONS": {
"migration_cluster": "cluster",
"connections_min": 1,
@@ -35,6 +36,7 @@
},
"s1r2": {
"ENGINE": "clickhouse_backend.backend",
+ "PASSWORD": "clickhouse_password",
"PORT": 9001,
"OPTIONS": {
"migration_cluster": "cluster",
@@ -52,6 +54,7 @@
},
"s2r1": {
"ENGINE": "clickhouse_backend.backend",
+ "PASSWORD": "clickhouse_password",
"PORT": 9002,
"OPTIONS": {
"migration_cluster": "cluster",