From b68b30f71f1a5a1c7c66b837a6f944ae7ddca084 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 18 Nov 2014 17:52:42 +0100 Subject: [PATCH 01/60] Adding support for DynamoDB local --- index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 9727a25..643e9bd 100644 --- a/index.js +++ b/index.js @@ -241,7 +241,7 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t var error = null; try{ - AWS.config.loadFromPath('./credentials.json'); + AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); } catch (e) { e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; @@ -331,8 +331,15 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // extremly simple table names var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec - if(DynamoDB === false) - DynamoDB = new AWS.DynamoDB(); + var Endpoint = collection.connections[connection]['config']['endPoint']; + if(DynamoDB === false) { + DynamoDB = new AWS.DynamoDB( + Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} + : null + ); + if (Endpoint) + Vogels.dynamoDriver(DynamoDB); + } DynamoDB.describeTable({TableName:tableName}, function(err, res){ if (err) { From 427be85f34759955d6b33ea6d022cb2422db6557 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 18 Nov 2014 17:54:01 +0100 Subject: [PATCH 02/60] Documenting DynamoDB support --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e96709..300f798 100755 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ module.exports.adapters = { }, dynamoDb: { - adapter: "sails-dynamodb" + adapter: "sails-dynamodb", + endPoint: "http://localhost:8000", // Optional: add for DynamoDB local }, }; From cb6bfd5d5b8bb4652517a556b9e1df60a0284d8f Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 15:44:05 +0100 Subject: [PATCH 03/60] Code Formatting ( no changes ) --- index.js | 1000 ++++++++++++++++++++++++++---------------------------- 1 file changed, 489 insertions(+), 511 deletions(-) diff --git a/index.js b/index.js index 643e9bd..6aa168c 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ - /** * Module Dependencies */ @@ -17,11 +16,11 @@ var DynamoDB = false; * Sails Boilerplate Adapter * * Most of the methods below are optional. - * + * * If you don't need / can't get to every method, just implement * what you have time for. The other methods will only fail if * you try to call them! - * + * * For many adapters, this file is all you need. For very complex adapters, you may need more flexiblity. * In any case, it's probably a good idea to start with one file and refactor only if necessary. * If you do go that route, it's conventional in Node to create a `./lib` directory for your private submodules @@ -36,9 +35,9 @@ module.exports = (function () { // (aka model) that gets registered with this adapter. var _modelReferences = {}; - var _definedTables = {}; + var _definedTables = {}; - // You may also want to store additional, private data + // You may also want to store additional, private data // per-collection (esp. if your data store uses persistent // connections). // @@ -66,10 +65,7 @@ module.exports = (function () { var adapter = { - identity: 'sails-dynamodb' - - , keyId: "id" - , indexPrefix: "-Index" + identity: 'sails-dynamodb', keyId: "id", indexPrefix: "-Index" // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. @@ -80,10 +76,7 @@ module.exports = (function () { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - accessKeyId: null - , secretAccessKey: null - , region: 'us-west-1' - , credentialsFilePath: './credentials.json' + accessKeyId: null, secretAccessKey: null, region: 'us-west-1', credentialsFilePath: './credentials.json' // For example: // port: 3306, // host: 'localhost', @@ -105,178 +98,169 @@ module.exports = (function () { // safe => Don't change anything (good for production DBs) , migrate: 'alter' // , schema: false - } - - + }, _getModel: function (collectionName) { - , _getModel: function (collectionName) { - - var collection = _modelReferences[collectionName]; + var collection = _modelReferences[collectionName]; //console.log("currenct collection.definition", collection.definition); //console.log(collection); -/* -currenct collection -{ - keyId: 'id', - indexPrefix: '-Index', - syncable: true, - defaults: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - _getModel: [Function], - _getPrimaryKeys: [Function], - registerCollection: [Function], - teardown: [Function], - define: [Function], - describe: [Function], - drop: [Function], - find: [Function], - _searchCondition: [Function], - create: [Function], - update: [Function], - destroy: [Function], - _setColumnType: [Function], - _resultFormat: [Function], - config: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - definition: - { user_id: { primaryKey: true, unique: true }, - name: { type: 'string', index: true }, - password: { type: 'string', index: true }, - email: { type: 'string', index: true }, - activated: { type: 'boolean', defaultsTo: false }, - activationToken: { type: 'string' }, - isSocial: { type: 'boolean' }, - socialActivated: { type: 'boolean' }, - createdAt: { type: 'datetime', default: 'NOW' }, - updatedAt: { type: 'datetime', default: 'NOW' } }, - identity: 'user' } -*/ - -var primaryKeys = require("lodash").where(collection.definition, { primaryKey: true }); + /* + currenct collection + { + keyId: 'id', + indexPrefix: '-Index', + syncable: true, + defaults: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + _getModel: [Function], + _getPrimaryKeys: [Function], + registerCollection: [Function], + teardown: [Function], + define: [Function], + describe: [Function], + drop: [Function], + find: [Function], + _searchCondition: [Function], + create: [Function], + update: [Function], + destroy: [Function], + _setColumnType: [Function], + _resultFormat: [Function], + config: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + definition: + { user_id: { primaryKey: true, unique: true }, + name: { type: 'string', index: true }, + password: { type: 'string', index: true }, + email: { type: 'string', index: true }, + activated: { type: 'boolean', defaultsTo: false }, + activationToken: { type: 'string' }, + isSocial: { type: 'boolean' }, + socialActivated: { type: 'boolean' }, + createdAt: { type: 'datetime', default: 'NOW' }, + updatedAt: { type: 'datetime', default: 'NOW' } }, + identity: 'user' } + */ + + var primaryKeys = require("lodash").where(collection.definition, {primaryKey: true}); //console.log("primaryKeys", primaryKeys); - return Vogels.define(collectionName, function (schema) { + return Vogels.define(collectionName, function (schema) { //console.log("_getModel", collectionName); - var columns = collection.definition; - var primaryKeys = [] - var indexes = []; - // set columns - for(var columnName in columns){ - var attributes = columns[columnName]; + var columns = collection.definition; + var primaryKeys = [] + var indexes = []; + // set columns + for (var columnName in columns) { + var attributes = columns[columnName]; // console.log(columnName+":", attributes); - if(typeof attributes !== "function"){ - adapter._setColumnType(schema, columnName, attributes); - // search primarykey + if (typeof attributes !== "function") { + adapter._setColumnType(schema, columnName, attributes); + // search primarykey // if("primaryKey" in attributes)primaryKeys.push( columnName ); - // search index - if("index" in attributes) indexes.push(columnName); - } - } - // set primary key - var primaryKeys = adapter._getPrimaryKeys(collectionName); - var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" + // search index + if ("index" in attributes) indexes.push(columnName); + } + } + // set primary key + var primaryKeys = adapter._getPrimaryKeys(collectionName); + var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" // console.log("collection.definition", collection.definition); - if(primaryKeys.length < 1) - schema.UUID( adapter.keyId, {hashKey: true}); - else{ - if (!require("lodash").isUndefined(primaryKeys[0])) { - adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); - if (!require("lodash").isUndefined(primaryKeys[1])) { - adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); - } - } + if (primaryKeys.length < 1) + schema.UUID(adapter.keyId, {hashKey: true}); + else { + if (!require("lodash").isUndefined(primaryKeys[0])) { + adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); + if (!require("lodash").isUndefined(primaryKeys[1])) { + adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); } + } + } // schema.String( primaryKey, {hashKey: true}); - for(var i = 0; i < indexes.length; i++){ - var key = indexes[i]; - schema.globalIndex(key + adapter.indexPrefix, { hashKey: key}); - } - - schema.Date('createdAt', {default: Date.now}); - schema.Date('updatedAt', {default: Date.now}); - }); - } + for (var i = 0; i < indexes.length; i++) { + var key = indexes[i]; + schema.globalIndex(key + adapter.indexPrefix, {hashKey: key}); + } - , _getPrimaryKeys: function (collectionName) { - var lodash = require("lodash"); - var collection = _modelReferences[collectionName]; + schema.Date('createdAt', {default: Date.now}); + schema.Date('updatedAt', {default: Date.now}); + }); + }, _getPrimaryKeys: function (collectionName) { + var lodash = require("lodash"); + var collection = _modelReferences[collectionName]; - var maps = lodash.mapValues(collection.definition, "primaryKey"); - // console.log(results); - var list = lodash.pick(maps, function (value, key) { - return typeof value !== "undefined"; - }); - var primaryKeys = lodash.keys(list); - return primaryKeys; + var maps = lodash.mapValues(collection.definition, "primaryKey"); + // console.log(results); + var list = lodash.pick(maps, function (value, key) { + return typeof value !== "undefined"; + }); + var primaryKeys = lodash.keys(list); + return primaryKeys; } - - /** - * - * This method runs when a model is initially registered - * at server-start-time. This is the only required method. - * - * @param string collection [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - , registerConnection: function (connection, collections, cb) { + + /** + * + * This method runs when a model is initially registered + * at server-start-time. This is the only required method. + * + * @param string collection [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */, registerConnection: function (connection, collections, cb) { //var sails = require("sails"); //console.log("load registerConnection"); //console.log("::connection",connection); //console.log("::collections",collections); - if(!connection.identity) return cb(Errors.IdentityMissing); - if(connections[connection.identity]) return cb(Errors.IdentityDuplicate); + if (!connection.identity) return cb(Errors.IdentityMissing); + if (connections[connection.identity]) return cb(Errors.IdentityDuplicate); var error = null; - try{ - AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); - } - catch (e) { - e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; - error = e; - } - // Keep a reference to this collection - _modelReferences = collections; - cb(error); + try { + AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); + } + catch (e) { + e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; + error = e; + } + // Keep a reference to this collection + _modelReferences = collections; + cb(error); } /** * Fired when a model is unregistered, typically when the server * is killed. Useful for tearing-down remaining open connections, * etc. - * + * * @param {Function} cb [description] * @return {[type]} [description] - */ - , teardown: function(connection, cb) { + */, teardown: function (connection, cb) { cb(); }, - /** - * + * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} definition [description] * @param {Function} cb [description] * @return {[type]} [description] */ - define: function(connection, collectionName, definition, cb) { + define: function (connection, collectionName, definition, cb) { //console.info("adaptor::define"); //console.info("::collectionName", collectionName); //console.info("::definition", definition); @@ -285,25 +269,26 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; - if(! _definedTables[collectionName] ){ - var table = adapter._getModel(collectionName); - - _definedTables[collectionName] = table; - Vogels.createTables({ - collectionName: {readCapacity: 1, writeCapacity: 1} - }, function (err) { - if(err) { - console.warn('Error creating tables', err); - cb(err); - } else { + if (!_definedTables[collectionName]) { + var table = adapter._getModel(collectionName); + + _definedTables[collectionName] = table; + Vogels.createTables({ + collectionName: {readCapacity: 1, writeCapacity: 1} + }, function (err) { + if (err) { + console.warn('Error creating tables', err); + cb(err); + } + else { // console.log('table are now created and active'); - cb(); - } - }); - } - else{ cb(); - } + } + }); + } + else { + cb(); + } // Define a new "table" or "collection" schema in the data store }, @@ -312,12 +297,12 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {Function} cb [description] * @return {[type]} [description] */ - describe: function(connection, collectionName, cb) { + describe: function (connection, collectionName, cb) { //console.info("adaptor::describe"); //console.log("::connection",connection); //console.log("::collection",collectionName); @@ -329,33 +314,34 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // Respond with the schema (attributes) for a collection or table in the data store var attributes = {}; - // extremly simple table names - var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec - var Endpoint = collection.connections[connection]['config']['endPoint']; - if(DynamoDB === false) { - DynamoDB = new AWS.DynamoDB( - Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} - : null - ); - if (Endpoint) - Vogels.dynamoDriver(DynamoDB); - } + // extremly simple table names + var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec + var Endpoint = collection.connections[connection]['config']['endPoint']; + if (DynamoDB === false) { + DynamoDB = new AWS.DynamoDB( + Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} + : null + ); + if (Endpoint) + Vogels.dynamoDriver(DynamoDB); + } - DynamoDB.describeTable({TableName:tableName}, function(err, res){ - if (err) { - if('code' in err && err['code'] === 'ResourceNotFoundException'){ - cb(); - } - else{ - console.warn('Error describe tables'+__filename, err); - cb(err); - } + DynamoDB.describeTable({TableName: tableName}, function (err, res) { + if (err) { + if ('code' in err && err['code'] === 'ResourceNotFoundException') { + cb(); + } + else { + console.warn('Error describe tables' + __filename, err); + cb(err); + } // console.log(err); // an error occurred - } else { + } + else { // console.log(data); // successful response - cb(); - } - }); + cb(); + } + }); }, @@ -364,13 +350,13 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} relations [description] * @param {Function} cb [description] * @return {[type]} [description] */ - drop: function(connection, collectionName, relations, cb) { + drop: function (connection, collectionName, relations, cb) { //console.info("adaptor::drop", collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -380,8 +366,6 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t }, - - // OVERRIDES NOT CURRENTLY FULLY SUPPORTED FOR: // // alter: function (collectionName, changes, cb) {}, @@ -392,177 +376,174 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // removeIndex: function(indexName, options, cb) {}, - /** - * + * * REQUIRED method if users expect to call Model.find(), Model.findOne(), * or related. - * + * * You should implement this method to respond with an array of instances. * Waterline core will take care of supporting all the other different * find methods/usages. - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - find: function(connection, collectionName, options, cb) { + find: function (connection, collectionName, options, cb) { //console.info("adaptor::find", collectionName); //console.info("::option", options); - var collection = _modelReferences[collectionName]; - // Options object is normalized for you: - // - // options.where - // options.limit - // options.skip - // options. - - // Filter, paginate, and sort records from the datastore. - // You should end up w/ an array of objects as a result. - // If no matches were found, this will be an empty array. - - if ('limit' in options && options.limit < 2 ){ - // query mode - // get primarykeys - var primaryKeys = adapter._getPrimaryKeys(collectionName); - // get current condition - var wheres = require("lodash").keys(options.where); - // compare both of keys - var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - // get model - var model = adapter._getModel(collectionName); - if (primaryQuery.length < 1) { // secondary key search - var hashKey = wheres[0]; - var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) - } - else{ // primary key search - var hashKey = primaryKeys[0]; - var query = model.query(options.where[hashKey]); - } - + var collection = _modelReferences[collectionName]; + // Options object is normalized for you: + // + // options.where + // options.limit + // options.skip + // options. + + // Filter, paginate, and sort records from the datastore. + // You should end up w/ an array of objects as a result. + // If no matches were found, this will be an empty array. + + if ('limit' in options && options.limit < 2) { + // query mode + // get primarykeys + var primaryKeys = adapter._getPrimaryKeys(collectionName); + // get current condition + var wheres = require("lodash").keys(options.where); + // compare both of keys + var primaryQuery = require("lodash").intersection(primaryKeys, wheres); + // get model + var model = adapter._getModel(collectionName); + if (primaryQuery.length < 1) { // secondary key search + var hashKey = wheres[0]; + var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) } - else{ + else { // primary key search + var hashKey = primaryKeys[0]; + var query = model.query(options.where[hashKey]); + } + + } + else { // scan mode - var query = adapter._getModel(collectionName).scan(); - // If you need to access your private data for this collection: + var query = adapter._getModel(collectionName).scan(); + // If you need to access your private data for this collection: - if ('where' in options && !options.where){ - for(var key in options['where']){ - //console.log(options['where'][key]); - query = query.where(key).contains(options['where'][key]); - } + if ('where' in options && !options.where) { + for (var key in options['where']) { + //console.log(options['where'][key]); + query = query.where(key).contains(options['where'][key]); + } - query = adapter._searchCondition(query, options); - } - else{ - query = adapter._searchCondition(query, options); - } + query = adapter._searchCondition(query, options); } + else { + query = adapter._searchCondition(query, options); + } + } + + query.exec(function (err, res) { + if (!err) { + console.log("success", adapter._resultFormat(res)); + adapter._valueDecode(collection.definition, res.attrs); + cb(null, adapter._resultFormat(res)); + } + else { + console.warn('Error exec query:' + __filename, err); + cb(err); + } + }); - query.exec( function(err, res){ - if(!err){ - console.log("success", adapter._resultFormat(res)); - adapter._valueDecode(collection.definition,res.attrs); - cb(null, adapter._resultFormat(res)); - } - else{ - console.warn('Error exec query:'+__filename, err); - cb(err); - } - }); - // Respond with an error, or the results. // cb(null, []); } - /** - * search condition - * @param query - * @param options - * @returns {*} - * @private - */ - , _searchCondition: function(query, options){ - if ('limit' in options){ + /** + * search condition + * @param query + * @param options + * @returns {*} + * @private + */, _searchCondition: function (query, options) { + if ('limit' in options) { // query = query.limit(1); - } - - if ('skip' in options){ - } + } - if ('sort' in options){ - } + if ('skip' in options) { + } - return query + if ('sort' in options) { } + return query + } - /** - * - * REQUIRED method if users expect to call Model.create() or any methods - * - * @param {[type]} collectionName [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - , create: function(connection, collectionName, values, cb) { + + /** + * + * REQUIRED method if users expect to call Model.create() or any methods + * + * @param {[type]} collectionName [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */, create: function (connection, collectionName, values, cb) { //console.info("adaptor::create", collectionName); //console.info("values", values); //console.log("collection", _modelReferences[collectionName]); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition,values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition, values); - // Create a single new model (specified by `values`) - var current = Model.create(values, function(err, res){ - if(err) { - console.warn(__filename+", create error:", err); - cb(err); - } else { - adapter._valueDecode(collection.definition,res.attrs); + // Create a single new model (specified by `values`) + var current = Model.create(values, function (err, res) { + if (err) { + console.warn(__filename + ", create error:", err); + cb(err); + } + else { + adapter._valueDecode(collection.definition, res.attrs); // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. - cb(null, res.attrs); - } - }); - }, - - - - // - - /** - * - * - * REQUIRED method if users expect to call Model.update() - * - * @param {[type]} collectionName [description] - * @param {[type]} options [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - update: function(connection, collectionName, options, values, cb) { + // Respond with error or the newly-created record. + cb(null, res.attrs); + } + }); + }, + + + // + + /** + * + * + * REQUIRED method if users expect to call Model.update() + * + * @param {[type]} collectionName [description] + * @param {[type]} options [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */ + update: function (connection, collectionName, options, values, cb) { //console.info("adaptor::update", collectionName); //console.info("::options", options); //console.info("::values", values); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition,values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition, values); - // id filter (bug?) - if (adapter.keyId in values && typeof values[adapter.keyId] === 'number'){ - if ('where' in options && adapter.keyId in options.where){ - values[adapter.keyId] = options.where[adapter.keyId]; - } - } + // id filter (bug?) + if (adapter.keyId in values && typeof values[adapter.keyId] === 'number') { + if ('where' in options && adapter.keyId in options.where) { + values[adapter.keyId] = options.where[adapter.keyId]; + } + } // 1. Filter, paginate, and sort records from the datastore. // You should end up w/ an array of objects as a result. @@ -571,37 +552,38 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - var updateValues = require("lodash").assign(options.where, values); + var updateValues = require("lodash").assign(options.where, values); //console.log(updateValues); - var current = Model.update(updateValues, function (err, res) { - if(err) { - console.warn('Error update data'+__filename, err); - cb(err); - } else { + var current = Model.update(updateValues, function (err, res) { + if (err) { + console.warn('Error update data' + __filename, err); + cb(err); + } + else { // console.log('add model data',res.attrs); - adapter._valueDecode(collection.definition,res.attrs); - // Respond with error or the newly-created record. - cb(null, [res.attrs]); - } - }); + adapter._valueDecode(collection.definition, res.attrs); + // Respond with error or the newly-created record. + cb(null, [res.attrs]); + } + }); // Respond with error or an array of updated records. // cb(null, []); }, - + /** * * REQUIRED method if users expect to call Model.destroy() - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - destroy: function(connection, collectionName, options, cb) { + destroy: function (connection, collectionName, options, cb) { //console.info("adaptor::destory", collectionName); //console.info("options", options); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -616,219 +598,215 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // (do both in a single query if you can-- it's faster) // Return an error, otherwise it's declared a success. - if ('where' in options){ - var values = options.where; - var current = Model.destroy(values, function(err, res){ - if(err) { - console.warn('Error destory data'+__filename, err); - cb(err); - } else { + if ('where' in options) { + var values = options.where; + var current = Model.destroy(values, function (err, res) { + if (err) { + console.warn('Error destory data' + __filename, err); + cb(err); + } + else { // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. - cb(); - } - }); - } - else + // Respond with error or the newly-created record. cb(); + } + }); + } + else + cb(); } /* - ********************************************** - * Optional overrides - ********************************************** - - // Optional override of built-in batch create logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, Waterline core uses create() - createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, - - // Optional override of built-in findOrCreate logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, uses find() and create() - findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, - */ + ********************************************** + * Optional overrides + ********************************************** + + // Optional override of built-in batch create logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, Waterline core uses create() + createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, + + // Optional override of built-in findOrCreate logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, uses find() and create() + findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, + */ /* - ********************************************** - * Custom methods - ********************************************** + ********************************************** + * Custom methods + ********************************************** + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // + // > NOTE: There are a few gotchas here you should be aware of. + // + // + The collectionName argument is always prepended as the first argument. + // This is so you can know which model is requesting the adapter. + // + // + All adapter functions are asynchronous, even the completely custom ones, + // and they must always include a callback as the final argument. + // The first argument of callbacks is always an error object. + // For core CRUD methods, Waterline will add support for .done()/promise usage. + // + // + The function signature for all CUSTOM adapter methods below must be: + // `function (collectionName, options, cb) { ... }` + // + //////////////////////////////////////////////////////////////////////////////////////////////////// + + + // Custom methods defined here will be available on all models + // which are hooked up to this adapter: + // + // e.g.: + // + foo: function (collectionName, options, cb) { + return cb(null,"ok"); + }, + bar: function (collectionName, options, cb) { + if (!options.jello) return cb("Failure!"); + else return cb(); + } + + // So if you have three models: + // Tiger, Sparrow, and User + // 2 of which (Tiger and Sparrow) implement this custom adapter, + // then you'll be able to access: + // + // Tiger.foo(...) + // Tiger.bar(...) + // Sparrow.foo(...) + // Sparrow.bar(...) + + + // Example success usage: + // + // (notice how the first argument goes away:) + Tiger.foo({}, function (err, result) { + if (err) return console.error(err); + else console.log(result); + + // outputs: ok + }); + + // Example error usage: + // + // (notice how the first argument goes away:) + Sparrow.bar({test: 'yes'}, function (err, result){ + if (err) console.error(err); + else console.log(result); + + // outputs: Failure! + }) - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // > NOTE: There are a few gotchas here you should be aware of. - // - // + The collectionName argument is always prepended as the first argument. - // This is so you can know which model is requesting the adapter. - // - // + All adapter functions are asynchronous, even the completely custom ones, - // and they must always include a callback as the final argument. - // The first argument of callbacks is always an error object. - // For core CRUD methods, Waterline will add support for .done()/promise usage. - // - // + The function signature for all CUSTOM adapter methods below must be: - // `function (collectionName, options, cb) { ... }` - // - //////////////////////////////////////////////////////////////////////////////////////////////////// - - - // Custom methods defined here will be available on all models - // which are hooked up to this adapter: - // - // e.g.: - // - foo: function (collectionName, options, cb) { - return cb(null,"ok"); - }, - bar: function (collectionName, options, cb) { - if (!options.jello) return cb("Failure!"); - else return cb(); - } - // So if you have three models: - // Tiger, Sparrow, and User - // 2 of which (Tiger and Sparrow) implement this custom adapter, - // then you'll be able to access: - // - // Tiger.foo(...) - // Tiger.bar(...) - // Sparrow.foo(...) - // Sparrow.bar(...) - - - // Example success usage: - // - // (notice how the first argument goes away:) - Tiger.foo({}, function (err, result) { - if (err) return console.error(err); - else console.log(result); - - // outputs: ok - }); - // Example error usage: - // - // (notice how the first argument goes away:) - Sparrow.bar({test: 'yes'}, function (err, result){ - if (err) console.error(err); - else console.log(result); - // outputs: Failure! - }) - - - - - */ - - /** - * set column attributes - * @param schema vogels::define return value - * @param name column name - * @param attr columns detail - * @private - */ - , _setColumnType: function(schema, name, attr, options){ - options = (typeof options !== 'undefined') ? options : {}; + */ - // set columns + /** + * set column attributes + * @param schema vogels::define return value + * @param name column name + * @param attr columns detail + * @private + */, _setColumnType: function (schema, name, attr, options) { + options = (typeof options !== 'undefined') ? options : {}; + + // set columns // console.log("name:", name); // console.log("attr:", attr); - var type = (require("lodash").isString(attr)) ? attr : attr.type; + var type = (require("lodash").isString(attr)) ? attr : attr.type; - switch (type){ - case "date": - case "time": - case "datetime": + switch (type) { + case "date": + case "time": + case "datetime": // console.log("Set Date:", name); - schema.Date(name, options); - break; + schema.Date(name, options); + break; - case "integer": - case "float": + case "integer": + case "float": // console.log("Set Number:", name); - schema.Number(name, options); - break; + schema.Number(name, options); + break; - case "boolean": + case "boolean": // console.log("Set Boolean:", name); - schema.Boolean(name, options); - break; + schema.Boolean(name, options); + break; - case "array": // not support - schema.StringSet(name, options); - break; + case "array": // not support + schema.StringSet(name, options); + break; // case "json": // case "string": // case "binary": - default: + default: // console.log("Set String", name); - schema.String(name, options); - break; - } + schema.String(name, options); + break; } + } - /** - * From Object to Array - * @param results response data - * @returns {Array} replaced array - * @private - */ - , _resultFormat: function(results){ - var items = [] - - for(var i in results.Items){ - items.push(results.Items[i].attrs); - } + /** + * From Object to Array + * @param results response data + * @returns {Array} replaced array + * @private + */, _resultFormat: function (results) { + var items = [] + + for (var i in results.Items) { + items.push(results.Items[i].attrs); + } //console.log(items); - return items; - } + return items; + } - /* - collection.definition; - { user_id: { primaryKey: true, unique: true, type: 'string' }, - range: { primaryKey: true, unique: true, type: 'integer' }, - title: { type: 'string' }, - chart1: { type: 'json' }, - chart2: { type: 'json' }, - chart3: { type: 'json' }, - createdAt: { type: 'datetime' }, - updatedAt: { type: 'datetime' } }, - */ - /** - * convert values - * @param definition - * @param values - * @private - */ - , _valueEncode: function(definition, values){ - adapter._valueConvert(definition, values, true); - } - , _valueDecode: function(definition, values){ - adapter._valueConvert(definition, values, false); - } - , _valueConvert: function(definition, values, encode){ - for(var key in definition){ - var type = definition[key].type; - - if(require("lodash").has(values, key)){ - switch(type){ - case "json": - if(!encode) values[key] = JSON.parse(values[key]); - else values[key] = JSON.stringify(values[key]); - break; - default : - break; - } - } + /* + collection.definition; + { user_id: { primaryKey: true, unique: true, type: 'string' }, + range: { primaryKey: true, unique: true, type: 'integer' }, + title: { type: 'string' }, + chart1: { type: 'json' }, + chart2: { type: 'json' }, + chart3: { type: 'json' }, + createdAt: { type: 'datetime' }, + updatedAt: { type: 'datetime' } }, + */ + /** + * convert values + * @param definition + * @param values + * @private + */, _valueEncode: function (definition, values) { + adapter._valueConvert(definition, values, true); + }, _valueDecode: function (definition, values) { + adapter._valueConvert(definition, values, false); + }, _valueConvert: function (definition, values, encode) { + for (var key in definition) { + var type = definition[key].type; + + if (require("lodash").has(values, key)) { + switch (type) { + case "json": + if (!encode) values[key] = JSON.parse(values[key]); + else values[key] = JSON.stringify(values[key]); + break; + default : + break; } + } } + } }; From a326ea5410b12b7fb8d6c75fd902399592ee9d50 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 15:48:05 +0100 Subject: [PATCH 04/60] Revert "Code Formatting ( no changes )" This reverts commit cb6bfd5d5b8bb4652517a556b9e1df60a0284d8f. --- index.js | 1000 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 511 insertions(+), 489 deletions(-) diff --git a/index.js b/index.js index 6aa168c..643e9bd 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ + /** * Module Dependencies */ @@ -16,11 +17,11 @@ var DynamoDB = false; * Sails Boilerplate Adapter * * Most of the methods below are optional. - * + * * If you don't need / can't get to every method, just implement * what you have time for. The other methods will only fail if * you try to call them! - * + * * For many adapters, this file is all you need. For very complex adapters, you may need more flexiblity. * In any case, it's probably a good idea to start with one file and refactor only if necessary. * If you do go that route, it's conventional in Node to create a `./lib` directory for your private submodules @@ -35,9 +36,9 @@ module.exports = (function () { // (aka model) that gets registered with this adapter. var _modelReferences = {}; - var _definedTables = {}; + var _definedTables = {}; - // You may also want to store additional, private data + // You may also want to store additional, private data // per-collection (esp. if your data store uses persistent // connections). // @@ -65,7 +66,10 @@ module.exports = (function () { var adapter = { - identity: 'sails-dynamodb', keyId: "id", indexPrefix: "-Index" + identity: 'sails-dynamodb' + + , keyId: "id" + , indexPrefix: "-Index" // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. @@ -76,7 +80,10 @@ module.exports = (function () { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - accessKeyId: null, secretAccessKey: null, region: 'us-west-1', credentialsFilePath: './credentials.json' + accessKeyId: null + , secretAccessKey: null + , region: 'us-west-1' + , credentialsFilePath: './credentials.json' // For example: // port: 3306, // host: 'localhost', @@ -98,169 +105,178 @@ module.exports = (function () { // safe => Don't change anything (good for production DBs) , migrate: 'alter' // , schema: false - }, _getModel: function (collectionName) { + } - var collection = _modelReferences[collectionName]; + + + , _getModel: function (collectionName) { + + var collection = _modelReferences[collectionName]; //console.log("currenct collection.definition", collection.definition); //console.log(collection); - /* - currenct collection - { - keyId: 'id', - indexPrefix: '-Index', - syncable: true, - defaults: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - _getModel: [Function], - _getPrimaryKeys: [Function], - registerCollection: [Function], - teardown: [Function], - define: [Function], - describe: [Function], - drop: [Function], - find: [Function], - _searchCondition: [Function], - create: [Function], - update: [Function], - destroy: [Function], - _setColumnType: [Function], - _resultFormat: [Function], - config: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - definition: - { user_id: { primaryKey: true, unique: true }, - name: { type: 'string', index: true }, - password: { type: 'string', index: true }, - email: { type: 'string', index: true }, - activated: { type: 'boolean', defaultsTo: false }, - activationToken: { type: 'string' }, - isSocial: { type: 'boolean' }, - socialActivated: { type: 'boolean' }, - createdAt: { type: 'datetime', default: 'NOW' }, - updatedAt: { type: 'datetime', default: 'NOW' } }, - identity: 'user' } - */ - - var primaryKeys = require("lodash").where(collection.definition, {primaryKey: true}); +/* +currenct collection +{ + keyId: 'id', + indexPrefix: '-Index', + syncable: true, + defaults: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + _getModel: [Function], + _getPrimaryKeys: [Function], + registerCollection: [Function], + teardown: [Function], + define: [Function], + describe: [Function], + drop: [Function], + find: [Function], + _searchCondition: [Function], + create: [Function], + update: [Function], + destroy: [Function], + _setColumnType: [Function], + _resultFormat: [Function], + config: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + definition: + { user_id: { primaryKey: true, unique: true }, + name: { type: 'string', index: true }, + password: { type: 'string', index: true }, + email: { type: 'string', index: true }, + activated: { type: 'boolean', defaultsTo: false }, + activationToken: { type: 'string' }, + isSocial: { type: 'boolean' }, + socialActivated: { type: 'boolean' }, + createdAt: { type: 'datetime', default: 'NOW' }, + updatedAt: { type: 'datetime', default: 'NOW' } }, + identity: 'user' } +*/ + +var primaryKeys = require("lodash").where(collection.definition, { primaryKey: true }); //console.log("primaryKeys", primaryKeys); - return Vogels.define(collectionName, function (schema) { + return Vogels.define(collectionName, function (schema) { //console.log("_getModel", collectionName); - var columns = collection.definition; - var primaryKeys = [] - var indexes = []; - // set columns - for (var columnName in columns) { - var attributes = columns[columnName]; + var columns = collection.definition; + var primaryKeys = [] + var indexes = []; + // set columns + for(var columnName in columns){ + var attributes = columns[columnName]; // console.log(columnName+":", attributes); - if (typeof attributes !== "function") { - adapter._setColumnType(schema, columnName, attributes); - // search primarykey + if(typeof attributes !== "function"){ + adapter._setColumnType(schema, columnName, attributes); + // search primarykey // if("primaryKey" in attributes)primaryKeys.push( columnName ); - // search index - if ("index" in attributes) indexes.push(columnName); - } - } - // set primary key - var primaryKeys = adapter._getPrimaryKeys(collectionName); - var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" + // search index + if("index" in attributes) indexes.push(columnName); + } + } + // set primary key + var primaryKeys = adapter._getPrimaryKeys(collectionName); + var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" // console.log("collection.definition", collection.definition); - if (primaryKeys.length < 1) - schema.UUID(adapter.keyId, {hashKey: true}); - else { - if (!require("lodash").isUndefined(primaryKeys[0])) { - adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); - if (!require("lodash").isUndefined(primaryKeys[1])) { - adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); + if(primaryKeys.length < 1) + schema.UUID( adapter.keyId, {hashKey: true}); + else{ + if (!require("lodash").isUndefined(primaryKeys[0])) { + adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); + if (!require("lodash").isUndefined(primaryKeys[1])) { + adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); + } + } } - } - } // schema.String( primaryKey, {hashKey: true}); - for (var i = 0; i < indexes.length; i++) { - var key = indexes[i]; - schema.globalIndex(key + adapter.indexPrefix, {hashKey: key}); - } - - schema.Date('createdAt', {default: Date.now}); - schema.Date('updatedAt', {default: Date.now}); - }); - }, _getPrimaryKeys: function (collectionName) { - var lodash = require("lodash"); - var collection = _modelReferences[collectionName]; + for(var i = 0; i < indexes.length; i++){ + var key = indexes[i]; + schema.globalIndex(key + adapter.indexPrefix, { hashKey: key}); + } - var maps = lodash.mapValues(collection.definition, "primaryKey"); - // console.log(results); - var list = lodash.pick(maps, function (value, key) { - return typeof value !== "undefined"; - }); - var primaryKeys = lodash.keys(list); - return primaryKeys; + schema.Date('createdAt', {default: Date.now}); + schema.Date('updatedAt', {default: Date.now}); + }); } - /** - * - * This method runs when a model is initially registered - * at server-start-time. This is the only required method. - * - * @param string collection [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */, registerConnection: function (connection, collections, cb) { + , _getPrimaryKeys: function (collectionName) { + var lodash = require("lodash"); + var collection = _modelReferences[collectionName]; + + var maps = lodash.mapValues(collection.definition, "primaryKey"); + // console.log(results); + var list = lodash.pick(maps, function (value, key) { + return typeof value !== "undefined"; + }); + var primaryKeys = lodash.keys(list); + return primaryKeys; + } + + /** + * + * This method runs when a model is initially registered + * at server-start-time. This is the only required method. + * + * @param string collection [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */ + , registerConnection: function (connection, collections, cb) { //var sails = require("sails"); //console.log("load registerConnection"); //console.log("::connection",connection); //console.log("::collections",collections); - if (!connection.identity) return cb(Errors.IdentityMissing); - if (connections[connection.identity]) return cb(Errors.IdentityDuplicate); + if(!connection.identity) return cb(Errors.IdentityMissing); + if(connections[connection.identity]) return cb(Errors.IdentityDuplicate); var error = null; - try { - AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); - } - catch (e) { - e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; - error = e; - } - // Keep a reference to this collection - _modelReferences = collections; - cb(error); + try{ + AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); + } + catch (e) { + e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; + error = e; + } + // Keep a reference to this collection + _modelReferences = collections; + cb(error); } /** * Fired when a model is unregistered, typically when the server * is killed. Useful for tearing-down remaining open connections, * etc. - * + * * @param {Function} cb [description] * @return {[type]} [description] - */, teardown: function (connection, cb) { + */ + , teardown: function(connection, cb) { cb(); }, + /** - * + * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} definition [description] * @param {Function} cb [description] * @return {[type]} [description] */ - define: function (connection, collectionName, definition, cb) { + define: function(connection, collectionName, definition, cb) { //console.info("adaptor::define"); //console.info("::collectionName", collectionName); //console.info("::definition", definition); @@ -269,26 +285,25 @@ module.exports = (function () { // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; - if (!_definedTables[collectionName]) { - var table = adapter._getModel(collectionName); - - _definedTables[collectionName] = table; - Vogels.createTables({ - collectionName: {readCapacity: 1, writeCapacity: 1} - }, function (err) { - if (err) { - console.warn('Error creating tables', err); - cb(err); - } - else { + if(! _definedTables[collectionName] ){ + var table = adapter._getModel(collectionName); + + _definedTables[collectionName] = table; + Vogels.createTables({ + collectionName: {readCapacity: 1, writeCapacity: 1} + }, function (err) { + if(err) { + console.warn('Error creating tables', err); + cb(err); + } else { // console.log('table are now created and active'); + cb(); + } + }); + } + else{ cb(); - } - }); - } - else { - cb(); - } + } // Define a new "table" or "collection" schema in the data store }, @@ -297,12 +312,12 @@ module.exports = (function () { * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {Function} cb [description] * @return {[type]} [description] */ - describe: function (connection, collectionName, cb) { + describe: function(connection, collectionName, cb) { //console.info("adaptor::describe"); //console.log("::connection",connection); //console.log("::collection",collectionName); @@ -314,34 +329,33 @@ module.exports = (function () { // Respond with the schema (attributes) for a collection or table in the data store var attributes = {}; - // extremly simple table names - var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec - var Endpoint = collection.connections[connection]['config']['endPoint']; - if (DynamoDB === false) { - DynamoDB = new AWS.DynamoDB( - Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} - : null - ); - if (Endpoint) - Vogels.dynamoDriver(DynamoDB); - } + // extremly simple table names + var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec + var Endpoint = collection.connections[connection]['config']['endPoint']; + if(DynamoDB === false) { + DynamoDB = new AWS.DynamoDB( + Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} + : null + ); + if (Endpoint) + Vogels.dynamoDriver(DynamoDB); + } - DynamoDB.describeTable({TableName: tableName}, function (err, res) { - if (err) { - if ('code' in err && err['code'] === 'ResourceNotFoundException') { - cb(); - } - else { - console.warn('Error describe tables' + __filename, err); - cb(err); - } + DynamoDB.describeTable({TableName:tableName}, function(err, res){ + if (err) { + if('code' in err && err['code'] === 'ResourceNotFoundException'){ + cb(); + } + else{ + console.warn('Error describe tables'+__filename, err); + cb(err); + } // console.log(err); // an error occurred - } - else { + } else { // console.log(data); // successful response - cb(); - } - }); + cb(); + } + }); }, @@ -350,13 +364,13 @@ module.exports = (function () { * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} relations [description] * @param {Function} cb [description] * @return {[type]} [description] */ - drop: function (connection, collectionName, relations, cb) { + drop: function(connection, collectionName, relations, cb) { //console.info("adaptor::drop", collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -366,6 +380,8 @@ module.exports = (function () { }, + + // OVERRIDES NOT CURRENTLY FULLY SUPPORTED FOR: // // alter: function (collectionName, changes, cb) {}, @@ -376,174 +392,177 @@ module.exports = (function () { // removeIndex: function(indexName, options, cb) {}, + /** - * + * * REQUIRED method if users expect to call Model.find(), Model.findOne(), * or related. - * + * * You should implement this method to respond with an array of instances. * Waterline core will take care of supporting all the other different * find methods/usages. - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - find: function (connection, collectionName, options, cb) { + find: function(connection, collectionName, options, cb) { //console.info("adaptor::find", collectionName); //console.info("::option", options); - var collection = _modelReferences[collectionName]; - // Options object is normalized for you: - // - // options.where - // options.limit - // options.skip - // options. - - // Filter, paginate, and sort records from the datastore. - // You should end up w/ an array of objects as a result. - // If no matches were found, this will be an empty array. - - if ('limit' in options && options.limit < 2) { - // query mode - // get primarykeys - var primaryKeys = adapter._getPrimaryKeys(collectionName); - // get current condition - var wheres = require("lodash").keys(options.where); - // compare both of keys - var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - // get model - var model = adapter._getModel(collectionName); - if (primaryQuery.length < 1) { // secondary key search - var hashKey = wheres[0]; - var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) - } - else { // primary key search - var hashKey = primaryKeys[0]; - var query = model.query(options.where[hashKey]); + var collection = _modelReferences[collectionName]; + // Options object is normalized for you: + // + // options.where + // options.limit + // options.skip + // options. + + // Filter, paginate, and sort records from the datastore. + // You should end up w/ an array of objects as a result. + // If no matches were found, this will be an empty array. + + if ('limit' in options && options.limit < 2 ){ + // query mode + // get primarykeys + var primaryKeys = adapter._getPrimaryKeys(collectionName); + // get current condition + var wheres = require("lodash").keys(options.where); + // compare both of keys + var primaryQuery = require("lodash").intersection(primaryKeys, wheres); + // get model + var model = adapter._getModel(collectionName); + if (primaryQuery.length < 1) { // secondary key search + var hashKey = wheres[0]; + var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) + } + else{ // primary key search + var hashKey = primaryKeys[0]; + var query = model.query(options.where[hashKey]); + } + } - - } - else { + else{ // scan mode - var query = adapter._getModel(collectionName).scan(); - // If you need to access your private data for this collection: + var query = adapter._getModel(collectionName).scan(); + // If you need to access your private data for this collection: - if ('where' in options && !options.where) { - for (var key in options['where']) { - //console.log(options['where'][key]); - query = query.where(key).contains(options['where'][key]); - } + if ('where' in options && !options.where){ + for(var key in options['where']){ + //console.log(options['where'][key]); + query = query.where(key).contains(options['where'][key]); + } - query = adapter._searchCondition(query, options); - } - else { - query = adapter._searchCondition(query, options); - } - } - - query.exec(function (err, res) { - if (!err) { - console.log("success", adapter._resultFormat(res)); - adapter._valueDecode(collection.definition, res.attrs); - cb(null, adapter._resultFormat(res)); - } - else { - console.warn('Error exec query:' + __filename, err); - cb(err); + query = adapter._searchCondition(query, options); + } + else{ + query = adapter._searchCondition(query, options); + } } - }); + query.exec( function(err, res){ + if(!err){ + console.log("success", adapter._resultFormat(res)); + adapter._valueDecode(collection.definition,res.attrs); + cb(null, adapter._resultFormat(res)); + } + else{ + console.warn('Error exec query:'+__filename, err); + cb(err); + } + }); + // Respond with an error, or the results. // cb(null, []); } - /** - * search condition - * @param query - * @param options - * @returns {*} - * @private - */, _searchCondition: function (query, options) { - if ('limit' in options) { + /** + * search condition + * @param query + * @param options + * @returns {*} + * @private + */ + , _searchCondition: function(query, options){ + if ('limit' in options){ // query = query.limit(1); - } + } - if ('skip' in options) { - } + if ('skip' in options){ + } - if ('sort' in options) { - } + if ('sort' in options){ + } - return query - } + return query + } - /** - * - * REQUIRED method if users expect to call Model.create() or any methods - * - * @param {[type]} collectionName [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */, create: function (connection, collectionName, values, cb) { + /** + * + * REQUIRED method if users expect to call Model.create() or any methods + * + * @param {[type]} collectionName [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */ + , create: function(connection, collectionName, values, cb) { //console.info("adaptor::create", collectionName); //console.info("values", values); //console.log("collection", _modelReferences[collectionName]); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition, values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition,values); - // Create a single new model (specified by `values`) - var current = Model.create(values, function (err, res) { - if (err) { - console.warn(__filename + ", create error:", err); - cb(err); - } - else { - adapter._valueDecode(collection.definition, res.attrs); + // Create a single new model (specified by `values`) + var current = Model.create(values, function(err, res){ + if(err) { + console.warn(__filename+", create error:", err); + cb(err); + } else { + adapter._valueDecode(collection.definition,res.attrs); // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. - cb(null, res.attrs); - } - }); - }, - - - // - - /** - * - * - * REQUIRED method if users expect to call Model.update() - * - * @param {[type]} collectionName [description] - * @param {[type]} options [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - update: function (connection, collectionName, options, values, cb) { + // Respond with error or the newly-created record. + cb(null, res.attrs); + } + }); + }, + + + + // + + /** + * + * + * REQUIRED method if users expect to call Model.update() + * + * @param {[type]} collectionName [description] + * @param {[type]} options [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */ + update: function(connection, collectionName, options, values, cb) { //console.info("adaptor::update", collectionName); //console.info("::options", options); //console.info("::values", values); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition, values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition,values); - // id filter (bug?) - if (adapter.keyId in values && typeof values[adapter.keyId] === 'number') { - if ('where' in options && adapter.keyId in options.where) { - values[adapter.keyId] = options.where[adapter.keyId]; - } - } + // id filter (bug?) + if (adapter.keyId in values && typeof values[adapter.keyId] === 'number'){ + if ('where' in options && adapter.keyId in options.where){ + values[adapter.keyId] = options.where[adapter.keyId]; + } + } // 1. Filter, paginate, and sort records from the datastore. // You should end up w/ an array of objects as a result. @@ -552,38 +571,37 @@ module.exports = (function () { // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - var updateValues = require("lodash").assign(options.where, values); + var updateValues = require("lodash").assign(options.where, values); //console.log(updateValues); - var current = Model.update(updateValues, function (err, res) { - if (err) { - console.warn('Error update data' + __filename, err); - cb(err); - } - else { + var current = Model.update(updateValues, function (err, res) { + if(err) { + console.warn('Error update data'+__filename, err); + cb(err); + } else { // console.log('add model data',res.attrs); - adapter._valueDecode(collection.definition, res.attrs); - // Respond with error or the newly-created record. - cb(null, [res.attrs]); - } - }); + adapter._valueDecode(collection.definition,res.attrs); + // Respond with error or the newly-created record. + cb(null, [res.attrs]); + } + }); // Respond with error or an array of updated records. // cb(null, []); }, - + /** * * REQUIRED method if users expect to call Model.destroy() - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - destroy: function (connection, collectionName, options, cb) { + destroy: function(connection, collectionName, options, cb) { //console.info("adaptor::destory", collectionName); //console.info("options", options); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -598,215 +616,219 @@ module.exports = (function () { // (do both in a single query if you can-- it's faster) // Return an error, otherwise it's declared a success. - if ('where' in options) { - var values = options.where; - var current = Model.destroy(values, function (err, res) { - if (err) { - console.warn('Error destory data' + __filename, err); - cb(err); - } - else { + if ('where' in options){ + var values = options.where; + var current = Model.destroy(values, function(err, res){ + if(err) { + console.warn('Error destory data'+__filename, err); + cb(err); + } else { // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. + // Respond with error or the newly-created record. + cb(); + } + }); + } + else cb(); - } - }); - } - else - cb(); } /* - ********************************************** - * Optional overrides - ********************************************** - - // Optional override of built-in batch create logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, Waterline core uses create() - createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, - - // Optional override of built-in findOrCreate logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, uses find() and create() - findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, - */ + ********************************************** + * Optional overrides + ********************************************** + + // Optional override of built-in batch create logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, Waterline core uses create() + createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, + + // Optional override of built-in findOrCreate logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, uses find() and create() + findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, + */ /* - ********************************************** - * Custom methods - ********************************************** - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // > NOTE: There are a few gotchas here you should be aware of. - // - // + The collectionName argument is always prepended as the first argument. - // This is so you can know which model is requesting the adapter. - // - // + All adapter functions are asynchronous, even the completely custom ones, - // and they must always include a callback as the final argument. - // The first argument of callbacks is always an error object. - // For core CRUD methods, Waterline will add support for .done()/promise usage. - // - // + The function signature for all CUSTOM adapter methods below must be: - // `function (collectionName, options, cb) { ... }` - // - //////////////////////////////////////////////////////////////////////////////////////////////////// - - - // Custom methods defined here will be available on all models - // which are hooked up to this adapter: - // - // e.g.: - // - foo: function (collectionName, options, cb) { - return cb(null,"ok"); - }, - bar: function (collectionName, options, cb) { - if (!options.jello) return cb("Failure!"); - else return cb(); - } - - // So if you have three models: - // Tiger, Sparrow, and User - // 2 of which (Tiger and Sparrow) implement this custom adapter, - // then you'll be able to access: - // - // Tiger.foo(...) - // Tiger.bar(...) - // Sparrow.foo(...) - // Sparrow.bar(...) - - - // Example success usage: - // - // (notice how the first argument goes away:) - Tiger.foo({}, function (err, result) { - if (err) return console.error(err); - else console.log(result); - - // outputs: ok - }); - - // Example error usage: - // - // (notice how the first argument goes away:) - Sparrow.bar({test: 'yes'}, function (err, result){ - if (err) console.error(err); - else console.log(result); - - // outputs: Failure! - }) + ********************************************** + * Custom methods + ********************************************** + //////////////////////////////////////////////////////////////////////////////////////////////////// + // + // > NOTE: There are a few gotchas here you should be aware of. + // + // + The collectionName argument is always prepended as the first argument. + // This is so you can know which model is requesting the adapter. + // + // + All adapter functions are asynchronous, even the completely custom ones, + // and they must always include a callback as the final argument. + // The first argument of callbacks is always an error object. + // For core CRUD methods, Waterline will add support for .done()/promise usage. + // + // + The function signature for all CUSTOM adapter methods below must be: + // `function (collectionName, options, cb) { ... }` + // + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Custom methods defined here will be available on all models + // which are hooked up to this adapter: + // + // e.g.: + // + foo: function (collectionName, options, cb) { + return cb(null,"ok"); + }, + bar: function (collectionName, options, cb) { + if (!options.jello) return cb("Failure!"); + else return cb(); + } - */ + // So if you have three models: + // Tiger, Sparrow, and User + // 2 of which (Tiger and Sparrow) implement this custom adapter, + // then you'll be able to access: + // + // Tiger.foo(...) + // Tiger.bar(...) + // Sparrow.foo(...) + // Sparrow.bar(...) - /** - * set column attributes - * @param schema vogels::define return value - * @param name column name - * @param attr columns detail - * @private - */, _setColumnType: function (schema, name, attr, options) { - options = (typeof options !== 'undefined') ? options : {}; - - // set columns + + // Example success usage: + // + // (notice how the first argument goes away:) + Tiger.foo({}, function (err, result) { + if (err) return console.error(err); + else console.log(result); + + // outputs: ok + }); + + // Example error usage: + // + // (notice how the first argument goes away:) + Sparrow.bar({test: 'yes'}, function (err, result){ + if (err) console.error(err); + else console.log(result); + + // outputs: Failure! + }) + + + + + */ + + /** + * set column attributes + * @param schema vogels::define return value + * @param name column name + * @param attr columns detail + * @private + */ + , _setColumnType: function(schema, name, attr, options){ + options = (typeof options !== 'undefined') ? options : {}; + + // set columns // console.log("name:", name); // console.log("attr:", attr); - var type = (require("lodash").isString(attr)) ? attr : attr.type; + var type = (require("lodash").isString(attr)) ? attr : attr.type; - switch (type) { - case "date": - case "time": - case "datetime": + switch (type){ + case "date": + case "time": + case "datetime": // console.log("Set Date:", name); - schema.Date(name, options); - break; + schema.Date(name, options); + break; - case "integer": - case "float": + case "integer": + case "float": // console.log("Set Number:", name); - schema.Number(name, options); - break; + schema.Number(name, options); + break; - case "boolean": + case "boolean": // console.log("Set Boolean:", name); - schema.Boolean(name, options); - break; + schema.Boolean(name, options); + break; - case "array": // not support - schema.StringSet(name, options); - break; + case "array": // not support + schema.StringSet(name, options); + break; // case "json": // case "string": // case "binary": - default: + default: // console.log("Set String", name); - schema.String(name, options); - break; + schema.String(name, options); + break; + } } - } - /** - * From Object to Array - * @param results response data - * @returns {Array} replaced array - * @private - */, _resultFormat: function (results) { - var items = [] - - for (var i in results.Items) { - items.push(results.Items[i].attrs); - } + /** + * From Object to Array + * @param results response data + * @returns {Array} replaced array + * @private + */ + , _resultFormat: function(results){ + var items = [] + + for(var i in results.Items){ + items.push(results.Items[i].attrs); + } //console.log(items); - return items; - } + return items; + } - /* - collection.definition; - { user_id: { primaryKey: true, unique: true, type: 'string' }, - range: { primaryKey: true, unique: true, type: 'integer' }, - title: { type: 'string' }, - chart1: { type: 'json' }, - chart2: { type: 'json' }, - chart3: { type: 'json' }, - createdAt: { type: 'datetime' }, - updatedAt: { type: 'datetime' } }, - */ - /** - * convert values - * @param definition - * @param values - * @private - */, _valueEncode: function (definition, values) { - adapter._valueConvert(definition, values, true); - }, _valueDecode: function (definition, values) { - adapter._valueConvert(definition, values, false); - }, _valueConvert: function (definition, values, encode) { - for (var key in definition) { - var type = definition[key].type; - - if (require("lodash").has(values, key)) { - switch (type) { - case "json": - if (!encode) values[key] = JSON.parse(values[key]); - else values[key] = JSON.stringify(values[key]); - break; - default : - break; + /* + collection.definition; + { user_id: { primaryKey: true, unique: true, type: 'string' }, + range: { primaryKey: true, unique: true, type: 'integer' }, + title: { type: 'string' }, + chart1: { type: 'json' }, + chart2: { type: 'json' }, + chart3: { type: 'json' }, + createdAt: { type: 'datetime' }, + updatedAt: { type: 'datetime' } }, + */ + /** + * convert values + * @param definition + * @param values + * @private + */ + , _valueEncode: function(definition, values){ + adapter._valueConvert(definition, values, true); + } + , _valueDecode: function(definition, values){ + adapter._valueConvert(definition, values, false); + } + , _valueConvert: function(definition, values, encode){ + for(var key in definition){ + var type = definition[key].type; + + if(require("lodash").has(values, key)){ + switch(type){ + case "json": + if(!encode) values[key] = JSON.parse(values[key]); + else values[key] = JSON.stringify(values[key]); + break; + default : + break; + } + } } - } } - } }; From 399d0de40281d7bf81d5dc5cd340a74b2a08c6d5 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 15:54:49 +0100 Subject: [PATCH 05/60] Code Formatting ( no changes ) --- index.js | 1000 ++++++++++++++++++++++++++---------------------------- 1 file changed, 489 insertions(+), 511 deletions(-) diff --git a/index.js b/index.js index 643e9bd..cc31fad 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ - /** * Module Dependencies */ @@ -17,11 +16,11 @@ var DynamoDB = false; * Sails Boilerplate Adapter * * Most of the methods below are optional. - * + * * If you don't need / can't get to every method, just implement * what you have time for. The other methods will only fail if * you try to call them! - * + * * For many adapters, this file is all you need. For very complex adapters, you may need more flexiblity. * In any case, it's probably a good idea to start with one file and refactor only if necessary. * If you do go that route, it's conventional in Node to create a `./lib` directory for your private submodules @@ -36,9 +35,9 @@ module.exports = (function () { // (aka model) that gets registered with this adapter. var _modelReferences = {}; - var _definedTables = {}; + var _definedTables = {}; - // You may also want to store additional, private data + // You may also want to store additional, private data // per-collection (esp. if your data store uses persistent // connections). // @@ -66,10 +65,7 @@ module.exports = (function () { var adapter = { - identity: 'sails-dynamodb' - - , keyId: "id" - , indexPrefix: "-Index" + identity: 'sails-dynamodb', keyId: "id", indexPrefix: "-Index" // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. @@ -80,10 +76,7 @@ module.exports = (function () { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - accessKeyId: null - , secretAccessKey: null - , region: 'us-west-1' - , credentialsFilePath: './credentials.json' + accessKeyId: null, secretAccessKey: null, region: 'us-west-1', credentialsFilePath: './credentials.json' // For example: // port: 3306, // host: 'localhost', @@ -105,178 +98,169 @@ module.exports = (function () { // safe => Don't change anything (good for production DBs) , migrate: 'alter' // , schema: false - } - - + }, _getModel: function (collectionName) { - , _getModel: function (collectionName) { - - var collection = _modelReferences[collectionName]; + var collection = _modelReferences[collectionName]; //console.log("currenct collection.definition", collection.definition); //console.log(collection); -/* -currenct collection -{ - keyId: 'id', - indexPrefix: '-Index', - syncable: true, - defaults: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - _getModel: [Function], - _getPrimaryKeys: [Function], - registerCollection: [Function], - teardown: [Function], - define: [Function], - describe: [Function], - drop: [Function], - find: [Function], - _searchCondition: [Function], - create: [Function], - update: [Function], - destroy: [Function], - _setColumnType: [Function], - _resultFormat: [Function], - config: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - credentialsFilePath: './credentials.json', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - definition: - { user_id: { primaryKey: true, unique: true }, - name: { type: 'string', index: true }, - password: { type: 'string', index: true }, - email: { type: 'string', index: true }, - activated: { type: 'boolean', defaultsTo: false }, - activationToken: { type: 'string' }, - isSocial: { type: 'boolean' }, - socialActivated: { type: 'boolean' }, - createdAt: { type: 'datetime', default: 'NOW' }, - updatedAt: { type: 'datetime', default: 'NOW' } }, - identity: 'user' } -*/ - -var primaryKeys = require("lodash").where(collection.definition, { primaryKey: true }); + /* + currenct collection + { + keyId: 'id', + indexPrefix: '-Index', + syncable: true, + defaults: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + _getModel: [Function], + _getPrimaryKeys: [Function], + registerCollection: [Function], + teardown: [Function], + define: [Function], + describe: [Function], + drop: [Function], + find: [Function], + _searchCondition: [Function], + create: [Function], + update: [Function], + destroy: [Function], + _setColumnType: [Function], + _resultFormat: [Function], + config: + { accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', + credentialsFilePath: './credentials.json', + migrate: 'alter', + adapter: 'sails-dynamodb' }, + definition: + { user_id: { primaryKey: true, unique: true }, + name: { type: 'string', index: true }, + password: { type: 'string', index: true }, + email: { type: 'string', index: true }, + activated: { type: 'boolean', defaultsTo: false }, + activationToken: { type: 'string' }, + isSocial: { type: 'boolean' }, + socialActivated: { type: 'boolean' }, + createdAt: { type: 'datetime', default: 'NOW' }, + updatedAt: { type: 'datetime', default: 'NOW' } }, + identity: 'user' } + */ + + var primaryKeys = require("lodash").where(collection.definition, {primaryKey: true}); //console.log("primaryKeys", primaryKeys); - return Vogels.define(collectionName, function (schema) { + return Vogels.define(collectionName, function (schema) { //console.log("_getModel", collectionName); - var columns = collection.definition; - var primaryKeys = [] - var indexes = []; - // set columns - for(var columnName in columns){ - var attributes = columns[columnName]; + var columns = collection.definition; + var primaryKeys = [] + var indexes = []; + // set columns + for (var columnName in columns) { + var attributes = columns[columnName]; // console.log(columnName+":", attributes); - if(typeof attributes !== "function"){ - adapter._setColumnType(schema, columnName, attributes); - // search primarykey + if (typeof attributes !== "function") { + adapter._setColumnType(schema, columnName, attributes); + // search primarykey // if("primaryKey" in attributes)primaryKeys.push( columnName ); - // search index - if("index" in attributes) indexes.push(columnName); - } - } - // set primary key - var primaryKeys = adapter._getPrimaryKeys(collectionName); - var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" + // search index + if ("index" in attributes) indexes.push(columnName); + } + } + // set primary key + var primaryKeys = adapter._getPrimaryKeys(collectionName); + var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" // console.log("collection.definition", collection.definition); - if(primaryKeys.length < 1) - schema.UUID( adapter.keyId, {hashKey: true}); - else{ - if (!require("lodash").isUndefined(primaryKeys[0])) { - adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); - if (!require("lodash").isUndefined(primaryKeys[1])) { - adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); - } - } + if (primaryKeys.length < 1) + schema.UUID(adapter.keyId, {hashKey: true}); + else { + if (!require("lodash").isUndefined(primaryKeys[0])) { + adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); + if (!require("lodash").isUndefined(primaryKeys[1])) { + adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); } + } + } // schema.String( primaryKey, {hashKey: true}); - for(var i = 0; i < indexes.length; i++){ - var key = indexes[i]; - schema.globalIndex(key + adapter.indexPrefix, { hashKey: key}); - } - - schema.Date('createdAt', {default: Date.now}); - schema.Date('updatedAt', {default: Date.now}); - }); - } + for (var i = 0; i < indexes.length; i++) { + var key = indexes[i]; + schema.globalIndex(key + adapter.indexPrefix, {hashKey: key}); + } - , _getPrimaryKeys: function (collectionName) { - var lodash = require("lodash"); - var collection = _modelReferences[collectionName]; + schema.Date('createdAt', {default: Date.now}); + schema.Date('updatedAt', {default: Date.now}); + }); + }, _getPrimaryKeys: function (collectionName) { + var lodash = require("lodash"); + var collection = _modelReferences[collectionName]; - var maps = lodash.mapValues(collection.definition, "primaryKey"); - // console.log(results); - var list = lodash.pick(maps, function (value, key) { - return typeof value !== "undefined"; - }); - var primaryKeys = lodash.keys(list); - return primaryKeys; + var maps = lodash.mapValues(collection.definition, "primaryKey"); + // console.log(results); + var list = lodash.pick(maps, function (value, key) { + return typeof value !== "undefined"; + }); + var primaryKeys = lodash.keys(list); + return primaryKeys; } - - /** - * - * This method runs when a model is initially registered - * at server-start-time. This is the only required method. - * - * @param string collection [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - , registerConnection: function (connection, collections, cb) { + + /** + * + * This method runs when a model is initially registered + * at server-start-time. This is the only required method. + * + * @param string collection [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */, registerConnection: function (connection, collections, cb) { //var sails = require("sails"); //console.log("load registerConnection"); //console.log("::connection",connection); //console.log("::collections",collections); - if(!connection.identity) return cb(Errors.IdentityMissing); - if(connections[connection.identity]) return cb(Errors.IdentityDuplicate); + if (!connection.identity) return cb(Errors.IdentityMissing); + if (connections[connection.identity]) return cb(Errors.IdentityDuplicate); var error = null; - try{ - AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); - } - catch (e) { - e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; - error = e; - } - // Keep a reference to this collection - _modelReferences = collections; - cb(error); + try { + AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); + } + catch (e) { + e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; + error = e; + } + // Keep a reference to this collection + _modelReferences = collections; + cb(error); } /** * Fired when a model is unregistered, typically when the server * is killed. Useful for tearing-down remaining open connections, * etc. - * + * * @param {Function} cb [description] * @return {[type]} [description] - */ - , teardown: function(connection, cb) { + */, teardown: function (connection, cb) { cb(); }, - /** - * + * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} definition [description] * @param {Function} cb [description] * @return {[type]} [description] */ - define: function(connection, collectionName, definition, cb) { + define: function (connection, collectionName, definition, cb) { //console.info("adaptor::define"); //console.info("::collectionName", collectionName); //console.info("::definition", definition); @@ -285,25 +269,26 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; - if(! _definedTables[collectionName] ){ - var table = adapter._getModel(collectionName); - - _definedTables[collectionName] = table; - Vogels.createTables({ - collectionName: {readCapacity: 1, writeCapacity: 1} - }, function (err) { - if(err) { - console.warn('Error creating tables', err); - cb(err); - } else { + if (!_definedTables[collectionName]) { + var table = adapter._getModel(collectionName); + + _definedTables[collectionName] = table; + Vogels.createTables({ + collectionName: {readCapacity: 1, writeCapacity: 1} + }, function (err) { + if (err) { + console.warn('Error creating tables', err); + cb(err); + } + else { // console.log('table are now created and active'); - cb(); - } - }); - } - else{ cb(); - } + } + }); + } + else { + cb(); + } // Define a new "table" or "collection" schema in the data store }, @@ -312,12 +297,12 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {Function} cb [description] * @return {[type]} [description] */ - describe: function(connection, collectionName, cb) { + describe: function (connection, collectionName, cb) { //console.info("adaptor::describe"); //console.log("::connection",connection); //console.log("::collection",collectionName); @@ -329,33 +314,34 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // Respond with the schema (attributes) for a collection or table in the data store var attributes = {}; - // extremly simple table names - var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec - var Endpoint = collection.connections[connection]['config']['endPoint']; - if(DynamoDB === false) { - DynamoDB = new AWS.DynamoDB( - Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} - : null - ); - if (Endpoint) - Vogels.dynamoDriver(DynamoDB); - } + // extremly simple table names + var tableName = collectionName.toLowerCase() + 's'; // 's' is vogels spec + var Endpoint = collection.connections[connection]['config']['endPoint']; + if (DynamoDB === false) { + DynamoDB = new AWS.DynamoDB( + Endpoint ? {endpoint: new AWS.Endpoint(Endpoint)} + : null + ); + if (Endpoint) + Vogels.dynamoDriver(DynamoDB); + } - DynamoDB.describeTable({TableName:tableName}, function(err, res){ - if (err) { - if('code' in err && err['code'] === 'ResourceNotFoundException'){ - cb(); - } - else{ - console.warn('Error describe tables'+__filename, err); - cb(err); - } + DynamoDB.describeTable({TableName: tableName}, function (err, res) { + if (err) { + if ('code' in err && err['code'] === 'ResourceNotFoundException') { + cb(); + } + else { + console.warn('Error describe tables' + __filename, err); + cb(err); + } // console.log(err); // an error occurred - } else { + } + else { // console.log(data); // successful response - cb(); - } - }); + cb(); + } + }); }, @@ -364,13 +350,13 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t * * REQUIRED method if integrating with a schemaful * (SQL-ish) database. - * + * * @param {[type]} collectionName [description] * @param {[type]} relations [description] * @param {Function} cb [description] * @return {[type]} [description] */ - drop: function(connection, collectionName, relations, cb) { + drop: function (connection, collectionName, relations, cb) { //console.info("adaptor::drop", collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -380,8 +366,6 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t }, - - // OVERRIDES NOT CURRENTLY FULLY SUPPORTED FOR: // // alter: function (collectionName, changes, cb) {}, @@ -392,177 +376,174 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // removeIndex: function(indexName, options, cb) {}, - /** - * + * * REQUIRED method if users expect to call Model.find(), Model.findOne(), * or related. - * + * * You should implement this method to respond with an array of instances. * Waterline core will take care of supporting all the other different * find methods/usages. - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - find: function(connection, collectionName, options, cb) { + find: function (connection, collectionName, options, cb) { //console.info("adaptor::find", collectionName); //console.info("::option", options); - var collection = _modelReferences[collectionName]; - // Options object is normalized for you: - // - // options.where - // options.limit - // options.skip - // options. - - // Filter, paginate, and sort records from the datastore. - // You should end up w/ an array of objects as a result. - // If no matches were found, this will be an empty array. - - if ('limit' in options && options.limit < 2 ){ - // query mode - // get primarykeys - var primaryKeys = adapter._getPrimaryKeys(collectionName); - // get current condition - var wheres = require("lodash").keys(options.where); - // compare both of keys - var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - // get model - var model = adapter._getModel(collectionName); - if (primaryQuery.length < 1) { // secondary key search - var hashKey = wheres[0]; - var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) - } - else{ // primary key search - var hashKey = primaryKeys[0]; - var query = model.query(options.where[hashKey]); - } - + var collection = _modelReferences[collectionName]; + // Options object is normalized for you: + // + // options.where + // options.limit + // options.skip + // options. + + // Filter, paginate, and sort records from the datastore. + // You should end up w/ an array of objects as a result. + // If no matches were found, this will be an empty array. + + if ('limit' in options && options.limit < 2) { + // query mode + // get primarykeys + var primaryKeys = adapter._getPrimaryKeys(collectionName); + // get current condition + var wheres = require("lodash").keys(options.where); + // compare both of keys + var primaryQuery = require("lodash").intersection(primaryKeys, wheres); + // get model + var model = adapter._getModel(collectionName); + if (primaryQuery.length < 1) { // secondary key search + var hashKey = wheres[0]; + var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) } - else{ + else { // primary key search + var hashKey = primaryKeys[0]; + var query = model.query(options.where[hashKey]); + } + + } + else { // scan mode - var query = adapter._getModel(collectionName).scan(); - // If you need to access your private data for this collection: + var query = adapter._getModel(collectionName).scan(); + // If you need to access your private data for this collection: - if ('where' in options && !options.where){ - for(var key in options['where']){ - //console.log(options['where'][key]); - query = query.where(key).contains(options['where'][key]); - } + if ('where' in options && !options.where) { + for (var key in options['where']) { + //console.log(options['where'][key]); + query = query.where(key).contains(options['where'][key]); + } - query = adapter._searchCondition(query, options); - } - else{ - query = adapter._searchCondition(query, options); - } + query = adapter._searchCondition(query, options); } + else { + query = adapter._searchCondition(query, options); + } + } + + query.exec(function (err, res) { + if (!err) { + console.log("success", adapter._resultFormat(res)); + adapter._valueDecode(collection.definition, res.attrs); + cb(null, adapter._resultFormat(res)); + } + else { + console.warn('Error exec query:' + __filename, err); + cb(err); + } + }); - query.exec( function(err, res){ - if(!err){ - console.log("success", adapter._resultFormat(res)); - adapter._valueDecode(collection.definition,res.attrs); - cb(null, adapter._resultFormat(res)); - } - else{ - console.warn('Error exec query:'+__filename, err); - cb(err); - } - }); - // Respond with an error, or the results. // cb(null, []); } - /** - * search condition - * @param query - * @param options - * @returns {*} - * @private - */ - , _searchCondition: function(query, options){ - if ('limit' in options){ + /** + * search condition + * @param query + * @param options + * @returns {*} + * @private + */, _searchCondition: function (query, options) { + if ('limit' in options) { // query = query.limit(1); - } - - if ('skip' in options){ - } + } - if ('sort' in options){ - } + if ('skip' in options) { + } - return query + if ('sort' in options) { } + return query + } - /** - * - * REQUIRED method if users expect to call Model.create() or any methods - * - * @param {[type]} collectionName [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - , create: function(connection, collectionName, values, cb) { + + /** + * + * REQUIRED method if users expect to call Model.create() or any methods + * + * @param {[type]} collectionName [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */, create: function (connection, collectionName, values, cb) { //console.info("adaptor::create", collectionName); //console.info("values", values); //console.log("collection", _modelReferences[collectionName]); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition,values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition, values); - // Create a single new model (specified by `values`) - var current = Model.create(values, function(err, res){ - if(err) { - console.warn(__filename+", create error:", err); - cb(err); - } else { - adapter._valueDecode(collection.definition,res.attrs); + // Create a single new model (specified by `values`) + var current = Model.create(values, function (err, res) { + if (err) { + console.warn(__filename + ", create error:", err); + cb(err); + } + else { + adapter._valueDecode(collection.definition, res.attrs); // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. - cb(null, res.attrs); - } - }); - }, - - - - // - - /** - * - * - * REQUIRED method if users expect to call Model.update() - * - * @param {[type]} collectionName [description] - * @param {[type]} options [description] - * @param {[type]} values [description] - * @param {Function} cb [description] - * @return {[type]} [description] - */ - update: function(connection, collectionName, options, values, cb) { + // Respond with error or the newly-created record. + cb(null, res.attrs); + } + }); + }, + + + // + + /** + * + * + * REQUIRED method if users expect to call Model.update() + * + * @param {[type]} collectionName [description] + * @param {[type]} options [description] + * @param {[type]} values [description] + * @param {Function} cb [description] + * @return {[type]} [description] + */ + update: function (connection, collectionName, options, values, cb) { //console.info("adaptor::update", collectionName); //console.info("::options", options); //console.info("::values", values); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); - // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; - adapter._valueEncode(collection.definition,values); + // If you need to access your private data for this collection: + var collection = _modelReferences[collectionName]; + adapter._valueEncode(collection.definition, values); - // id filter (bug?) - if (adapter.keyId in values && typeof values[adapter.keyId] === 'number'){ - if ('where' in options && adapter.keyId in options.where){ - values[adapter.keyId] = options.where[adapter.keyId]; - } - } + // id filter (bug?) + if (adapter.keyId in values && typeof values[adapter.keyId] === 'number') { + if ('where' in options && adapter.keyId in options.where) { + values[adapter.keyId] = options.where[adapter.keyId]; + } + } // 1. Filter, paginate, and sort records from the datastore. // You should end up w/ an array of objects as a result. @@ -571,37 +552,38 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - var updateValues = require("lodash").assign(options.where, values); + var updateValues = require("lodash").assign(options.where, values); //console.log(updateValues); - var current = Model.update(updateValues, function (err, res) { - if(err) { - console.warn('Error update data'+__filename, err); - cb(err); - } else { + var current = Model.update(updateValues, function (err, res) { + if (err) { + console.warn('Error update data' + __filename, err); + cb(err); + } + else { // console.log('add model data',res.attrs); - adapter._valueDecode(collection.definition,res.attrs); - // Respond with error or the newly-created record. - cb(null, [res.attrs]); - } - }); + adapter._valueDecode(collection.definition, res.attrs); + // Respond with error or the newly-created record. + cb(null, [res.attrs]); + } + }); // Respond with error or an array of updated records. // cb(null, []); }, - + /** * * REQUIRED method if users expect to call Model.destroy() - * + * * @param {[type]} collectionName [description] * @param {[type]} options [description] * @param {Function} cb [description] * @return {[type]} [description] */ - destroy: function(connection, collectionName, options, cb) { + destroy: function (connection, collectionName, options, cb) { //console.info("adaptor::destory", collectionName); //console.info("options", options); - var Model = adapter._getModel(collectionName); + var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -616,219 +598,215 @@ var primaryKeys = require("lodash").where(collection.definition, { primaryKey: t // (do both in a single query if you can-- it's faster) // Return an error, otherwise it's declared a success. - if ('where' in options){ - var values = options.where; - var current = Model.destroy(values, function(err, res){ - if(err) { - console.warn('Error destory data'+__filename, err); - cb(err); - } else { + if ('where' in options) { + var values = options.where; + var current = Model.destroy(values, function (err, res) { + if (err) { + console.warn('Error destory data' + __filename, err); + cb(err); + } + else { // console.log('add model data',res.attrs); - // Respond with error or the newly-created record. - cb(); - } - }); - } - else + // Respond with error or the newly-created record. cb(); + } + }); + } + else + cb(); } /* - ********************************************** - * Optional overrides - ********************************************** - - // Optional override of built-in batch create logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, Waterline core uses create() - createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, - - // Optional override of built-in findOrCreate logic for increased efficiency - // (since most databases include optimizations for pooled queries, at least intra-connection) - // otherwise, uses find() and create() - findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, - */ + ********************************************** + * Optional overrides + ********************************************** + + // Optional override of built-in batch create logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, Waterline core uses create() + createEach: function (collectionName, arrayOfObjects, cb) { cb(); }, + + // Optional override of built-in findOrCreate logic for increased efficiency + // (since most databases include optimizations for pooled queries, at least intra-connection) + // otherwise, uses find() and create() + findOrCreate: function (collectionName, arrayOfAttributeNamesWeCareAbout, newAttributesObj, cb) { cb(); }, + */ /* - ********************************************** - * Custom methods - ********************************************** + ********************************************** + * Custom methods + ********************************************** + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // + // > NOTE: There are a few gotchas here you should be aware of. + // + // + The collectionName argument is always prepended as the first argument. + // This is so you can know which model is requesting the adapter. + // + // + All adapter functions are asynchronous, even the completely custom ones, + // and they must always include a callback as the final argument. + // The first argument of callbacks is always an error object. + // For core CRUD methods, Waterline will add support for .done()/promise usage. + // + // + The function signature for all CUSTOM adapter methods below must be: + // `function (collectionName, options, cb) { ... }` + // + //////////////////////////////////////////////////////////////////////////////////////////////////// + + + // Custom methods defined here will be available on all models + // which are hooked up to this adapter: + // + // e.g.: + // + foo: function (collectionName, options, cb) { + return cb(null,"ok"); + }, + bar: function (collectionName, options, cb) { + if (!options.jello) return cb("Failure!"); + else return cb(); + } + + // So if you have three models: + // Tiger, Sparrow, and User + // 2 of which (Tiger and Sparrow) implement this custom adapter, + // then you'll be able to access: + // + // Tiger.foo(...) + // Tiger.bar(...) + // Sparrow.foo(...) + // Sparrow.bar(...) + + + // Example success usage: + // + // (notice how the first argument goes away:) + Tiger.foo({}, function (err, result) { + if (err) return console.error(err); + else console.log(result); + + // outputs: ok + }); + + // Example error usage: + // + // (notice how the first argument goes away:) + Sparrow.bar({test: 'yes'}, function (err, result){ + if (err) console.error(err); + else console.log(result); + + // outputs: Failure! + }) - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // > NOTE: There are a few gotchas here you should be aware of. - // - // + The collectionName argument is always prepended as the first argument. - // This is so you can know which model is requesting the adapter. - // - // + All adapter functions are asynchronous, even the completely custom ones, - // and they must always include a callback as the final argument. - // The first argument of callbacks is always an error object. - // For core CRUD methods, Waterline will add support for .done()/promise usage. - // - // + The function signature for all CUSTOM adapter methods below must be: - // `function (collectionName, options, cb) { ... }` - // - //////////////////////////////////////////////////////////////////////////////////////////////////// - - - // Custom methods defined here will be available on all models - // which are hooked up to this adapter: - // - // e.g.: - // - foo: function (collectionName, options, cb) { - return cb(null,"ok"); - }, - bar: function (collectionName, options, cb) { - if (!options.jello) return cb("Failure!"); - else return cb(); - } - // So if you have three models: - // Tiger, Sparrow, and User - // 2 of which (Tiger and Sparrow) implement this custom adapter, - // then you'll be able to access: - // - // Tiger.foo(...) - // Tiger.bar(...) - // Sparrow.foo(...) - // Sparrow.bar(...) - - - // Example success usage: - // - // (notice how the first argument goes away:) - Tiger.foo({}, function (err, result) { - if (err) return console.error(err); - else console.log(result); - - // outputs: ok - }); - // Example error usage: - // - // (notice how the first argument goes away:) - Sparrow.bar({test: 'yes'}, function (err, result){ - if (err) console.error(err); - else console.log(result); - // outputs: Failure! - }) - - - - - */ - - /** - * set column attributes - * @param schema vogels::define return value - * @param name column name - * @param attr columns detail - * @private - */ - , _setColumnType: function(schema, name, attr, options){ - options = (typeof options !== 'undefined') ? options : {}; + */ - // set columns + /** + * set column attributes + * @param schema vogels::define return value + * @param name column name + * @param attr columns detail + * @private + */, _setColumnType: function (schema, name, attr, options) { + options = (typeof options !== 'undefined') ? options : {}; + + // set columns // console.log("name:", name); // console.log("attr:", attr); - var type = (require("lodash").isString(attr)) ? attr : attr.type; + var type = (require("lodash").isString(attr)) ? attr : attr.type; - switch (type){ - case "date": - case "time": - case "datetime": + switch (type) { + case "date": + case "time": + case "datetime": // console.log("Set Date:", name); - schema.Date(name, options); - break; + schema.Date(name, options); + break; - case "integer": - case "float": + case "integer": + case "float": // console.log("Set Number:", name); - schema.Number(name, options); - break; + schema.Number(name, options); + break; - case "boolean": + case "boolean": // console.log("Set Boolean:", name); - schema.Boolean(name, options); - break; + schema.Boolean(name, options); + break; - case "array": // not support - schema.StringSet(name, options); - break; + case "array": // not support + schema.StringSet(name, options); + break; // case "json": // case "string": // case "binary": - default: + default: // console.log("Set String", name); - schema.String(name, options); - break; - } + schema.String(name, options); + break; } + } - /** - * From Object to Array - * @param results response data - * @returns {Array} replaced array - * @private - */ - , _resultFormat: function(results){ - var items = [] - - for(var i in results.Items){ - items.push(results.Items[i].attrs); - } + /** + * From Object to Array + * @param results response data + * @returns {Array} replaced array + * @private + */, _resultFormat: function (results) { + var items = [] + + for (var i in results.Items) { + items.push(results.Items[i].attrs); + } //console.log(items); - return items; - } + return items; + } - /* - collection.definition; - { user_id: { primaryKey: true, unique: true, type: 'string' }, - range: { primaryKey: true, unique: true, type: 'integer' }, - title: { type: 'string' }, - chart1: { type: 'json' }, - chart2: { type: 'json' }, - chart3: { type: 'json' }, - createdAt: { type: 'datetime' }, - updatedAt: { type: 'datetime' } }, - */ - /** - * convert values - * @param definition - * @param values - * @private - */ - , _valueEncode: function(definition, values){ - adapter._valueConvert(definition, values, true); - } - , _valueDecode: function(definition, values){ - adapter._valueConvert(definition, values, false); - } - , _valueConvert: function(definition, values, encode){ - for(var key in definition){ - var type = definition[key].type; - - if(require("lodash").has(values, key)){ - switch(type){ - case "json": - if(!encode) values[key] = JSON.parse(values[key]); - else values[key] = JSON.stringify(values[key]); - break; - default : - break; - } - } + /* + collection.definition; + { user_id: { primaryKey: true, unique: true, type: 'string' }, + range: { primaryKey: true, unique: true, type: 'integer' }, + title: { type: 'string' }, + chart1: { type: 'json' }, + chart2: { type: 'json' }, + chart3: { type: 'json' }, + createdAt: { type: 'datetime' }, + updatedAt: { type: 'datetime' } }, + */ + /** + * convert values + * @param definition + * @param values + * @private + */, _valueEncode: function (definition, values) { + adapter._valueConvert(definition, values, true); + }, _valueDecode: function (definition, values) { + adapter._valueConvert(definition, values, false); + }, _valueConvert: function (definition, values, encode) { + for (var key in definition) { + var type = definition[key].type; + + if (require("lodash").has(values, key)) { + switch (type) { + case "json": + if (!encode) values[key] = JSON.parse(values[key]); + else values[key] = JSON.stringify(values[key]); + break; + default : + break; } + } } + } }; From 3b73b9d779546ad97de8b7acfa7a4fdf3a8bca9c Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 16:01:54 +0100 Subject: [PATCH 06/60] Adding support for find where --- README.md | 16 ++++++++++++++++ index.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 300f798..d754b6a 100755 --- a/README.md +++ b/README.md @@ -50,6 +50,22 @@ module.exports.adapters = { }; ``` +## Find +Support for where is added as following: +``` + ?where={"name":{"null":true}} + ?where={"name":{"notNull":true}} + ?where={"name":{"equals":"firstName lastName"}} + ?where={"name":{"lte":"firstName lastName"}} + ?where={"name":{"lt":"firstName lastName"}} + ?where={"name":{"gte":"firstName lastName"}} + ?where={"name":{"gt":"firstName lastName"}} + ?where={"name":{"contains":"firstName lastName"}} + ?where={"name":{"contains":"firstName lastName"}} + ?where={"name":{"beginsWith":"firstName"}} + ?where={"name":{"in":["firstName lastName", "another name"]}} + ?where={"name":{"between":["firstName, "lastName""]}} +``` ## Testing Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. diff --git a/index.js b/index.js index cc31fad..64343d9 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,32 @@ var Vogels = require('vogels'); var AWS = Vogels.AWS; var _ = require('lodash'); var DynamoDB = false; +var filters = { + //?where={"name":{"null":true}} + null: false, + //?where={"name":{"notNull":true}} + notNull: false, + //?where={"name":{"equals":"firstName lastName"}} + equals: true, + //?where={"name":{"lte":"firstName lastName"}} + lte: true, + //?where={"name":{"lt":"firstName lastName"}} + lt: true, + //?where={"name":{"gte":"firstName lastName"}} + gte: true, + //?where={"name":{"gt":"firstName lastName"}} + gt: true, + //?where={"name":{"contains":"firstName lastName"}} + contains: true, + //?where={"name":{"contains":"firstName lastName"}} + notContains: true, + //?where={"name":{"beginsWith":"firstName"}} + beginsWith: true, + //?where={"name":{"in":["firstName lastName", "another name"]}} + in: true, + //?where={"name":{"between":["firstName, "lastName""]}} + between: true +}; /** * Sails Boilerplate Adapter @@ -207,6 +233,14 @@ module.exports = (function () { }); var primaryKeys = lodash.keys(list); return primaryKeys; + }, _keys: function (collectionName) { + var lodash = require("lodash"); + var collection = _modelReferences[collectionName]; + + var list = lodash.pick(collection.definition, function (value, key) { + return (typeof value !== "undefined"); + }); + return lodash.keys(list); } /** @@ -431,12 +465,24 @@ module.exports = (function () { var query = adapter._getModel(collectionName).scan(); // If you need to access your private data for this collection: - if ('where' in options && !options.where) { + if ('where' in options && _.isObject(options.where)) { for (var key in options['where']) { - //console.log(options['where'][key]); - query = query.where(key).contains(options['where'][key]); + if (adapter._keys(collectionName).indexOf(key) === -1) { + return cb("Wrong attribute given : " + key); + } + var filter = _.keys(options['where'][key])[0]; + if (filter in filters) { + try { + query = query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); + } + catch (e) { + return cb(e.message); + } + } + else { + return cb("Wrong filter given :" + filter); + } } - query = adapter._searchCondition(query, options); } else { From c459afd8e3ca0946a9dc621e9935c0783b79c00f Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 16:27:32 +0100 Subject: [PATCH 07/60] Updating repo URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d754b6a..b3b63af 100755 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Install is through NPM. ```bash $ sails new project && cd project -$ npm install git://github.com/dohzoh/sails-dynamodb.git +$ npm install git://github.com/gadelkareem/sails-dynamodb.git $ cp node_modules/sails-dynamodb/credentials.example.json ./credentials.json # & put your amazon keys ``` Todo: to npm package From 7623f82359530f53a9220b2f70d811671ecc0a67 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 17:51:59 +0100 Subject: [PATCH 08/60] Fix search with key value --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index 64343d9..60f4c1b 100644 --- a/index.js +++ b/index.js @@ -470,6 +470,9 @@ module.exports = (function () { if (adapter._keys(collectionName).indexOf(key) === -1) { return cb("Wrong attribute given : " + key); } + if (!_.isObject(options['where'][key])) { + continue; + } var filter = _.keys(options['where'][key])[0]; if (filter in filters) { try { From 4b0da537ffd61f94f24e8970423a80106ad0e495 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 18:10:34 +0100 Subject: [PATCH 09/60] Fix search with key value --- index.js | 79 +++++++++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/index.js b/index.js index 60f4c1b..3afcf1b 100644 --- a/index.js +++ b/index.js @@ -436,63 +436,44 @@ module.exports = (function () { // options.skip // options. - // Filter, paginate, and sort records from the datastore. - // You should end up w/ an array of objects as a result. - // If no matches were found, this will be an empty array. - - if ('limit' in options && options.limit < 2) { - // query mode - // get primarykeys - var primaryKeys = adapter._getPrimaryKeys(collectionName); - // get current condition - var wheres = require("lodash").keys(options.where); - // compare both of keys - var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - // get model - var model = adapter._getModel(collectionName); - if (primaryQuery.length < 1) { // secondary key search - var hashKey = wheres[0]; - var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) - } - else { // primary key search - var hashKey = primaryKeys[0]; - var query = model.query(options.where[hashKey]); - } - - } - else { - // scan mode - var query = adapter._getModel(collectionName).scan(); - // If you need to access your private data for this collection: - - if ('where' in options && _.isObject(options.where)) { - for (var key in options['where']) { - if (adapter._keys(collectionName).indexOf(key) === -1) { - return cb("Wrong attribute given : " + key); + // scan mode + var query = adapter._getModel(collectionName).scan(), + modelKeys = adapter._keys(collectionName); + + if ('where' in options && _.isObject(options.where)) { + for (var key in options['where']) { + if (modelKeys.indexOf(key) === -1) { + return cb("Wrong attribute given : " + key); + } + if (_.isString(options['where'][key])) { + try { + query = query.where(key).equals(options['where'][key]); } - if (!_.isObject(options['where'][key])) { - continue; + catch (e) { + return cb(e.message); } - var filter = _.keys(options['where'][key])[0]; - if (filter in filters) { - try { - query = query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); - } - catch (e) { - return cb(e.message); - } + continue; + } + var filter = _.keys(options['where'][key])[0]; + if (filter in filters) { + try { + query = query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); } - else { - return cb("Wrong filter given :" + filter); + catch (e) { + return cb(e.message); } } - query = adapter._searchCondition(query, options); - } - else { - query = adapter._searchCondition(query, options); + else { + return cb("Wrong filter given :" + filter); + } } + query = adapter._searchCondition(query, options); + } + else { + query = adapter._searchCondition(query, options); } + query.exec(function (err, res) { if (!err) { console.log("success", adapter._resultFormat(res)); From 22aec440a00df232b80b06ad054dab2aa0a6eede Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 19:58:27 +0100 Subject: [PATCH 10/60] Pagination support --- README.md | 13 +++++++ index.js | 110 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index b3b63af..bbda1c7 100755 --- a/README.md +++ b/README.md @@ -66,6 +66,19 @@ Support for where is added as following: ?where={"name":{"in":["firstName lastName", "another name"]}} ?where={"name":{"between":["firstName, "lastName""]}} ``` + + +## Pagination +Support for where is added as following: +1. First add a limit to current request +``` + /user?limit=2 +``` +2. Then get the last primaryKey value and send it as startKey in the next request +``` + /user?limit=2&startKey={"PrimaryKey": "2"} +``` + ## Testing Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. diff --git a/index.js b/index.js index 3afcf1b..db461ca 100644 --- a/index.js +++ b/index.js @@ -436,44 +436,78 @@ module.exports = (function () { // options.skip // options. - // scan mode - var query = adapter._getModel(collectionName).scan(), - modelKeys = adapter._keys(collectionName); - - if ('where' in options && _.isObject(options.where)) { - for (var key in options['where']) { - if (modelKeys.indexOf(key) === -1) { - return cb("Wrong attribute given : " + key); - } - if (_.isString(options['where'][key])) { - try { - query = query.where(key).equals(options['where'][key]); + // Filter, paginate, and sort records from the datastore. + // You should end up w/ an array of objects as a result. + // If no matches were found, this will be an empty array. + + if ('limit' in options && options.limit < 2) { + // query mode + // get primarykeys + var primaryKeys = adapter._getPrimaryKeys(collectionName); + // get current condition + var wheres = require("lodash").keys(options.where); + // compare both of keys + var primaryQuery = require("lodash").intersection(primaryKeys, wheres); + // get model + var model = adapter._getModel(collectionName); + if (primaryQuery.length < 1) { // secondary key search + var hashKey = wheres[0]; + var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) + } + else { // primary key search + var hashKey = primaryKeys[0]; + var query = model.query(options.where[hashKey]); + } + + } + else { + // scan mode + var query = adapter._getModel(collectionName).scan(), + modelKeys = adapter._keys(collectionName); + + if ('where' in options && _.isObject(options.where)) { + for (var key in options['where']) { + if (key == 'startKey') { + try { + query.startKey(JSON.parse(options['where'][key])); + } + catch (e) { + return cb("Wrong start key format :" + e.message); + } + continue; } - catch (e) { - return cb(e.message); + if (modelKeys.indexOf(key) === -1) { + return cb("Wrong attribute given : " + key); } - continue; - } - var filter = _.keys(options['where'][key])[0]; - if (filter in filters) { - try { - query = query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); + if (_.isString(options['where'][key])) { + try { + query.where(key).equals(options['where'][key]); + } + catch (e) { + return cb(e.message); + } + continue; } - catch (e) { - return cb(e.message); + var filter = _.keys(options['where'][key])[0]; + if (filter in filters) { + try { + query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); + } + catch (e) { + return cb(e.message); + } + } + else { + return cb("Wrong filter given :" + filter); } } - else { - return cb("Wrong filter given :" + filter); - } + query = adapter._searchCondition(query, options); + } + else { + query = adapter._searchCondition(query, options); } - query = adapter._searchCondition(query, options); - } - else { - query = adapter._searchCondition(query, options); } - query.exec(function (err, res) { if (!err) { console.log("success", adapter._resultFormat(res)); @@ -488,28 +522,24 @@ module.exports = (function () { // Respond with an error, or the results. // cb(null, []); - } - /** + }/** * search condition * @param query * @param options * @returns {*} * @private */, _searchCondition: function (query, options) { - if ('limit' in options) { -// query = query.limit(1); - } - - if ('skip' in options) { - } - if ('sort' in options) { + query = query.sort(options.sort); + } + if ('limit' in options) { + query = query.limit(options.limit); } - return query } + /** * * REQUIRED method if users expect to call Model.create() or any methods From 537175b35142c65905b2eab72d4c1f52f3dd64c1 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 26 Nov 2014 20:23:43 +0100 Subject: [PATCH 11/60] Adding sort support --- README.md | 2 +- index.js | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bbda1c7..70b4233 100755 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Support for where is added as following: ## Pagination -Support for where is added as following: +Support for Pagination is added as following: 1. First add a limit to current request ``` /user?limit=2 diff --git a/index.js b/index.js index db461ca..e2457f3 100644 --- a/index.js +++ b/index.js @@ -501,11 +501,8 @@ module.exports = (function () { return cb("Wrong filter given :" + filter); } } - query = adapter._searchCondition(query, options); - } - else { - query = adapter._searchCondition(query, options); } + query = adapter._searchCondition(query, options); } query.exec(function (err, res) { @@ -530,7 +527,14 @@ module.exports = (function () { * @private */, _searchCondition: function (query, options) { if ('sort' in options) { - query = query.sort(options.sort); + //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward + var sort = _.keys(options.sort)[0]; + if (sort == 1) { + query = query.ascending(); + } + else if (sort == -1) { + query = query.descending(); + } } if ('limit' in options) { query = query.limit(options.limit); From 93825276cef1b74093c79e1bbe4b3af74af81083 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Thu, 27 Nov 2014 22:57:26 +0100 Subject: [PATCH 12/60] Fixing array value search --- index.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index e2457f3..9640220 100644 --- a/index.js +++ b/index.js @@ -425,8 +425,8 @@ module.exports = (function () { * @return {[type]} [description] */ find: function (connection, collectionName, options, cb) { -//console.info("adaptor::find", collectionName); -//console.info("::option", options); + console.info("adaptor::find", collectionName); + console.info("::option", options); var collection = _modelReferences[collectionName]; // Options object is normalized for you: @@ -479,25 +479,28 @@ module.exports = (function () { if (modelKeys.indexOf(key) === -1) { return cb("Wrong attribute given : " + key); } - if (_.isString(options['where'][key])) { + var filter = _.keys(options['where'][key])[0]; + if (filter in filters) { try { - query.where(key).equals(options['where'][key]); + query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); } catch (e) { return cb(e.message); } - continue; } - var filter = _.keys(options['where'][key])[0]; - if (filter in filters) { + else { try { - query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); + if (_.isString(options['where'][key]) || _.isNumber(options['where'][key])) { + query.where(key).equals(options['where'][key]); + } + else if (_.isArray(options['where'][key])) { + query.where(key).in(options['where'][key]); + } + continue; } catch (e) { return cb(e.message); } - } - else { return cb("Wrong filter given :" + filter); } } @@ -612,9 +615,9 @@ module.exports = (function () { // 1. Filter, paginate, and sort records from the datastore. // You should end up w/ an array of objects as a result. // If no matches were found, this will be an empty array. - // + // // 2. Update all result records with `values`. - // + // // (do both in a single query if you can-- it's faster) var updateValues = require("lodash").assign(options.where, values); //console.log(updateValues); @@ -656,9 +659,9 @@ module.exports = (function () { // 1. Filter, paginate, and sort records from the datastore. // You should end up w/ an array of objects as a result. // If no matches were found, this will be an empty array. - // + // // 2. Destroy all result records. - // + // // (do both in a single query if you can-- it's faster) // Return an error, otherwise it's declared a success. From 64f3919c962afb465b3291947ab07f9cfd2c2554 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Thu, 27 Nov 2014 23:05:23 +0100 Subject: [PATCH 13/60] Error on none matching values on find --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index 9640220..fdd758c 100644 --- a/index.js +++ b/index.js @@ -495,6 +495,8 @@ module.exports = (function () { } else if (_.isArray(options['where'][key])) { query.where(key).in(options['where'][key]); + }else{ + return cb("Wrong value given : " + options['where'][key]); } continue; } From f42f3f253851b9ca5a10edb5203ef3780997e025 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Mon, 1 Dec 2014 22:36:48 +0100 Subject: [PATCH 14/60] Removing primaryKeys search based on limit and merging it to scan instead --- index.js | 64 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/index.js b/index.js index fdd758c..1b3cec5 100644 --- a/index.js +++ b/index.js @@ -110,11 +110,11 @@ module.exports = (function () { // ssl: false, // customThings: ['eh'] - // If setting syncable, you should consider the migrate option, + // If setting syncable, you should consider the migrate option, // which allows you to set how the sync will be performed. // It can be overridden globally in an app (config/adapters.js) // and on a per-model basis. - // + // // IMPORTANT: // `migrate` is not a production data migration solution! // In production, always use `migrate: safe` @@ -200,8 +200,8 @@ module.exports = (function () { } } // set primary key - var primaryKeys = adapter._getPrimaryKeys(collectionName); - var primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" + primaryKeys = adapter._getPrimaryKeys(collectionName); + primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" // console.log("collection.definition", collection.definition); if (primaryKeys.length < 1) schema.UUID(adapter.keyId, {hashKey: true}); @@ -401,7 +401,7 @@ module.exports = (function () { // OVERRIDES NOT CURRENTLY FULLY SUPPORTED FOR: - // + // // alter: function (collectionName, changes, cb) {}, // addAttribute: function(collectionName, attrName, attrDef, cb) {}, // removeAttribute: function(collectionName, attrName, attrDef, cb) {}, @@ -429,6 +429,8 @@ module.exports = (function () { console.info("::option", options); var collection = _modelReferences[collectionName]; + var model = adapter._getModel(collectionName); + var query = null; // Options object is normalized for you: // // options.where @@ -440,32 +442,29 @@ module.exports = (function () { // You should end up w/ an array of objects as a result. // If no matches were found, this will be an empty array. - if ('limit' in options && options.limit < 2) { - // query mode - // get primarykeys + if ('where' in options && _.isObject(options.where)) { var primaryKeys = adapter._getPrimaryKeys(collectionName); // get current condition var wheres = require("lodash").keys(options.where); // compare both of keys var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - // get model - var model = adapter._getModel(collectionName); - if (primaryQuery.length < 1) { // secondary key search - var hashKey = wheres[0]; - var query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) - } - else { // primary key search - var hashKey = primaryKeys[0]; - var query = model.query(options.where[hashKey]); - } + if (primaryQuery.length == wheres.length) { //TODO: search for indexes in wheres + var hashKey = null; +// if (primaryQuery.length < 1) { // secondary key search +// hashKey = wheres[0]; +// query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) +// } +// else { // primary key search + hashKey = primaryKeys[0]; + query = model.query(options.where[hashKey]); +// } - } - else { - // scan mode - var query = adapter._getModel(collectionName).scan(), - modelKeys = adapter._keys(collectionName); + } + else { + // scan mode + query = model.scan(); + var modelKeys = adapter._keys(collectionName); - if ('where' in options && _.isObject(options.where)) { for (var key in options['where']) { if (key == 'startKey') { try { @@ -495,7 +494,8 @@ module.exports = (function () { } else if (_.isArray(options['where'][key])) { query.where(key).in(options['where'][key]); - }else{ + } + else { return cb("Wrong value given : " + options['where'][key]); } continue; @@ -507,9 +507,8 @@ module.exports = (function () { } } } - query = adapter._searchCondition(query, options); } - + query = adapter._searchCondition(query, options, model); query.exec(function (err, res) { if (!err) { console.log("success", adapter._resultFormat(res)); @@ -530,19 +529,22 @@ module.exports = (function () { * @param options * @returns {*} * @private - */, _searchCondition: function (query, options) { + */, _searchCondition: function (query, options, model) { + if (!query) { + query = model.scan().loadAll(); + } if ('sort' in options) { //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward var sort = _.keys(options.sort)[0]; if (sort == 1) { - query = query.ascending(); + query.ascending(); } else if (sort == -1) { - query = query.descending(); + query.descending(); } } if ('limit' in options) { - query = query.limit(options.limit); + query.limit(options.limit); } return query } From f1f83a11f47af684e6d968dfb5b25301dbdeec04 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 2 Dec 2014 18:56:18 +0100 Subject: [PATCH 15/60] Fixing limit with loadall --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 1b3cec5..a7bbcce 100644 --- a/index.js +++ b/index.js @@ -531,7 +531,7 @@ module.exports = (function () { * @private */, _searchCondition: function (query, options, model) { if (!query) { - query = model.scan().loadAll(); + query = model.scan(); } if ('sort' in options) { //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward @@ -545,6 +545,8 @@ module.exports = (function () { } if ('limit' in options) { query.limit(options.limit); + }else{ + query.loadAll(); } return query } From da105f2df708125f050d1a5e6c575516fef06065 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Thu, 4 Dec 2014 20:15:44 +0100 Subject: [PATCH 16/60] Moving credentials to connection options --- index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index a7bbcce..7ad5580 100644 --- a/index.js +++ b/index.js @@ -261,7 +261,12 @@ module.exports = (function () { var error = null; try { - AWS.config.update(JSON.parse(AWS.util.readFileSync('./credentials.json'))); + //TODO: readme and remove credentials.json ref + AWS.config.update({ + "accessKeyId": connection.accessKeyId, + "secretAccessKey": connection.secretAccessKey, + "region": connection.region + }); } catch (e) { e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; @@ -545,7 +550,8 @@ module.exports = (function () { } if ('limit' in options) { query.limit(options.limit); - }else{ + } + else { query.loadAll(); } return query From 453c38888edd03151607a0fa6d5ba58b2a3ad13f Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 5 Dec 2014 15:09:39 +0100 Subject: [PATCH 17/60] Removing credentials.json and updating readme file --- README.md | 35 +++++------------------------------ credentials.example.json | 5 ----- index.js | 7 ++----- package.json | 31 +++++++++++++++++++++++++------ 4 files changed, 32 insertions(+), 46 deletions(-) delete mode 100644 credentials.example.json diff --git a/README.md b/README.md index 70b4233..04087b1 100755 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Install is through NPM. ```bash $ sails new project && cd project $ npm install git://github.com/gadelkareem/sails-dynamodb.git -$ cp node_modules/sails-dynamodb/credentials.example.json ./credentials.json # & put your amazon keys ``` +Add your amazon keys to your adapter config + Todo: to npm package @@ -33,6 +34,9 @@ module.exports.adapters = { dynamoDb: { adapter: "sails-dynamodb", + accessKeyId: process.env.DYNAMO_ACCESS_KEY_ID, + secretAccessKey: process.env.DYNAMO_SECRET_ACCESS_KEY, + region: "us-west-1" endPoint: "http://localhost:8000", // Optional: add for DynamoDB local }, @@ -50,35 +54,6 @@ module.exports.adapters = { }; ``` -## Find -Support for where is added as following: -``` - ?where={"name":{"null":true}} - ?where={"name":{"notNull":true}} - ?where={"name":{"equals":"firstName lastName"}} - ?where={"name":{"lte":"firstName lastName"}} - ?where={"name":{"lt":"firstName lastName"}} - ?where={"name":{"gte":"firstName lastName"}} - ?where={"name":{"gt":"firstName lastName"}} - ?where={"name":{"contains":"firstName lastName"}} - ?where={"name":{"contains":"firstName lastName"}} - ?where={"name":{"beginsWith":"firstName"}} - ?where={"name":{"in":["firstName lastName", "another name"]}} - ?where={"name":{"between":["firstName, "lastName""]}} -``` - - -## Pagination -Support for Pagination is added as following: -1. First add a limit to current request -``` - /user?limit=2 -``` -2. Then get the last primaryKey value and send it as startKey in the next request -``` - /user?limit=2&startKey={"PrimaryKey": "2"} -``` - ## Testing Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. diff --git a/credentials.example.json b/credentials.example.json deleted file mode 100644 index 7c29450..0000000 --- a/credentials.example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "accessKeyId": "YOUR_ACCESS_KEY_ID" - , "secretAccessKey": "YOUR_SECRET_ACCESS_KEY" - , "region": "us-east-1" -} \ No newline at end of file diff --git a/index.js b/index.js index 7ad5580..845e2a3 100644 --- a/index.js +++ b/index.js @@ -102,7 +102,7 @@ module.exports = (function () { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - accessKeyId: null, secretAccessKey: null, region: 'us-west-1', credentialsFilePath: './credentials.json' + accessKeyId: null, secretAccessKey: null, region: 'us-west-1' // For example: // port: 3306, // host: 'localhost', @@ -140,7 +140,6 @@ module.exports = (function () { { accessKeyId: null, secretAccessKey: null, region: 'us-west-1', - credentialsFilePath: './credentials.json', migrate: 'alter', adapter: 'sails-dynamodb' }, _getModel: [Function], @@ -161,7 +160,6 @@ module.exports = (function () { { accessKeyId: null, secretAccessKey: null, region: 'us-west-1', - credentialsFilePath: './credentials.json', migrate: 'alter', adapter: 'sails-dynamodb' }, definition: @@ -261,7 +259,6 @@ module.exports = (function () { var error = null; try { - //TODO: readme and remove credentials.json ref AWS.config.update({ "accessKeyId": connection.accessKeyId, "secretAccessKey": connection.secretAccessKey, @@ -269,7 +266,7 @@ module.exports = (function () { }); } catch (e) { - e.message = e.message + ". Please create credentials.json on your sails project root and restart node"; + e.message = e.message + ". Please make sure you added the right keys to your adapter config"; error = e; } // Keep a reference to this collection diff --git a/package.json b/package.json index 6ae13c7..c3b26f3 100755 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/dohzoh/sails-dynamodb.git" + "url": "https://github.com/gadelkareem/sails-dynamodb.git" }, "keywords": [ "dynamodb", @@ -20,13 +20,22 @@ "sailsjs", "sails.js" ], - "author": "dohzoh", + "author": [ + { + "name": "dohzoh", + "email": "dohzoh@outlook.com" + }, + { + "name": "gadelkareem", + "email": "gadelkareem@gmail.com" + } + ], "license": "MIT", "readmeFilename": "README.md", "dependencies": { - "vogels": "*" - , "lodash": "" - , "async": "" + "vogels": "*", + "lodash": "", + "async": "" }, "devDependencies": { "mocha": "~1.13.0", @@ -40,5 +49,15 @@ "queryable", "associations" ] - } + }, + "readme": "# sails-dynamodb\n\nA [Waterline](https://github.com/balderdashy/waterline) adapter for DynamoDB. May be used in a [Sails](https://github.com/balderdashy/sails) app or anything using Waterline for the ORM.\n\n\n> _**Note:** This adapter support the Sails.js v0.10.x. check 0.9 branch if you use before v0.10\n\n## Install\n\nInstall is through NPM.\n\n```bash\n$ sails new project && cd project\n$ npm install git://github.com/gadelkareem/sails-dynamodb.git\n$ cp node_modules/sails-dynamodb/credentials.example.json ./credentials.json # & put your amazon keys\n```\nTodo: to npm package\n\n\n## Configuration\n\nThe following config options are available along with their default values:\n\nconfig/connection.js\n```javascript\nmodule.exports.adapters = {\n\n // If you leave the adapter config unspecified \n // in a model definition, 'default' will be used.\n localDiskDb: {\n adapter: 'sails-disk'\n },\n \n dynamoDb: {\n adapter: \"sails-dynamodb\",\n endPoint: \"http://localhost:8000\", // Optional: add for DynamoDB local\n },\n \n};\n```\n\nconfig/models.js\n```javascript\nmodule.exports.adapters = {\n\n // If you leave the adapter config unspecified \n // in a model definition, 'default' will be used.\n connection: 'dynamoDb'\n \n};\n```\n\n## Testing\n\nTest are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API.\n\nTo run tests:\n\n```bash\n$ npm test\n```\n\n\n## About Sails.js and Waterline\nhttp://sailsjs.org\n\nWaterline is a new kind of storage and retrieval engine for Sails.js. It provides a uniform API for accessing stuff from different kinds of databases, protocols, and 3rd party APIs. That means you write the same code to get users, whether they live in mySQL, LDAP, MongoDB, or Facebook.\n\n\n![image_squidhome@2x.png](http://i.imgur.com/RIvu9.png) \n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 dozo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n", + "bugs": { + "url": "https://github.com/gadelkareem/sails-dynamodb/issues" + }, + "_id": "sails-dynamodb@0.10.0", + "dist": { + "shasum": "2dc7fe333c4f0a95f92bff00f7e89f4c1a773220" + }, + "_resolved": "git://github.com/gadelkareem/sails-dynamodb.git#da8474615e68a043115f9e943acca5e28dd7a0ab", + "_from": "sails-dynamodb@git://github.com/gadelkareem/sails-dynamodb.git" } From 602f1d62fb523dcfb5350f9864e7a95747e9b26b Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 5 Dec 2014 15:46:24 +0100 Subject: [PATCH 18/60] Adding npm package --- README.md | 4 +--- package.json | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 04087b1..a18141b 100755 --- a/README.md +++ b/README.md @@ -11,12 +11,10 @@ Install is through NPM. ```bash $ sails new project && cd project -$ npm install git://github.com/gadelkareem/sails-dynamodb.git +$ npm install sails-dynamodb --save ``` Add your amazon keys to your adapter config -Todo: to npm package - ## Configuration diff --git a/package.json b/package.json index c3b26f3..d22670f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.10.0", + "version": "0.11.0", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { @@ -54,10 +54,12 @@ "bugs": { "url": "https://github.com/gadelkareem/sails-dynamodb/issues" }, - "_id": "sails-dynamodb@0.10.0", + "_id": "sails-dynamodb@0.11.0", "dist": { "shasum": "2dc7fe333c4f0a95f92bff00f7e89f4c1a773220" }, "_resolved": "git://github.com/gadelkareem/sails-dynamodb.git#da8474615e68a043115f9e943acca5e28dd7a0ab", - "_from": "sails-dynamodb@git://github.com/gadelkareem/sails-dynamodb.git" + "_from": "sails-dynamodb@*", + "homepage": "https://github.com/gadelkareem/sails-dynamodb", + "_shasum": "fbbe01ec1482df7edc9e9eec01ba60414a9007a4" } From 29fc259f742d966a1d001e98a96a7f81fb600748 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 5 Dec 2014 15:49:33 +0100 Subject: [PATCH 19/60] updating for npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d22670f..ad7310b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.11.0", + "version": "0.11.1", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { From 6407e9b5cf026d030e35093586742a7fd20ef945 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 9 Dec 2014 17:54:14 +0100 Subject: [PATCH 20/60] Adding query using global index --- index.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 845e2a3..4193eaf 100644 --- a/index.js +++ b/index.js @@ -239,6 +239,14 @@ module.exports = (function () { return (typeof value !== "undefined"); }); return lodash.keys(list); + }, _indexes: function (collectionName) { + var lodash = require("lodash"); + var collection = _modelReferences[collectionName]; + + var list = lodash.pick(collection.definition, function (value, key) { + return ("index" in value && value.index === true) + }); + return lodash.keys(list); } /** @@ -446,21 +454,23 @@ module.exports = (function () { if ('where' in options && _.isObject(options.where)) { var primaryKeys = adapter._getPrimaryKeys(collectionName); + var modelIndexes = adapter._indexes(collectionName); + // get current condition var wheres = require("lodash").keys(options.where); // compare both of keys var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - if (primaryQuery.length == wheres.length) { //TODO: search for indexes in wheres - var hashKey = null; -// if (primaryQuery.length < 1) { // secondary key search -// hashKey = wheres[0]; -// query = model.query(options.where[hashKey]).usingIndex(wheres[0] + adapter.indexPrefix) -// } -// else { // primary key search + var indexQuery = require("lodash").intersection(modelIndexes, wheres); + + if (primaryQuery.length == wheres.length) { hashKey = primaryKeys[0]; query = model.query(options.where[hashKey]); -// } - + sails.log.verbose('using PK ' + hashKey) + } + else if (indexQuery.length == wheres.length) { + hashKey = wheres[0]; + query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); + sails.log.verbose('using index ' + wheres[0] + adapter.indexPrefix) } else { // scan mode From 9f763394d3070eb551657226d0080b5746aa9cb5 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 10 Dec 2014 21:31:29 +0100 Subject: [PATCH 21/60] Fixing primary key and index search --- index.js | 114 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/index.js b/index.js index 4193eaf..544ae9f 100644 --- a/index.js +++ b/index.js @@ -176,7 +176,7 @@ module.exports = (function () { identity: 'user' } */ - var primaryKeys = require("lodash").where(collection.definition, {primaryKey: true}); + var primaryKeys = _.where(collection.definition, {primaryKey: true}); //console.log("primaryKeys", primaryKeys); return Vogels.define(collectionName, function (schema) { @@ -199,14 +199,14 @@ module.exports = (function () { } // set primary key primaryKeys = adapter._getPrimaryKeys(collectionName); - primaryKeys = require("lodash").difference(primaryKeys, ["id"]); // ignore "id" + primaryKeys = _.difference(primaryKeys, ["id"]); // ignore "id" // console.log("collection.definition", collection.definition); if (primaryKeys.length < 1) schema.UUID(adapter.keyId, {hashKey: true}); else { - if (!require("lodash").isUndefined(primaryKeys[0])) { + if (!_.isUndefined(primaryKeys[0])) { adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); - if (!require("lodash").isUndefined(primaryKeys[1])) { + if (!_.isUndefined(primaryKeys[1])) { adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); } } @@ -221,7 +221,7 @@ module.exports = (function () { schema.Date('updatedAt', {default: Date.now}); }); }, _getPrimaryKeys: function (collectionName) { - var lodash = require("lodash"); + var lodash = _; var collection = _modelReferences[collectionName]; var maps = lodash.mapValues(collection.definition, "primaryKey"); @@ -232,7 +232,7 @@ module.exports = (function () { var primaryKeys = lodash.keys(list); return primaryKeys; }, _keys: function (collectionName) { - var lodash = require("lodash"); + var lodash = _; var collection = _modelReferences[collectionName]; var list = lodash.pick(collection.definition, function (value, key) { @@ -240,7 +240,7 @@ module.exports = (function () { }); return lodash.keys(list); }, _indexes: function (collectionName) { - var lodash = require("lodash"); + var lodash = _; var collection = _modelReferences[collectionName]; var list = lodash.pick(collection.definition, function (value, key) { @@ -453,70 +453,74 @@ module.exports = (function () { // If no matches were found, this will be an empty array. if ('where' in options && _.isObject(options.where)) { - var primaryKeys = adapter._getPrimaryKeys(collectionName); - var modelIndexes = adapter._indexes(collectionName); + var query = null, + primaryKeys = adapter._getPrimaryKeys(collectionName), + modelIndexes = adapter._indexes(collectionName), + modelKeys = adapter._keys(collectionName); // get current condition - var wheres = require("lodash").keys(options.where); + var wheres = _.keys(options.where); // compare both of keys - var primaryQuery = require("lodash").intersection(primaryKeys, wheres); - var indexQuery = require("lodash").intersection(modelIndexes, wheres); + var primaryQuery = _.intersection(primaryKeys, wheres); + var indexQuery = _.intersection(modelIndexes, wheres); if (primaryQuery.length == wheres.length) { - hashKey = primaryKeys[0]; + var hashKey = primaryKeys[0]; query = model.query(options.where[hashKey]); sails.log.verbose('using PK ' + hashKey) + options.where = _.without(options.where, hashKey); } - else if (indexQuery.length == wheres.length) { - hashKey = wheres[0]; + else if (indexQuery.length > 0) { + var hashKey = indexQuery[0]; query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); - sails.log.verbose('using index ' + wheres[0] + adapter.indexPrefix) + sails.log.verbose('using index ' + wheres[0] + adapter.indexPrefix); + delete options.where[hashKey]; } - else { - // scan mode + + // scan mode + if (!query) { query = model.scan(); - var modelKeys = adapter._keys(collectionName); + sails.log.verbose('using scan() '); + } - for (var key in options['where']) { - if (key == 'startKey') { - try { - query.startKey(JSON.parse(options['where'][key])); - } - catch (e) { - return cb("Wrong start key format :" + e.message); - } - continue; + sails.log(options.where); + for (var key in options.where) { + if (key == 'startKey') { + try { + query.startKey(JSON.parse(options.where[key])); } - if (modelKeys.indexOf(key) === -1) { - return cb("Wrong attribute given : " + key); + catch (e) { + return cb("Wrong start key format :" + e.message); } - var filter = _.keys(options['where'][key])[0]; - if (filter in filters) { - try { - query.where(key)[filter](filters[filter] ? options['where'][key][filter] : null); - } - catch (e) { - return cb(e.message); - } + continue; + } + if (modelKeys.indexOf(key) === -1) { + return cb("Wrong attribute given : " + key); + } + var filter = _.keys(options.where[key])[0]; + if (filter in filters) { + try { + query.where(key)[filter](filters[filter] ? options.where[key][filter] : null); } - else { - try { - if (_.isString(options['where'][key]) || _.isNumber(options['where'][key])) { - query.where(key).equals(options['where'][key]); - } - else if (_.isArray(options['where'][key])) { - query.where(key).in(options['where'][key]); - } - else { - return cb("Wrong value given : " + options['where'][key]); - } + catch (e) { + return cb(e.message); + } + } + else { + try { + if (_.isString(options.where[key]) || _.isNumber(options.where[key])) { + query.where(key).equals(options.where[key]); continue; } - catch (e) { - return cb(e.message); + else if (_.isArray(options.where[key])) { + query.where(key).in(options.where[key]); + continue; } - return cb("Wrong filter given :" + filter); } + catch (e) { + return cb(e.message); + } + return cb("Wrong filter given :" + filter); } } } @@ -638,7 +642,7 @@ module.exports = (function () { // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - var updateValues = require("lodash").assign(options.where, values); + var updateValues = _.assign(options.where, values); //console.log(updateValues); var current = Model.update(updateValues, function (err, res) { if (err) { @@ -805,7 +809,7 @@ module.exports = (function () { // set columns // console.log("name:", name); // console.log("attr:", attr); - var type = (require("lodash").isString(attr)) ? attr : attr.type; + var type = (_.isString(attr)) ? attr : attr.type; switch (type) { case "date": @@ -881,7 +885,7 @@ module.exports = (function () { for (var key in definition) { var type = definition[key].type; - if (require("lodash").has(values, key)) { + if (_.has(values, key)) { switch (type) { case "json": if (!encode) values[key] = JSON.parse(values[key]); From 9d2686ce15d62b37245168cc1323506a3b1913d0 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 12 Dec 2014 01:53:18 +0100 Subject: [PATCH 22/60] Avoiding querying PK by array --- index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 544ae9f..a865562 100644 --- a/index.js +++ b/index.js @@ -466,9 +466,11 @@ module.exports = (function () { if (primaryQuery.length == wheres.length) { var hashKey = primaryKeys[0]; - query = model.query(options.where[hashKey]); - sails.log.verbose('using PK ' + hashKey) - options.where = _.without(options.where, hashKey); + if (!_.isArray(options.where[hashKey])) { + query = model.query(options.where[hashKey]); + sails.log.verbose('using PK ' + hashKey) + options.where = _.without(options.where, hashKey); + } } else if (indexQuery.length > 0) { var hashKey = indexQuery[0]; From de1d198f03ea5f01a25355e5d62056aee30da523 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 12 Dec 2014 11:22:25 +0100 Subject: [PATCH 23/60] Fix readme --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index a18141b..4e6b6c8 100755 --- a/README.md +++ b/README.md @@ -52,6 +52,35 @@ module.exports.adapters = { }; ``` +## Find +Support for where is added as following: +``` + ?where={"name":{"null":true}} + ?where={"name":{"notNull":true}} + ?where={"name":{"equals":"firstName lastName"}} + ?where={"name":{"lte":"firstName lastName"}} + ?where={"name":{"lt":"firstName lastName"}} + ?where={"name":{"gte":"firstName lastName"}} + ?where={"name":{"gt":"firstName lastName"}} + ?where={"name":{"contains":"firstName lastName"}} + ?where={"name":{"contains":"firstName lastName"}} + ?where={"name":{"beginsWith":"firstName"}} + ?where={"name":{"in":["firstName lastName", "another name"]}} + ?where={"name":{"between":["firstName, "lastName""]}} +``` + + +## Pagination +Support for Pagination is added as following: +1. First add a limit to current request +``` + /user?limit=2 +``` +2. Then get the last primaryKey value and send it as startKey in the next request +``` + /user?limit=2&startKey={"PrimaryKey": "2"} +``` + ## Testing Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. From f8edc0c0a910bd32bbfec6fb85bc4023ed3881f3 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Fri, 12 Dec 2014 12:54:55 +0100 Subject: [PATCH 24/60] Adding not equal comparison operator --- README.md | 1 + index.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e6b6c8..4951e01 100755 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Support for where is added as following: ?where={"name":{"null":true}} ?where={"name":{"notNull":true}} ?where={"name":{"equals":"firstName lastName"}} + ?where={"name":{"ne":"firstName lastName"}} ?where={"name":{"lte":"firstName lastName"}} ?where={"name":{"lt":"firstName lastName"}} ?where={"name":{"gte":"firstName lastName"}} diff --git a/index.js b/index.js index a865562..8353564 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,8 @@ var filters = { notNull: false, //?where={"name":{"equals":"firstName lastName"}} equals: true, + //?where={"name":{"ne":"firstName lastName"}} + ne: true, //?where={"name":{"lte":"firstName lastName"}} lte: true, //?where={"name":{"lt":"firstName lastName"}} @@ -485,7 +487,6 @@ module.exports = (function () { sails.log.verbose('using scan() '); } - sails.log(options.where); for (var key in options.where) { if (key == 'startKey') { try { From 74c6220c7e4f73a3a1c72da3a78f8dfff6972a6a Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Sat, 13 Dec 2014 20:52:01 +0100 Subject: [PATCH 25/60] - Fixing error on populate - Fixing multi query using PK or index and switching to scan --- index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 8353564..54a758a 100644 --- a/index.js +++ b/index.js @@ -454,7 +454,7 @@ module.exports = (function () { // You should end up w/ an array of objects as a result. // If no matches were found, this will be an empty array. - if ('where' in options && _.isObject(options.where)) { + if (options && 'where' in options && _.isObject(options.where)) { var query = null, primaryKeys = adapter._getPrimaryKeys(collectionName), modelIndexes = adapter._indexes(collectionName), @@ -466,7 +466,7 @@ module.exports = (function () { var primaryQuery = _.intersection(primaryKeys, wheres); var indexQuery = _.intersection(modelIndexes, wheres); - if (primaryQuery.length == wheres.length) { + if (primaryQuery.length > 0 && wheres.length < 2) { var hashKey = primaryKeys[0]; if (!_.isArray(options.where[hashKey])) { query = model.query(options.where[hashKey]); @@ -474,7 +474,7 @@ module.exports = (function () { options.where = _.without(options.where, hashKey); } } - else if (indexQuery.length > 0) { + else if (indexQuery.length > 0 && wheres.length < 2) { var hashKey = indexQuery[0]; query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); sails.log.verbose('using index ' + wheres[0] + adapter.indexPrefix); @@ -552,6 +552,9 @@ module.exports = (function () { if (!query) { query = model.scan(); } + if (!options) { + return query; + } if ('sort' in options) { //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward var sort = _.keys(options.sort)[0]; From a4cdf07e285c59ceeb2f5129c539863fc26eb3ba Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Mon, 15 Dec 2014 17:17:47 +0100 Subject: [PATCH 26/60] clean up --- index.js | 66 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index 54a758a..eb2463a 100644 --- a/index.js +++ b/index.js @@ -307,10 +307,10 @@ module.exports = (function () { * @return {[type]} [description] */ define: function (connection, collectionName, definition, cb) { -//console.info("adaptor::define"); -//console.info("::collectionName", collectionName); -//console.info("::definition", definition); -//console.info("::model", adapter._getModel(collectionName)); +//sails.log.silly("adaptor::define"); +//sails.log.silly("::collectionName", collectionName); +//sails.log.silly("::definition", definition); +//sails.log.silly("::model", adapter._getModel(collectionName)); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; @@ -323,7 +323,7 @@ module.exports = (function () { collectionName: {readCapacity: 1, writeCapacity: 1} }, function (err) { if (err) { - console.warn('Error creating tables', err); + sails.log.error('Error creating tables', err); cb(err); } else { @@ -349,7 +349,7 @@ module.exports = (function () { * @return {[type]} [description] */ describe: function (connection, collectionName, cb) { -//console.info("adaptor::describe"); +//sails.log.silly("adaptor::describe"); //console.log("::connection",connection); //console.log("::collection",collectionName); @@ -378,7 +378,7 @@ module.exports = (function () { cb(); } else { - console.warn('Error describe tables' + __filename, err); + sails.log.error('Error describe tables' + __filename, err); cb(err); } // console.log(err); // an error occurred @@ -403,10 +403,10 @@ module.exports = (function () { * @return {[type]} [description] */ drop: function (connection, collectionName, relations, cb) { -//console.info("adaptor::drop", collectionName); +//sails.log.silly("adaptor::drop", collectionName); // If you need to access your private data for this collection: var collection = _modelReferences[collectionName]; -//console.warn('drop: not supported') +//sails.log.error('drop: not supported') // Drop a "table" or "collection" schema from the data store cb(); }, @@ -437,12 +437,13 @@ module.exports = (function () { * @return {[type]} [description] */ find: function (connection, collectionName, options, cb) { - console.info("adaptor::find", collectionName); - console.info("::option", options); + sails.log.silly("adaptor::find", collectionName); + sails.log.silly("::option", options); - var collection = _modelReferences[collectionName]; - var model = adapter._getModel(collectionName); - var query = null; + var collection = _modelReferences[collectionName], + model = adapter._getModel(collectionName), + query = null, + hashKey = null; // Options object is normalized for you: // // options.where @@ -455,11 +456,12 @@ module.exports = (function () { // If no matches were found, this will be an empty array. if (options && 'where' in options && _.isObject(options.where)) { - var query = null, - primaryKeys = adapter._getPrimaryKeys(collectionName), + var primaryKeys = adapter._getPrimaryKeys(collectionName), modelIndexes = adapter._indexes(collectionName), modelKeys = adapter._keys(collectionName); + query = null; + // get current condition var wheres = _.keys(options.where); // compare both of keys @@ -467,24 +469,24 @@ module.exports = (function () { var indexQuery = _.intersection(modelIndexes, wheres); if (primaryQuery.length > 0 && wheres.length < 2) { - var hashKey = primaryKeys[0]; + hashKey = primaryKeys[0]; if (!_.isArray(options.where[hashKey])) { query = model.query(options.where[hashKey]); - sails.log.verbose('using PK ' + hashKey) + sails.log.silly('using PK ' + hashKey) options.where = _.without(options.where, hashKey); } } else if (indexQuery.length > 0 && wheres.length < 2) { - var hashKey = indexQuery[0]; + hashKey = indexQuery[0]; query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); - sails.log.verbose('using index ' + wheres[0] + adapter.indexPrefix); + sails.log.silly('using index ' + wheres[0] + adapter.indexPrefix); delete options.where[hashKey]; } // scan mode if (!query) { query = model.scan(); - sails.log.verbose('using scan() '); + sails.log.silly('using scan() '); } for (var key in options.where) { @@ -535,7 +537,7 @@ module.exports = (function () { cb(null, adapter._resultFormat(res)); } else { - console.warn('Error exec query:' + __filename, err); + sails.log.error('Error exec query:' + __filename, err); cb(err); } }); @@ -585,8 +587,8 @@ module.exports = (function () { * @param {Function} cb [description] * @return {[type]} [description] */, create: function (connection, collectionName, values, cb) { -//console.info("adaptor::create", collectionName); -//console.info("values", values); +//sails.log.silly("adaptor::create", collectionName); +//sails.log.silly("values", values); //console.log("collection", _modelReferences[collectionName]); var Model = adapter._getModel(collectionName); @@ -598,7 +600,7 @@ module.exports = (function () { // Create a single new model (specified by `values`) var current = Model.create(values, function (err, res) { if (err) { - console.warn(__filename + ", create error:", err); + sails.log.error(__filename + ", create error:", err); cb(err); } else { @@ -625,9 +627,9 @@ module.exports = (function () { * @return {[type]} [description] */ update: function (connection, collectionName, options, values, cb) { -//console.info("adaptor::update", collectionName); -//console.info("::options", options); -//console.info("::values", values); +//sails.log.silly("adaptor::update", collectionName); +//sails.log.silly("::options", options); +//sails.log.silly("::values", values); var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: @@ -652,7 +654,7 @@ module.exports = (function () { //console.log(updateValues); var current = Model.update(updateValues, function (err, res) { if (err) { - console.warn('Error update data' + __filename, err); + sails.log.error('Error update data' + __filename, err); cb(err); } else { @@ -677,8 +679,8 @@ module.exports = (function () { * @return {[type]} [description] */ destroy: function (connection, collectionName, options, cb) { -//console.info("adaptor::destory", collectionName); -//console.info("options", options); +//sails.log.silly("adaptor::destory", collectionName); +//sails.log.silly("options", options); var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: @@ -698,7 +700,7 @@ module.exports = (function () { var values = options.where; var current = Model.destroy(values, function (err, res) { if (err) { - console.warn('Error destory data' + __filename, err); + sails.log.error('Error destory data' + __filename, err); cb(err); } else { From a1014c7cbfdcf45628a6aa3398e33fad1ba2e267 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 13 Jan 2015 16:48:11 +0100 Subject: [PATCH 27/60] fixing vogel new api issue --- index.js | 8 ++++---- package.json | 39 ++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index eb2463a..f9fe9f0 100644 --- a/index.js +++ b/index.js @@ -71,15 +71,15 @@ module.exports = (function () { // // Keep in mind that models can be configured to use different databases // within the same app, at the same time. - // + // // i.e. if you're writing a MariaDB adapter, you should be aware that one // model might be configured as `host="localhost"` and another might be using - // `host="foo.com"` at the same time. Same thing goes for user, database, + // `host="foo.com"` at the same time. Same thing goes for user, database, // password, or any other config. // // You don't have to support this feature right off the bat in your // adapter, but it ought to get done eventually. - // + // // Sounds annoying to deal with... // ...but it's not bad. In each method, acquire a connection using the config // for the current model (looking it up from `_modelReferences`), establish @@ -87,7 +87,7 @@ module.exports = (function () { // Finally, as an optimization, you might use a db pool for each distinct // connection configuration, partioning pools for each separate configuration // for your adapter (i.e. worst case scenario is a pool for each model, best case - // scenario is one single single pool.) For many databases, any change to + // scenario is one single single pool.) For many databases, any change to // host OR database OR user OR password = separate pool. var _dbPools = {}; diff --git a/package.json b/package.json index ad7310b..b99ba80 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.11.1", + "version": "0.11.4", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { @@ -20,20 +20,10 @@ "sailsjs", "sails.js" ], - "author": [ - { - "name": "dohzoh", - "email": "dohzoh@outlook.com" - }, - { - "name": "gadelkareem", - "email": "gadelkareem@gmail.com" - } - ], + "author": "gadelkareem", "license": "MIT", - "readmeFilename": "README.md", "dependencies": { - "vogels": "*", + "vogels": "0.12.0", "lodash": "", "async": "" }, @@ -50,16 +40,27 @@ "associations" ] }, - "readme": "# sails-dynamodb\n\nA [Waterline](https://github.com/balderdashy/waterline) adapter for DynamoDB. May be used in a [Sails](https://github.com/balderdashy/sails) app or anything using Waterline for the ORM.\n\n\n> _**Note:** This adapter support the Sails.js v0.10.x. check 0.9 branch if you use before v0.10\n\n## Install\n\nInstall is through NPM.\n\n```bash\n$ sails new project && cd project\n$ npm install git://github.com/gadelkareem/sails-dynamodb.git\n$ cp node_modules/sails-dynamodb/credentials.example.json ./credentials.json # & put your amazon keys\n```\nTodo: to npm package\n\n\n## Configuration\n\nThe following config options are available along with their default values:\n\nconfig/connection.js\n```javascript\nmodule.exports.adapters = {\n\n // If you leave the adapter config unspecified \n // in a model definition, 'default' will be used.\n localDiskDb: {\n adapter: 'sails-disk'\n },\n \n dynamoDb: {\n adapter: \"sails-dynamodb\",\n endPoint: \"http://localhost:8000\", // Optional: add for DynamoDB local\n },\n \n};\n```\n\nconfig/models.js\n```javascript\nmodule.exports.adapters = {\n\n // If you leave the adapter config unspecified \n // in a model definition, 'default' will be used.\n connection: 'dynamoDb'\n \n};\n```\n\n## Testing\n\nTest are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API.\n\nTo run tests:\n\n```bash\n$ npm test\n```\n\n\n## About Sails.js and Waterline\nhttp://sailsjs.org\n\nWaterline is a new kind of storage and retrieval engine for Sails.js. It provides a uniform API for accessing stuff from different kinds of databases, protocols, and 3rd party APIs. That means you write the same code to get users, whether they live in mySQL, LDAP, MongoDB, or Facebook.\n\n\n![image_squidhome@2x.png](http://i.imgur.com/RIvu9.png) \n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 dozo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n", "bugs": { "url": "https://github.com/gadelkareem/sails-dynamodb/issues" }, - "_id": "sails-dynamodb@0.11.0", "dist": { - "shasum": "2dc7fe333c4f0a95f92bff00f7e89f4c1a773220" + "shasum": "93002d46304fd8b42fbe1c39e9ab84a89f14e27f", + "tarball": "http://registry.npmjs.org/sails-dynamodb/-/sails-dynamodb-0.11.4.tgz" }, - "_resolved": "git://github.com/gadelkareem/sails-dynamodb.git#da8474615e68a043115f9e943acca5e28dd7a0ab", - "_from": "sails-dynamodb@*", + "_resolved": "https://registry.npmjs.org/sails-dynamodb/-/sails-dynamodb-0.11.4.tgz", + "_from": "sails-dynamodb@>=0.11.1 <0.12.0", "homepage": "https://github.com/gadelkareem/sails-dynamodb", - "_shasum": "fbbe01ec1482df7edc9e9eec01ba60414a9007a4" + "_shasum": "93002d46304fd8b42fbe1c39e9ab84a89f14e27f", + "_npmVersion": "2.1.12", + "_nodeVersion": "0.10.33", + "_npmUser": { + "name": "gadelkareem", + "email": "gadelkareem@gmail.com" + }, + "maintainers": [ + "gadelkareem " + ], + "directories": { + "test": "test" + } } From 8d36bfa71d28b86ea06d106bc45bb8abc7ac89e3 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Tue, 20 Jan 2015 23:10:47 -0500 Subject: [PATCH 28/60] remove references to sails for use with waterline standalone. --- index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index f9fe9f0..7594d02 100644 --- a/index.js +++ b/index.js @@ -323,7 +323,7 @@ module.exports = (function () { collectionName: {readCapacity: 1, writeCapacity: 1} }, function (err) { if (err) { - sails.log.error('Error creating tables', err); + //sails.log.error('Error creating tables', err); cb(err); } else { @@ -378,7 +378,7 @@ module.exports = (function () { cb(); } else { - sails.log.error('Error describe tables' + __filename, err); + //sails.log.error('Error describe tables' + __filename, err); cb(err); } // console.log(err); // an error occurred @@ -437,8 +437,8 @@ module.exports = (function () { * @return {[type]} [description] */ find: function (connection, collectionName, options, cb) { - sails.log.silly("adaptor::find", collectionName); - sails.log.silly("::option", options); + //sails.log.silly("adaptor::find", collectionName); + //sails.log.silly("::option", options); var collection = _modelReferences[collectionName], model = adapter._getModel(collectionName), @@ -472,14 +472,14 @@ module.exports = (function () { hashKey = primaryKeys[0]; if (!_.isArray(options.where[hashKey])) { query = model.query(options.where[hashKey]); - sails.log.silly('using PK ' + hashKey) + //sails.log.silly('using PK ' + hashKey) options.where = _.without(options.where, hashKey); } } else if (indexQuery.length > 0 && wheres.length < 2) { hashKey = indexQuery[0]; query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); - sails.log.silly('using index ' + wheres[0] + adapter.indexPrefix); + //sails.log.silly('using index ' + wheres[0] + adapter.indexPrefix); delete options.where[hashKey]; } @@ -537,7 +537,7 @@ module.exports = (function () { cb(null, adapter._resultFormat(res)); } else { - sails.log.error('Error exec query:' + __filename, err); + //sails.log.error('Error exec query:' + __filename, err); cb(err); } }); @@ -654,7 +654,7 @@ module.exports = (function () { //console.log(updateValues); var current = Model.update(updateValues, function (err, res) { if (err) { - sails.log.error('Error update data' + __filename, err); + //sails.log.error('Error update data' + __filename, err); cb(err); } else { @@ -700,7 +700,7 @@ module.exports = (function () { var values = options.where; var current = Model.destroy(values, function (err, res) { if (err) { - sails.log.error('Error destory data' + __filename, err); + //sails.log.error('Error destory data' + __filename, err); cb(err); } else { From 40f7c787b335f06d819a7c787c7b10317fe9722f Mon Sep 17 00:00:00 2001 From: devin ivy Date: Wed, 21 Jan 2015 10:51:35 -0500 Subject: [PATCH 29/60] missed one `sails` --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 7594d02..ddfb71f 100644 --- a/index.js +++ b/index.js @@ -486,7 +486,7 @@ module.exports = (function () { // scan mode if (!query) { query = model.scan(); - sails.log.silly('using scan() '); + //sails.log.silly('using scan() '); } for (var key in options.where) { From 61b7280a369df2547eb1d095109b8034212cf311 Mon Sep 17 00:00:00 2001 From: devinivy Date: Wed, 21 Jan 2015 13:39:06 -0500 Subject: [PATCH 30/60] filter on non-range parameters when not scanning. --- index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ddfb71f..a3f023e 100644 --- a/index.js +++ b/index.js @@ -484,12 +484,16 @@ module.exports = (function () { } // scan mode + var scanning = false; if (!query) { + scanning = true; query = model.scan(); //sails.log.silly('using scan() '); } for (var key in options.where) { + + // Using startKey? if (key == 'startKey') { try { query.startKey(JSON.parse(options.where[key])); @@ -499,13 +503,18 @@ module.exports = (function () { } continue; } + + // Okay, in the case that we're not scanning, + // we need to use where for the range and filter for other stuff + var queryOp = (primaryKeys[1] == key || scanning) ? 'where' : 'filter'; + if (modelKeys.indexOf(key) === -1) { return cb("Wrong attribute given : " + key); } var filter = _.keys(options.where[key])[0]; if (filter in filters) { try { - query.where(key)[filter](filters[filter] ? options.where[key][filter] : null); + query[queryOp](key)[filter](filters[filter] ? options.where[key][filter] : null); } catch (e) { return cb(e.message); @@ -514,11 +523,11 @@ module.exports = (function () { else { try { if (_.isString(options.where[key]) || _.isNumber(options.where[key])) { - query.where(key).equals(options.where[key]); + query[queryOp](key).equals(options.where[key]); continue; } else if (_.isArray(options.where[key])) { - query.where(key).in(options.where[key]); + query[queryOp](key).in(options.where[key]); continue; } } @@ -532,7 +541,7 @@ module.exports = (function () { query = adapter._searchCondition(query, options, model); query.exec(function (err, res) { if (!err) { - console.log("success", adapter._resultFormat(res)); + //console.log("success", adapter._resultFormat(res)); adapter._valueDecode(collection.definition, res.attrs); cb(null, adapter._resultFormat(res)); } @@ -600,7 +609,7 @@ module.exports = (function () { // Create a single new model (specified by `values`) var current = Model.create(values, function (err, res) { if (err) { - sails.log.error(__filename + ", create error:", err); + //sails.log.error(__filename + ", create error:", err); cb(err); } else { From e13f385d79e57ec9a6a5841b32731f20050d3c3d Mon Sep 17 00:00:00 2001 From: devinivy Date: Thu, 22 Jan 2015 15:34:56 -0500 Subject: [PATCH 31/60] refactor to cache vogels models and support various indices. --- index.js | 614 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 427 insertions(+), 187 deletions(-) diff --git a/index.js b/index.js index a3f023e..9e1402f 100644 --- a/index.js +++ b/index.js @@ -61,7 +61,8 @@ module.exports = (function () { // You'll want to maintain a reference to each collection // (aka model) that gets registered with this adapter. - var _modelReferences = {}; + var _collectionReferences = {}; + var _vogelsReferences = {}; var _definedTables = {}; @@ -93,18 +94,24 @@ module.exports = (function () { var adapter = { - identity: 'sails-dynamodb', keyId: "id", indexPrefix: "-Index" + identity: 'sails-dynamodb', + keyId: "id", + indexPrefix: "-Index", // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if your data store is not SQL/schemaful. - , syncable: true, + + // This doesn't make sense for dynamo, where the schema parts are locked-down during table creation. + syncable: false, // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - accessKeyId: null, secretAccessKey: null, region: 'us-west-1' + accessKeyId: null, + secretAccessKey: null, + region: 'us-west-1', // For example: // port: 3306, // host: 'localhost', @@ -124,133 +131,182 @@ module.exports = (function () { // drop => Drop schema and data, then recreate it // alter => Drop/add columns as necessary. // safe => Don't change anything (good for production DBs) - , migrate: 'alter' -// , schema: false - }, _getModel: function (collectionName) { - - var collection = _modelReferences[collectionName]; -//console.log("currenct collection.definition", collection.definition); -//console.log(collection); - - /* - currenct collection - { - keyId: 'id', - indexPrefix: '-Index', - syncable: true, - defaults: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - _getModel: [Function], - _getPrimaryKeys: [Function], - registerCollection: [Function], - teardown: [Function], - define: [Function], - describe: [Function], - drop: [Function], - find: [Function], - _searchCondition: [Function], - create: [Function], - update: [Function], - destroy: [Function], - _setColumnType: [Function], - _resultFormat: [Function], - config: - { accessKeyId: null, - secretAccessKey: null, - region: 'us-west-1', - migrate: 'alter', - adapter: 'sails-dynamodb' }, - definition: - { user_id: { primaryKey: true, unique: true }, - name: { type: 'string', index: true }, - password: { type: 'string', index: true }, - email: { type: 'string', index: true }, - activated: { type: 'boolean', defaultsTo: false }, - activationToken: { type: 'string' }, - isSocial: { type: 'boolean' }, - socialActivated: { type: 'boolean' }, - createdAt: { type: 'datetime', default: 'NOW' }, - updatedAt: { type: 'datetime', default: 'NOW' } }, - identity: 'user' } - */ - - var primaryKeys = _.where(collection.definition, {primaryKey: true}); -//console.log("primaryKeys", primaryKeys); - - return Vogels.define(collectionName, function (schema) { -//console.log("_getModel", collectionName); + + //Indices currently never change in dynamo + migrate: 'safe', +// schema: false + }, + + _createModel: function (collectionName) { + + var collection = _collectionReferences[collectionName]; + + // Attrs with primaryKeys + var primaryKeys = _.filter(collection.definition, function(attr) { return !!attr.primaryKey } ); + var primaryKeyNames =_.keys(primaryKeys); + + if (primaryKeyNames.length < 1 || primaryKeyNames.length > 2) { + throw new Error('Must have one or two primary key attributes.'); + } + + // One primary key, then it's a hash + if (primaryKeyNames.length == 1) { + collection.definition[primaryKeyNames[0]].primaryKey = 'hash'; + } + + var vogelsModel = Vogels.define(collectionName, function (schema) { + var columns = collection.definition; - var primaryKeys = [] - var indexes = []; + + var indices = {}; + // set columns for (var columnName in columns) { + var attributes = columns[columnName]; - -// console.log(columnName+":", attributes); + if (typeof attributes !== "function") { + + // Add column to Vogel model adapter._setColumnType(schema, columnName, attributes); - // search primarykey -// if("primaryKey" in attributes)primaryKeys.push( columnName ); - // search index - if ("index" in attributes) indexes.push(columnName); - } - } - // set primary key - primaryKeys = adapter._getPrimaryKeys(collectionName); - primaryKeys = _.difference(primaryKeys, ["id"]); // ignore "id" -// console.log("collection.definition", collection.definition); - if (primaryKeys.length < 1) - schema.UUID(adapter.keyId, {hashKey: true}); - else { - if (!_.isUndefined(primaryKeys[0])) { - adapter._setColumnType(schema, primaryKeys[0], columns[primaryKeys[0]], {hashKey: true}); - if (!_.isUndefined(primaryKeys[1])) { - adapter._setColumnType(schema, primaryKeys[1], columns[primaryKeys[1]], {rangeKey: true}); + + // Save set indices + var index; + var indexParts; + var indexName; + var indexType; + + if ("index" in attributes && attributes.index !== 'secondary') { + + index = attributes.index; + + indexParts = adapter._parseIndex(index); + indexName = indexParts[0]; + indexType = indexParts[1]; + + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } + + indices[indexName][indexType] = columnName; + } + } + } -// schema.String( primaryKey, {hashKey: true}); - for (var i = 0; i < indexes.length; i++) { - var key = indexes[i]; - schema.globalIndex(key + adapter.indexPrefix, {hashKey: key}); + + // Set global secondary indices + for (indexName in indices) { + schema.globalIndex(indexName, indices[indexName]); } - - schema.Date('createdAt', {default: Date.now}); - schema.Date('updatedAt', {default: Date.now}); + }); - }, _getPrimaryKeys: function (collectionName) { + + // Cache Vogels model + _vogelsReferences[collectionName] = vogelsModel; + + return vogelsModel; + + }, + + _getModel: function(collectionName) { + return _vogelsReferences[collectionName] || this._createModel(collectionName); + }, + + _getPrimaryKeys: function (collectionName) { + var lodash = _; - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; var maps = lodash.mapValues(collection.definition, "primaryKey"); // console.log(results); var list = lodash.pick(maps, function (value, key) { return typeof value !== "undefined"; }); + var primaryKeys = lodash.keys(list); + return primaryKeys; - }, _keys: function (collectionName) { + }, + + _keys: function (collectionName) { var lodash = _; - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; var list = lodash.pick(collection.definition, function (value, key) { return (typeof value !== "undefined"); }); return lodash.keys(list); - }, _indexes: function (collectionName) { + }, + + _indexes: function (collectionName) { var lodash = _; - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; var list = lodash.pick(collection.definition, function (value, key) { return ("index" in value && value.index === true) }); return lodash.keys(list); - } + }, + + // index: 'secondary' + _getLocalIndices: function(collectionName) { + + }, + + // index: 'indexName-fieldType' (i.e. 'users-hash' and 'users-range') + _getGlobalIndices: function(collectionName) { + + }, + _parseIndex: function(index) { + + // Two helpers + var stringEndsWith = function(str, needle) { + + if (str.indexOf(needle) !== -1 && + str.indexOf(needle) === str.length-needle.length) { + return true; + } else { + return false; + } + + } + + var removeSuffixFromString = function(str, suffix) { + + if (stringEndsWith(str, suffix)) { + return str.slice(0, str.length-suffix.length); + } else { + return str; + } + + } + + var indexName; + var indexType; + + if (index === true) { + + indexName = columnName; + indexType = 'hashKey'; + } else if (strEndsWith(index, '-hash')) { + + indexName = removeSuffixFromString(index, '-hash'); + indexType = 'hashKey'; + } else if (strEndsWith(index, '-range')) { + + indexName = removeSuffixFromString(index, '-range'); + indexType = 'rangeKey'; + } else { + throw new Error('Index must be a hash or range.'); + } + + return [indexName, indexType]; + + }, + /** * * This method runs when a model is initially registered @@ -259,30 +315,36 @@ module.exports = (function () { * @param string collection [description] * @param {Function} cb [description] * @return {[type]} [description] - */, registerConnection: function (connection, collections, cb) { -//var sails = require("sails"); -//console.log("load registerConnection"); -//console.log("::connection",connection); -//console.log("::collections",collections); + */ + + registerConnection: function (connection, collections, cb) { + if (!connection.identity) return cb(Errors.IdentityMissing); if (connections[connection.identity]) return cb(Errors.IdentityDuplicate); - var error = null; try { + AWS.config.update({ "accessKeyId": connection.accessKeyId, "secretAccessKey": connection.secretAccessKey, "region": connection.region }); - } - catch (e) { + } catch (e) { + e.message = e.message + ". Please make sure you added the right keys to your adapter config"; - error = e; + return cb(e) } - // Keep a reference to this collection - _modelReferences = collections; - cb(error); - } + + // Keep a reference to these collections + _collectionReferences = collections; + + // Create Vogels models for the collections + _.forOwn(collections, function(coll, collName) { + adapter._createModel(collName); + }); + + cb(); + }, /** * Fired when a model is unregistered, typically when the server @@ -291,7 +353,8 @@ module.exports = (function () { * * @param {Function} cb [description] * @return {[type]} [description] - */, teardown: function (connection, cb) { + */ + teardown: function (connection, cb) { cb(); }, @@ -313,7 +376,7 @@ module.exports = (function () { //sails.log.silly("::model", adapter._getModel(collectionName)); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; if (!_definedTables[collectionName]) { var table = adapter._getModel(collectionName); @@ -354,7 +417,7 @@ module.exports = (function () { //console.log("::collection",collectionName); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; //console.log("::collection.definition",collection.definition); // Respond with the schema (attributes) for a collection or table in the data store @@ -405,7 +468,7 @@ module.exports = (function () { drop: function (connection, collectionName, relations, cb) { //sails.log.silly("adaptor::drop", collectionName); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; //sails.log.error('drop: not supported') // Drop a "table" or "collection" schema from the data store cb(); @@ -440,10 +503,11 @@ module.exports = (function () { //sails.log.silly("adaptor::find", collectionName); //sails.log.silly("::option", options); - var collection = _modelReferences[collectionName], + var collection = _collectionReferences[collectionName], model = adapter._getModel(collectionName), query = null, hashKey = null; + // Options object is normalized for you: // // options.where @@ -456,6 +520,7 @@ module.exports = (function () { // If no matches were found, this will be an empty array. if (options && 'where' in options && _.isObject(options.where)) { + var primaryKeys = adapter._getPrimaryKeys(collectionName), modelIndexes = adapter._indexes(collectionName), modelKeys = adapter._keys(collectionName); @@ -464,81 +529,57 @@ module.exports = (function () { // get current condition var wheres = _.keys(options.where); - // compare both of keys - var primaryQuery = _.intersection(primaryKeys, wheres); - var indexQuery = _.intersection(modelIndexes, wheres); - - if (primaryQuery.length > 0 && wheres.length < 2) { - hashKey = primaryKeys[0]; - if (!_.isArray(options.where[hashKey])) { - query = model.query(options.where[hashKey]); - //sails.log.silly('using PK ' + hashKey) - options.where = _.without(options.where, hashKey); - } - } - else if (indexQuery.length > 0 && wheres.length < 2) { - hashKey = indexQuery[0]; - query = model.query(options.where[hashKey]).usingIndex(hashKey + adapter.indexPrefix); - //sails.log.silly('using index ' + wheres[0] + adapter.indexPrefix); - delete options.where[hashKey]; - } - - // scan mode + + var indexing = adapter._whichIndex(collectionName, wheres); + var hash = indexing.hash; + var range = indexing.range; + var indexName = indexing.index; + var scanning = false; - if (!query) { + if (indexing) { + + query = model.query(options.where[hash]) + delete options.where[hash]; + + if (indexName) { + query.usingIndex(indexName); + } + + if (range) { + adapter._applyQueryFilter(query, 'where', range, options.where[range]); + delete options.where[range]; + } + + } else { + scanning = true; query = model.scan(); - //sails.log.silly('using scan() '); } + var queryOp = scanning ? 'where' : 'filter'; + for (var key in options.where) { // Using startKey? if (key == 'startKey') { + try { + query.startKey(JSON.parse(options.where[key])); - } - catch (e) { + } catch (e) { + return cb("Wrong start key format :" + e.message); } - continue; - } - - // Okay, in the case that we're not scanning, - // we need to use where for the range and filter for other stuff - var queryOp = (primaryKeys[1] == key || scanning) ? 'where' : 'filter'; - - if (modelKeys.indexOf(key) === -1) { - return cb("Wrong attribute given : " + key); - } - var filter = _.keys(options.where[key])[0]; - if (filter in filters) { - try { - query[queryOp](key)[filter](filters[filter] ? options.where[key][filter] : null); - } - catch (e) { - return cb(e.message); - } - } - else { - try { - if (_.isString(options.where[key]) || _.isNumber(options.where[key])) { - query[queryOp](key).equals(options.where[key]); - continue; - } - else if (_.isArray(options.where[key])) { - query[queryOp](key).in(options.where[key]); - continue; - } - } - catch (e) { - return cb(e.message); - } - return cb("Wrong filter given :" + filter); + + } else { + adapter._applyQueryFilter(query, queryOp, key, options.where[key]); } + } } + query = adapter._searchCondition(query, options, model); + query.exec(function (err, res) { if (!err) { //console.log("success", adapter._resultFormat(res)); @@ -553,22 +594,204 @@ module.exports = (function () { // Respond with an error, or the results. // cb(null, []); - }/** + }, + + _applyQueryFilter: function(query, op, key, condition) { + + try { + + if (_.isString(condition) || _.isNumber(condition)) { + + query[queryOp](key).equals(condition); + + } else if (_.isArray(condition)) { + + query[queryOp](key).in(condition); + + } else if (_.isObject(condition)) { + + var filter = _.keys(condition)[0]; + + if (filter in filters) { + + query[op](key)[filter](filters[filter] ? condition[filter] : null); + + } else { + + return cb(new Error("Wrong filter given :" + filter)); + } + + } else { + return cb(new Error("Wrong filter given :" + filter)); + } + + } catch (e) { + + return cb(e.message); + } + + }, + + // Return {index: 'name', hash: 'field1', range:'field2'} + // Primary hash and range > primary hash and secondary range > global secondary hash and range + // > primary hash > global secondary hash > no index/primary + _whichIndex: function(collectionName, fields) { + + var columns = _collectionReferences[collectionName].definition; + + var primaryHash = false; + var primaryRange = false; + var secondaryRange = false; + var globalHash = false; + var globalRange = false; + + var globalIndexName; + + // holds all index info from fields + var indices = {}; + + // temps for loop + var fieldName; + var column; + var indexInfo; + var indexName; + var indexType; + for (var i = 0; i < fields.length; i++) { + + fieldName = fields[i]; + column = columns[fieldName]; + + // set primary hash + if (column.primaryKey && column.primaryKey === true || column.primaryKey === 'hash') { + primaryHash = fieldName; + continue; + } + + // set primary range + if (column.primaryKey && column.primaryKey === 'range') { + primaryRange = fieldName; + continue; + } + + // set secondary range + if (column.index && column.index === 'secondary') { + secondaryRange = fieldName; + continue; + } + + // build global secondary hash info + if (column.index && column.index !== 'secondary') { + + indexInfo = adapter._parseIndex(column.index); + indexName = indexInfo[0]; + indexType = indexInfo[1]; + + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } + + indices[indexName][indexType] = fieldName; + + continue; + } + + } + + // set global secondary hash info + var indicesHashed; + var indicesRanged; + + // pick out those with just a hash key + var indicesHashed = _.pick(indices, function(ind) { + return !!ind.hashKey && !ind.rangeKey; + }); + + // pick out those with a hash and a range key + var indicesRanged = _.pick(indices, function(ind) { + return !!ind.hashKey && !!ind.rangeKey; + }); + + // found a good ranged global secondary index? + if (!_.isEmpty(indicesRanged)) { + + globalIndexName = Object.keys(indicesRanged)[0]; + globalHash = indicesRanged[globalIndexName].hashKey; + globalRange = indicesRanged[globalIndexName].rangeKey; + + } else if (!_.isEmpty(indicesHashed)) { + + globalIndexName = Object.keys(indicesHashed)[0]; + globalHash = indicesHashed[globalIndexName].hashKey; + + } + + if (primaryHash && primaryRange) { + + return { + index: 'primary', + hash: primaryHash, + range: primaryRange + } + + } else if (primaryHash && secondaryRange) { + + return { + index: secondaryRange+'Index', // per Vogels + hash: primaryHash, + range: secondaryRange + } + + } else if (globalHash && globalRange) { + + return { + index: globalIndexName, + hash: globalHash, + range: globalRange + } + + } else if (primaryHash) { + + return { + index: 'primary', + hash: primaryHash + } + + } else if (globalHash) { + + return { + index: globalIndexName, + hash: globalHash + } + + } else { + + return false; + } + + }, + + /** * search condition * @param query * @param options * @returns {*} * @private - */, _searchCondition: function (query, options, model) { + */ + _searchCondition: function (query, options, model) { + if (!query) { query = model.scan(); } + if (!options) { return query; } + if ('sort' in options) { + //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward var sort = _.keys(options.sort)[0]; + if (sort == 1) { query.ascending(); } @@ -576,14 +799,17 @@ module.exports = (function () { query.descending(); } } + if ('limit' in options) { + query.limit(options.limit); - } - else { + } else { + query.loadAll(); } - return query - } + + return query; + }, @@ -595,7 +821,7 @@ module.exports = (function () { * @param {[type]} values [description] * @param {Function} cb [description] * @return {[type]} [description] - */, create: function (connection, collectionName, values, cb) { + */create: function (connection, collectionName, values, cb) { //sails.log.silly("adaptor::create", collectionName); //sails.log.silly("values", values); //console.log("collection", _modelReferences[collectionName]); @@ -603,7 +829,7 @@ module.exports = (function () { var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; adapter._valueEncode(collection.definition, values); // Create a single new model (specified by `values`) @@ -642,7 +868,7 @@ module.exports = (function () { var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; adapter._valueEncode(collection.definition, values); // id filter (bug?) @@ -693,7 +919,7 @@ module.exports = (function () { var Model = adapter._getModel(collectionName); // If you need to access your private data for this collection: - var collection = _modelReferences[collectionName]; + var collection = _collectionReferences[collectionName]; // 1. Filter, paginate, and sort records from the datastore. @@ -721,7 +947,7 @@ module.exports = (function () { } else cb(); - } + }, @@ -820,9 +1046,23 @@ module.exports = (function () { * @param name column name * @param attr columns detail * @private - */, _setColumnType: function (schema, name, attr, options) { + */ + _setColumnType: function (schema, name, attr, options) { + options = (typeof options !== 'undefined') ? options : {}; - + + // Set primary key options + if (attr.primaryKey === 'hash') { + + _.merge(options, {hashKey: true}); + } else if (attr.primaryKey === 'range') { + + _.merge(options, {rangeKey: true}); + } else if (attr.index === 'secondary') { + + _.merge(options, {secondaryIndex: true}); + } + // set columns // console.log("name:", name); // console.log("attr:", attr); From d3bd29ea94b4f433c5fdb36ef44a1cba3c173662 Mon Sep 17 00:00:00 2001 From: devinivy Date: Thu, 22 Jan 2015 18:28:39 -0500 Subject: [PATCH 32/60] fix typos from last commit. --- index.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 9e1402f..975055f 100644 --- a/index.js +++ b/index.js @@ -142,7 +142,7 @@ module.exports = (function () { var collection = _collectionReferences[collectionName]; // Attrs with primaryKeys - var primaryKeys = _.filter(collection.definition, function(attr) { return !!attr.primaryKey } ); + var primaryKeys = _.pick(collection.definition, function(attr) { return !!attr.primaryKey } ); var primaryKeyNames =_.keys(primaryKeys); if (primaryKeyNames.length < 1 || primaryKeyNames.length > 2) { @@ -291,11 +291,11 @@ module.exports = (function () { indexName = columnName; indexType = 'hashKey'; - } else if (strEndsWith(index, '-hash')) { + } else if (stringEndsWith(index, '-hash')) { indexName = removeSuffixFromString(index, '-hash'); indexType = 'hashKey'; - } else if (strEndsWith(index, '-range')) { + } else if (stringEndsWith(index, '-range')) { indexName = removeSuffixFromString(index, '-range'); indexType = 'rangeKey'; @@ -506,7 +506,7 @@ module.exports = (function () { var collection = _collectionReferences[collectionName], model = adapter._getModel(collectionName), query = null, - hashKey = null; + error; // Options object is normalized for you: // @@ -521,10 +521,6 @@ module.exports = (function () { if (options && 'where' in options && _.isObject(options.where)) { - var primaryKeys = adapter._getPrimaryKeys(collectionName), - modelIndexes = adapter._indexes(collectionName), - modelKeys = adapter._keys(collectionName); - query = null; // get current condition @@ -546,7 +542,10 @@ module.exports = (function () { } if (range) { - adapter._applyQueryFilter(query, 'where', range, options.where[range]); + + error = adapter._applyQueryFilter(query, 'where', range, options.where[range]); + if (error) return cb(error); + delete options.where[range]; } @@ -572,7 +571,9 @@ module.exports = (function () { } } else { - adapter._applyQueryFilter(query, queryOp, key, options.where[key]); + + error = adapter._applyQueryFilter(query, queryOp, key, options.where[key]); + if (error) return cb(error); } } @@ -602,11 +603,11 @@ module.exports = (function () { if (_.isString(condition) || _.isNumber(condition)) { - query[queryOp](key).equals(condition); + query[op](key).equals(condition); } else if (_.isArray(condition)) { - query[queryOp](key).in(condition); + query[op](key).in(condition); } else if (_.isObject(condition)) { @@ -618,16 +619,17 @@ module.exports = (function () { } else { - return cb(new Error("Wrong filter given :" + filter)); + throw new Error("Wrong filter given :" + filter); } } else { - return cb(new Error("Wrong filter given :" + filter)); + + throw new Error("Wrong filter given :" + filter); } } catch (e) { - return cb(e.message); + return e; } }, From f8ae62716556ec8846d1502337f1e95622aa318a Mon Sep 17 00:00:00 2001 From: devinivy Date: Thu, 22 Jan 2015 18:47:54 -0500 Subject: [PATCH 33/60] user primary keys for update --- index.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 975055f..76fe5c3 100644 --- a/index.js +++ b/index.js @@ -868,7 +868,8 @@ module.exports = (function () { //sails.log.silly("::options", options); //sails.log.silly("::values", values); var Model = adapter._getModel(collectionName); - + var primaryKeys = adapter._getPrimaryKeys(collectionName); + // If you need to access your private data for this collection: var collection = _collectionReferences[collectionName]; adapter._valueEncode(collection.definition, values); @@ -887,10 +888,26 @@ module.exports = (function () { // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - var updateValues = _.assign(options.where, values); + + // Move primary keys to values (Vogels-style) so rest of wheres can be used for expected clause. + // Actually, seems like the primary key has to stay in the wheres so as not to create a new item. + var primaryKeyName; + for (var i = 0; i < primaryKeys.length; i++) { + + primaryKeyName = primaryKeys[i]; + + if (options.where[primaryKeyName]) { + values[primaryKeyName] = options.where[primaryKeyName]; + } + + } + + var vogelsOptions = !_.isEmpty(options.where) ? { expected: options.where } : {}; + //console.log(updateValues); - var current = Model.update(updateValues, function (err, res) { + Model.update(values, vogelsOptions, function (err, res) { if (err) { + ConditionalCheckFailedException //sails.log.error('Error update data' + __filename, err); cb(err); } From 6e6e99a718c2cc6837d3c2bac894e5dca3b0c770 Mon Sep 17 00:00:00 2001 From: devinivy Date: Fri, 23 Jan 2015 08:35:39 -0500 Subject: [PATCH 34/60] update update method. deal with update conditionals. --- index.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 76fe5c3..9aa9df7 100644 --- a/index.js +++ b/index.js @@ -906,17 +906,27 @@ module.exports = (function () { //console.log(updateValues); Model.update(values, vogelsOptions, function (err, res) { + if (err) { - ConditionalCheckFailedException + //sails.log.error('Error update data' + __filename, err); - cb(err); - } - else { + + // Deal with AWS's funny way of telling us it couldnt update that item + if (err.code == 'ConditionalCheckFailedException') { + + cb(null, []); + } else { + + cb(err); + } + + } else { // console.log('add model data',res.attrs); adapter._valueDecode(collection.definition, res.attrs); - // Respond with error or the newly-created record. + // Respond with error or the newly-updated record. cb(null, [res.attrs]); } + }); // Respond with error or an array of updated records. From 666025fc6dc6b3d95f3a0ebb445c5a78166d4379 Mon Sep 17 00:00:00 2001 From: devinivy Date: Fri, 23 Jan 2015 10:09:51 -0500 Subject: [PATCH 35/60] allow identity name to match table name by removing 's'. --- index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 9aa9df7..74184d5 100644 --- a/index.js +++ b/index.js @@ -154,7 +154,13 @@ module.exports = (function () { collection.definition[primaryKeyNames[0]].primaryKey = 'hash'; } - var vogelsModel = Vogels.define(collectionName, function (schema) { + // Vogels adds an 's'. So let's remove an 's'. + var vogelsCollectionName = collectionName[collectionName.length-1] === 's' ? + + collectionName.slice(0, collectionName.length-1) : + collectionName; + + var vogelsModel = Vogels.define(vogelsCollectionName, function (schema) { var columns = collection.definition; @@ -906,7 +912,6 @@ module.exports = (function () { //console.log(updateValues); Model.update(values, vogelsOptions, function (err, res) { - if (err) { //sails.log.error('Error update data' + __filename, err); @@ -923,10 +928,9 @@ module.exports = (function () { } else { // console.log('add model data',res.attrs); adapter._valueDecode(collection.definition, res.attrs); - // Respond with error or the newly-updated record. + // Respond with error or the newly-created record. cb(null, [res.attrs]); } - }); // Respond with error or an array of updated records. From d4e7514224fa7b84a2665f0c4038515cc58a4d42 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Mon, 2 Feb 2015 23:45:02 -0500 Subject: [PATCH 36/60] use column name properly when determining index name. --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 74184d5..64a083b 100644 --- a/index.js +++ b/index.js @@ -186,7 +186,7 @@ module.exports = (function () { index = attributes.index; - indexParts = adapter._parseIndex(index); + indexParts = adapter._parseIndex(index, columnName); indexName = indexParts[0]; indexType = indexParts[1]; @@ -266,7 +266,7 @@ module.exports = (function () { }, - _parseIndex: function(index) { + _parseIndex: function(index, columnName) { // Two helpers var stringEndsWith = function(str, needle) { @@ -690,7 +690,7 @@ module.exports = (function () { // build global secondary hash info if (column.index && column.index !== 'secondary') { - indexInfo = adapter._parseIndex(column.index); + indexInfo = adapter._parseIndex(column.index, fieldName); indexName = indexInfo[0]; indexType = indexInfo[1]; From 8850d2373a28aec95cbe0481f5a9f2e5802905a6 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Mon, 9 Feb 2015 21:15:41 -0500 Subject: [PATCH 37/60] Update readme with info on indexes and update. --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4951e01..a068d76 100755 --- a/README.md +++ b/README.md @@ -70,8 +70,7 @@ Support for where is added as following: ?where={"name":{"between":["firstName, "lastName""]}} ``` - -## Pagination +### Pagination Support for Pagination is added as following: 1. First add a limit to current request ``` @@ -82,6 +81,50 @@ Support for Pagination is added as following: /user?limit=2&startKey={"PrimaryKey": "2"} ``` +## Using DynamoDB Indexes +Primary hash/range keys, local secondary indexes, and global secondary indexes are currently supported by this adapter, but their usage is always inferred from query conditions–`Model.find` will attempt to use the most optimal index using the following precedence: +``` +Primary hash and range > primary hash and secondary range > global secondary hash and range +> primary hash > global secondary hash > no index/primary +``` +If an index is being used and there are additional query conditions, then results are compiled using DynamoDB's result filtering. If no index can be used for a query, then the adapter will perform a scan on the table for results. + +### Adding Indexes +#### Primary hash and primary range +``` +UserId: { + type: 'integer', + primaryKey: 'hash' +}, +GameTitle: { + type: 'string', + primaryKey: 'range' +} +``` +#### Secondary range (local secondary index) +The index name used for a local secondary index is the name of the field. In this case the index name is `Time`. +``` +Time: { + type: 'datetime', + index: 'secondary' +} +``` +#### Global secondary index +The index name used for a global secondary index is specified in the `index` property before the type of key (`hash` or `range`). In this case the index name is `GameTitleIndex`. +``` +GameTitle: { + type: 'string', + index: 'GameTitleIndex-hash' +}, +HighScore: { + type: 'integer', + index: 'GameTitleIndex-range' +} +``` + +## Update +The `Model.update` method is currently expected to update exactly one item since DynamoDB only offers an [UpdateItem](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) endpoint. A complete primary key must be supplied. Any additional "where" conditions passed to `Model.update` are used to build a conditional expression for the update. Despite the fact the DynamoDB updates only one item, `Model.update` will always return an array of the (one or zero) updated items upon success. + ## Testing Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. From 094184de34ff1f4ffccf498bd902bca0428fa1d7 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Mon, 9 Feb 2015 21:21:57 -0500 Subject: [PATCH 38/60] adjust list so it works with code blocks. --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a068d76..2d30ac8 100755 --- a/README.md +++ b/README.md @@ -72,14 +72,18 @@ Support for where is added as following: ### Pagination Support for Pagination is added as following: + 1. First add a limit to current request -``` - /user?limit=2 -``` + + ``` +/user?limit=2 + ``` + 2. Then get the last primaryKey value and send it as startKey in the next request -``` - /user?limit=2&startKey={"PrimaryKey": "2"} -``` + + ``` +/user?limit=2&startKey={"PrimaryKey": "2"} + ``` ## Using DynamoDB Indexes Primary hash/range keys, local secondary indexes, and global secondary indexes are currently supported by this adapter, but their usage is always inferred from query conditions–`Model.find` will attempt to use the most optimal index using the following precedence: From 06a556a40bb3604c793eadcd8f2fedb854e6aa7c Mon Sep 17 00:00:00 2001 From: devin ivy Date: Mon, 9 Feb 2015 23:32:18 -0500 Subject: [PATCH 39/60] add to readme, secondary indexes are suffixed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d30ac8..480c0d8 100755 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ GameTitle: { } ``` #### Secondary range (local secondary index) -The index name used for a local secondary index is the name of the field. In this case the index name is `Time`. +The index name used for a local secondary index is the name of the field suffixed by "Index". In this case the index name is `TimeIndex`. ``` Time: { type: 'datetime', From 97d8410b50987c71a1d11b65ed232b415720c95b Mon Sep 17 00:00:00 2001 From: devin ivy Date: Tue, 10 Feb 2015 10:43:59 -0500 Subject: [PATCH 40/60] do not use any index name when just selecting on primary key --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 64a083b..de975d6 100644 --- a/index.js +++ b/index.js @@ -543,7 +543,7 @@ module.exports = (function () { query = model.query(options.where[hash]) delete options.where[hash]; - if (indexName) { + if (indexName && indexName != 'primary') { query.usingIndex(indexName); } From cca45ad29ede8d8bbeb84fb72be51e48d412e745 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Wed, 11 Feb 2015 21:57:56 -0500 Subject: [PATCH 41/60] Add autoPk support by making autoIncrement Vogels' UUID and setting pkFormat. --- index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index de975d6..714356c 100644 --- a/index.js +++ b/index.js @@ -95,9 +95,10 @@ module.exports = (function () { var adapter = { identity: 'sails-dynamodb', - keyId: "id", - indexPrefix: "-Index", - + pkFormat: 'string', + + keyId: 'id', + // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if your data store is not SQL/schemaful. @@ -1127,6 +1128,17 @@ module.exports = (function () { // case "json": // case "string": // case "binary": + case "string": + + if (attr.autoIncrement) { + + schema.UUID(name, options); + } else { + + schema.String(name, options); + } + break; + default: // console.log("Set String", name); schema.String(name, options); From 92f5ab584eefded856679b37b0bc69ada21b0559 Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Tue, 24 Mar 2015 16:09:20 +0100 Subject: [PATCH 42/60] bump v0.12.0 and adding dohzoh to npm package --- contributors.md | 14 ++++++++++++++ package.json | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 contributors.md diff --git a/contributors.md b/contributors.md new file mode 100644 index 0000000..2177b05 --- /dev/null +++ b/contributors.md @@ -0,0 +1,14 @@ +###### Contributors +[dozo](https://github.com/dohzoh) +46 Commits / 943++ / 517-- +38.98% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

+[Mike McNeil](https://github.com/mikermcneil) +31 Commits / 1371++ / 725-- +26.27% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

+[Waleed Gadelkareem](https://github.com/gadelkareem) +27 Commits / 1975++ / 1842-- +22.88% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

+[devin ivy](https://github.com/devinivy) +14 Commits / 586++ / 245-- +11.86% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

+###### [Generated](https://github.com/jakeleboeuf/contributor) on Tue Mar 24 2015 14:49:47 GMT+0000 (UTC) \ No newline at end of file diff --git a/package.json b/package.json index b99ba80..d75a2fd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.11.4", + "version": "0.12.0", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { @@ -23,7 +23,7 @@ "author": "gadelkareem", "license": "MIT", "dependencies": { - "vogels": "0.12.0", + "vogels": "~0.12.0", "lodash": "", "async": "" }, @@ -58,9 +58,48 @@ "email": "gadelkareem@gmail.com" }, "maintainers": [ - "gadelkareem " + "gadelkareem ", + "devinivy " ], "directories": { "test": "test" - } + }, + "contributors": [ + { + "name": "Waleed Gadelkareem", + "email": "gadelkareem@gmail.com", + "url": "https://github.com/gadelkareem", + "contributions": 27, + "additions": 1975, + "deletions": 1842, + "hireable": false + }, + { + "name": "Mike McNeil", + "email": "customers@balderdash.co", + "url": "https://github.com/mikermcneil", + "contributions": 31, + "additions": 1371, + "deletions": 725, + "hireable": true + }, + { + "name": "devin ivy", + "email": "devin@bigroomstudios.com", + "url": "https://github.com/devinivy", + "contributions": 14, + "additions": 586, + "deletions": 245, + "hireable": false + }, + { + "name": "dozo", + "email": "", + "url": "https://github.com/dohzoh", + "contributions": 46, + "additions": 943, + "deletions": 517, + "hireable": false + } + ] } From 90a3b918e6b43e28bd9afd1930f563c148553fd6 Mon Sep 17 00:00:00 2001 From: voravor Date: Mon, 1 Jun 2015 14:32:24 -0400 Subject: [PATCH 43/60] Update index.js Allow DynamoDB Local endpoints to be passed into AWS.config.update. --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 714356c..5301c9c 100644 --- a/index.js +++ b/index.js @@ -334,7 +334,8 @@ module.exports = (function () { AWS.config.update({ "accessKeyId": connection.accessKeyId, "secretAccessKey": connection.secretAccessKey, - "region": connection.region + "region": connection.region, + "endpoint": connection.endPoint }); } catch (e) { From 9ca0c93195c012103fb4dd4941e1e7ad2398db2e Mon Sep 17 00:00:00 2001 From: voravor Date: Wed, 10 Jun 2015 17:34:13 -0400 Subject: [PATCH 44/60] Allow for passing a logger object into AWS from connection.js; tested/passed not breaking if connection/adapter does not have a logger defined --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 5301c9c..f30f31b 100644 --- a/index.js +++ b/index.js @@ -335,7 +335,8 @@ module.exports = (function () { "accessKeyId": connection.accessKeyId, "secretAccessKey": connection.secretAccessKey, "region": connection.region, - "endpoint": connection.endPoint + "endpoint": connection.endPoint, + "logger": connection.logger }); } catch (e) { From 6592738988bf9b12391097cd4de7283e7f4f31f8 Mon Sep 17 00:00:00 2001 From: devinivy Date: Tue, 30 Jun 2015 09:45:09 -0400 Subject: [PATCH 45/60] 0.12.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d75a2fd..021e81b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.12.0", + "version": "0.12.1", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { From ffbf0ac25dd53a842e53959fdd5beb6332549626 Mon Sep 17 00:00:00 2001 From: Miguel Meza Date: Thu, 29 Oct 2015 10:07:42 -0300 Subject: [PATCH 46/60] Missing , in the adapter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 480c0d8..0b97dd1 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ module.exports.adapters = { adapter: "sails-dynamodb", accessKeyId: process.env.DYNAMO_ACCESS_KEY_ID, secretAccessKey: process.env.DYNAMO_SECRET_ACCESS_KEY, - region: "us-west-1" + region: "us-west-1", endPoint: "http://localhost:8000", // Optional: add for DynamoDB local }, From d6998d5f74566abff160fec5ce5e7ab872fd7184 Mon Sep 17 00:00:00 2001 From: Phillip Hall Date: Mon, 25 Jan 2016 09:34:08 +1100 Subject: [PATCH 47/60] Fixed missing lodash dependency version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 021e81b..72ad4b1 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "license": "MIT", "dependencies": { "vogels": "~0.12.0", - "lodash": "", + "lodash": "^3.10.1", "async": "" }, "devDependencies": { From 08a82af02026e7a66e1380ac8ff1fbbb77015693 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Thu, 28 Jan 2016 22:56:39 -0500 Subject: [PATCH 48/60] Specify async version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72ad4b1..2b0f280 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "vogels": "~0.12.0", "lodash": "^3.10.1", - "async": "" + "async": "^1.5.0" }, "devDependencies": { "mocha": "~1.13.0", From fa295c1d383b2dc020de3438ab60af63e32970c1 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Thu, 28 Jan 2016 22:57:10 -0500 Subject: [PATCH 49/60] 0.12.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 package.json diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 2b0f280..965c220 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.12.1", + "version": "0.12.2", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { From 14150f1756f722e0ca33ec335913a5e790e82589 Mon Sep 17 00:00:00 2001 From: Matt McCarty Date: Tue, 2 Feb 2016 17:47:20 -0500 Subject: [PATCH 50/60] added new method to make where queries consistent with mongodb adapter --- index.js | 111 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index f30f31b..9c37d41 100644 --- a/index.js +++ b/index.js @@ -336,7 +336,7 @@ module.exports = (function () { "secretAccessKey": connection.secretAccessKey, "region": connection.region, "endpoint": connection.endPoint, - "logger": connection.logger + "logger": connection.logger }); } catch (e) { @@ -513,10 +513,11 @@ module.exports = (function () { //sails.log.silly("::option", options); var collection = _collectionReferences[collectionName], - model = adapter._getModel(collectionName), - query = null, + model = adapter._getModel(collectionName), + query = null, error; + // Options object is normalized for you: // // options.where @@ -530,20 +531,16 @@ module.exports = (function () { if (options && 'where' in options && _.isObject(options.where)) { - query = null; + var wheres = options.where, + whereExt = this._getSubQueryWhereConditions(options), + indexing = adapter._whichIndex(collectionName, ((whereExt) ? whereExt : wheres )), + hash = indexing.hash, + range = indexing.range, + indexName = indexing.index, + scanning = false; - // get current condition - var wheres = _.keys(options.where); - - var indexing = adapter._whichIndex(collectionName, wheres); - var hash = indexing.hash; - var range = indexing.range; - var indexName = indexing.index; - - var scanning = false; if (indexing) { - - query = model.query(options.where[hash]) + query = model.query(options.where[hash]) delete options.where[hash]; if (indexName && indexName != 'primary') { @@ -556,16 +553,14 @@ module.exports = (function () { if (error) return cb(error); delete options.where[range]; - } + } } else { - scanning = true; query = model.scan(); } var queryOp = scanning ? 'where' : 'filter'; - for (var key in options.where) { // Using startKey? @@ -580,34 +575,76 @@ module.exports = (function () { } } else { - - error = adapter._applyQueryFilter(query, queryOp, key, options.where[key]); - if (error) return cb(error); + + var condition = (whereExt) ? whereExt : options.where[key]; + if (whereExt) { + for (var subKey in condition) { + error = adapter._applyQueryFilter(query, queryOp, subKey, condition[subKey]); + if (error) return cb(error); + } + options.where = whereExt; + } else { + error = adapter._applyQueryFilter(query, queryOp, key, condition); + if (error) return cb(error); + } } - } } - + query = adapter._searchCondition(query, options, model); - query.exec(function (err, res) { if (!err) { - //console.log("success", adapter._resultFormat(res)); adapter._valueDecode(collection.definition, res.attrs); cb(null, adapter._resultFormat(res)); } else { - //sails.log.error('Error exec query:' + __filename, err); cb(err); } }); + }, - // Respond with an error, or the results. -// cb(null, []); + /** + * _getSubQueryWhereConditions + * @description :: Handle where objects that contain subquery arrays (i.e: and: [], or: [], etc). + * For consistency, This is useful when using dynamo and mongo data connections + * in the same project. + * @author :: Matt McCarty (https://github.com/mattmccarty) + * @param :: object + * @return :: Object filled with 'where' values or false + */ + _getSubQueryWhereConditions: function(options) { + var wheresCurrent = _.keys(options.where), + wheres = [], + whereExt = false; + + for (var key in wheresCurrent) { + var where = options.where[wheresCurrent[key]]; + if (!_.isArray(where)) { + wheres.push(wheresCurrent[key]); + continue; + } + + for (var arrKey in where) { + if (typeof where[arrKey] !== 'object') { + continue; + } + + var subKeys = _.keys(where[arrKey]); + + // Concat unique keys + wheres = _.union(wheres, subKeys); + + for (var subKey in subKeys) { + if (!whereExt) whereExt = {}; + whereExt[subKeys[subKey]] = where[arrKey][subKeys[subKey]]; + } + } + } + + return whereExt; }, _applyQueryFilter: function(query, op, key, condition) { - try { if (_.isString(condition) || _.isNumber(condition)) { @@ -615,19 +652,16 @@ module.exports = (function () { query[op](key).equals(condition); } else if (_.isArray(condition)) { - query[op](key).in(condition); } else if (_.isObject(condition)) { var filter = _.keys(condition)[0]; - + if (filter in filters) { - query[op](key)[filter](filters[filter] ? condition[filter] : null); } else { - throw new Error("Wrong filter given :" + filter); } @@ -637,10 +671,9 @@ module.exports = (function () { } } catch (e) { - + return e; } - }, // Return {index: 'name', hash: 'field1', range:'field2'} @@ -671,7 +704,7 @@ module.exports = (function () { fieldName = fields[i]; column = columns[fieldName]; - + // set primary hash if (column.primaryKey && column.primaryKey === true || column.primaryKey === 'hash') { primaryHash = fieldName; @@ -789,7 +822,6 @@ module.exports = (function () { * @private */ _searchCondition: function (query, options, model) { - if (!query) { query = model.scan(); } @@ -818,7 +850,7 @@ module.exports = (function () { query.loadAll(); } - + return query; }, @@ -1207,5 +1239,4 @@ module.exports = (function () { // Expose adapter definition return adapter; -})(); - +})(); \ No newline at end of file From 541e0f907b60ab0fbf10d5a370766dc7d6b43e75 Mon Sep 17 00:00:00 2001 From: Matt McCarty Date: Tue, 9 Feb 2016 16:44:48 -0500 Subject: [PATCH 51/60] added support for conditional operators --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9c37d41..ec454b5 100644 --- a/index.js +++ b/index.js @@ -336,7 +336,7 @@ module.exports = (function () { "secretAccessKey": connection.secretAccessKey, "region": connection.region, "endpoint": connection.endPoint, - "logger": connection.logger + "logger": connection.logger }); } catch (e) { From 4c53c2d6d6eed5596c2638b14502f79419cef9c1 Mon Sep 17 00:00:00 2001 From: Matt McCarty Date: Tue, 9 Feb 2016 16:46:25 -0500 Subject: [PATCH 52/60] added support for conditional operators --- index.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ec454b5..ffcce6a 100644 --- a/index.js +++ b/index.js @@ -613,9 +613,11 @@ module.exports = (function () { * @return :: Object filled with 'where' values or false */ _getSubQueryWhereConditions: function(options) { - var wheresCurrent = _.keys(options.where), - wheres = [], - whereExt = false; + var wheresCurrent = _.keys(options.where), + conditionalOperator = 'AND', + wheres = [], + whereExt = false, + count = 0; for (var key in wheresCurrent) { var where = options.where[wheresCurrent[key]]; @@ -624,6 +626,10 @@ module.exports = (function () { continue; } + if (typeof wheresCurrent[key] === 'string' && wheresCurrent[key].toUpperCase() === 'OR') { + conditionalOperator = 'OR'; + } + for (var arrKey in where) { if (typeof where[arrKey] !== 'object') { continue; @@ -637,17 +643,24 @@ module.exports = (function () { for (var subKey in subKeys) { if (!whereExt) whereExt = {}; whereExt[subKeys[subKey]] = where[arrKey][subKeys[subKey]]; + count++; } } } + if (whereExt && count > 1) { + whereExt.ConditionalOperator = conditionalOperator; + } + return whereExt; }, _applyQueryFilter: function(query, op, key, condition) { try { - - if (_.isString(condition) || _.isNumber(condition)) { + + if (key === 'ConditionalOperator' && query.request) { + query.request.ConditionalOperator = condition; + } else if (_.isString(condition) || _.isNumber(condition)) { query[op](key).equals(condition); From e07a78e0ed2b49c12718cf4ad7ca56685801bba5 Mon Sep 17 00:00:00 2001 From: Matt McCarty Date: Wed, 10 Feb 2016 14:54:34 -0500 Subject: [PATCH 53/60] fixed findOne() queries based on aws docs --- index.js | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index ffcce6a..9e1065a 100644 --- a/index.js +++ b/index.js @@ -592,8 +592,41 @@ module.exports = (function () { } query = adapter._searchCondition(query, options, model); - query.exec(function (err, res) { + this._findQuery(adapter, collection, query, false, cb); + }, + + /** + * _findQuery + * @description :: Return result if found. If not and the developer set a limit + on the number of entries to return, then we must keep + scanning the DB until the end is reached or until a result is returned + * @author :: Matt McCarty (https://github.com/mattmccarty) + * @param :: object adapter - Current sails-dynamodb instance + * @param :: object collection - Collection reference + * @param :: object query - Current query + * @param :: object startKey - Contains primary key of record where the search should start + * @param :: function callback + * @return :: callback(err, results) + */ + _findQuery: function(adapter, collection, query, startKey, cb) { + var _self = this; + + if (startKey) { + query.request = query.request || {}; + query.request.ExclusiveStartKey = startKey; + } + + query.exec(function(err, res) { if (!err) { + // The developer requested a specific number of items, so loop over each DB entry + // until the end of the db table is reached or until a result is found + if (res && res.Count <= 0 && res.LastEvaluatedKey && res.LastEvaluatedKey.id) { + var lastKey = { + id: { S: res.LastEvaluatedKey.id }, + } + return adapter._findQuery(adapter, collection, query, lastKey, cb); + } + adapter._valueDecode(collection.definition, res.attrs); cb(null, adapter._resultFormat(res)); } From e3de07eda6b63d9e20baf15f43133b73038c1b33 Mon Sep 17 00:00:00 2001 From: Matt Ferrante Date: Thu, 16 Feb 2017 12:08:29 -0700 Subject: [PATCH 54/60] Update README.md for sort info (#36) Added sort documentation to README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 0b97dd1..2001222 100755 --- a/README.md +++ b/README.md @@ -126,6 +126,17 @@ HighScore: { } ``` +### Sorting By Indexes +Sorting does not look like how it looks with the normal sails database adapters. You can not sort by an arbitrary field, you must sort by a range field in an index. The index is automatically inferred by what you are querying and you can specify a direction to sort the range fields of the used index. Using the GSI defined above, this will query for descending highscores of Super Mario World: +``` +GameScores.find({ + where: { + GameTitle: "Super Mario World" + }, + sort: "-1" +}) +``` + ## Update The `Model.update` method is currently expected to update exactly one item since DynamoDB only offers an [UpdateItem](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) endpoint. A complete primary key must be supplied. Any additional "where" conditions passed to `Model.update` are used to build a conditional expression for the update. Despite the fact the DynamoDB updates only one item, `Model.update` will always return an array of the (one or zero) updated items upon success. From 339f9a4bbaba8fec9d9a973953e14ce82c861554 Mon Sep 17 00:00:00 2001 From: Greg Pagendam-Turner Date: Thu, 9 Mar 2017 02:52:00 +1000 Subject: [PATCH 55/60] Creates tables at startup (#38) Added stack attribute to err object to prevent exception in Water Line --- index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 9e1065a..86b4e5e 100644 --- a/index.js +++ b/index.js @@ -212,6 +212,15 @@ module.exports = (function () { // Cache Vogels model _vogelsReferences[collectionName] = vogelsModel; + + Vogels.createTables(function (err) { + if (err) { + console.log('Error creating tables: ', err); + } else { + console.log('Tables have been created'); + } + }); + return vogelsModel; @@ -925,6 +934,7 @@ module.exports = (function () { var current = Model.create(values, function (err, res) { if (err) { //sails.log.error(__filename + ", create error:", err); + err.stack = ''; cb(err); } else { @@ -1285,4 +1295,4 @@ module.exports = (function () { // Expose adapter definition return adapter; -})(); \ No newline at end of file +})(); From c07817bdc6940227f5f04de90baee9559f6a5151 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Wed, 8 Mar 2017 09:56:02 -0700 Subject: [PATCH 56/60] increment minor version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 965c220..34e9602 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.12.2", + "version": "0.12.3", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { From 28ca5e1cab3d08bdbed3ee0992d29ab6deebe83f Mon Sep 17 00:00:00 2001 From: Matt Ferrante Date: Thu, 30 Mar 2017 13:20:40 -0600 Subject: [PATCH 57/60] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2001222..547acd9 100755 --- a/README.md +++ b/README.md @@ -75,15 +75,15 @@ Support for Pagination is added as following: 1. First add a limit to current request - ``` +``` /user?limit=2 - ``` +``` 2. Then get the last primaryKey value and send it as startKey in the next request - ``` +``` /user?limit=2&startKey={"PrimaryKey": "2"} - ``` +``` ## Using DynamoDB Indexes Primary hash/range keys, local secondary indexes, and global secondary indexes are currently supported by this adapter, but their usage is always inferred from query conditions–`Model.find` will attempt to use the most optimal index using the following precedence: From 0cbaa43c4ae4be77be16fe0d13efa431ee4d5c5f Mon Sep 17 00:00:00 2001 From: Matt Ferrante Date: Thu, 30 Mar 2017 16:05:24 -0600 Subject: [PATCH 58/60] Fix startKey usage and support fields with primary and GSI indexes (#40) --- README.md | 56 ++++++++- index.js | 355 +++++++++++++++++++++++++++--------------------------- 2 files changed, 232 insertions(+), 179 deletions(-) diff --git a/README.md b/README.md index 547acd9..bc7970a 100755 --- a/README.md +++ b/README.md @@ -67,11 +67,14 @@ Support for where is added as following: ?where={"name":{"contains":"firstName lastName"}} ?where={"name":{"beginsWith":"firstName"}} ?where={"name":{"in":["firstName lastName", "another name"]}} - ?where={"name":{"between":["firstName, "lastName""]}} + ?where={"name":{"between":["firstName, "lastName"]}} ``` ### Pagination -Support for Pagination is added as following: +__NOTE__: `skip` is not supported! + +Support for Pagination is done using DynamoDB's `LastEvaluatedKey` and passing that to `ExclusiveStartKey`. +See: [DynamoDB Documentation](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 1. First add a limit to current request @@ -85,6 +88,35 @@ Support for Pagination is added as following: /user?limit=2&startKey={"PrimaryKey": "2"} ``` +For more complex queries, you must provide all the fields that are used for an index of the last object returned. An example not using the blueprint apis below. Assume there is a GSI on email (hash) and loginDate (range). + +``` +// Looking for recent logins by a specific email address +UserLogins.find({ + where: { + email: 'someone@like.you' + loginDate: {"lt": new Date().toISOString()} + }, + limit: 10 +}).exec((err, userLogins) => { + UserLogins.find({ + where: { + email: 'someone@like.you' + loginDate: {"lt": new Date().toISOString()}, + startKey: { + email: 'someone@like.you', + loginDate: userLogins[userLogins.length - 1].loginDate + } + }, + limit: 10 + }).exec((err, moreUserLogins) => { + doSomethingWithLogins(userLogins.concat(moreUserLogins)); + }); +}); +``` + +See that the startKey is in the `where` block and that it has both fields of the Global Secondary Index. + ## Using DynamoDB Indexes Primary hash/range keys, local secondary indexes, and global secondary indexes are currently supported by this adapter, but their usage is always inferred from query conditions–`Model.find` will attempt to use the most optimal index using the following precedence: ``` @@ -126,6 +158,26 @@ HighScore: { } ``` +#### Fields with multiple indexes +A field can be both the primary and part of a GSI index. Participating in multiple GSI indexes is not currently supported. + +``` +GameTitle: { + type: 'string', + primaryKey: 'hash' + index: 'GameTitleIndex-hash' +} +``` + +__Not Supported Yet__: +``` +GameTitle: { + type: 'string', + primaryKey: 'hash' + index: ['GameTitleIndex-hash'. 'SomeOtherIndex-hash'] +} +``` + ### Sorting By Indexes Sorting does not look like how it looks with the normal sails database adapters. You can not sort by an arbitrary field, you must sort by a range field in an index. The index is automatically inferred by what you are querying and you can specify a direction to sort the range fields of the used index. Using the GSI defined above, this will query for descending highscores of Super Mario World: ``` diff --git a/index.js b/index.js index 86b4e5e..7ae5aa5 100644 --- a/index.js +++ b/index.js @@ -96,13 +96,13 @@ module.exports = (function () { identity: 'sails-dynamodb', pkFormat: 'string', - + keyId: 'id', - + // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if your data store is not SQL/schemaful. - + // This doesn't make sense for dynamo, where the schema parts are locked-down during table creation. syncable: false, @@ -132,84 +132,84 @@ module.exports = (function () { // drop => Drop schema and data, then recreate it // alter => Drop/add columns as necessary. // safe => Don't change anything (good for production DBs) - + //Indices currently never change in dynamo migrate: 'safe', // schema: false }, - + _createModel: function (collectionName) { - + var collection = _collectionReferences[collectionName]; // Attrs with primaryKeys var primaryKeys = _.pick(collection.definition, function(attr) { return !!attr.primaryKey } ); var primaryKeyNames =_.keys(primaryKeys); - + if (primaryKeyNames.length < 1 || primaryKeyNames.length > 2) { throw new Error('Must have one or two primary key attributes.'); } - + // One primary key, then it's a hash if (primaryKeyNames.length == 1) { collection.definition[primaryKeyNames[0]].primaryKey = 'hash'; } - + // Vogels adds an 's'. So let's remove an 's'. var vogelsCollectionName = collectionName[collectionName.length-1] === 's' ? - + collectionName.slice(0, collectionName.length-1) : collectionName; - + var vogelsModel = Vogels.define(vogelsCollectionName, function (schema) { - + var columns = collection.definition; - + var indices = {}; - + // set columns for (var columnName in columns) { - + var attributes = columns[columnName]; - + if (typeof attributes !== "function") { - + // Add column to Vogel model adapter._setColumnType(schema, columnName, attributes); - + // Save set indices var index; var indexParts; var indexName; var indexType; - + if ("index" in attributes && attributes.index !== 'secondary') { - + index = attributes.index; - + indexParts = adapter._parseIndex(index, columnName); indexName = indexParts[0]; indexType = indexParts[1]; - + if (typeof indices[indexName] === 'undefined') { indices[indexName] = {}; } - + indices[indexName][indexType] = columnName; - + } - + } - + } - + // Set global secondary indices for (indexName in indices) { schema.globalIndex(indexName, indices[indexName]); } - + }); - + // Cache Vogels model _vogelsReferences[collectionName] = vogelsModel; @@ -221,17 +221,17 @@ module.exports = (function () { } }); - + return vogelsModel; - + }, - + _getModel: function(collectionName) { return _vogelsReferences[collectionName] || this._createModel(collectionName); }, - + _getPrimaryKeys: function (collectionName) { - + var lodash = _; var collection = _collectionReferences[collectionName]; @@ -240,12 +240,12 @@ module.exports = (function () { var list = lodash.pick(maps, function (value, key) { return typeof value !== "undefined"; }); - + var primaryKeys = lodash.keys(list); - + return primaryKeys; }, - + _keys: function (collectionName) { var lodash = _; var collection = _collectionReferences[collectionName]; @@ -255,7 +255,7 @@ module.exports = (function () { }); return lodash.keys(list); }, - + _indexes: function (collectionName) { var lodash = _; var collection = _collectionReferences[collectionName]; @@ -265,64 +265,64 @@ module.exports = (function () { }); return lodash.keys(list); }, - + // index: 'secondary' _getLocalIndices: function(collectionName) { - + }, - + // index: 'indexName-fieldType' (i.e. 'users-hash' and 'users-range') _getGlobalIndices: function(collectionName) { - + }, _parseIndex: function(index, columnName) { - + // Two helpers var stringEndsWith = function(str, needle) { - + if (str.indexOf(needle) !== -1 && str.indexOf(needle) === str.length-needle.length) { return true; } else { return false; } - + } - + var removeSuffixFromString = function(str, suffix) { - + if (stringEndsWith(str, suffix)) { return str.slice(0, str.length-suffix.length); } else { return str; } - + } - + var indexName; var indexType; - + if (index === true) { - + indexName = columnName; indexType = 'hashKey'; } else if (stringEndsWith(index, '-hash')) { - + indexName = removeSuffixFromString(index, '-hash'); indexType = 'hashKey'; } else if (stringEndsWith(index, '-range')) { - + indexName = removeSuffixFromString(index, '-range'); - indexType = 'rangeKey'; + indexType = 'rangeKey'; } else { throw new Error('Index must be a hash or range.'); } - + return [indexName, indexType]; - + }, - + /** * * This method runs when a model is initially registered @@ -332,14 +332,14 @@ module.exports = (function () { * @param {Function} cb [description] * @return {[type]} [description] */ - + registerConnection: function (connection, collections, cb) { if (!connection.identity) return cb(Errors.IdentityMissing); if (connections[connection.identity]) return cb(Errors.IdentityDuplicate); try { - + AWS.config.update({ "accessKeyId": connection.accessKeyId, "secretAccessKey": connection.secretAccessKey, @@ -348,19 +348,19 @@ module.exports = (function () { "logger": connection.logger }); } catch (e) { - + e.message = e.message + ". Please make sure you added the right keys to your adapter config"; return cb(e) } - + // Keep a reference to these collections _collectionReferences = collections; - + // Create Vogels models for the collections _.forOwn(collections, function(coll, collName) { adapter._createModel(collName); }); - + cb(); }, @@ -525,7 +525,7 @@ module.exports = (function () { model = adapter._getModel(collectionName), query = null, error; - + // Options object is normalized for you: // @@ -539,7 +539,7 @@ module.exports = (function () { // If no matches were found, this will be an empty array. if (options && 'where' in options && _.isObject(options.where)) { - + var wheres = options.where, whereExt = this._getSubQueryWhereConditions(options), indexing = adapter._whichIndex(collectionName, ((whereExt) ? whereExt : wheres )), @@ -551,19 +551,19 @@ module.exports = (function () { if (indexing) { query = model.query(options.where[hash]) delete options.where[hash]; - + if (indexName && indexName != 'primary') { query.usingIndex(indexName); } - + if (range) { - + error = adapter._applyQueryFilter(query, 'where', range, options.where[range]); if (error) return cb(error); - + delete options.where[range]; } - + } else { scanning = true; query = model.scan(); @@ -571,18 +571,21 @@ module.exports = (function () { var queryOp = scanning ? 'where' : 'filter'; for (var key in options.where) { - + // Using startKey? if (key == 'startKey') { - + try { - - query.startKey(JSON.parse(options.where[key])); + if (_.isString(options.where.startKey)){ + query.startKey(JSON.parse(options.where[key])); + }else{ + query.startKey(options.where.startKey); + } } catch (e) { - + return cb("Wrong start key format :" + e.message); } - + } else { var condition = (whereExt) ? whereExt : options.where[key]; @@ -609,7 +612,7 @@ module.exports = (function () { * @description :: Return result if found. If not and the developer set a limit on the number of entries to return, then we must keep scanning the DB until the end is reached or until a result is returned - * @author :: Matt McCarty (https://github.com/mattmccarty) + * @author :: Matt McCarty (https://github.com/mattmccarty) * @param :: object adapter - Current sails-dynamodb instance * @param :: object collection - Collection reference * @param :: object query - Current query @@ -635,7 +638,7 @@ module.exports = (function () { } return adapter._findQuery(adapter, collection, query, lastKey, cb); } - + adapter._valueDecode(collection.definition, res.attrs); cb(null, adapter._resultFormat(res)); } @@ -648,10 +651,10 @@ module.exports = (function () { /** * _getSubQueryWhereConditions * @description :: Handle where objects that contain subquery arrays (i.e: and: [], or: [], etc). - * For consistency, This is useful when using dynamo and mongo data connections + * For consistency, This is useful when using dynamo and mongo data connections * in the same project. - * @author :: Matt McCarty (https://github.com/mattmccarty) - * @param :: object + * @author :: Matt McCarty (https://github.com/mattmccarty) + * @param :: object * @return :: Object filled with 'where' values or false */ _getSubQueryWhereConditions: function(options) { @@ -696,59 +699,59 @@ module.exports = (function () { return whereExt; }, - + _applyQueryFilter: function(query, op, key, condition) { try { if (key === 'ConditionalOperator' && query.request) { query.request.ConditionalOperator = condition; } else if (_.isString(condition) || _.isNumber(condition)) { - + query[op](key).equals(condition); - + } else if (_.isArray(condition)) { query[op](key).in(condition); - + } else if (_.isObject(condition)) { - + var filter = _.keys(condition)[0]; if (filter in filters) { query[op](key)[filter](filters[filter] ? condition[filter] : null); - + } else { throw new Error("Wrong filter given :" + filter); } - + } else { - + throw new Error("Wrong filter given :" + filter); } - + } catch (e) { return e; } }, - + // Return {index: 'name', hash: 'field1', range:'field2'} // Primary hash and range > primary hash and secondary range > global secondary hash and range // > primary hash > global secondary hash > no index/primary _whichIndex: function(collectionName, fields) { - + var columns = _collectionReferences[collectionName].definition; - + var primaryHash = false; var primaryRange = false; var secondaryRange = false; var globalHash = false; var globalRange = false; - + var globalIndexName; - + // holds all index info from fields var indices = {}; - + // temps for loop var fieldName; var column; @@ -756,119 +759,117 @@ module.exports = (function () { var indexName; var indexType; for (var i = 0; i < fields.length; i++) { - + fieldName = fields[i]; column = columns[fieldName]; - // set primary hash - if (column.primaryKey && column.primaryKey === true || column.primaryKey === 'hash') { - primaryHash = fieldName; + if (column === undefined){ // happens in the case of startKey continue; } - - // set primary range - if (column.primaryKey && column.primaryKey === 'range') { - primaryRange = fieldName; - continue; - } - - // set secondary range - if (column.index && column.index === 'secondary') { - secondaryRange = fieldName; - continue; + + // set primary hash + if (column.primaryKey){ + if (column.primaryKey === true || column.primaryKey === 'hash'){ + primaryHash = fieldName; + }else if (column.primaryKey === 'range') { + primaryRange = fieldName; + } } - - // build global secondary hash info - if (column.index && column.index !== 'secondary') { - - indexInfo = adapter._parseIndex(column.index, fieldName); - indexName = indexInfo[0]; - indexType = indexInfo[1]; - - if (typeof indices[indexName] === 'undefined') { - indices[indexName] = {}; + + // using secondary or GSIs + if (column.index){ + if (_.isArray(column.index)){ + throw new Error(`No support yet for multiple non-primary indexes, ${fieldName} = ${column.index}`); + }else if (column.index === 'secondary'){ + secondaryRange = fieldName; + }else{ + indexInfo = adapter._parseIndex(column.index, fieldName); + indexName = indexInfo[0]; + indexType = indexInfo[1]; + + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } + + indices[indexName][indexType] = fieldName; } - - indices[indexName][indexType] = fieldName; - - continue; } - + } - + // set global secondary hash info var indicesHashed; var indicesRanged; - + // pick out those with just a hash key var indicesHashed = _.pick(indices, function(ind) { return !!ind.hashKey && !ind.rangeKey; }); - + // pick out those with a hash and a range key var indicesRanged = _.pick(indices, function(ind) { return !!ind.hashKey && !!ind.rangeKey; }); - + // found a good ranged global secondary index? if (!_.isEmpty(indicesRanged)) { - + globalIndexName = Object.keys(indicesRanged)[0]; globalHash = indicesRanged[globalIndexName].hashKey; globalRange = indicesRanged[globalIndexName].rangeKey; - + } else if (!_.isEmpty(indicesHashed)) { - + globalIndexName = Object.keys(indicesHashed)[0]; globalHash = indicesHashed[globalIndexName].hashKey; - + } - + if (primaryHash && primaryRange) { - + return { index: 'primary', hash: primaryHash, range: primaryRange } - + } else if (primaryHash && secondaryRange) { - + return { index: secondaryRange+'Index', // per Vogels hash: primaryHash, range: secondaryRange } - + } else if (globalHash && globalRange) { - + return { index: globalIndexName, hash: globalHash, range: globalRange } - + } else if (primaryHash) { - + return { index: 'primary', hash: primaryHash } - + } else if (globalHash) { - + return { index: globalIndexName, hash: globalHash } - + } else { - + return false; } - + }, - + /** * search condition * @param query @@ -880,16 +881,16 @@ module.exports = (function () { if (!query) { query = model.scan(); } - + if (!options) { return query; } - + if ('sort' in options) { - + //according to http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward var sort = _.keys(options.sort)[0]; - + if (sort == 1) { query.ascending(); } @@ -897,12 +898,12 @@ module.exports = (function () { query.descending(); } } - + if ('limit' in options) { - + query.limit(options.limit); } else { - + query.loadAll(); } @@ -966,7 +967,7 @@ module.exports = (function () { //sails.log.silly("::values", values); var Model = adapter._getModel(collectionName); var primaryKeys = adapter._getPrimaryKeys(collectionName); - + // If you need to access your private data for this collection: var collection = _collectionReferences[collectionName]; adapter._valueEncode(collection.definition, values); @@ -985,37 +986,37 @@ module.exports = (function () { // 2. Update all result records with `values`. // // (do both in a single query if you can-- it's faster) - + // Move primary keys to values (Vogels-style) so rest of wheres can be used for expected clause. // Actually, seems like the primary key has to stay in the wheres so as not to create a new item. var primaryKeyName; for (var i = 0; i < primaryKeys.length; i++) { - + primaryKeyName = primaryKeys[i]; - + if (options.where[primaryKeyName]) { values[primaryKeyName] = options.where[primaryKeyName]; } - + } - + var vogelsOptions = !_.isEmpty(options.where) ? { expected: options.where } : {}; - + //console.log(updateValues); Model.update(values, vogelsOptions, function (err, res) { if (err) { - + //sails.log.error('Error update data' + __filename, err); - + // Deal with AWS's funny way of telling us it couldnt update that item if (err.code == 'ConditionalCheckFailedException') { - + cb(null, []); } else { - + cb(err); } - + } else { // console.log('add model data',res.attrs); adapter._valueDecode(collection.definition, res.attrs); @@ -1172,21 +1173,21 @@ module.exports = (function () { * @private */ _setColumnType: function (schema, name, attr, options) { - + options = (typeof options !== 'undefined') ? options : {}; - + // Set primary key options if (attr.primaryKey === 'hash') { - + _.merge(options, {hashKey: true}); } else if (attr.primaryKey === 'range') { - + _.merge(options, {rangeKey: true}); } else if (attr.index === 'secondary') { - + _.merge(options, {secondaryIndex: true}); } - + // set columns // console.log("name:", name); // console.log("attr:", attr); @@ -1219,16 +1220,16 @@ module.exports = (function () { // case "string": // case "binary": case "string": - + if (attr.autoIncrement) { - + schema.UUID(name, options); } else { - + schema.String(name, options); } break; - + default: // console.log("Set String", name); schema.String(name, options); From 101697e13b4486c41c4c906e7b2a01114515048f Mon Sep 17 00:00:00 2001 From: Matt Ferrante Date: Thu, 11 May 2017 15:55:18 -0600 Subject: [PATCH 59/60] Multiple GSI Support (#42) * increment version * index option now supports multiple indexes --- README.md | 13 ++++++++++-- index.js | 60 +++++++++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bc7970a..1fd58c7 100755 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ HighScore: { ``` #### Fields with multiple indexes -A field can be both the primary and part of a GSI index. Participating in multiple GSI indexes is not currently supported. +A field can be both the primary and part of a GSI index. Participating in multiple GSI indexes is supported as of v0.12.5. ``` GameTitle: { @@ -169,7 +169,7 @@ GameTitle: { } ``` -__Not Supported Yet__: +Multiple GSIs: ``` GameTitle: { type: 'string', @@ -178,6 +178,15 @@ GameTitle: { } ``` +Multiple GSIs and a secondary index: +``` +GameTitle: { + type: 'string', + primaryKey: 'hash' + index: ['secondary', GameTitleIndex-hash'. 'SomeOtherIndex-hash'] +} +``` + ### Sorting By Indexes Sorting does not look like how it looks with the normal sails database adapters. You can not sort by an arbitrary field, you must sort by a range field in an index. The index is automatically inferred by what you are querying and you can specify a direction to sort the range fields of the used index. Using the GSI defined above, this will query for descending highscores of Super Mario World: ``` diff --git a/index.js b/index.js index 7ae5aa5..7765ece 100644 --- a/index.js +++ b/index.js @@ -187,15 +187,29 @@ module.exports = (function () { index = attributes.index; - indexParts = adapter._parseIndex(index, columnName); - indexName = indexParts[0]; - indexType = indexParts[1]; + if (_.isArray(index)){ + index.forEach((oneIndex) => { + indexParts = adapter._parseIndex(oneIndex, columnName); + indexName = indexParts[0]; + indexType = indexParts[1]; + + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } + + indices[indexName][indexType] = columnName; + }); + }else{ + indexParts = adapter._parseIndex(index, columnName); + indexName = indexParts[0]; + indexType = indexParts[1]; - if (typeof indices[indexName] === 'undefined') { - indices[indexName] = {}; - } + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } - indices[indexName][indexType] = columnName; + indices[indexName][indexType] = columnName; + } } @@ -549,6 +563,8 @@ module.exports = (function () { scanning = false; if (indexing) { + // console.log("USING INDEX") + // console.log(indexing); query = model.query(options.where[hash]) delete options.where[hash]; @@ -758,6 +774,14 @@ module.exports = (function () { var indexInfo; var indexName; var indexType; + + if (!(_.isArray(fields))){ + fields = Object.keys(fields); + } + + // console.log("FIELDS") + // console.log(fields); + for (var i = 0; i < fields.length; i++) { fieldName = fields[i]; @@ -778,8 +802,25 @@ module.exports = (function () { // using secondary or GSIs if (column.index){ + // console.log("COLUMN.INDEX") + // console.log(column.index) if (_.isArray(column.index)){ - throw new Error(`No support yet for multiple non-primary indexes, ${fieldName} = ${column.index}`); + column.index.forEach((oneIndex) => { + if (oneIndex === 'secondary'){ + secondaryRange = fieldName; + }else{ + indexInfo = adapter._parseIndex(oneIndex, fieldName); + indexName = indexInfo[0]; + indexType = indexInfo[1]; + + if (typeof indices[indexName] === 'undefined') { + indices[indexName] = {}; + } + + indices[indexName][indexType] = fieldName; + } + }); + // throw new Error(`No support yet for multiple non-primary indexes, ${fieldName} = ${column.index}`); }else if (column.index === 'secondary'){ secondaryRange = fieldName; }else{ @@ -797,6 +838,9 @@ module.exports = (function () { } + // console.log("INDICES") + // console.log(indices) + // set global secondary hash info var indicesHashed; var indicesRanged; diff --git a/package.json b/package.json index 34e9602..11614de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-dynamodb", - "version": "0.12.3", + "version": "0.12.5", "description": "Amazon DynamoDB adapter for Sails / Waterline", "main": "index.js", "scripts": { From 719e014f03203555fb44322a27a5174dc9d00e15 Mon Sep 17 00:00:00 2001 From: Shubhankar Date: Mon, 24 Jul 2017 19:12:32 +0000 Subject: [PATCH 60/60] Punctuation fixes in documentation. Added Select option (#45) * punctions * added 'select' option to query * Documentation for "select" option in find query --- README.md | 11 ++++++++--- index.js | 10 +++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1fd58c7..b4c7445 100755 --- a/README.md +++ b/README.md @@ -67,7 +67,12 @@ Support for where is added as following: ?where={"name":{"contains":"firstName lastName"}} ?where={"name":{"beginsWith":"firstName"}} ?where={"name":{"in":["firstName lastName", "another name"]}} - ?where={"name":{"between":["firstName, "lastName"]}} + ?where={"name":{"between":["firstName", "lastName"]}} +``` +You can specify what attributes/keys should be returned from the query as following: +``` + //This will return only name and age in the result (if the field exists in the result) + ?where={"name":{"equals":"firstName lastName"}, "select": ["name","age"]} ``` ### Pagination @@ -174,7 +179,7 @@ Multiple GSIs: GameTitle: { type: 'string', primaryKey: 'hash' - index: ['GameTitleIndex-hash'. 'SomeOtherIndex-hash'] + index: ['GameTitleIndex-hash', 'SomeOtherIndex-hash'] } ``` @@ -183,7 +188,7 @@ Multiple GSIs and a secondary index: GameTitle: { type: 'string', primaryKey: 'hash' - index: ['secondary', GameTitleIndex-hash'. 'SomeOtherIndex-hash'] + index: ['secondary', 'GameTitleIndex-hash', 'SomeOtherIndex-hash'] } ``` diff --git a/index.js b/index.js index 7765ece..b8b25d1 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ var filters = { beginsWith: true, //?where={"name":{"in":["firstName lastName", "another name"]}} in: true, - //?where={"name":{"between":["firstName, "lastName""]}} + //?where={"name":{"between":["firstName", "lastName"]}} between: true }; @@ -951,6 +951,14 @@ module.exports = (function () { query.loadAll(); } + if ('select' in options) { + if (_.isString(options.select)) { + query = query.attributes([options.select]); + } else { + query = query.attributes(options.select); + } + } + return query; },