class IOperator(Interface):
    """
    I provide functions for doing operations.  My methods are suitable for
    use as the function in L{IRunner.runInteraction}.
    """


    def insert(cursor, obj):
        """
        Insert the ORM object into the database, update the object's attributes
        and return the object.
        """


    def query(cursor, query):
        """
        XXX
        """


    def refresh(cursor, obj):
        """
        XXX
        """


    def update(cursor, obj):
        """
        XXX
        """


    def delete(cursor, obj):
        """
        XXX
        """ class Int(Property):
    

    def _validate(self, prop, obj, value):
        if value is None:
            return value
        if type(value) not in (int, long):
            raise TypeError('%r must be an integer, not %r' % (prop, value))
        return value class String(Property):


    def _validate(self, prop, obj, value):
        if type(value) not in (type(None), str):
            raise TypeError('%r must be a str, not %r' % (prop, value))
        return value class Unicode(Property):


    def _validate(self, prop, obj, value):
        if type(value) not in (type(None), unicode):
            raise TypeError('%r must be a unicode, not %r' % (prop, value))
        return value class Date(Property):


    def _validate(self, prop, obj, value):
        if type(value) not in (type(None), date):
            raise TypeError('%r must be a date, not %r' % (prop, value))
        return value class DateTime(Property):


    def _validate(self, prop, obj, value):
        if type(value) not in (type(None), datetime):
            raise TypeError('%r must be a datetime, not %r' % (prop, value))
        return value class Bool(Property):


    def _validate(self, prop, obj, value):
        if value is None:
            return None
        if type(value) not in (bool, int):
            raise TypeError('%r must be a boolean, not %r' % (prop, value))
        return bool(value) class Query(object):
    """
    I am a query for a set of objects.  Pass me to L{IOperator.query}.
    """


    def __init__(self, select, *constraints, **kwargs):
        """
        @param select: Class(es) to return in the result.  This may either be
            a single class or a list/tuple of classes.
        @param constraints: A L{Comparison} or other compileable expression.

        @param kwargs:
            joins
        """
        if type(select) not in (list, tuple):
            select = (select,)
        self.select = select
        if constraints:
            self.constraints = And(*constraints)
        else:
            self.constraints = None
        self.joins = kwargs.pop('joins', None) or []
        self._classes = []
        self._props = []
        self._process()


    def _process(self):
        self._classes = []
        self._props = []
        for item in self.select:
            info = classInfo(item)
            keys = sorted(info.attributes.values())
            self._props.extend(keys)
            self._classes.append(item)


    def properties(self):
        """
        Get a tuple of the Properties that will be returned by the query.
        """
        return tuple(self._props)


    def classes(self):
        """
        Return a list of classes involved in the query.
        """
        return self._classes


    def find(self, select, constraints=None, joins=None):
        """
        Search for another kind of object with additional constraints.
        """
        all_constraints = [x for x in [self.constraints, constraints] if x]
        joins = joins or []
        return Query(select, *all_constraints, joins=self.joins + joins) class Eq(Comparison):
    op = '=' class And(LogicalBinaryOp):
    join = ' AND ' class LeftJoin(Join):

    oper = 'LEFT JOIN' class NotFound(Error):
    pass def ormHandle(pool):
    operator = None
    if pool.db_scheme == 'sqlite':
        from norm.sqlite import SqliteOperator
        operator = SqliteOperator()
    elif pool.db_scheme == 'postgres':
        from norm.postgres import PostgresOperator
        operator = PostgresOperator()
    return ORMHandle(pool, operator)
from twisted.internet import defer
from zope.interface.verify import verifyObject
from datetime import datetime, date
from norm.interface import IOperator
from norm.orm.props import Int, String, Unicode, Date, DateTime, Bool
from norm.orm.expr import Query, Eq, And, LeftJoin
from norm.orm.error import NotFound
from norm import ormHandle
        self.assertEqual(empty.name, None)
        self.assertEqual(empty.uni, None)
        self.assertEqual(empty.date, None)
        self.assertEqual(empty.dtime, None)
        self.assertEqual(empty.mybool, None)


    @defer.inlineCallbacks
    def test_insert_values(self):
        """
        You can insert into the database with some values
        """
        oper = yield self.getOperator()
        pool = yield self.getPool()
        empty = Empty()
        empty.name = 'foo'
        empty.uni = u'something'
        empty.date = date(2000, 1, 1)
        empty.dtime = datetime(2000, 1, 1, 12, 23, 22)
        empty.mybool = True

        yield pool.runInteraction(oper.insert, empty)
        self.assertNotEqual(empty.id, None)
        self.assertEqual(empty.name, 'foo')
        self.assertEqual(empty.uni, u'something')
        self.assertEqual(empty.date, date(2000, 1, 1))
        self.assertEqual(empty.dtime, datetime(2000, 1, 1, 12, 23, 22))
        self.assertEqual(empty.mybool, True)


    @defer.inlineCallbacks
    def test_insert_defaults(self):
        """
        After inserting, the object should have the default values from the
        database on it.
        """
        oper = yield self.getOperator()
        pool = yield self.getPool()
        defs = Defaults()

        yield pool.runInteraction(oper.insert, defs)
        self.assertNotEqual(defs.id, None)
        self.assertEqual(defs.name, 'hey')
        self.assertEqual(defs.uni, u'ho')
        self.assertEqual(defs.date, date(2001, 1, 1))
        self.assertEqual(defs.dtime, datetime(2001, 1, 1, 12, 22, 32))
        self.assertEqual(defs.mybool, True)


    @defer.inlineCallbacks
    def test_insert_pythonOverrideDefaults(self):
        """
        Explicitly-set python values should override any database defaults
        """
        oper = yield self.getOperator()
        pool = yield self.getPool()
        defs = Defaults()

        defs.name = 'something'
        defs.mybool = False
        yield pool.runInteraction(oper.insert, defs)
        self.assertEqual(defs.mybool, False)
        self.assertEqual(defs.name, 'something')


    @defer.inlineCallbacks
    def test_insert_binary(self):
        """
        Should handle binary data okay
        """
        oper = yield self.getOperator()
        pool = yield self.getPool()
        empty = Empty()
        empty.name = '\x00\x01\x02hey\x00'

        yield pool.runInteraction(oper.insert, empty)
        self.assertEqual(empty.name, '\x00\x01\x02hey\x00')


    @defer.inlineCallbacks
    def test_query_basic(self):
        """
        A basic query should work
        """
        oper = yield self.getOperator()
        pool = yield self.getPool()
        
        e1 = Empty()
        e1.name = '1'
        yield pool.runInteraction(oper.insert, e1)

        e2 = Empty()
        e2.name = '2'
        yield pool.runInteraction(oper.insert, e2)

        items = yield pool.runInteraction(oper.query, Query(Empty))
        self.assertEqual(len(items), 2)
        items = sorted(items, key=lambda x:x.name)
        
        self.assertTrue(isinstance(items[0], Empty))
        self.assertEqual(items[0].name, '1')

        self.assertTrue(isinstance(items[1], Empty))
        self.assertEqual(items[1].name, '2')


    @defer.inlineCallbacks
    def test_query_Eq(self):
        oper = yield self.getOperator()
        pool = yield self.getPool()

        e1 = Empty()
        e1.name = '1'
        yield pool.runInteraction(oper.insert, e1)

        e2 = Empty()
        e2.name = '2'
        yield pool.runInteraction(oper.insert, e2)

        items = yield pool.runInteraction(oper.query,

                                          Query(Empty, Eq(Empty.id, e1.id)))