Skip to content

Getting Started with the NoSQL Database API

Arpillai edited this page Jan 11, 2018 · 36 revisions

The goal of this guide is to get you familiarized with the Predix SDK For iOS's NoSQL Local Database API .

Prerequisites

You must have:

  • Completed all tasks from our Getting Started Guide
  • Knowledge of Xcode and Swift programming concepts
  • General understanding of NoSQL Databases

Opening and Closing Databases

1. Open or access your database. You can add the following configuration containing the database name, local file system URL, and a list of indexes to create within the database.

Example:

	let configuration = Database.OpenDatabaseConfiguration.default
	
	do {
	    let database = try Database.open(with: configuration, create: true)
	    if let database = database {
	        // ... the database is now open, and ready for interactions
	    }
	} catch let error {
	    // ... handle error...
	}

Note - A database refers to a physical set of files on the device, multiple calls to open the same database result in returning the same Database object. It is advised to avoid retaining multiple references to the same database. If a database is already open, use the _openedWith_ function to retrieve a reference to it.

2. Close your database when the database operations are completed.

**** (How ?)****

Notes - After closing the database:

  • No database API methods should be called on the database object. It is advised to set the database object to nil or ensure it goes out of scope after calling close.

  • Care must be taken to ensure no calls are made to any other references to that database.

Deleting a Database

**** (How ?)****

Deleting a database first closes the database, then removes the physical database files from the device. Once deleted, the local database no longer exists and it cannot be recovered.

To learn more about Databases, see the No SQL Local Database Programming Guide

Working with Documents

Documents are the central data structure of the PredixSDK database. The Document object is a type of dictionary where the dictionary keys must be strings. Accessing values from a dictionary is similar to accessing values from any other Dictionary class in Swift.

Example:

let myValue = myDocument[myKey]

You can perform the following with the Documents:

    1. Write/ Update/Save Documents
    1. Read/Fetch Documents
    1. Delete Documents

To read more about Documents and different components of the Document, see No SQL Local Database Programming Guide

2. Creating (Write/Update/Save) Documents

The _save_ method automatically determines if the document already exists. The _save_ method is especially useful in scenarios where a developer does not remember if a Document is created or updated. If a document exists, then the method updates it, otherwise add it. However, in some cases this is not desired so the Database object also includes _add_ and _update_ methods that return errors if the operation is not appropriate for the provided Document object.

Examples: Save Method

let database = Database.openedWith(Database.Configuration())
let document1: Document = ["aString": "string data", "anInt": 123, "aDouble": 3.14]

database.save(document1) { result in
	switch result {
		case .success(let savedDocument):
			print("Saved document with id: \(savedDocument.metadData.id)")
		case .failed(let error)
			print("Error saving document: \(error)")
	}
}

Add/Update Method

3.Fetching (Read) Documents

The _fetchDocument_ method allows you to retrieve a document from the Database. This method returns an optional Document object.

Example:

let database = Database.openedWith(Database.Configuration())
let myDocumentId = "my_document"

database.fetchDocument(myDocumentId) { fetchedDocument in
	if let document = fetcheDocument {
			print("Fetched document with id: \(document.metadData.id)")
	} else {
		print("No document with id: \(myDocumentId) exists")
	}
}

4. Fetching Multiple Documents You can fetch multiple documents in a single call using the fetchDocuments method. This method takes an array of document ids, and the completion handler is provided a [String: Document] dictionary where the dictionary key is a document id. If any strings in the input array are not associated with a Document, then those keys are excluded from the output dictionary.

Example:

let database = Database.openedWith(Database.Configuration())

// a document with the id of "my_document" exists, but not "not_a_document_id".
let ids = ["my_document_id", "not_a_document_id"] 

database.fetchDocuments(ids) { fetchedDocuments in
	for (_, document) in fetchedDocuments {
		print("fetched document: \(document.metaData.id)")
	}
}

// Will print:
// fetched document: my_document_id

Deleting Documents

You can call the _delete_ method to delete the documents form the database. The _delete_ method takes the ID of the Document that you want to delete. The completion handler for this method is passed an UpdateResult enumeration containing the ID of the deleted document.

Example:

let database = Database.openedWith(Database.Configuration())

database.delete("my_document_id") { result in
	switch result {
		case .success(let deletedDocumentId):
			print("Deleted document with id: \(deletedDocumentId)")
		case .failed(let error)
			print("Error deleting document: \(error)")
	}
}

Data Replication to Predix Sync Service

Data replication with a PredixSync backend service is a powerful tool that you can use to access Predix data while offline and share this data with other system users. You can choose two key replication options:

Repeating Replication — automatically detects changes and replicates them as needed, until explicitly stopped or when the application shuts down. This type of replication is useful when you want to ensure all changes from one system are sent to another system as soon as possible.

Bidirectional Replication — refers to the changes being sent from the PredixSync server to the client and from the client back to the PredixSync server. This type of replication is useful for read-only systems or systems that want to receive changes immediately but delay sending changes to the server.

To learn more about Replication concepts, see the NoSQL Database Programming Guide

Configuring Replication

1. Supply the PredixSync service URL and choose two key options: repeating and bidirectional.

2. Set the ReplicationConfiguation object's repeating property to true to enable the repeating replication. If the property is set to false, the replication is non-repeating. In other words, a single exchange of data will occur and the replication is completed. Replication must start again for another exchange of data to take place.

3. Set the ReplicationConfiguation object's bidirectional property to true to enable the bidirectional replication. If the property is set to false, data replicates from the PredixSync service to the client. No client changes are sent to the server.

Pre-configured Options

The following ReplicationConfiguration options are easy to manage because the ReplicationConfiguration object has static initializers to create common types of replication.

Example:

Create a repeating, bidirectional replication configuration:

let replicationConfig = ReplicationConfiguration.repeatingBidirectionalReplication(with: myPredixSyncURL)

Create a non-repeating, bidirectional replication:

let replicationConfig = ReplicationConfiguration.oneTimeBidirectionalReplication(with: myPredixSyncURL)

Create a non-repeating, non-bidirectional replication:

let replicationConfig = ReplicationConfiguration.oneTimeServerToClientReplication(with: myPredixSyncURL)

Starting and Stopping Replication

Prerequisites:

  • Knowledge of Predix Sync Service

1. To start the replication exchange enter:

database.startReplication(with: replicationConfig)

2. To stop the replication exchange enter:

database.stopReplication()

Notes -

  • Stopping a non-repeating replication is not necessary, the replication automatically stops when the data exchange is completed. However, you can use _stopReplication()_ on a non-repeating replication to cancel a long-running in-process replication.
  • All replication work happens in a background queue so there may be a slight delay between calling these methods and the data exchange starting or ending.

ReplicationStatusDelegate

Replication uses a standard delegate pattern to provide information on the current replication status. The object associated with the replicationStatusDelegate property of the database is called for the following replication events:

  • replicationDidComplete
  • replicationIsSending
  • replicationIsReceiving
  • replicationFailed

Information in each of these events allow you to handle errors, update status UI, or know when a data exchange is completed.

Querying Indexes

Using Indexs/Queries is a fast and efficient way to interact with data from the database. An index consists of three components: a String name, a String version, and mapping closure. Queries are how Indexes are used. You cannot run a query without an index. In a query, you can identify the index keys that you are interested in finding, and then run the query returns results matching the index keys of your choice.

To read more about Indexes, Queries, and the additional Map/Reduce function, see the NoSQL Database Programming Guide

You can create an Index and then run a query against that Index to request a document.

Prerequisites:

  • Knowledge or Map/Reduce
  • General Understanding of Indexes and Queries
  1. Create an Index. You can create an Index---(How ??)--.

Example:

  1. Run a query. You can run a query by "key" or "range".

Example:

Query by Key — Provides a list of explicit keys. The system returns the index rows that match these keys. The structure QueryByKeyList is used to create these type of queries:

In the following example, let us assume that the database has an index called "ColorIndex" defined, where the key is the name of a color. This query will only return rows where the key is one of the four listed colors.

let query= QueryByKeyList()
query.keys = ["red", "green", "blue", "purple"]

database.runQuery(on: "ColorIndex", with: query) { queryEnumerator in 
	print("Returned \(queryEnumerator.count) rows")
}

Query by Range — Specifies a range with a starting key and an ending key. It returns all keys falling within this range, as sorted by the index. Sorting rules vary by the data type of the key, so strings are sorted alphabetically, numbers sorted numerically, etc. Additionally, leaving a start key nil indicates that the query should begin at the very first row of the index; a nil end key indicates the results should end at the last row of the index, thus providing a "less than" and "greater than" type query:

In the following example, let us assume that the database has an index called "InvoiceCostIndex" defined, where the key is a numerical value. This query will return all index rows where the value of the key is 1000 or greater.


let query= QueryByKeyRange()
query.startKey = 1000

database.runQuery(on: "InvoiceCostIndex", with: query) { queryEnumerator in 
	print("Returned \(queryEnumerator.count) invoices with a total cost greater than 1000")
}

Query Results

The query completion handler provides a QueryResultEnumerator object, which enumerates over a collection of QueryResultRow objects. The QueryResultRow has properties for the index key, the index value, and the document id of the document that generated the key/value pair in the index.

Example: In the following example, the system prints an invoice number (from the value of the index) and the total cost (from the key of the index) for all documents that have a cost value of 1000 or greater.


let query= QueryByKeyRange()
query.startKey = 1000

database.runQuery(on: "InvoiceCostIndex", with: query) { queryEnumerator in 
	print("Returned \(queryEnumerator.count) invoices with a total cost greater than 1000")
	
	while let queryRow = queryEnumerator.next() {
		print("   Invoice: \(queryRow.value) - total cost: \(queryRow.key)")
	}
}

Next Steps

Clone this wiki locally