1
- # Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License"). You
4
- # may not use this file except in compliance with the License. A copy of
5
- # the License is located at
6
- #
7
- # https://aws.amazon.com/apache2.0/
8
- #
9
- # or in the "license" file accompanying this file. This file is
10
- # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
- # ANY KIND, either express or implied. See the License for the specific
12
- # language governing permissions and limitations under the License.
1
+ # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
13
3
import re
14
4
from collections import namedtuple
15
5
16
6
from boto3 .exceptions import (
17
7
DynamoDBNeedsConditionError ,
18
- DynamoDBNeedsKeyConditionError ,
19
- DynamoDBOperationNotSupportedError ,
20
8
)
21
9
from boto3 .dynamodb .conditions import (
22
10
ConditionBase ,
23
11
BuiltConditionExpression ,
24
12
AttributeBase ,
25
- Key ,
26
- Contains ,
27
- BeginsWith ,
28
13
)
29
- from aws_database_encryption_sdk .transform import dict_to_ddb
30
14
31
- ATTR_NAME_REGEX = re .compile (r'[^.\[\]]+(?![^\[]*\])' )
32
15
33
- class ConditionExpressionBuilder :
34
- """This class is used to build condition expressions with placeholders"""
16
+ class InternalDBESDKDynamoDBConditionExpressionBuilder :
17
+ """
18
+ This internal class is used to transform boto3 DyamoDB conditions from
19
+ Python classes to DynamoDB ConditionExpressions
20
+ for use by internal DBESDK DyanmoDB operation transformers.
35
21
36
- def __init__ (self ):
37
- self ._name_count = 0
38
- self ._value_count = 0
39
- self ._name_placeholder = 'n'
40
- self ._value_placeholder = 'v'
41
-
42
- def _get_name_placeholder (self ):
43
- return '#' + self ._name_placeholder + str (self ._name_count )
44
-
45
- def _get_value_placeholder (self ):
46
- return ':' + self ._value_placeholder + str (self ._value_count )
47
-
48
- def reset (self ):
49
- """Resets the placeholder name and values"""
50
- self ._name_count = 0
51
- self ._value_count = 0
22
+ This class is a modified version of boto3's ConditionExpressionBuilder:
23
+ https://github.com/boto/boto3/blob/c5c634b53be589d6e913e6dca51ad8d6480f58c7/boto3/dynamodb/conditions.py#L304
24
+ The original class is intended to interface from boto3 Table resources to boto3 clients,
25
+ and has logic to replace attribute names and values with placeholders.
26
+ This class is intended to interface from boto3 Table resources to internal DBESDK DyanmoDB operation transformers,
27
+ which does not expect inserted placeholders.
28
+ The placeholder insertion logic has been removed from this class along with its supporting logic,
29
+ but this class maintains boto3's recursive ConditionExpression traversal logic.
30
+ """
52
31
53
32
def build_expression (
54
33
self ,
55
34
condition ,
56
35
expression_attribute_names ,
57
36
expression_attribute_values ,
58
- is_key_condition = False
59
37
):
60
- """Builds the condition expression and the dictionary of placeholders .
38
+ """Builds the condition expression.
61
39
62
40
:type condition: ConditionBase
63
- :param condition: A condition to be built into a condition expression
64
- string with any necessary placeholders.
65
-
66
- :type is_key_condition: Boolean
67
- :param is_key_condition: True if the expression is for a
68
- KeyConditionExpression. False otherwise.
41
+ :param condition: A condition to be built into a condition expression string.
69
42
70
43
:rtype: (string, dict, dict)
71
- :returns: Will return a string representing the condition with
72
- placeholders inserted where necessary, a dictionary of
73
- placeholders for attribute names, and a dictionary of
74
- placeholders for attribute values. Here is a sample return value:
75
-
76
- ('#n0 = :v0', {'#n0': 'myattribute'}, {':v1': 'myvalue'})
44
+ :returns: Will return a string representing the condition expression,
45
+ the original dictionary of attribute names,
46
+ and the original dictionary of attribute values.
77
47
"""
78
48
if not isinstance (condition , ConditionBase ):
79
49
raise DynamoDBNeedsConditionError (condition )
80
- attribute_name_placeholders = expression_attribute_names
81
- attribute_value_placeholders = expression_attribute_values
82
- condition_expression = self ._build_expression (
83
- condition ,
84
- attribute_name_placeholders ,
85
- attribute_value_placeholders ,
86
- is_key_condition = is_key_condition ,
87
- )
50
+ condition_expression = self ._build_expression (condition )
88
51
return BuiltConditionExpression (
89
52
condition_expression = condition_expression ,
90
- attribute_name_placeholders = attribute_name_placeholders ,
91
- attribute_value_placeholders = attribute_value_placeholders ,
53
+ # BuiltConditionExpression uses "placeholders" nomenclature;
54
+ # this is an artifact of the original boto3 ConditionExpressionBuilder.
55
+ # This class neither creates placeholders, nor even uses the provided name/values dicts.
56
+ # They are only here as required arguments for the BuiltConditionExpression class.
57
+ attribute_name_placeholders = expression_attribute_names ,
58
+ attribute_value_placeholders = expression_attribute_values ,
92
59
)
93
60
94
61
def _build_expression (
95
62
self ,
96
63
condition ,
97
- attribute_name_placeholders ,
98
- attribute_value_placeholders ,
99
- is_key_condition ,
100
64
):
101
65
expression_dict = condition .get_expression ()
102
66
replaced_values = []
103
67
for value in expression_dict ['values' ]:
104
- # Build the necessary placeholders for that value.
105
- # Placeholders are built for both attribute names and values.
106
- replaced_value = self ._build_expression_component (
107
- value ,
108
- attribute_name_placeholders ,
109
- attribute_value_placeholders ,
110
- condition ,
111
- is_key_condition ,
112
- )
68
+ # Recurse for each value in the expression.
69
+ replaced_value = self ._build_expression_component (condition , value )
113
70
replaced_values .append (replaced_value )
114
- # Fill out the expression using the operator and the
115
- # values that have been replaced with placeholders.
71
+ # Fill out the expression using the operator and its recursive expressions.
116
72
return expression_dict ['format' ].format (
117
73
* replaced_values , operator = expression_dict ['operator' ]
118
74
)
119
75
120
76
def _build_expression_component (
121
77
self ,
122
- value ,
123
- attribute_name_placeholders ,
124
- attribute_value_placeholders ,
125
78
condition ,
126
- is_key_condition ,
79
+ value ,
127
80
):
128
- has_grouped_values = condition .has_grouped_values
129
81
# Continue to recurse if the value is a ConditionBase in order
130
82
# to extract out all parts of the expression.
131
83
if isinstance (value , ConditionBase ):
132
84
return self ._build_expression (
133
85
value ,
134
- attribute_name_placeholders ,
135
- attribute_value_placeholders ,
136
- is_key_condition ,
137
86
)
138
87
# If it is not a ConditionBase, we can recurse no further.
139
- # So we check if it is an attribute and add placeholders for
140
- # its name
88
+ # If it's an attribute, insert its name.
141
89
elif isinstance (value , AttributeBase ):
142
- if is_key_condition and not isinstance (value , Key ):
143
- raise DynamoDBNeedsKeyConditionError (
144
- f'Attribute object { value .name } is of type { type (value )} . '
145
- f'KeyConditionExpression only supports Attribute objects '
146
- f'of type Key'
147
- )
148
- # contains expressions can have attribute values where the rest of this class expects an attribute name
149
- if isinstance (condition , Contains ) or isinstance (condition , BeginsWith ):
150
- return value .name
151
- return self ._build_name_placeholder (
152
- value , attribute_name_placeholders
153
- )
154
- # If it is anything else, we treat it as a value and thus placeholders
155
- # are needed for the value.
156
- else :
157
- if isinstance (condition , Contains ) or isinstance (condition , BeginsWith ):
158
- return value
159
- return self ._build_value_placeholder (
160
- value , attribute_value_placeholders , has_grouped_values
161
- )
162
-
163
- def _build_name_placeholder (self , value , attribute_name_placeholders ):
164
- return value .name
165
- attribute_name = value .name
166
- # Figure out which parts of the attribute name that needs replacement.
167
- attribute_name_parts = ATTR_NAME_REGEX .findall (attribute_name )
168
-
169
- # Add a temporary placeholder for each of these parts.
170
- placeholder_format = ATTR_NAME_REGEX .sub ('%s' , attribute_name )
171
- str_format_args = []
172
- for part in attribute_name_parts :
173
- if part [0 ] == "#" :
174
- str_format_args .append (part )
175
- else :
176
- name_placeholder = self ._get_name_placeholder ()
177
- self ._name_count += 1
178
- str_format_args .append (name_placeholder )
179
- # Add the placeholder and value to dictionary of name placeholders.
180
- attribute_name_placeholders [name_placeholder ] = part
181
- # Replace the temporary placeholders with the designated placeholders.
182
- return placeholder_format % tuple (str_format_args )
183
-
184
- def _build_value_placeholder (
185
- self , value , attribute_value_placeholders , has_grouped_values = False
186
- ):
187
- return value
188
- # If the values are grouped, we need to add a placeholder for
189
- # each element inside of the actual value.
190
- if has_grouped_values :
191
- placeholder_list = []
192
- for v in value :
193
- if v [0 ] != ":" :
194
- value_placeholder = self ._get_value_placeholder ()
195
- self ._value_count += 1
196
- placeholder_list .append (value_placeholder )
197
- attribute_value_placeholders [value_placeholder ] = list (dict_to_ddb ({value_placeholder : v }).values ())[0 ]
198
- else :
199
- placeholder_list .append (v )
200
- # Assuming the values are grouped by parenthesis.
201
- # IN is the currently the only one that uses this so it maybe
202
- # needed to be changed in future.
203
- return '(' + ', ' .join (placeholder_list ) + ')'
204
- # Otherwise, treat the value as a single value that needs only
205
- # one placeholder.
90
+ return value .name
91
+ # If it is anything else, we treat it as a value.
206
92
else :
207
- if value [0 ] != ":" :
208
- value_placeholder = self ._get_value_placeholder ()
209
- self ._value_count += 1
210
- # Store DDB JSON
211
- attribute_value_placeholders [value_placeholder ] = list (dict_to_ddb ({value_placeholder : value }).values ())[0 ]
212
- return value_placeholder
213
- else :
214
- return value
93
+ if condition .has_grouped_values :
94
+ # Assuming the values are grouped by parenthesis.
95
+ # IN is the currently the only one that uses this so it maybe
96
+ # needed to be changed in future.
97
+ return '(' + ', ' .join (value ) + ')'
98
+ return value
0 commit comments