diff options
Diffstat (limited to 'lib/sqlalchemy/mapping/sync.py')
| -rw-r--r-- | lib/sqlalchemy/mapping/sync.py | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/lib/sqlalchemy/mapping/sync.py b/lib/sqlalchemy/mapping/sync.py new file mode 100644 index 000000000..d5c1f87bf --- /dev/null +++ b/lib/sqlalchemy/mapping/sync.py @@ -0,0 +1,121 @@ +import sqlalchemy.sql as sql +import sqlalchemy.schema as schema +from sqlalchemy.exceptions import * +import properties + +"""contains the ClauseSynchronizer class which is used to map attributes between two objects +in a manner corresponding to a SQL clause that compares column values.""" + +ONETOMANY = 0 +MANYTOONE = 1 +MANYTOMANY = 2 + +class ClauseSynchronizer(object): + """Given a SQL clause, usually a series of one or more binary + expressions between columns, and a set of 'source' and 'destination' mappers, compiles a set of SyncRules + corresponding to that information. The ClauseSynchronizer can then be executed given a set of parent/child + objects or destination dictionary, which will iterate through each of its SyncRules and execute them. + Each SyncRule will copy the value of a single attribute from the parent + to the child, corresponding to the pair of columns in a particular binary expression, using the source and + destination mappers to map those two columns to object attributes within parent and child.""" + def __init__(self, parent_mapper, child_mapper, direction): + self.parent_mapper = parent_mapper + self.child_mapper = child_mapper + self.direction = direction + self.syncrules = [] + + def compile(self, sqlclause, source_tables, target_tables, issecondary=None): + def check_for_table(binary, l): + for col in [binary.left, binary.right]: + if col.table in l: + return col + else: + return None + + def compile_binary(binary): + """assembles a SyncRule given a single binary condition""" + if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column): + return + + if binary.left.table == binary.right.table: + # self-cyclical relation + if binary.left.primary_key: + source = binary.left + dest = binary.right + elif binary.right.primary_key: + source = binary.right + dest = binary.left + else: + raise ArgumentError("Cant determine direction for relationship %s = %s" % (binary.left.fullname, binary.right.fullname)) + if self.direction == ONETOMANY: + self.syncrules.append(SyncRule(self.parent_mapper, source, dest, dest_mapper=self.child_mapper)) + elif self.direction == MANYTOONE: + self.syncrules.append(SyncRule(self.child_mapper, source, dest, dest_mapper=self.parent_mapper)) + else: + raise AssertionError("assert failed") + else: + pt = check_for_table(binary, source_tables) + tt = check_for_table(binary, target_tables) + #print "OK", binary, [t.name for t in source_tables], [t.name for t in target_tables] + if pt and tt: + if self.direction == ONETOMANY: + self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper)) + elif self.direction == MANYTOONE: + self.syncrules.append(SyncRule(self.child_mapper, tt, pt, dest_mapper=self.parent_mapper)) + else: + if not issecondary: + self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper, issecondary=issecondary)) + else: + self.syncrules.append(SyncRule(self.child_mapper, pt, tt, dest_mapper=self.parent_mapper, issecondary=issecondary)) + + rules_added = len(self.syncrules) + processor = BinaryVisitor(compile_binary) + sqlclause.accept_visitor(processor) + if len(self.syncrules) == rules_added: + raise ArgumentError("No syncrules generated for join criterion " + str(sqlclause)) + + def execute(self, source, dest, obj, child, clearkeys): + for rule in self.syncrules: + rule.execute(source, dest, obj, child, clearkeys) + +class SyncRule(object): + """An instruction indicating how to populate the objects on each side of a relationship. + i.e. if table1 column A is joined against + table2 column B, and we are a one-to-many from table1 to table2, a syncrule would say + 'take the A attribute from object1 and assign it to the B attribute on object2'. + + A rule contains the source mapper, the source column, destination column, + destination mapper in the case of a one/many relationship, and + the integer direction of this mapper relative to the association in the case + of a many to many relationship. + """ + def __init__(self, source_mapper, source_column, dest_column, dest_mapper=None, issecondary=None): + self.source_mapper = source_mapper + self.source_column = source_column + self.issecondary = issecondary + self.dest_mapper = dest_mapper + self.dest_column = dest_column + #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper, direction + + def execute(self, source, dest, obj, child, clearkeys): + if source is None: + if self.issecondary is False: + source = obj + elif self.issecondary is True: + source = child + if clearkeys or source is None: + value = None + else: + value = self.source_mapper._getattrbycolumn(source, self.source_column) + if isinstance(dest, dict): + dest[self.dest_column.key] = value + else: + #print "SYNC VALUE", value, "TO", dest + self.dest_mapper._setattrbycolumn(dest, self.dest_column, value) + +class BinaryVisitor(sql.ClauseVisitor): + def __init__(self, func): + self.func = func + def visit_binary(self, binary): + self.func(binary) + |
