Skip to content

Commit 49a9223

Browse files
committed
v0.1.4
1 parent dd8e560 commit 49a9223

12 files changed

+462
-2
lines changed

HISTORY.rst

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. :changelog:
2+
3+
History
4+
-------
5+
6+
0.1.4 (2013-06-01)
7+
~~~~~~~~~~~~~~~~~~
8+
9+
- Packaging fix
10+
11+
0.1.3 (2013-06-01)
12+
~~~~~~~~~~~~~~~~~~
13+
14+
- Packaging fix
15+
16+
0.1.2 (2013-06-01)
17+
~~~~~~~~~~~~~~~~~~
18+
19+
- Packaging fix
20+
21+
0.1.1 (2013-06-01)
22+
~~~~~~~~~~~~~~~~~~
23+
24+
- Packaging fix
25+
26+
0.1.0 (2013-06-01)
27+
~~~~~~~~~~~~~~~~~~
28+
29+
- Initial release

LICENSE

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2013 Max Tepkeev
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification,
5+
are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice,
8+
this list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
14+
3. Neither the name of this project nor the names of its contributors may be
15+
used to endorse or promote products derived from this software without
16+
specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MANIFEST.in

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include README.rst
2+
include HISTORY.rst
3+
include LICENSE

README.md

-2
This file was deleted.

README.rst

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
Django DB Parti
2+
===============
3+
4+
Django DB Parti is a package for Django which aim is to make table partitioning on the fly.
5+
Partitioning is a division of one large table into smaller tables which represent that table.
6+
Partitioning is usually done for manageability, performance or availability reasons. If you
7+
are unsure whether you need partitioning or not, then you almost certainly don't need it.
8+
9+
Requirements
10+
------------
11+
12+
* Django 1.5 (http://www.djangoproject.com) (may work with older versions, but untested)
13+
14+
Installation
15+
------------
16+
17+
From pypi_::
18+
19+
$ pip install django-db-parti
20+
21+
or clone from github_::
22+
23+
$ git clone git://github.com/maxtepkeev/django-db-parti.git
24+
25+
Configuration
26+
-------------
27+
28+
Add dbparti to PYTHONPATH and installed applications::
29+
30+
INSTALLED_APPS = (
31+
...
32+
'dbparti'
33+
)
34+
35+
Create the model as usual which will represent the partitioned table, if you are using South
36+
for migrations, you can also create the model as usual. No additional steps required. After that
37+
we need to make a few changes to the model:
38+
39+
\1) In models.py add the following import statement at the top of the file::
40+
41+
from dbparti.models import Partitionable
42+
43+
\2) Make your model to inherit from Partitionable, to do that change::
44+
45+
class YourModelName(models.Model):
46+
47+
to::
48+
49+
class YourModelName(Partitionable):
50+
51+
\3) Optionally add a Meta class to your model with a few settings (or if you already have a Meta class change it as the following)::
52+
53+
class Meta(Partitionable.Meta):
54+
partition_range = 'month'
55+
partition_column = 'partdate'
56+
57+
That's it! Easy right?! Now a few words about what we just did. We made our model to inherit from Partitionable, also we
58+
used "month" as partition range and "partdate" as partition column, that means that from now on, a new partition will be
59+
created every month and a value from partdate column will be used for that. You can also customize how data from that model
60+
will be displayed in the Django admin interface, for that you need to do the following:
61+
62+
\1) In admin.py add the following import statement at the top of the file::
63+
64+
from dbparti.admin import PartitionableAdmin
65+
66+
\2) Create admin model as usual and then change::
67+
68+
class YourAdminModelName(admin.ModelAdmin):
69+
70+
to::
71+
72+
class YourAdminModelName(PartitionableAdmin):
73+
74+
\3) Optionally add a setting which tells how records are displayed in Django admin interface (more on that below)::
75+
76+
partition_show = 'all'
77+
78+
Available settings
79+
------------------
80+
81+
Model settings:
82+
~~~~~~~~~~~~~~~
83+
84+
All model settings are done inside model's Meta class which should inherit from Partitionable.Meta
85+
86+
``partition_range`` - how often a new partition will be created, currently accepts the following:
87+
88+
* week
89+
* month (default)
90+
91+
``partition_column`` - column name, which value will be used to determine which partition record belongs to:
92+
93+
* partdate (default)
94+
95+
ModelAdmin settings:
96+
~~~~~~~~~~~~~~~~~~~~
97+
98+
All model admin settings are done inside model admin class itself
99+
100+
``partition_show`` - data from which partition will be shown in Django admin, the following values are possible:
101+
102+
* all (default)
103+
* current
104+
* previous
105+
106+
Example
107+
-------
108+
109+
Let's imagine that we would like to create a table for storing log files. Without partitioning our table would have
110+
millions of rows very soon and as the table grows performance will become slower. With partitioning we can tell database
111+
that we want a new table to be created every month and that we will use a value from partdate to determine to which partition
112+
every new record belongs to. To be more specific let's call our table "logdata", it will have only 3 columns: id, content and
113+
logdate. Now when we insert the following record: id='1', content='blablabla', logdate='2013-05-20', this record will be
114+
inserted not to our "logdata" table but to the "logdata_y2013m05", then if we insert another record like that: id='2',
115+
content='yadayadayada', logdate='2013-07-16' it will be inserted to the table "logdata_y2013m07" BUT the great thing about
116+
all of that is that you are doing your inserts/updates/selects to the table "logdata"! Again, your are working with the table
117+
"logdata" as usual and you don't may even know that actually your data is stored in a lot of different tables, everything is
118+
done for you automatically at the database level, isn't that cool ?!
119+
120+
Backends
121+
--------
122+
123+
Django DB Parti is designed in a modular way, so new db backends can be added easily, currently the following backends are available:
124+
125+
* postgresql
126+
127+
Limitations
128+
-----------
129+
130+
Currently partitioning is only possible on a date basis, so you can't partition for example by ZIP code or something else. Other
131+
partitioning options will be added in next releases.
132+
133+
Contact & Support
134+
-----------------
135+
136+
I will be glad to get your feedback, pull requests, issues, whatever. Feel free to contact me for any questions.
137+
138+
Copyright & License
139+
-------------------
140+
141+
``django-db-parti`` is protected by BSD licence.
142+
143+
.. _pypi: https://pypi.python.org/pypi/django-db-parti
144+
.. _github: https://github.com/maxtepkeev/django-db-parti

dbparti/__init__.py

Whitespace-only changes.

dbparti/admin.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from django.contrib import admin
2+
from dbparti.utilities import DateTimeMixin
3+
4+
5+
class PartitionableAdmin(DateTimeMixin, admin.ModelAdmin):
6+
partition_show = 'all'
7+
8+
def __init__(self, *args, **kwargs):
9+
admin.ModelAdmin.__init__(self, *args, **kwargs)
10+
DateTimeMixin.__init__(
11+
self,
12+
self.partition_show,
13+
self.opts.partition_range,
14+
None,
15+
self.opts.get_field(self.opts.partition_column).get_internal_type(),
16+
)
17+
18+
def queryset(self, request):
19+
fday, lday = self.get_partition_show_period(self.get_datetime_string('date'))
20+
qs = super(PartitionableAdmin, self).queryset(request)
21+
22+
if fday is None and lday is None:
23+
qs = qs.all()
24+
else:
25+
qs = qs.filter(
26+
**{self.opts.partition_column + '__gte': fday, self.opts.partition_column + '__lte': lday}
27+
)
28+
29+
return qs

dbparti/backends/__init__.py

Whitespace-only changes.

dbparti/backends/postgresql.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from django.db import connection, transaction
2+
3+
4+
class Postgresql(object):
5+
def __init__(self, table, partition_column):
6+
self.table = table
7+
self.cursor = connection.cursor()
8+
self.partition_column = partition_column
9+
10+
def partition_exists(self, partition_name):
11+
self.cursor.execute('SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=%s)',
12+
(self.table + partition_name,))
13+
return self.cursor.fetchone()[0]
14+
15+
def create_partition(self, partition_name, datetype, fday, lday):
16+
self.cursor.execute('''
17+
-- We need to create a table first
18+
CREATE TABLE {child_table} (
19+
CHECK ( {partition_column} >= {datetype} '{fday}' AND {partition_column} <= {datetype} '{lday}' )
20+
) INHERITS ({parent_table});
21+
22+
-- Then we create an index to speed up things a little
23+
CREATE INDEX {child_table}_{partition_column} ON {child_table} ({partition_column});
24+
25+
-- Now we need to create a before insert function
26+
CREATE OR REPLACE FUNCTION {parent_table}_insert_child()
27+
RETURNS TRIGGER AS $$
28+
BEGIN
29+
INSERT INTO {child_table} VALUES (NEW.*);
30+
RETURN NEW;
31+
END;
32+
$$ LANGUAGE plpgsql;
33+
34+
-- Then we create a trigger which calls the before insert function
35+
DO $$
36+
BEGIN
37+
IF NOT EXISTS(
38+
SELECT 1
39+
FROM information_schema.triggers
40+
WHERE event_object_table = '{parent_table}'
41+
AND trigger_name = 'before_insert_{parent_table}_trigger'
42+
) THEN
43+
CREATE TRIGGER before_insert_{parent_table}_trigger
44+
BEFORE INSERT ON {parent_table}
45+
FOR EACH ROW EXECUTE PROCEDURE {parent_table}_insert_child();
46+
END IF;
47+
END $$;
48+
49+
-- Then we create a function to delete duplicate row from the master table after insert
50+
CREATE OR REPLACE FUNCTION {parent_table}_delete_master()
51+
RETURNS TRIGGER AS $$
52+
BEGIN
53+
DELETE FROM ONLY {parent_table} WHERE id = NEW.id;
54+
RETURN NEW;
55+
END;
56+
$$ LANGUAGE plpgsql;
57+
58+
-- Lastly we create the after insert trigger that calls the after insert function
59+
DO $$
60+
BEGIN
61+
IF NOT EXISTS(
62+
SELECT 1
63+
FROM information_schema.triggers
64+
WHERE event_object_table = '{parent_table}'
65+
AND trigger_name = 'after_insert_{parent_table}_trigger'
66+
) THEN
67+
CREATE TRIGGER after_insert_{parent_table}_trigger
68+
AFTER INSERT ON {parent_table}
69+
FOR EACH ROW EXECUTE PROCEDURE {parent_table}_delete_master();
70+
END IF;
71+
END $$;
72+
'''.format(
73+
child_table=self.table + partition_name,
74+
parent_table=self.table,
75+
partition_column=self.partition_column,
76+
fday=fday,
77+
lday=lday,
78+
datetype='DATE' if datetype == 'date' else 'TIMESTAMP',
79+
))
80+
81+
transaction.commit_unless_managed()

dbparti/models.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.db import models, connection
2+
from dbparti.utilities import DateTimeMixin
3+
4+
5+
models.options.DEFAULT_NAMES += ('partition_range', 'partition_column')
6+
7+
class Partitionable(DateTimeMixin, models.Model):
8+
def __init__(self, *args, **kwargs):
9+
models.Model.__init__(self, *args, **kwargs)
10+
DateTimeMixin.__init__(
11+
self,
12+
False,
13+
self._meta.partition_range,
14+
getattr(self, self._meta.partition_column),
15+
self._meta.get_field(self._meta.partition_column).get_internal_type(),
16+
)
17+
18+
self.db = getattr(__import__('dbparti.backends.' + connection.vendor,
19+
fromlist=[connection.vendor.capitalize()]), connection.vendor.capitalize())(
20+
self._meta.db_table, self._meta.partition_column)
21+
22+
def save(self, *args, **kwargs):
23+
fday, lday = self.get_partition_range_period(self.get_datetime_string('date'))
24+
25+
if not self.db.partition_exists(self.get_partition_name()):
26+
self.db.create_partition(self.get_partition_name(), self.get_datetype(), fday, lday)
27+
28+
super(Partitionable, self).save(*args, **kwargs)
29+
30+
class Meta:
31+
abstract = True
32+
partition_range = 'month'
33+
partition_column = 'partdate'

0 commit comments

Comments
 (0)