Skip to content

Commit 4cdfd7f

Browse files
m
1 parent 0e41bed commit 4cdfd7f

File tree

3 files changed

+135
-309
lines changed

3 files changed

+135
-309
lines changed

DynamoDbEncryption/runtimes/python/src/aws_database_encryption_sdk/encryptor/table.py

Lines changed: 1 addition & 306 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,6 @@
2929
DynamoDbTablesEncryptionConfig,
3030
)
3131

32-
from boto3.dynamodb.conditions import (
33-
Key,
34-
Equals,
35-
NotEquals,
36-
LessThan,
37-
LessThanEquals,
38-
GreaterThan,
39-
GreaterThanEquals,
40-
In,
41-
Between,
42-
BeginsWith,
43-
And,
44-
Or,
45-
Contains,
46-
)
47-
from boto3.dynamodb.types import TypeSerializer
48-
49-
from boto3.dynamodb.conditions import Key, Attr
50-
from boto3.dynamodb.types import TypeSerializer
51-
52-
from boto3.dynamodb.conditions import Equals, LessThan, GreaterThan, BeginsWith, Between
53-
from boto3.dynamodb.types import TypeSerializer
5432

5533
def transform_transformed_response_for_table_query_or_scan_response(sdk_output, transformed_response):
5634
response = transformed_response
@@ -81,20 +59,7 @@ def transform_transformed_request_for_query_or_scan_on_table(transformed_request
8159
for name, ddb_value in transformed_request["ExpressionAttributeValues"].items():
8260
dict_item = ddb_to_dict({name: ddb_value})
8361
transformed_request["ExpressionAttributeValues"][list(dict_item.keys())[0]] = list(dict_item.values())[0]
84-
85-
# substitution_map = {}
86-
# tokenized_original_string = untransformed_request["KeyConditionExpression"].split(" ")
87-
# tokenized_transformed_string = untransformed_request["KeyConditionExpression"].split(" ")
88-
# assert len(tokenized_original_string) == len(tokenized_transformed_string)
89-
# for i in range(len(tokenized_original_string)):
90-
# original = tokenized_original_string[i]
91-
# transformed = tokenized_original_string[i]
92-
# if original != transformed:
93-
# substitution_map[original] = transformed
94-
95-
96-
97-
62+
9863
return transformed_request
9964

10065

@@ -150,9 +115,6 @@ def transform_query_or_scan_input_for_internal_transformer(**kwargs):
150115
kwargs["ExpressionAttributeValues"] = expression_attribute_values
151116
return kwargs
152117

153-
def is_number(obj):
154-
return isinstance(obj, (int, float, Decimal))
155-
156118
def convert_conditions_to_client_expression(condition, expression_attribute_names, expression_attribute_values):
157119
"""
158120
Converts a Key/Attr condition into a client-compatible DynamoDB expression.
@@ -164,279 +126,12 @@ def convert_conditions_to_client_expression(condition, expression_attribute_name
164126
- ExpressionAttributeNames dictionary
165127
"""
166128

167-
# from boto3.dynamodb.conditions import ConditionExpressionBuilder
168129
from aws_database_encryption_sdk.internal.condition_expression_builder import InternalDBESDKDynamoDBConditionExpressionBuilder
169130

170131
a = InternalDBESDKDynamoDBConditionExpressionBuilder()
171132
out = a.build_expression(condition, expression_attribute_names, expression_attribute_values)
172-
print(f'our {out=}')
173133
return out
174134

175-
def _recurse(condition, expression_attribute_names, expression_attribute_values):
176-
expression = condition.get_expression()
177-
expression_format = expression["format"]
178-
expression_operator = expression["operator"]
179-
expression_values = expression["values"]
180-
processed_expression_values = []
181-
182-
for elem in expression_values:
183-
if isinstance(elem, Key) or isinstance(elem, Attr):
184-
attribute_name = elem.name
185-
# if attribute_name in expression_attribute_names:
186-
# placeholder_name = expression_attribute_names[attribute_name]
187-
# else:
188-
# placeholder_name = "#pln" + str(len(expression_attribute_names))
189-
# expression_attribute_names[placeholder_name] = attribute_name
190-
elem = attribute_name
191-
elif isinstance(elem, Equals) or isinstance(elem, And) or isinstance(elem, Or) or isinstance(elem, Between) or isinstance(elem, BeginsWith) or isinstance(elem, GreaterThanEquals) or isinstance(elem, NotEquals) or isinstance(elem, Contains):
192-
elem, expression_attribute_names, expression_attribute_values = _recurse(elem, expression_attribute_names, expression_attribute_values)
193-
elif isinstance(elem, str) or is_number(elem):
194-
# assume it's a value...
195-
attribute_value = elem
196-
# # if attribute_value in expression_attribute_values:
197-
# # placeholder_value = expression_attribute_values[attribute_value]
198-
# # else:
199-
# placeholder_value = ":plv" + str(len(expression_attribute_values))
200-
# expression_attribute_values[placeholder_value] = attribute_value
201-
elem = attribute_value
202-
elif isinstance(elem, list):
203-
s = "("
204-
for list_elem in elem:
205-
s += str(list_elem)
206-
s += ")"
207-
elem = s
208-
else:
209-
print(f"unknown {elem=}")
210-
print(f"unknown {elem.__dict__=}")
211-
print(f"unknown {dir(elem)=}")
212-
raise ValueError(f"{elem=}")
213-
processed_expression_values.append(elem)
214-
output = expression_format.format(*processed_expression_values, operator = expression_operator)
215-
return (output, expression_attribute_names, expression_attribute_values)
216-
217-
condition_expression, expression_attribute_names, expression_attribute_values = _recurse(condition, expression_attribute_names, expression_attribute_values)
218-
print(f"{(condition_expression, expression_attribute_names, expression_attribute_values)=}")
219-
# expression_attribute_names = {v: k for k, v in swapped_expression_attribute_names.items() if }
220-
# expression_attribute_values = {v: k for k, v in swapped_expression_attribute_values.items()}
221-
return (condition_expression, expression_attribute_names, expression_attribute_values)
222-
223-
224-
def process_condition(condition):
225-
nonlocal expression, expression_attribute_values, expression_attribute_names
226-
227-
if isinstance(condition, Equals):
228-
attr_name = f"#{condition.attr_name}"
229-
placeholder = f":val_{len(expression_attribute_values)}"
230-
expression_attribute_names[attr_name] = condition.attr_name
231-
expression_attribute_values[placeholder] = serializer.serialize(condition.values[0])
232-
return f"{attr_name} = {placeholder}"
233-
elif isinstance(condition, LessThan):
234-
attr_name = f"#{condition.attr_name}"
235-
placeholder = f":val_{len(expression_attribute_values)}"
236-
expression_attribute_names[attr_name] = condition.attr_name
237-
expression_attribute_values[placeholder] = serializer.serialize(condition.values[0])
238-
return f"{attr_name} < {placeholder}"
239-
elif isinstance(condition, GreaterThan):
240-
attr_name = f"#{condition.attr_name}"
241-
placeholder = f":val_{len(expression_attribute_values)}"
242-
expression_attribute_names[attr_name] = condition.attr_name
243-
expression_attribute_values[placeholder] = serializer.serialize(condition.values[0])
244-
return f"{attr_name} > {placeholder}"
245-
elif isinstance(condition, BeginsWith):
246-
attr_name = f"#{condition.attr_name}"
247-
placeholder = f":val_{len(expression_attribute_values)}"
248-
expression_attribute_names[attr_name] = condition.attr_name
249-
expression_attribute_values[placeholder] = serializer.serialize(condition.values[0])
250-
return f"begins_with({attr_name}, {placeholder})"
251-
elif isinstance(condition, Between):
252-
attr_name = f"#{condition.attr_name}"
253-
placeholder1 = f":val_{len(expression_attribute_values)}"
254-
placeholder2 = f":val_{len(expression_attribute_values) + 1}"
255-
expression_attribute_names[attr_name] = condition.attr_name
256-
expression_attribute_values[placeholder1] = serializer.serialize(condition.values[0])
257-
expression_attribute_values[placeholder2] = serializer.serialize(condition.values[1])
258-
return f"{attr_name} BETWEEN {placeholder1} AND {placeholder2}"
259-
else:
260-
raise ValueError(f"Unsupported condition type: {type(condition).__name__}")
261-
262-
expression = process_condition(condition)
263-
264-
return expression, expression_attribute_values, expression_attribute_names
265-
# from boto3.dynamodb.conditions import Key, Attr
266-
# from boto3.dynamodb.types import TypeDeserializer
267-
268-
# def convert_client_expression_to_conditions(expression, expression_values, expression_names=None):
269-
# """
270-
# Converts DynamoDB client-compatible expressions into Key or Attr conditions.
271-
272-
# :param expression: A string of the DynamoDB client expression (e.g., "RecNum = :zero").
273-
# :param expression_values: A dictionary of attribute values (e.g., {":zero": {"N": "0"}}).
274-
# :param expression_names: A dictionary of attribute names, if placeholders are used (e.g., {"#attr": "RecNum"}).
275-
# :return: A boto3.dynamodb.conditions object (Key or Attr).
276-
# """
277-
# # Split the expression into attribute and value
278-
# tokens = expression.split()
279-
# # if len(tokens) != 3:
280-
# # raise ValueError("Only simple expressions (e.g., 'AttrName = :val') are supported.")
281-
282-
# attr_name = tokens[0] # The attribute name or placeholder
283-
# operator = tokens[1].upper() # The operator (e.g., '=', '<', '>', "BETWEEN")
284-
# attr_value_placeholder = tokens[2] # The value placeholder (e.g., ":zero")
285-
286-
# # Map operator to Key or Attr
287-
# if operator == "=":
288-
# return Key(attr_name).eq(attr_value_placeholder)
289-
# elif operator == "<":
290-
# return Key(attr_name).lt(attr_value_placeholder)
291-
# elif operator == "<=":
292-
# return Key(attr_name).lte(attr_value_placeholder)
293-
# elif operator == ">":
294-
# return Key(attr_name).gt(attr_value_placeholder)
295-
# elif operator == ">=":
296-
# return Key(attr_name).gte(attr_value_placeholder)
297-
# elif operator == "begins_with":
298-
# return Key(attr_name).begins_with(attr_value_placeholder)
299-
# elif operator == "BETWEEN":
300-
# raise NotImplementedError("BETWEEN is not currently supported by this function.")
301-
# else:
302-
# # If no Key match, assume Attr condition
303-
# if operator == "!=" or operator == "<>":
304-
# return Attr(attr_name).ne(attr_value_placeholder)
305-
# elif operator == "contains":
306-
# return Attr(attr_name).contains(attr_value_placeholder)
307-
# else:
308-
# raise ValueError(f"Unsupported operator: {operator}")
309-
310-
from boto3.dynamodb.conditions import Key, Attr, And, Or, Not
311-
from boto3.dynamodb.types import TypeDeserializer
312-
313-
314-
def convert_client_expression_to_conditions(expression):
315-
"""
316-
Crypto Tools internal method to convert a DynamoDB filter/key expression to boto3 Resource tokens.
317-
DO NOT USE FOR ANY OTHER PURPOSE.
318-
This is a basic implementation for simple expressions that will fail with complex expressions.
319-
320-
To extend this to support one or a few complex expressions, consider extending the existing logic.
321-
To extend this to support all expressions, consider implementing and extending the code below:
322-
323-
```
324-
from aws_database_encryption_sdk.internaldafny.generated.DynamoDBFilterExpr import default__ as filter_expr
325-
import _dafny
326-
from smithy_dafny_standard_library.internaldafny.generated import Wrappers
327-
328-
dafny_expr_token = filter_expr.ParseExpr(
329-
_dafny.Seq(
330-
expression
331-
),
332-
)
333-
```
334-
335-
This library's generated internal Dafny code has a DynamoDB string parser.
336-
This will parse a _dafny.Seq and produce Dafny tokens for the expression.
337-
Implementing this will involve
338-
1. Mapping Dafny tokens to boto3 Resource tokens.
339-
(e.g. class Token_Between -> boto3.dynamodb.conditions.Between)
340-
2. Converting Dafny token grammar to boto3 Resource token grammar.
341-
(e.g.
342-
Dafny: [Token_Between, Token_Open, Token_Attr, Token_And, Token_Attr, Token_Close]
343-
->
344-
boto3: [Between(Attr, Attr)]
345-
)
346-
347-
:param expression: A string of the DynamoDB client expression (e.g., "AttrName = :val").
348-
:param expression_values: A dictionary of attribute values (e.g., {":val": {"N": "0"}}).
349-
:param expression_names: A dictionary of attribute names, if placeholders are used (e.g., {"#attr": "AttrName"}).
350-
:return: A boto3.dynamodb.conditions object (Key, Attr, or a combination of them).
351-
"""
352-
353-
# Recursive parser for complex expressions
354-
def parse_expression(expr_tokens):
355-
# simple between
356-
if "BETWEEN" == expr_tokens[1].upper():
357-
attr_name = expr_tokens[0]
358-
value1 = expr_tokens[2]
359-
value2 = expr_tokens[4]
360-
return Key(attr_name).between(value1, value2)
361-
362-
# simple in
363-
elif "IN" == expr_tokens[1].upper():
364-
print(f"IN {expr_tokens=}")
365-
attr_name = expr_tokens[0]
366-
values_in_list = expr_tokens[3:-1]
367-
for i in range(len(values_in_list)):
368-
if values_in_list[i][-1] == ",":
369-
values_in_list[i] = values_in_list[i][:-1]
370-
return Attr(attr_name).is_in(values_in_list)
371-
372-
# simple contains
373-
elif "CONTAINS" == expr_tokens[0].upper():
374-
attr_name = expr_tokens[2]
375-
if attr_name[-1] == ",":
376-
attr_name = attr_name[:-1]
377-
value = expr_tokens[3]
378-
return Attr(attr_name).contains(value)
379-
380-
# simple begins_with
381-
elif "BEGINS_WITH" == expr_tokens[0].upper():
382-
attr_name = expr_tokens[2]
383-
if attr_name[-1] == ",":
384-
attr_name = attr_name[:-1]
385-
value = expr_tokens[3]
386-
return Attr(attr_name).begins_with(value)
387-
388-
# Base case: Single comparison or condition
389-
if "AND" not in [t.upper() for t in expr_tokens] and "OR" not in [t.upper() for t in expr_tokens]:
390-
391-
# simple comparison
392-
attr_name = expr_tokens[0]
393-
operator = expr_tokens[1].upper()
394-
value = expr_tokens[2]
395-
396-
# Map operator to Key or Attr
397-
if operator == "=":
398-
return Key(attr_name).eq(value)
399-
elif operator == "<":
400-
return Key(attr_name).lt(value)
401-
elif operator == "<=":
402-
return Key(attr_name).lte(value)
403-
elif operator == ">":
404-
return Key(attr_name).gt(value)
405-
elif operator == ">=":
406-
return Key(attr_name).gte(value)
407-
elif operator in ("!=", "<>"):
408-
return Attr(attr_name).ne(value)
409-
else:
410-
raise ValueError(f"Unsupported operator: {operator}")
411-
412-
# Recursive case: Logical AND/OR
413-
stack = []
414-
current_expr = []
415-
416-
for token in expr_tokens:
417-
if token.upper() in ("AND", "OR"):
418-
left = parse_expression(current_expr)
419-
current_expr = []
420-
stack.append((left, token)) # Save the left condition and operator
421-
else:
422-
current_expr.append(token)
423-
424-
# Handle the final condition on the right
425-
right = parse_expression(current_expr)
426-
427-
# Combine the stack of conditions
428-
while stack:
429-
left, operator = stack.pop()
430-
if operator.upper() == "AND":
431-
right = And(left, right)
432-
elif operator.upper() == "OR":
433-
right = Or(left, right)
434-
435-
return right
436-
437-
# Tokenize the expression and parse it
438-
tokens = expression.replace("(", " ( ").replace(")", " ) ").split()
439-
return parse_expression(tokens)
440135

441136
class EncryptedTable:
442137

DynamoDbEncryption/runtimes/python/src/aws_database_encryption_sdk/internal/condition_expression_builder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class InternalDBESDKDynamoDBConditionExpressionBuilder:
2020
https://github.com/boto/boto3/blob/c5c634b53be589d6e913e6dca51ad8d6480f58c7/boto3/dynamodb/conditions.py#L304
2121
The original class is intended to interface from boto3 Table resources to boto3 clients,
2222
and has logic to replace attribute names and values with placeholders.
23-
This class is intended to interface from boto3 Table resources to internal DBESDK DyanmoDB operation transformers,
24-
which does not expect inserted placeholders.
25-
The placeholder insertion logic has been removed from this class along with its supporting logic,
23+
The class defined here is intended to interface from boto3 Table resources
24+
to internal DBESDK DynamoDB operation transformers, which does not expect inserted placeholders.
25+
The placeholder insertion logic has been removed from this class, along with supporting logic,
2626
but this class maintains boto3's recursive ConditionExpression traversal logic.
2727
"""
2828

0 commit comments

Comments
 (0)