Tuesday, February 28, 2012

IrisCouch and CouchCocoa

  • Here's some sample code that can be thrown directly into your iPhone application's application:didFinishLaunchingWithOptions: method, in order to perform a basic connectivity test for your IrisCouch domain.
    • You may refer to this post to get CouchCocoa into your iOS project.
    ...
    #import "CouchCocoa.h"
    ...
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    ...
    /* Try to connect to the IrisCouch instance */
    // 1) Create a server object via the URL
    NSURL* serverURL = [NSURL URLWithString: @"https://mydomain.iriscouch.com"];
    CouchServer *server = [[CouchServer alloc] initWithURL: serverURL];
    // 2) Set the credentials
    [server setCredential:[NSURLCredential credentialWithUser:@"username"
    password:@"password"
    persistence:NSURLCredentialPersistenceNone]];
    // 3) Express your interest in creating a new database
    CouchDatabase *database = [server databaseNamed: @"mynewdatabase"];
    RESTOperation* op = [database create]; // 3.1) prepare the REST operation
    if (![op wait]) { // 3.2) make it happen
    NSLog(@"%@", [op.error localizedDescription]); // 3.3) log any problems
    }
    // 4) Express your interest in creating a new document in mynewdatabase
    CouchDocument *doc = [database untitledDocument];
    NSDictionary *keyValuePairs = [NSDictionary dictionaryWithObjectsAndKeys:
    @"its alive", @"status", nil];
    op = [doc putProperties:keyValuePairs];
    if (![op wait]) { // 4.2) make it happen
    NSLog(@"%@", [op.error localizedDescription]); // 4.3) log any problems
    }
    ...
    }
    view raw gistfile1.m hosted with ❤ by GitHub
  • If you see error messages such as the following in your Xcode console then you are well on your way:
    unauthorized: You are not a server admin.
    not_found: no_db_file
    view raw gistfile1.txt hosted with ❤ by GitHub
  • As mentioned by in this thread, simply make the following changes to your IrisCouch domain:
    • Go to http://mydomain.iriscouch.com/_utils/
    • Log into your account
    • Click “Configuration” in the sidebar
    • Click “Add a new section” at the bottom
    • Fill in
      • section: httpd
      • option: WWW-Authenticate
      • value: Basic realm="administrator"
  • It should work well now and after you spot the following logs, you can go confirm that a new database has actually been created on IrisCouch via your /_utils/ web console.
    REST: Authentication challenge! credential=<NSURLCredential: 0x857b040>: username
    view raw gistfile1.txt hosted with ❤ by GitHub

How to use the ElasticSearch Query DSL

I'm also in the process of developing an ElasticSearch Client based in Objective-C using RestKit, feel free to have a look & use it ... and shoot me an email if you want to contribute: https://github.com/pulkitsinghal/ElasticSearchClient
// 1) Lets start with the simplest query that you can run in
// the head plugin for ElasticSearch located at the url:
// http://localhost:9200/_plugin/head/
{
"query": {
"match_all": {}
}
}
// 2) Before jumping into forming queries with the ES Query DSL
// syntax alone, lets try out a few queries written within
// the comfort zone of those who are already familiar with
// Lucene and/or Solr
{
"query": {
"query_string": {
"query": "+camera +laptop",
"use_dis_max": true
}
}
}
// 3) Lets try it again but with slightly different syntax, it gives
// exactly the same number of total hits as the previous query.
{
"query": {
"query_string": {
"query": "camera AND laptop",
"use_dis_max": true
}
}
}
// 4) What if we want to specify the exact fields or terms, which should
// be searched for the query? Well, here's how:
{
"query": {
"query_string": {
"fields": [
"name",
"shortDescription"
],
"query": "+camera +laptop",
"use_dis_max": true
}
}
}
// 5) Someone who is not yet well-versed with JSON, may ask: what if I just want
// to search inside one field exactly for a match and not multiple ones?
// Simple, don't specify multiple fields:
{
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
}
}
// 6) That's all good but now we want to limit the fields coming back
// in the response. It would be nice to specify exactly the fields
// that we want to retrieve:
{
"fields": [
"name",
"shortDescription",
"longDescription"
],
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
}
}
// 7) Now lets try something different like a range query to try and
// group our results so far into price buckets.
//
// WARNING: Make sure to POST to the exact index:
// http://localhost:9200/my_index/_search/
// And not against any & all indices
// http://localhost:9200/_search/
// Otherwise you may run into an exception like the following:
// org.elasticsearch.search.facet.FacetPhaseExecutionException:
// Facet [range1]: No mapping found for key_field [regularPrice]
// from other indices that don't share the same fields:
{
"fields": [
"name",
"upc",
"salePrice",
"regularPrice"
],
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"range1": {
"range": {
"regularPrice": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200,
"to": 300
},
{
"from": 300
}
]
}
}
}
}
// 8) The total # of results is simple to find out but what about getting
// all the results back? Well both getting the results back and doing so
// in an orderly and paginated manner is easy to do like so:
//
// 8.1) Get the results starting from offset 0 and get a total of 2 entries
{
"from": 0,
"size": 2,
"fields": [
"name"
],
"query": {
"match_all": {}
}
}
// 8.2) Get the results starting from offset 2 and get a total of 2 entries
{
"from": 2,
"size": 2,
"fields": [
"name"
],
"query": {
"match_all": {}
}
}
// 9) If your index doesn't change often, then neither will the position of results
// being returned by the query. These results are positioned by the score allotted
// to each one of them in terms of how likely Lucene/ES thinks they are to be
// the closest-fit / best-answer to your query.
//
// Lets say you want random results, what then? How can you ask ES to change the
// score and return different results for every query? Here's how:
{
"from": 0,
"size": 2,
"fields": [
"name",
"upc"
],
"query": {
"custom_score": {
"query": {
"match_all": {}
},
"script": "random()"
}
}
}
// In this example by separating out the key and value facet fields,
// the range buckets are filled based on the key field ... while the
// averages etc. are calculated based on the the value field ... the
// assumption here (I suppose) is that the documents which have the
// key fields present, will also have the value field present (optimistic?)
{
"fields": [
"name",
"upc",
"salePrice",
"regularPrice"
],
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"range1": {
"range": {
"key_field": "regularPrice",
"value_field": "salePrice",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200,
"to": 300
},
{
"from": 300
}
]
}
}
}
}
// Good example of using histogram facets if the ranges that you specify
// happen to be at regular intervals anyway.
{
"fields": [
"name",
"upc",
"salePrice",
"regularPrice"
],
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"histo1": {
"histogram": {
"field": "regularPrice",
"interval": 100
}
}
}
}
// The following is supposed to be the same but seems to give more metrics
{
"fields": [
"name",
"upc",
"salePrice",
"regularPrice"
],
"query": {
"query_string": {
"fields": [
"name"
],
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"histo1": {
"histogram": {
"key_field": "regularPrice",
"value_field": "salePrice",
"interval": 100
}
}
}
}
// This is a really exciting example where we take our query results and
// bucket them by making a date histogram facet, therefore, shirking all
// responsibility of figuring out which items fall in which year by startDate
// All work is done by ES! Can you say $profit$ :)
{
"fields": [
"name",
"upc",
"salePrice",
"regularPrice"
],
"query": {
"query_string": {
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"histo1": {
"date_histogram": {
"key_field": "startDate",
"value_field": "startDate",
"interval": "year"
}
}
}
}
// What if you have a scenario where out want to figure out if there are
// really good sales going on in the area that you are interested in?
// Then statistical facet queries should get your motor started.
{
"query": {
"query_string": {
"fields": [
"name",
"description"
],
"query": "+camera +laptop",
"use_dis_max": true
}
},
"facets": {
"stat1": {
"statistical": {
"field": "regularPrice"
}
},
"stat2": {
"statistical": {
"field": "salePrice"
}
}
}
}
view raw gistfile1.json hosted with ❤ by GitHub

Wednesday, February 22, 2012

CouchCocoa - An iOS client for working with CouchDB

CouchCocoa is a framework / library for iPhone use that abstracts out the work needed to talk to CouchDB instances running on the web or on the device itself. If you want to jump right into the setup for a simple project, then simply follow these steps:
  1. Add CouchCocoa as a submodule to your project:
    • cd ~/dev/myProject/
    • git submodule add git://github.com/couchbaselabs/CouchCocoa.git
  2. Get all the dependencies of this submodule itself:
    • cd ~/dev/myProject/CouchCocoa/
    • git submodule update --init --recursive
  3. Drag & drop CouchCocoa's .xcodeproj file under your own project in Xcode
  4. Add the following as your target's dependencies so that they will get built:
    • iOS Framework (CouchCocoa)
    • iOS Library (CouchCocoa)
  5. Add the following in "Link Binary With Libraries" for your target:
    • libCouchCocoa.a
  6. Add the following in "Header Search Paths" for your target:
    • "$(SRCROOT)/CouchCocoa/Model"
    • "$(SRCROOT)/CouchCocoa/REST"
    • "$(SRCROOT)/CouchCocoa/Couch"
    • "$(SRCROOT)/CouchCocoa/UI/iOS"
  7. In Xcode, select CouchCocoa.xcodeproj in the Project Navigator and edit the Target > Build Settings and set the Skip Install setting to Yes.
  8. If at a later point of time you realize that you want to work with TouchDB as well then you *may* need to update your CouchCocoa submodule and bring it up to speed:
    • cd ~/dev/myProject/CouchCocoa/
    • git checkout touchdb
    • git pull origin touchdb
If you have any problems, post your questions on the google group for mobile couchbase.

Aside from the process there are some "big picture" points to consider:
  • CouchCocoa can talk directly to CouchDB (or CouchBase - a more performant variant). For example, if you use the hosting services provided by IrisCouch, you can configure CouchCocoa to work directly with that Couch server.
  • CouchCocoa can work with an embedded version of CouchDB on mobile devices.
    • TouchDB is one such implementation which is lightweight and exists for iOS and Android mobile devices.
      • For iOS however, I do find it a bit confusing when I think about how to structure my project, there are two ways to do it:
        • Build and soft-link the TouchDB framework. And use CouchCocoa as a submodule as outlined earlier in the blog.
          • If you choose to use TouchDB-iOS as a submodule as well then don't forget that TouchDB's itself also needs a soft-link to CouchCocoa.framework:
            cd ~/dev/superProject
            ln -s ./DerivedData/superProject/Build/Products/Debug-ios-universal/CouchCocoa.framework ./TouchDB-iOS/Demo-iOS/Frameworks/CouchCocoa.framework
        • Syncpoint-iOS which bundles TouchDB and CouchCocoa together but lags behind the individual projects in terms of stability and features. To experiment with it, you can follow a this discussion thread where it was introduced to the community.
      • Another huge pain-point is the fact that for all of GitHub's glory it doesn't let me search for content in wiki pages so when it comes time to try out the infamous Grocery-Sync example, I'm left wondering where to look! This relative path should shed some light I hope: TouchDB-iOS/Demo-iOS/DemoAppDelegate.m
    • Couchbase Mobile is another implementation. Recently it has seen some serious progress in terms of tutorials (one & two) and example-code covering its usage.