Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to make Amazon AWS DynamoDB queries using Swift?

I have recently implemented the AWS SDK for my iOS app, which I am developing in Swift. I have connected to my DB instance and am able to get a query response, however, I am struggling to convert this into usable data. I'm relatively new to Swift, AWS and programming in general so maybe missing something obvious!

My code is as follows:

    let atVal = AWSDynamoDBAttributeValue()
    atVal.S = "123456abc"
    let condition = AWSDynamoDBCondition()
    condition.comparisonOperator = AWSDynamoDBComparisonOperator.EQ
    condition.attributeValueList = [atVal]

    let myDic: [String: AWSDynamoDBCondition] = ["userid": condition]
    let query = AWSDynamoDBQueryInput()
    query.indexName = "userid-index"
    query.tableName = "users"
    query.keyConditions = myDic
    query.limit = 1


    dynamoDB.query(query).continueWithBlock {
        (task: BFTask!) -> AnyObject! in
        let results = task.result as AWSDynamoDBQueryOutput

        let myResults = results.items
        println("object: \(myResults.description)")

        return nil
    }

And the console output for this is:

object: [{ area = " {\n S = \"West Hampstead\";\n}"; name = " {\n S = \"Olly Mayes\";\n}"; userid = " {\n S = \"123456abc\";\n}"; }]

There doesn't seem to be much precedence for using AWS and Swift, understandably, so any help would be much appreciated!

like image 803
ollym Avatar asked Nov 16 '14 15:11

ollym


People also ask

How can I make DynamoDB query faster?

You can increase your DynamoDB throughput by several times, by parallelizing reads/writes over multiple partitions. Use DynamoDB as an attribute store rather than as a document store. This will not only reduce the read/write costs but also improve the performance of your operations considerably.

Is DynamoDB good for querying?

Querying is a very powerful operation in DynamoDB. It allows you to select multiple Items that have the same partition ("HASH") key but different sort ("RANGE") keys.

What query language does DynamoDB use?

Amazon DynamoDB supports PartiQL , a SQL-compatible query language, to select, insert, update, and delete data in Amazon DynamoDB.

Can API gateway talk directly to DynamoDB?

Thus, we can simply have the data come in via API Gateway and get injected directly into DynamoDB (with some basic data transformation, and integration of the user's ID). This alleviates the need for a Lambda, and avoids the cost of that.


1 Answers

The easy answer to your question is : convert the JSON string returned into a Dictionary object. Something like this :

let data = jsonDataItem.dataUsingEncoding(NSUTF8StringEncoding)

    if data != nil {
        var error : NSError?
        let dict = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments, error: &error) as? NSDictionary

        if let e = error {
            ...
        } else {
            ...
        }

However, I would strongly encourage you to look at the higher level DynamoDB mapper class When using DynamoDB mapper class, you define your data structure, and just give it to DynamoDB Mapper. Here is a full sample, from table creation to table deletion. The example inserts, removes, scans and query the table using DynamoDB Mapper.

First, you need to initialise the client SDK

    let cp = AWSStaticCredentialsProvider(accessKey: "AK...", secretKey: "xxx")
    let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: cp)
    AWSServiceManager.defaultServiceManager().setDefaultServiceConfiguration(configuration)

This is a sample only. It is not a good practice to embed an Access Key and Secret Key in the code. Best practice would be to use the AWSCognitoCredentialsProvider instead (read more about Cognito).

Define a class that maps your items in the table

class Item : AWSDynamoDBModel, AWSDynamoDBModeling {

    var email  : String = ""
    var date   : String = ""
    var note   : String = ""
    var number : Double = 0.0

    override init!() { super.init() }

    required init!(coder: NSCoder!) {
        fatalError("init(coder:) has not been implemented")
    }

    class func dynamoDBTableName() -> String! {
        return "Demo"
    }
    class func hashKeyAttribute() -> String! {
        return "email"
    }
    class func rangeKeyAttribute() -> String! {
        return "date"
    }

    //required to let DynamoDB Mapper create instances of this class
    override init(dictionary dictionaryValue: [NSObject : AnyObject]!, error: NSErrorPointer) {
        super.init(dictionary: dictionaryValue, error: error)
    }

    //workaround to possible XCode 6.1 Bug : "Type NotificationAck" does not conform to protocol "NSObjectProtocol"
    override func isEqual(anObject: AnyObject?) -> Bool {
        return super.isEqual(anObject)
    } }

To create a table

self.createTable().continueWithSuccessBlock {(task: BFTask!) -> BFTask! in
    NSLog("Create table - success")
    return nil
}


func createTable() -> BFTask! {
    let pt = AWSDynamoDBProvisionedThroughput()
    pt.readCapacityUnits  = 10
    pt.writeCapacityUnits = 10

    let emailAttr = AWSDynamoDBAttributeDefinition()
    emailAttr.attributeName = "email"
    emailAttr.attributeType = AWSDynamoDBScalarAttributeType.S

    let dateAttr = AWSDynamoDBAttributeDefinition()
    dateAttr.attributeName = "date"
    dateAttr.attributeType = AWSDynamoDBScalarAttributeType.S

    let emailKey = AWSDynamoDBKeySchemaElement()
    emailKey.attributeName = "email"
    emailKey.keyType = AWSDynamoDBKeyType.Hash

    let dateKey = AWSDynamoDBKeySchemaElement()
    dateKey.attributeName = "date"
    dateKey.keyType = AWSDynamoDBKeyType.Range

    let ct = AWSDynamoDBCreateTableInput()
    ct.tableName = "Demo"
    ct.provisionedThroughput = pt
    ct.attributeDefinitions = [emailAttr, dateAttr]
    ct.keySchema = [ emailKey, dateKey ]

    NSLog("Creating table")

    let client = AWSDynamoDB.defaultDynamoDB()
    return client.createTable(ct)
}

To delete a table

self.deleteTable().continueWithSuccessBlock({ (task: BFTask!) -> BFTask! in
    NSLog("Delete table - success")
    return nil
})

func deleteTable() -> BFTask! {

    let dt = AWSDynamoDBDeleteTableInput()
    dt.tableName = "Demo"

    NSLog("Deleting table")
    let client = AWSDynamoDB.defaultDynamoDB()
    return client.deleteTable(dt)
}

To insert items

self.insertSomeItems().continueWithBlock({
    (task: BFTask!) -> BFTask! in

    if (task.error != nil) {
        NSLog(task.error.description)
    } else {
        NSLog("DynamoDB save succeeded")
    }

    return nil;
})

func insertSomeItems() -> BFTask! {
    let mapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()

    var item = Item()
    item.email  = "[email protected]"
    item.date   = "20141101"
    item.note   = "This is item #1"
    item.number = 1.0
    let task1 = mapper.save(item)

    item = Item()
    item.email  = "[email protected]"
    item.date   = "20141102"
    item.note   = "This is item #2"
    item.number = 2.0
    let task2 = mapper.save(item)

    item = Item()
    item.email  = "[email protected]"
    item.date   = "20141103"
    item.note   = "This is item #3"
    item.number = 3.0
    let task3 = mapper.save(item)

    return BFTask(forCompletionOfAllTasks: [task1, task2, task3])
}

To load one single item

self.load("[email protected]", range:"20141101").continueWithSuccessBlock({ (task: BFTask!) -> BFTask! in
    NSLog("Load one value - success")
    let item = task.result as Item
    print(item)
    return nil
})


func load(hash: String, range: String) -> BFTask! {
    let mapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
    return mapper.load(Item.self, hashKey: hash, rangeKey: range)

}

To query on hash and range key

/* 
   keyConditions
   http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSDynamoDBQueryInput.html#//api/name/keyConditions
*/
let cond = AWSDynamoDBCondition()
let v1    = AWSDynamoDBAttributeValue(); v1.S = "20141101"
cond.comparisonOperator = AWSDynamoDBComparisonOperator.EQ
cond.attributeValueList = [ v1 ]
let c = [ "date" : cond ]
self.query("[email protected]", keyConditions:c).continueWithSuccessBlock({ (task: BFTask!) -> BFTask! in
    NSLog("Query multiple values - success")
    let results = task.result as AWSDynamoDBPaginatedOutput
    for r in results.items {
        print(r)
    }
    return nil
})

func query(hash: String, keyConditions:[NSObject:AnyObject]) -> BFTask! {
    let mapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()

    let exp = AWSDynamoDBQueryExpression()
    exp.hashKeyValues      = hash
    exp.rangeKeyConditions = keyConditions

    return mapper.query(Item.self, expression: exp)
}

To scan items (full table scan)

let cond = AWSDynamoDBCondition()
let v1    = AWSDynamoDBAttributeValue(); v1.S = "20141101"
cond.comparisonOperator = AWSDynamoDBComparisonOperator.GT
cond.attributeValueList = [ v1 ]

let exp = AWSDynamoDBScanExpression()
exp.scanFilter = [ "date" : cond ]

self.scan(exp).continueWithSuccessBlock({ (task: BFTask!) -> BFTask! in
    NSLog("Scan multiple values - success")
    let results = task.result as AWSDynamoDBPaginatedOutput
    for r in results.items {
        print(r)
    }
    return nil
})

func scan(expression : AWSDynamoDBScanExpression) -> BFTask! {

    let mapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
    return mapper.scan(Item.self, expression: expression)
}

If you have no other usage of DynamoDB in US EAST 1, there is no cost associated with running this sample, as it is falling into DynamoDB's free tier.

like image 194
Sébastien Stormacq Avatar answered Sep 18 '22 14:09

Sébastien Stormacq