Loading records for secondary index

467 Views Asked by At

I am trying to implement user registration and login using DynamoDB. There's three ways a user can login. They can use username, phone Number or Email Id to login. My DynamoDB table has Phone Number as hash in primary index and thus I am able to load it properly using -

- (void)loadUser:(Class)userClass withHashKey:(NSString*)hashkey withBlock:(void(^)(id))completionBlock{
    [[dynamoDBObjectMapper load:userClass hashKey:hashkey rangeKey:nil]
     continueWithBlock:^id(BFTask *task) {
         if (task.error) {
             NSLog(@"The request failed. Error: [%@]", task.error);
         }
         if (task.exception) {
             NSLog(@"The request failed. Exception: [%@]", task.exception);
         }
         if (task.result) {
             if(completionBlock)
                 completionBlock(task.result);
         }
         return nil;
     }];
}

But I am unable to use the same method for Username and Email. I get the following error when I try username or email instead -

2015-04-27 19:01:42.549 Barnc[691:122171] AWSiOSSDKv2 [Verbose] AWSURLResponseSerialization.m line:86 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: [{"__type":"com.amazon.coral.validate#ValidationException","message":"The provided key element does not match the schema"}]
2015-04-27 19:01:42.556 Barnc[691:122171] The request failed. Error: [Error Domain=com.amazonaws.AWSDynamoDBErrorDomain Code=0 "The operation couldn’t be completed. (com.amazonaws.AWSDynamoDBErrorDomain error 0.)" UserInfo=0x17b96ed0 {message=The provided key element does not match the schema, __type=com.amazon.coral.validate#ValidationException}]

Here's the details of the table Table Details

and here's the secondary global indices - Secondary Indices

(Sorry for the images instead of text, I was not sure how to show it properly here.)

Is there something else required to access secondary indices? I am interested in using the last two indices to load user using username or email.

Update:
My model class is different for different hash key because hash key was a class method. Here's what my parent class looks like -

//AmazonUser.h
#import <Foundation/Foundation.h>
#import <AWSDynamoDB/AWSDynamoDB.h>
@class BarncUser;

@interface AmazonUser : AWSDynamoDBObjectModel <AWSDynamoDBModeling>

@property (nonatomic, strong) NSString *phone_no;
@property (nonatomic, strong) NSString *password;
@property (nonatomic, strong) NSString *c2CallPassword;
@property (nonatomic, strong) NSString *activated;
@property (nonatomic, strong) NSMutableArray *catalog_ids;
@property (nonatomic, strong) NSNumber *defaultOnlineStatus;
@property (nonatomic, strong) NSNumber *signed_in_counts;
@property (nonatomic, strong) NSNumber *total_referrals;
@property (nonatomic, strong) NSString *account_creation_date;
@property (nonatomic, strong) NSString *email_id;
@property (nonatomic, strong) NSString *account_owner;
@property (nonatomic, strong) NSString *username;
@property (nonatomic, strong) NSString *first_name;
@property (nonatomic, strong) NSString *chat_enabled;
@property (nonatomic, strong) NSString *last_name;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *last_date_of_sign_out;
@property (nonatomic, strong) NSString *date_of_birth;
@property (nonatomic, strong) NSString *last_date_signed_in;
@property (nonatomic, strong) NSString *address_line1;

@property (nonatomic, strong) NSString *gplus_user_id;
@property (nonatomic, strong) NSString *fb_user_id;

- (void)createDummy;
- (void)loadFromUser:(BarncUser*)user;
- (void)loadToUser:(BarncUser*)user;
@end

//AmazonUser.m
@implementation AmazonUser  
+ (NSString *)dynamoDBTableName {
    return @"Users";
}
+ (NSString *)hashKeyAttribute {
    return @"phone_no";
}
//implementation of the methods which is not included here.
@end

And this is how I have used different class for phone_no and email_id hash key -

//AWSUserWithPhone.m
#import "AWSUserWithPhone.h"

@implementation AWSUserWithPhone

+ (NSString *)hashKeyAttribute {
    return @"phone_no";
}

@end


//AWSUserWIthEmail
#import "AWSUserWithEmail.h"

@implementation AWSUserWithEmail

+ (NSString *)hashKeyAttribute {
    return @"email_id";
}

@end

In Load method I just pass different class for different users(I have updated it with actual method). When I use phone_no hashkey, everything works well and I get the following output -

2015-04-28 13:26:58.908 Barnc[1359:264405] AWSiOSSDKv2 [Verbose] AWSURLRequestSerialization.m line:111 | -[AWSJSONRequestSerializer serializeRequest:headers:parameters:] | Request body: [{"IdentityId":"us-east-1:xxx"}]
2015-04-28 13:27:01.590 Barnc[1359:264405] AWSiOSSDKv2 [Debug] AWSURLResponseSerialization.m line:81 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response header: [{
    "Content-Length" = 1052;
    "Content-Type" = "application/x-amz-json-1.1";
    Date = "Tue, 28 Apr 2015 07:57:01 GMT";
    "x-amzn-RequestId" = "xxx";
}]
2015-04-28 13:27:01.591 Barnc[1359:264405] AWSiOSSDKv2 [Verbose] AWSURLResponseSerialization.m line:86 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: [{"Credentials":{"AccessKeyId":"xxx","Expiration":1.430211421E9,"SecretKey":"xxx"},"IdentityId":"us-east-1:xxx"}]
2015-04-28 13:27:01.648 Barnc[1359:264405] AWSiOSSDKv2 [Verbose] AWSURLRequestSerialization.m line:111 | -[AWSJSONRequestSerializer serializeRequest:headers:parameters:] | Request body: [{"Key":{"phone_no":{"S":"+912222"}},"TableName":"Users"}]
2015-04-28 13:27:01.661 Barnc[1359:264405] AWSiOSSDKv2 [Debug] AWSSignature.m line:305 | -[AWSSignatureV4Signer signRequestV4:] | AWS4 Canonical Request: [POST
/

accept-encoding:
content-type:application/x-amz-json-1.0
host:dynamodb.us-east-1.amazonaws.com
user-agent:aws-sdk-iOS/2.1.0 iPhone-OS/8.3 en_IN
x-amz-date:20150428T075701Z
x-amz-security-token:xxx
x-amz-target:DynamoDB_20120810.GetItem

accept-encoding;content-type;host;user-agent;x-amz-date;x-amz-security-token;x-amz-target
xxx]
2015-04-28 13:27:01.663 Barnc[1359:264405] AWSiOSSDKv2 [Debug] AWSSignature.m line:306 | -[AWSSignatureV4Signer signRequestV4:] | payload {"Key":{"phone_no":{"S":"+912222"}},"TableName":"Users"}
2015-04-28 13:27:01.665 Barnc[1359:264405] AWSiOSSDKv2 [Debug] AWSSignature.m line:322 | -[AWSSignatureV4Signer signRequestV4:] | AWS4 String to Sign: [AWS4-HMAC-SHA256
20150428T075701Z
20150428/us-east-1/dynamodb/aws4_request
xxx]
2015-04-28 13:27:03.738 Barnc[1359:264405] AWSiOSSDKv2 [Debug] AWSURLResponseSerialization.m line:81 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response header: [{
    "Content-Length" = 593;
    "Content-Type" = "application/x-amz-json-1.0";
    Date = "Tue, 28 Apr 2015 07:57:03 GMT";
    "x-amz-crc32" = 2404923750;
    "x-amzn-RequestId" = xxx;
}]
2015-04-28 13:27:03.739 Barnc[1359:264405] AWSiOSSDKv2 [Verbose] AWSURLResponseSerialization.m line:86 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: [{"Item":{"fb_user_id":{"S":"null"},"catalog_ids":{"L":[{"S":"0"},{"S":"1"}]},"first_name":{"S":"xxx"},"email_id":{"S":"xxx"},"password":{"S":"xxx"},"date_of_birth":{"S":"xxx"},"phone_no":{"S":"+912222"},"chat_enabled":{"S":"Yes"},"account_creation_date":{"S":"25-Apr-2015"},"title":{"S":"mr"},"last_name":{"S":"xxx"},"account_owner":{"S":"null"},"address_line1":{"S":"null"},"last_date_signed_in":{"S":"25-Apr-2015"},"last_date_of_sign_out":{"S":"25-Apr-2015"},"gplus_user_id":{"S":"null"},"activated":{"S":"Yes"},"c2CallPassword":{"S":"xxx"}}}]
2015-04-28 13:27:03.768 Barnc[1359:264405] logged
2015-04-28 13:27:03.771 Barnc[1359:264405] This is a block
2015-04-28 13:27:05.797 Barnc[1359:263979] observeValueForKeyPath : sessionId
2015-04-28 13:27:06.189 Barnc[1359:264534] observeValueForKeyPath : ownNumberVerified
2015-04-28 13:27:06.190 Barnc[1359:264534] ownNumberVerified : 0
AudioUnitGraph 0x54F000:
  Member Nodes:
    node 1: 'auou' 'rioc' 'appl', instance 0x171dab00 O  
    node 2: 'aumx' 'mcmx' 'appl', instance 0x15fea630 O  
    node 3: 'aufc' 'conv' 'appl', instance 0x171b23e0 O  
  Connections:
    node   2 bus   0 => node   1 bus   0  [ 1 ch,  48000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]
    node   3 bus   0 => node   2 bus   0  [ 1 ch,  48000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]
  Input Callbacks:
    {0x18d755, 0x172bbf80} => node   2 bus   1  [1 ch, 48000 Hz]
    {0x18d755, 0x172bbf80} => node   3 bus   0  [1 ch, 8000 Hz]
  CurrentState:
    mLastUpdateError=0, eventsToProcess=F, isInitialized=F, isRunning=F

But when I use email_id hash key, it doesn't work and returns the ValidationException. Here's the complete verbose log -

2015-04-28 13:31:11.432 Barnc[1359:263979] AWSiOSSDKv2 [Verbose] AWSURLRequestSerialization.m line:111 | -[AWSJSONRequestSerializer serializeRequest:headers:parameters:] | Request body: [{"Key":{"email_id":{"S":"xxx"}},"TableName":"Users"}]
2015-04-28 13:31:11.443 Barnc[1359:263979] AWSiOSSDKv2 [Debug] AWSSignature.m line:305 | -[AWSSignatureV4Signer signRequestV4:] | AWS4 Canonical Request: [POST
/

accept-encoding:
content-type:application/x-amz-json-1.0
host:dynamodb.us-east-1.amazonaws.com
user-agent:aws-sdk-iOS/2.1.0 iPhone-OS/8.3 en_IN
x-amz-date:20150428T080111Z
x-amz-security-token:xxx
x-amz-target:DynamoDB_20120810.GetItem

accept-encoding;content-type;host;user-agent;x-amz-date;x-amz-security-token;x-amz-target
830520dc23c906e3612ed64618172cd60661698f151658092eb879bcda635827]
2015-04-28 13:31:11.445 Barnc[1359:263979] AWSiOSSDKv2 [Debug] AWSSignature.m line:306 | -[AWSSignatureV4Signer signRequestV4:] | payload {"Key":{"email_id":{"S":"xxx"}},"TableName":"Users"}
2015-04-28 13:31:11.446 Barnc[1359:263979] AWSiOSSDKv2 [Debug] AWSSignature.m line:322 | -[AWSSignatureV4Signer signRequestV4:] | AWS4 String to Sign: [AWS4-HMAC-SHA256
20150428T080111Z
20150428/us-east-1/dynamodb/aws4_request
xxx]
2015-04-28 13:31:11.453 Barnc[1359:265181] plugin com.swiftkey.SwiftKeyApp.Keyboard invalidated
2015-04-28 13:31:13.291 Barnc[1359:265197] AWSiOSSDKv2 [Debug] AWSURLResponseSerialization.m line:81 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response header: [{
    "Content-Length" = 121;
    "Content-Type" = "application/x-amz-json-1.0";
    Date = "Tue, 28 Apr 2015 08:01:12 GMT";
    "x-amz-crc32" = 3485231410;
    "x-amzn-RequestId" = xxx;
}]
2015-04-28 13:31:13.292 Barnc[1359:265197] AWSiOSSDKv2 [Verbose] AWSURLResponseSerialization.m line:86 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: [{"__type":"com.amazon.coral.validate#ValidationException","message":"The provided key element does not match the schema"}]
2015-04-28 13:31:13.301 Barnc[1359:265197] The request failed. Error: [Error Domain=com.amazonaws.AWSDynamoDBErrorDomain Code=0 "The operation couldn’t be completed. (com.amazonaws.AWSDynamoDBErrorDomain error 0.)" UserInfo=0x1727b680 {message=The provided key element does not match the schema, __type=com.amazon.coral.validate#ValidationException}]

PS: Some information is hidden (marked by xxx and random phone number) for security reasons.

2

There are 2 best solutions below

3
On BEST ANSWER

Thank you for providing details about your schema.

This error message means that one of the keys that you have defined for your table (or indexed attributes) does not have the correct AttributeValue type in the request being made. Please enable logging and see if you could provide the JSON of the actual Put/Update/BatchWrite Item request that is being made.

Also, it is possible also that your DynamoDBMapper annotations do not align perfectly with the base table and GSI schemas of your table. Please confirm or post the important details of your annotated class here.

0
On

The real problem was definitely what Alexander mentioned. I am posting this answer in context of iOS code. Global Secondary Index can not use load operation. We need query for this. Here's how my current code to load the user via email looks like -

- (void)loadUserWithQuery:(Class)userClass withHashKey:(NSString*)hashkey withIndexName:(NSString*)indexName withBlock:(void(^)(id))completionBlock{
    AWSDynamoDBQueryExpression* expression = [AWSDynamoDBQueryExpression new];
    expression.hashKeyValues = hashkey;
    expression.indexName = indexName;
    expression.scanIndexForward = @YES;
    expression.limit = @1;

    [[dynamoDBObjectMapper query:userClass expression:expression] continueWithBlock:^id(BFTask *task) {
        if (task.error) {
            NSLog(@"The request failed. Error: [%@]", task.error);
        }
        if (task.exception) {
            NSLog(@"The request failed. Exception: [%@]", task.exception);
        }
        if (task.result) {
            if(completionBlock)
                completionBlock(task.result);
        }
        return nil;
    }];
}

The result comes in AWSDynamoDBPaginatedOutput, so you will need to extract your result from there. I use following code -

AWSDynamoDBPaginatedOutput *output = (AWSDynamoDBPaginatedOutput*)result;
AmazonUser* user = (AmazonUser*)[output.items objectAtIndex:0];

You can further enhance it by adding checks to see if the output has at least one item in it.