Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add attribute name use checks to validation layer #8612

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions moto/dynamodb/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@
from moto.dynamodb.models.table_import import TableImport
from moto.dynamodb.parsing import partiql
from moto.dynamodb.parsing.executors import UpdateExpressionExecutor
from moto.dynamodb.parsing.expressions import ( # type: ignore
ExpressionAttributeName,
UpdateExpressionParser,
)
from moto.dynamodb.parsing.expressions import UpdateExpressionParser # type: ignore
from moto.dynamodb.parsing.validators import UpdateExpressionValidator


Expand Down Expand Up @@ -530,23 +527,6 @@ def update_item(
except ItemSizeTooLarge:
raise ItemSizeToUpdateTooLarge()

# Ensure all ExpressionAttributeNames are requested
# Either in the Condition, or in the UpdateExpression
attr_name_clauses = update_expression_ast.find_clauses(
[ExpressionAttributeName]
)
attr_names_in_expression = [
attr.get_attribute_name_placeholder() for attr in attr_name_clauses
]
attr_names_in_condition = condition_expression_parser.expr_attr_names_found
for attr_name in expression_attribute_names or []:
if (
attr_name not in attr_names_in_expression
and attr_name not in attr_names_in_condition
):
raise MockValidationException(
f"Value provided in ExpressionAttributeNames unused in expressions: keys: {{{attr_name}}}"
)
else:
item.update_with_attribute_updates(attribute_updates) # type: ignore
if table.stream_shard is not None:
Expand Down
28 changes: 18 additions & 10 deletions moto/dynamodb/parsing/key_condition_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def parse_expression(
expression_attribute_values: Dict[str, Dict[str, str]],
expression_attribute_names: Dict[str, str],
schema: List[Dict[str, str]],
) -> Tuple[Dict[str, Any], Optional[str], List[Dict[str, Any]]]:
) -> Tuple[Dict[str, Any], Optional[str], List[Dict[str, Any]], List[str]]:
"""
Parse a KeyConditionExpression using the provided expression attribute names/values

Expand All @@ -37,6 +37,7 @@ def parse_expression(
current_phrase = ""
key_name = comparison = ""
key_values: List[Union[Dict[str, str], str]] = []
expression_attribute_names_used: List[str] = []
results: List[Tuple[str, str, Any]] = []
tokenizer = GenericTokenizer(key_condition_expression)
for crnt_char in tokenizer:
Expand All @@ -50,9 +51,11 @@ def parse_expression(
else:
# start_date < :sk and primary = :pk
# ^
key_name = expression_attribute_names.get(
current_phrase, current_phrase
)
if expression_attribute_names.get(current_phrase):
key_name = expression_attribute_names[current_phrase]
expression_attribute_names_used.append(current_phrase)
else:
key_name = current_phrase
current_phrase = ""
current_stage = EXPRESSION_STAGES.COMPARISON
tokenizer.skip_white_space()
Expand Down Expand Up @@ -103,9 +106,11 @@ def parse_expression(
EXPRESSION_STAGES.KEY_NAME,
EXPRESSION_STAGES.INITIAL_STAGE,
]:
key_name = expression_attribute_names.get(
current_phrase, current_phrase
)
if expression_attribute_names.get(current_phrase):
key_name = expression_attribute_names[current_phrase]
expression_attribute_names_used.append(current_phrase)
else:
key_name = current_phrase
current_phrase = ""
if crnt_char in ["<", ">"] and tokenizer.peek() == "=":
comparison = crnt_char + tokenizer.__next__()
Expand All @@ -118,9 +123,11 @@ def parse_expression(
if current_stage == EXPRESSION_STAGES.KEY_NAME:
# hashkey = :id and begins_with(sortkey, :sk)
# ^ --> ^
key_name = expression_attribute_names.get(
current_phrase, current_phrase
)
if expression_attribute_names.get(current_phrase):
key_name = expression_attribute_names[current_phrase]
expression_attribute_names_used.append(current_phrase)
else:
key_name = current_phrase
current_phrase = ""
current_stage = EXPRESSION_STAGES.KEY_VALUE
tokenizer.skip_white_space()
Expand Down Expand Up @@ -192,6 +199,7 @@ def parse_expression(
hash_value,
range_comparison.upper() if range_comparison else None,
range_values,
expression_attribute_names_used,
)


Expand Down
Loading