Best Way to Make Amazon Aws Dynamodb Queries Using Swift

Best way to make Amazon AWS DynamoDB queries using Swift?

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 = "stormacq@amazon.com"
item.date = "20141101"
item.note = "This is item #1"
item.number = 1.0
let task1 = mapper.save(item)

item = Item()
item.email = "stormacq@amazon.com"
item.date = "20141102"
item.note = "This is item #2"
item.number = 2.0
let task2 = mapper.save(item)

item = Item()
item.email = "stormacq@amazon.lu"
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("stormacq@amazon.com", 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("stormacq@amazon.com", 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.

AWS DynamoDB queries using Swift

Fetching data from the network is an asynchronous action. You can't delay loading the screen while it completes. It may take a long time. It might not ever complete.

Your view controller must handle the case that it doesn't have data yet, and update itself when that data becomes available. The first step to this is avoiding making network queries in your view controller. View controllers should never directly query the network. They should query model objects that outlive the view controller. The model objects are responsible for making queries to the network and updating themselves with the results. Then the view controller will update itself based on the model. The name for this pattern is Model View Controller and is fundamental to Cocoa development. (Search around for many tutorials and discussions of this pattern.)

But regardless of where you make the queries and store the data, you will always have to deal with the case where the data is not yet available, and display something in the meantime. Nothing can fix this in a distributed system.

DynamoDB Swift4 complex query

You won't be able to do this with a DynamoDB query. When you query a table (or index) in DynamoDB you must always specify the complete primary key. In your case that would mean the full value of "firstname:lastname:email".

You could sort of do this with a DynamoDB scan and a filter expression, but that will look at every item in your table, so it could be slow and expensive. Amazon will charge you for the read capacity necessary to look at every item in the table.

So if you really wanted to, the filter expression for the scan operation would be something like:

"contains (#FirstName, :firstName) and contains (#LastName, : lastName)"

Note that contains looks for an exact substring match, so if you want case insensitive matches (like ILIKE in SQL) it won't work.

If you need to do these types of queries then you need to evaluate whether or not DynamoDB is the right choice for you. DynamoDB is a NoSQL key/value store basically. It trades limited querying functionality for scalability and performance. If you are coming at DynamoDB from a SQL background and are expecting to be able to do freeform queries of anything in your table, you will be disappointed.

DynamoDB Swift Table Scan Mapping Class

1. 'Item' does not conform to protocol 'AWSDynamoDBModeling'

Replace

class func hashKeyAttribute() -> Int! {
return SongID
}

By

class func hashKeyAttribute() -> String! {
return "SongID"
}

Explanation: You need to provide the name of the hashkey attribute, not it's type. The protocol requires that you return a String.

2. Failable initializer 'init()' cannot override a non-failable
initializer

There is no need to extend NSObject. You can remove all init(..) methods from your code. As far as I can see they are not needed.

3. Instance member 'SongID' cannot be used on type 'Item' (My hash Key
is an int not string)

This will go away when you fix error 1.

4. Initializer does not override a designated initializer from its
superclass

This will go away when you fix error 2

I wrote a tutorial about using DynamoDB for a Swift app. Part 3 contains a simple example of a class AMZUser that is mapped to DynamoDB



Related Topics



Leave a reply



Submit