Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to integrate postgresql 10/11 declarative table partitioning (i.e. PARTITION BY clause) in a Django model?

PostgreSQL 10 introduces declarative table partitioning with the PARTITION BY clause, and I would like to use it to a Django model.

In principle all what I would need to do is introduce the PARTITION BY clause at the end of the CREATE TABLE statement that the Django ORM creates.

CREATE TABLE measurement (
    city_id         int not null,
    logdate         date not null,
    peaktemp        int,
    unitsales       int
) PARTITION BY RANGE (logdate);

Is it possible to insert this clause into the model? I thought that maybe there is a way to somehow append custom SQL to the query that the ORM generates, e.g. using the Meta:

class Measurement(models.Model):
    ...
    class Meta:
        append = "PARTITION BY RANGE (logdate)"

As far as I am concern the above is not possible. I have also look into the architect library, but it does not use the new PARTITION BY clause. Instead, it uses inheritance and triggers so the code does not suggest any way in which I could append the clause (neither it does for other databases, e.g. MySQL).

I have also though of customizing the migrations, by adding an ALTER TABLE... operation, e.g.:

operations = [
    migrations.RunSQL(
        "ALTER TABLE measurement PARTITION BY RANGE (logdate)",
    ),
] 

Unfortunately, the above (or similar) doesn't seem to be supported in PostgreSQL ALTER TABLE statement(at least not yet).

A final idea would be to retrieve the CREATE TABLE statement that the Django model generates, before sending the query, e.g. sql = Measurement.get_statement() where Measurement is the model. Then, I could append the PARTITION BY clause, and send the query directly. I couldn't find any method that returns the statement. I went through the Django create_model code and the sql is generated and directly send to the database, so it would not be easy to extract the statement from there.

Does anybody has a clue how this could be achieved in a way in which I can I still use the benefits of the Django ORM?

like image 636
toto_tico Avatar asked Nov 01 '18 23:11

toto_tico


People also ask

Does PostgreSQL support table partitioning?

PostgreSQL supports basic table partitioning. This section describes why and how to implement partitioning as part of your database design.

How do I partition an existing table in PostgreSQL?

You can use the ALTER TABLE… ADD PARTITION statement to add a partition to a table with a DEFAULT rule as long as there are no conflicting values between existing rows in the table and the values of the partition to be added.

How do I split a partition in PostgreSQL?

Use the ALTER TABLE… SPLIT PARTITION command to divide a single partition into two partitions, maintaining the partitioning of the original table in the newly created partitions, and redistributing the partition's contents between the new partitions.


1 Answers

An approach I suggest trying is to use a SQL capturing schema editor to collect the SQL necessary to perform the create_model. That's what powers the sqlmigrate feature by the way.

from django.db.migrations import CreateModel

class CreatePartitionedModel(CreateModel):
    def __init__(self, name, fields, partition_sql, **kwargs):
        self.partition_sql = partition_sql
        super().__init__(name, fields, **kwargs)

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        collector = type(schema_editor)(
            schema_editor.connection, collect_sql=True, atomic=False
        )
        with collector:
            super().database_forwards(
                app_label, collector, from_state, to_state
            )
        collected_sql = collector.collected_sql
        schema_editor.deferred_sql.extend(
            collector.deferred_sql
        )

        model = to_state.apps.get_model(app_label, self.name)
        create_table = 'CREATE TABLE %s' % schema_editor.quote_name(
            model._meta.db_table
        )
        for sql in collected_sql:
            if str(sql).startswith(create_table):
                sql = '%s PARTITION BY %s' % (sql.rstrip(';'), self.partition_sql)
            schema_editor.execute(sql)

From that point you should simply have to replace your makemigrations auto-generated CreateModel with a CreatePartitionedModel operation and make sure to specify partition_sql='RANGE (logdate)'.

like image 125
Simon Charette Avatar answered Oct 16 '22 07:10

Simon Charette