KijiREST: GET, PUT & POST

Update: the KijiREST User Guide is out! The material in the User Guide supersedes this blog post starting with KijiREST v0.3.0.

The KijiREST project emerged from growing interest in building a REST interface for Kiji. This blog is the last in a series explaining the usage of KijiREST v0.1.0 (included in Albacore BentoBox v1.0.4 and higher).

Previously…

We discussed how to set up a KijiREST server and perform some basic GET operations on instances and tables.

In this article, we will study how GET, PUT and POST operate on rows. We will access a table with layout players.json, which describes player states in a hypothetical RPG.

Note that the behavior and formats of these operations are still under development. As KijiREST evolves, the functionality may be adapted to meet the needs of Kiji users. Tell us what use cases you want to see on the KijiProject by subscribing to the Kiji mailing list: user@kiji.org.

Behavior of GET, PUT & POST on row(s) resources

Let’s first distinguish between .../rows collection resource and the .../rows/<row> item resource. The .../rows collection resource is addressed by a resource path which terminates with the rows endpoint. For example:

/v1/instances/default/tables/user_table/rows

The .../rows/<row> item resource is addressed by a resource path which identifies the hexadecimal row key. For example:

/v1/instances/default/tables/user_table/rows/0ab12c34d5e6789f

GET, PUT, and POST act differently on collection resources and item resources.

Resource type GET PUT POST DELETE
…/rows display many rows add a new row to the table delete rows unimplemented
…/rows/<row> display one row replace row or add a new row delete a row unimplemented

Note that DELETE is currently unimplemented on rows.

The user may not always know the ASCII-encoded hexadecimal row keys of the row they would like to query and instead only has the formatted entityId (a JSON-like tuple). For cases like this, there exists a special hexadecimal row key “constructor” that takes the formatted entityId as a query parameter and returns the ASCII-encoded hexadecimal row key.

Request:

GET /v1/instances/default/tables/players/entityId?eid=["ptolemaios","africa.north"]

Output:

{"rowKey":"2d3970746f6c656d61696f73006166726963612e6e6f72746800"}

PUT row (…/rows/<row> item)

The paradigm for row PUT is quite different from the GET and POST paradigms. It is only possible to PUT to one row, though it is possible to put any number of cells to that row.

The parameters of the PUT request are all passed in the query portion of the URL. Moreover, due to a known bug REST-26, the Content-Type header must be specified as application/json. The parameters are <family>:<column>=<value> tokens separated by the & delimiter. In order to maintain idempotency of PUT, a timestamp must be specified with every PUT request.

Let’s PUT cells to the row corresponding to the player “ptolemaios” on domain “africa.north”:

Request:

PUT /v1/instances/default/tables/players/2d3970746f6c656d61696f73006166726963612e6e6f72746800?info:fullname=”Lemy Soter”&info:hitpoints=89&info:mana=10&timestamp=1371840459

Output:

{“target": "/v1/instances/default/tables/players/rows/2d3970746f6c656d61696f73006166726963612e6e6f72746800"}

The result is a JSON blob specifying the resource path of the row.

For individual columns, you can override the “timestamp” query parameter (which is still mandatory) with the query parameter timestamp.<family>:<column>=…

Let’s PUT a few more cells:

Request:

PUT /v1/instances/default/tables/players/rows/fa5773656c65756b6f7300617369612e63656e7472616c00?info:hitpoints=25&info:fullname="Luke Nikator"&info:mana=55&friends:ptolemaios@africa.north=1371840490&timestamp=1371840490&quests:tyre=25&timestamp.quests:tyre=1371840590

Output:

{"target": /v1/instances/default/tables/players/rows/fa5773656c65756b6f7300617369612e63656e7472616c00"}

GET row (…/rows/<row> item)

Lets get the row we just put in with the following request.

Request:

GET /v1/instances/default/tables/players/rows/fa5773656c65756b6f7300617369612e63656e7472616c00

Output:

{
  "entityId": "['seleukos', 'asia.central']",
  "rowKey": "fa5773656c65756b6f7300617369612e63656e7472616c00",
  "cells": [
	{
  	"columnFamily": "friends",
  	"columnQualifier": "ptolemaios@africa.north",
  	"value": 1371840490,
  	"timestamp": 1371840490
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "fullname",
  	"value": ""Luke Nikator"",
  	"timestamp": 1371840490
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "hitpoints",
  	"value": 25,
  	"timestamp": 1371840490
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "mana",
  	"value": 55,
  	"timestamp": 1371840490
	},
	{
  	"columnFamily": "quest_progress",
  	"columnQualifier": "tyre",
  	"value": 25,
  	"timestamp": 1371840590
	}
  ]
}

It is possible to further refine the GET request with additional optional query parameters:

  • cols (single, optional, default=*) – A comma separated list of column family or column family:qualifier to return. Specify “*” for all columns. Specifying just a column family will yield values for all column family + qualifiers.
  • versions (single, optional, default=1) – An integer representing the number of versions per cell to return for the given row.
  • timerange (single, optional) – A string denoting the time range (in ms since UNIX epoch) to return (specified by min..max where min/max is the ms since UNIX epoch. min and max are both optional; however, if a value is provided for this parameter, at least one of min/max must be present.)

Let’s try another GET incorporating timerange and other features.

Request:

GET /v1/instances/default/tables/players/rows/fa5773656c65756b6f7300617369612e63656e7472616c00?versions=10&cols=info:fullname,quest_progress&timerange=0..1371840591

Output:

{
  "entityId": "['seleukos', 'asia.central']",
  "rowKey": "fa5773656c65756b6f7300617369612e63656e7472616c00",
  "cells": [
	{
  	"columnFamily": "info",
  	"columnQualifier": "fullname",
  	"value": ""Luke Nikator"",
  	"timestamp": 1371840490
	},
	{
  	"columnFamily": "quest_progress",
  	"columnQualifier": "tyre",
  	"value": 25,
  	"timestamp": 1371840590
	}
  ]
}

GET rows (…/rows collection)

Instead of calling GET in every individual rows/<row> item, it is possible to scan many rows by calling GET on the rows collection.

Request:

GET /v1/instances/default/tables/players/rows?cols=info:fullname&timerange=0..1371840591

Output:

{"entityId":"['ptolemaios', 'africa.north']","rowKey":"2d3970746f6c656d61696f73006166726963612e6e6f72746800","cells":[{"columnFamily":"info","columnQualifier":"fullname","value":"”Lemy Soter”","timestamp":1371840459}]}
{"entityId":"['seleukos', 'asia.central']","rowKey":"fa5773656c65756b6f7300617369612e63656e7472616c00","cells":[{"columnFamily":"info","columnQualifier":"fullname","value":""Luke Nikator"","timestamp":1371840490}]}

The result is a stream of JSON blobs, separated by “rn” (carriage return + line feed characters) representing a scan. All the query options available on GET .../rows/<row> are also available with GET .../rows. Additional useful query parameters include:

  • eid (single, optional) – A JSON representation of the list of components (for example: “['string1', 2, 'string3']”) of the entity id to retrieve. When specified, returns the row represented by the given entity id.
  • start_rk (single, optional) – If executing a range query, the row key (specified as a hexadecimal string) to inclusively start from.
  • end_rk (single, optional) – If executing a range query, the row key (specified as a hexadecimal string) to exclusively end with.
  • limit (single, optional, default=100) – The number of rows to return. To return all rows, set limit=-1.

Request:

GET /v1/instances/default/tables/players/rows?start_rk=2d3970746f6c656d61696f73006166726963612e6e6f72746800&limit=2

Output:

{"entityId":"['ptolemaios', 'africa.north']","rowKey":"2d3970746f6c656d61696f73006166726963612e6e6f72746800","cells":[{"columnFamily":"info","columnQualifier":"fullname","value":"”Lemy Soter”","timestamp":1371840459},{"columnFamily":"info","columnQualifier":"hitpoints","value":89,"timestamp":1371840459},{"columnFamily":"info","columnQualifier":"mana","value":10,"timestamp":1371840459}]}
{"entityId":"['antipater', 'europe.east']","rowKey":"b735616e74697061746572006575726f70652e6561737400","cells":[{"columnFamily":"info","columnQualifier":"fullname","value":"”Pate Mac”","timestamp":1371840739},{"columnFamily":"info","columnQualifier":"hitpoints","value":58,"timestamp":1371840739},{"columnFamily":"info","columnQualifier":"mana","value":53,"timestamp":1371840739}]}

The eid parameter is nice to have even to GET a single row since it provides a way to enter the formatted entity id instead of the hexadecimal row key. Note that if eid is specified, then start_rk, end_rk, and limit are ignored.

Request:

GET /v1/instances/default/tables/players/rows?eid=["ptolemaios","africa.north"]

Output:

{
  "entityId": "['ptolemaios', 'africa.north']",
  "rowKey": "2d3970746f6c656d61696f73006166726963612e6e6f72746800",
  "cells": [
	{
  	"columnFamily": "info",
  	"columnQualifier": "fullname",
  	"value": "”Lemy Soter”",
  	"timestamp": 1371840459
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "hitpoints",
  	"value": 89,
  	"timestamp": 1371840459
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "mana",
  	"value": 10,
  	"timestamp": 1371840459
	}
  ]
}

POST rows (…/rows collection)

Similarly to GET .../rows, POST exists in order to enter complex JSON encoded entries into the table. Unlike PUT which uses query parameters alone, POST operates on the request body and has no query parameters. It is necessary to specify the Content-Type header as application/json.

In order to maintain an analogy with GET rows, POST request bodies resemble the output JSON blobs from GET rows. The one difference is that the POSTed blobs must use formatted entity ids and cannot use hexadecimal row keys.

Request:

POST /v1/instances/default/tables/players/rows

Request Body:

{
  "entityId": "['antipater', 'europe.east']",
  "cells": [
	{
  	"columnFamily": "info",
  	"columnQualifier": "fullname",
  	"value": "”Pate Mac”",
  	"timestamp": 1371840739
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "hitpoints",
  	"value": 58,
  	"timestamp": 1371840739
	},
	{
  	"columnFamily": "info",
  	"columnQualifier": "mana",
  	"value": 53,
  	"timestamp": 1371840739
	}
  ]
}

Output:

{"target":"/v1/instances/default/tables/players/rows/b735616e74697061746572006575726f70652e6561737400"}

Note that currently, it is only possible to POST one row of data. Batch POST is working its way through JIRA as ticket REST-21.

Going forward

This article concludes the Revealing KijiREST series. Much of this material will soon be available in an up-to-date and regularly maintained User Guide. Stay tuned and join the Kiji user mailing list user@kiji.org to voice your requests and recommendations.