/rome

Relational Object Mapping Extension for key/value stores. It is used to provide a NoSQL database backend to the Nova controller.

Primary LanguagePython

ROME (Relational Object Mapping Extension)

Introduction

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.

Installation

Grab the source code

First, execute following the following command in a shell:

git clone https://github.com/badock/rome.git

Python dependencies

ROME needs some python dependencies, run the follwing commands in a shell:

pip install itertools
pip install sqlalchemy
pip install riak

Python dependencies

The Rome project provides an installation script, which is supposed to be used like this:

python setup.py install

Running tests (to check if the environment is ready)

Launch riak (default implementation)

Execute the following command in a shell:

riak start

Launch tests

Execute the following command in a shell:

python execute_tests.py

Documentation

Integration in OpenStack

http://dropbox.jonathanpastor.fr/openstack_rome

Folder architecture

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

Declare an Entity class

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.

Create an entity object and save it in database

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 objects

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

Deleting objects

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

Joining tables

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)]]

Functions

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]