Repository: getmoto/moto

Issue:
[moto]IndexError: list index out of range
I am having issue with moto dynamo db batch_execute_stetement. The details are below.

**Test Code:**
```
import pytest
from moto import mock_dynamodb
import boto3


@pytest.fixture(autouse=True)
def mock_boto():
    mock_dynamodb().start()
    ddb_res = boto3.resource('dynamodb')
    table = ddb_res.create_table(
        TableName='users',
        KeySchema=[{'AttributeName': 'username', 'KeyType': 'HASH'}],
        AttributeDefinitions=[{'AttributeName': 'username', 'AttributeType': 'S'}],
        ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}
    )
    table.put_item(Item={'username': 'XXXX', 'first_name': 'test1', 'last_name': 'test2'})
    table.put_item(Item={'username': 'YYYY', 'first_name': 'test3', 'last_name': 'test4'})
    yield
    mock_dynamodb().stop()


def test_batch_exec():
    ddb_cli = boto3.client('dynamodb')
    statements = [
        {
            'Statement': 'UPDATE users SET "first_name" = ?, "last_name" = ? WHERE "username"= ?',
            'Parameters': [{"S": "test5"}, {"S": "test6"}]
        }
    ]
    response = ddb_cli.batch_execute_statement(
        Statements=statements
    )
    ddb_res = boto3.resource('dynamodb')
    table = ddb_res.Table('users')
    res = table.scan()
    print(res["Items"])
```

**Error I am getting when running the test:** 

```
 @classmethod
 def get_query_metadata(cls, query: str):
     query = query.replace("\n", " ")
     clauses = re.split("SELECT | FROM | WHERE ", query, flags=re.IGNORECASE)
     from_clauses = FromParser().parse(clauses[2])
     IndexError: list index out of range
```



Discussion:
Hi @r4m4k0735w4r4r40, which version of `moto` and `py_partiql_parser` do you have installed?
Hi @bblommers I am using py-partiql-parser==0.4.1 and moto==4.2.14
Moto 4.2.14 requires `py_partiql_parser==0.5.0` - see https://github.com/getmoto/moto/blob/24b94fc192dccb311f07d2e8246d0ab27f3d38dc/setup.cfg#L59

You might still run into other errors though, as I don't know how the parser behaves when three parameters are required, but only two are passed (`test5` and `test6`)
Updated the `py_partiql_parser` version to `0.5.0`. But still getting same error.

Resolving patch:
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -855,27 +855,31 @@ def batch_execute_statement(
                 }
             else:
                 response["TableName"] = table_name
-                table = self.tables[table_name]
-                for required_attr in table.table_key_attrs:
-                    if required_attr not in filter_keys:
-                        response["Error"] = {
-                            "Code": "ValidationError",
-                            "Message": "Select statements within BatchExecuteStatement must specify the primary key in the where clause.",
-                        }
+                if metadata.is_select_query():
+                    table = self.tables[table_name]
+                    for required_attr in table.table_key_attrs:
+                        if required_attr not in filter_keys:
+                            response["Error"] = {
+                                "Code": "ValidationError",
+                                "Message": "Select statements within BatchExecuteStatement must specify the primary key in the where clause.",
+                            }
             responses.append(response)
 
         # Execution
         for idx, stmt in enumerate(statements):
             if "Error" in responses[idx]:
                 continue
-            items = self.execute_statement(
-                statement=stmt["Statement"], parameters=stmt.get("Parameters", [])
-            )
-            # Statements should always contain a HashKey and SortKey
-            # An item with those keys may not exist
-            if items:
-                # But if it does, it will always only contain one item at most
-                responses[idx]["Item"] = items[0]
+            try:
+                items = self.execute_statement(
+                    statement=stmt["Statement"], parameters=stmt.get("Parameters", [])
+                )
+                # Statements should always contain a HashKey and SortKey
+                # An item with those keys may not exist
+                if items:
+                    # But if it does, it will always only contain one item at most
+                    responses[idx]["Item"] = items[0]
+            except Exception as e:
+                responses[idx] = {"Error": {"Code": e.name, "Message": e.message}}  # type: ignore
         return responses
 
 


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb_statements.py b/tests/test_dynamodb/test_dynamodb_statements.py
--- a/tests/test_dynamodb/test_dynamodb_statements.py
+++ b/tests/test_dynamodb/test_dynamodb_statements.py
@@ -334,6 +334,75 @@ def test_update_data(table_name=None):
     assert item2 in items
 
 
+@mock_aws
+def test_batch_update__not_enough_parameters():
+    ddb_cli = boto3.client("dynamodb", "us-east-1")
+    ddb_res = boto3.resource("dynamodb", "us-east-1")
+    ddb_res.create_table(
+        TableName="users",
+        KeySchema=[{"AttributeName": "username", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "username", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+    )
+
+    statements = [
+        {
+            "Statement": 'UPDATE users SET "first_name" = ?, "last_name" = ? WHERE "username"= ?',
+            "Parameters": [{"S": "test5"}, {"S": "test6"}],
+        }
+    ]
+    resp = ddb_cli.batch_execute_statement(Statements=statements)["Responses"]
+    assert resp == [
+        {
+            "Error": {
+                "Code": "ValidationError",
+                "Message": "Number of parameters in request and statement don't match.",
+            }
+        }
+    ]
+
+
+@mock_aws
+def test_batch_update():
+    ddb_cli = boto3.client("dynamodb", "us-east-1")
+    ddb_res = boto3.resource("dynamodb", "us-east-1")
+    table = ddb_res.create_table(
+        TableName="users",
+        KeySchema=[{"AttributeName": "username", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "username", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+    )
+    table.put_item(
+        Item={"username": "XXXX", "first_name": "test1", "last_name": "test2"}
+    )
+    table.put_item(
+        Item={"username": "YYYY", "first_name": "test3", "last_name": "test4"}
+    )
+
+    statements = [
+        {
+            "Statement": 'UPDATE users SET "first_name" = ?, "last_name" = ? WHERE "username"= ?',
+            "Parameters": [{"S": "test5"}, {"S": "test6"}, {"S": "XXXX"}],
+        },
+        {"Statement": "DELETE FROM users WHERE username='YYYY'"},
+        {"Statement": "INSERT INTO users value {'username': 'new'}"},
+    ]
+    response = ddb_cli.batch_execute_statement(Statements=statements)["Responses"]
+    assert response == [
+        {"TableName": "users"},
+        {"TableName": "users"},
+        {"TableName": "users"},
+    ]
+
+    users = ddb_res.Table("users").scan()["Items"]
+    assert len(users) == 2
+
+    # Changed
+    assert {"username": "XXXX", "first_name": "test5", "last_name": "test6"} in users
+    # New
+    assert {"username": "new"} in users
+
+
 @pytest.mark.aws_verified
 @dynamodb_aws_verified()
 def test_delete_data(table_name=None):