Skip to content

Commit

Permalink
feat: add hexEncodeTableName option
Browse files Browse the repository at this point in the history
If the `hexEncodeTableName` DynamoDB option is set to `true`,
DynamoDBDOWN encodes table names in hexadecimal. This can be useful if
one would like to pass `location` parameter values to `levelup` that
aren't compatible with DynamoDB's restrictions on table names.
  • Loading branch information
KlausTrainer committed Sep 26, 2016
1 parent eca8895 commit bc819a2
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 29 deletions.
64 changes: 45 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,44 @@ When running the above example, you should get the following console output:
read stream closed
```

### Table Creation ###
## Hash Keys ##

In DynamoDB, keys consist of two parts: a *hash key* and a *range key*. To achieve LevelDB-like behaviour, all keys in a database instance are given the same hash key. That means that you can't do range queries over keys with different hash keys.

The default hash key is `!`. You can specify it by putting a `$` in the `location` argument. The `$` separates the table name from the hash key.

### Example ###

```js
const levelup = require('levelup')
const DynamoDBDOWN = require('dynamodbdown')

const dynamoDBOptions = {
region: 'eu-west-1',
secretAccessKey: 'abc',
accessKeyId: '123'
}

const options = {
db: DynamoDBDOWN,
dynamodb: dynamoDBOptions // required AWS configuration
}

const db = levelup('tableName$hashKey', options)

db.put('some key', 'some value', => err {
// the DynamoDB object would now look like this:
// {
// hkey: 'hashKey',
// rkey: 'some key',
// value: 'some value'
// }
})
```

If you are fine with sharing capacity units across multiple database instances or applications, you can reuse a table by specifying the same table name, but different hash keys.

## Table Creation ##

If the table doesn't exist, DynamoDBDOWN will try to create a table. You can specify the read/write throughput. If not specified, it will default to `1/1`. If the table already exists, the specified throughput will have no effect. Throughput can be changed for tables that already exist by using the DynamoDB API or the AWS Console.

Expand All @@ -69,18 +106,16 @@ const dynamoDBOptions = {
}

const options = {
db: location => DynamoDBDOWN,
db: DynamoDBDOWN,
dynamodb: dynamoDBOptions // required AWS configuration
}

const db = levelup('tableName', options)
```

### Hash Keys ###
## Table Name Encoding ##

In DynamoDB, keys consist of two parts: a *hash key* and a *range key*. To achieve LevelDB-like behaviour, all keys in a database instance are given the same hash key. That means that you can't do range queries over keys with different hash keys.

The default hash key is `!`. You can specify it by putting a `$` in the `location` argument. The `$` separates the table name from the hash key.
DynamoDBDOWN encodes table names in hexadecimal if you set the `dynamodb.hexEncodeTableName` option to `true`. This can be useful if you'd like pass `location` parameter values to `levelup` that aren't compatible with DynamoDB's restrictions on table names (see [here](docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html)).

### Example ###

Expand All @@ -91,28 +126,19 @@ const DynamoDBDOWN = require('dynamodbdown')
const dynamoDBOptions = {
region: 'eu-west-1',
secretAccessKey: 'abc',
accessKeyId: '123'
accessKeyId: '123',
hexEncodeTableName: true
}

const options = {
db: DynamoDBDOWN,
dynamodb: dynamoDBOptions // required AWS configuration
}

const db = levelup('tableName/hashKey', options)

db.put('some key', 'some value', => err {
// the DynamoDB object would now look like this:
// {
// hkey: 'hashKey',
// rkey: 'some key',
// value: 'some value'
// }
})
const db = levelup('tableName', options) // the DynaoDB table name will
// be '7461626c654e616d65'
```

If you are fine with sharing capacity units across multiple database instances or applications, you can reuse a table by specifying the same table name, but different hash keys.

## Changelog ##

See [here](https://github.com/KlausTrainer/dynamodbdown/releases).
Expand Down
34 changes: 25 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ function DynamoDBDOWN (location) {

inherits(DynamoDBDOWN, AbstractLevelDOWN)

function hexEncodeTableName (str) {
var hex = ''

for (var pos = 0; pos < str.length; pos++) {
hex += String(str.charCodeAt(pos).toString(16))
}

return hex
}

DynamoDBDOWN.prototype._open = function (options, cb) {
if (!options.dynamodb) {
return cb(new Error('`open` requires `options` argument with "dynamodb" key'))
Expand All @@ -37,7 +47,13 @@ DynamoDBDOWN.prototype._open = function (options, cb) {
this.tableName = this.tableName.replace(options.prefix, '')
}

const dynamodbOptions = Object.assign({tableName: this.tableName}, options.dynamodb)
if (options.dynamodb.hexEncodeTableName === true) {
this.encodedTableName = hexEncodeTableName(this.tableName)
} else {
this.encodedTableName = this.tableName
}

const dynamodbOptions = Object.assign({tableName: this.encodedTableName}, options.dynamodb)

this.dynamoDb = new AWS.DynamoDB(dynamodbOptions)

Expand All @@ -64,7 +80,7 @@ DynamoDBDOWN.prototype._close = function (cb) {

DynamoDBDOWN.prototype._put = function (key, value, options, cb) {
const params = {
TableName: this.tableName,
TableName: this.encodedTableName,
Item: {
hkey: {S: this.hashKey.toString()},
rkey: {S: key.toString()},
Expand All @@ -77,7 +93,7 @@ DynamoDBDOWN.prototype._put = function (key, value, options, cb) {

DynamoDBDOWN.prototype._get = function (key, options, cb) {
const params = {
TableName: this.tableName,
TableName: this.encodedTableName,
Key: {
hkey: {S: this.hashKey.toString()},
rkey: {S: key.toString()}
Expand All @@ -99,7 +115,7 @@ DynamoDBDOWN.prototype._get = function (key, options, cb) {

DynamoDBDOWN.prototype._del = function (key, options, cb) {
const params = {
TableName: this.tableName,
TableName: this.encodedTableName,
Key: {
hkey: {S: this.hashKey.toString()},
rkey: {S: key.toString()}
Expand Down Expand Up @@ -172,8 +188,8 @@ DynamoDBDOWN.prototype._batch = function (array, options, cb) {

const reqs = []

if (data && data.UnprocessedItems && data.UnprocessedItems[this.tableName]) {
reqs.push.apply(reqs, data.UnprocessedItems[this.tableName])
if (data && data.UnprocessedItems && data.UnprocessedItems[this.encodedTableName]) {
reqs.push.apply(reqs, data.UnprocessedItems[this.encodedTableName])
}

reqs.push.apply(reqs, ops.splice(0, 25 - reqs.length))
Expand All @@ -182,7 +198,7 @@ DynamoDBDOWN.prototype._batch = function (array, options, cb) {
return cb()
}

params.RequestItems[this.tableName] = reqs
params.RequestItems[this.encodedTableName] = reqs
this.dynamoDb.batchWriteItem(params, loop)
}

Expand All @@ -195,7 +211,7 @@ DynamoDBDOWN.prototype._iterator = function (options) {

DynamoDBDOWN.prototype.createTable = function (opts, cb) {
const params = {
TableName: this.tableName,
TableName: this.encodedTableName,
AttributeDefinitions: [
{AttributeName: 'hkey', AttributeType: 'S'},
{AttributeName: 'rkey', AttributeType: 'S'}
Expand All @@ -218,7 +234,7 @@ DynamoDBDOWN.destroy = function (name, callback) {
const store = globalStore[name]

if (store) {
store.dynamoDb.deleteTable({TableName: store.tableName}, (err, data) => {
store.dynamoDb.deleteTable({TableName: store.encodedTableName}, (err, data) => {
if (err) {
callback()
} else {
Expand Down
2 changes: 1 addition & 1 deletion iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ DynamoDBIterator.prototype.getRange = function (opts, cb) {
const rkey = createRKey(opts)

const params = {
TableName: this.db.tableName,
TableName: this.db.encodedTableName,
KeyConditions: {
hkey: {
ComparisonOperator: 'EQ',
Expand Down

0 comments on commit bc819a2

Please sign in to comment.