44
55
66class TimescaleSchemaEditor (PostGISSchemaEditor ):
7+ sql_is_hypertable = 'SELECT * FROM timescaledb_information.hypertables WHERE hypertable_name = {table}'
8+
9+ sql_assert_is_hypertable = (
10+ 'DO $do$ BEGIN '
11+ 'IF EXISTS ( '
12+ + sql_is_hypertable +
13+ ') '
14+ 'THEN NULL; '
15+ 'ELSE RAISE EXCEPTION {error_message}; '
16+ 'END IF;'
17+ 'END; $do$'
18+ )
19+ sql_assert_is_not_hypertable = (
20+ 'DO $do$ BEGIN '
21+ 'IF EXISTS ( '
22+ + sql_is_hypertable +
23+ ') '
24+ 'THEN RAISE EXCEPTION {error_message}; '
25+ 'ELSE NULL; '
26+ 'END IF;'
27+ 'END; $do$'
28+ )
29+
30+ sql_drop_primary_key = 'ALTER TABLE {table} DROP CONSTRAINT {pkey}'
31+
732 sql_add_hypertable = (
833 "SELECT create_hypertable("
934 "{table}, {partition_column}, "
10- "chunk_time_interval => interval {interval})"
35+ "chunk_time_interval => interval {interval}, "
36+ "migrate_data => {migrate})"
1137 )
1238
13- sql_drop_primary_key = (
14- 'ALTER TABLE {table} '
15- 'DROP CONSTRAINT {pkey}'
16- )
39+ sql_set_chunk_time_interval = 'SELECT set_chunk_time_interval({table}, interval {interval})'
1740
18- def drop_primary_key (self , model ):
41+ def _assert_is_hypertable (self , model ):
42+ """
43+ Assert if the table is a hyper table
44+ """
45+ table = self .quote_value (model ._meta .db_table )
46+ error_message = self .quote_value ("assert failed - " + table + " should be a hyper table" )
47+
48+ sql = self .sql_assert_is_hypertable .format (table = table , error_message = error_message )
49+ self .execute (sql )
50+
51+ def _assert_is_not_hypertable (self , model ):
52+ """
53+ Assert if the table is not a hyper table
54+ """
55+ table = self .quote_value (model ._meta .db_table )
56+ error_message = self .quote_value ("assert failed - " + table + " should not be a hyper table" )
57+
58+ sql = self .sql_assert_is_not_hypertable .format (table = table , error_message = error_message )
59+ self .execute (sql )
60+
61+ def _drop_primary_key (self , model ):
1962 """
2063 Hypertables can't partition if the primary key is not
2164 the partition column.
@@ -29,26 +72,70 @@ def drop_primary_key(self, model):
2972
3073 self .execute (sql )
3174
32- def create_hypertable (self , model , field ):
75+ def _create_hypertable (self , model , field , should_migrate = False ):
3376 """
3477 Create the hypertable with the partition column being the field.
3578 """
79+ # assert that the table is not already a hypertable
80+ self ._assert_is_not_hypertable (model )
81+
82+ # drop primary key of the table
83+ self ._drop_primary_key (model )
84+
3685 partition_column = self .quote_value (field .column )
3786 interval = self .quote_value (field .interval )
3887 table = self .quote_value (model ._meta .db_table )
88+ migrate = "true" if should_migrate else "false"
89+
90+ if should_migrate and getattr (settings , "TIMESCALE_MIGRATE_HYPERTABLE_WITH_FRESH_TABLE" , False ):
91+ # TODO migrate with fresh table [https://github.com/schlunsen/django-timescaledb/issues/16]
92+ raise NotImplementedError ()
93+ else :
94+ sql = self .sql_add_hypertable .format (
95+ table = table , partition_column = partition_column , interval = interval , migrate = migrate
96+ )
97+ self .execute (sql )
98+
99+ def _set_chunk_time_interval (self , model , field ):
100+ """
101+ Change time interval for hypertable
102+ """
103+ # assert if already a hypertable
104+ self ._assert_is_hypertable (model )
39105
40- sql = self .sql_add_hypertable .format (
41- table = table , partition_column = partition_column , interval = interval
42- )
106+ table = self .quote_value (model ._meta .db_table )
107+ interval = self .quote_value (field .interval )
43108
109+ sql = self .sql_set_chunk_time_interval .format (table = table , interval = interval )
44110 self .execute (sql )
45111
46112 def create_model (self , model ):
47113 super ().create_model (model )
48114
115+ # scan if any field is of instance `TimescaleDateTimeField`
49116 for field in model ._meta .local_fields :
50- if not isinstance (field , TimescaleDateTimeField ):
51- continue
117+ if isinstance (field , TimescaleDateTimeField ):
118+ # create hypertable, with the field as partition column
119+ self ._create_hypertable (model , field )
120+ break
121+
122+ def add_field (self , model , field ):
123+ super ().add_field (model , field )
124+
125+ # check if this field is type `TimescaleDateTimeField`
126+ if isinstance (field , TimescaleDateTimeField ):
127+ # migrate existing table to hypertable
128+ self ._create_hypertable (model , field , True )
129+
130+ def alter_field (self , model , old_field , new_field , strict = False ):
131+ super ().alter_field (model , old_field , new_field , strict )
52132
53- self .drop_primary_key (model )
54- self .create_hypertable (model , field )
133+ # check if old_field is not type `TimescaleDateTimeField` and new_field is
134+ if not isinstance (old_field , TimescaleDateTimeField ) and isinstance (new_field , TimescaleDateTimeField ):
135+ # migrate existing table to hypertable
136+ self ._create_hypertable (model , new_field , True )
137+ # check if old_field and new_field is type `TimescaleDateTimeField` and `interval` is changed
138+ elif isinstance (old_field , TimescaleDateTimeField ) and isinstance (new_field , TimescaleDateTimeField ) \
139+ and old_field .interval != new_field .interval :
140+ # change chunk-size
141+ self ._set_chunk_time_interval (model , new_field )
0 commit comments