alternator: nicer error message for integer overflow in list index

In the DynamoDB API, when "a" is a list attribute, a[999] returns the
1000th element. But if the list isn't that long (e.g., it only has 5
elements), a[999] returns nothing - it's not an error.

But it turns out that when the index is so long that it can't even be
parsed as an integer, e.g., 99999999999999, DynamoDB does report an
error:

    Invalid ProjectionExpression: List index is not within the
    allowable range; index: [99999999999999]

Before this patch, Alternator also returned an error in this case,
with the right type (ValidationException), but with a strange low-level
error text:

    Failed parsing ProjectionExpression 'a[99999999999999]':
    std::out_of_range (stoi)

The problem was that the code (in alternator/expressions.g) ran stoi()
without converting its std::out_of_range exception to a better user-facing
message. We do this in this patch, and the error message now looks like:

    Failed parsing ProjectionExpression 'a[99999999999999]':
    list index out of integer range

This patch also includes a test reproducing this error, which passes
on DynamDB and on Alternator it fails before this patch and passes with
the patch.

Fixes #25947

Signed-off-by: Nadav Har'El <nyh@scylladb.com>

Closes scylladb/scylladb#25951
This commit is contained in:
Nadav Har'El
2025-09-10 15:37:40 +03:00
committed by Pavel Emelyanov
parent 208d3986a7
commit b4e3d4ac2f
2 changed files with 38 additions and 1 deletions

View File

@@ -184,7 +184,13 @@ path_component: NAME | NAMEREF;
path returns [parsed::path p]:
root=path_component { $p.set_root($root.text); }
( '.' name=path_component { $p.add_dot($name.text); }
| '[' INTEGER ']' { $p.add_index(std::stoi($INTEGER.text)); }
| '[' INTEGER ']' {
try {
$p.add_index(std::stoi($INTEGER.text));
} catch(std::out_of_range&) {
throw expressions_syntax_error("list index out of integer range");
}
}
)*;
/* See comment above why the "depth" counter was needed here */

View File

@@ -347,3 +347,34 @@ def test_projection_expression_path_nesting_levels(test_table_s):
# nesting levels: 33".
with pytest.raises(ClientError, match='ValidationException.*nesting levels'):
test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a'+('.b'*32))
# Above we already checked different cases of reading individual elements
# from a list - the expression a[i]. The following test exercises these
# list indexes more rigourously, including testing what happens when the
# index overflows an integer (reproducing #25947).
def test_projection_expression_list_index(test_table_s):
p = random_string()
test_table_s.put_item(Item={'p': p, 'a': [7, 42]})
# a[0] and a[1] return the elements from the list, as expected
# (note that a[i] actually returns an array with a single element a[i])
assert {'a': [7]} == test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[0]')['Item']
assert {'a': [42]} == test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[1]')['Item']
# If the index is beyond the length of the array, such as a[2] or a[999],
# we expect to get back an empty Item - not an error, and not missing Item.
assert {} == test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[2]')['Item']
assert {} == test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[999]')['Item']
# If the index is so high that it can't be parsed as an integer, it isn't
# silently ignored like 999 above, but causes a parse error. DynamoDB
# reports: "Invalid ProjectionExpression: List index is not within the
# allowable range; index: [99999999999999]". After fixing #25947,
# Alternator reports: "Failed parsing ProjectionExpression
# 'a[99999999999999]': list index out of integer range".
with pytest.raises(ClientError, match='ValidationException.*ProjectionExpression.*index'):
test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[99999999999999]')['Item']
# Trying to use a negative number as an index, like a[-1], is just a
# syntax error - the parser expects to see digits, not "-".
with pytest.raises(ClientError, match='ValidationException.*ProjectionExpression.*[Ss]yntax error'):
test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[-1]')['Item']
# A completely missing index - a[] - is also a syntax error:
with pytest.raises(ClientError, match='ValidationException.*ProjectionExpression.*[Ss]yntax error'):
test_table_s.get_item(Key={'p': p}, ConsistentRead=True, ProjectionExpression='a[]')['Item']