├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── module-design.md ├── package.json └── test ├── index.js └── suite.js /.gitignore: -------------------------------------------------------------------------------- 1 | !**/* 2 | 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10.38" 4 | - "4.2" 5 | - "4.2" 6 | - "4" 7 | - "5" 8 | - "6" 9 | before_install: 10 | - source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list 11 | - wget -qO- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add - 12 | - sudo apt-get update -q 13 | - sudo apt-get -y --force-yes install rethinkdb 14 | script: 15 | - npm run test 16 | before_script: 17 | - rethinkdb --daemon 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RethinkDB Init 2 | 3 | [![Build Status](https://travis-ci.org/thejsj/rethinkdb-init.svg?branch=master)](https://travis-ci.org/thejsj/rethinkdb-init) 4 | 5 | Create all RethinkDB databases, tables and indexes automatically through a schema object. 6 | 7 | Often times, application code that uses RethinkDB has a significant portion of code meant for creating the database, tables and indexes [example](https://github.com/thejsj/image-pin/blob/master/server/db/index.js). This module is meant so that you can pass an object with the required initial state of your database and get a promise/callback for when all the necesary componentes have been added to the database. This removes a lot of boilerplate code from your application code and makes it easier to understand what is needed in the database to run the application. 8 | 9 | ## Examples 10 | 11 | ### Instantiating database with a table and an index 12 | 13 | The first argument is a connection object with `host`, `port`, and `db`. If the `db` doesn’t exist, it will be created automatically. 14 | 15 | The second argument is an array of tables. Each table can either be a string or an object. If the entry is an object, it must have a `name` property. 16 | 17 | ```javascript 18 | var r = require('rethinkdb'); 19 | require('rethinkdb-init')(r); 20 | 21 | r.init({ 22 | host: 'localhost', 23 | port: 28015, 24 | db: 'superDatabase' 25 | }, [ 26 | { 27 | name: 'person', 28 | indexes: ['firstName', 'lastName'] 29 | }, 30 | 'address' 31 | ] 32 | }) 33 | .then(function (conn) { 34 | // All tables and indexes have been created 35 | }); 36 | ``` 37 | 38 | ### Instantiating a database with 4 tables with no indexes 39 | 40 | When the array contains a string, a table will be added with that name. 41 | 42 | ```javascript 43 | var r = require('rethinkdb'); 44 | require('rethinkdb-init')(r); 45 | 46 | r.init({ 47 | host: 'localhost', 48 | port: 28015, 49 | db: 'helloDatabase' 50 | }, 51 | [ 52 | 'hello_table', 53 | 'another_table', 54 | 'yet_another_table', 55 | 'one_last_table', 56 | ] 57 | }) 58 | .then(function (conn) { 59 | // All tables and indexes have been created 60 | }); 61 | ``` 62 | 63 | ### Instantiating a database with 2 tables and 2 indexes on one of the tables 64 | 65 | Table objects can contain indexes (which can also be strings or objects). 66 | 67 | ```javascript 68 | var r = require('rethinkdb'); 69 | require('rethinkdb-init')(r); 70 | 71 | r.init({ 72 | host: 'localhost', 73 | port: 28015, 74 | db: 'helloDatabase' 75 | }, 76 | [ 77 | { 78 | name: 'helloTable', 79 | indexes: ['superIndex', 'superDuperIndex'] 80 | }, 81 | 'anotherTable' 82 | ] 83 | }) 84 | .then(function (conn) { 85 | // All tables and indexes have been created 86 | }); 87 | ``` 88 | 89 | ### Instantiating a database with 1 tables and 1 geo index 90 | 91 | You can add a `geo` or `multi` attribute along with an index and it will be passed along to the [`indexCreate`](http://rethinkdb.com/api/javascript/index_create/) function. 92 | 93 | ```javascript 94 | var r = require('rethinkdb'); 95 | require('rethinkdb-init')(r); 96 | 97 | r.init({ 98 | host: 'localhost', 99 | port: 28015, 100 | db: 'helloDatabase' 101 | }, 102 | [ 103 | { 104 | name: 'helloTable', 105 | indexes: [{ 106 | name: 'location', 107 | geo: true, 108 | }] 109 | }, 110 | ] 111 | }) 112 | .then(function (conn) { 113 | // All tables and indexes have been created 114 | }); 115 | ``` 116 | 117 | ### Instantiating a database with 1 tables and 1 multi+geo index 118 | 119 | ```javascript 120 | var r = require('rethinkdb'); 121 | require('rethinkdb-init')(r); 122 | 123 | r.init({ 124 | host: 'localhost', 125 | port: 28015, 126 | db: 'helloDatabase' 127 | }, 128 | [ 129 | { 130 | name: 'helloTable', 131 | indexes: [{ 132 | name: 'location', 133 | geo: true, 134 | multi: true, 135 | }] 136 | }, 137 | ] 138 | }) 139 | .then(function (conn) { 140 | // All tables and indexes have been created 141 | }); 142 | ``` 143 | 144 | ### Instantiating a database with 1 table and 1 index with an indexFunction 145 | 146 | You can add a `indexFunction` attribute along with an index and it will be passed along to the [`indexCreate`](http://rethinkdb.com/api/javascript/index_create/) function. 147 | 148 | ```javascript 149 | var r = require('rethinkdb'); 150 | require('rethinkdb-init')(r); 151 | 152 | r.init({ 153 | host: 'localhost', 154 | port: 28015, 155 | db: 'helloDatabase' 156 | }, 157 | [ 158 | { 159 | name: 'helloTable', 160 | indexes: [{ 161 | name: 'has_location', 162 | indexFunction: function (row) { 163 | return row.hasFields('location'); 164 | }, 165 | }] 166 | }, 167 | ] 168 | }) 169 | .then(function (conn) { 170 | // All tables and indexes have been created 171 | }); 172 | ``` 173 | ### Instantiating a database with 1 table with a different primaryKey, soft durability, 2 replicas, and 2 shards 174 | 175 | You can pass a `primaryKey`, `durability`, `replicas`, or `shards` attribute to a table and it will be passed along to the [`tableCreate`](http://rethinkdb.com/api/javascript/table_create/) function. 176 | 177 | 178 | ```javascript 179 | var r = require('rethinkdb'); 180 | require('rethinkdb-init')(r); 181 | 182 | r.init({ 183 | host: 'localhost', 184 | port: 28015, 185 | db: 'helloDatabase' 186 | }, 187 | [ 188 | { 189 | name: 'helloTable', 190 | primaryKey: 'location', 191 | durability: 'soft' 192 | replicas: 2, 193 | shards: 2 194 | }, 195 | ] 196 | }) 197 | .then(function (conn) { 198 | // All tables and indexes have been created 199 | }); 200 | ``` 201 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var q = require('q') 3 | var _ = require('lodash') 4 | 5 | var rethinkdbInit = function (r) { 6 | // Is there a sane way of checking that this is a RethinkDB instance without importing the module? 7 | if (typeof r !== 'function') throw new TypeError('r must be a RethinkDB instance. Passed instances is a `' + (typeof r) + '`') 8 | 9 | /** 10 | * Returns true only if object is not `null` and is not an array 11 | * 12 | * @param {Any} 13 | * @return {Boolean} 14 | */ 15 | var isRealObject = function (value) { 16 | if (typeof value === 'object' && value !== null && !Array.isArray(value)) return true 17 | return false 18 | } 19 | 20 | /** 21 | * Throws an error if the error given is not an `already exists` error 22 | * 23 | * @param 24 | */ 25 | var existsHandler = function (err) { 26 | if (err.name === 'ReqlOpFailedError' && (err.msg || err.message).indexOf('already exists')) return 27 | if (err.name === 'RqlRuntimeError' && (err.msg || err.message).indexOf('already exists')) return 28 | throw err 29 | } 30 | 31 | /** 32 | * Maps a table object to an array of promises to create indexes for that table 33 | * 34 | * @param {Object} table - Table object 35 | * @param {Object} table.name - Name of table 36 | * @param {Array} table.indexes - Array of index 37 | * @param {Array} table.indexes - Array of strings with index names 38 | * @param {Array} table.indexes - Array of objects with index `name`, `indexFunction`, `mutli`, `geo` 39 | * @param {String} db - Database name 40 | * @param {Object} conn - RethinkDB connection 41 | * @return {Array} 42 | */ 43 | var mapIndexes = function (table, db, conn) { 44 | return table.indexes.map(function (index) { 45 | if (typeof index !== 'object' && typeof index !== 'string') throw new TypeError('index entry in table entry must be `Object` or `String`') 46 | if (typeof index === 'string') return r.db(db).table(table.name).indexCreate(index).run(conn).catch(existsHandler) 47 | if (index.name === undefined) throw new TypeError('index entry object in table schema must have a `name` property') 48 | var opts = [] 49 | if (index.indexFunction) opts.push(index.indexFunction) 50 | if (index.multi || index.geo) opts.push(_.pick(index, ['multi', 'geo'])) 51 | 52 | return r.db(db).table(table.name) 53 | .indexCreate(index.name, opts[0], opts[1]) 54 | .run(conn) 55 | .catch(existsHandler) 56 | }) 57 | } 58 | 59 | /** 60 | * Maps a schema object to an array of promises to create tables 61 | * 62 | * @param {Object} schema - Schema for table creation 63 | * @param {String} schema.table - Name of the table 64 | * @param {Object} schema.table - Object describing table 65 | * @param {String} schema.table.name - Name of the table 66 | * @param {Array} schema.table.indexes - Array of indexes 67 | * @param {String} db - Database name 68 | * @param {Object} conn - Database connection 69 | * @return {Array} 70 | */ 71 | var mapTables = function (schema, db, conn) { 72 | return schema.map(function (table) { 73 | if (!isRealObject(table) && typeof table !== 'string') { 74 | throw new TypeError('table entry in schema must be `Object` or `String`') 75 | } 76 | if (typeof table === 'string') { 77 | return r.db(db).tableCreate(table).run(conn).catch(existsHandler) 78 | } 79 | if (table.name === undefined) throw new TypeError('table entry object in schema must have a `name` property') 80 | var options = _.pick(table, ['primaryKey', 'durability', 'shards', 'replicas', 'primaryReplicaTag']) 81 | return r.db(db).tableCreate(table.name, options).run(conn) 82 | .catch(existsHandler) 83 | .then(function () { 84 | // Create indexes 85 | if (table.indexes === undefined) return true 86 | if (!Array.isArray(table.indexes)) throw new TypeError('Table indexes attribute should be an Array.') 87 | return q.all(mapIndexes(table, db, conn)) 88 | }) 89 | .then(function () { 90 | return r.db(db).table(table.name).indexWait().run(conn) 91 | }) 92 | }) 93 | } 94 | 95 | /** 96 | * Create databases, tables, and indexes as defined by the schema 97 | * 98 | * @param {Object} connection - Connection details for database (See: https://rethinkdb.com/api/javascript/connect/) 99 | * @param {String} connection.db - Database name 100 | * @param {String} connection.host - Database host 101 | * @param {String} connection.port - Database port 102 | * @param {Object} schema - Schema for table creation 103 | * @param {String} schema.table - Name of the table 104 | * @param {Object} schema.table - Object describing table 105 | * @param {String} schema.table.name - Name of the table 106 | * @param {Array} schema.table.indexes - Array of indexes 107 | * @return {Promise} 108 | */ 109 | var init = function (connection, schema) { 110 | // Connection must be an object and have a db, host, and port 111 | return q().then(function () { 112 | if (!isRealObject(connection)) throw new TypeError('Connection object must be an object.') 113 | if (typeof connection.db !== 'string') throw new TypeError('Connection object must have a db property. rethinkdb-init won\'t add tables to the `test` database unless explicitly declared') 114 | if (!Array.isArray(schema)) throw new TypeError('Schema argument must be an array.') 115 | var db = connection.db 116 | return r.connect(connection) 117 | .then(function (conn) { 118 | return r.dbCreate(db).run(conn) 119 | .catch(existsHandler) 120 | .then(function () { 121 | // Take an array of tables and create all tables 122 | // Create all indexes 123 | return q.all(mapTables(schema, db, conn)) 124 | }) 125 | .return(conn) 126 | }) 127 | }) 128 | } 129 | 130 | // Attach `init` function to RethinkDB instance 131 | r.init = init 132 | // Return init function 133 | return init 134 | } 135 | 136 | module.exports = rethinkdbInit 137 | -------------------------------------------------------------------------------- /module-design.md: -------------------------------------------------------------------------------- 1 | # RethinkDB Init 2 | 3 | A RethinkDB driver plugin to bootstrap all databases, tables and indexes on init. 4 | 5 | ## Basic Idea 6 | 7 | ```javascript 8 | var r = require(‘rethinkdb-init’)(require(‘rethinkdb’)); 9 | 10 | r.init({ 11 | host: ‘localhost’, 12 | port: 28015, 13 | db: ‘helloDatabase’ 14 | }, [{ 15 | name: ‘helloTable’, 16 | indexes: [‘superIndex’] 17 | }] 18 | }); 19 | ``` 20 | 21 | ## Questions? 22 | 23 | 1. `rethinkdb-init` or `rethinkdb-bootstrap`? 24 | 2. Append to prototype or provide a function? 25 | 3. Take in a two arguments (a connection and a schema) or one big object? 26 | 4. What should be the right name for what you’re passing? It’s not really a schema? 27 | 5. What should the callback/promise return? should it return a connection? 28 | 6. Should it add the promises to the object? I don’t think so! 29 | 7. Add seed data variables so that it adds data automatically! 30 | 31 | ## Question #1 32 | 33 | ```javascript 34 | require(‘rethinkdb-init’); 35 | ``` 36 | or 37 | 38 | ```javascript 39 | require(‘rethinkdb-bootstrap’); 40 | ``` 41 | 42 | ```javascript 43 | require('rethinkdb-seed'); 44 | ``` 45 | 46 | ``` 47 | require('rethinkdb-quickstart'); 48 | ``` 49 | 50 | ## Question #2 (Resolved #4) 51 | 52 | Provide a function 53 | ```javascript 54 | // #1 55 | var r = require(‘rethinkdb’); 56 | var rInit = require(‘rethinkdb-init’); 57 | ``` 58 | or 59 | Decorator that returns nothing 60 | ```javascript 61 | // #2 62 | var r = require(‘rethinkdb’); 63 | require(‘rethinkdb-init’)(r); 64 | ``` 65 | or 66 | Decorator that returns r instance 67 | ```javascript 68 | // #3 69 | var r = require(‘rethinkdb’); 70 | var r = require(‘rethinkdb-init’)(r); 71 | ``` 72 | or 73 | Decorator that returns r function 74 | ```javascript 75 | // #4 76 | var r = require(‘rethinkdb’); 77 | var rInit = require(‘rethinkdb-init’)(r); 78 | 79 | r.init() 80 | ``` 81 | 82 | ## Question #3 (Resolved #1) 83 | 84 | Should it take one argument for the connection and another for the schema? 85 | 86 | ``` 87 | // #1 88 | r.init({ 89 | host: ‘localhost’, 90 | port: 28015, 91 | db: ‘sharejs’ 92 | }, [{ 93 | name: ‘helloDatabase’, 94 | tables: [‘helloTable’] 95 | }] 96 | ); 97 | ``` 98 | or 99 | ``` 100 | // #2 101 | r.init({ 102 | conneciton: { 103 | host: ‘localhost’, 104 | port: 28015, 105 | db: ‘sharejs’ 106 | }, 107 | databases: [{ 108 | name: ‘helloDatabase’, 109 | tables: [‘helloTable’] 110 | }] 111 | }); 112 | ``` 113 | ## Question #4 114 | 115 | 1. schema 116 | 2. blueprint 117 | 3. seed 118 | 119 | ## Question #5 (Resolved #1) 120 | 121 | What should the init promise return? 122 | 123 | ``` 124 | // #1 125 | r.init({ 126 | host: ‘localhost’, 127 | port: 28015, 128 | db: ‘sharejs’ 129 | }, [{ 130 | name: ‘helloDatabase’, 131 | tables: [‘helloTable’] 132 | }] 133 | }).then(function (conn) { 134 | // Store it for later 135 | r.conn = conn; 136 | }); 137 | ``` 138 | ## Question #6 (Resolved #1) 139 | 140 | ``` 141 | // #1 142 | var promise = r.init(config, { }); 143 | 144 | promise.then(function (conn) { 145 | // Do something 146 | }); 147 | ``` 148 | or 149 | ``` 150 | // #2 151 | r.init(config, { }); 152 | 153 | // Automatically add promise 154 | r.ready.then(function (conn) { 155 | // Do Something 156 | }); 157 | ``` 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rethinkdb-init", 3 | "version": "0.2.2", 4 | "description": "Create all RethinkDB databases, tables and indexes automatically through a schema object", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/mocha/bin/mocha --bail -t 5000 --slow 0 ./test" 8 | }, 9 | "keywords": [ 10 | "rethinkdb", 11 | "database", 12 | "bootstrap", 13 | "init" 14 | ], 15 | "author": "thejsj", 16 | "license": "MIT", 17 | "dependencies": { 18 | "lodash": "^3.7.0", 19 | "q": "^1.2.1" 20 | }, 21 | "devDependencies": { 22 | "mocha": "^2.2.4", 23 | "rethinkdb": "^2.0.0", 24 | "rethinkdbdash": "^2.2.17", 25 | "should": "^6.0.1", 26 | "standard": "^7.1.2" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/thejsj/rethinkdb-init" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /*global describe:true */ 2 | 'use strict' 3 | require('should') 4 | var NODE_VERSION = process.env.TRAVIS_NODE_VERSION || false 5 | var suite = require('./suite') 6 | 7 | var connectionOpts = { 8 | host: 'localhost', 9 | port: 28015, 10 | db: 'rethinkdb_init_test' 11 | } 12 | 13 | describe('Driver - rethinkdb', function () { 14 | suite(require('rethinkdb'), connectionOpts) 15 | }) 16 | 17 | if (NODE_VERSION && NODE_VERSION[0] !== '0') { // 0.10.38 - 0.12.* 18 | describe('Driver - rethinkdbdash', function () { 19 | suite(require('rethinkdbdash')({ pool: false }), connectionOpts) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | /*global describe:true, it:true, before:true, beforeEach:true */ 2 | 'use strict' 3 | require('should') 4 | var _ = require('lodash') 5 | 6 | module.exports = function (r, connectionOpts) { 7 | var init = require('../')(r) 8 | 9 | var dropDatabase = function (done) { 10 | r.connect(connectionOpts) 11 | .then(function (conn) { 12 | r.dbDrop(connectionOpts.db).run(conn) 13 | .catch(function () { }) 14 | .then(done.bind(null, null)) 15 | }) 16 | } 17 | 18 | describe('Property', function () { 19 | it('should return a function', function () { 20 | init.should.be.a.Function 21 | }) 22 | 23 | it('should have an init property', function () { 24 | r.should.have.property('init') 25 | }) 26 | }) 27 | 28 | describe('Create Database', function () { 29 | before(dropDatabase) 30 | 31 | it('should create a database when the database doesn\'t exist and is passed into a connection', function (done) { 32 | r.init(connectionOpts, []) 33 | .then(function (conn) { 34 | r 35 | .db('rethinkdb') 36 | .table('db_config') 37 | .filter({ name: 'rethinkdb_init_test' }) 38 | .count() 39 | .run(conn) 40 | .then(function (result) { 41 | result.should.equal(1) 42 | done() 43 | }) 44 | }) 45 | .catch(done) 46 | }) 47 | 48 | it('should not throw an error when a database has already been created and is connected to it', function (done) { 49 | r.init(connectionOpts, []) 50 | .then(function (conn) { 51 | done() 52 | }) 53 | .catch(done) 54 | }) 55 | }) 56 | 57 | describe('Create Tables', function () { 58 | var tables = ['table_1', 'table_2', 'table_3'] 59 | describe('Basic', function () { 60 | beforeEach(dropDatabase) 61 | 62 | it('should throw an error if something other than an object is passed as the first argument', function (done) { 63 | r.init('not-an-object', ['table1']) 64 | .then(done) 65 | .catch(function (err) { 66 | err.name.should.equal('TypeError') 67 | err.message.indexOf('object').should.not.equal(-1) 68 | done() 69 | }) 70 | }) 71 | 72 | it('should throw an error if something other than an array is passes as the second argument', function (done) { 73 | r.init(connectionOpts, 'table1') 74 | .then(done) 75 | .catch(function (err) { 76 | err.name.should.equal('TypeError') 77 | err.message.indexOf('array').should.not.equal(-1) 78 | done() 79 | }) 80 | }) 81 | 82 | it('should throw an error if something other than an a string or object is append to the tables array', function (done) { 83 | r.init(connectionOpts, [null, 1, 3]) 84 | .then(done) 85 | .catch(function (err) { 86 | err.name.should.equal('TypeError') 87 | err.message.toLowerCase().indexOf('string').should.not.equal(-1) 88 | }) 89 | .then(function () { 90 | // Test numbers 91 | return r.init(connectionOpts, [3]) 92 | .then(done) 93 | .catch(function (err) { 94 | err.name.should.equal('TypeError') 95 | err.message.toLowerCase().indexOf('object').should.not.equal(-1) 96 | done() 97 | }) 98 | }) 99 | }) 100 | 101 | it('should create tables with passed as strings to the init function', function (done) { 102 | r.init(connectionOpts, tables) 103 | .then(function (conn) { 104 | r 105 | .db(connectionOpts.db) 106 | .tableList() 107 | .run(conn) 108 | .then(function (result) { 109 | result.should.eql(tables) 110 | done() 111 | }) 112 | }) 113 | .catch(done) 114 | }) 115 | 116 | it('should create tables passed as strings or objects to the init function', function (done) { 117 | var tablesWithObjects = ['table_1', { name: 'table_2' }, { name: 'table_3' }] 118 | r.init(connectionOpts, tablesWithObjects) 119 | .then(function (conn) { 120 | r 121 | .db(connectionOpts.db) 122 | .tableList() 123 | .run(conn) 124 | .then(function (result) { 125 | result.should.eql(tables) 126 | done() 127 | }) 128 | }) 129 | .catch(done) 130 | }) 131 | 132 | it('should create tables passed as strings or objects to the init function with table options', function (done) { 133 | var tablesWithObjects = ['table_1', { name: 'table_2', primaryKey: 'location', durability: 'soft' }, { name: 'table_3', durability: 'soft' }] 134 | r.init(connectionOpts, tablesWithObjects) 135 | .then(function (conn) { 136 | r 137 | .db(connectionOpts.db) 138 | .tableList() 139 | .run(conn) 140 | .then(function (result) { 141 | result.should.eql(tables) 142 | return r 143 | .db('rethinkdb') 144 | .table('table_config') 145 | .filter({ name: 'table_2', db: connectionOpts.db }) 146 | .coerceTo('array').nth(0) 147 | .run(conn) 148 | }) 149 | .then(function (table2Result) { 150 | table2Result.primary_key.should.equal('location') 151 | table2Result.durability.should.equal('soft') 152 | return r 153 | .db('rethinkdb') 154 | .table('table_config') 155 | .filter({ name: 'table_3', db: connectionOpts.db }) 156 | .coerceTo('array').nth(0) 157 | .run(conn) 158 | }) 159 | .then(function (table3Result) { 160 | table3Result.durability.should.equal('soft') 161 | done() 162 | }) 163 | }) 164 | .catch(done) 165 | }) 166 | }) 167 | 168 | describe('Error Handling', function () { 169 | it('should not throw an error when a table that is not already created is passed again', function (done) { 170 | r.init(connectionOpts, ['table_2']) 171 | .then(function (conn) { 172 | r 173 | .db(connectionOpts.db) 174 | .tableList() 175 | .run(conn) 176 | .then(function (result) { 177 | result.should.eql(tables) 178 | done() 179 | }) 180 | }) 181 | .catch(done) 182 | }) 183 | }) 184 | }) 185 | 186 | describe('Create Tables with Indexes', function () { 187 | describe('Basic', function () { 188 | beforeEach(dropDatabase) 189 | 190 | it('should throw an error if something other than an array is passes to the `indexes` property', function (done) { 191 | var tablesWithObjects = { name: 'table_2', indexes: 'not-an-array' } 192 | r.init(connectionOpts, tablesWithObjects) 193 | .then(done) 194 | .catch(function (err) { 195 | err.name.should.equal('TypeError') 196 | err.message.toLowerCase().indexOf('array').should.not.equal(-1) 197 | done() 198 | }) 199 | }) 200 | 201 | it('should throw an error if something other than a string or object is appended to the `indexes` property array', function (done) { 202 | var tablesWithObjects = [{ name: 'table_2', indexes: 'not-an-array' }] 203 | r.init(connectionOpts, tablesWithObjects) 204 | .then(done) 205 | .catch(function (err) { 206 | err.name.should.equal('TypeError') 207 | err.message.toLowerCase().indexOf('array').should.not.equal(-1) 208 | done() 209 | }) 210 | }) 211 | 212 | it('should create indexes passed as strings', function (done) { 213 | this.timeout(15000) 214 | var indexes = [ 'index1', 'index2' ] 215 | var tablesWithObjects = [ 216 | { name: 'table_2', indexes: indexes } 217 | ] 218 | r.init(connectionOpts, tablesWithObjects) 219 | .then(function (conn) { 220 | r 221 | .db(connectionOpts.db) 222 | .table('table_2') 223 | .indexList() 224 | .run(conn) 225 | .then(function (indexesResult) { 226 | indexes.should.eql(indexesResult) 227 | done() 228 | }) 229 | .catch(done) 230 | }) 231 | }) 232 | 233 | it('should create indexes passed as strings or objects', function (done) { 234 | this.timeout(15000) 235 | var indexes = [{ name: 'index3' }, { name: 'index4' }] 236 | var tablesWithObjects = [{ name: 'table_2', indexes: indexes }] 237 | r.init(connectionOpts, tablesWithObjects) 238 | .then(function (conn) { 239 | r 240 | .db(connectionOpts.db) 241 | .table('table_2') 242 | .indexList() 243 | .run(conn) 244 | .then(function (indexesResult) { 245 | indexesResult.should.eql([ 'index3', 'index4' ]) 246 | done() 247 | }) 248 | .catch(done) 249 | }) 250 | }) 251 | 252 | it('should create geo indexes', function (done) { 253 | this.timeout(15000) 254 | var indexes = [{ name: 'location', geo: true }] 255 | var tablesWithObjects = [{ name: 'table_9', indexes: indexes }] 256 | 257 | r.init(connectionOpts, tablesWithObjects) 258 | .then(function (conn) { 259 | return r 260 | .db(connectionOpts.db) 261 | .table('table_9') 262 | .indexList() 263 | .run(conn) 264 | .then(function (indexesResult) { 265 | indexesResult.should.eql(['location']) 266 | }) 267 | .then(function () { 268 | return r.db(connectionOpts.db).table('table_9') 269 | .insert([ 270 | { name: 'carlos', location: r.point(-122, 35.0001) }, 271 | { name: 'jorge', location: r.point(-122, 35.0002) }, 272 | { name: 'peter', location: r.point(-122, 35.0003) }, 273 | { name: 'john', location: r.point(-122, 35.0004) }, 274 | { name: 'matt', location: r.point(-122, 35.0005) } 275 | ]) 276 | .run(conn) 277 | }) 278 | .then(function () { 279 | return r.db(connectionOpts.db).table('table_9') 280 | .getNearest(r.point(-122, 35), { index: 'location' }) 281 | .coerceTo('array') 282 | .map(r.row('doc')) 283 | .run(conn) 284 | .then(function (result) { 285 | var names = _.pluck(result, 'name') 286 | names.should.eql(['carlos', 'jorge', 'peter', 'john', 'matt']) 287 | done() 288 | }) 289 | }) 290 | .catch(done) 291 | }) 292 | }) 293 | 294 | it('should create multi indexes', function (done) { 295 | this.timeout(15000) 296 | var indexes = [{ name: 'vals', multi: true }] 297 | var tablesWithObjects = [{ name: 'table_2', indexes: indexes }] 298 | r.init(connectionOpts, tablesWithObjects) 299 | .then(function (conn) { 300 | r 301 | .db(connectionOpts.db) 302 | .table('table_2') 303 | .indexList() 304 | .run(conn) 305 | .then(function (indexesResult) { 306 | indexesResult.should.eql(['vals']) 307 | }) 308 | .then(function () { 309 | return r.db(connectionOpts.db).table('table_2') 310 | .insert([ 311 | { name: 'jorge', vals: [1, 2, 3] }, 312 | { name: 'carlos', vals: [1, 5, 11] }, 313 | { name: 'peter', vals: [4, 5, 6] }, 314 | { name: 'john', vals: [4, 5, 6, 7] }, 315 | { name: 'matt', vals: 4 } 316 | ]) 317 | .run(conn) 318 | }) 319 | .then(function () { 320 | return r.db(connectionOpts.db).table('table_2') 321 | .getAll(1, { index: 'vals' }) 322 | .coerceTo('array') 323 | .run(conn) 324 | .then(function (result) { 325 | var names = _.pluck(result, 'name') 326 | names.sort().should.eql(['jorge', 'carlos'].sort()) 327 | }) 328 | }) 329 | .then(function () { 330 | return r.db(connectionOpts.db).table('table_2') 331 | .getAll(4, { index: 'vals' }) 332 | .coerceTo('array') 333 | .run(conn) 334 | .then(function (result) { 335 | var names = _.pluck(result, 'name') 336 | names.sort().should.eql(['peter', 'john', 'matt'].sort()) 337 | done() 338 | }) 339 | }) 340 | .catch(done) 341 | }) 342 | }) 343 | 344 | it('should create function indexes', function (done) { 345 | this.timeout(15000) 346 | var indexes = [ 347 | { name: 'index6', indexFunction: function (row) { return row('a').add(row('b')) } } 348 | ] 349 | var tablesWithObjects = [ 350 | { name: 'table_2', indexes: indexes } 351 | ] 352 | r.init(connectionOpts, tablesWithObjects) 353 | .then(function (conn) { 354 | r 355 | .db(connectionOpts.db) 356 | .table('table_2') 357 | .indexList() 358 | .run(conn) 359 | .then(function (indexesResult) { 360 | indexesResult.should.eql(['index6']) 361 | }) 362 | .then(function () { 363 | return r 364 | .db(connectionOpts.db).table('table_2') 365 | .insert([ 366 | { a: 0, b: 2, name: 'one' }, 367 | { a: 1, b: 5, name: 'three' }, 368 | { a: 3, b: 1, name: 'two' } 369 | ]) 370 | .run(conn) 371 | }) 372 | .then(function () { 373 | return r 374 | .db(connectionOpts.db).table('table_2') 375 | .orderBy({ 'index': r.desc('index6') }) 376 | .coerceTo('array') 377 | .run(conn) 378 | .then(function (result) { 379 | var names = _.pluck(result, 'name') 380 | names.should.eql(['three', 'two', 'one']) 381 | done() 382 | }) 383 | }) 384 | .catch(done) 385 | }) 386 | }) 387 | }) 388 | }) 389 | } 390 | --------------------------------------------------------------------------------