1# orm/sync.py
2# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
8"""private module containing functions used for copying data
9between instances based on join conditions.
10
11"""
12
13from . import exc, util as orm_util, attributes
14
15
16def populate(source, source_mapper, dest, dest_mapper,
17             synchronize_pairs, uowcommit, flag_cascaded_pks):
18    source_dict = source.dict
19    dest_dict = dest.dict
20
21    for l, r in synchronize_pairs:
22        try:
23            # inline of source_mapper._get_state_attr_by_column
24            prop = source_mapper._columntoproperty[l]
25            value = source.manager[prop.key].impl.get(source, source_dict,
26                                                      attributes.PASSIVE_OFF)
27        except exc.UnmappedColumnError:
28            _raise_col_to_prop(False, source_mapper, l, dest_mapper, r)
29
30        try:
31            # inline of dest_mapper._set_state_attr_by_column
32            prop = dest_mapper._columntoproperty[r]
33            dest.manager[prop.key].impl.set(dest, dest_dict, value, None)
34        except exc.UnmappedColumnError:
35            _raise_col_to_prop(True, source_mapper, l, dest_mapper, r)
36
37        # technically the "r.primary_key" check isn't
38        # needed here, but we check for this condition to limit
39        # how often this logic is invoked for memory/performance
40        # reasons, since we only need this info for a primary key
41        # destination.
42        if flag_cascaded_pks and l.primary_key and \
43                r.primary_key and \
44                r.references(l):
45            uowcommit.attributes[("pk_cascaded", dest, r)] = True
46
47
48def bulk_populate_inherit_keys(
49        source_dict, source_mapper, synchronize_pairs):
50    # a simplified version of populate() used by bulk insert mode
51    for l, r in synchronize_pairs:
52        try:
53            prop = source_mapper._columntoproperty[l]
54            value = source_dict[prop.key]
55        except exc.UnmappedColumnError:
56            _raise_col_to_prop(False, source_mapper, l, source_mapper, r)
57
58        try:
59            prop = source_mapper._columntoproperty[r]
60            source_dict[prop.key] = value
61        except exc.UnmappedColumnError:
62            _raise_col_to_prop(True, source_mapper, l, source_mapper, r)
63
64
65def clear(dest, dest_mapper, synchronize_pairs):
66    for l, r in synchronize_pairs:
67        if r.primary_key and \
68            dest_mapper._get_state_attr_by_column(
69                dest, dest.dict, r) not in orm_util._none_set:
70
71            raise AssertionError(
72                "Dependency rule tried to blank-out primary key "
73                "column '%s' on instance '%s'" %
74                (r, orm_util.state_str(dest))
75            )
76        try:
77            dest_mapper._set_state_attr_by_column(dest, dest.dict, r, None)
78        except exc.UnmappedColumnError:
79            _raise_col_to_prop(True, None, l, dest_mapper, r)
80
81
82def update(source, source_mapper, dest, old_prefix, synchronize_pairs):
83    for l, r in synchronize_pairs:
84        try:
85            oldvalue = source_mapper._get_committed_attr_by_column(
86                source.obj(), l)
87            value = source_mapper._get_state_attr_by_column(
88                source, source.dict, l, passive=attributes.PASSIVE_OFF)
89        except exc.UnmappedColumnError:
90            _raise_col_to_prop(False, source_mapper, l, None, r)
91        dest[r.key] = value
92        dest[old_prefix + r.key] = oldvalue
93
94
95def populate_dict(source, source_mapper, dict_, synchronize_pairs):
96    for l, r in synchronize_pairs:
97        try:
98            value = source_mapper._get_state_attr_by_column(
99                source, source.dict, l, passive=attributes.PASSIVE_OFF)
100        except exc.UnmappedColumnError:
101            _raise_col_to_prop(False, source_mapper, l, None, r)
102
103        dict_[r.key] = value
104
105
106def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
107    """return true if the source object has changes from an old to a
108    new value on the given synchronize pairs
109
110    """
111    for l, r in synchronize_pairs:
112        try:
113            prop = source_mapper._columntoproperty[l]
114        except exc.UnmappedColumnError:
115            _raise_col_to_prop(False, source_mapper, l, None, r)
116        history = uowcommit.get_attribute_history(
117            source, prop.key, attributes.PASSIVE_NO_INITIALIZE)
118        if bool(history.deleted):
119            return True
120    else:
121        return False
122
123
124def _raise_col_to_prop(isdest, source_mapper, source_column,
125                       dest_mapper, dest_column):
126    if isdest:
127        raise exc.UnmappedColumnError(
128            "Can't execute sync rule for "
129            "destination column '%s'; mapper '%s' does not map "
130            "this column.  Try using an explicit `foreign_keys` "
131            "collection which does not include this column (or use "
132            "a viewonly=True relation)." % (dest_column, dest_mapper))
133    else:
134        raise exc.UnmappedColumnError(
135            "Can't execute sync rule for "
136            "source column '%s'; mapper '%s' does not map this "
137            "column.  Try using an explicit `foreign_keys` "
138            "collection which does not include destination column "
139            "'%s' (or use a viewonly=True relation)." %
140            (source_column, source_mapper, dest_column))
141