1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
# mapper/sync.py
# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import sqlalchemy.sql as sql
import sqlalchemy.schema as schema
from sqlalchemy.exceptions import *
from sqlalchemy import logging
import util as mapperutil
"""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, issecondary=None, foreignkey=None):
def compile_binary(binary):
"""assemble 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
source_column = None
dest_column = None
if foreignkey is not None:
# for self-referential relationships,
# the best we can do right now is figure out which side
# is the primary key
# TODO: need some better way for this
if binary.left.table == binary.right.table:
if binary.left.primary_key:
source_column = binary.left
dest_column = binary.right
elif binary.right.primary_key:
source_column = binary.right
dest_column = binary.left
else:
raise ArgumentError("Can't locate a primary key column in self-referential equality clause '%s'" % str(binary))
# for other relationships we are more flexible
# and go off the 'foreignkey' property
elif binary.left in foreignkey:
dest_column = binary.left
source_column = binary.right
elif binary.right in foreignkey:
dest_column = binary.right
source_column = binary.left
else:
return
else:
if binary.left in [f.column for f in binary.right.foreign_keys]:
dest_column = binary.right
source_column = binary.left
elif binary.right in [f.column for f in binary.left.foreign_keys]:
dest_column = binary.left
source_column = binary.right
if source_column and dest_column:
if self.direction == ONETOMANY:
self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper))
elif self.direction == MANYTOONE:
self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, dest_mapper=self.parent_mapper))
else:
if not issecondary:
self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper, issecondary=issecondary))
else:
self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, 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 dest_columns(self):
return [r.dest_column for r in self.syncrules if r.dest_column is not None]
def execute(self, source, dest, obj=None, child=None, clearkeys=None):
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
def dest_primary_key(self):
try:
return self._dest_primary_key
except AttributeError:
self._dest_primary_key = self.dest_mapper is not None and self.dest_column in self.dest_mapper.pks_by_table[self.dest_column.table]
return self._dest_primary_key
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.get_attr_by_column(source, self.source_column)
if isinstance(dest, dict):
dest[self.dest_column.key] = value
else:
if clearkeys and self.dest_primary_key():
raise exceptions.AssertionError("Dependency rule tried to blank-out a primary key column")
if logging.is_debug_enabled(self.logger):
self.logger.debug("execute() instances: %s(%s)->%s(%s) ('%s')" % (mapperutil.instance_str(source), str(self.source_column), mapperutil.instance_str(dest), str(self.dest_column), value))
self.dest_mapper.set_attr_by_column(dest, self.dest_column, value)
SyncRule.logger = logging.class_logger(SyncRule)
class BinaryVisitor(sql.ClauseVisitor):
def __init__(self, func):
self.func = func
def visit_binary(self, binary):
self.func(binary)
|