--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Query representation
+#
+# Copyright (C) UPMC Paris Universitas
+# Authors:
+# Jordan Augé <jordan.auge@lip6.fr>
+# Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
+# Thierry Parmentelat <thierry.parmentelat@inria.fr>
+
+from types import StringTypes
+from manifold.core.filter import Filter, Predicate
+from manifold.util.frozendict import frozendict
+from manifold.util.type import returns, accepts
+import copy
+
+import json
+import uuid
+
+def uniqid ():
+ return uuid.uuid4().hex
+
+debug=False
+debug=True
+
+class ParameterError(StandardError): pass
+
+class Query(object):
+ """
+ Implements a TopHat query.
+
+ We assume this is a correct DAG specification.
+
+ 1/ A field designates several tables = OR specification.
+ 2/ The set of fields specifies a AND between OR clauses.
+ """
+
+ #---------------------------------------------------------------------------
+ # Constructor
+ #---------------------------------------------------------------------------
+
+ def __init__(self, *args, **kwargs):
+
+ self.query_uuid = uniqid()
+
+ # Initialize optional parameters
+ self.clear()
+
+ #l = len(kwargs.keys())
+ len_args = len(args)
+
+ if len(args) == 1:
+ if isinstance(args[0], dict):
+ kwargs = args[0]
+ args = []
+
+ # Initialization from a tuple
+
+ if len_args in range(2, 7) and type(args) == tuple:
+ # Note: range(x,y) <=> [x, y[
+
+ # XXX UGLY
+ if len_args == 3:
+ self.action = 'get'
+ self.params = {}
+ self.timestamp = 'now'
+ self.object, self.filters, self.fields = args
+ elif len_args == 4:
+ self.object, self.filters, self.params, self.fields = args
+ self.action = 'get'
+ self.timestamp = 'now'
+ else:
+ self.action, self.object, self.filters, self.params, self.fields, self.timestamp = args
+
+ # Initialization from a dict
+ elif "object" in kwargs:
+ if "action" in kwargs:
+ self.action = kwargs["action"]
+ del kwargs["action"]
+ else:
+ print "W: defaulting to get action"
+ self.action = "get"
+
+
+ self.object = kwargs["object"]
+ del kwargs["object"]
+
+ if "filters" in kwargs:
+ self.filters = kwargs["filters"]
+ del kwargs["filters"]
+ else:
+ self.filters = Filter([])
+
+ if "fields" in kwargs:
+ self.fields = set(kwargs["fields"])
+ del kwargs["fields"]
+ else:
+ self.fields = set([])
+
+ # "update table set x = 3" => params == set
+ if "params" in kwargs:
+ self.params = kwargs["params"]
+ del kwargs["params"]
+ else:
+ self.params = {}
+
+ if "timestamp" in kwargs:
+ self.timestamp = kwargs["timestamp"]
+ del kwargs["timestamp"]
+ else:
+ self.timestamp = "now"
+
+ if kwargs:
+ raise ParameterError, "Invalid parameter(s) : %r" % kwargs.keys()
+ #else:
+ # raise ParameterError, "No valid constructor found for %s : args = %r" % (self.__class__.__name__, args)
+
+ if not self.filters: self.filters = Filter([])
+ if not self.params: self.params = {}
+ if not self.fields: self.fields = set([])
+ if not self.timestamp: self.timestamp = "now"
+
+ if isinstance(self.filters, list):
+ f = self.filters
+ self.filters = Filter([])
+ for x in f:
+ pred = Predicate(x)
+ self.filters.add(pred)
+
+ if isinstance(self.fields, list):
+ self.fields = set(self.fields)
+
+ for field in self.fields:
+ if not isinstance(field, StringTypes):
+ raise TypeError("Invalid field name %s (string expected, got %s)" % (field, type(field)))
+
+ #---------------------------------------------------------------------------
+ # Helpers
+ #---------------------------------------------------------------------------
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ def clear(self):
+ self.action = 'get'
+ self.object = None
+ self.filters = Filter([])
+ self.params = {}
+ self.fields = set([])
+ self.timestamp = "now"
+ self.timestamp = 'now' # ignored for now
+
+ @returns(StringTypes)
+ def __str__(self):
+ return "SELECT %s FROM %s WHERE %s" % (
+ ", ".join(self.get_select()) if self.get_select() else '*',
+ self.get_from(),
+ self.get_where()
+ )
+
+ @returns(StringTypes)
+ def __repr__(self):
+ return self.__str__()
+
+ def __key(self):
+ return (self.action, self.object, self.filters, frozendict(self.params), frozenset(self.fields))
+
+ def __hash__(self):
+ print "HASH", self.__key()
+ return hash(self.__key())
+
+ #---------------------------------------------------------------------------
+ # Conversion
+ #---------------------------------------------------------------------------
+
+ def to_dict(self):
+ return {
+ 'action': self.action,
+ 'object': self.object,
+ 'timestamp': self.timestamp,
+ 'filters': self.filters,
+ 'params': self.params,
+ 'fields': self.fields
+ }
+
+ def to_json (self, analyzed_query=None):
+ query_uuid=self.query_uuid
+ a=self.action
+ s=self.object
+ t=self.timestamp
+ f=json.dumps (self.filters.to_list())
+ p=json.dumps (self.params)
+ c=json.dumps (list(self.fields))
+ # xxx unique can be removed, but for now we pad the js structure
+ unique=0
+
+ if not analyzed_query:
+ aq = 'null'
+ else:
+ aq = analyzed_query.to_json()
+ sq="{}"
+
+ result= """ new ManifoldQuery('%(a)s', '%(s)s', '%(t)s', %(f)s, %(p)s, %(c)s, %(unique)s, '%(query_uuid)s', %(aq)s, %(sq)s)"""%locals()
+ if debug: print 'ManifoldQuery.to_json:',result
+ return result
+
+ # this builds a ManifoldQuery object from a dict as received from javascript through its ajax request
+ # we use a json-encoded string - see manifold.js for the sender part
+ # e.g. here's what I captured from the server's output
+ # manifoldproxy.proxy: request.POST <QueryDict: {u'json': [u'{"action":"get","object":"resource","timestamp":"latest","filters":[["slice_hrn","=","ple.inria.omftest"]],"params":[],"fields":["hrn","hostname"],"unique":0,"query_uuid":"436aae70a48141cc826f88e08fbd74b1","analyzed_query":null,"subqueries":{}}']}>
+ def fill_from_POST (self, POST_dict):
+ try:
+ json_string=POST_dict['json']
+ dict=json.loads(json_string)
+ for (k,v) in dict.iteritems():
+ setattr(self,k,v)
+ except:
+ print "Could not decode incoming ajax request as a Query, POST=",POST_dict
+ if (debug):
+ import traceback
+ traceback.print_exc()
+
+ #---------------------------------------------------------------------------
+ # Accessors
+ #---------------------------------------------------------------------------
+
+ @returns(StringTypes)
+ def get_action(self):
+ return self.action
+
+ @returns(frozenset)
+ def get_select(self):
+ return frozenset(self.fields)
+
+ @returns(StringTypes)
+ def get_from(self):
+ return self.object
+
+ @returns(Filter)
+ def get_where(self):
+ return self.filters
+
+ @returns(dict)
+ def get_params(self):
+ return self.params
+
+ @returns(StringTypes)
+ def get_timestamp(self):
+ return self.timestamp
+
+#DEPRECATED#
+#DEPRECATED# def make_filters(self, filters):
+#DEPRECATED# return Filter(filters)
+#DEPRECATED#
+#DEPRECATED# def make_fields(self, fields):
+#DEPRECATED# if isinstance(fields, (list, tuple)):
+#DEPRECATED# return set(fields)
+#DEPRECATED# else:
+#DEPRECATED# raise Exception, "Invalid field specification"
+
+ #---------------------------------------------------------------------------
+ # LINQ-like syntax
+ #---------------------------------------------------------------------------
+
+ @classmethod
+ def action(self, action, object):
+ query = Query()
+ query.action = 'get'
+ query.object = object
+ return query
+
+ @classmethod
+ def get(self, object): return self.action('get', object)
+
+ @classmethod
+ def update(self, object): return self.action('update', object)
+
+ @classmethod
+ def create(self, object): return self.action('create', object)
+
+ @classmethod
+ def delete(self, object): return self.action('delete', object)
+
+ @classmethod
+ def execute(self, object): return self.action('execute', object)
+
+ def filter_by(self, *args):
+ if len(args) == 1:
+ filters = args[0]
+ if not isinstance(filters, (set, list, tuple, Filter)):
+ filters = [filters]
+ for predicate in filters:
+ self.filters.add(predicate)
+ elif len(args) == 3:
+ predicate = Predicate(*args)
+ self.filters.add(predicate)
+ else:
+ raise Exception, 'Invalid expression for filter'
+ return self
+
+ def select(self, fields):
+ if not isinstance(fields, (set, list, tuple)):
+ fields = [fields]
+ for field in fields:
+ self.fields.add(field)
+ return self
+
+ def set(self, params):
+ self.params.update(params)
+ return self
+
+class AnalyzedQuery(Query):
+
+ # XXX we might need to propagate special parameters sur as DEBUG, etc.
+
+ def __init__(self, query=None):
+ self.clear()
+ if query:
+ self.query_uuid = query.query_uuid
+ self.analyze(query)
+ else:
+ self.query_uuid = uniqid()
+
+ @returns(StringTypes)
+ def __str__(self):
+ out = []
+ out.append("SELECT %s FROM %s WHERE %s" % (
+ ", ".join(self.get_select()),
+ self.get_from(),
+ self.get_where()
+ ))
+ cpt = 1
+ for method, subquery in self.subqueries():
+ out.append(' [SQ #%d : %s] %s' % (cpt, method, str(subquery)))
+ cpt += 1
+
+ return "\n".join(out)
+
+ def clear(self):
+ super(AnalyzedQuery, self).clear()
+ self._subqueries = {}
+
+ def subquery(self, method):
+ # Allows for the construction of a subquery
+ if not method in self._subqueries:
+ analyzed_query = AnalyzedQuery()
+ analyzed_query.action = self.action
+ analyzed_query.object = method
+ self._subqueries[method] = analyzed_query
+ return self._subqueries[method]
+
+ def subqueries(self):
+ for method, subquery in self._subqueries.iteritems():
+ yield (method, subquery)
+
+ def filter_by(self, filters):
+ if not filters: return self
+ if not isinstance(filters, (set, list, tuple, Filter)):
+ filters = [filters]
+ for predicate in filters:
+ if '.' in predicate.key:
+ method, subkey = pred.key.split('.', 1)
+ sub_pred = Predicate(subkey, pred.op, pred.value)
+ self.subquery(method).filter_by(sub_pred)
+ else:
+ super(AnalyzedQuery, self).filter_by(predicate)
+ return self
+
+ def select(self, fields):
+ if not isinstance(fields, (set, list, tuple)):
+ fields = [fields]
+ for field in fields:
+ if '.' in field:
+ method, subfield = field.split('.', 1)
+ self.subquery(method).select(subfield)
+ else:
+ super(AnalyzedQuery, self).select(field)
+ return self
+
+ def set(self, params):
+ for param, value in self.params.items():
+ if '.' in param:
+ method, subparam = param.split('.', 1)
+ self.subquery(method).set({subparam: value})
+ else:
+ super(AnalyzedQuery, self).set({param: value})
+ return self
+
+ def analyze(self, query):
+ self.clear()
+ self.action = query.action
+ self.object = query.object
+ self.filter_by(query.filters)
+ self.set(query.params)
+ self.select(query.fields)
+
+ def to_json (self):
+ query_uuid=self.query_uuid
+ a=self.action
+ s=self.object
+ t=self.timestamp
+ f=json.dumps (self.filters.to_list())
+ p=json.dumps (self.params)
+ c=json.dumps (list(self.fields))
+ # xxx unique can be removed, but for now we pad the js structure
+ unique=0
+
+ aq = 'null'
+ sq=", ".join ( [ "'%s':%s" % (subject, subquery.to_json())
+ for (subject, subquery) in self._subqueries.iteritems()])
+ sq="{%s}"%sq
+
+ result= """ new ManifoldQuery('%(a)s', '%(s)s', '%(t)s', %(f)s, %(p)s, %(c)s, %(unique)s, '%(query_uuid)s', %(aq)s, %(sq)s)"""%locals()
+ if debug: print 'ManifoldQuery.to_json:',result
+ return result