infi.clickhouse_orm/README.rst

210 lines
7.6 KiB
ReStructuredText
Raw Normal View History

2016-06-26 15:11:23 +03:00
Overview
========
This project is simple ORM for working with the `ClickHouse database <https://clickhouse.yandex/>`_.
It allows you to define model classes whose instances can be written to the database and read from it.
Installation
============
To install infi.clickhouse_orm::
pip install infi.clickhouse_orm
Usage
=====
Defining Models
---------------
2016-06-28 11:49:43 +03:00
Models are defined in a way reminiscent of Django's ORM::
2016-06-26 15:11:23 +03:00
from infi.clickhouse_orm import models, fields, engines
class Person(models.Model):
first_name = fields.StringField()
last_name = fields.StringField()
birthday = fields.DateField()
height = fields.Float32Field()
engine = engines.MergeTree('birthday', ('first_name', 'last_name', 'birthday'))
2016-06-26 16:52:25 +03:00
It is possible to provide a default value for a field, instead of its "natural" default (empty string for string fields, zero for numeric fields etc.).
2016-06-26 15:11:23 +03:00
2016-06-26 16:52:25 +03:00
See below for the supported field types and table engines.
2016-06-26 15:11:23 +03:00
Using Models
------------
2016-06-28 11:49:43 +03:00
Once you have a model, you can create model instances::
2016-06-26 15:11:23 +03:00
>>> dan = Person(first_name='Dan', last_name='Schwartz')
>>> suzy = Person(first_name='Suzy', last_name='Jones')
>>> dan.first_name
u'Dan'
2016-06-26 16:52:25 +03:00
When values are assigned to model fields, they are immediately converted to their Pythonic data type.
2016-06-28 11:49:43 +03:00
In case the value is invalid, a ``ValueError`` is raised::
2016-06-26 15:11:23 +03:00
>>> suzy.birthday = '1980-01-17'
>>> suzy.birthday
datetime.date(1980, 1, 17)
>>> suzy.birthday = 0.5
ValueError: Invalid value for DateField - 0.5
>>> suzy.birthday = '1922-05-31'
ValueError: DateField out of range - 1922-05-31 is not between 1970-01-01 and 2038-01-19
2016-06-26 16:52:25 +03:00
Inserting to the Database
-------------------------
2016-06-28 11:49:43 +03:00
To write your instances to ClickHouse, you need a ``Database`` instance::
2016-06-26 15:11:23 +03:00
from infi.clickhouse_orm.database import Database
db = Database('my_test_db')
This automatically connects to http://localhost:8123 and creates a database called my_test_db, unless it already exists.
2016-06-28 11:49:43 +03:00
If necessary, you can specify a different database URL and optional credentials::
2016-06-26 15:11:23 +03:00
db = Database('my_test_db', db_url='http://192.168.1.1:8050', username='scott', password='tiger')
2016-06-28 11:49:43 +03:00
Using the ``Database`` instance you can create a table for your model, and insert instances to it::
2016-06-26 15:11:23 +03:00
db.create_table(Person)
db.insert([dan, suzy])
2016-06-26 16:52:25 +03:00
The ``insert`` method can take any iterable of model instances, but they all must belong to the same model class.
Reading from the Database
-------------------------
2016-06-28 11:49:43 +03:00
Loading model instances from the database is simple::
2016-06-26 16:52:25 +03:00
for person in db.select("SELECT * FROM my_test_db.person", model_class=Person):
print person.first_name, person.last_name
Do not include a ``FORMAT`` clause in the query, since the ORM automatically sets the format to ``TabSeparatedWithNamesAndTypes``.
2016-06-28 11:49:43 +03:00
It is possible to select only a subset of the columns, and the rest will receive their default values::
2016-06-26 15:11:23 +03:00
2016-06-26 16:52:25 +03:00
for person in db.select("SELECT first_name FROM my_test_db.person WHERE last_name='Smith'", model_class=Person):
print person.first_name
2016-06-26 15:11:23 +03:00
2016-06-26 18:04:46 +03:00
Ad-Hoc Models
*************
2016-06-26 16:52:25 +03:00
Specifying a model class is not required. In case you do not provide a model class, an ad-hoc class will
2016-06-28 11:49:43 +03:00
be defined based on the column names and types returned by the query::
2016-06-26 16:52:25 +03:00
for row in db.select("SELECT max(height) as max_height FROM my_test_db.person"):
print row.max_height
2016-06-26 18:04:46 +03:00
This is a very convenient feature that saves you the need to define a model for each query, while still letting
you work with Pythonic column values and an elegant syntax.
2016-06-26 16:52:25 +03:00
Counting
--------
2016-06-28 11:49:43 +03:00
The ``Database`` class also supports counting records easily::
2016-06-26 16:52:25 +03:00
>>> db.count(Person)
117
>>> db.count(Person, conditions="height > 1.90")
6
2016-06-26 15:11:23 +03:00
2016-07-03 15:39:08 +03:00
Pagination
----------
It is possible to paginate through model instances::
>>> order_by = 'first_name, last_name'
>>> page = db.paginate(Person, order_by, page_num=1, page_size=100)
>>> print page.number_of_objects
2507
>>> print page.pages_total
251
>>> for person in page.objects:
>>> # do something
The ``paginate`` method returns a ``namedtuple`` containing the following fields:
- ``objects`` - the list of objects in this page
- ``number_of_objects`` - total number of objects in all pages
- ``pages_total`` - total number of pages
- ``number`` - the page number
- ``page_size`` - the number of objects per page
You can optionally pass conditions to the query::
>>> page = db.paginate(Person, order_by, page_num=1, page_size=100, conditions='height > 1.90')
2016-07-04 11:42:30 +03:00
Note that ``order_by`` must be chosen so that the ordering is unique, otherwise there might be
inconsistencies in the pagination (such as an instance that appears on two different pages).
2016-06-26 15:11:23 +03:00
Field Types
-----------
Currently the following field types are supported:
2016-06-26 17:56:45 +03:00
============= ======== ================= ===================================================
Class DB Type Pythonic Type Comments
============= ======== ================= ===================================================
StringField String unicode Encoded as UTF-8 when written to ClickHouse
DateField Date datetime.date Range 1970-01-01 to 2038-01-19
DateTimeField DateTime datetime.datetime Minimal value is 1970-01-01 00:00:00; Always in UTC
Int8Field Int8 int Range -128 to 127
2016-06-26 18:04:46 +03:00
Int16Field Int16 int Range -32768 to 32767
2016-06-26 17:56:45 +03:00
Int32Field Int32 int Range -2147483648 to 2147483647
Int64Field Int64 int/long Range -9223372036854775808 to 9223372036854775807
UInt8Field UInt8 int Range 0 to 255
UInt16Field UInt16 int Range 0 to 65535
UInt32Field UInt32 int Range 0 to 4294967295
UInt64Field UInt64 int/long Range 0 to 18446744073709551615
Float32Field Float32 float
Float64Field Float64 float
============= ======== ================= ===================================================
2016-06-26 15:11:23 +03:00
2016-06-26 16:52:25 +03:00
Table Engines
-------------
2016-06-26 17:56:45 +03:00
Each model must have an engine instance, used when creating the table in ClickHouse.
2016-06-26 15:11:23 +03:00
2016-06-28 11:49:43 +03:00
To define a ``MergeTree`` engine, supply the date column name and the names (or expressions) for the key columns::
2016-06-26 17:56:45 +03:00
engine = engines.MergeTree('EventDate', ('CounterID', 'EventDate'))
2016-06-28 11:49:43 +03:00
You may also provide a sampling expression::
2016-06-26 17:56:45 +03:00
engine = engines.MergeTree('EventDate', ('CounterID', 'EventDate'), sampling_expr='intHash32(UserID)')
2016-06-28 11:49:43 +03:00
A ``CollapsingMergeTree`` engine is defined in a similar manner, but requires also a sign column::
2016-06-26 17:56:45 +03:00
engine = engines.CollapsingMergeTree('EventDate', ('CounterID', 'EventDate'), 'Sign')
2016-06-28 11:49:43 +03:00
For a ``SummingMergeTree`` you can optionally specify the summing columns::
2016-06-26 17:56:45 +03:00
engine = engines.SummingMergeTree('EventDate', ('OrderID', 'EventDate', 'BannerID'),
summing_cols=('Shows', 'Clicks', 'Cost'))
2016-06-26 18:04:46 +03:00
Data Replication
****************
2016-06-28 11:49:43 +03:00
Any of the above engines can be converted to a replicated engine (e.g. ``ReplicatedMergeTree``) by adding two parameters, ``replica_table_path`` and ``replica_name``::
2016-06-26 17:56:45 +03:00
engine = engines.MergeTree('EventDate', ('CounterID', 'EventDate'),
replica_table_path='/clickhouse/tables/{layer}-{shard}/hits',
replica_name='{replica}')
2016-06-26 15:11:23 +03:00
Development
===========
After cloning the project, run the following commands::
easy_install -U infi.projector
cd infi.clickhouse_orm
projector devenv build
To run the tests, ensure that the ClickHouse server is running on http://localhost:8123/ (this is the default), and run::
2016-07-03 15:39:08 +03:00
bin/nosetests