Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(datastore): support setting a property indexed value #218

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions lib/datastore/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,28 @@ 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 will determine the correct Datastore method to execute (`upsert`,
* `insert`, `update`, and `insertAutoId`) by using the key(s) provided. For
* example, if you provide an incomplete key (one without an ID), the request
* will create a new entity and have its ID automatically assigned. If you
* provide a complete key, the entity will be updated with the data specified.
*
* By default, all properties are indexed. To prevent a property from being
* included in *all* indexes, 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 `excludeFromIndexes` property, set to `true` or `false`.
* @param {function} callback - The callback function.
*
* @example
Expand All @@ -210,6 +223,19 @@ Dataset.prototype.get = function(key, callback) {
* // populated with the complete, generated key.
* });
*
* // To specify an `excludeFromIndexes` 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',
* excludeFromIndexes: 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 @@ -237,13 +237,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, all properties are indexed. To prevent a property from being
* included in *all* indexes, 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 `excludeFromIndexes` property, set to `true` or `false`.
* @param {function} callback - The callback function.
*
* @example
Expand All @@ -260,6 +270,19 @@ Transaction.prototype.get = function(keys, callback) {
* // populated with the complete, generated key.
* });
*
* // To specify an `excludeFromIndexes` 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',
* excludeFromIndexes: false
* }
* ]
* }, function(err, key) {});
*
* // Save multiple entities at once.
* transaction.save([
* {
Expand All @@ -286,7 +309,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.excludeFromIndexes, 'boolean')) {
data.value.indexed = !data.excludeFromIndexes;
delete data.excludeFromIndexes;
}
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