Skip to content

Commit

Permalink
feat(datastore): support setting a property indexed value
Browse files Browse the repository at this point in the history
By default, property index values are set to `true`, without allowing
the user to specify an override. Now, when a user passes in an array
to `dataset.save`, they will have the option of setting `true` or
`false`.

Example:

dataset.save({
  key: dataset.key('Company'),
  data: [
    name: 'propertyName',
    value: 'any value type',
    indexed: false
  ]
}, function(err, keys) {}).

Resolves: #208
Related: http://goo.gl/tKVvhP
  • Loading branch information
stephenplusplus committed Sep 17, 2014
1 parent 91c7036 commit 108b7a3
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 89 deletions.
31 changes: 27 additions & 4 deletions lib/datastore/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,25 @@ Dataset.prototype.get = function(key, callback) {
};

/**
* Insert or update the specified object(s) in the current transaction. If a
* key is incomplete, its associated object is inserted and its generated
* identifier is returned to the callback.
* Insert or update the specified object(s) in the current transaction. If a key
* is incomplete, its associated object is inserted and its generated identifier
* is returned to the callback.
*
* This method automatically handles the `upsert`, `insert`, `update`, and
* `insertAutoId` Datastore methods.
*
* By default, properties are set with an `indexed` value of `true`. To override
* this, you must supply an entity's `data` property as an array. See below for
* an example.
*
* @borrows {module:datastore/transaction#save} as save
*
* @param {object|object[]} entities - Datastore key object(s).
* @param {Key} entities.key - Datastore key object.
* @param {object} entities.data - Data to save with the provided key.
* @param {object|object[]} entities.data - Data to save with the provided key.
* If you provide an array of objects, you must use the explicit syntax:
* `name` for the name of the property and `value` for its value. You may
* also specify an `indexed` property, set to `true` or `false`.
* @param {function} callback - The callback function.
*
* @example
Expand All @@ -210,6 +220,19 @@ Dataset.prototype.get = function(key, callback) {
* // populated with the complete, generated key.
* });
*
* // To specify an `indexed` value for a Datastore entity, pass in an array for
* // the key's data. The above example would then look like:
* transaction.save({
* key: dataset.key('Company'),
* data: [
* {
* name: 'rating',
* value: '10',
* indexed: false
* }
* ]
* }, function(err, key) {});
*
* // Save multiple entities at once.
* dataset.save([
* {
Expand Down
28 changes: 16 additions & 12 deletions lib/datastore/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@

'use strict';

/**
* @type {object}
*/
/** @type {object} */
var entityMeta = {};

/** @const {regexp} Regular expression to verify a field name. */
Expand Down Expand Up @@ -457,6 +455,8 @@ function valueToProperty(v) {
throw new Error('Unsupported field value, ' + v + ', is provided.');
}

module.exports.valueToProperty = valueToProperty;

/**
* Convert an entity object to an entity protocol object.
*
Expand All @@ -465,19 +465,23 @@ function valueToProperty(v) {
*
* @example
* entityToEntityProto({
* {
* name: 'Burcu',
* legit: true
* }
* name: 'Burcu',
* legit: true
* });
* // {
* // key: null,
* // properties: {
* // name: {
* // stringValue: 'Burcu'
* // property: [
* // {
* // name: 'name',
* // value: {
* // string_value: 'Burcu'
* // }
* // },
* // legit: {
* // booleanValue: true
* // {
* // name: 'legit',
* // value: {
* // boolean_value: true
* // }
* // }
* // }
* // }
Expand Down
45 changes: 40 additions & 5 deletions lib/datastore/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,23 @@ Transaction.prototype.get = function(keys, callback) {
};

/**
* Insert or update the specified object(s) in the current transaction. If a
* key is incomplete, its associated object is inserted and its generated
* identifier is returned to the callback.
* Insert or update the specified object(s) in the current transaction. If a key
* is incomplete, its associated object is inserted and its generated identifier
* is returned to the callback.
*
* This method automatically handles the `upsert`, `insert`, `update`, and
* `insertAutoId` Datastore methods.
*
* By default, properties are set with an `indexed` value of `true`. To override
* this, you must supply an entity's `data` property as an array. See below for
* an example.
*
* @param {object|object[]} entities - Datastore key object(s).
* @param {Key} entities.key - Datastore key object.
* @param {object} entities.data - Data to save with the provided key.
* @param {object|object[]} entities.data - Data to save with the provided key.
* If you provide an array of objects, you must use the explicit syntax:
* `name` for the name of the property and `value` for its value. You may
* also specify an `indexed` property, set to `true` or `false`.
* @param {function} callback - The callback function.
*
* @example
Expand All @@ -264,6 +274,19 @@ Transaction.prototype.get = function(keys, callback) {
* // populated with the complete, generated key.
* });
*
* // To specify an `indexed` value for a Datastore entity, pass in an array for
* // the key's data. The above example would then look like:
* transaction.save({
* key: dataset.key('Company'),
* data: [
* {
* name: 'rating',
* value: '10',
* indexed: false
* }
* ]
* }, function(err, key) {});
*
* // Save multiple entities at once.
* transaction.save([
* {
Expand All @@ -290,7 +313,19 @@ Transaction.prototype.save = function(entities, callback) {
var req = {
mode: MODE_NON_TRANSACTIONAL,
mutation: entities.reduce(function(acc, entityObject, index) {
var ent = entity.entityToEntityProto(entityObject.data);
var ent = {};
if (Array.isArray(entityObject.data)) {
ent.property = entityObject.data.map(function(data) {
data.value = entity.valueToProperty(data.value);
if (util.is(data.indexed, 'boolean')) {
data.value.indexed = data.indexed;
delete data.indexed;
}
return data;
});
} else {
ent = entity.entityToEntityProto(entityObject.data);
}
ent.key = entity.keyToKeyProto(entityObject.key);
if (entity.isKeyComplete(entityObject.key)) {
acc.upsert.push(ent);
Expand Down
151 changes: 119 additions & 32 deletions test/datastore/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,39 +304,22 @@ describe('entityFromEntityProto', function() {
});

describe('entityToEntityProto', function() {
it('should support bool, int, double, str, entity & list values', function() {
var now = new Date();
var proto = entity.entityToEntityProto({
name: 'Burcu',
desc: 'Description',
count: new entity.Int(6),
primitiveCount: 6,
legit: true,
date : now,
bytes: new Buffer('Hello'),
list: ['a', new entity.Double(54.7)],
metadata: {
key1: 'value1',
key2: 'value2'
}
it('should format an entity', function() {
var val = entity.entityToEntityProto({
name: 'name'
});
var properties = proto.property;
assert.equal(properties[0].value.string_value, 'Burcu');
assert.equal(properties[1].value.string_value, 'Description');
assert.equal(properties[2].value.integer_value, 6);
assert.equal(properties[3].value.integer_value, 6);
assert.equal(properties[4].value.boolean_value, true);
assert.equal(
properties[5].value.timestamp_microseconds_value, now.getTime() * 1000);
assert.deepEqual(properties[6].value.blob_value, new Buffer('Hello'));

var listValue = properties[7].value.list_value;
assert.equal(listValue[0].string_value, 'a');
assert.equal(listValue[1].double_value, 54.7);

var entityValue = properties[8].value.entity_value;
assert.equal(entityValue.property[0].value.string_value, 'value1');
assert.equal(entityValue.property[1].value.string_value, 'value2');
var expected = {
key: null,
property: [
{
name: 'name',
value: {
string_value: 'name'
}
}
]
};
assert.deepEqual(val, expected);
});
});

Expand All @@ -350,3 +333,107 @@ describe('queryToQueryProto', function() {
assert.deepEqual(proto, queryFilterProto);
});
});

describe('valueToProperty', function() {
it('should translate a boolean', function() {
var val = entity.valueToProperty(true);
assert.deepEqual(val, {
boolean_value: true
});
});

it('should translate an int', function() {
var val1 = entity.valueToProperty(new entity.Int(3));
var val2 = entity.valueToProperty(3);
var expected = { integer_value: 3 };
assert.deepEqual(val1, expected);
assert.deepEqual(val2, expected);
});

it('should translate a double', function() {
var val1 = entity.valueToProperty(new entity.Double(3.1));
var val2 = entity.valueToProperty(3.1);
var expected = { double_value: 3.1 };
assert.deepEqual(val1, expected);
assert.deepEqual(val2, expected);
});

it('should translate a date', function() {
var date = new Date();
var val = entity.valueToProperty(date);
var expected = {
timestamp_microseconds_value: date.getTime() * 1000
};
assert.deepEqual(val, expected);
});

it('should translate a string', function() {
var val = entity.valueToProperty('Hi');
var expected = {
string_value: 'Hi'
};
assert.deepEqual(val, expected);
});

it('should translate a buffer', function() {
var buffer = new Buffer('Hi');
var val = entity.valueToProperty(buffer);
var expected = {
blob_value: buffer
};
assert.deepEqual(val, expected);
});

it('should translate an array', function() {
var array = [1, '2', true];
var val = entity.valueToProperty(array);
var expected = {
list_value: [
{ integer_value: 1 },
{ string_value: '2' },
{ boolean_value: true }
]
};
assert.deepEqual(val, expected);
});

it('should translate a Key', function() {
var key = new entity.Key({
namespace: 'ns',
path: ['Kind', 3]
});
var val = entity.valueToProperty(key);
var expected = {
key_value: entity.keyToKeyProto(key)
};
assert.deepEqual(val, expected);
});

describe('objects', function() {
it('should translate an object', function() {
var val = entity.valueToProperty({
name: 'value'
});
var expected = {
entity_value: {
property: [
{
name: 'name',
value: {
string_value: 'value',
}
}
]
},
indexed: false
};
assert.deepEqual(val, expected);
});

it('should not translate a key-less object', function() {
assert.throws(function() {
entity.valueToProperty({});
}, /Unsupported field value/);
});
});
});
Loading

0 comments on commit 108b7a3

Please sign in to comment.