Repository: getmoto/moto

Issue:
DynamoDB query fails when using GSI with range key if target table has record that have no range key attribute
## Summary
mock_dynamodb2 query method will fail with `AttributeError: 'NoneType' object has no attribute 'compare'`
when target table has GSI with range key and target table has record that have no GSI range key attribute


## Reproduce procedure

- Create table and define GSI that contains hash key and sort(range) key
- Put record thant has no GSI range key attributes
- Query record using GSI
- Query will fail

  
```
import moto
import boto3

moto.mock_dynamodb2().start()

dynamodb = boto3.client('dynamodb', region_name='us-east-1')
dynamodb.create_table(
    TableName='test',
    KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
    AttributeDefinitions=[
        {'AttributeName': 'id', 'AttributeType': 'S'},
        {'AttributeName': 'gsi_hash_key', 'AttributeType': 'S'},
        {'AttributeName': 'gsi_range_key', 'AttributeType': 'S'}
    ],
    ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1},
    GlobalSecondaryIndexes=[
        {
            'IndexName': 'test_gsi',
            'KeySchema': [
                {
                    'AttributeName': 'gsi_hash_key',
                    'KeyType': 'HASH'
                },
                {
                    'AttributeName': 'gsi_range_key',
                    'KeyType': 'RANGE'
                },
            ],
            'Projection': {
                'ProjectionType': 'ALL',
            },
            'ProvisionedThroughput': {
                'ReadCapacityUnits': 1,
                'WriteCapacityUnits': 1
            }
        },
    ]
)

dynamodb.put_item(
  TableName='test',
  Item={
      'id': {'S': 'test1'},
      'gsi_hash_key': {'S': 'key1'},
      'gsi_range_key': {'S': 'range1'},
  }
)
dynamodb.put_item(
  TableName='test',
  Item={
      'id': {'S': 'test2'},
      'gsi_hash_key': {'S': 'key1'},
  }
)

dynamodb.query(TableName='test', IndexName='test_gsi',
  KeyConditionExpression='gsi_hash_key = :gsi_hash_key AND gsi_range_key = :gsi_range_key',
  ExpressionAttributeValues={
    ':gsi_hash_key': {'S': 'key1'},
    ':gsi_range_key': {'S': 'range1'}
  })

```

I expect this code will return 1 item but moto's mock_dynamodb will fail

## Tracebacks
```
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/client.py", line 648, in _make_api_call
    operation_model, request_dict, request_context)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/client.py", line 667, in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/endpoint.py", line 102, in make_request
    return self._send_request(request_dict, operation_model)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/endpoint.py", line 137, in _send_request
    success_response, exception):
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/endpoint.py", line 231, in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 356, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 183, in __call__
    if self._checker(attempts, response, caught_exception):
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 251, in __call__
    caught_exception)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 269, in _should_retry
    return self._checker(attempt_number, response, caught_exception)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 317, in __call__
    caught_exception)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 223, in __call__
    attempt_number, caught_exception)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/retryhandler.py", line 359, in _check_caught_exception
    raise caught_exception
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/endpoint.py", line 197, in _do_get_response
    responses = self._event_emitter.emit(event_name, request=request)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 356, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/.venv/lib/python3.6/site-packages/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/core/models.py", line 298, in __call__
    status, headers, body = response_callback(request, request.url, request.headers)
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/core/responses.py", line 117, in dispatch
    return cls()._dispatch(*args, **kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/core/responses.py", line 200, in _dispatch
    return self.call_action()
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/core/utils.py", line 264, in _wrapper
    response = f(*args, **kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/dynamodb2/responses.py", line 125, in call_action
    response = getattr(self, endpoint)()
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/dynamodb2/responses.py", line 514, in query
    filter_expression=filter_expression, **filter_kwargs
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/dynamodb2/models.py", line 889, in query
    exclusive_start_key, scan_index_forward, projection_expression, index_name, filter_expression, **filter_kwargs)
  File "/Users/xxxxxxxxxxxxxxx/moto/moto/dynamodb2/models.py", line 614, in query
    if result.attrs.get(index_range_key['AttributeName']).compare(range_comparison, range_objs):
AttributeError: 'NoneType' object has no attribute 'compare'
```

## moto and boto version
- boto3 (1.9.121)
- botocore (1.12.121)
- moto (1.3.7)



Discussion:

Resolving patch:
diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py
--- a/moto/dynamodb2/models.py
+++ b/moto/dynamodb2/models.py
@@ -570,6 +570,7 @@ def query(self, hash_key, range_comparison, range_objs, limit,
               exclusive_start_key, scan_index_forward, projection_expression,
               index_name=None, filter_expression=None, **filter_kwargs):
         results = []
+
         if index_name:
             all_indexes = (self.global_indexes or []) + (self.indexes or [])
             indexes_by_name = dict((i['IndexName'], i) for i in all_indexes)
@@ -586,24 +587,28 @@ def query(self, hash_key, range_comparison, range_objs, limit,
                 raise ValueError('Missing Hash Key. KeySchema: %s' %
                                  index['KeySchema'])
 
+            try:
+                index_range_key = [key for key in index[
+                    'KeySchema'] if key['KeyType'] == 'RANGE'][0]
+            except IndexError:
+                index_range_key = None
+
             possible_results = []
             for item in self.all_items():
                 if not isinstance(item, Item):
                     continue
                 item_hash_key = item.attrs.get(index_hash_key['AttributeName'])
-                if item_hash_key and item_hash_key == hash_key:
-                    possible_results.append(item)
+                if index_range_key is None:
+                    if item_hash_key and item_hash_key == hash_key:
+                        possible_results.append(item)
+                else:
+                    item_range_key = item.attrs.get(index_range_key['AttributeName'])
+                    if item_hash_key and item_hash_key == hash_key and item_range_key:
+                        possible_results.append(item)
         else:
             possible_results = [item for item in list(self.all_items()) if isinstance(
                 item, Item) and item.hash_key == hash_key]
 
-        if index_name:
-            try:
-                index_range_key = [key for key in index[
-                    'KeySchema'] if key['KeyType'] == 'RANGE'][0]
-            except IndexError:
-                index_range_key = None
-
         if range_comparison:
             if index_name and not index_range_key:
                 raise ValueError(


Test patch:
diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py
--- a/tests/test_dynamodb2/test_dynamodb.py
+++ b/tests/test_dynamodb2/test_dynamodb.py
@@ -1612,3 +1612,70 @@ def test_condition_expressions():
                 ':match': {'S': 'match2'}
             }
         )
+
+
+@mock_dynamodb2
+def test_query_gsi_with_range_key():
+    dynamodb = boto3.client('dynamodb', region_name='us-east-1')
+    dynamodb.create_table(
+        TableName='test',
+        KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
+        AttributeDefinitions=[
+            {'AttributeName': 'id', 'AttributeType': 'S'},
+            {'AttributeName': 'gsi_hash_key', 'AttributeType': 'S'},
+            {'AttributeName': 'gsi_range_key', 'AttributeType': 'S'}
+        ],
+        ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1},
+        GlobalSecondaryIndexes=[
+            {
+                'IndexName': 'test_gsi',
+                'KeySchema': [
+                    {
+                        'AttributeName': 'gsi_hash_key',
+                        'KeyType': 'HASH'
+                    },
+                    {
+                        'AttributeName': 'gsi_range_key',
+                        'KeyType': 'RANGE'
+                    },
+                ],
+                'Projection': {
+                    'ProjectionType': 'ALL',
+                },
+                'ProvisionedThroughput': {
+                    'ReadCapacityUnits': 1,
+                    'WriteCapacityUnits': 1
+                }
+            },
+        ]
+    )
+
+    dynamodb.put_item(
+        TableName='test',
+        Item={
+            'id': {'S': 'test1'},
+            'gsi_hash_key': {'S': 'key1'},
+            'gsi_range_key': {'S': 'range1'},
+        }
+    )
+    dynamodb.put_item(
+        TableName='test',
+        Item={
+            'id': {'S': 'test2'},
+            'gsi_hash_key': {'S': 'key1'},
+        }
+    )
+
+    res = dynamodb.query(TableName='test', IndexName='test_gsi',
+                         KeyConditionExpression='gsi_hash_key = :gsi_hash_key AND gsi_range_key = :gsi_range_key',
+                         ExpressionAttributeValues={
+                            ':gsi_hash_key': {'S': 'key1'},
+                            ':gsi_range_key': {'S': 'range1'}
+                         })
+    res.should.have.key("Count").equal(1)
+    res.should.have.key("Items")
+    res['Items'][0].should.equal({
+        'id': {'S': 'test1'},
+        'gsi_hash_key': {'S': 'key1'},
+        'gsi_range_key': {'S': 'range1'},
+    })