From e44ec89a44eca25203316dbb02a4d50769daffb6 Mon Sep 17 00:00:00 2001 From: Cd Chen Date: Sun, 27 Apr 2025 05:20:16 +0800 Subject: [PATCH 1/4] fix for table name contains any upper case characters. --- timescale/db/backends/postgresql/schema.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/timescale/db/backends/postgresql/schema.py b/timescale/db/backends/postgresql/schema.py index 368165c..310f7b6 100644 --- a/timescale/db/backends/postgresql/schema.py +++ b/timescale/db/backends/postgresql/schema.py @@ -2,7 +2,9 @@ from django.db.backends.postgresql.schema import DatabaseSchemaEditor from timescale.db.models.fields import TimescaleDateTimeField +import re +reg = re.compile(r"[A-Z]") class TimescaleSchemaEditor(DatabaseSchemaEditor): sql_is_hypertable = '''SELECT * FROM timescaledb_information.hypertables @@ -70,6 +72,16 @@ def _assert_is_not_hypertable(self, model): self.execute(sql) + # def quote_name(self, name): + # if reg.findall(name): + # return '"%s"' % name + # return super().quote_name(name) + + def quote_upper_name(self, name): + if reg.findall(name): + return '\'"%s"\'' % name + return self.quote_name(name) + def _drop_primary_key(self, model): """ Hypertables can't partition if the primary key is not @@ -97,7 +109,9 @@ def _create_hypertable(self, model, field, should_migrate=False): partition_column = self.quote_value(field.column) interval = self.quote_value(field.interval) - table = self.quote_value(model._meta.db_table) + # cdchen-2020427: Fix for upper case table name + # table = self.quote_value(model._meta.db_table) + table = self.quote_upper_name(model._meta.db_table) migrate = "true" if should_migrate else "false" if should_migrate and getattr(settings, "TIMESCALE_MIGRATE_HYPERTABLE_WITH_FRESH_TABLE", False): From 115a5222297757393bcf9bd0090c86637f74aac6 Mon Sep 17 00:00:00 2001 From: Cd Chen Date: Sun, 27 Apr 2025 08:15:23 +0800 Subject: [PATCH 2/4] Fix errors. --- timescale/db/backends/postgresql/schema.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/timescale/db/backends/postgresql/schema.py b/timescale/db/backends/postgresql/schema.py index 310f7b6..5e92526 100644 --- a/timescale/db/backends/postgresql/schema.py +++ b/timescale/db/backends/postgresql/schema.py @@ -72,15 +72,10 @@ def _assert_is_not_hypertable(self, model): self.execute(sql) - # def quote_name(self, name): - # if reg.findall(name): - # return '"%s"' % name - # return super().quote_name(name) - - def quote_upper_name(self, name): + def quote_upper_value(self, name): if reg.findall(name): return '\'"%s"\'' % name - return self.quote_name(name) + return self.quote_value(name) def _drop_primary_key(self, model): """ @@ -111,7 +106,7 @@ def _create_hypertable(self, model, field, should_migrate=False): interval = self.quote_value(field.interval) # cdchen-2020427: Fix for upper case table name # table = self.quote_value(model._meta.db_table) - table = self.quote_upper_name(model._meta.db_table) + table = self.quote_upper_value(model._meta.db_table) migrate = "true" if should_migrate else "false" if should_migrate and getattr(settings, "TIMESCALE_MIGRATE_HYPERTABLE_WITH_FRESH_TABLE", False): From fe996b8348c2d10b7bc6e11a5da84039663b391a Mon Sep 17 00:00:00 2001 From: Cd Chen Date: Wed, 7 May 2025 05:22:17 +0800 Subject: [PATCH 3/4] Add get primary key constraint from information_schema.table_constraints table. --- timescale/db/backends/postgresql/schema.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/timescale/db/backends/postgresql/schema.py b/timescale/db/backends/postgresql/schema.py index 5e92526..8734186 100644 --- a/timescale/db/backends/postgresql/schema.py +++ b/timescale/db/backends/postgresql/schema.py @@ -85,12 +85,22 @@ def _drop_primary_key(self, model): """ db_table = model._meta.db_table table = self.quote_name(db_table) - pkey_length = self.connection.ops.max_name_length() - pkey = self.quote_name(f'{db_table[:pkey_length - 5]}_pkey') - - sql = self.sql_drop_primary_key.format(table=table, pkey=pkey) - - self.execute(sql) + ## cdchen-20250507: Get primary key from information_schema.table_constraints + # + # pkey_length = self.connection.ops.max_name_length() + # pkey = self.quote_name(f'{db_table[:pkey_length - 5]}_pkey') + # + # sql = self.sql_drop_primary_key.format(table=table, pkey=pkey) + # + # self.execute(sql) + with self.connection.cursor() as cursor: + sql = "SELECT constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'PRIMARY' AND table_name = '{table}' ".format(table=table) + cursor.execute(sql) + row = cursor.fetchone() + if row: + pkey = row[0] + sql = self.sql_drop_primary_key.format(table=table, pkey=pkey) + cursor.execute(sql) def _create_hypertable(self, model, field, should_migrate=False): """ From d0f13e8691009a286d9463373d3632e3224ef94a Mon Sep 17 00:00:00 2001 From: Cd Chen Date: Wed, 7 May 2025 10:40:25 +0800 Subject: [PATCH 4/4] Add `retain_primary_key` to TimescaleDateTimeField --- timescale/db/backends/postgresql/schema.py | 5 +++-- timescale/db/models/fields.py | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/timescale/db/backends/postgresql/schema.py b/timescale/db/backends/postgresql/schema.py index 8734186..5e56596 100644 --- a/timescale/db/backends/postgresql/schema.py +++ b/timescale/db/backends/postgresql/schema.py @@ -109,8 +109,9 @@ def _create_hypertable(self, model, field, should_migrate=False): # assert that the table is not already a hypertable self._assert_is_not_hypertable(model) - # drop primary key of the table - self._drop_primary_key(model) + # drop primary key of the table if not retain + if not field.retain_primary_key: + self._drop_primary_key(model) partition_column = self.quote_value(field.column) interval = self.quote_value(field.interval) diff --git a/timescale/db/models/fields.py b/timescale/db/models/fields.py index 2b35ab2..0023bc9 100644 --- a/timescale/db/models/fields.py +++ b/timescale/db/models/fields.py @@ -2,12 +2,15 @@ class TimescaleDateTimeField(DateTimeField): - def __init__(self, *args, interval, **kwargs): + def __init__(self, *args, interval, retain_primary_key=False, **kwargs): self.interval = interval + self.retain_primary_key = retain_primary_key super().__init__(*args, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() - kwargs['interval'] = self.interval - + kwargs.update({ + 'interval': self.interval, + 'retain_primary_key': self.retain_primary_key, + }) return name, path, args, kwargs \ No newline at end of file