# HG changeset patch # User Sylvain Thénault # Date 1247148922 -7200 # Node ID 9b4bac626977c7d8bc4a0c58ac2206d5f633c665 # Parent b11f1068a0d3aec10fa84aed2c0c04e257bdf455 ability to map attributes to something else than usual cw mapping on sql generation diff -r b11f1068a0d3 -r 9b4bac626977 server/sources/native.py --- a/server/sources/native.py Thu Jul 09 16:14:22 2009 +0200 +++ b/server/sources/native.py Thu Jul 09 16:15:22 2009 +0200 @@ -31,6 +31,7 @@ from cubicweb.server.sources.rql2sql import SQLGenerator +ATTR_MAP = {} NONSYSTEM_ETYPES = set() NONSYSTEM_RELATIONS = set() @@ -90,6 +91,7 @@ class NativeSQLSource(SQLAdapterMixIn, AbstractSource): """adapter for source using the native cubicweb schema (see below) """ + sqlgen_class = SQLGenerator # need default value on class since migration doesn't call init method has_deleted_entitites_table = True @@ -141,8 +143,8 @@ AbstractSource.__init__(self, repo, appschema, source_config, *args, **kwargs) # sql generator - self._rql_sqlgen = SQLGenerator(appschema, self.dbhelper, - self.encoding) + self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper, + self.encoding, ATTR_MAP.copy()) # full text index helper self.indexer = get_indexer(self.dbdriver, self.encoding) # advanced functionality helper @@ -209,6 +211,9 @@ pool.pool_reset() self.repo._free_pool(pool) + def map_attribute(self, etype, attr, cb): + self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb + # ISource interface ####################################################### def compile_rql(self, rql): diff -r b11f1068a0d3 -r 9b4bac626977 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Thu Jul 09 16:14:22 2009 +0200 +++ b/server/sources/rql2sql.py Thu Jul 09 16:15:22 2009 +0200 @@ -303,7 +303,7 @@ protected by a lock """ - def __init__(self, schema, dbms_helper, dbencoding='UTF-8'): + def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None): self.schema = schema self.dbms_helper = dbms_helper self.dbencoding = dbencoding @@ -313,6 +313,9 @@ if not self.dbms_helper.union_parentheses_support: self.union_sql = self.noparen_union_sql self._lock = threading.Lock() + if attrmap is None: + attrmap = {} + self.attr_map = attrmap def generate(self, union, args=None, varmap=None): """return SQL queries and a variable dictionnary from a RQL syntax tree @@ -853,27 +856,30 @@ relation.r_type) return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) - def _visit_attribute_relation(self, relation): + def _visit_attribute_relation(self, rel): """generate SQL for an attribute relation""" - lhs, rhs = relation.get_parts() + lhs, rhs = rel.get_parts() rhssql = rhs.accept(self) table = self._var_table(lhs.variable) if table is None: - assert relation.r_type == 'eid' + assert rel.r_type == 'eid' lhssql = lhs.accept(self) else: try: - lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)] + lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)] except KeyError: - if relation.r_type == 'eid': + mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type) + if mapkey in self.attr_map: + lhssql = self.attr_map[mapkey](self, lhs.variable, rel) + elif rel.r_type == 'eid': lhssql = lhs.variable._q_sql else: - lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type) + lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type) try: - if relation._q_needcast == 'TODAY': + if rel._q_needcast == 'TODAY': sql = 'DATE(%s)%s' % (lhssql, rhssql) # XXX which cast function should be used - #elif relation._q_needcast == 'NOW': + #elif rel._q_needcast == 'NOW': # sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql) else: sql = '%s%s' % (lhssql, rhssql) @@ -884,15 +890,15 @@ else: return sql - def _visit_has_text_relation(self, relation): + def _visit_has_text_relation(self, rel): """generate SQL for a has_text relation""" - lhs, rhs = relation.get_parts() + lhs, rhs = rel.get_parts() const = rhs.children[0] - alias = self._fti_table(relation) + alias = self._fti_table(rel) jointo = lhs.accept(self) restriction = '' lhsvar = lhs.variable - me_is_principal = lhsvar.stinfo.get('principal') is relation + me_is_principal = lhsvar.stinfo.get('principal') is rel if me_is_principal: if not lhsvar.stinfo['typerels']: # the variable is using the fti table, no join needed @@ -908,8 +914,8 @@ else: etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes']) restriction = " AND %s.type IN (%s)" % (ealias, etypes) - if isinstance(relation.parent, Not): - self._state.done.add(relation.parent) + if isinstance(rel.parent, Not): + self._state.done.add(rel.parent) not_ = True else: not_ = False @@ -1117,6 +1123,9 @@ if isinstance(linkedvar, ColumnAlias): raise BadRQLQuery('variable %s should be selected by the subquery' % variable.name) + mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type) + if mapkey in self.attr_map: + return self.attr_map[mapkey](self, linkedvar, rel) try: sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] except KeyError: diff -r b11f1068a0d3 -r 9b4bac626977 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Thu Jul 09 16:14:22 2009 +0200 +++ b/server/test/unittest_rql2sql.py Thu Jul 09 16:15:22 2009 +0200 @@ -1436,6 +1436,22 @@ '''SELECT COUNT(1) WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''') + def test_attr_map(self): + def generate_ref(gen, linkedvar, rel): + linkedvar.accept(gen) + return 'VERSION_DATA(%s)' % linkedvar._q_sql + self.o.attr_map['Affaire.ref'] = generate_ref + try: + self._check('Any R WHERE X ref R', + '''SELECT VERSION_DATA(X.cw_eid) +FROM cw_Affaire AS X''') + self._check('Any X WHERE X ref 1', + '''SELECT X.cw_eid +FROM cw_Affaire AS X +WHERE VERSION_DATA(X.cw_eid)=1''') + finally: + self.o.attr_map.clear() + class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):