########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Drivers/SQL.py,v 1.19 2005/02/12 22:42:45 mbrown Exp $
"""
Routines for RDF drivers that use ANSI SQL databases (MySQL, Postgres, maybe others)

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import sys, codecs, cPickle, cStringIO

from Ft.Lib.DbUtil import EscapeQuotes
from Ft.Rdf import Statement, Model, OBJECT_TYPE_UNKNOWN
from Ft.Rdf.Drivers import DataBaseExceptions

enc, dec, srdr, swtr = codecs.lookup('utf-8')
dec_utf8 = lambda x: dec(x)[0]

_escape = EscapeQuotes
# could be None
EscapeQuotes = lambda s: _escape(isinstance(s or '', unicode) and (s or '') or dec_utf8(s and str(s) or ''))

class Commands:
    ADD = 10
    REMOVE = 11
    COMPLETE = 12
    CONTAINS = 13
    SIZE = 14
    SIZE_ALL = 15

    BIND = 20
    UNBIND = 21
    LOOKUP = 22
    KEYS = 23
    HAS_KEY = 24

    SUBJECT_LIST = 30
    SUBJECT_LIST_SCOPED = 32
    PREDICATE_LIST = 34
    PREDICATE_LIST_SCOPED = 36
    OBJECT_LIST = 38
    OBJECT_LIST_SCOPED = 40
    RESOURCE_OBJECT_LIST = 42
    RESOURCE_OBJECT_LIST_SCOPED = 44
    IS_RESOURCE = 46

    BUILD_SUBJS_FROM_PREDS_AND_OBJ = 100

    BUILD_OBJS_FROM_SUB_AND_PREDS = 102
    BUILD_OBJS_FROM_SUB_AND_PREDS_SCOPED = 104
    BUILD_OBJS_FROM_SUBS_AND_PRED = 106
    BUILD_OBJS_FROM_SUBS_AND_PRED_SCOPED = 108
    BUILD_NON_DISTINCT_OBJS_FROM_SUB_AND_PREDS = 110


    BUILD_SUBJS_FROM_PRED_AND_OBJS = 112
    BUILD_SUBJS_FROM_PRED_AND_OBJS_SCOPED = 114



class SqlAdapter:
    def __init__(self, sqlCommands, comparisons,modelName):
        self._sql = sqlCommands
        self._cmp = comparisons
        self._db = None
        self._modelName = modelName
        return

    def begin(self):
        raise NotImplementedError('%s should override this' %
                                  self.__class__.__name__)

    def commit(self):
        raise NotImplementedError('%s should override this' %
                                  self.__class__.__name__)

    def rollback(self):
        raise NotImplementedError('%s should override this' %
                                  self.__class__.__name__)

    def add(self, statements):
        # Takes a list of tuples [(s, p, o, stmtUri, srcUri, otype), ...]
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        for s in statements:
            self._sql[Commands.ADD].execute(self._db,
                                            subject=EscapeQuotes(s[0]),
                                            predicate=EscapeQuotes(s[1]),
                                            object=EscapeQuotes(s[2]),
                                            statementUri=EscapeQuotes(s[3]),
                                            scope=EscapeQuotes(s[4]),
                                            otype=EscapeQuotes(s[5]),
                                            modelName=self._modelName,
                                            )
        return

    def remove(self, statements):
        for s in statements:
            self.removePattern(s[0], s[1], s[2], s[3], s[4], {})

    def removePattern(self, subject, predicate, object, statementUri,
                      scope, flags):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        subject = EscapeQuotes(subject)
        predicate = EscapeQuotes(predicate)
        object = EscapeQuotes(object)
        statementUri = EscapeQuotes(statementUri)
        scope = EscapeQuotes(scope)

        # We can use an emtpy string check because of EscapeQuotes()
        command = self._sql[(Commands.REMOVE,
                             subject != '',
                             predicate != '',
                             object != '',
                             statementUri != '',
                             scope != '',
                             )]

        command.execute(self._db,
                        subject=subject,
                        predicate=predicate,
                        object=object,
                        statementUri=statementUri,
                        scope=scope,
                        subjectOp=self._cmp[flags.get('subjectFlags')],
                        predicateOp=self._cmp[flags.get('predicateFlags')],
                        objectOp=self._cmp[flags.get('objectFlags')],
                        statementUriOp=self._cmp[flags.get('statementUriFlags')],
                        scopeOp=self._cmp[flags.get('scopeFlags')],
                        modelName = self._modelName,
                        )
        return

    def complete(self, subject, predicate, object, statementUri, scope,
                 flags):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        subject = EscapeQuotes(subject)
        predicate = EscapeQuotes(predicate)
        object = EscapeQuotes(object)
        statementUri = EscapeQuotes(statementUri)
        scope = EscapeQuotes(scope)

        # We can use an emtpy string check because of EscapeQuotes()
        command = self._sql[(Commands.COMPLETE,
                             subject != '',
                             predicate != '',
                             object != '',
                             statementUri != '',
                             scope != '',
                             )]

        result = command.query(self._db,
                               subject=subject,
                               predicate=predicate,
                               object=object,
                               statementUri=statementUri,
                               scope=scope,
                               subjectOp=self._cmp[flags.get('subjectFlags')],
                               predicateOp=self._cmp[flags.get('predicateFlags')],
                               objectOp=self._cmp[flags.get('objectFlags')],
                               statementUriOp=self._cmp[flags.get('statementUriFlags')],
                               scopeOp=self._cmp[flags.get('scopeFlags')],
                               modelName = self._modelName,
                               )
        return [ (dec_utf8(x[0]), dec_utf8(x[1]), dec_utf8(x[2]),
                  dec_utf8(x[3]), dec_utf8(x[4]), x[5])
                 for x in (result or []) ]

    def size(self, scope):
        if not self._db:
            raise DataBaseExceptions.NoTransaction
        if scope:
            uri = EscapeQuotes(scope)
            result = self._sql[Commands.SIZE].query(self._db, scope=uri,modelName=self._modelName)
        else:
            result = self._sql[Commands.SIZE_ALL].query(self._db,modelName = self._modelName)
        return result and result[0][0] or 0

    def contains(self, subject, predicate, object, statementUri, scope, flags):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        subject = EscapeQuotes(subject)
        predicate = EscapeQuotes(predicate)
        object = EscapeQuotes(object)
        statementUri = EscapeQuotes(statementUri)
        scope = EscapeQuotes(scope)

        # We can use an emtpy string check because of EscapeQuotes()
        command = self._sql[(Commands.CONTAINS,
                             subject != '',
                             predicate != '',
                             object != '',
                             statementUri != '',
                             scope != '',
                             )]

        result = command.query(self._db,
                               subject=subject,
                               predicate=predicate,
                               object=object,
                               statementUri=statementUri,
                               scope=scope,
                               subjectOp=self._cmp[flags.get('subjectFlags')],
                               predicateOp=self._cmp[flags.get('predicateFlags')],
                               objectOp=self._cmp[flags.get('objectFlags')],
                               statementUriOp=self._cmp[flags.get('statementUriFlags')],
                               scopeOp=self._cmp[flags.get('scopeFlags')],
                               modelName = self._modelName,
                               )

        return result and result[0][0] > 0 or 0

    def bind(self, object, name, scope):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        self._sql[Commands.BIND].execute(self._db,
                                         object=EscapeQuotes(cPickle.dumps(object)),
                                         name=EscapeQuotes(name),
                                         scope=EscapeQuotes(scope),
                                         modelName = self._modelName,
                                         )
        return

    def unbind(self, name, scope):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        self._sql[Commands.UNBIND].execute(self._db,
                                           name=EscapeQuotes(name),
                                           scope=EscapeQuotes(scope),
                                           modelName = self._modelName,
                                           )
        return

    def lookup(self, name, scope):
        if not self._db:
            raise DataBaseExceptions.NoTransaction

        result = self._sql[Commands.LOOKUP].query(self._db,
                                                  name=EscapeQuotes(name),
                                                  scope=EscapeQuotes(scope),
                                                  modelName = self._modelName,
                                                  )
        return result and cPickle.loads(result[0][0]) or None

    def keys(self, scope):
        result = self._sql[Commands.KEYS].query(self._db,
                                                scope=EscapeQuotes(scope),
                                                modelName = self._modelName,
                                                )
        return map(lambda x: x[0], result or [])

    def has_key(self, name, scope):
        result = self._sql[Commands.HAS_KEY].query(self._db,
                                                   name=EscapeQuotes(name),
                                                   scope=EscapeQuotes(scope),
                                                   modelName = self._modelName,
                                                   )
        return result and int(result[0][0]) or 0

    def subjectsFromPredsAndObj(self, predicates, object, scope):
        if object is not None:
            cmd = self._sql[Commands.BUILD_SUBJS_FROM_PREDS_AND_OBJ](
                object=EscapeQuotes(object),
                predicates=map(EscapeQuotes, predicates),
                modelName=self._modelName
                )
            results = cmd.query(self._db)
        else:
            results = self._sql[Commands.OBJECT_LIST].query(
                self._db,
                modelName=self._modelName
                )
        return map(lambda x: dec_utf8(x[0]), results or [])

    def subjectsFromPredAndObjs(self, predicate, objects, scope):
        """
        Get a list of resources with the given predicate and object
        """
        if predicate is not None:
            if scope:
                cmd = self._sql[Commands.BUILD_SUBJS_FROM_PRED_AND_OBJS_SCOPED](
                    predicate=EscapeQuotes(predicate),
                    objects=map(EscapeQuotes,objects),
                    modelName=self._modelName,
                    scope=EscapeQuotes(scope))
                results = cmd.query(self._db)
            else:
                cmd = self._sql[Commands.BUILD_SUBJS_FROM_PRED_AND_OBJS](
                    predicate=EscapeQuotes(predicate),
                    objects=map(EscapeQuotes,objects),
                    modelName=self._modelName)
                results = cmd.query(self._db, scope=scope)
        else:
            if scope:
                results = self._sql[Commands.SUBJECT_LIST_SCOPED].query(
                    self._db, modelName=self._modelName, scope=scope)
            else:
                results = self._sql[Commands.SUBJECT_LIST].query(
                    self._db, modelName=self._modelName)
        return map(lambda x: dec_utf8(x[0]), results or [])

    def objectsFromSubsAndPred(self, subjects, predicate, scope):
        """
        Get a list of obejcts with the given predicate and subjects
        """
        if predicate is not None:
            if scope:
                cmd = self._sql[Commands.BUILD_OBJS_FROM_SUBS_AND_PRED_SCOPED](
                    subjects=map(EscapeQuotes,subjects),
                    predicate=EscapeQuotes(predicate),
                    modelName=self._modelName, scope=scope)
                results = cmd.query(self._db)
            else:
                cmd = self._sql[Commands.BUILD_OBJS_FROM_SUBS_AND_PRED](
                    subjects=map(EscapeQuotes,subjects),
                    predicate=EscapeQuotes(predicate),
                    modelName=self._modelName)
                results = cmd.query(self._db)
        else:
            if scope:
                results = self._sql[Commands.OBJECT_LIST_SCOPED].query(
                    self._db, modelName=self._modelName, scope=scope)
            else:
                results = self._sql[Commands.OBJECT_LIST].query(
                    self._db, modelName=self._modelName)
        return map(lambda x: (dec_utf8(x[0]), x[1]), results or [])

    def objectsFromSubsAndPredNonDistinct(self, subjects, predicate, scope):
        """
        Get a list of *non-distinct* objects with the given predicate and subjects
        """
        if predicate is not None:
            if scope:
                cmd = self._sql[Commands.BUILD_NON_DISTINCT_OBJS_FROM_SUB_AND_PREDS](
                    subjects=map(EscapeQuotes,subjects),
                    predicate=EscapeQuotes(predicate),
                    modelName=self._modelName, scope=scope)
                results = cmd.query(self._db)
            else:
                cmd = self._sql[Commands.BUILD_NON_DISTINCT_OBJS_FROM_SUB_AND_PREDS](
                    subjects=map(EscapeQuotes,subjects),
                    predicate=EscapeQuotes(predicate),
                    modelName=self._modelName)
                results = cmd.query(self._db)
        else:
            if scope:
                results = self._sql[Commands.OBJECT_LIST_SCOPED].query(
                    self._db, modelName=self._modelName, scope=scope)
            else:
                results = self._sql[Commands.OBJECT_LIST].query(
                    self._db, modelName=self._modelName)
        return map(lambda x: (dec_utf8(x[0]), x[1]), results or [])

    def objectsFromSubAndPreds(self, subject, predicates, scope):
        """
        Get a list of obejcts with the given predicates and subject
        """
        if subject is not None:
            if scope:
                cmd = self._sql[Commands.BUILD_OBJS_FROM_SUB_AND_PREDS_SCOPED](
                    subject=EscapeQuotes(subject),
                    predicates=map(EscapeQuotes, predicates),
                    modelName=self._modelName, scope=scope)
                results = cmd.query(self._db)
            else:
                cmd = self._sql[Commands.BUILD_OBJS_FROM_SUB_AND_PREDS](
                    subject=EscapeQuotes(subject),
                    predicates=map(EscapeQuotes, predicates),
                    modelName=self._modelName)
                results = cmd.query(self._db)
        else:
            if scope:
                results = self._sql[Commands.OBJECT_LIST_SCOPED].query(
                    self._db, modelName=self._modelName, scope=scope)
            else:
                results = self._sql[Commands.OBJECT_LIST].query(
                    self._db, modelName=self._modelName)
        return map(lambda x: (dec_utf8(x[0]), x[1]), results or [])

    def isResource(self, res):
        result = self._sql[Commands.IS_RESOURCE].query(
            self._db, subject=EscapeQuotes(res),
            modelName = self._modelName,
            )
        return result and (int(result[0][0]) > 0) or 0

    def resources(self, scope):
        if scope:
            results = self._sql[Commands.SUBJECT_LIST_SCOPED].query(
                self._db, modelName=self._modelName, scope=EscapeQuotes(scope),
                )
            pred_res = self._sql[Commands.PREDICATE_LIST_SCOPED].query(
                self._db, modelName=self._modelName, scope=EscapeQuotes(scope),
                )
        else:
            results = self._sql[Commands.SUBJECT_LIST].query(
                self._db, modelName=self._modelName
                )
            pred_res = self._sql[Commands.PREDICATE_LIST].query(
                self._db, modelName=self._modelName
                )
        results = list(results)
        for p in pred_res:
            if p not in results:
                results.append(p)
        return map(lambda x: dec_utf8(x[0]), results or [])

