ROME (Relational Object Mapping Extension) is an extension for NoQSL databases, that made them compliant to relational algebra. In short terms, it enables to use NoSQL databases with an ORM like software, that is respecting the same interfaces as SQLAlchemy. This document gives instructions to install/configure/run ROME, and a technical documentation about how it works.
First, execute following the following command in a shell:
git clone https://github.com/badock/rome.git
ROME needs some python dependencies, run the follwing commands in a shell:
pip install itertools pip install sqlalchemy pip install riak
The Rome project provides an installation script, which is supposed to be used like this:
python setup.py install
Execute the following command in a shell:
riak start
Execute the following command in a shell:
python execute_tests.py
http://dropbox.jonathanpastor.fr/openstack_rome
Detailed project folders architecture:
Rome ├── lib ....................................... library files │ └── rome ....................................... folder containing ROME files │ ├── core ....................................... files of the core of ROME │ │ ├── dataformat ....................................... dataformat files (conversion to JSON) │ │ └── orm ....................................... object relational mapping files (query) │ ├── driver ....................................... folder containing database drivers │ │ └── riak ....................................... files related to the RIAK implementation │ └── engine ....................................... files used by the ROME engine └── test ....................................... files related to testing
To declare an entity class you will have to extend the lib.rome.core.models.Entity class. In the following example, I create an entity class that represents Dogs. In the current version, Entity classes are composed of attributes that follows SQLAlchemy types: this will be soon replaced by types provided by ROME, however it useful to garanty some kind of compatibility with SQLAlchemy, thus easing the integration of this driver with existing code from Nova controller.
import lib.rome.driver.database_driver as database_driver
from lib.rome.core.models import Entity
from lib.rome.core.models import global_scope
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Index, Integer, BigInteger, Enum, String, schema
from sqlalchemy.dialects.mysql import MEDIUMTEXT
from sqlalchemy import orm
from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float
BASE = declarative_base()
@global_scope
class Dog(BASE, Entity):
"""Represents a dog."""
__tablename__ = 'dogs'
id = Column(Integer, primary_key=True)
name = Column(String(255))
specy = Column(String(255))
In order to execute flawlessly the tutorial, please execute the following code:
from lib.rome.core.orm.query import Query
# Deleting existing dogs to not disturb the tutorial!
dogs = Query(Dog).all()
for dog in dogs:
dog.soft_delete()
/Library/Python/2.7/site-packages/riak-2.1.0-py2.7.egg/riak/security.py:32: UserWarning: Found OpenSSL 0.9.8y 5 Feb 2013 version, but expected at least OpenSSL 1.0.1g. Security may not support TLS 1.2.
Leveraging the class created above, I create a dog called Bobby who is proud descendant of Griffons. Once bobby is created, I persist it in database, so that it cannot be forgotten.
dogs_model = [{"name": "Bobby", "specy": "Griffon"},
{"name": "Rintintin", "specy": "Berger allemand"},
{"name": "Snoopy", "specy": "Beagle"}
]
for dog_model in dogs_model:
# Instanciation of a dog
dog = Dog()
# Setting dog's properties
dog.name = dog_model["name"]
dog.specy = dog_model["specy"]
# Saving the dog
dog.save()
During Bobby's insertion in the database, the ROME driver has outputed some information about its actions: first an ID has been given to Bobby, second the data representation (JSON) is displayed. Now that Bobby is in the database, we would like to find him.
Querying of objects follows the same steps as with SQLAlchemy: 1. import the Query class 2. create a query 3. execute the query
Indeed to find every dogs that are stored in the database:
from lib.rome.core.orm.query import Query
# "Select *" query
dogs = Query(Dog).all()
print("I may have found some dogs: %s" % (dogs))
# "Count *" query
dogs_count = Query(Dog).count()
print("There are %i dog(s) in the database" % (dogs_count))
# "Select * where X and Y" query
dog = Query(Dog).filter(Dog.name=="Bobby").filter_by(specy="Griffon").first()
print("I may have found one dog: %s" % (dog))
I may have found some dogs: [Lazy(Dog_1:dogs:0), Lazy(Dog_2:dogs:0), Lazy(Dog_3:dogs:-1)] There are 3 dog(s) in the database I may have found one dog: Lazy(Dog_1:dogs:0)
The previously executed queries returned a list of Lazy(None_1:dogs:-1) objects, but no instance of Dog.
print("Here are nice dogs with following specs:")
for dog in dogs:
print(" * name: %s, specy: %s" % (dog.name, dog.specy))
Here are nice dogs with following specs: * name: Bobby, specy: Griffon * name: Rintintin, specy: Berger allemand * name: Snoopy, specy: Beagle
This section will illustrate how an object that has been persisted in database can be deleted. With the current version of the driver, the object is not "physically" deleted, but its key is removed from the key index and made available for reuse. When the key is again used, the previous object paired with the key will be replaced by this key.
from lib.rome.core.orm.query import Query
# Check if Rintintin is in the database
rintintin_count = Query(Dog).filter(Dog.name=="Rintintin").count()
print("I have found %i Rintintin(s) in the database" % (rintintin_count))
# Find and Rintintin
rintintin = Query(Dog).filter(Dog.name=="Rintintin").first()
rintintin.soft_delete()
# Check if Rintintin is in the database
rintintin_count = Query(Dog).filter(Dog.name=="Rintintin").count()
print("I have found %i Rintintin(s) in the database" % (rintintin_count))
I have found 1 Rintintin(s) in the database I have found 0 Rintintin(s) in the database
To illustrate the joining of tables, let's first create a new table for species:
import lib.rome.driver.database_driver as database_driver
from lib.rome.core.models import Entity
from lib.rome.core.models import global_scope
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Index, Integer, BigInteger, Enum, String, schema
from sqlalchemy.dialects.mysql import MEDIUMTEXT
from sqlalchemy import orm
from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float
BASE = declarative_base()
@global_scope
class Specy(BASE, Entity):
"""Represents a specy."""
__tablename__ = 'species'
id = Column(Integer, primary_key=True)
name = Column(String(255))
from lib.rome.core.orm.query import Query
# Deleting existing species to not disturb the tutorial!
species = Query(Specy).all()
for specy in species:
specy.soft_delete()
And let's spawn some species:
species_model = [{"name": "Griffon"},
{"name": "Berger allemand"},
{"name": "Beagle"}
]
for specy_model in species_model:
# Instanciation of a specy
specy = Specy()
# Setting specy's properties
specy.name = specy_model["name"]
# Saving the specy
specy.save()
As the "specy" field in Dog correspond to the "name" field in Dog, let's try to join the two entity classes on these fields:
results = Query(Dog).join(Specy, Specy.name==Dog.specy).all()
print(results)
results = Query(Dog, Specy).filter(Specy.name==Dog.specy).all()
print(results)
[[Lazy(Dog_1:dogs:1), Lazy(Specy_1:species:1)], [Lazy(Dog_3:dogs:1), Lazy(Specy_3:species:1)]] [[Lazy(Dog_1:dogs:1), Lazy(Specy_1:species:1)], [Lazy(Dog_3:dogs:1), Lazy(Specy_3:species:1)]]
As in SQLAlchemy, it is possible to use SQL built in functions. At this moment only "count" and "sum" are available, however it is possible to add new functions. Here is an example:
from sqlalchemy.sql import func
result = Query(Dog, func.sum(Dog.id), func.count(Dog.id)).all()
for item in result:
print(item)
[Lazy(Dog_1:dogs:0), 4, 2] [Lazy(Dog_3:dogs:0), 4, 2]