├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── bin ├── dev-server.js ├── enable-couchdb-cors.sh ├── es3ify.js ├── run-couch-master-on-travis.sh ├── run-test.sh ├── test-browser.js ├── test-coverage.sh └── test-node.sh ├── bower.json ├── dist ├── pouchdb.find.js └── pouchdb.find.min.js ├── index.html ├── lib ├── abstract-mapreduce │ ├── create-view.js │ ├── index.js │ ├── taskqueue.js │ ├── upsert.js │ └── utils.js ├── adapters │ ├── http │ │ └── index.js │ └── local │ │ ├── abstract-mapper.js │ │ ├── create-index │ │ └── index.js │ │ ├── delete-index │ │ └── index.js │ │ ├── find │ │ ├── in-memory-filter.js │ │ ├── index.js │ │ └── query-planner.js │ │ ├── get-indexes │ │ └── index.js │ │ ├── index.js │ │ └── utils.js ├── deps │ ├── blob.js │ ├── errors.js │ └── parse-uri.js ├── index.js ├── massageCreateIndexRequest.js └── utils.js ├── package.json ├── scripts └── generate_kitchen_sink.py ├── test ├── bind-polyfill.js ├── index.html ├── test-abstract-mapreduce │ ├── evalfunc.js │ ├── index.js │ ├── mapreduce.js │ ├── test.custom.js │ ├── test.mapreduce.js │ ├── test.persisted.js │ └── utils.js ├── test-suite-1 │ ├── index.js │ ├── test.and.js │ ├── test.array.js │ ├── test.basic.js │ ├── test.basic2.js │ ├── test.basic3.js │ ├── test.callbacks.js │ ├── test.combinational.js │ ├── test.ddoc.js │ ├── test.deep-fields.js │ ├── test.default-index.js │ ├── test.elem-match.js │ ├── test.eq.js │ ├── test.errors.js │ ├── test.exists.js │ ├── test.fields.js │ ├── test.issue66.js │ ├── test.limit-skip.js │ ├── test.limit.js │ ├── test.ltgt.js │ ├── test.matching-indexes.js │ ├── test.mod.js │ ├── test.ne.js │ ├── test.not.js │ ├── test.pick-fields.js │ ├── test.regex.js │ ├── test.set-operations.js │ ├── test.skip.js │ ├── test.sorting.js │ └── test.type.js ├── test-suite-2 │ ├── index.js │ ├── test.kitchen-sink-2.js │ └── test.kitchen-sink.js ├── test-utils.js ├── test.js └── webrunner.js └── www ├── ace ├── ace.js ├── mode-javascript.js ├── theme-xcode.js └── worker-javascript.js ├── app.js ├── bootstrap ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ ├── bootstrap.css.map │ └── bootstrap.min.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── npm.js ├── handlebars ├── handlebars-v2.0.0.js └── handlebars.runtime-v2.0.0.js ├── jquery └── jquery.min.js ├── pouchdb-3.2.2-prerelease.js ├── ribbon.png ├── smashbros.css └── smashers.png /.eslintignore: -------------------------------------------------------------------------------- 1 | test/test-bundle.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | 9 | "env": { 10 | "browser": true, 11 | "node": true, 12 | "mocha": true 13 | }, 14 | 15 | "globals": { 16 | "Map": true, 17 | "Set": true, 18 | "chrome": true, 19 | "Promise": true, 20 | "Uint8Array": true, 21 | "ArrayBuffer": true, 22 | "FileReaderSync": true, 23 | "sqlitePlugin": true, 24 | "emit": true, 25 | "PouchDB": true, 26 | "should": true, 27 | "assert": true, 28 | "testUtils": true, 29 | "importScripts": true 30 | }, 31 | 32 | "rules": { 33 | "no-empty": 0, 34 | "no-console": 0, 35 | "semi": ["error", "always"], 36 | "curly": ["error", "all"], 37 | "space-unary-ops": ["error", {"words": true}], 38 | "space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *~ 4 | coverage 5 | test/test-bundle.js 6 | npm-debug.log 7 | test/.cloudant-password.js 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | node_modules 3 | .DS_Store 4 | *~ 5 | coverage 6 | npm-debug.log 7 | vendor/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "6" 5 | 6 | services: 7 | - docker 8 | 9 | sudo: 10 | false 11 | 12 | addons: 13 | firefox: "41.0.1" 14 | 15 | before_install: 16 | # Install CouchDB Master 17 | - docker run -d -p 5984:5984 klaemo/couchdb:2.0-dev --with-haproxy --with-admin-party-please -n 1 18 | - npm install add-cors-to-couchdb 19 | - "while [ '200' != $(curl -s -o /dev/null -w \"%{http_code}\" 127.0.0.1:5984) ]; do echo waiting for couch to load... ; sleep 1; done" 20 | - ./node_modules/.bin/add-cors-to-couchdb 21 | 22 | script: npm run $COMMAND 23 | 24 | before_script: 25 | - "export DISPLAY=:99.0" 26 | - "sh -e /etc/init.d/xvfb start" 27 | 28 | env: 29 | matrix: 30 | - COMMAND=test 31 | - CLIENT=selenium:firefox COMMAND=test 32 | - CLIENT=selenium:phantomjs COMMAND=test 33 | - COMMAND=report-coverage 34 | 35 | branches: 36 | only: 37 | - master 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to contribute to pouchdb-find 2 | ======= 3 | 4 | CouchDB setup 5 | ------- 6 | 7 | For the HTTP tests you need the master branch of CouchDB running. See the CouchDB [Readme](https://github.com/apache/couchdb) for instructions on running. 8 | Then setup a 1 node cluster for testing - `./dev/run --with-admin-party-please --with-haproxy -n 1`. 9 | You will also need to enable cors on that node by running `./bin/enable-couchdb-cors.sh`. 10 | 11 | Building 12 | ---- 13 | npm install 14 | npm run build 15 | 16 | Your plugin is now located at `dist/pouchdb.find.js` and `dist/pouchdb.find.min.js` and is ready for distribution. 17 | 18 | Testing 19 | ---- 20 | 21 | ### In Node 22 | 23 | This will run the tests in Node using LevelDB: 24 | 25 | npm test 26 | 27 | You can specify an alternate CouchDB server other than `http://localhost:5984`: 28 | 29 | COUCH_HOST=http://localhost:6984 npm test 30 | 31 | You can also check for 100% code coverage using: 32 | 33 | npm run coverage 34 | 35 | You can filter the tests by running: 36 | 37 | GREP=mysearch npm run test-node 38 | 39 | ### In the browser 40 | 41 | Run `npm run dev` and then point your favorite browser to [http://127.0.0.1:8001/test/index.html](http://127.0.0.1:8001/test/index.html). 42 | 43 | The query param `?grep=mysearch` will search for tests matching `mysearch`. The query param `couchHost=http://localhost:6984` will use a custom CouchDB server. 44 | 45 | ### Automated browser tests 46 | 47 | You can run e.g. 48 | 49 | CLIENT=selenium:firefox npm test 50 | CLIENT=selenium:phantomjs npm test 51 | 52 | This will run the tests automatically and the process will exit with a 0 or a 1 when it's done. Firefox uses IndexedDB, and PhantomJS uses WebSQL. `COUCH_HOST` works here as well. 53 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ⚠️⚠️⚠️ PLEASE DON'T OPEN ISSUES HERE! ⚠️⚠️⚠️ 2 | ======= 3 | 4 | This repo has been moved to http://github.com/pouchdb/pouchdb/ 5 | 6 | Please open issues there instead! 😊 7 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ⚠️⚠️⚠️ PLEASE DON'T OPEN PULL REQUESTS HERE! ⚠️⚠️⚠️ 2 | ======= 3 | 4 | This repo has been moved to http://github.com/pouchdb/pouchdb/ 5 | 6 | Please open issues there instead! 😊 7 | -------------------------------------------------------------------------------- /bin/dev-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var HTTP_PORT = 8001; 6 | 7 | var http_server = require("http-server"); 8 | var fs = require('fs'); 9 | var indexfile = "./test/test.js"; 10 | var dotfile = "./test/.test-bundle.js"; 11 | var outfile = "./test/test-bundle.js"; 12 | var watchify = require("watchify"); 13 | var browserify = require('browserify'); 14 | var w = watchify(browserify(indexfile, { 15 | cache: {}, 16 | packageCache: {}, 17 | fullPaths: true, 18 | debug: true 19 | })); 20 | 21 | w.on('update', bundle); 22 | bundle(); 23 | 24 | var filesWritten = false; 25 | var serverStarted = false; 26 | var readyCallback; 27 | 28 | function bundle() { 29 | var wb = w.bundle(); 30 | wb.on('error', function (err) { 31 | console.error(String(err)); 32 | }); 33 | wb.on("end", end); 34 | wb.pipe(fs.createWriteStream(dotfile)); 35 | 36 | function end() { 37 | fs.rename(dotfile, outfile, function (err) { 38 | if (err) { return console.error(err); } 39 | console.log('Updated:', outfile); 40 | filesWritten = true; 41 | checkReady(); 42 | }); 43 | } 44 | } 45 | 46 | function startServers(callback) { 47 | readyCallback = callback; 48 | http_server.createServer().listen(HTTP_PORT); 49 | var msg = 'Tests: http://127.0.0.1:' + HTTP_PORT + '/test/index.html'; 50 | if (process.env.COUCH_HOST) { 51 | msg += '?couchHost=' + process.env.COUCH_HOST; 52 | } 53 | console.log(msg); 54 | serverStarted = true; 55 | checkReady(); 56 | } 57 | 58 | function checkReady() { 59 | if (filesWritten && serverStarted && readyCallback) { 60 | readyCallback(); 61 | } 62 | } 63 | 64 | if (require.main === module) { 65 | startServers(); 66 | } else { 67 | module.exports.start = startServers; 68 | } 69 | -------------------------------------------------------------------------------- /bin/enable-couchdb-cors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -X PUT http://127.0.0.1:15984/_node/node1@127.0.0.1/_config/httpd/enable_cors -d '"true"' 4 | curl -X PUT http://127.0.0.1:15984/_node/node1@127.0.0.1/_config/cors/origins -d '"*"' 5 | curl -X PUT http://127.0.0.1:15984/_node/node1@127.0.0.1/_config/cors/credentials -d '"true"' 6 | curl -X PUT http://127.0.0.1:15984/_node/node1@127.0.0.1/_config/cors/methods -d '"GET, PUT, POST, HEAD, DELETE"' 7 | curl -X PUT http://127.0.0.1:15984/_node/node1@127.0.0.1/_config/cors/headers -d '"accept, authorization, content-type, origin, referer, x-csrf-token"' 8 | -------------------------------------------------------------------------------- /bin/es3ify.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | var es3ify = require('es3ify'); 4 | return process.stdin.pipe(es3ify()).pipe(process.stdout); 5 | -------------------------------------------------------------------------------- /bin/run-couch-master-on-travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | CWD=$(pwd) 6 | 7 | sudo apt-get remove erlang-base erlang-crypto erlang-base-hipe 8 | sudo apt-get -y install haproxy default-jdk libwxgtk3.0 9 | wget http://packages.erlang-solutions.com/site/esl/esl-erlang/FLAVOUR_1_general/esl-erlang_18.1-1~ubuntu~precise_amd64.deb 10 | sudo dpkg -i esl-erlang_18.1-1~ubuntu~precise_amd64.deb 11 | 12 | # Sweet, build CouchDB 13 | git clone https://github.com/apache/couchdb.git ~/couchdb 14 | cd ~/couchdb 15 | ./configure --disable-docs --disable-fauxton 16 | make 17 | 18 | # All done, run a cluster 19 | python dev/run -n 1 --with-admin-party-please --with-haproxy & 20 | sleep 10 21 | 22 | cd $CWD 23 | -------------------------------------------------------------------------------- /bin/run-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | : ${CLIENT:="node"} 4 | 5 | if [ "$CLIENT" == "node" ]; then 6 | npm run test-node 7 | else 8 | npm run test-browser 9 | fi 10 | -------------------------------------------------------------------------------- /bin/test-browser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var wd = require('wd'); 5 | var sauceConnectLauncher = require('sauce-connect-launcher'); 6 | var selenium = require('selenium-standalone'); 7 | var querystring = require("querystring"); 8 | 9 | var devserver = require('./dev-server.js'); 10 | 11 | var testTimeout = 30 * 60 * 1000; 12 | 13 | var username = process.env.SAUCE_USERNAME; 14 | var accessKey = process.env.SAUCE_ACCESS_KEY; 15 | 16 | // process.env.CLIENT is a colon seperated list of 17 | // (saucelabs|selenium):browserName:browserVerion:platform 18 | var tmp = (process.env.CLIENT || 'selenium:firefox').split(':'); 19 | var client = { 20 | runner: tmp[0] || 'selenium', 21 | browser: tmp[1] || 'firefox', 22 | version: tmp[2] || null, // Latest 23 | platform: tmp[3] || null 24 | }; 25 | 26 | var testUrl = 'http://127.0.0.1:8001/test/index.html'; 27 | var qs = {}; 28 | 29 | var sauceClient; 30 | var sauceConnectProcess; 31 | var tunnelId = process.env.TRAVIS_JOB_NUMBER || 'tunnel-' + Date.now(); 32 | 33 | if (client.runner === 'saucelabs') { 34 | qs.saucelabs = true; 35 | } 36 | if (process.env.GREP) { 37 | qs.grep = process.env.GREP; 38 | } 39 | if (process.env.COUCH_HOST) { 40 | qs.couchHost = process.env.COUCH_HOST; 41 | } 42 | testUrl += '?'; 43 | testUrl += querystring.stringify(qs); 44 | 45 | if (process.env.TRAVIS && 46 | client.browser !== 'firefox' && 47 | client.browser !== 'phantomjs' && 48 | process.env.TRAVIS_SECURE_ENV_VARS === 'false') { 49 | console.error('Not running test, cannot connect to saucelabs'); 50 | process.exit(1); 51 | return; 52 | } 53 | 54 | function testError(e) { 55 | console.error(e); 56 | console.error('Doh, tests failed'); 57 | sauceClient.quit(); 58 | process.exit(3); 59 | } 60 | 61 | function postResult(result) { 62 | process.exit(!process.env.PERF && result.failed ? 1 : 0); 63 | } 64 | 65 | function testComplete(result) { 66 | console.log(result); 67 | 68 | sauceClient.quit().then(function () { 69 | if (sauceConnectProcess) { 70 | sauceConnectProcess.close(function () { 71 | postResult(result); 72 | }); 73 | } else { 74 | postResult(result); 75 | } 76 | }); 77 | } 78 | 79 | function startSelenium(callback) { 80 | // Start selenium 81 | var opts = {version: '2.45.0'}; 82 | selenium.install(opts, function(err) { 83 | if (err) { 84 | console.error('Failed to install selenium'); 85 | process.exit(1); 86 | } 87 | selenium.start(opts, function(err, server) { 88 | if (err) { 89 | console.error('Failed to start Selenium: ' + err.message); 90 | process.exit(1); 91 | } 92 | console.log('Started Selenium: PID %s', server.pid); 93 | sauceClient = wd.promiseChainRemote(); 94 | callback(); 95 | }); 96 | }); 97 | } 98 | 99 | function startSauceConnect(callback) { 100 | 101 | var options = { 102 | username: username, 103 | accessKey: accessKey, 104 | tunnelIdentifier: tunnelId 105 | }; 106 | 107 | sauceConnectLauncher(options, function (err, process) { 108 | if (err) { 109 | console.error('Failed to connect to saucelabs'); 110 | console.error(err); 111 | return process.exit(1); 112 | } 113 | sauceConnectProcess = process; 114 | sauceClient = wd.promiseChainRemote("localhost", 4445, username, accessKey); 115 | callback(); 116 | }); 117 | } 118 | 119 | function startTest() { 120 | 121 | console.log('Starting', client); 122 | 123 | var opts = { 124 | browserName: client.browser, 125 | version: client.version, 126 | platform: client.platform, 127 | tunnelTimeout: testTimeout, 128 | name: client.browser + ' - ' + tunnelId, 129 | 'max-duration': 60 * 30, 130 | 'command-timeout': 599, 131 | 'idle-timeout': 599, 132 | 'tunnel-identifier': tunnelId 133 | }; 134 | 135 | sauceClient.init(opts).get(testUrl, function () { 136 | 137 | var interval = setInterval(function () { 138 | sauceClient.eval('window.results', function (err, results) { 139 | if (err) { 140 | clearInterval(interval); 141 | testError(err); 142 | } else if (results.completed || results.failures.length) { 143 | clearInterval(interval); 144 | testComplete(results); 145 | } else { 146 | console.log('=> ', results); 147 | } 148 | }); 149 | }, 10 * 1000); 150 | }); 151 | } 152 | 153 | devserver.start(function () { 154 | if (client.runner === 'saucelabs') { 155 | startSauceConnect(startTest); 156 | } else { 157 | startSelenium(startTest); 158 | } 159 | }); 160 | -------------------------------------------------------------------------------- /bin/test-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | COVERAGE=1 npm test 4 | -------------------------------------------------------------------------------- /bin/test-node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! $COVERAGE ]; then 4 | ./node_modules/.bin/mocha \ 5 | --grep=$GREP \ 6 | test/test.js 7 | else 8 | ./node_modules/.bin/istanbul cover \ 9 | ./node_modules/mocha/bin/_mocha \ 10 | test/test.js 11 | fi 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouchdb-find", 3 | "version": "0.10.0", 4 | "description": "Easy-to-use query language for PouchDB", 5 | "homepage": "https://github.com/nolanlawson/pouchdb-find", 6 | "authors": [ 7 | "Nolan Lawson " 8 | ], 9 | "main": "dist/pouchdb.find.min.js", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "keywords": [ 14 | "pouchdb", 15 | "find", 16 | "indexes", 17 | "couchdb", 18 | "plugin" 19 | ], 20 | "license": "Apache 2", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests", 27 | "vendor" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Learn you pouchdb-find 5 | 6 | 7 | 8 | 9 | 10 | 107 | 108 | 109 | 110 | 111 |

Learn you pouchdb-find

112 | 113 |
114 |

115 | pouchdb-find is an advanced query language for PouchDB. Now in beta! 116 |

117 |

118 | Inspired by MongoDB, it 119 | provides a simple API with operators like 120 | $gt (greater-than) and $eq (equals), so you can write less 121 | code to achieve the same performance as the map/reduce 122 | API. 123 |

124 |

125 | This API is also available as 126 | Cloudant Query Language 127 | and will be released in CouchDB 2.0. 128 |

129 |
130 |
131 |
132 |
133 |
134 | 137 |
138 |
139 | 142 |
143 |
144 | 147 |
148 |
149 | 152 |
153 |
154 | 157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | 168 |
169 |
170 |
171 |
172 |
173 |
174 | 
175 | 176 |
177 | 178 | Fork me on GitHub 182 | 183 |
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /lib/abstract-mapreduce/create-view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var upsert = require('./upsert'); 4 | var utils = require('./utils'); 5 | var Promise = utils.Promise; 6 | 7 | function stringify(input) { 8 | if (!input) { 9 | return 'undefined'; // backwards compat for empty reduce 10 | } 11 | // for backwards compat with mapreduce, functions/strings are stringified 12 | // as-is. everything else is JSON-stringified. 13 | switch (typeof input) { 14 | case 'function': 15 | // e.g. a mapreduce map 16 | return input.toString(); 17 | case 'string': 18 | // e.g. a mapreduce built-in _reduce function 19 | return input.toString(); 20 | default: 21 | // e.g. a JSON object in the case of mango queries 22 | return JSON.stringify(input); 23 | } 24 | } 25 | 26 | module.exports = function (opts) { 27 | var sourceDB = opts.db; 28 | var viewName = opts.viewName; 29 | var mapFun = opts.map; 30 | var reduceFun = opts.reduce; 31 | var temporary = opts.temporary; 32 | var pluginName = opts.pluginName; 33 | 34 | // the "undefined" part is for backwards compatibility 35 | var viewSignature = stringify(mapFun) + stringify(reduceFun) + 36 | 'undefined'; 37 | 38 | if (!temporary && sourceDB._cachedViews) { 39 | var cachedView = sourceDB._cachedViews[viewSignature]; 40 | if (cachedView) { 41 | return Promise.resolve(cachedView); 42 | } 43 | } 44 | 45 | return sourceDB.info().then(function (info) { 46 | 47 | var depDbName = info.db_name + '-mrview-' + 48 | (temporary ? 'temp' : utils.MD5(viewSignature)); 49 | 50 | // save the view name in the source PouchDB so it can be cleaned up if necessary 51 | // (e.g. when the _design doc is deleted, remove all associated view data) 52 | function diffFunction(doc) { 53 | doc.views = doc.views || {}; 54 | var fullViewName = viewName; 55 | if (fullViewName.indexOf('/') === -1) { 56 | fullViewName = viewName + '/' + viewName; 57 | } 58 | var depDbs = doc.views[fullViewName] = doc.views[fullViewName] || {}; 59 | /* istanbul ignore if */ 60 | if (depDbs[depDbName]) { 61 | return; // no update necessary 62 | } 63 | depDbs[depDbName] = true; 64 | return doc; 65 | } 66 | return upsert(sourceDB, '_local/' + pluginName, diffFunction).then(function () { 67 | return sourceDB.registerDependentDatabase(depDbName).then(function (res) { 68 | var db = res.db; 69 | db.auto_compaction = true; 70 | var view = { 71 | name: depDbName, 72 | db: db, 73 | sourceDB: sourceDB, 74 | adapter: sourceDB.adapter, 75 | mapFun: mapFun, 76 | reduceFun: reduceFun 77 | }; 78 | return view.db.get('_local/lastSeq').catch(function (err) { 79 | /* istanbul ignore if */ 80 | if (err.status !== 404) { 81 | throw err; 82 | } 83 | }).then(function (lastSeqDoc) { 84 | view.seq = lastSeqDoc ? lastSeqDoc.seq : 0; 85 | if (!temporary) { 86 | sourceDB._cachedViews = sourceDB._cachedViews || {}; 87 | sourceDB._cachedViews[viewSignature] = view; 88 | view.db.on('destroyed', function () { 89 | delete sourceDB._cachedViews[viewSignature]; 90 | }); 91 | } 92 | return view; 93 | }); 94 | }); 95 | }); 96 | }); 97 | }; 98 | -------------------------------------------------------------------------------- /lib/abstract-mapreduce/taskqueue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* 3 | * Simple task queue to sequentialize actions. Assumes callbacks will eventually fire (once). 4 | */ 5 | 6 | var Promise = require('./utils').Promise; 7 | 8 | function TaskQueue() { 9 | this.promise = new Promise(function (fulfill) {fulfill(); }); 10 | } 11 | TaskQueue.prototype.add = function (promiseFactory) { 12 | this.promise = this.promise.catch(function () { 13 | // just recover 14 | }).then(function () { 15 | return promiseFactory(); 16 | }); 17 | return this.promise; 18 | }; 19 | TaskQueue.prototype.finish = function () { 20 | return this.promise; 21 | }; 22 | 23 | module.exports = TaskQueue; 24 | -------------------------------------------------------------------------------- /lib/abstract-mapreduce/upsert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var upsert = require('pouchdb-upsert').upsert; 4 | 5 | module.exports = function (db, doc, diffFun) { 6 | return upsert.apply(db, [doc, diffFun]); 7 | }; -------------------------------------------------------------------------------- /lib/abstract-mapreduce/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* istanbul ignore if */ 3 | exports.Promise = require('pouchdb-promise'); 4 | 5 | exports.inherits = require('inherits'); 6 | exports.extend = require('pouchdb-extend'); 7 | var argsarray = require('argsarray'); 8 | 9 | /* istanbul ignore next */ 10 | exports.promisedCallback = function (promise, callback) { 11 | if (callback) { 12 | promise.then(function (res) { 13 | process.nextTick(function () { 14 | callback(null, res); 15 | }); 16 | }, function (reason) { 17 | process.nextTick(function () { 18 | callback(reason); 19 | }); 20 | }); 21 | } 22 | return promise; 23 | }; 24 | 25 | /* istanbul ignore next */ 26 | exports.callbackify = function (fun) { 27 | return argsarray(function (args) { 28 | var cb = args.pop(); 29 | var promise = fun.apply(this, args); 30 | if (typeof cb === 'function') { 31 | exports.promisedCallback(promise, cb); 32 | } 33 | return promise; 34 | }); 35 | }; 36 | 37 | // Promise finally util similar to Q.finally 38 | /* istanbul ignore next */ 39 | exports.fin = function (promise, cb) { 40 | return promise.then(function (res) { 41 | var promise2 = cb(); 42 | if (typeof promise2.then === 'function') { 43 | return promise2.then(function () { 44 | return res; 45 | }); 46 | } 47 | return res; 48 | }, function (reason) { 49 | var promise2 = cb(); 50 | if (typeof promise2.then === 'function') { 51 | return promise2.then(function () { 52 | throw reason; 53 | }); 54 | } 55 | throw reason; 56 | }); 57 | }; 58 | 59 | exports.sequentialize = function (queue, promiseFactory) { 60 | return function () { 61 | var args = arguments; 62 | var that = this; 63 | return queue.add(function () { 64 | return promiseFactory.apply(that, args); 65 | }); 66 | }; 67 | }; 68 | 69 | exports.flatten = function (arrs) { 70 | var res = []; 71 | for (var i = 0, len = arrs.length; i < len; i++) { 72 | res = res.concat(arrs[i]); 73 | } 74 | return res; 75 | }; 76 | 77 | // uniq an array of strings, order not guaranteed 78 | // similar to underscore/lodash _.uniq 79 | exports.uniq = function (arr) { 80 | var map = {}; 81 | 82 | for (var i = 0, len = arr.length; i < len; i++) { 83 | map['$' + arr[i]] = true; 84 | } 85 | 86 | var keys = Object.keys(map); 87 | var output = new Array(keys.length); 88 | 89 | for (i = 0, len = keys.length; i < len; i++) { 90 | output[i] = keys[i].substring(1); 91 | } 92 | return output; 93 | }; 94 | 95 | var crypto = require('crypto'); 96 | var Md5 = require('spark-md5'); 97 | 98 | exports.MD5 = function (string) { 99 | /* istanbul ignore else */ 100 | if (!process.browser) { 101 | return crypto.createHash('md5').update(string).digest('hex'); 102 | } else { 103 | return Md5.hash(string); 104 | } 105 | }; -------------------------------------------------------------------------------- /lib/adapters/http/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var massageCreateIndexRequest = require('../../massageCreateIndexRequest'); 4 | 5 | function createIndex(db, requestDef, callback) { 6 | requestDef = massageCreateIndexRequest(requestDef); 7 | 8 | db.request({ 9 | method: 'POST', 10 | url: '_index', 11 | body: requestDef 12 | }, callback); 13 | } 14 | 15 | function find(db, requestDef, callback) { 16 | db.request({ 17 | method: 'POST', 18 | url: '_find', 19 | body: requestDef 20 | }, callback); 21 | } 22 | 23 | function getIndexes(db, callback) { 24 | db.request({ 25 | method: 'GET', 26 | url: '_index' 27 | }, callback); 28 | } 29 | 30 | function deleteIndex(db, indexDef, callback) { 31 | 32 | 33 | var ddoc = indexDef.ddoc; 34 | var type = indexDef.type || 'json'; 35 | var name = indexDef.name; 36 | 37 | if (!ddoc) { 38 | return callback(new Error('you must provide an index\'s ddoc')); 39 | } 40 | 41 | if (!name) { 42 | return callback(new Error('you must provide an index\'s name')); 43 | } 44 | 45 | var url = '_index/' + [ddoc, type, name].map(encodeURIComponent).join('/'); 46 | 47 | db.request({ 48 | method: 'DELETE', 49 | url: url 50 | }, callback); 51 | } 52 | 53 | exports.createIndex = createIndex; 54 | exports.find = find; 55 | exports.getIndexes = getIndexes; 56 | exports.deleteIndex = deleteIndex; -------------------------------------------------------------------------------- /lib/adapters/local/abstract-mapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var localUtils = require('./utils'); 4 | var abstractMapReduce = require('../../abstract-mapreduce'); 5 | var parseField = localUtils.parseField; 6 | 7 | // 8 | // One thing about these mappers: 9 | // 10 | // Per the advice of John-David Dalton (http://youtu.be/NthmeLEhDDM), 11 | // what you want to do in this case is optimize for the smallest possible 12 | // function, since that's the thing that gets run over and over again. 13 | // 14 | // This code would be a lot simpler if all the if/elses were inside 15 | // the function, but it would also be a lot less performant. 16 | // 17 | 18 | 19 | function createDeepMultiMapper(fields, emit) { 20 | return function (doc) { 21 | var toEmit = []; 22 | for (var i = 0, iLen = fields.length; i < iLen; i++) { 23 | var parsedField = parseField(fields[i]); 24 | var value = doc; 25 | for (var j = 0, jLen = parsedField.length; j < jLen; j++) { 26 | var key = parsedField[j]; 27 | value = value[key]; 28 | if (!value) { 29 | break; 30 | } 31 | } 32 | toEmit.push(value); 33 | } 34 | emit(toEmit); 35 | }; 36 | } 37 | 38 | function createDeepSingleMapper(field, emit) { 39 | var parsedField = parseField(field); 40 | return function (doc) { 41 | var value = doc; 42 | for (var i = 0, len = parsedField.length; i < len; i++) { 43 | var key = parsedField[i]; 44 | value = value[key]; 45 | if (!value) { 46 | return; // do nothing 47 | } 48 | } 49 | emit(value); 50 | }; 51 | } 52 | 53 | function createShallowSingleMapper(field, emit) { 54 | return function (doc) { 55 | emit(doc[field]); 56 | }; 57 | } 58 | 59 | function createShallowMultiMapper(fields, emit) { 60 | return function (doc) { 61 | var toEmit = []; 62 | for (var i = 0, len = fields.length; i < len; i++) { 63 | toEmit.push(doc[fields[i]]); 64 | } 65 | emit(toEmit); 66 | }; 67 | } 68 | 69 | function checkShallow(fields) { 70 | for (var i = 0, len = fields.length; i < len; i++) { 71 | var field = fields[i]; 72 | if (field.indexOf('.') !== -1) { 73 | return false; 74 | } 75 | } 76 | return true; 77 | } 78 | 79 | function createMapper(fields, emit) { 80 | var isShallow = checkShallow(fields); 81 | var isSingle = fields.length === 1; 82 | 83 | // notice we try to optimize for the most common case, 84 | // i.e. single shallow indexes 85 | if (isShallow) { 86 | if (isSingle) { 87 | return createShallowSingleMapper(fields[0], emit); 88 | } else { // multi 89 | return createShallowMultiMapper(fields, emit); 90 | } 91 | } else { // deep 92 | if (isSingle) { 93 | return createDeepSingleMapper(fields[0], emit); 94 | } else { // multi 95 | return createDeepMultiMapper(fields, emit); 96 | } 97 | } 98 | } 99 | 100 | function mapper(mapFunDef, emit) { 101 | // mapFunDef is a list of fields 102 | 103 | var fields = Object.keys(mapFunDef.fields); 104 | 105 | return createMapper(fields, emit); 106 | } 107 | 108 | /* istanbul ignore next */ 109 | function reducer(/*reduceFunDef*/) { 110 | throw new Error('reduce not supported'); 111 | } 112 | 113 | function ddocValidator(ddoc, viewName) { 114 | var view = ddoc.views[viewName]; 115 | // This doesn't actually need to be here apparently, but 116 | // I feel safer keeping it. 117 | /* istanbul ignore if */ 118 | if (!view.map || !view.map.fields) { 119 | throw new Error('ddoc ' + ddoc._id +' with view ' + viewName + 120 | ' doesn\'t have map.fields defined. ' + 121 | 'maybe it wasn\'t created by this plugin?'); 122 | } 123 | } 124 | 125 | var abstractMapper = abstractMapReduce({ 126 | name: 'indexes', 127 | mapper: mapper, 128 | reducer: reducer, 129 | ddocValidator: ddocValidator 130 | }); 131 | 132 | module.exports = abstractMapper; -------------------------------------------------------------------------------- /lib/adapters/local/create-index/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../../utils'); 4 | var log = utils.log; 5 | 6 | var pouchUpsert = require('pouchdb-upsert'); 7 | var abstractMapper = require('../abstract-mapper'); 8 | var localUtils = require('../utils'); 9 | var validateIndex = localUtils.validateIndex; 10 | var massageIndexDef = localUtils.massageIndexDef; 11 | var massageCreateIndexRequest = require('../../../massageCreateIndexRequest'); 12 | 13 | function upsert(db, docId, diffFun) { 14 | return pouchUpsert.upsert.call(db, docId, diffFun); 15 | } 16 | 17 | function createIndex(db, requestDef) { 18 | requestDef = massageCreateIndexRequest(requestDef); 19 | var originalIndexDef = utils.clone(requestDef.index); 20 | requestDef.index = massageIndexDef(requestDef.index); 21 | 22 | validateIndex(requestDef.index); 23 | 24 | var md5 = utils.MD5(JSON.stringify(requestDef)); 25 | 26 | var viewName = requestDef.name || ('idx-' + md5); 27 | 28 | var ddocName = requestDef.ddoc || ('idx-' + md5); 29 | var ddocId = '_design/' + ddocName; 30 | 31 | var hasInvalidLanguage = false; 32 | var viewExists = false; 33 | 34 | function updateDdoc(doc) { 35 | if (doc._rev && doc.language !== 'query') { 36 | hasInvalidLanguage = true; 37 | } 38 | doc.language = 'query'; 39 | doc.views = doc.views || {}; 40 | 41 | viewExists = !!doc.views[viewName]; 42 | 43 | if (viewExists) { 44 | return false; 45 | } 46 | 47 | doc.views[viewName] = { 48 | map: { 49 | fields: utils.mergeObjects(requestDef.index.fields) 50 | }, 51 | reduce: '_count', 52 | options: { 53 | def: originalIndexDef 54 | } 55 | }; 56 | 57 | return doc; 58 | } 59 | 60 | log('creating index', ddocId); 61 | 62 | return upsert(db, ddocId, updateDdoc).then(function () { 63 | if (hasInvalidLanguage) { 64 | throw new Error('invalid language for ddoc with id "' + 65 | ddocId + 66 | '" (should be "query")'); 67 | } 68 | }).then(function () { 69 | // kick off a build 70 | // TODO: abstract-pouchdb-mapreduce should support auto-updating 71 | // TODO: should also use update_after, but pouchdb/pouchdb#3415 blocks me 72 | var signature = ddocName + '/' + viewName; 73 | return abstractMapper.query.call(db, signature, { 74 | limit: 0, 75 | reduce: false 76 | }).then(function () { 77 | return { 78 | id: ddocId, 79 | name: viewName, 80 | result: viewExists ? 'exists' : 'created' 81 | }; 82 | }); 83 | }); 84 | } 85 | 86 | module.exports = createIndex; 87 | -------------------------------------------------------------------------------- /lib/adapters/local/delete-index/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var abstractMapper = require('../abstract-mapper'); 4 | var upsert = require('../../../abstract-mapreduce/upsert'); 5 | 6 | function deleteIndex(db, index) { 7 | 8 | if (!index.ddoc) { 9 | throw new Error('you must supply an index.ddoc when deleting'); 10 | } 11 | 12 | if (!index.name) { 13 | throw new Error('you must supply an index.name when deleting'); 14 | } 15 | 16 | var docId = index.ddoc; 17 | var viewName = index.name; 18 | 19 | function deltaFun(doc) { 20 | if (Object.keys(doc.views).length === 1 && doc.views[viewName]) { 21 | // only one view in this ddoc, delete the whole ddoc 22 | return {_id: docId, _deleted: true}; 23 | } 24 | // more than one view here, just remove the view 25 | delete doc.views[viewName]; 26 | return doc; 27 | } 28 | 29 | return upsert(db, docId, deltaFun).then(function () { 30 | return abstractMapper.viewCleanup.apply(db); 31 | }).then(function () { 32 | return {ok: true}; 33 | }); 34 | } 35 | 36 | module.exports = deleteIndex; -------------------------------------------------------------------------------- /lib/adapters/local/find/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../../utils'); 4 | var clone = utils.clone; 5 | var getIndexes = require('../get-indexes'); 6 | var collate = require('pouchdb-collate').collate; 7 | var abstractMapper = require('../abstract-mapper'); 8 | var planQuery = require('./query-planner'); 9 | var localUtils = require('../utils'); 10 | var filterInMemoryFields = require('./in-memory-filter'); 11 | var massageSelector = localUtils.massageSelector; 12 | var massageSort = localUtils.massageSort; 13 | var getValue = localUtils.getValue; 14 | var validateFindRequest = localUtils.validateFindRequest; 15 | var validateSort = localUtils.validateSort; 16 | var reverseOptions = localUtils.reverseOptions; 17 | var filterInclusiveStart = localUtils.filterInclusiveStart; 18 | var Promise = utils.Promise; 19 | 20 | function indexToSignature(index) { 21 | // remove '_design/' 22 | return index.ddoc.substring(8) + '/' + index.name; 23 | } 24 | 25 | function doAllDocs(db, originalOpts) { 26 | var opts = clone(originalOpts); 27 | 28 | // CouchDB responds in weird ways when you provide a non-string to _id; 29 | // we mimic the behavior for consistency. See issue66 tests for details. 30 | 31 | if (opts.descending) { 32 | if ('endkey' in opts && typeof opts.endkey !== 'string') { 33 | opts.endkey = ''; 34 | } 35 | if ('startkey' in opts && typeof opts.startkey !== 'string') { 36 | opts.limit = 0; 37 | } 38 | } else { 39 | if ('startkey' in opts && typeof opts.startkey !== 'string') { 40 | opts.startkey = ''; 41 | } 42 | if ('endkey' in opts && typeof opts.endkey !== 'string') { 43 | opts.limit = 0; 44 | } 45 | } 46 | if ('key' in opts && typeof opts.key !== 'string') { 47 | opts.limit = 0; 48 | } 49 | 50 | return db.allDocs(opts); 51 | } 52 | 53 | function find(db, requestDef) { 54 | if (requestDef.selector) { 55 | requestDef.selector = massageSelector(requestDef.selector); 56 | } 57 | if (requestDef.sort) { 58 | requestDef.sort = massageSort(requestDef.sort); 59 | } 60 | 61 | validateFindRequest(requestDef); 62 | 63 | return getIndexes(db).then(function (getIndexesRes) { 64 | 65 | var queryPlan = planQuery(requestDef, getIndexesRes.indexes); 66 | 67 | var indexToUse = queryPlan.index; 68 | 69 | validateSort(requestDef, indexToUse); 70 | 71 | var opts = utils.extend(true, { 72 | include_docs: true, 73 | reduce: false 74 | }, queryPlan.queryOpts); 75 | 76 | if ('startkey' in opts && 'endkey' in opts && 77 | collate(opts.startkey, opts.endkey) > 0) { 78 | // can't possibly return any results, startkey > endkey 79 | return {docs: []}; 80 | } 81 | 82 | var isDescending = requestDef.sort && 83 | typeof requestDef.sort[0] !== 'string' && 84 | getValue(requestDef.sort[0]) === 'desc'; 85 | 86 | if (isDescending) { 87 | // either all descending or all ascending 88 | opts.descending = true; 89 | opts = reverseOptions(opts); 90 | } 91 | 92 | if (!queryPlan.inMemoryFields.length) { 93 | // no in-memory filtering necessary, so we can let the 94 | // database do the limit/skip for us 95 | if ('limit' in requestDef) { 96 | opts.limit = requestDef.limit; 97 | } 98 | if ('skip' in requestDef) { 99 | opts.skip = requestDef.skip; 100 | } 101 | } 102 | 103 | return Promise.resolve().then(function () { 104 | if (indexToUse.name === '_all_docs') { 105 | return doAllDocs(db, opts); 106 | } else { 107 | var signature = indexToSignature(indexToUse); 108 | return abstractMapper.query.call(db, signature, opts); 109 | } 110 | }).then(function (res) { 111 | if (opts.inclusive_start === false) { 112 | // may have to manually filter the first one, 113 | // since couchdb has no true inclusive_start option 114 | res.rows = filterInclusiveStart(res.rows, opts.startkey, indexToUse); 115 | } 116 | 117 | if (queryPlan.inMemoryFields.length) { 118 | // need to filter some stuff in-memory 119 | res.rows = filterInMemoryFields(res.rows, requestDef, queryPlan.inMemoryFields); 120 | } 121 | 122 | var resp = { 123 | docs: res.rows.map(function (row) { 124 | var doc = row.doc; 125 | if (requestDef.fields) { 126 | return utils.pick(doc, requestDef.fields); 127 | } 128 | return doc; 129 | }) 130 | }; 131 | 132 | if (indexToUse.defaultUsed) { 133 | resp.warning = 'no matching index found, create an index to optimize query time'; 134 | } 135 | 136 | return resp; 137 | }); 138 | }); 139 | } 140 | 141 | module.exports = find; 142 | -------------------------------------------------------------------------------- /lib/adapters/local/get-indexes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../../utils'); 4 | 5 | var localUtils = require('../utils'); 6 | var massageIndexDef = localUtils.massageIndexDef; 7 | 8 | function getIndexes(db) { 9 | // just search through all the design docs and filter in-memory. 10 | // hopefully there aren't that many ddocs. 11 | return db.allDocs({ 12 | startkey: '_design/', 13 | endkey: '_design/\uffff', 14 | include_docs: true 15 | }).then(function (allDocsRes) { 16 | var res = { 17 | indexes: [{ 18 | ddoc: null, 19 | name: '_all_docs', 20 | type: 'special', 21 | def: { 22 | fields: [{_id: 'asc'}] 23 | } 24 | }] 25 | }; 26 | 27 | res.indexes = utils.flatten(res.indexes, allDocsRes.rows.filter(function (row) { 28 | return row.doc.language === 'query'; 29 | }).map(function (row) { 30 | var viewNames = row.doc.views !== undefined ? Object.keys(row.doc.views) : []; 31 | 32 | return viewNames.map(function (viewName) { 33 | var view = row.doc.views[viewName]; 34 | return { 35 | ddoc: row.id, 36 | name: viewName, 37 | type: 'json', 38 | def: massageIndexDef(view.options.def) 39 | }; 40 | }); 41 | })); 42 | 43 | // these are sorted by view name for some reason 44 | res.indexes.sort(function (left, right) { 45 | return utils.compare(left.name, right.name); 46 | }); 47 | res.total_rows = res.indexes.length; 48 | return res; 49 | }); 50 | } 51 | 52 | module.exports = getIndexes; 53 | -------------------------------------------------------------------------------- /lib/adapters/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../utils'); 4 | var callbackify = utils.callbackify; 5 | 6 | exports.createIndex = callbackify(require('./create-index')); 7 | exports.find = callbackify(require('./find')); 8 | exports.getIndexes = callbackify(require('./get-indexes')); 9 | exports.deleteIndex = callbackify(require('./delete-index')); -------------------------------------------------------------------------------- /lib/deps/blob.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | //Abstracts constructing a Blob object, so it also works in older 4 | //browsers that don't support the native Blob constructor. (i.e. 5 | //old QtWebKit versions, at least). 6 | function createBlob(parts, properties) { 7 | parts = parts || []; 8 | properties = properties || {}; 9 | try { 10 | return new Blob(parts, properties); 11 | } catch (e) { 12 | if (e.name !== "TypeError") { 13 | throw e; 14 | } 15 | var BlobBuilder = global.BlobBuilder || 16 | global.MSBlobBuilder || 17 | global.MozBlobBuilder || 18 | global.WebKitBlobBuilder; 19 | var builder = new BlobBuilder(); 20 | for (var i = 0; i < parts.length; i += 1) { 21 | builder.append(parts[i]); 22 | } 23 | return builder.getBlob(properties.type); 24 | } 25 | } 26 | 27 | module.exports = createBlob; 28 | 29 | -------------------------------------------------------------------------------- /lib/deps/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function PouchError(opts) { 4 | this.status = opts.status; 5 | this.name = opts.error; 6 | this.message = opts.reason; 7 | this.error = true; 8 | } 9 | 10 | PouchError.prototype__proto__ = Error.prototype; 11 | 12 | PouchError.prototype.toString = function () { 13 | return JSON.stringify({ 14 | status: this.status, 15 | name: this.name, 16 | message: this.message 17 | }); 18 | }; 19 | 20 | exports.UNAUTHORIZED = new PouchError({ 21 | status: 401, 22 | error: 'unauthorized', 23 | reason: "Name or password is incorrect." 24 | }); 25 | 26 | exports.MISSING_BULK_DOCS = new PouchError({ 27 | status: 400, 28 | error: 'bad_request', 29 | reason: "Missing JSON list of 'docs'" 30 | }); 31 | 32 | exports.MISSING_DOC = new PouchError({ 33 | status: 404, 34 | error: 'not_found', 35 | reason: 'missing' 36 | }); 37 | 38 | exports.REV_CONFLICT = new PouchError({ 39 | status: 409, 40 | error: 'conflict', 41 | reason: 'Document update conflict' 42 | }); 43 | 44 | exports.INVALID_ID = new PouchError({ 45 | status: 400, 46 | error: 'invalid_id', 47 | reason: '_id field must contain a string' 48 | }); 49 | 50 | exports.MISSING_ID = new PouchError({ 51 | status: 412, 52 | error: 'missing_id', 53 | reason: '_id is required for puts' 54 | }); 55 | 56 | exports.RESERVED_ID = new PouchError({ 57 | status: 400, 58 | error: 'bad_request', 59 | reason: 'Only reserved document ids may start with underscore.' 60 | }); 61 | 62 | exports.NOT_OPEN = new PouchError({ 63 | status: 412, 64 | error: 'precondition_failed', 65 | reason: 'Database not open' 66 | }); 67 | 68 | exports.UNKNOWN_ERROR = new PouchError({ 69 | status: 500, 70 | error: 'unknown_error', 71 | reason: 'Database encountered an unknown error' 72 | }); 73 | 74 | exports.BAD_ARG = new PouchError({ 75 | status: 500, 76 | error: 'badarg', 77 | reason: 'Some query argument is invalid' 78 | }); 79 | 80 | exports.INVALID_REQUEST = new PouchError({ 81 | status: 400, 82 | error: 'invalid_request', 83 | reason: 'Request was invalid' 84 | }); 85 | 86 | exports.QUERY_PARSE_ERROR = new PouchError({ 87 | status: 400, 88 | error: 'query_parse_error', 89 | reason: 'Some query parameter is invalid' 90 | }); 91 | 92 | exports.DOC_VALIDATION = new PouchError({ 93 | status: 500, 94 | error: 'doc_validation', 95 | reason: 'Bad special document member' 96 | }); 97 | 98 | exports.BAD_REQUEST = new PouchError({ 99 | status: 400, 100 | error: 'bad_request', 101 | reason: 'Something wrong with the request' 102 | }); 103 | 104 | exports.NOT_AN_OBJECT = new PouchError({ 105 | status: 400, 106 | error: 'bad_request', 107 | reason: 'Document must be a JSON object' 108 | }); 109 | 110 | exports.DB_MISSING = new PouchError({ 111 | status: 404, 112 | error: 'not_found', 113 | reason: 'Database not found' 114 | }); 115 | 116 | exports.IDB_ERROR = new PouchError({ 117 | status: 500, 118 | error: 'indexed_db_went_bad', 119 | reason: 'unknown' 120 | }); 121 | 122 | exports.WSQ_ERROR = new PouchError({ 123 | status: 500, 124 | error: 'web_sql_went_bad', 125 | reason: 'unknown' 126 | }); 127 | 128 | exports.LDB_ERROR = new PouchError({ 129 | status: 500, 130 | error: 'levelDB_went_went_bad', 131 | reason: 'unknown' 132 | }); 133 | 134 | exports.FORBIDDEN = new PouchError({ 135 | status: 403, 136 | error: 'forbidden', 137 | reason: 'Forbidden by design doc validate_doc_update function' 138 | }); 139 | 140 | exports.INVALID_REV = new PouchError({ 141 | status: 400, 142 | error: 'bad_request', 143 | reason: 'Invalid rev format' 144 | }); 145 | 146 | exports.FILE_EXISTS = new PouchError({ 147 | status: 412, 148 | error: 'file_exists', 149 | reason: 'The database could not be created, the file already exists.' 150 | }); 151 | 152 | exports.MISSING_STUB = new PouchError({ 153 | status: 412, 154 | error: 'missing_stub' 155 | }); 156 | 157 | exports.error = function (error, reason, name) { 158 | function CustomPouchError(reason) { 159 | // inherit error properties from our parent error manually 160 | // so as to allow proper JSON parsing. 161 | for (var p in error) { 162 | if (typeof error[p] !== 'function') { 163 | this[p] = error[p]; 164 | } 165 | } 166 | if (name !== undefined) { 167 | this.name = name; 168 | } 169 | if (reason !== undefined) { 170 | this.reason = reason; 171 | } 172 | } 173 | CustomPouchError.prototype = PouchError.prototype; 174 | return new CustomPouchError(reason); 175 | }; 176 | 177 | // Find one of the errors defined above based on the value 178 | // of the specified property. 179 | // If reason is provided prefer the error matching that reason. 180 | // This is for differentiating between errors with the same name and status, 181 | // eg, bad_request. 182 | exports.getErrorTypeByProp = function (prop, value, reason) { 183 | var errors = exports; 184 | var keys = Object.keys(errors).filter(function (key) { 185 | var error = errors[key]; 186 | return typeof error !== 'function' && error[prop] === value; 187 | }); 188 | var key = reason && keys.filter(function (key) { 189 | var error = errors[key]; 190 | return error.message === reason; 191 | })[0] || keys[0]; 192 | return (key) ? errors[key] : null; 193 | }; 194 | 195 | exports.generateErrorFromResponse = function (res) { 196 | var error, errName, errType, errMsg, errReason; 197 | var errors = exports; 198 | 199 | errName = (res.error === true && typeof res.name === 'string') ? 200 | res.name : 201 | res.error; 202 | errReason = res.reason; 203 | errType = errors.getErrorTypeByProp('name', errName, errReason); 204 | 205 | if (res.missing || 206 | errReason === 'missing' || 207 | errReason === 'deleted' || 208 | errName === 'not_found') { 209 | errType = errors.MISSING_DOC; 210 | } else if (errName === 'doc_validation') { 211 | // doc validation needs special treatment since 212 | // res.reason depends on the validation error. 213 | // see utils.js 214 | errType = errors.DOC_VALIDATION; 215 | errMsg = errReason; 216 | } else if (errName === 'bad_request' && errType.message !== errReason) { 217 | // if bad_request error already found based on reason don't override. 218 | 219 | // attachment errors. 220 | if (errReason.indexOf('unknown stub attachment') === 0) { 221 | errType = errors.MISSING_STUB; 222 | errMsg = errReason; 223 | } else { 224 | errType = errors.BAD_REQUEST; 225 | } 226 | } 227 | 228 | // fallback to error by statys or unknown error. 229 | if (!errType) { 230 | errType = errors.getErrorTypeByProp('status', res.status, errReason) || 231 | errors.UNKNOWN_ERROR; 232 | } 233 | 234 | error = errors.error(errType, errReason, errName); 235 | 236 | // Keep custom message. 237 | if (errMsg) { 238 | error.message = errMsg; 239 | } 240 | 241 | // Keep helpful response data in our error messages. 242 | if (res.id) { 243 | error.id = res.id; 244 | } 245 | if (res.status) { 246 | error.status = res.status; 247 | } 248 | if (res.statusText) { 249 | error.name = res.statusText; 250 | } 251 | if (res.missing) { 252 | error.missing = res.missing; 253 | } 254 | 255 | return error; 256 | }; 257 | -------------------------------------------------------------------------------- /lib/deps/parse-uri.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // originally parseUri 1.2.2, now patched by us 4 | // (c) Steven Levithan 5 | // MIT License 6 | var options = { 7 | strictMode: false, 8 | key: ["source", "protocol", "authority", "userInfo", "user", "password", 9 | "host", "port", "relative", "path", "directory", "file", "query", 10 | "anchor"], 11 | q: { 12 | name: "queryKey", 13 | parser: /(?:^|&)([^&=]*)=?([^&]*)/g 14 | }, 15 | parser: { 16 | strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, 17 | loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ 18 | } 19 | }; 20 | function parseUri(str) { 21 | var o = options; 22 | var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str); 23 | var uri = {}; 24 | var i = 14; 25 | 26 | while (i--) { 27 | var key = o.key[i]; 28 | var value = m[i] || ""; 29 | var encoded = ['user', 'password'].indexOf(key) !== -1; 30 | uri[key] = encoded ? decodeURIComponent(value) : value; 31 | } 32 | 33 | uri[o.q.name] = {}; 34 | uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { 35 | if ($1) { 36 | uri[o.q.name][$1] = $2; 37 | } 38 | }); 39 | 40 | return uri; 41 | } 42 | 43 | 44 | module.exports = parseUri; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | 5 | var httpIndexes = require('./adapters/http'); 6 | var localIndexes = require('./adapters/local'); 7 | 8 | var plugin = {}; 9 | plugin.createIndex = utils.toPromise(function (requestDef, callback) { 10 | 11 | if (typeof requestDef !== 'object') { 12 | return callback(new Error('you must provide an index to create')); 13 | } 14 | 15 | var adapter = this.type() === 'http' ? httpIndexes : localIndexes; 16 | 17 | adapter.createIndex(this, requestDef, callback); 18 | }); 19 | 20 | plugin.find = utils.toPromise(function (requestDef, callback) { 21 | 22 | if (typeof callback === 'undefined') { 23 | callback = requestDef; 24 | requestDef = undefined; 25 | } 26 | 27 | if (typeof requestDef !== 'object') { 28 | return callback(new Error('you must provide search parameters to find()')); 29 | } 30 | 31 | var adapter = this.type() === 'http' ? httpIndexes : localIndexes; 32 | 33 | adapter.find(this, requestDef, callback); 34 | }); 35 | 36 | plugin.getIndexes = utils.toPromise(function (callback) { 37 | 38 | var adapter = this.type() === 'http' ? httpIndexes : localIndexes; 39 | 40 | adapter.getIndexes(this, callback); 41 | }); 42 | 43 | plugin.deleteIndex = utils.toPromise(function (indexDef, callback) { 44 | 45 | if (typeof indexDef !== 'object') { 46 | return callback(new Error('you must provide an index to delete')); 47 | } 48 | 49 | var adapter = this.type() === 'http' ? httpIndexes : localIndexes; 50 | 51 | adapter.deleteIndex(this, indexDef, callback); 52 | }); 53 | 54 | module.exports = plugin; 55 | 56 | /* istanbul ignore next */ 57 | if (typeof window !== 'undefined' && window.PouchDB) { 58 | window.PouchDB.plugin(plugin); 59 | } 60 | -------------------------------------------------------------------------------- /lib/massageCreateIndexRequest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var clone = utils.clone; 5 | 6 | // we restucture the supplied JSON considerably, because the official 7 | // Mango API is very particular about a lot of this stuff, but we like 8 | // to be liberal with what we accept in order to prevent mental 9 | // breakdowns in our users 10 | module.exports = function (requestDef) { 11 | requestDef = clone(requestDef); 12 | 13 | if (!requestDef.index) { 14 | requestDef.index = {}; 15 | } 16 | 17 | ['type', 'name', 'ddoc'].forEach(function (key) { 18 | if (requestDef.index[key]) { 19 | requestDef[key] = requestDef.index[key]; 20 | delete requestDef.index[key]; 21 | } 22 | }); 23 | 24 | if (requestDef.fields) { 25 | requestDef.index.fields = requestDef.fields; 26 | delete requestDef.fields; 27 | } 28 | 29 | if (!requestDef.type) { 30 | requestDef.type = 'json'; 31 | } 32 | return requestDef; 33 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('pouchdb-promise'); 4 | 5 | /* istanbul ignore next */ 6 | exports.once = function (fun) { 7 | var called = false; 8 | return exports.getArguments(function (args) { 9 | if (called) { 10 | console.trace(); 11 | throw new Error('once called more than once'); 12 | } else { 13 | called = true; 14 | fun.apply(this, args); 15 | } 16 | }); 17 | }; 18 | /* istanbul ignore next */ 19 | exports.getArguments = function (fun) { 20 | return function () { 21 | var len = arguments.length; 22 | var args = new Array(len); 23 | var i = -1; 24 | while (++i < len) { 25 | args[i] = arguments[i]; 26 | } 27 | return fun.call(this, args); 28 | }; 29 | }; 30 | /* istanbul ignore next */ 31 | exports.toPromise = function (func) { 32 | //create the function we will be returning 33 | return exports.getArguments(function (args) { 34 | var self = this; 35 | var tempCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false; 36 | // if the last argument is a function, assume its a callback 37 | var usedCB; 38 | if (tempCB) { 39 | // if it was a callback, create a new callback which calls it, 40 | // but do so async so we don't trap any errors 41 | usedCB = function (err, resp) { 42 | process.nextTick(function () { 43 | tempCB(err, resp); 44 | }); 45 | }; 46 | } 47 | var promise = new Promise(function (fulfill, reject) { 48 | try { 49 | var callback = exports.once(function (err, mesg) { 50 | if (err) { 51 | reject(err); 52 | } else { 53 | fulfill(mesg); 54 | } 55 | }); 56 | // create a callback for this invocation 57 | // apply the function in the orig context 58 | args.push(callback); 59 | func.apply(self, args); 60 | } catch (e) { 61 | reject(e); 62 | } 63 | }); 64 | // if there is a callback, call it back 65 | if (usedCB) { 66 | promise.then(function (result) { 67 | usedCB(null, result); 68 | }, usedCB); 69 | } 70 | promise.cancel = function () { 71 | return this; 72 | }; 73 | return promise; 74 | }); 75 | }; 76 | 77 | exports.inherits = require('inherits'); 78 | exports.Promise = Promise; 79 | 80 | exports.clone = function (obj) { 81 | return exports.extend(true, {}, obj); 82 | }; 83 | 84 | exports.extend = require('pouchdb-extend'); 85 | 86 | exports.callbackify = function (fun) { 87 | return exports.getArguments(function (args) { 88 | var cb = args.pop(); 89 | var promise = fun.apply(this, args); 90 | exports.promisedCallback(promise, cb); 91 | return promise; 92 | }); 93 | }; 94 | 95 | exports.promisedCallback = function (promise, callback) { 96 | promise.then(function (res) { 97 | process.nextTick(function () { 98 | callback(null, res); 99 | }); 100 | }, function (reason) { 101 | process.nextTick(function () { 102 | callback(reason); 103 | }); 104 | }); 105 | return promise; 106 | }; 107 | 108 | var crypto = require('crypto'); 109 | var Md5 = require('spark-md5'); 110 | 111 | exports.MD5 = function (string) { 112 | /* istanbul ignore else */ 113 | if (!process.browser) { 114 | return crypto.createHash('md5').update(string).digest('hex'); 115 | } else { 116 | return Md5.hash(string); 117 | } 118 | }; 119 | 120 | exports.flatten = exports.getArguments(function (args) { 121 | var res = []; 122 | for (var i = 0, len = args.length; i < len; i++) { 123 | var subArr = args[i]; 124 | if (Array.isArray(subArr)) { 125 | res = res.concat(exports.flatten.apply(null, subArr)); 126 | } else { 127 | res.push(subArr); 128 | } 129 | } 130 | return res; 131 | }); 132 | 133 | exports.mergeObjects = function (arr) { 134 | var res = {}; 135 | for (var i = 0, len = arr.length; i < len; i++) { 136 | res = exports.extend(true, res, arr[i]); 137 | } 138 | return res; 139 | }; 140 | 141 | // this would just be "return doc[field]", but fields 142 | // can be "deep" due to dot notation 143 | exports.getFieldFromDoc = function (doc, parsedField) { 144 | var value = doc; 145 | for (var i = 0, len = parsedField.length; i < len; i++) { 146 | var key = parsedField[i]; 147 | value = value[key]; 148 | if (!value) { 149 | break; 150 | } 151 | } 152 | return value; 153 | }; 154 | 155 | exports.setFieldInDoc = function (doc, parsedField, value) { 156 | for (var i = 0, len = parsedField.length; i < len-1; i++) { 157 | var elem = parsedField[i]; 158 | doc = doc[elem] = {}; 159 | } 160 | doc[parsedField[len-1]] = value; 161 | }; 162 | 163 | // Converts a string in dot notation to an array of its components, with backslash escaping 164 | exports.parseField = function (fieldName) { 165 | // fields may be deep (e.g. "foo.bar.baz"), so parse 166 | var fields = []; 167 | var current = ''; 168 | for (var i = 0, len = fieldName.length; i < len; i++) { 169 | var ch = fieldName[i]; 170 | if (ch === '.') { 171 | if (i > 0 && fieldName[i - 1] === '\\') { // escaped delimiter 172 | current = current.substring(0, current.length - 1) + '.'; 173 | } else { // not escaped, so delimiter 174 | fields.push(current); 175 | current = ''; 176 | } 177 | } else { // normal character 178 | current += ch; 179 | } 180 | } 181 | fields.push(current); 182 | return fields; 183 | }; 184 | 185 | // Selects a list of fields defined in dot notation from one doc 186 | // and copies them to a new doc. Like underscore _.pick but supports nesting. 187 | exports.pick = function (obj, arr) { 188 | var res = {}; 189 | for (var i = 0, len = arr.length; i < len; i++) { 190 | var parsedField = exports.parseField(arr[i]); 191 | var value = exports.getFieldFromDoc(obj, parsedField); 192 | if(typeof value !== 'undefined') { 193 | exports.setFieldInDoc(res, parsedField, value); 194 | } 195 | } 196 | return res; 197 | }; 198 | 199 | // e.g. ['a'], ['a', 'b'] is true, but ['b'], ['a', 'b'] is false 200 | exports.oneArrayIsSubArrayOfOther = function (left, right) { 201 | 202 | for (var i = 0, len = Math.min(left.length, right.length); i < len; i++) { 203 | if (left[i] !== right[i]) { 204 | return false; 205 | } 206 | } 207 | return true; 208 | }; 209 | 210 | // e.g.['a', 'b', 'c'], ['a', 'b'] is false 211 | exports.oneArrayIsStrictSubArrayOfOther = function (left, right) { 212 | 213 | if (left.length > right.length) { 214 | return false; 215 | } 216 | 217 | return exports.oneArrayIsSubArrayOfOther(left, right); 218 | }; 219 | 220 | // same as above, but treat the left array as an unordered set 221 | // e.g. ['b', 'a'], ['a', 'b', 'c'] is true, but ['c'], ['a', 'b', 'c'] is false 222 | exports.oneSetIsSubArrayOfOther = function (left, right) { 223 | left = left.slice(); 224 | for (var i = 0, len = right.length; i < len; i++) { 225 | var field = right[i]; 226 | if (!left.length) { 227 | break; 228 | } 229 | var leftIdx = left.indexOf(field); 230 | if (leftIdx === -1) { 231 | return false; 232 | } else { 233 | left.splice(leftIdx, 1); 234 | } 235 | } 236 | return true; 237 | }; 238 | 239 | exports.compare = function (left, right) { 240 | return left < right ? -1 : left > right ? 1 : 0; 241 | }; 242 | 243 | exports.arrayToObject = function (arr) { 244 | var res = {}; 245 | for (var i = 0, len = arr.length; i < len; i++) { 246 | res[arr[i]] = true; 247 | } 248 | return res; 249 | }; 250 | 251 | exports.max = function (arr, fun) { 252 | var max = null; 253 | var maxScore = -1; 254 | for (var i = 0, len = arr.length; i < len; i++) { 255 | var element = arr[i]; 256 | var score = fun(element); 257 | if (score > maxScore) { 258 | maxScore = score; 259 | max = element; 260 | } 261 | } 262 | return max; 263 | }; 264 | 265 | exports.arrayEquals = function (arr1, arr2) { 266 | if (arr1.length !== arr2.length) { 267 | return false; 268 | } 269 | for (var i = 0, len = arr1.length; i < len; i++) { 270 | if (arr1[i] !== arr2[i]) { 271 | return false; 272 | } 273 | } 274 | return true; 275 | }; 276 | 277 | exports.uniq = function (arr) { 278 | var obj = {}; 279 | for (var i = 0; i < arr.length; i++) { 280 | obj['$' + arr[i]] = true; 281 | } 282 | return Object.keys(obj).map(function (key) { 283 | return key.substring(1); 284 | }); 285 | }; 286 | 287 | exports.log = require('debug')('pouchdb:find'); 288 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouchdb-find", 3 | "version": "0.10.5", 4 | "description": "Easy-to-use query language for PouchDB", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/nolanlawson/pouchdb-find.git" 9 | }, 10 | "keywords": [ 11 | "pouch", 12 | "pouchdb", 13 | "plugin", 14 | "find", 15 | "mango", 16 | "query", 17 | "couch", 18 | "couchdb" 19 | ], 20 | "author": "", 21 | "license": "Apache-2.0", 22 | "bugs": { 23 | "url": "https://github.com/nolanlawson/pouchdb-find/issues" 24 | }, 25 | "scripts": { 26 | "test-node": "bash ./bin/test-node.sh", 27 | "test-browser": "node ./bin/test-browser.js", 28 | "test": "npm run eslint && bash ./bin/run-test.sh", 29 | "build": "mkdirp dist && npm run browserify && npm run min", 30 | "browserify": "browserify . -s pouchdbFind -p bundle-collapser/plugin | ./bin/es3ify.js | derequire > dist/pouchdb.find.js", 31 | "min": "uglifyjs dist/pouchdb.find.js -mc > dist/pouchdb.find.min.js", 32 | "dev": "browserify test/test.js > test/test-bundle.js && npm run dev-server", 33 | "dev-server": "node ./bin/dev-server.js", 34 | "coverage": "bash bin/test-coverage.sh && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100", 35 | "report-coverage": "npm run coverage && istanbul-coveralls --no-rm", 36 | "install-couch": "sh ./bin/run-couch-master-on-travis.sh && sh ./bin/enable-couchdb-cors.sh", 37 | "eslint": "eslint lib test" 38 | }, 39 | "dependencies": { 40 | "argsarray": "0.0.1", 41 | "debug": "^2.1.0", 42 | "inherits": "^2.0.1", 43 | "is-array": "^1.0.1", 44 | "pouchdb-collate": "^1.2.0", 45 | "pouchdb-extend": "^0.1.2", 46 | "pouchdb-promise": "5.4.0", 47 | "pouchdb-upsert": "~2.0.1", 48 | "spark-md5": "2.0.2" 49 | }, 50 | "devDependencies": { 51 | "bluebird": "^1.0.7", 52 | "browserify": "^12.0.2", 53 | "bundle-collapser": "1.2.1", 54 | "chai": "^3.5.0", 55 | "chai-as-promised": "^5.3.0", 56 | "derequire": "^2.0.0", 57 | "es3ify": "^0.1.3", 58 | "eslint": "^3.14.1", 59 | "http-server": "~0.5.5", 60 | "inherits": "^2.0.1", 61 | "istanbul": "^0.2.7", 62 | "istanbul-coveralls": "^1.0.3", 63 | "memdown": "^1.1.0", 64 | "mkdirp": "^0.5.0", 65 | "mocha": "^2.4.5", 66 | "phantomjs-prebuilt": "^2.1.7", 67 | "pouchdb-adapter-http": "^5.4.5", 68 | "pouchdb-adapter-memory": "^5.4.5", 69 | "pouchdb-core": "^5.4.5", 70 | "pouchdb-replication": "^5.4.5", 71 | "query-string": "^2.4.2", 72 | "request": "^2.36.0", 73 | "sauce-connect-launcher": "^0.14.0", 74 | "selenium-standalone": "^5.1.0", 75 | "uglify-js": "^2.4.13", 76 | "watchify": "^3.1.0", 77 | "wd": "^0.2.21" 78 | }, 79 | "browser": { 80 | "crypto": false, 81 | "buffer": false 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /scripts/generate_kitchen_sink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # generate the "kitchen sink" tests 3 | 4 | import json, random 5 | 6 | configs = [] 7 | operators = ['$gt', '$gte', '$eq', '$ne', '$lt', '$lte'] 8 | fields = ['_id', 'rank', 'series', 'debut', 'name'] 9 | 10 | docs = [ 11 | {'debut': 1981, 'series': 'mario', '_id': 'mario', 'name': 'mario', 'rank': 5}, 12 | {'debut': 1996, 'series': 'pokemon', '_id': 'puff', 'name': 'jigglypuff', 'rank': 8}, 13 | {'debut': 1986, 'series': 'zelda', '_id': 'link', 'name': 'link', 'rank': 10}, 14 | {'debut': 1981, 'series': 'mario', '_id': 'dk', 'name': 'donkey kong', 'rank': 7}, 15 | {'debut': 1996, 'series': 'pokemon', '_id': 'pikach', 'name': 'pikach', 'rank': 1}, 16 | {'debut': 1990, 'series': 'f-zero', '_id': 'falcon', 'name': 'captain falcon', 'rank': 4}, 17 | {'debut': 1983, 'series': 'mario', '_id': 'luigi', 'name': 'luigi', 'rank': 11}, 18 | {'debut': 1993, 'series': 'star fox', '_id': 'fox', 'name': 'fox', 'rank': 3}, 19 | {'debut': 1994, 'series': 'earthbound', '_id': 'ness', 'name': 'ness', 'rank': 9}, 20 | {'debut': 1986, 'series': 'metroid', '_id': 'samus', 'name': 'samus', 'rank': 12}, 21 | {'debut': 1990, 'series': 'mario', '_id': 'yoshi', 'name': 'yoshi', 'rank': 6}, 22 | {'debut': 1992, 'series': 'kirby', '_id': 'kirby', 'name': 'kirby', 'rank': 2}] 23 | 24 | def create_random_selector(): 25 | operator = random.choice(operators) 26 | field = random.choice(fields) 27 | value = random.choice(docs)[field] 28 | 29 | return {field: {operator: value}} 30 | 31 | for i in range(100): 32 | num_criteria = random.choice([1, 2, 3, 4]); 33 | if num_criteria == 1: 34 | selector = create_random_selector() 35 | else: 36 | selector = {'$and': []} 37 | for j in range(num_criteria): 38 | selector['$and'].append(create_random_selector()) 39 | 40 | num_sort_fields = random.choice([0, 1, 2, 3]) 41 | sort = [] 42 | for j in range(num_sort_fields): 43 | sort.append(random.choice(fields)) 44 | sort = list(set(sort)) 45 | random.shuffle(sort) 46 | 47 | config = {'selector': selector} 48 | if (len(sort) > 0): 49 | config['sort'] = sort 50 | configs.append(config); 51 | 52 | print json.dumps(configs) 53 | -------------------------------------------------------------------------------- /test/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // minimal polyfill for phantomjs; in the future, we 5 | // should do ES5_SHIM=true like pouchdb 6 | if (!Function.prototype.bind) { 7 | Function.prototype.bind = function (oThis) { 8 | if (typeof this !== "function") { 9 | // closest thing possible to the ECMAScript 5 10 | // internal IsCallable function 11 | throw new TypeError( 12 | "Function.prototype.bind - what is trying " + 13 | "to be bound is not callable"); 14 | } 15 | 16 | var aArgs = Array.prototype.slice.call(arguments, 1), 17 | fToBind = this, 18 | FNOP = function () {}, 19 | fBound = function () { 20 | return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, 21 | aArgs.concat(Array.prototype.slice.call(arguments))); 22 | }; 23 | 24 | FNOP.prototype = this.prototype; 25 | fBound.prototype = new FNOP(); 26 | 27 | return fBound; 28 | }; 29 | } 30 | 31 | //minimal polyfill for phantomjs 32 | if (!Array.prototype.find) { 33 | Array.prototype.find = function (predicate) { 34 | if (this === null) { 35 | throw new TypeError('Array.prototype.find called on null or undefined'); 36 | } 37 | if (typeof predicate !== 'function') { 38 | throw new TypeError('predicate must be a function'); 39 | } 40 | var list = Object(this); 41 | var length = list.length >>> 0; 42 | var thisArg = arguments[1]; 43 | var value; 44 | 45 | for (var i = 0; i < length; i++) { 46 | value = list[i]; 47 | if (predicate.call(thisArg, value, i, list)) { 48 | return value; 49 | } 50 | } 51 | return undefined; 52 | }; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/test-abstract-mapreduce/evalfunc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | module.exports = function (func, emit, sum, log, isArray, toJSON) { 5 | return eval("'use strict'; (" + func.replace(/;\s*$/, "") + ");"); 6 | }; 7 | -------------------------------------------------------------------------------- /test/test-abstract-mapreduce/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Mapreduce = require('./mapreduce'); 4 | 5 | module.exports = function (dbName, dbType, Pouch) { 6 | 7 | Pouch.plugin(Mapreduce); 8 | 9 | var viewTypes = ['persisted', 'temp']; 10 | viewTypes.forEach(function (viewType) { 11 | var testSuiteName = 'abstract-mapreduce: ' + dbType + ' with ' + 12 | viewType + ' views:'; 13 | describe(testSuiteName, function () { 14 | this.timeout(120000); 15 | tests(dbName, dbType, viewType); 16 | }); 17 | }); 18 | 19 | function tests(dbName, dbType, viewType) { 20 | require('./test.custom.js')(dbName, dbType, viewType, Pouch); 21 | require('./test.mapreduce.js')(dbName, dbType, viewType, Pouch); 22 | if (viewType === 'persisted') { 23 | require('./test.persisted.js')(dbName, dbType, viewType, Pouch); 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/test-abstract-mapreduce/test.custom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var abstractMapReduce = require('../../lib/abstract-mapreduce'); 4 | 5 | module.exports = function (dbName, dbType, viewType, Pouch) { 6 | 7 | describe('custom mapper', function () { 8 | 9 | var db; 10 | beforeEach(function () { 11 | db = new Pouch(dbName); 12 | }); 13 | afterEach(function () { 14 | return db.destroy(); 15 | }); 16 | 17 | it('basic custom mapper test', function () { 18 | var customMapper = abstractMapReduce({ 19 | name: 'custom', 20 | mapper: function (fields, emit) { 21 | // mapFunDef is an array 22 | return function (doc) { 23 | emit(doc[fields[0]]); 24 | }; 25 | }, 26 | reducer: function () { 27 | throw new Error('reduce not supported'); 28 | }, 29 | ddocValidator: function () { 30 | // do nothing 31 | } 32 | }); 33 | 34 | return db.put({ 35 | _id: '_design/foo', 36 | views: { 37 | foo: { 38 | map: ['somefield'] 39 | } 40 | } 41 | }).then(function () { 42 | return db.bulkDocs([ 43 | {_id: 'foo', somefield: 'foo'}, 44 | {_id: 'bar', somefield: 'bar'}, 45 | ]); 46 | }).then(function () { 47 | return customMapper.query.apply(db, ['foo']).then(function (res) { 48 | res.rows.should.deep.equal([ 49 | {key: 'bar', id: 'bar', value: null}, 50 | {key: 'foo', id: 'foo', value: null} 51 | ]); 52 | }); 53 | }); 54 | }); 55 | }); 56 | }; -------------------------------------------------------------------------------- /test/test-abstract-mapreduce/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* istanbul ignore if */ 3 | if (typeof global.Promise === 'function') { 4 | exports.Promise = global.Promise; 5 | } else { 6 | exports.Promise = require('lie'); 7 | } 8 | 9 | exports.inherits = require('inherits'); 10 | exports.extend = require('pouchdb-extend'); 11 | var argsarray = require('argsarray'); 12 | 13 | exports.promisedCallback = function (promise, callback) { 14 | if (callback) { 15 | promise.then(function (res) { 16 | process.nextTick(function () { 17 | callback(null, res); 18 | }); 19 | }, function (reason) { 20 | process.nextTick(function () { 21 | callback(reason); 22 | }); 23 | }); 24 | } 25 | return promise; 26 | }; 27 | 28 | exports.callbackify = function (fun) { 29 | return argsarray(function (args) { 30 | var cb = args.pop(); 31 | var promise = fun.apply(this, args); 32 | if (typeof cb === 'function') { 33 | exports.promisedCallback(promise, cb); 34 | } 35 | return promise; 36 | }); 37 | }; 38 | 39 | // Promise finally util similar to Q.finally 40 | exports.fin = function (promise, cb) { 41 | return promise.then(function (res) { 42 | var promise2 = cb(); 43 | if (typeof promise2.then === 'function') { 44 | return promise2.then(function () { 45 | return res; 46 | }); 47 | } 48 | return res; 49 | }, function (reason) { 50 | var promise2 = cb(); 51 | /* istanbul ignore next */ 52 | if (typeof promise2.then === 'function') { 53 | return promise2.then(function () { 54 | throw reason; 55 | }); 56 | } 57 | throw reason; 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /test/test-suite-1/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbName, dbType, Pouch) { 4 | describe(dbType + ' test suite 1', function () { 5 | this.timeout(100000); 6 | 7 | var context = {}; 8 | 9 | beforeEach(function () { 10 | this.timeout(60000); 11 | 12 | var dbNameWithTimestamp = dbName; 13 | if (dbType === 'http') { 14 | dbNameWithTimestamp = dbName + (new Date()).getTime(); 15 | } 16 | 17 | context.db = new Pouch(dbNameWithTimestamp); 18 | return context.db; 19 | }); 20 | afterEach(function () { 21 | this.timeout(60000); 22 | return context.db.destroy(); 23 | }); 24 | 25 | require('./test.callbacks')(dbType, context); 26 | require('./test.basic')(dbType, context); 27 | require('./test.basic2')(dbType, context); 28 | require('./test.basic3')(dbType, context); 29 | require('./test.ddoc')(dbType, context); 30 | require('./test.set-operations')(dbType, context); 31 | require('./test.limit')(dbType, context); 32 | require('./test.skip')(dbType, context); 33 | require('./test.limit-skip')(dbType, context); 34 | require('./test.sorting')(dbType, context); 35 | require('./test.fields')(dbType, context); 36 | require('./test.ltgt')(dbType, context); 37 | require('./test.eq')(dbType, context); 38 | require('./test.deep-fields')(dbType, context); 39 | require('./test.pick-fields')(dbType, context); 40 | require('./test.exists')(dbType, context); 41 | require('./test.type')(dbType, context); 42 | require('./test.ne')(dbType, context); 43 | require('./test.matching-indexes')(dbType, context); 44 | require('./test.errors')(dbType, context); 45 | require('./test.array')(dbType, context); 46 | require('./test.combinational')(dbType, context); 47 | require('./test.elem-match')(dbType, context); 48 | require('./test.mod')(dbType, context); 49 | require('./test.regex')(dbType, context); 50 | require('./test.not')(dbType, context); 51 | require('./test.issue66')(dbType, context); 52 | require('./test.and')(dbType, context); 53 | require('./test.default-index')(dbType, context); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /test/test-suite-1/test.and.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': $and', function () { 6 | 7 | it('does and for _id', function () { 8 | var db = context.db; 9 | return db.bulkDocs([ 10 | { name: 'mario', _id: 'mario', rank: 5, series: 'mario', debut: 1981 }, 11 | { name: 'jigglypuff', _id: 'puff', rank: 8, series: 'pokemon', debut: 1996 }, 12 | { name: 'link', rank: 10, _id: 'link', series: 'zelda', debut: 1986 }, 13 | { name: 'donkey kong', rank: 7, _id: 'dk', series: 'mario', debut: 1981 }, 14 | { name: 'pikachu', series: 'pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 15 | { name: 'captain falcon', _id: 'falcon', rank: 4, series: 'f-zero', debut: 1990 }, 16 | { name: 'luigi', rank: 11, _id: 'luigi', series: 'mario', debut: 1983 }, 17 | { name: 'fox', _id: 'fox', rank: 3, series: 'star fox', debut: 1993 }, 18 | { name: 'ness', rank: 9, _id: 'ness', series: 'earthbound', debut: 1994 }, 19 | { name: 'samus', rank: 12, _id: 'samus', series: 'metroid', debut: 1986 }, 20 | { name: 'yoshi', _id: 'yoshi', rank: 6, series: 'mario', debut: 1990 }, 21 | { name: 'kirby', _id: 'kirby', series: 'kirby', rank: 2, debut: 1992 } 22 | ]).then(function () { 23 | return db.find({ 24 | selector: {$and: [ 25 | {_id: {$in: ['pikachu', 'puff']}}, 26 | {_id: {$gt: null}} 27 | ]}, 28 | fields: ["_id"], 29 | }); 30 | }).then(function (resp) { 31 | resp.should.deep.equal({ 32 | docs: [ 33 | {_id: 'pikachu'}, 34 | {_id: 'puff'}, 35 | ] 36 | }); 37 | }); 38 | }); 39 | 40 | it('does and for index', function () { 41 | var db = context.db; 42 | var index = { 43 | "index": { 44 | "fields": ["debut"] 45 | } 46 | }; 47 | return db.createIndex(index).then(function () { 48 | return db.bulkDocs([ 49 | { name: 'mario', _id: 'mario', rank: 5, series: 'mario', debut: 1981 }, 50 | { name: 'jigglypuff', _id: 'puff', rank: 8, series: 'pokemon', debut: 1996 }, 51 | { name: 'link', rank: 10, _id: 'link', series: 'zelda', debut: 1986 }, 52 | { name: 'donkey kong', rank: 7, _id: 'dk', series: 'mario', debut: 1981 }, 53 | { name: 'pikachu', series: 'pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 54 | { name: 'captain falcon', _id: 'falcon', rank: 4, series: 'f-zero', debut: 1990 }, 55 | { name: 'luigi', rank: 11, _id: 'luigi', series: 'mario', debut: 1983 }, 56 | { name: 'fox', _id: 'fox', rank: 3, series: 'star fox', debut: 1993 }, 57 | { name: 'ness', rank: 9, _id: 'ness', series: 'earthbound', debut: 1994 }, 58 | { name: 'samus', rank: 12, _id: 'samus', series: 'metroid', debut: 1986 }, 59 | { name: 'yoshi', _id: 'yoshi', rank: 6, series: 'mario', debut: 1990 }, 60 | { name: 'kirby', _id: 'kirby', series: 'kirby', rank: 2, debut: 1992 } 61 | ]);}).then(function () { 62 | return db.find({ 63 | selector: {$and: [ 64 | {debut: {$in: [1996]}}, 65 | {debut: {$gt: null}} 66 | ]}, 67 | fields: ["_id"], 68 | }); 69 | }).then(function (resp) { 70 | resp.should.deep.equal({ 71 | docs: [ 72 | {_id: 'pikachu'}, 73 | {_id: 'puff'}, 74 | ] 75 | }); 76 | }); 77 | }); 78 | 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /test/test-suite-1/test.basic2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | var should = testUtils.should; 6 | 7 | module.exports = function (dbType, context) { 8 | 9 | describe(dbType + ': basic2', function () { 10 | 11 | beforeEach(function () { 12 | return context.db.bulkDocs([ 13 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981 }, 14 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996 }, 15 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986 }, 16 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981 }, 17 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 18 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990 }, 19 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983 }, 20 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993 }, 21 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994 }, 22 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986 }, 23 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990 }, 24 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992 } 25 | ]); 26 | }); 27 | 28 | it('should include ddocs in _id results', function () { 29 | var db = context.db; 30 | var index = { 31 | "index": { 32 | "fields": ["foo"] 33 | }, 34 | "name": "foo-index", 35 | "type": "json" 36 | }; 37 | return db.createIndex(index).then(function () { 38 | return db.getIndexes(); 39 | }).then(function (res) { 40 | var ddoc = res.indexes[1].ddoc; 41 | return db.find({ 42 | selector: {_id: {$gt: '\u0000'}}, 43 | fields: ['_id'], 44 | sort: ['_id'] 45 | }).then(function (response) { 46 | response.docs.should.deep.equal([ 47 | {"_id": ddoc}, 48 | {"_id": "dk"}, 49 | {"_id": "falcon"}, 50 | {"_id": "fox"}, 51 | {"_id": "kirby"}, 52 | {"_id": "link"}, 53 | {"_id": "luigi"}, 54 | {"_id": "mario"}, 55 | {"_id": "ness"}, 56 | {"_id": "pikachu"}, 57 | {"_id": "puff"}, 58 | {"_id": "samus"}, 59 | {"_id": "yoshi"} 60 | ]); 61 | }); 62 | }); 63 | }); 64 | 65 | it('should find debut > 1990', function () { 66 | var db = context.db; 67 | return db.createIndex({ 68 | "index": { 69 | "fields": ["name"] 70 | } 71 | }).then(function () { 72 | return db.createIndex({ 73 | index: {fields: ['debut']} 74 | }); 75 | }).then(function () { 76 | return db.find({ 77 | selector: {debut: {$gt: 1990}}, 78 | fields: ['_id'], 79 | sort: ['debut'] 80 | }); 81 | }).then(function (response) { 82 | response.docs.should.deep.equal([ 83 | {"_id":"kirby"}, 84 | {"_id":"fox"}, 85 | {"_id":"ness"}, 86 | {"_id":"pikachu"}, 87 | {"_id":"puff"} 88 | ]); 89 | }); 90 | }); 91 | 92 | it('should find debut > 1990 2', function () { 93 | var db = context.db; 94 | return db.createIndex({ 95 | "index": { 96 | "fields": ["name"] 97 | } 98 | }).then(function () { 99 | return db.createIndex({ 100 | index: {fields: ['debut']} 101 | }); 102 | }).then(function () { 103 | return db.createIndex({ 104 | index: {fields: ['series', 'debut']} 105 | }); 106 | }).then(function () { 107 | return db.find({ 108 | selector: {debut: {$gt: 1990}}, 109 | fields: ['_id'], 110 | sort: ['debut'] 111 | }); 112 | }).then(function (response) { 113 | response.docs.should.deep.equal([ 114 | {"_id":"kirby"}, 115 | {"_id":"fox"}, 116 | {"_id":"ness"}, 117 | {"_id":"pikachu"}, 118 | {"_id":"puff"} 119 | ]); 120 | }); 121 | }); 122 | 123 | it('should find debut > 1990 3', function () { 124 | var db = context.db; 125 | return db.createIndex({ 126 | "index": { 127 | "fields": ["name"] 128 | } 129 | }).then(function () { 130 | return db.createIndex({ 131 | index: {fields: ['debut']} 132 | }); 133 | }).then(function () { 134 | return db.createIndex({ 135 | index: {fields: ['series', 'debut']} 136 | }); 137 | }).then(function () { 138 | return db.find({ 139 | selector: {debut: {$gt: 1990}}, 140 | fields: ['_id'] 141 | }); 142 | }).then(function (response) { 143 | response.docs.sort(sortById); 144 | response.docs.should.deep.equal([ 145 | {"_id":"fox"}, 146 | {"_id":"kirby"}, 147 | {"_id":"ness"}, 148 | {"_id":"pikachu"}, 149 | {"_id":"puff"} 150 | ]); 151 | }); 152 | }); 153 | 154 | it('should find series == mario', function () { 155 | var db = context.db; 156 | return db.createIndex({ 157 | "index": { 158 | "fields": ["name"] 159 | } 160 | }).then(function () { 161 | return db.createIndex({ 162 | index: {fields: ['debut']} 163 | }); 164 | }).then(function () { 165 | return db.createIndex({ 166 | index: {fields: ['series', 'debut']} 167 | }); 168 | }).then(function () { 169 | return db.find({ 170 | selector: {series: {$eq: 'Mario'}}, 171 | fields: ['_id', 'debut'], 172 | sort: [{series: 'desc'}, {debut: 'desc'}] 173 | }); 174 | }).then(function (response) { 175 | response.docs.should.deep.equal([ 176 | {"_id":"yoshi","debut":1990}, 177 | {"_id":"luigi","debut":1983}, 178 | {"_id":"mario","debut":1981}, 179 | {"_id":"dk","debut":1981} 180 | ]); 181 | }); 182 | }); 183 | 184 | it('throws an error for an invalid selector/sort', function () { 185 | var db = context.db; 186 | return db.createIndex({ 187 | index: {fields: ['series', 'debut']} 188 | }).then(function () { 189 | return db.find({ 190 | selector: {series: 'Mario', debut: 1981}, 191 | sort: ['name'] 192 | }); 193 | }).then(function () { 194 | throw new Error('expected an error'); 195 | }, function (err) { 196 | should.exist(err); 197 | }); 198 | }); 199 | 200 | }); 201 | }; 202 | -------------------------------------------------------------------------------- /test/test-suite-1/test.basic3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': basic3', function () { 9 | 10 | beforeEach(function () { 11 | return context.db.bulkDocs([ 12 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981, awesome: true }, 13 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996, 14 | awesome: false }, 15 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986, awesome: true }, 16 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981, awesome: false }, 17 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996, awesome: true }, 18 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990, 19 | awesome: true }, 20 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983, awesome: false }, 21 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993, awesome: true }, 22 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994, awesome: true }, 23 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986, awesome: true }, 24 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990, awesome: true }, 25 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992, awesome: true }, 26 | { name: 'Master Hand', _id: 'master_hand', series: 'Smash Bros', rank: 0, debut: 1999, 27 | awesome: false } 28 | ]); 29 | }); 30 | 31 | it('should be able to search for numbers', function () { 32 | var db = context.db; 33 | var index = { 34 | "index": { 35 | "fields": ["rank"] 36 | } 37 | }; 38 | return db.createIndex(index).then(function () { 39 | return db.find({ 40 | selector: {rank: 12}, 41 | fields: ['_id'] 42 | }).then(function (response) { 43 | response.docs.should.deep.equal([ 44 | {"_id": "samus"} 45 | ]); 46 | }); 47 | }); 48 | }); 49 | 50 | it('should use $exists for an in-memory filter', function () { 51 | var db = context.db; 52 | var index = { 53 | "index": { 54 | "fields": ["rank"] 55 | } 56 | }; 57 | return db.createIndex(index).then(function () { 58 | return db.find({ 59 | selector: {rank: 12, name: {$exists: true}}, 60 | fields: ['_id'] 61 | }).then(function (response) { 62 | response.docs.should.deep.equal([ 63 | {"_id": "samus"} 64 | ]); 65 | }); 66 | }); 67 | }); 68 | 69 | it('should be able to search for 0', function () { 70 | var db = context.db; 71 | var index = { 72 | "index": { 73 | "fields": ["rank"] 74 | } 75 | }; 76 | return db.createIndex(index).then(function () { 77 | return db.find({ 78 | selector: {rank: 0}, 79 | fields: ['_id'] 80 | }).then(function (response) { 81 | response.docs.should.deep.equal([ 82 | {"_id": "master_hand"} 83 | ]); 84 | }); 85 | }); 86 | }); 87 | 88 | it('should be able to search for boolean true', function () { 89 | var db = context.db; 90 | var index = { 91 | "index": { 92 | "fields": ["awesome"] 93 | } 94 | }; 95 | return db.createIndex(index).then(function () { 96 | return db.find({ 97 | selector: {awesome: true}, 98 | fields: ['_id'] 99 | }).then(function (response) { 100 | response.docs.sort(sortById); 101 | response.docs.should.deep.equal([{"_id":"falcon"},{"_id":"fox"},{"_id":"kirby"}, 102 | {"_id":"link"},{"_id":"mario"},{"_id":"ness"},{"_id":"pikachu"}, 103 | {"_id":"samus"},{"_id":"yoshi"}]); 104 | }); 105 | }); 106 | }); 107 | 108 | it('should be able to search for boolean true', function () { 109 | var db = context.db; 110 | var index = { 111 | "index": { 112 | "fields": ["awesome"] 113 | } 114 | }; 115 | return db.createIndex(index).then(function () { 116 | return db.find({ 117 | selector: {awesome: false}, 118 | fields: ['_id'] 119 | }).then(function (response) { 120 | response.docs.sort(sortById); 121 | response.docs.should.deep.equal([{"_id":"dk"},{"_id":"luigi"}, 122 | {"_id":"master_hand"},{"_id":"puff"}]); 123 | }); 124 | }); 125 | }); 126 | 127 | it('#73 should be able to create a custom index name', function () { 128 | var db = context.db; 129 | var index = { 130 | index: { 131 | fields: ["awesome"], 132 | name: 'myindex', 133 | ddoc: 'mydesigndoc' 134 | } 135 | }; 136 | return db.createIndex(index).then(function () { 137 | return db.getIndexes(); 138 | }).then(function (res) { 139 | var indexes = res.indexes.map(function (index) { 140 | return { 141 | name: index.name, 142 | ddoc: index.ddoc, 143 | type: index.type 144 | }; 145 | }); 146 | indexes.should.deep.equal([ 147 | { 148 | name: '_all_docs', 149 | type: 'special', 150 | ddoc: null 151 | }, 152 | { 153 | name: 'myindex', 154 | ddoc: '_design/mydesigndoc', 155 | type: 'json' 156 | } 157 | ]); 158 | return db.get('_design/mydesigndoc'); 159 | }); 160 | }); 161 | 162 | it('#73 should be able to create a custom index, alt style', function () { 163 | var db = context.db; 164 | var index = { 165 | index: { 166 | fields: ["awesome"], 167 | }, 168 | name: 'myindex', 169 | ddoc: 'mydesigndoc' 170 | }; 171 | return db.createIndex(index).then(function () { 172 | return db.getIndexes(); 173 | }).then(function (res) { 174 | var indexes = res.indexes.map(function (index) { 175 | return { 176 | name: index.name, 177 | ddoc: index.ddoc, 178 | type: index.type 179 | }; 180 | }); 181 | indexes.should.deep.equal([ 182 | { 183 | name: '_all_docs', 184 | type: 'special', 185 | ddoc: null 186 | }, 187 | { 188 | name: 'myindex', 189 | ddoc: '_design/mydesigndoc', 190 | type: 'json' 191 | } 192 | ]); 193 | return db.get('_design/mydesigndoc'); 194 | }); 195 | }); 196 | 197 | it('#73 should be able to create a custom index, alt style 2', function () { 198 | var db = context.db; 199 | var index = { 200 | name: 'myindex', 201 | ddoc: 'mydesigndoc', 202 | fields: ["awesome"] 203 | }; 204 | return db.createIndex(index).then(function () { 205 | return db.getIndexes(); 206 | }).then(function (res) { 207 | var indexes = res.indexes.map(function (index) { 208 | return { 209 | name: index.name, 210 | ddoc: index.ddoc, 211 | type: index.type 212 | }; 213 | }); 214 | indexes.should.deep.equal([ 215 | { 216 | name: '_all_docs', 217 | type: 'special', 218 | ddoc: null 219 | }, 220 | { 221 | name: 'myindex', 222 | ddoc: '_design/mydesigndoc', 223 | type: 'json' 224 | } 225 | ]); 226 | return db.get('_design/mydesigndoc'); 227 | }); 228 | }); 229 | 230 | }); 231 | }; -------------------------------------------------------------------------------- /test/test-suite-1/test.callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var Promise = testUtils.Promise; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': basic', function () { 9 | 10 | it('should create an index', function () { 11 | var db = context.db; 12 | var index = { 13 | "index": { 14 | "fields": ["foo"] 15 | }, 16 | "name": "foo-index", 17 | "type": "json" 18 | }; 19 | return new Promise(function (resolve, reject) { 20 | return db.createIndex(index, function (err, res) { 21 | if (err) { 22 | return reject(err); 23 | } 24 | return resolve(res); 25 | }); 26 | }).then(function (response) { 27 | response.id.should.match(/^_design\//); 28 | response.name.should.equal('foo-index'); 29 | response.result.should.equal('created'); 30 | return new Promise(function (resolve, reject) { 31 | db.createIndex(index, function (err, res) { 32 | if (err) { 33 | return reject(err); 34 | } 35 | return resolve(res); 36 | }); 37 | }); 38 | }).then(function (response) { 39 | response.id.should.match(/^_design\//); 40 | response.name.should.equal('foo-index'); 41 | response.result.should.equal('exists'); 42 | }); 43 | }); 44 | 45 | it('should find existing indexes', function () { 46 | var db = context.db; 47 | return new Promise(function (resolve, reject) { 48 | db.getIndexes(function (err, response) { 49 | if (err) { 50 | return reject(err); 51 | } 52 | resolve(response); 53 | }); 54 | }).then(function (response) { 55 | response.should.deep.equal({ 56 | "total_rows": 1, 57 | indexes: [{ 58 | ddoc: null, 59 | name: '_all_docs', 60 | type: 'special', 61 | def: {fields: [{_id: 'asc'}]} 62 | }] 63 | }); 64 | var index = { 65 | "index": { 66 | "fields": ["foo"] 67 | }, 68 | "name": "foo-index", 69 | "type": "json" 70 | }; 71 | return db.createIndex(index); 72 | }).then(function () { 73 | return db.getIndexes(); 74 | }).then(function (resp) { 75 | var ddoc = resp.indexes[1].ddoc; 76 | ddoc.should.match(/_design\/.+/); 77 | delete resp.indexes[1].ddoc; 78 | resp.should.deep.equal({ 79 | "total_rows": 2, 80 | "indexes": [ 81 | { 82 | "ddoc": null, 83 | "name": "_all_docs", 84 | "type": "special", 85 | "def": { 86 | "fields": [ 87 | { 88 | "_id": "asc" 89 | } 90 | ] 91 | } 92 | }, 93 | { 94 | "name": "foo-index", 95 | "type": "json", 96 | "def": { 97 | "fields": [ 98 | { 99 | "foo": "asc" 100 | } 101 | ] 102 | } 103 | } 104 | ] 105 | }); 106 | }); 107 | }); 108 | }); 109 | }; 110 | -------------------------------------------------------------------------------- /test/test-suite-1/test.combinational.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var should = testUtils.should; 5 | 6 | module.exports = function (dbType, context) { 7 | describe(dbType + ': Combinational', function () { 8 | 9 | describe('$or', function () { 10 | 11 | it('does $or queries', function () { 12 | var db = context.db; 13 | var index = { 14 | "index": { 15 | "fields": ["age"] 16 | }, 17 | "name": "age-index", 18 | "type": "json" 19 | }; 20 | 21 | return db.createIndex(index).then(function () { 22 | return db.bulkDocs([ 23 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 24 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 25 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 26 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 27 | ]); 28 | }).then(function () { 29 | return db.find({ 30 | selector: { 31 | $and:[ 32 | {age:{$gte: 75}}, 33 | {$or: [ 34 | {"name.first": "Nancy"}, 35 | {"name.first": "Mick"} 36 | ]} 37 | ] 38 | } 39 | }); 40 | }).then(function (resp) { 41 | var docs = resp.docs.map(function (doc) { 42 | delete doc._rev; 43 | return doc; 44 | }); 45 | 46 | docs.should.deep.equal([ 47 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 48 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}} 49 | ]); 50 | }); 51 | }); 52 | 53 | it('does $or queries 2', function () { 54 | var db = context.db; 55 | var index = { 56 | "index": { 57 | "fields": ["_id"] 58 | }, 59 | "name": "age-index", 60 | "type": "json" 61 | }; 62 | 63 | return db.createIndex(index).then(function () { 64 | return db.bulkDocs([ 65 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 66 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 67 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 68 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 69 | { _id: '5', age: 40, name: {first: 'Dave', surname: 'Grohl'}} 70 | ]); 71 | }).then(function () { 72 | return db.find({ 73 | selector: { 74 | $and:[ 75 | {_id:{$gte: '0'}}, 76 | {$or: [ 77 | {"name.first": "Nancy"}, 78 | {age : {$lte: 40}} 79 | ]} 80 | ] 81 | } 82 | }); 83 | }).then(function (resp) { 84 | var docs = resp.docs.map(function (doc) { 85 | delete doc._rev; 86 | return doc; 87 | }); 88 | 89 | docs.should.deep.equal([ 90 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 91 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 92 | { _id: '5', age: 40, name: {first: 'Dave', surname: 'Grohl'}} 93 | ]); 94 | }); 95 | }); 96 | 97 | }); 98 | 99 | describe('$nor', function () { 100 | 101 | it('does $nor queries', function () { 102 | var db = context.db; 103 | var index = { 104 | "index": { 105 | "fields": ["age"] 106 | }, 107 | "name": "age-index", 108 | "type": "json" 109 | }; 110 | 111 | return db.createIndex(index).then(function () { 112 | return db.bulkDocs([ 113 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 114 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 115 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 116 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 117 | ]); 118 | }).then(function () { 119 | return db.find({ 120 | selector: { 121 | $and:[ 122 | {age:{$gte: 75}}, 123 | {$nor: [ 124 | {"name.first": "Nancy"}, 125 | {"name.first": "Mick"} 126 | ]} 127 | ] 128 | } 129 | }); 130 | }).then(function (resp) { 131 | var docs = resp.docs.map(function (doc) { 132 | delete doc._rev; 133 | return doc; 134 | }); 135 | 136 | docs.should.deep.equal([ 137 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 138 | ]); 139 | }); 140 | }); 141 | 142 | it('does $nor queries 2', function () { 143 | var db = context.db; 144 | var index = { 145 | "index": { 146 | "fields": ["_id"] 147 | }, 148 | "name": "age-index", 149 | "type": "json" 150 | }; 151 | 152 | return db.createIndex(index).then(function () { 153 | return db.bulkDocs([ 154 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 155 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 156 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 157 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 158 | { _id: '5', age: 40, name: {first: 'Dave', surname: 'Grohl'}} 159 | ]); 160 | }).then(function () { 161 | return db.find({ 162 | selector: { 163 | $and:[ 164 | {_id:{$lte: '6'}}, 165 | {$nor: [ 166 | {"name.first": "Nancy"}, 167 | {age : {$lte: 40}} 168 | ]} 169 | ] 170 | } 171 | }); 172 | }).then(function (resp) { 173 | var docs = resp.docs.map(function (doc) { 174 | delete doc._rev; 175 | return doc; 176 | }); 177 | 178 | docs.should.deep.equal([ 179 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 180 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 181 | ]); 182 | }); 183 | }); 184 | 185 | it('handles $or/$nor typos', function () { 186 | var db = context.db; 187 | var index = { 188 | "index": { 189 | "fields": ["_id"] 190 | }, 191 | "name": "age-index", 192 | "type": "json" 193 | }; 194 | 195 | return db.createIndex(index).then(function () { 196 | return db.bulkDocs([ 197 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 198 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 199 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 200 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 201 | { _id: '5', age: 40, name: {first: 'Dave', surname: 'Grohl'}} 202 | ]); 203 | }).then(function () { 204 | return db.find({ 205 | selector: { 206 | $and:[ 207 | {_id:{$lte: '6'}}, 208 | {$noor: [ 209 | {"name.first": "Nancy"}, 210 | {age : {$lte: 40}} 211 | ]} 212 | ] 213 | } 214 | }); 215 | }).then(function () { 216 | throw new Error('expected an error'); 217 | }, function (err) { 218 | should.exist(err); 219 | }); 220 | }); 221 | 222 | }); 223 | }); 224 | 225 | }; 226 | -------------------------------------------------------------------------------- /test/test-suite-1/test.ddoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var should = testUtils.should; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': ddoc', function () { 9 | 10 | it('should create an index', function () { 11 | var db = context.db; 12 | var index = { 13 | index: { 14 | fields: ["foo"] 15 | }, 16 | name: "foo-index", 17 | type: "json", 18 | ddoc: 'foo' 19 | }; 20 | return db.createIndex(index).then(function (response) { 21 | response.id.should.match(/^_design\//); 22 | response.name.should.equal('foo-index'); 23 | response.result.should.equal('created'); 24 | return db.createIndex(index); 25 | }).then(function (response) { 26 | response.id.should.match(/^_design\//); 27 | response.name.should.equal('foo-index'); 28 | response.result.should.equal('exists'); 29 | return db.getIndexes(); 30 | }).then(function (resp) { 31 | resp.should.deep.equal({ 32 | "total_rows": 2, 33 | "indexes": [ 34 | { 35 | "ddoc": null, 36 | "name": "_all_docs", 37 | "type": "special", 38 | "def": { 39 | "fields": [ 40 | { 41 | "_id": "asc" 42 | } 43 | ] 44 | } 45 | }, 46 | { 47 | "ddoc": "_design/foo", 48 | "name": "foo-index", 49 | "type": "json", 50 | "def": { 51 | "fields": [ 52 | { 53 | "foo": "asc" 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | }); 60 | }); 61 | }); 62 | 63 | it('should create an index, existing ddoc', function () { 64 | var db = context.db; 65 | var index = { 66 | index: { 67 | fields: ["foo"] 68 | }, 69 | name: "foo-index", 70 | type: "json", 71 | ddoc: 'foo' 72 | }; 73 | return db.put({ 74 | _id: '_design/foo', 75 | "language": "query" 76 | }).then(function () { 77 | return db.createIndex(index); 78 | }).then(function (response) { 79 | response.id.should.match(/^_design\//); 80 | response.name.should.equal('foo-index'); 81 | response.result.should.equal('created'); 82 | return db.createIndex(index); 83 | }).then(function (response) { 84 | response.id.should.match(/^_design\//); 85 | response.name.should.equal('foo-index'); 86 | response.result.should.equal('exists'); 87 | return db.getIndexes(); 88 | }).then(function (resp) { 89 | resp.should.deep.equal({ 90 | "total_rows": 2, 91 | "indexes": [ 92 | { 93 | "ddoc": null, 94 | "name": "_all_docs", 95 | "type": "special", 96 | "def": { 97 | "fields": [ 98 | { 99 | "_id": "asc" 100 | } 101 | ] 102 | } 103 | }, 104 | { 105 | "ddoc": "_design/foo", 106 | "name": "foo-index", 107 | "type": "json", 108 | "def": { 109 | "fields": [ 110 | { 111 | "foo": "asc" 112 | } 113 | ] 114 | } 115 | } 116 | ] 117 | }); 118 | }); 119 | }); 120 | 121 | it('should create an index, reused ddoc', function () { 122 | var db = context.db; 123 | var index = { 124 | index: { 125 | fields: ["foo"] 126 | }, 127 | name: "foo-index", 128 | type: "json", 129 | ddoc: 'myddoc' 130 | }; 131 | var index2 = { 132 | index: { 133 | fields: ['bar'] 134 | }, 135 | name: "bar-index", 136 | ddoc: 'myddoc' 137 | }; 138 | return db.createIndex(index).then(function (response) { 139 | response.id.should.match(/^_design\//); 140 | response.name.should.equal('foo-index'); 141 | response.result.should.equal('created'); 142 | return db.createIndex(index); 143 | }).then(function (response) { 144 | response.id.should.match(/^_design\//); 145 | response.name.should.equal('foo-index'); 146 | response.result.should.equal('exists'); 147 | return db.createIndex(index2); 148 | }).then(function (response) { 149 | response.id.should.match(/^_design\//); 150 | response.name.should.equal('bar-index'); 151 | response.result.should.equal('created'); 152 | return db.createIndex(index2); 153 | }).then(function (response) { 154 | response.id.should.match(/^_design\//); 155 | response.name.should.equal('bar-index'); 156 | response.result.should.equal('exists'); 157 | }).then(function () { 158 | return db.getIndexes(); 159 | }).then(function (resp) { 160 | resp.should.deep.equal({ 161 | "total_rows":3, 162 | "indexes": [ 163 | { 164 | "ddoc": null, 165 | "name": "_all_docs", 166 | "type": "special", 167 | "def": { 168 | "fields": [ 169 | { 170 | "_id": "asc" 171 | } 172 | ] 173 | } 174 | }, 175 | { 176 | "ddoc": "_design/myddoc", 177 | "name": "bar-index", 178 | "type": "json", 179 | "def": { 180 | "fields": [ 181 | { 182 | "bar": "asc" 183 | } 184 | ] 185 | } 186 | }, 187 | { 188 | "ddoc": "_design/myddoc", 189 | "name": "foo-index", 190 | "type": "json", 191 | "def": { 192 | "fields": [ 193 | { 194 | "foo": "asc" 195 | } 196 | ] 197 | } 198 | } 199 | ] 200 | }); 201 | }); 202 | }); 203 | 204 | it('Error: invalid ddoc lang', function () { 205 | var db = context.db; 206 | var index = { 207 | index: { 208 | fields: ["foo"] 209 | }, 210 | name: "foo-index", 211 | type: "json", 212 | ddoc: 'foo' 213 | }; 214 | return db.put({ 215 | _id: '_design/foo' 216 | }).then(function () { 217 | return db.createIndex(index); 218 | }).then(function () { 219 | throw new Error('shouldnt be here'); 220 | }, function (err) { 221 | should.exist(err); 222 | }); 223 | }); 224 | 225 | it('handles ddoc with no views and ignores it', function () { 226 | var db = context.db; 227 | 228 | return db.put({ 229 | _id: '_design/missing-view', 230 | language: 'query' 231 | }) 232 | .then(function () { 233 | return db.getIndexes(); 234 | }) 235 | .then(function (resp) { 236 | resp.indexes.length.should.equal(1); 237 | }); 238 | 239 | }); 240 | }); 241 | }; 242 | -------------------------------------------------------------------------------- /test/test-suite-1/test.deep-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': deep fields', function () { 6 | 7 | it('deep fields', function () { 8 | var db = context.db; 9 | var index = { 10 | "index": { 11 | "fields": [ 12 | "foo.bar" 13 | ] 14 | }, 15 | "name": "foo-index", 16 | "type": "json" 17 | }; 18 | return db.createIndex(index).then(function () { 19 | return db.bulkDocs([ 20 | {_id: 'doc', foo: {bar: 'a'}}, 21 | ]); 22 | }).then(function () { 23 | return db.find({ 24 | selector: {'foo.bar': 'a'}, 25 | fields: ['_id'] 26 | }); 27 | }).then(function (res) { 28 | res.should.deep.equal({ 29 | "docs": [ 30 | { 31 | "_id": "doc" 32 | } 33 | ] 34 | }); 35 | }); 36 | }); 37 | 38 | it('deeper fields', function () { 39 | var db = context.db; 40 | var index = { 41 | "index": { 42 | "fields": [ 43 | "foo.bar.baz" 44 | ] 45 | }, 46 | "name": "foo-index", 47 | "type": "json" 48 | }; 49 | return db.createIndex(index).then(function () { 50 | return db.bulkDocs([ 51 | {_id: 'doc', foo: {bar: {baz: 'a'}}}, 52 | ]); 53 | }).then(function () { 54 | return db.find({ 55 | selector: {'foo.bar.baz': 'a'}, 56 | fields: ['_id'] 57 | }); 58 | }).then(function (res) { 59 | res.should.deep.equal({ 60 | "docs": [ 61 | { 62 | "_id": "doc" 63 | } 64 | ] 65 | }); 66 | }); 67 | }); 68 | 69 | it('deep fields escaped', function () { 70 | var db = context.db; 71 | var index = { 72 | "index": { 73 | "fields": [ 74 | "foo\\.bar" 75 | ] 76 | }, 77 | "name": "foo-index", 78 | "type": "json" 79 | }; 80 | return db.createIndex(index).then(function () { 81 | return db.bulkDocs([ 82 | {_id: 'doc1', foo: {bar: 'a'}}, 83 | {_id: 'doc2', 'foo.bar': 'a'} 84 | ]); 85 | }).then(function () { 86 | return db.find({ 87 | selector: {'foo\\.bar': 'a'}, 88 | fields: ['_id'] 89 | }); 90 | }).then(function (res) { 91 | res.should.deep.equal({ 92 | "docs": [{ "_id": "doc2"}] 93 | }); 94 | }); 95 | }); 96 | 97 | it('should create a deep multi mapper', function () { 98 | var db = context.db; 99 | var index = { 100 | "index": { 101 | "fields": [ 102 | "foo.bar", "bar.baz" 103 | ] 104 | } 105 | }; 106 | return db.createIndex(index).then(function () { 107 | return db.bulkDocs([ 108 | {_id: 'a', foo: {bar: 'yo'}, bar: {baz: 'hey'}}, 109 | {_id: 'b', foo: {bar: 'sup'}, bar: {baz: 'dawg'}} 110 | ]); 111 | }).then(function () { 112 | return db.find({ 113 | selector: {"foo.bar": 'yo', "bar.baz": 'hey'}, 114 | fields: ['_id'] 115 | }); 116 | }).then(function (res) { 117 | res.docs.should.deep.equal([{_id: 'a'}]); 118 | return db.find({ 119 | selector: {"foo.bar": 'yo', "bar.baz": 'sup'}, 120 | fields: ['_id'] 121 | }); 122 | }).then(function (res) { 123 | res.docs.should.have.length(0); 124 | return db.find({ 125 | selector: {"foo.bar": 'bruh', "bar.baz": 'nah'}, 126 | fields: ['_id'] 127 | }); 128 | }).then(function (res) { 129 | res.docs.should.have.length(0); 130 | }); 131 | }); 132 | 133 | it('should create a deep multi mapper, tricky docs', function () { 134 | var db = context.db; 135 | var index = { 136 | "index": { 137 | "fields": [ 138 | "foo.bar", "bar.baz" 139 | ] 140 | } 141 | }; 142 | return db.createIndex(index).then(function () { 143 | return db.bulkDocs([ 144 | {_id: 'a', foo: {bar: 'yo'}, bar: {baz: 'hey'}}, 145 | {_id: 'b', foo: {bar: 'sup'}, bar: {baz: 'dawg'}}, 146 | {_id: 'c', foo: true, bar: "yo"}, 147 | {_id: 'd', foo: null, bar: []} 148 | ]); 149 | }).then(function () { 150 | return db.find({ 151 | selector: {"foo.bar": 'yo', "bar.baz": 'hey'}, 152 | fields: ['_id'] 153 | }); 154 | }).then(function (res) { 155 | res.docs.should.deep.equal([{_id: 'a'}]); 156 | return db.find({ 157 | selector: {"foo.bar": 'yo', "bar.baz": 'sup'}, 158 | fields: ['_id'] 159 | }); 160 | }).then(function (res) { 161 | res.docs.should.have.length(0); 162 | return db.find({ 163 | selector: {"foo.bar": 'bruh', "bar.baz": 'nah'}, 164 | fields: ['_id'] 165 | }); 166 | }).then(function (res) { 167 | res.docs.should.have.length(0); 168 | }); 169 | }); 170 | }); 171 | }; 172 | -------------------------------------------------------------------------------- /test/test-suite-1/test.elem-match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': $elemMatch', function () { 6 | if (dbType === 'http') { return;} 7 | 8 | beforeEach(function () { 9 | return context.db.bulkDocs([ 10 | {'_id': 'peach', eats: ['cake', 'turnips', 'sweets'], results: [ 82, 85, 88 ]}, 11 | {'_id': 'sonic', eats: ['chili dogs'], results: [ 75, 88, 89 ]}, 12 | {'_id': 'fox', eats: []}, 13 | {'_id': 'mario', eats: ['cake', 'mushrooms']}, 14 | {'_id': 'samus', eats: ['pellets']}, 15 | {'_id': 'kirby', eats: 'anything', results: [ 82, 86, 10 ]} 16 | ]); 17 | }); 18 | 19 | it('basic test', function () { 20 | var db = context.db; 21 | return db.find({ 22 | selector: { 23 | _id: {$gt: 'a'}, 24 | eats: {$elemMatch: {$eq: 'cake'}} 25 | } 26 | }).then(function (resp) { 27 | resp.docs.map(function (doc) { 28 | return doc._id; 29 | }).sort().should.deep.equal(['mario', 'peach']); 30 | }); 31 | }); 32 | 33 | it('basic test with two operators', function () { 34 | var db = context.db; 35 | return db.find({ 36 | selector: { 37 | _id: {$gt: 'a'}, 38 | results: {$elemMatch: {$gte: 80, $lt: 85}} 39 | } 40 | }).then(function (resp) { 41 | resp.docs.map(function (doc) { 42 | return doc._id; 43 | }).should.deep.equal(['kirby', 'peach']); 44 | }); 45 | }); 46 | 47 | it('with object in array', function () { 48 | var db = context.db; 49 | var docs = [ 50 | {_id: '1', events: [{eventId: 1, status: 'completed'}, {eventId: 2, status: 'started'}]}, 51 | {_id: '2', events: [{eventId: 1, status: 'pending'}, {eventId: 2, status: 'finished'}]}, 52 | {_id: '3', events: [{eventId: 1, status: 'pending'}, {eventId: 2, status: 'started'}]}, 53 | ]; 54 | 55 | return db.bulkDocs(docs).then(function () { 56 | return db.find({ 57 | selector: { 58 | _id: {$gt: null}, 59 | events: {$elemMatch: {"status": {$eq: 'pending'}, "eventId": {$eq: 1}}}, 60 | }, 61 | fields: ['_id'] 62 | }).then(function (resp) { 63 | resp.docs.map(function (doc) { 64 | return doc._id; 65 | }).should.deep.equal(['2', '3']); 66 | }); 67 | }); 68 | }); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /test/test-suite-1/test.errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var should = testUtils.should; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': errors', function () { 9 | 10 | it('error: gimme some args', function () { 11 | var db = context.db; 12 | return db.find().then(function () { 13 | throw Error('should not be here'); 14 | }, function (err) { 15 | should.exist(err); 16 | }); 17 | }); 18 | 19 | it('error: missing required key selector', function () { 20 | var db = context.db; 21 | return db.find({}).then(function () { 22 | throw Error('should not be here'); 23 | }, function (err) { 24 | should.exist(err); 25 | }); 26 | }); 27 | 28 | it('error: unsupported mixed sort', function () { 29 | var db = context.db; 30 | var index = { 31 | "index": { 32 | "fields": [ 33 | {"foo": "desc"}, 34 | "bar" 35 | ] 36 | }, 37 | "name": "foo-index", 38 | "type": "json" 39 | }; 40 | return db.createIndex(index).then(function () { 41 | throw new Error('should not be here'); 42 | }, function (err) { 43 | should.exist(err); 44 | }); 45 | }); 46 | 47 | it('error: invalid sort json', function () { 48 | var db = context.db; 49 | var index = { 50 | "index": { 51 | "fields": ["foo"] 52 | }, 53 | "name": "foo-index", 54 | "type": "json" 55 | }; 56 | 57 | return db.createIndex(index).then(function () { 58 | return db.bulkDocs([ 59 | { _id: '1', foo: 'eyo'}, 60 | { _id: '2', foo: 'ebb'}, 61 | { _id: '3', foo: 'eba'}, 62 | { _id: '4', foo: 'abo'} 63 | ]); 64 | }).then(function () { 65 | return db.find({ 66 | selector: {foo: {"$lte": "eba"}}, 67 | fields: ["_id", "foo"], 68 | sort: {foo: "asc"} 69 | }); 70 | }).then(function () { 71 | throw new Error('shouldnt be here'); 72 | }, function (err) { 73 | should.exist(err); 74 | }); 75 | }); 76 | 77 | it('error: conflicting sort and selector', function () { 78 | var db = context.db; 79 | var index = { 80 | "index": { 81 | "fields": ["foo"] 82 | }, 83 | "name": "foo-index", 84 | "type": "json" 85 | }; 86 | return db.createIndex(index).then(function () { 87 | return db.find({ 88 | "selector": {"foo": {"$gt": "\u0000\u0000"}}, 89 | "fields": ["_id", "foo"], 90 | "sort": [{"_id": "asc"}] 91 | }); 92 | }).then(function (res) { 93 | res.warning.should.match(/no matching index found/); 94 | }); 95 | }); 96 | 97 | it('error - no selector', function () { 98 | var db = context.db; 99 | var index = { 100 | "index": { 101 | "fields": ["foo"] 102 | }, 103 | "name": "foo-index", 104 | "type": "json" 105 | }; 106 | return db.createIndex(index).then(function () { 107 | return db.find({ 108 | "fields": ["_id", "foo"], 109 | "sort": [{"foo": "asc"}] 110 | }); 111 | }).then(function () { 112 | throw new Error('shouldnt be here'); 113 | }, function (err) { 114 | should.exist(err); 115 | }); 116 | }); 117 | 118 | it('invalid ddoc', function () { 119 | var db = context.db; 120 | 121 | var index = { 122 | "index": { 123 | "fields": ["foo"] 124 | }, 125 | "name": "foo-index", 126 | "ddoc": "myddoc", 127 | "type": "json" 128 | }; 129 | 130 | return db.put({ 131 | _id: '_design/myddoc', 132 | views: { 133 | 'foo-index': { 134 | map: "function (){emit(1)}" 135 | } 136 | } 137 | }).then(function () { 138 | return db.createIndex(index).then(function () { 139 | throw new Error('expected an error'); 140 | }, function (err) { 141 | should.exist(err); 142 | }); 143 | }); 144 | }); 145 | 146 | it('non-logical errors with no other selector', function () { 147 | var db = context.db; 148 | 149 | return db.createIndex({ 150 | index: { 151 | fields: ['foo'] 152 | } 153 | }).then(function () { 154 | return db.bulkDocs([ 155 | {_id: '1', foo: 1}, 156 | {_id: '2', foo: 2}, 157 | {_id: '3', foo: 3}, 158 | {_id: '4', foo: 4} 159 | ]); 160 | }).then(function () { 161 | return db.find({ 162 | selector: { 163 | foo: {$mod: {gte: 3}} 164 | } 165 | }).then(function () { 166 | throw new Error('expected an error'); 167 | }, function (err) { 168 | should.exist(err); 169 | }); 170 | }); 171 | }); 172 | }); 173 | }; 174 | -------------------------------------------------------------------------------- /test/test-suite-1/test.exists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': exists', function () { 9 | 10 | it('does $exists queries - true', function () { 11 | var db = context.db; 12 | return db.bulkDocs([ 13 | {_id: 'a', foo: 'bar'}, 14 | {_id: 'b', foo: {yo: 'dude'}}, 15 | {_id: 'c', foo: null}, 16 | {_id: 'd'} 17 | ]).then(function () { 18 | return db.find({ 19 | selector: { 20 | _id: { $gt: null}, 21 | 'foo': {'$exists': true} 22 | }, 23 | fields: ['_id'] 24 | }); 25 | }).then(function (res) { 26 | res.docs.sort(sortById); 27 | res.docs.should.deep.equal([ 28 | {"_id": "a"}, 29 | {"_id": "b"}, 30 | {"_id": "c"} 31 | ]); 32 | }); 33 | }); 34 | 35 | it('does $exists queries - false', function () { 36 | var db = context.db; 37 | return db.bulkDocs([ 38 | {_id: 'a', foo: 'bar'}, 39 | {_id: 'b', foo: {yo: 'dude'}}, 40 | {_id: 'c', foo: null}, 41 | {_id: 'd'} 42 | ]).then(function () { 43 | return db.find({ 44 | selector: { 45 | _id: { $gt: null}, 46 | 'foo': {'$exists': false} 47 | }, 48 | fields: ['_id'] 49 | }); 50 | }).then(function (res) { 51 | res.docs.sort(sortById); 52 | res.docs.should.deep.equal([ 53 | {"_id": "d"} 54 | ]); 55 | }); 56 | }); 57 | 58 | it('does $exists queries - true/undef (multi-field)', function () { 59 | var db = context.db; 60 | return db.bulkDocs([ 61 | {_id: 'a', foo: 'bar', bar: 'baz'}, 62 | {_id: 'b', foo: {yo: 'dude'}}, 63 | {_id: 'c', foo: null, bar: 'quux'}, 64 | {_id: 'd'} 65 | ]).then(function () { 66 | return db.find({ 67 | selector: { 68 | _id: { $gt: null}, 69 | 'foo': {'$exists': true} 70 | }, 71 | fields: ['_id'] 72 | }); 73 | }).then(function (res) { 74 | res.docs.sort(sortById); 75 | res.docs.should.deep.equal([ 76 | {"_id": "a"}, 77 | {"_id": "b"}, 78 | {"_id": "c"} 79 | ]); 80 | }); 81 | }); 82 | 83 | it('does $exists queries - $eq/true (multi-field)', function () { 84 | var db = context.db; 85 | var index = { 86 | "index": { 87 | "fields": ["foo"] 88 | }, 89 | "name": "foo-index", 90 | "type": "json" 91 | }; 92 | return db.createIndex(index).then(function () { 93 | return db.bulkDocs([ 94 | {_id: 'a', foo: 'bar', bar: 'baz'}, 95 | {_id: 'b', foo: 'bar', bar: {yo: 'dude'}}, 96 | {_id: 'c', foo: null, bar: 'quux'}, 97 | {_id: 'd'} 98 | ]); 99 | }).then(function () { 100 | return db.find({ 101 | selector: {'foo': 'bar', bar: {$exists: true}}, 102 | fields: ['_id'] 103 | }); 104 | }).then(function (res) { 105 | res.docs.sort(sortById); 106 | res.docs.should.deep.equal([ 107 | {"_id": "a"}, 108 | {"_id": "b"} 109 | ]); 110 | }); 111 | }); 112 | 113 | it('does $exists queries - $eq/false (multi-field)', function () { 114 | var db = context.db; 115 | var index = { 116 | "index": { 117 | "fields": ["foo"] 118 | }, 119 | "name": "foo-index", 120 | "type": "json" 121 | }; 122 | return db.createIndex(index).then(function () { 123 | return db.bulkDocs([ 124 | {_id: 'a', foo: 'bar', bar: 'baz'}, 125 | {_id: 'b', foo: 'bar', bar: {yo: 'dude'}}, 126 | {_id: 'c', foo: 'bar', bar: 'yo'}, 127 | {_id: 'd', foo: 'bar'} 128 | ]); 129 | }).then(function () { 130 | return db.find({ 131 | selector: {'foo': 'bar', bar: {$exists: false}}, 132 | fields: ['_id'] 133 | }); 134 | }).then(function (res) { 135 | res.docs.sort(sortById); 136 | res.docs.should.deep.equal([ 137 | {"_id": "d"} 138 | ]); 139 | }); 140 | }); 141 | 142 | it('does $exists queries - true/true (multi-field)', function () { 143 | var db = context.db; 144 | return db.bulkDocs([ 145 | {_id: 'a', foo: 'bar', bar: 'baz'}, 146 | {_id: 'b', foo: {yo: 'dude'}}, 147 | {_id: 'c', foo: null, bar: 'quux'}, 148 | {_id: 'd'} 149 | ]).then(function () { 150 | return db.find({ 151 | selector: { 152 | _id: {$gt: null}, 153 | foo: {'$exists': true}, 154 | bar: {$exists: true} 155 | }, 156 | fields: ['_id'] 157 | }); 158 | }).then(function (res) { 159 | res.docs.sort(sortById); 160 | res.docs.should.deep.equal([ 161 | {"_id": "a"}, 162 | {"_id": "c"} 163 | ]); 164 | }); 165 | }); 166 | 167 | it('does $exists queries - true/false (multi-field)', function () { 168 | var db = context.db; 169 | return db.bulkDocs([ 170 | {_id: 'a', foo: 'bar', bar: 'baz'}, 171 | {_id: 'b', foo: {yo: 'dude'}}, 172 | {_id: 'c', foo: null, bar: 'quux'}, 173 | {_id: 'd'} 174 | ]).then(function () { 175 | return db.find({ 176 | selector: { 177 | _id: {$gt: null}, 178 | foo: {'$exists': true}, 179 | bar: {$exists: false} 180 | }, 181 | fields: ['_id'] 182 | }); 183 | }).then(function (res) { 184 | res.docs.sort(sortById); 185 | res.docs.should.deep.equal([ 186 | {"_id": "b"} 187 | ]); 188 | }); 189 | }); 190 | }); 191 | }; 192 | -------------------------------------------------------------------------------- /test/test-suite-1/test.fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': fields', function () { 9 | 10 | it('does 2-field queries', function () { 11 | var db = context.db; 12 | var index = { 13 | "index": { 14 | "fields": ["foo", "bar"] 15 | }, 16 | "name": "foo-index", 17 | "type": "json" 18 | }; 19 | return db.createIndex(index).then(function () { 20 | return db.bulkDocs([ 21 | { _id: '1', foo: 'a', bar: 'a'}, 22 | { _id: '2', foo: 'b', bar: 'b'}, 23 | { _id: '3', foo: 'a', bar: 'a'}, 24 | { _id: '4', foo: 'c', bar: 'a'}, 25 | { _id: '5', foo: 'b', bar: 'a'}, 26 | { _id: '6', foo: 'a', bar: 'b'} 27 | ]); 28 | }).then(function () { 29 | return db.find({ 30 | "selector": { 31 | "foo": {"$eq": "b"}, 32 | "bar": {"$eq": "b"} 33 | }, 34 | "fields": ["_id", "foo"] 35 | }); 36 | }).then(function (resp) { 37 | resp.should.deep.equal({ 38 | "docs": [ 39 | { "_id": "2", "foo": "b"} 40 | ] 41 | }); 42 | }); 43 | }); 44 | 45 | it('does 2-field queries eq/gte', function () { 46 | var db = context.db; 47 | var index = { 48 | "index": { 49 | "fields": ["foo", "bar"] 50 | }, 51 | "name": "foo-index", 52 | "type": "json" 53 | }; 54 | return db.createIndex(index).then(function () { 55 | return db.bulkDocs([ 56 | { _id: '1', foo: 'a', bar: 'a'}, 57 | { _id: '2', foo: 'a', bar: 'b'}, 58 | { _id: '3', foo: 'a', bar: 'c'}, 59 | { _id: '4', foo: 'b', bar: 'a'}, 60 | { _id: '5', foo: 'b', bar: 'b'}, 61 | { _id: '6', foo: 'c', bar: 'a'} 62 | ]); 63 | }).then(function () { 64 | return db.find({ 65 | "selector": { 66 | "foo": {"$eq": "a"}, 67 | "bar": {"$gte": "b"} 68 | }, 69 | "fields": ["_id"] 70 | }); 71 | }).then(function (resp) { 72 | resp.docs.sort(sortById); 73 | resp.docs.should.deep.equal([ 74 | { _id: '2' }, 75 | { _id: '3' } 76 | ]); 77 | }); 78 | }); 79 | 80 | it('does 2-field queries gte/gte', function () { 81 | var db = context.db; 82 | var index = { 83 | "index": { 84 | "fields": ["foo", "bar"] 85 | }, 86 | "name": "foo-index", 87 | "type": "json" 88 | }; 89 | return db.createIndex(index).then(function () { 90 | return db.bulkDocs([ 91 | { _id: '1', foo: 'a', bar: 'a'}, 92 | { _id: '2', foo: 'a', bar: 'b'}, 93 | { _id: '3', foo: 'a', bar: 'c'}, 94 | { _id: '4', foo: 'b', bar: 'a'}, 95 | { _id: '5', foo: 'b', bar: 'b'}, 96 | { _id: '6', foo: 'c', bar: 'a'} 97 | ]); 98 | }).then(function () { 99 | return db.find({ 100 | "selector": { 101 | "foo": {"$gte": "b"}, 102 | "bar": {"$gte": "a"} 103 | }, 104 | "fields": ["_id"] 105 | }); 106 | }).then(function (resp) { 107 | resp.docs.sort(sortById); 108 | resp.docs.should.deep.equal([ 109 | { _id: '4' }, 110 | { _id: '5' }, 111 | { _id: '6' } 112 | ]); 113 | }); 114 | }); 115 | 116 | it('does 2-field queries gte/lte', function () { 117 | var db = context.db; 118 | var index = { 119 | "index": { 120 | "fields": ["foo", "bar"] 121 | }, 122 | "name": "foo-index", 123 | "type": "json" 124 | }; 125 | return db.createIndex(index).then(function () { 126 | return db.bulkDocs([ 127 | { _id: '1', foo: 'a', bar: 'a'}, 128 | { _id: '2', foo: 'a', bar: 'b'}, 129 | { _id: '3', foo: 'a', bar: 'c'}, 130 | { _id: '4', foo: 'b', bar: 'a'}, 131 | { _id: '5', foo: 'b', bar: 'b'}, 132 | { _id: '6', foo: 'c', bar: 'a'} 133 | ]); 134 | }).then(function () { 135 | return db.find({ 136 | "selector": { 137 | "foo": {"$gte": "b"}, 138 | "bar": {"$lte": "b"} 139 | }, 140 | "fields": ["_id"] 141 | }); 142 | }).then(function (resp) { 143 | resp.docs.sort(sortById); 144 | resp.docs.should.deep.equal([ 145 | { _id: '4' }, 146 | { _id: '5' }, 147 | { _id: '6' } 148 | ]); 149 | }); 150 | }); 151 | 152 | it('does 3-field queries eq/eq/eq 3-field index', function () { 153 | var db = context.db; 154 | var index = { 155 | "index": { 156 | "fields": ["foo", "bar", "baz"] 157 | }, 158 | "name": "foo-index", 159 | "type": "json" 160 | }; 161 | return db.createIndex(index).then(function () { 162 | return db.bulkDocs([ 163 | { _id: '1', foo: 'a', bar: 'a', baz: 'z'}, 164 | { _id: '2', foo: 'a', bar: 'b', baz: 'z'}, 165 | { _id: '3', foo: 'a', bar: 'c', baz: 'z'}, 166 | { _id: '4', foo: 'b', bar: 'a', baz: 'z'}, 167 | { _id: '5', foo: 'b', bar: 'b', baz: 'z'}, 168 | { _id: '6', foo: 'c', bar: 'a', baz: 'z'} 169 | ]); 170 | }).then(function () { 171 | return db.find({ 172 | "selector": { 173 | foo: 'b', 174 | bar: 'b', 175 | baz: 'z' 176 | }, 177 | "fields": ["_id"] 178 | }); 179 | }).then(function (resp) { 180 | resp.docs.sort(sortById); 181 | resp.docs.should.deep.equal([ 182 | { _id: '5' } 183 | ]); 184 | }); 185 | }); 186 | 187 | it('does 1-field queries eq/eq 2-field index', function () { 188 | var db = context.db; 189 | var index = { 190 | "index": { 191 | "fields": ["foo", "bar"] 192 | }, 193 | "name": "foo-index", 194 | "type": "json" 195 | }; 196 | return db.createIndex(index).then(function () { 197 | return db.bulkDocs([ 198 | { _id: '1', foo: 'a', bar: 'a', baz: 'z'}, 199 | { _id: '2', foo: 'a', bar: 'b', baz: 'z'}, 200 | { _id: '3', foo: 'a', bar: 'c', baz: 'z'}, 201 | { _id: '4', foo: 'b', bar: 'a', baz: 'z'}, 202 | { _id: '5', foo: 'b', bar: 'b', baz: 'z'}, 203 | { _id: '6', foo: 'c', bar: 'a', baz: 'z'} 204 | ]); 205 | }).then(function () { 206 | return db.find({ 207 | "selector": { 208 | foo: 'b' 209 | }, 210 | "fields": ["_id"] 211 | }); 212 | }).then(function (resp) { 213 | resp.docs.sort(sortById); 214 | resp.docs.should.deep.equal([ 215 | { _id: '4' }, 216 | { _id: '5' } 217 | ]); 218 | }); 219 | }); 220 | 221 | it('does 2-field queries eq/eq 3-field index', function () { 222 | var db = context.db; 223 | var index = { 224 | "index": { 225 | "fields": ["foo", "bar", "baz"] 226 | }, 227 | "name": "foo-index", 228 | "type": "json" 229 | }; 230 | return db.createIndex(index).then(function () { 231 | return db.bulkDocs([ 232 | { _id: '1', foo: 'a', bar: 'a', baz: 'z'}, 233 | { _id: '2', foo: 'a', bar: 'b', baz: 'z'}, 234 | { _id: '3', foo: 'a', bar: 'c', baz: 'z'}, 235 | { _id: '4', foo: 'b', bar: 'a', baz: 'z'}, 236 | { _id: '5', foo: 'b', bar: 'b', baz: 'z'}, 237 | { _id: '6', foo: 'c', bar: 'a', baz: 'z'} 238 | ]); 239 | }).then(function () { 240 | return db.find({ 241 | "selector": { 242 | foo: 'b', 243 | bar: 'b' 244 | }, 245 | "fields": ["_id"] 246 | }); 247 | }).then(function (resp) { 248 | resp.docs.sort(sortById); 249 | resp.docs.should.deep.equal([ 250 | { _id: '5' } 251 | ]); 252 | }); 253 | }); 254 | }); 255 | }; -------------------------------------------------------------------------------- /test/test-suite-1/test.limit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': limit', function () { 9 | 10 | beforeEach(function () { 11 | return context.db.bulkDocs([ 12 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981 }, 13 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996 }, 14 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986 }, 15 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981 }, 16 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 17 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990 }, 18 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983 }, 19 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993 }, 20 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994 }, 21 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986 }, 22 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990 }, 23 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992 } 24 | ]); 25 | }); 26 | 27 | it('should work with $and 1 limit 0', function () { 28 | var db = context.db; 29 | return db.createIndex({ 30 | "index": { 31 | "fields": ["series"] 32 | } 33 | }).then(function () { 34 | return db.createIndex({ 35 | "index": { 36 | "fields": ["debut"] 37 | } 38 | }); 39 | }).then(function () { 40 | return db.find({ 41 | selector: { 42 | $and: [ 43 | {series: 'Mario'}, 44 | {debut: {$gte: 1982}} 45 | ] 46 | }, 47 | fields: ['_id'], 48 | limit: 0 49 | }); 50 | }).then(function (res) { 51 | res.docs.sort(sortById); 52 | res.docs.should.deep.equal([]); 53 | }); 54 | }); 55 | 56 | it('should work with $and 1 limit 1', function () { 57 | var db = context.db; 58 | return db.createIndex({ 59 | "index": { 60 | "fields": ["series"] 61 | } 62 | }).then(function () { 63 | return db.createIndex({ 64 | "index": { 65 | "fields": ["debut"] 66 | } 67 | }); 68 | }).then(function () { 69 | return db.find({ 70 | selector: { 71 | $and: [ 72 | {series: 'Mario'}, 73 | {debut: {$gte: 1982}} 74 | ] 75 | }, 76 | fields: ['_id'], 77 | limit: 1 78 | }); 79 | }).then(function (res) { 80 | res.docs.sort(sortById); 81 | res.docs.should.deep.equal([{_id: 'luigi'}]); 82 | }); 83 | }); 84 | 85 | it('should work with $and 1 limit 2', function () { 86 | var db = context.db; 87 | return db.createIndex({ 88 | "index": { 89 | "fields": ["series"] 90 | } 91 | }).then(function () { 92 | return db.createIndex({ 93 | "index": { 94 | "fields": ["debut"] 95 | } 96 | }); 97 | }).then(function () { 98 | return db.find({ 99 | selector: { 100 | $and: [ 101 | {series: 'Mario'}, 102 | {debut: {$gte: 1982}} 103 | ] 104 | }, 105 | fields: ['_id'], 106 | limit: 2 107 | }); 108 | }).then(function (res) { 109 | res.docs.sort(sortById); 110 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 111 | }); 112 | }); 113 | 114 | it('should work with $and 2, same index limit 0', function () { 115 | var db = context.db; 116 | return db.createIndex({ 117 | "index": { 118 | "fields": ["series", "debut"] 119 | } 120 | }).then(function () { 121 | return db.find({ 122 | selector: { 123 | $and: [ 124 | {series: 'Mario'}, 125 | {debut: {$gte: 1982}} 126 | ] 127 | }, 128 | fields: ['_id'], 129 | limit: 0 130 | }); 131 | }).then(function (res) { 132 | res.docs.sort(sortById); 133 | res.docs.should.deep.equal([]); 134 | }); 135 | }); 136 | 137 | it('should work with $and 2, same index limit 1', function () { 138 | var db = context.db; 139 | return db.createIndex({ 140 | "index": { 141 | "fields": ["series", "debut"] 142 | } 143 | }).then(function () { 144 | return db.find({ 145 | selector: { 146 | $and: [ 147 | {series: 'Mario'}, 148 | {debut: {$gte: 1982}} 149 | ] 150 | }, 151 | fields: ['_id'], 152 | limit: 1 153 | }); 154 | }).then(function (res) { 155 | res.docs.sort(sortById); 156 | res.docs.should.deep.equal([{_id: 'luigi'}]); 157 | }); 158 | }); 159 | 160 | it('should work with $and 2, same index limit 2', function () { 161 | var db = context.db; 162 | return db.createIndex({ 163 | "index": { 164 | "fields": ["series", "debut"] 165 | } 166 | }).then(function () { 167 | return db.find({ 168 | selector: { 169 | $and: [ 170 | {series: 'Mario'}, 171 | {debut: {$gte: 1982}} 172 | ] 173 | }, 174 | fields: ['_id'], 175 | limit: 2 176 | }); 177 | }).then(function (res) { 178 | res.docs.sort(sortById); 179 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 180 | }); 181 | }); 182 | 183 | it('should work with $and 3, index/no-index limit 0', function () { 184 | var db = context.db; 185 | return db.createIndex({ 186 | "index": { 187 | "fields": ["series"] 188 | } 189 | }).then(function () { 190 | return db.createIndex({ 191 | "index": { 192 | "fields": ["rank"] 193 | } 194 | }); 195 | }).then(function () { 196 | return db.find({ 197 | selector: { 198 | $and: [ 199 | {series: 'Mario'}, 200 | {debut: {$gte: 1982}} 201 | ] 202 | }, 203 | fields: ['_id'], 204 | limit: 0 205 | }); 206 | }).then(function (res) { 207 | res.docs.sort(sortById); 208 | res.docs.should.deep.equal([]); 209 | }); 210 | }); 211 | 212 | it('should work with $and 3, index/no-index limit 1', function () { 213 | var db = context.db; 214 | return db.createIndex({ 215 | "index": { 216 | "fields": ["series"] 217 | } 218 | }).then(function () { 219 | return db.createIndex({ 220 | "index": { 221 | "fields": ["rank"] 222 | } 223 | }); 224 | }).then(function () { 225 | return db.find({ 226 | selector: { 227 | $and: [ 228 | {series: 'Mario'}, 229 | {debut: {$gte: 1982}} 230 | ] 231 | }, 232 | fields: ['_id'], 233 | limit: 1 234 | }); 235 | }).then(function (res) { 236 | res.docs.sort(sortById); 237 | res.docs.should.deep.equal([{_id: 'luigi'}]); 238 | }); 239 | }); 240 | 241 | it('should work with $and 3, index/no-index limit 2', function () { 242 | var db = context.db; 243 | return db.createIndex({ 244 | "index": { 245 | "fields": ["series"] 246 | } 247 | }).then(function () { 248 | return db.createIndex({ 249 | "index": { 250 | "fields": ["rank"] 251 | } 252 | }); 253 | }).then(function () { 254 | return db.find({ 255 | selector: { 256 | $and: [ 257 | {series: 'Mario'}, 258 | {debut: {$gte: 1983}} 259 | ] 260 | }, 261 | fields: ['_id'], 262 | limit: 2 263 | }); 264 | }).then(function (res) { 265 | res.docs.sort(sortById); 266 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 267 | }); 268 | }); 269 | 270 | it('should work with $and 4, wrong index', function () { 271 | var db = context.db; 272 | return db.createIndex({ 273 | "index": { 274 | "fields": ["rank"] 275 | } 276 | }).then(function () { 277 | return db.find({ 278 | selector: { 279 | $and: [ 280 | {series: 'Mario'}, 281 | {debut: {$gte: 1990}} 282 | ] 283 | }, 284 | fields: ['_id'], 285 | limit: 1 286 | }).then(function (resp) { 287 | resp.should.deep.equal({ 288 | warning: 'no matching index found, create an index to optimize query time', 289 | docs: [ 290 | {_id: 'yoshi'} 291 | ] 292 | }); 293 | }); 294 | }); 295 | }); 296 | }); 297 | }; 298 | -------------------------------------------------------------------------------- /test/test-suite-1/test.matching-indexes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': set-operations', function () { 9 | 10 | beforeEach(function () { 11 | return context.db.bulkDocs([ 12 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981 }, 13 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996 }, 14 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986 }, 15 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981 }, 16 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 17 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990 }, 18 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983 }, 19 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993 }, 20 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994 }, 21 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986 }, 22 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990 }, 23 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992 } 24 | ]); 25 | }); 26 | 27 | it('should pick a better matching index 1', function () { 28 | var db = context.db; 29 | return db.createIndex({ 30 | "index": { 31 | "fields": ["series"] 32 | } 33 | }).then(function () { 34 | return db.createIndex({ 35 | "index": { 36 | "fields": ["series", "debut"] 37 | } 38 | }); 39 | }).then(function () { 40 | return db.createIndex({ 41 | "index": { 42 | "fields": ["debut"] 43 | } 44 | }); 45 | }).then(function () { 46 | return db.find({ 47 | selector: { 48 | $and: [ 49 | {series: 'Mario'}, 50 | {debut: {$gte: 1983}} 51 | ] 52 | }, 53 | fields: ['_id'] 54 | }); 55 | }).then(function (res) { 56 | res.docs.sort(sortById); 57 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 58 | }); 59 | }); 60 | 61 | }); 62 | }; -------------------------------------------------------------------------------- /test/test-suite-1/test.mod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var should = testUtils.should; 5 | 6 | module.exports = function (dbType, context) { 7 | describe(dbType + ': $mod', function () { 8 | 9 | beforeEach(function () { 10 | return context.db.bulkDocs([ 11 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981, awesome: true }, 12 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996, 13 | awesome: false }, 14 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986, awesome: true }, 15 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981, awesome: false }, 16 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996, awesome: true }, 17 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990, 18 | awesome: true }, 19 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983, awesome: false }, 20 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993, awesome: true }, 21 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994, awesome: true }, 22 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986, awesome: true }, 23 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990, awesome: true }, 24 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992, awesome: true }, 25 | { name: 'Master Hand', _id: 'master_hand', series: 'Smash Bros', rank: 0, debut: 1999, 26 | awesome: false } 27 | ]); 28 | }); 29 | 30 | it('should get all even values', function () { 31 | var db = context.db; 32 | var index = { 33 | "index": { 34 | "fields": ["name"] 35 | } 36 | }; 37 | return db.createIndex(index).then(function () { 38 | return db.find({ 39 | selector: { 40 | name: {$gte: null}, 41 | rank: {$mod: [2, 0]} 42 | }, 43 | sort: ['name'] 44 | }).then(function (resp) { 45 | var docs = resp.docs.map(function (doc) { 46 | delete doc._rev; 47 | return doc; 48 | }); 49 | 50 | docs.should.deep.equal([ 51 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990, 52 | awesome: true }, 53 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996, 54 | awesome: false }, 55 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992, awesome: true }, 56 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986, awesome: true }, 57 | { name: 'Master Hand', _id: 'master_hand', series: 'Smash Bros', rank: 0, debut: 1999, 58 | awesome: false }, 59 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986, 60 | awesome: true }, 61 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990, awesome: true }, 62 | ]); 63 | }); 64 | }); 65 | }); 66 | 67 | it('should return error for zero divisor', function () { 68 | var db = context.db; 69 | var index = { 70 | "index": { 71 | "fields": ["name"] 72 | } 73 | }; 74 | return db.createIndex(index).then(function () { 75 | return db.find({ 76 | selector: { 77 | name: {$gte: null}, 78 | rank: {$mod: [0, 0]} 79 | }, 80 | sort: ['name'] 81 | }).then(function () { 82 | throw new Error('expected an error here'); 83 | }, function (err) { 84 | if (dbType === 'http') { 85 | should.exist(err); 86 | return; 87 | } 88 | 89 | err.message.should.match(/Bad divisor/); 90 | }); 91 | }); 92 | }); 93 | 94 | it('should return error for non-integer divisor', function () { 95 | var db = context.db; 96 | var index = { 97 | "index": { 98 | "fields": ["name"] 99 | } 100 | }; 101 | return db.createIndex(index).then(function () { 102 | return db.find({ 103 | selector: { 104 | name: {$gte: null}, 105 | rank: {$mod: ['a', 0]} 106 | }, 107 | sort: ['name'] 108 | }).then(function () { 109 | throw new Error('expected an error here'); 110 | }, function (err) { 111 | if (dbType === 'http') { 112 | should.exist(err); 113 | return; 114 | } 115 | 116 | err.message.should.match(/Divisor is not an integer/); 117 | }); 118 | }); 119 | }); 120 | 121 | it('should return error for non-integer modulus', function () { 122 | var db = context.db; 123 | var index = { 124 | "index": { 125 | "fields": ["name"] 126 | } 127 | }; 128 | return db.createIndex(index).then(function () { 129 | return db.find({ 130 | selector: { 131 | name: {$gte: null}, 132 | rank: {$mod: [1, 'a']} 133 | }, 134 | sort: ['name'] 135 | }).then(function () { 136 | throw new Error('expected an error here'); 137 | }, function (err) { 138 | if (dbType === 'http') { 139 | should.exist(err); 140 | return; 141 | } 142 | 143 | err.message.should.match(/Modulus is not an integer/); 144 | }); 145 | }); 146 | }); 147 | 148 | it('should return empty docs for non-integer field', function () { 149 | var db = context.db; 150 | var index = { 151 | "index": { 152 | "fields": ["name"] 153 | } 154 | }; 155 | return db.createIndex(index).then(function () { 156 | return db.find({ 157 | selector: { 158 | name: {$gte: null}, 159 | awesome: {$mod: [2, 0]} 160 | }, 161 | sort: ['name'] 162 | }).then(function (resp) { 163 | var docs = resp.docs.map(function (doc) { 164 | delete doc._rev; 165 | return doc; 166 | }); 167 | 168 | docs.should.deep.equal([]); 169 | }); 170 | }); 171 | }); 172 | }); 173 | }; 174 | -------------------------------------------------------------------------------- /test/test-suite-1/test.ne.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': ne', function () { 6 | 7 | it('#7 does ne queries 1', function () { 8 | var db = context.db; 9 | var index = { 10 | "index": { 11 | "fields": ["foo"] 12 | } 13 | }; 14 | 15 | return db.createIndex(index).then(function () { 16 | return db.bulkDocs([ 17 | { _id: '1', foo: 'eyo', bar: 'zxy'}, 18 | { _id: '2', foo: 'ebb', bar: 'zxy'}, 19 | { _id: '3', foo: 'eba', bar: 'zxz'}, 20 | { _id: '4', foo: 'abo', bar: 'zxz'} 21 | ]); 22 | }).then(function () { 23 | return db.find({ 24 | selector: {foo: {$gt: "a"}, bar: {$ne: 'zxy'}}, 25 | fields: ["_id"], 26 | sort: [{foo: "asc"}] 27 | }); 28 | }).then(function (resp) { 29 | resp.should.deep.equal({ 30 | docs: [ 31 | { _id: '4'}, 32 | { _id: '3'} 33 | ] 34 | }); 35 | }); 36 | }); 37 | 38 | it('#7 does ne queries 2', function () { 39 | var db = context.db; 40 | var index = { 41 | "index": { 42 | "fields": ["foo", "bar"] 43 | } 44 | }; 45 | 46 | return db.createIndex(index).then(function () { 47 | return db.bulkDocs([ 48 | {_id: '1', foo: 'eyo', bar: 'zxy'}, 49 | {_id: '2', foo: 'ebb', bar: 'zxy'}, 50 | {_id: '3', foo: 'eba', bar: 'zxz'}, 51 | {_id: '4', foo: 'abo', bar: 'zxz'} 52 | ]); 53 | }).then(function () { 54 | return db.find({ 55 | selector: {foo: {$gt: "a"}, bar: {$ne: 'zxy'}}, 56 | fields: ["_id"], 57 | sort: [{foo: "asc"}] 58 | }); 59 | }).then(function (resp) { 60 | resp.should.deep.equal({ 61 | docs: [ 62 | {_id: '4'}, 63 | {_id: '3'} 64 | ] 65 | }); 66 | }); 67 | }); 68 | 69 | it('$ne/$eq inconsistency', function () { 70 | var db = context.db; 71 | 72 | function normalize(res) { 73 | return res.docs.map(function getId(x) { 74 | return x._id; 75 | }).sort(); 76 | } 77 | 78 | return db.createIndex({ 79 | index: { 80 | fields: ['foo'] 81 | } 82 | }).then(function () { 83 | return db.bulkDocs([ 84 | {_id: '1', foo: 1}, 85 | {_id: '2', foo: 2}, 86 | {_id: '3', foo: 3}, 87 | {_id: '4', foo: 4} 88 | ]); 89 | }).then(function () { 90 | return db.find({ 91 | selector: {$and: [{foo: {$eq: 1}}, {foo: {$ne: 1}}]} 92 | }); 93 | }).then(function (res) { 94 | normalize(res).should.deep.equal([]); 95 | }); 96 | }); 97 | 98 | it('$ne/$eq consistency', function () { 99 | var db = context.db; 100 | 101 | function normalize(res) { 102 | return res.docs.map(function getId(x) { 103 | return x._id; 104 | }).sort(); 105 | } 106 | 107 | return db.createIndex({ 108 | index: { 109 | fields: ['foo'] 110 | } 111 | }).then(function () { 112 | return db.bulkDocs([ 113 | {_id: '1', foo: 1}, 114 | {_id: '2', foo: 2}, 115 | {_id: '3', foo: 3}, 116 | {_id: '4', foo: 4} 117 | ]); 118 | }).then(function () { 119 | return db.find({ 120 | selector: {$and: [{foo: {$eq: 1}}, {foo: {$ne: 3}}]} 121 | }); 122 | }).then(function (res) { 123 | normalize(res).should.deep.equal(['1']); 124 | }); 125 | }); 126 | 127 | it('does ne queries with gt', function () { 128 | var db = context.db; 129 | return db.bulkDocs([ 130 | { name: 'mario', _id: 'mario', rank: 5, series: 'mario', debut: 1981 }, 131 | { name: 'jigglypuff', _id: 'puff', rank: 8, series: 'pokemon', debut: 1996 }, 132 | { name: 'link', rank: 10, _id: 'link', series: 'zelda', debut: 1986 }, 133 | { name: 'donkey kong', rank: 7, _id: 'dk', series: 'mario', debut: 1981 }, 134 | { name: 'pikachu', series: 'pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 135 | { name: 'captain falcon', _id: 'falcon', rank: 4, series: 'f-zero', debut: 1990 }, 136 | { name: 'luigi', rank: 11, _id: 'luigi', series: 'mario', debut: 1983 }, 137 | { name: 'fox', _id: 'fox', rank: 3, series: 'star fox', debut: 1993 }, 138 | { name: 'ness', rank: 9, _id: 'ness', series: 'earthbound', debut: 1994 }, 139 | { name: 'samus', rank: 12, _id: 'samus', series: 'metroid', debut: 1986 }, 140 | { name: 'yoshi', _id: 'yoshi', rank: 6, series: 'mario', debut: 1990 }, 141 | { name: 'kirby', _id: 'kirby', series: 'kirby', rank: 2, debut: 1992 } 142 | ]).then(function () { 143 | return db.find({ 144 | selector: { 145 | $and: [ 146 | {_id: {$ne: "samus"}}, 147 | {_id: {$ne: "yoshia"}}, 148 | {_id: {$gt: "fox"}} 149 | ] 150 | }, 151 | fields: ["_id"], 152 | }); 153 | }).then(function (resp) { 154 | resp.should.deep.equal({ 155 | docs: [ 156 | {_id: 'kirby'}, 157 | {_id: 'link'}, 158 | {_id: 'luigi'}, 159 | {_id: 'mario'}, 160 | {_id: 'ness'}, 161 | {_id: 'pikachu'}, 162 | {_id: 'puff'}, 163 | {_id: 'yoshi'} 164 | ] 165 | }); 166 | }); 167 | }); 168 | 169 | }); 170 | }; 171 | -------------------------------------------------------------------------------- /test/test-suite-1/test.not.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': $not', function () { 6 | 7 | it('works with simple syntax', function () { 8 | var db = context.db; 9 | var index = { 10 | "index": { 11 | "fields": ["age"] 12 | }, 13 | "name": "age-index", 14 | "type": "json" 15 | }; 16 | 17 | return db.createIndex(index).then(function () { 18 | return db.bulkDocs([ 19 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 20 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 21 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 22 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 23 | ]); 24 | }).then(function () { 25 | return db.find({ 26 | selector: { 27 | age:{$gte: 40}, 28 | $not:{age: 75}, 29 | } 30 | }); 31 | }).then(function (resp) { 32 | var docs = resp.docs.map(function (doc) { 33 | delete doc._rev; 34 | return doc; 35 | }); 36 | 37 | docs.should.deep.equal([ 38 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 39 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 40 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}} 41 | ]); 42 | }); 43 | }); 44 | 45 | it('works with $and', function () { 46 | var db = context.db; 47 | var index = { 48 | "index": { 49 | "fields": ["age"] 50 | }, 51 | "name": "age-index", 52 | "type": "json" 53 | }; 54 | 55 | return db.createIndex(index).then(function () { 56 | return db.bulkDocs([ 57 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 58 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 59 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 60 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 61 | ]); 62 | }).then(function () { 63 | return db.find({ 64 | selector: { 65 | $and: [ 66 | {age:{$gte: 40}}, 67 | {$not:{age: {$eq: 75}}}, 68 | ] 69 | } 70 | }); 71 | }).then(function (resp) { 72 | var docs = resp.docs.map(function (doc) { 73 | delete doc._rev; 74 | return doc; 75 | }); 76 | 77 | docs.should.deep.equal([ 78 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 79 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 80 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}} 81 | ]); 82 | }); 83 | }); 84 | 85 | it('works with another combinational field', function () { 86 | var db = context.db; 87 | var index = { 88 | "index": { 89 | "fields": ["age"] 90 | }, 91 | "name": "age-index", 92 | "type": "json" 93 | }; 94 | 95 | return db.createIndex(index).then(function () { 96 | return db.bulkDocs([ 97 | { _id: '1', age: 75, name: {first: 'Nancy', surname: 'Sinatra'}}, 98 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 99 | { _id: '3', age: 80, name: {first: 'John', surname: 'Fogerty'}}, 100 | { _id: '4', age: 76, name: {first: 'Mick', surname: 'Jagger'}}, 101 | ]); 102 | }).then(function () { 103 | return db.find({ 104 | selector: { 105 | $and: [ 106 | {age:{$gte: 0}}, 107 | {$not:{age: {$eq: 75}}}, 108 | {$or: [ 109 | {"name.first": "Eddie"}, 110 | ]} 111 | ] 112 | } 113 | }); 114 | }).then(function (resp) { 115 | var docs = resp.docs.map(function (doc) { 116 | delete doc._rev; 117 | return doc; 118 | }); 119 | 120 | docs.should.deep.equal([ 121 | { _id: '2', age: 40, name: {first: 'Eddie', surname: 'Vedder'}}, 122 | ]); 123 | }); 124 | }); 125 | }); 126 | }; 127 | -------------------------------------------------------------------------------- /test/test-suite-1/test.pick-fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | 5 | describe(dbType + ': pick fields', function () { 6 | 7 | it('should pick shallow fields', function () { 8 | var db = context.db; 9 | return db.bulkDocs([ 10 | { name: 'Mario', _id: 'mario', series: 'Mario', debut: {year: 1981, month: 'May'} }, 11 | { name: 'Jigglypuff', _id: 'puff', series: 'Pokemon', debut: {year: 1996, month: 'June'} }, 12 | { name: 'Link', _id: 'link', series: 'Zelda', debut: {year: 1986, month: 'July'} }, 13 | { name: 'Donkey Kong', _id: 'dk', series: 'Mario', debut: {year: 1981, month: 'April'} }, 14 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', 15 | debut: {year: 1996, month: 'September'} }, 16 | { name: 'Captain Falcon', _id: 'falcon', series: 'F-Zero', 17 | debut: {year: 1990, month: 'December'} } 18 | ]).then(function () { 19 | return db.find({ 20 | selector: {_id: {$gt: null}}, 21 | sort: ['_id'], 22 | fields: ['name'] 23 | }); 24 | }).then(function (res) { 25 | res.docs.should.deep.equal([ 26 | { name: 'Donkey Kong' }, 27 | { name: 'Captain Falcon' }, 28 | { name: 'Link' }, 29 | { name: 'Mario' }, 30 | { name: 'Pikachu' }, 31 | { name: 'Jigglypuff' } ]); 32 | }); 33 | }); 34 | 35 | it('should pick deep fields', function () { 36 | var db = context.db; 37 | return db.bulkDocs([ 38 | {_id: 'a', foo: {bar: 'yo'}, bar: {baz: 'hey'}}, 39 | {_id: 'b', foo: {bar: 'sup'}, bar: {baz: 'dawg'}}, 40 | {_id: 'c', foo: true, bar: "yo"}, 41 | {_id: 'd', foo: null, bar: []} 42 | ]).then(function () { 43 | return db.find({ 44 | selector: {_id: {$gt: null}}, 45 | sort: ['_id'], 46 | fields: ['_id', 'bar.baz'] 47 | }); 48 | }).then(function (res) { 49 | res.docs.should.deep.equal([ 50 | { _id: 'a', bar: { baz: 'hey' } }, 51 | { _id: 'b', bar: { baz: 'dawg' } }, 52 | { _id: 'c' }, 53 | { _id: 'd' } ]); 54 | }); 55 | }); 56 | 57 | it('should pick really deep fields with escape', function () { 58 | var db = context.db; 59 | return db.bulkDocs([ 60 | {_id: 'a', really: {deeply: {nested: {'escaped.field': 'You found me!'}}}} 61 | ]).then(function () { 62 | return db.find({ 63 | selector: {_id: {$gt: null}}, 64 | fields: ['really.deeply.nested.escaped\\.field'] 65 | }); 66 | }).then(function (res) { 67 | res.docs.should.deep.equal([ 68 | { really: { deeply: { nested: { 'escaped.field': 'You found me!' } } } } 69 | ]); 70 | }); 71 | }); 72 | 73 | }); 74 | 75 | }; -------------------------------------------------------------------------------- /test/test-suite-1/test.regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (dbType, context) { 4 | describe(dbType + ': $regex', function () { 5 | 6 | beforeEach(function () { 7 | return context.db.bulkDocs([ 8 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981, awesome: true }, 9 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996, 10 | awesome: false }, 11 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986, awesome: true }, 12 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981, awesome: false }, 13 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996, awesome: true }, 14 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990, 15 | awesome: true }, 16 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983, awesome: false }, 17 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993, awesome: true }, 18 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994, awesome: true }, 19 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986, awesome: true }, 20 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990, awesome: true }, 21 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992, awesome: true }, 22 | { name: 'Master Hand', _id: 'master_hand', series: 'Smash Bros', rank: 0, debut: 1999, 23 | awesome: false } 24 | ]); 25 | }); 26 | 27 | it('should do a basic regex search', function () { 28 | var db = context.db; 29 | var index = { 30 | "index": { 31 | "fields": ["name"] 32 | } 33 | }; 34 | return db.createIndex(index).then(function () { 35 | return db.find({ 36 | selector: { 37 | name: {$gte: null}, 38 | series: {$regex: "^Mario"} 39 | }, 40 | sort: ['name'] 41 | }).then(function (resp) { 42 | var docs = resp.docs.map(function (doc) { 43 | delete doc._rev; 44 | return doc; 45 | }); 46 | 47 | docs.should.deep.equal([ 48 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', 49 | debut: 1981, awesome: false }, 50 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983, awesome: false }, 51 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981, awesome: true }, 52 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990, awesome: true }, 53 | ]); 54 | }); 55 | }); 56 | }); 57 | 58 | it('returns 0 docs for no match', function () { 59 | var db = context.db; 60 | var index = { 61 | "index": { 62 | "fields": ["name"] 63 | } 64 | }; 65 | return db.createIndex(index).then(function () { 66 | return db.find({ 67 | selector: { 68 | name: {$gte: null}, 69 | series: {$regex: "^Wrong"} 70 | }, 71 | sort: ['name'] 72 | }).then(function (resp) { 73 | var docs = resp.docs.map(function (doc) { 74 | delete doc._rev; 75 | return doc; 76 | }); 77 | 78 | docs.should.deep.equal([]); 79 | }); 80 | }); 81 | }); 82 | 83 | it('does not return docs for regex on non-string field', function () { 84 | var db = context.db; 85 | var index = { 86 | "index": { 87 | "fields": ["name"] 88 | } 89 | }; 90 | return db.createIndex(index).then(function () { 91 | return db.find({ 92 | selector: { 93 | name: {$gte: null}, 94 | debut: {$regex: "^Mario"} 95 | }, 96 | sort: ['name'] 97 | }).then(function (resp) { 98 | var docs = resp.docs.map(function (doc) { 99 | delete doc._rev; 100 | return doc; 101 | }); 102 | 103 | docs.should.deep.equal([]); 104 | }); 105 | }); 106 | }); 107 | 108 | }); 109 | }; 110 | -------------------------------------------------------------------------------- /test/test-suite-1/test.set-operations.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': set-operations', function () { 9 | 10 | beforeEach(function () { 11 | return context.db.bulkDocs([ 12 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981 }, 13 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996 }, 14 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986 }, 15 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981 }, 16 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 17 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990 }, 18 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983 }, 19 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993 }, 20 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994 }, 21 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986 }, 22 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990 }, 23 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992 } 24 | ]); 25 | }); 26 | 27 | it('should work with $and 1', function () { 28 | var db = context.db; 29 | return db.createIndex({ 30 | "index": { 31 | "fields": ["series"] 32 | } 33 | }).then(function () { 34 | return db.createIndex({ 35 | "index": { 36 | "fields": ["debut"] 37 | } 38 | }); 39 | }).then(function () { 40 | return db.find({ 41 | selector: { 42 | $and: [ 43 | {series: 'Mario'}, 44 | {debut: {$gte: 1990}} 45 | ] 46 | }, 47 | fields: ['_id'] 48 | }); 49 | }).then(function (res) { 50 | res.docs.sort(sortById); 51 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 52 | }); 53 | }); 54 | 55 | it('should work with $and 2, same index', function () { 56 | var db = context.db; 57 | return db.createIndex({ 58 | "index": { 59 | "fields": ["series", "debut"] 60 | } 61 | }).then(function () { 62 | return db.find({ 63 | selector: { 64 | $and: [ 65 | {series: 'Mario'}, 66 | {debut: {$gte: 1990}} 67 | ] 68 | }, 69 | fields: ['_id'] 70 | }); 71 | }).then(function (res) { 72 | res.docs.sort(sortById); 73 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 74 | }); 75 | }); 76 | 77 | it('should work with $and 3, index/no-index', function () { 78 | var db = context.db; 79 | return db.createIndex({ 80 | "index": { 81 | "fields": ["series"] 82 | } 83 | }).then(function () { 84 | return db.createIndex({ 85 | "index": { 86 | "fields": ["rank"] 87 | } 88 | }); 89 | }).then(function () { 90 | return db.find({ 91 | selector: { 92 | $and: [ 93 | {series: 'Mario'}, 94 | {debut: {$gte: 1990}} 95 | ] 96 | }, 97 | fields: ['_id'] 98 | }); 99 | }).then(function (res) { 100 | res.docs.sort(sortById); 101 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 102 | }); 103 | }); 104 | 105 | it('should work with $and 4, wrong index', function () { 106 | var db = context.db; 107 | return db.createIndex({ 108 | "index": { 109 | "fields": ["rank"] 110 | } 111 | }).then(function () { 112 | return db.find({ 113 | selector: { 114 | $and: [ 115 | {series: 'Mario'}, 116 | {debut: {$gte: 1990}} 117 | ] 118 | }, 119 | fields: ['_id'] 120 | }).then(function (resp) { 121 | resp.should.deep.equal({ 122 | warning: 'no matching index found, create an index to optimize query time', 123 | docs: [ 124 | {_id: 'yoshi'} 125 | ] 126 | }); 127 | }); 128 | }); 129 | }); 130 | }); 131 | }; 132 | -------------------------------------------------------------------------------- /test/test-suite-1/test.skip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': skip', function () { 9 | 10 | beforeEach(function () { 11 | return context.db.bulkDocs([ 12 | { name: 'Mario', _id: 'mario', rank: 5, series: 'Mario', debut: 1981 }, 13 | { name: 'Jigglypuff', _id: 'puff', rank: 8, series: 'Pokemon', debut: 1996 }, 14 | { name: 'Link', rank: 10, _id: 'link', series: 'Zelda', debut: 1986 }, 15 | { name: 'Donkey Kong', rank: 7, _id: 'dk', series: 'Mario', debut: 1981 }, 16 | { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 17 | { name: 'Captain Falcon', _id: 'falcon', rank: 4, series: 'F-Zero', debut: 1990 }, 18 | { name: 'Luigi', rank: 11, _id: 'luigi', series: 'Mario', debut: 1983 }, 19 | { name: 'Fox', _id: 'fox', rank: 3, series: 'Star Fox', debut: 1993 }, 20 | { name: 'Ness', rank: 9, _id: 'ness', series: 'Earthbound', debut: 1994 }, 21 | { name: 'Samus', rank: 12, _id: 'samus', series: 'Metroid', debut: 1986 }, 22 | { name: 'Yoshi', _id: 'yoshi', rank: 6, series: 'Mario', debut: 1990 }, 23 | { name: 'Kirby', _id: 'kirby', series: 'Kirby', rank: 2, debut: 1992 } 24 | ]); 25 | }); 26 | 27 | it('should work with $and 1 skip 0', function () { 28 | var db = context.db; 29 | return db.createIndex({ 30 | "index": { 31 | "fields": ["series"] 32 | } 33 | }).then(function () { 34 | return db.createIndex({ 35 | "index": { 36 | "fields": ["debut"] 37 | } 38 | }); 39 | }).then(function () { 40 | return db.find({ 41 | selector: { 42 | $and: [ 43 | {series: 'Mario'}, 44 | {debut: {$gte: 1982}} 45 | ] 46 | }, 47 | fields: ['_id'], 48 | skip: 0 49 | }); 50 | }).then(function (res) { 51 | res.docs.sort(sortById); 52 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 53 | }); 54 | }); 55 | 56 | it('should work with $and 1 skip 1', function () { 57 | var db = context.db; 58 | return db.createIndex({ 59 | "index": { 60 | "fields": ["series"] 61 | } 62 | }).then(function () { 63 | return db.createIndex({ 64 | "index": { 65 | "fields": ["debut"] 66 | } 67 | }); 68 | }).then(function () { 69 | return db.find({ 70 | selector: { 71 | $and: [ 72 | {series: 'Mario'}, 73 | {debut: {$gte: 1982}} 74 | ] 75 | }, 76 | fields: ['_id'], 77 | skip: 1 78 | }); 79 | }).then(function (res) { 80 | res.docs.sort(sortById); 81 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 82 | }); 83 | }); 84 | 85 | it('should work with $and 1 skip 2', function () { 86 | var db = context.db; 87 | return db.createIndex({ 88 | "index": { 89 | "fields": ["series"] 90 | } 91 | }).then(function () { 92 | return db.createIndex({ 93 | "index": { 94 | "fields": ["debut"] 95 | } 96 | }); 97 | }).then(function () { 98 | return db.find({ 99 | selector: { 100 | $and: [ 101 | {series: 'Mario'}, 102 | {debut: {$gte: 1982}} 103 | ] 104 | }, 105 | fields: ['_id'], 106 | skip: 2 107 | }); 108 | }).then(function (res) { 109 | res.docs.sort(sortById); 110 | res.docs.should.deep.equal([]); 111 | }); 112 | }); 113 | 114 | it('should work with $and 2, same index skip 0', function () { 115 | var db = context.db; 116 | return db.createIndex({ 117 | "index": { 118 | "fields": ["series", "debut"] 119 | } 120 | }).then(function () { 121 | return db.find({ 122 | selector: { 123 | $and: [ 124 | {series: 'Mario'}, 125 | {debut: {$gte: 1982}} 126 | ] 127 | }, 128 | fields: ['_id'], 129 | skip: 0 130 | }); 131 | }).then(function (res) { 132 | res.docs.sort(sortById); 133 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 134 | }); 135 | }); 136 | 137 | it('should work with $and 2, same index skip 1', function () { 138 | var db = context.db; 139 | return db.createIndex({ 140 | "index": { 141 | "fields": ["series", "debut"] 142 | } 143 | }).then(function () { 144 | return db.find({ 145 | selector: { 146 | $and: [ 147 | {series: 'Mario'}, 148 | {debut: {$gte: 1982}} 149 | ] 150 | }, 151 | fields: ['_id'], 152 | skip: 1 153 | }); 154 | }).then(function (res) { 155 | res.docs.sort(sortById); 156 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 157 | }); 158 | }); 159 | 160 | it('should work with $and 2, same index skip 2', function () { 161 | var db = context.db; 162 | return db.createIndex({ 163 | "index": { 164 | "fields": ["series", "debut"] 165 | } 166 | }).then(function () { 167 | return db.find({ 168 | selector: { 169 | $and: [ 170 | {series: 'Mario'}, 171 | {debut: {$gte: 1982}} 172 | ] 173 | }, 174 | fields: ['_id'], 175 | skip: 2 176 | }); 177 | }).then(function (res) { 178 | res.docs.sort(sortById); 179 | res.docs.should.deep.equal([]); 180 | }); 181 | }); 182 | 183 | it('should work with $and 3, index/no-index skip 0', function () { 184 | var db = context.db; 185 | return db.createIndex({ 186 | "index": { 187 | "fields": ["series"] 188 | } 189 | }).then(function () { 190 | return db.createIndex({ 191 | "index": { 192 | "fields": ["rank"] 193 | } 194 | }); 195 | }).then(function () { 196 | return db.find({ 197 | selector: { 198 | $and: [ 199 | {series: 'Mario'}, 200 | {debut: {$gte: 1982}} 201 | ] 202 | }, 203 | fields: ['_id'], 204 | skip: 0 205 | }); 206 | }).then(function (res) { 207 | res.docs.sort(sortById); 208 | res.docs.should.deep.equal([{_id: 'luigi'}, {_id: 'yoshi'}]); 209 | }); 210 | }); 211 | 212 | it('should work with $and 3, index/no-index skip 1', function () { 213 | var db = context.db; 214 | return db.createIndex({ 215 | "index": { 216 | "fields": ["series"] 217 | } 218 | }).then(function () { 219 | return db.createIndex({ 220 | "index": { 221 | "fields": ["rank"] 222 | } 223 | }); 224 | }).then(function () { 225 | return db.find({ 226 | selector: { 227 | $and: [ 228 | {series: 'Mario'}, 229 | {debut: {$gte: 1982}} 230 | ] 231 | }, 232 | fields: ['_id'], 233 | skip: 1 234 | }); 235 | }).then(function (res) { 236 | res.docs.sort(sortById); 237 | res.docs.should.deep.equal([{_id: 'yoshi'}]); 238 | }); 239 | }); 240 | 241 | it('should work with $and 3, index/no-index skip 2', function () { 242 | var db = context.db; 243 | return db.createIndex({ 244 | "index": { 245 | "fields": ["series"] 246 | } 247 | }).then(function () { 248 | return db.createIndex({ 249 | "index": { 250 | "fields": ["rank"] 251 | } 252 | }); 253 | }).then(function () { 254 | return db.find({ 255 | selector: { 256 | $and: [ 257 | {series: 'Mario'}, 258 | {debut: {$gte: 1983}} 259 | ] 260 | }, 261 | fields: ['_id'], 262 | skip: 2 263 | }); 264 | }).then(function (res) { 265 | res.docs.sort(sortById); 266 | res.docs.should.deep.equal([]); 267 | }); 268 | }); 269 | 270 | it('should work with $and 4, wrong index', function () { 271 | var db = context.db; 272 | return db.createIndex({ 273 | "index": { 274 | "fields": ["rank"] 275 | } 276 | }).then(function () { 277 | return db.find({ 278 | selector: { 279 | $and: [ 280 | {series: 'Mario'}, 281 | {debut: {$gte: 1990}} 282 | ] 283 | }, 284 | fields: ['_id'], 285 | skip: 1 286 | }).then(function (resp) { 287 | resp.should.deep.equal({ 288 | warning: 'no matching index found, create an index to optimize query time', 289 | docs: [] 290 | }); 291 | }); 292 | }); 293 | }); 294 | }); 295 | }; 296 | -------------------------------------------------------------------------------- /test/test-suite-1/test.sorting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var should = testUtils.should; 5 | 6 | module.exports = function (dbType, context) { 7 | describe(dbType + ': sorting', function () { 8 | 9 | it('sorts correctly - just _id', function () { 10 | var db = context.db; 11 | return db.bulkDocs([ 12 | {_id: 'a', foo: 'a'}, 13 | {_id: 'b', foo: 'b'} 14 | ]).then(function () { 15 | return db.find({ 16 | "selector": {"_id": {$gte: "a"}}, 17 | "fields": ["_id", "foo"], 18 | "sort": [{"_id": "asc"}] 19 | }); 20 | }).then(function (resp) { 21 | resp.should.deep.equal({ 22 | "docs": [ 23 | {"_id": "a", "foo": "a"}, 24 | {"_id": "b", "foo": "b"} 25 | ] 26 | }); 27 | }); 28 | }); 29 | 30 | it('sorts correctly - just _id desc', function () { 31 | var db = context.db; 32 | return db.bulkDocs([ 33 | {_id: 'a', foo: 'a'}, 34 | {_id: 'b', foo: 'b'} 35 | ]).then(function () { 36 | return db.find({ 37 | "selector": {"_id": {$gte: "a"}}, 38 | "fields": ["_id", "foo"], 39 | "sort": [{"_id": "desc"}] 40 | }); 41 | }).then(function (resp) { 42 | resp.should.deep.equal({ 43 | "docs": [ 44 | {"_id": "b", "foo": "b"}, 45 | {"_id": "a", "foo": "a"} 46 | ] 47 | }); 48 | }); 49 | }); 50 | 51 | it('sorts correctly - foo desc', function () { 52 | var db = context.db; 53 | var index = { 54 | "index": { 55 | "fields": [{"foo": "desc"}] 56 | }, 57 | "name": "foo-index", 58 | "type": "json" 59 | }; 60 | return db.createIndex(index).then(function () { 61 | return db.bulkDocs([ 62 | {_id: 'a', foo: 'b'}, 63 | {_id: 'b', foo: 'a'}, 64 | {_id: 'c', foo: 'c'}, 65 | {_id: '0', foo: 'd'} 66 | ]); 67 | }).then(function () { 68 | return db.find({ 69 | "selector": {"foo": {$lte: "d"}}, 70 | "fields": ["foo"] 71 | }); 72 | }).then(function (resp) { 73 | resp.should.deep.equal({ 74 | "docs": [ 75 | {"foo": "a"}, 76 | {"foo": "b"}, 77 | {"foo": "c"}, 78 | {"foo": "d"} 79 | ] 80 | }); 81 | }); 82 | }); 83 | 84 | it('sorts correctly - foo desc 2', function () { 85 | var db = context.db; 86 | var index = { 87 | "index": { 88 | "fields": [{"foo": "desc"}] 89 | }, 90 | "name": "foo-index", 91 | "type": "json" 92 | }; 93 | return db.createIndex(index).then(function () { 94 | return db.bulkDocs([ 95 | {_id: 'a', foo: 'b'}, 96 | {_id: 'b', foo: 'a'}, 97 | {_id: 'c', foo: 'c'}, 98 | {_id: '0', foo: 'd'} 99 | ]); 100 | }).then(function () { 101 | return db.find({ 102 | "selector": {"foo": {$lte: "d"}}, 103 | "fields": ["foo"], 104 | "sort": [{foo: "desc"}] 105 | }); 106 | }).then(function (resp) { 107 | resp.should.deep.equal({ 108 | "docs": [ 109 | {"foo": "d"}, 110 | {"foo": "c"}, 111 | {"foo": "b"}, 112 | {"foo": "a"} 113 | ] 114 | }); 115 | }); 116 | }); 117 | 118 | it('sorts correctly - complex', function () { 119 | var db = context.db; 120 | var index = { 121 | "index": { 122 | "fields": ["foo"] 123 | }, 124 | "name": "foo-index", 125 | "type": "json" 126 | }; 127 | return db.createIndex(index).then(function () { 128 | return db.bulkDocs([ 129 | { _id: '1', foo: 'AAA'}, 130 | { _id: '2', foo: 'aAA' }, 131 | { _id: '3', foo: 'BAA'}, 132 | { _id: '4', foo: 'bAA'}, 133 | { _id: '5', foo: '\u0000aAA'}, 134 | { _id: '6', foo: '\u0001AAA'} 135 | ]); 136 | }).then(function () { 137 | return db.find({ 138 | "selector": {"foo": {"$gt": "\u0000\u0000"}}, 139 | "fields": ["_id", "foo"], 140 | "sort": [{"foo": "asc"}] 141 | }); 142 | }).then(function (resp) { 143 | // ASCII vs ICU ordering. just gonna hack this 144 | if (dbType === 'http') { 145 | resp.should.deep.equal({ 146 | "docs": [ 147 | { "_id": "2", "foo": "aAA"}, 148 | { "_id": "5", "foo": "\u0000aAA"}, 149 | { "_id": "1", "foo": "AAA"}, 150 | { "_id": "6", "foo": "\u0001AAA"}, 151 | { "_id": "4", "foo": "bAA"}, 152 | { "_id": "3", "foo": "BAA"} 153 | ] 154 | }); 155 | } else { 156 | resp.should.deep.equal({ 157 | docs: [ 158 | { _id: '5', foo: '\u0000aAA' }, 159 | { _id: '6', foo: '\u0001AAA' }, 160 | { _id: '1', foo: 'AAA' }, 161 | { _id: '3', foo: 'BAA' }, 162 | { _id: '2', foo: 'aAA' }, 163 | { _id: '4', foo: 'bAA' } 164 | ] 165 | }); 166 | } 167 | }); 168 | }); 169 | 170 | 171 | it('supported mixed sort', function () { 172 | var db = context.db; 173 | var index = { 174 | "index": { 175 | "fields": [ 176 | "foo", 177 | "bar" 178 | ] 179 | }, 180 | "name": "foo-index", 181 | "type": "json" 182 | }; 183 | return db.createIndex(index).then(function () { 184 | return db.bulkDocs([ 185 | {_id: 'a1', foo: 'a', bar: '1'}, 186 | {_id: 'a2', foo: 'a', bar: '2'}, 187 | {_id: 'b1', foo: 'b', bar: '1'} 188 | ]); 189 | }).then(function () { 190 | return db.find({ 191 | selector: {foo: {$gte: 'a'}} 192 | }); 193 | }).then(function (res) { 194 | res.docs.forEach(function (doc) { 195 | should.exist(doc._rev); 196 | delete doc._rev; 197 | }); 198 | res.should.deep.equal({ 199 | "docs": [ 200 | { 201 | "_id": "a1", 202 | "foo": "a", 203 | "bar": "1" 204 | }, 205 | { 206 | "_id": "a2", 207 | "foo": "a", 208 | "bar": "2" 209 | }, 210 | { 211 | "_id": "b1", 212 | "foo": "b", 213 | "bar": "1" 214 | } 215 | ] 216 | }); 217 | }); 218 | }); 219 | 220 | it('supported mixed sort 2', function () { 221 | var db = context.db; 222 | var index = { 223 | "index": { 224 | "fields": [ 225 | "foo", 226 | "bar" 227 | ] 228 | }, 229 | "name": "foo-index", 230 | "type": "json" 231 | }; 232 | return db.createIndex(index).then(function () { 233 | return db.bulkDocs([ 234 | {_id: 'a1', foo: 'a', bar: '1'}, 235 | {_id: 'a2', foo: 'a', bar: '2'}, 236 | {_id: 'b1', foo: 'b', bar: '1'} 237 | ]); 238 | }).then(function () { 239 | return db.find({ 240 | selector: {foo: {$gte: 'b'}} 241 | }); 242 | }).then(function (res) { 243 | res.docs.forEach(function (doc) { 244 | should.exist(doc._rev); 245 | delete doc._rev; 246 | }); 247 | res.should.deep.equal({ 248 | "docs": [ 249 | { 250 | "_id": "b1", 251 | "foo": "b", 252 | "bar": "1" 253 | } 254 | ] 255 | }); 256 | }); 257 | }); 258 | 259 | it('sort error, not an array', function () { 260 | var db = context.db; 261 | 262 | return db.createIndex({ 263 | index: { 264 | fields: ['foo'] 265 | } 266 | }).then(function () { 267 | return db.bulkDocs([ 268 | {_id: '1', foo: 1}, 269 | {_id: '2', foo: 2}, 270 | {_id: '3', foo: 3}, 271 | {_id: '4', foo: 4} 272 | ]); 273 | }).then(function () { 274 | return db.find({ 275 | selector: {foo: {$eq: 1}}, 276 | sort: {} 277 | }).then(function () { 278 | throw new Error('expected an error'); 279 | }, function (err) { 280 | should.exist(err); 281 | }); 282 | }); 283 | }); 284 | 285 | }); 286 | }; 287 | -------------------------------------------------------------------------------- /test/test-suite-1/test.type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testUtils = require('../test-utils'); 4 | var sortById = testUtils.sortById; 5 | 6 | module.exports = function (dbType, context) { 7 | 8 | describe(dbType + ': $type', function () { 9 | 10 | beforeEach(function () { 11 | var db = context.db; 12 | return db.bulkDocs([ 13 | {_id: 'a', foo: 'bar'}, 14 | {_id: 'b', foo: 1}, 15 | {_id: 'c', foo: null}, 16 | {_id: 'd', foo: []}, 17 | {_id: 'e', foo: {}}, 18 | {_id: 'f', foo: false} 19 | ]); 20 | }); 21 | 22 | it('does null', function () { 23 | var db = context.db; 24 | return db.find({ 25 | selector: { 26 | _id: {$gt: null}, 27 | 'foo': {$type: 'null'} 28 | }, 29 | fields: ['_id'] 30 | }).then(function (res) { 31 | res.docs.sort(sortById); 32 | res.docs.should.deep.equal([{_id: 'c'}]); 33 | }); 34 | }); 35 | 36 | it('does boolean', function () { 37 | var db = context.db; 38 | return db.find({ 39 | selector: { 40 | _id: {$gt: null}, 41 | 'foo': {$type: 'boolean'} 42 | }, 43 | fields: ['_id'] 44 | }).then(function (res) { 45 | res.docs.sort(sortById); 46 | res.docs.should.deep.equal([{_id: 'f'}]); 47 | 48 | }); 49 | }); 50 | 51 | it('does number', function () { 52 | var db = context.db; 53 | return db.find({ 54 | selector: { 55 | _id: {$gt: null}, 56 | 'foo': {$type: 'number'} 57 | }, 58 | fields: ['_id'] 59 | }).then(function (res) { 60 | res.docs.sort(sortById); 61 | res.docs.should.deep.equal([{_id: 'b'}]); 62 | }); 63 | }); 64 | 65 | it('does string', function () { 66 | var db = context.db; 67 | return db.find({ 68 | selector: { 69 | _id: {$gt: null}, 70 | 'foo': {$type: 'string'} 71 | }, 72 | fields: ['_id'] 73 | }).then(function (res) { 74 | res.docs.sort(sortById); 75 | res.docs.should.deep.equal([{_id: 'a'}]); 76 | }); 77 | }); 78 | 79 | it('does array', function () { 80 | var db = context.db; 81 | return db.find({ 82 | selector: { 83 | _id: {$gt: null}, 84 | 'foo': {$type: 'array'} 85 | }, 86 | fields: ['_id'] 87 | }).then(function (res) { 88 | res.docs.sort(sortById); 89 | res.docs.should.deep.equal([{_id: 'd'}]); 90 | }); 91 | }); 92 | 93 | it('does object', function () { 94 | var db = context.db; 95 | return db.find({ 96 | selector: { 97 | _id: {$gt: null}, 98 | 'foo': {$type: 'object'} 99 | }, 100 | fields: ['_id'] 101 | }).then(function (res) { 102 | res.docs.sort(sortById); 103 | res.docs.should.deep.equal([{_id: 'e'}]); 104 | }); 105 | }); 106 | 107 | it('throws error for unmatched type', function () { 108 | var db = context.db; 109 | return db.find({ 110 | selector: { 111 | _id: {$gt: null}, 112 | 'foo': {$type: 'made-up'} 113 | }, 114 | fields: ['_id'] 115 | }).catch(function (err) { 116 | err.message.should.match(/made-up not supported/); 117 | }); 118 | }); 119 | }); 120 | }; 121 | -------------------------------------------------------------------------------- /test/test-suite-2/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('../test-utils').Promise; 4 | 5 | module.exports = function (dbName, dbType, Pouch) { 6 | describe(dbType + ' test suite 2', function () { 7 | // tests that don't destroy/recreate the database for each test, 8 | // because that makes them take a long time 9 | 10 | this.timeout(100000); 11 | 12 | var context = {}; 13 | 14 | before(function () { 15 | this.timeout(60000); 16 | context.db = new Pouch(dbName); 17 | 18 | return context.db.bulkDocs([ 19 | { name: 'mario', _id: 'mario', rank: 5, series: 'mario', debut: 1981 }, 20 | { name: 'jigglypuff', _id: 'puff', rank: 8, series: 'pokemon', debut: 1996 }, 21 | { name: 'link', rank: 10, _id: 'link', series: 'zelda', debut: 1986 }, 22 | { name: 'donkey kong', rank: 7, _id: 'dk', series: 'mario', debut: 1981 }, 23 | { name: 'pikachu', series: 'pokemon', _id: 'pikachu', rank: 1, debut: 1996 }, 24 | { name: 'captain falcon', _id: 'falcon', rank: 4, series: 'f-zero', debut: 1990 }, 25 | { name: 'luigi', rank: 11, _id: 'luigi', series: 'mario', debut: 1983 }, 26 | { name: 'fox', _id: 'fox', rank: 3, series: 'star fox', debut: 1993 }, 27 | { name: 'ness', rank: 9, _id: 'ness', series: 'earthbound', debut: 1994 }, 28 | { name: 'samus', rank: 12, _id: 'samus', series: 'metroid', debut: 1986 }, 29 | { name: 'yoshi', _id: 'yoshi', rank: 6, series: 'mario', debut: 1990 }, 30 | { name: 'kirby', _id: 'kirby', series: 'kirby', rank: 2, debut: 1992 } 31 | ]).then(function () { 32 | return Promise.all([ 33 | context.db.createIndex({index: {fields: ['rank']}}), 34 | context.db.createIndex({index: {fields: ['series']}}), 35 | context.db.createIndex({index: {fields: ['debut']}}), 36 | context.db.createIndex({index: {fields: ['name']}}), 37 | context.db.createIndex({index: {fields: ['name', 'rank']}}), 38 | context.db.createIndex({index: {fields: ['name', 'series']}}), 39 | context.db.createIndex({index: {fields: ['series', 'debut', 'rank']}}), 40 | context.db.createIndex({index: {fields: ['rank', 'debut']}}) 41 | ]); 42 | }); 43 | }); 44 | after(function () { 45 | this.timeout(60000); 46 | return context.db.destroy(); 47 | }); 48 | 49 | require('./test.kitchen-sink')(dbType, context); 50 | require('./test.kitchen-sink-2')(dbType, context); 51 | }); 52 | }; -------------------------------------------------------------------------------- /test/test-suite-2/test.kitchen-sink-2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * "kitchen sink" tests - just throw everything at the wall 5 | * and see what sticks. 6 | */ 7 | 8 | var testUtils = require('../test-utils'); 9 | var should = testUtils.should; 10 | 11 | function humanizeNum(i) { 12 | var res = (i + 1).toString(); 13 | while (res.length < 3) { 14 | res = '0' + res; 15 | } 16 | return res; 17 | } 18 | 19 | module.exports = function (dbType, context) { 20 | 21 | describe(dbType + ': kitchen-sink-2', function () { 22 | 23 | // to actually generate the list: 24 | // var configs = // whatever python spit out 25 | // var tests = [] 26 | // configs.forEach(function (config) {db.find(config).then(function (res) {return {res: {docs: res.docs.map(function (doc) {return doc._id;})}}}, function (err){return {err: err}}).then(function (res){tests.push({input: config, output: res})})}) 27 | var testConfigs = [{"input":{"selector":{"$and":[{"series":{"$ne":"kirby"}},{"series":{"$ne":"pokemon"}},{"_id":{"$gt":"mario"}},{"debut":{"$lte":1993}}]}},"output":{"res":{"docs":["samus","yoshi"]}}},{"input":{"selector":{"_id":{"$gte":"link"}}},"output":{"res":{"docs":["link","luigi","mario","ness","pikachu","puff","samus","yoshi"]}}},{"input":{"selector":{"_id":{"$lt":"samus", "$gt": "a"}}},"output":{"res":{"docs":["dk","falcon","fox","kirby","link","luigi","mario","ness","pikachu","puff"]}}},{"input":{"selector":{"$and":[{"_id":{"$eq":"pikach"}},{"name":{"$gte":"link"}},{"rank":{"$ne":8}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"rank":{"$lt":1}},{"_id":{"$lt":"fox"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"name":{"$ne":"jigglypuff"}},{"_id":{"$lt":"puff"}},{"rank":{"$gte":4}},{"_id":{"$gt":"pikach"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"_id":{"$lte":"dk"}},{"series":{"$lt":"mario"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"series":{"$lt":"star fox"}},{"_id":{"$gte":"ness"}}]}},"output":{"res":{"docs":["ness","pikachu","puff","samus","yoshi"]}}},{"input":{"selector":{"$and":[{"_id":{"$gt":"samus"}},{"name":{"$gt":"fox"}}]}},"output":{"res":{"docs":["yoshi"]}}},{"input":{"sort":["_id"],"selector":{"$and":[{"rank":{"$ne":4}},{"rank":{"$gte":9}},{"_id":{"$gt":"ness"}},{"_id":{"$ne":"ness"}}]}},"output":{"res":{"docs":["samus"]}}},{"input":{"sort":["_id"],"selector":{"$and":[{"name":{"$gt":"yoshi"}},{"_id":{"$lte":"puff"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"series":{"$gte":"kirby"}},{"_id":{"$lte":"dk"}}]}},"output":{"res":{"docs":["dk"]}}},{"input":{"selector":{"$and":[{"series":{"$gt":"mario"}},{"_id":{"$lt":"mario"}},{"name":{"$ne":"link"}}]}},"output":{"res":{"docs":["fox"]}}},{"input":{"selector":{"$and":[{"name":{"$eq":"fox"}},{"_id":{"$lte":"pikach"}}]}},"output":{"res":{"docs":["fox"]}}},{"input":{"selector":{"$and":[{"rank":{"$lt":9}},{"_id":{"$lte":"puff"}}]}},"output":{"res":{"docs":["dk","falcon","fox","kirby","mario","pikachu","puff"]}}},{"input":{"selector":{"$and":[{"name":{"$eq":"ness"}},{"_id":{"$lte":"luigi"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"name":{"$lte":"mario"}},{"_id":{"$lt":"mario"}},{"debut":{"$eq":1990}}]}},"output":{"res":{"docs":["falcon"]}}},{"input":{"selector":{"$and":[{"debut":{"$lte":1994}},{"_id":{"$lte":"link"}},{"_id":{"$lte":"pikach"}},{"rank":{"$gt":4}}]}},"output":{"res":{"docs":["dk","link"]}}},{"input":{"selector":{"$and":[{"name":{"$lt":"yoshi"}},{"_id":{"$eq":"falcon"}},{"rank":{"$ne":2}}]}},"output":{"res":{"docs":["falcon"]}}},{"input":{"sort":["_id"],"selector":{"_id":{"$lt":"falcon", "$gt": "a"}}},"output":{"res":{"docs":["dk"]}}},{"input":{"selector":{"$and":[{"rank":{"$gte":5}},{"_id":{"$lt":"link"}},{"series":{"$ne":"mario"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"rank":{"$gt":10}},{"_id":{"$lt":"luigi"}},{"series":{"$gte":"mario"}},{"debut":{"$lt":1992}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"name":{"$gt":"samus"}},{"_id":{"$gte":"kirby"}},{"series":{"$gte":"pokemon"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"series":{"$lt":"f-zero"}},{"_id":{"$lt":"pikach"}}]}},"output":{"res":{"docs":["ness"]}}},{"input":{"selector":{"$and":[{"rank":{"$lt":6}},{"_id":{"$eq":"kirby"}},{"name":{"$eq":"fox"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"name":{"$lt":"luigi"}},{"name":{"$lt":"jigglypuff"}},{"_id":{"$gt":"samus"}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"series":{"$lte":"mario"}},{"_id":{"$gte":"kirby"}},{"_id":{"$lt":"puff"}}]}},"output":{"res":{"docs":["kirby","luigi","mario","ness"]}}},{"input":{"sort":["_id"],"selector":{"$and":[{"series":{"$eq":"pokemon"}},{"_id":{"$gte":"samus"}},{"rank":{"$ne":4}},{"debut":{"$ne":1990}}]}},"output":{"res":{"docs":[]}}},{"input":{"selector":{"$and":[{"series":{"$gt":"mario"}},{"_id":{"$lt":"mario"}}]}},"output":{"res":{"docs":["fox","link"]}}},{"input":{"sort":["_id"],"selector":{"$and":[{"_id":{"$lt":"fox"}},{"debut":{"$ne":1993}},{"name":{"$lte":"luigi"}}]}},"output":{"res":{"docs":["dk","falcon"]}}},{"input":{"selector":{"$and":[{"_id":{"$lt":"samus"}},{"debut":{"$ne":1986}},{"debut":{"$lte":1983}}]}},"output":{"res":{"docs":["dk","luigi","mario"]}}},{"input":{"selector":{"_id":{"$gt":"fox"}}},"output":{"res":{"docs":["kirby","link","luigi","mario","ness","pikachu","puff","samus","yoshi"]}}}]; 28 | 29 | testConfigs.forEach(function (testConfig, i) { 30 | 31 | function kitchenSinkTest() { 32 | var db = context.db; 33 | var query = testConfig.input; 34 | query.fields = ['_id']; 35 | return db.find(query).then(function (res) { 36 | if (testConfig.output.res) { 37 | var ids = res.docs.map(function (x) { 38 | return x._id; 39 | }); 40 | if (!testConfig.input.sort) { 41 | // no guaranteed sorting, so ignore order 42 | ids.sort(); 43 | testConfig.output.res.docs.sort(); 44 | } 45 | ids.should.deep.equal(testConfig.output.res.docs); 46 | } else { 47 | should.exist(res.warning, 'expected a warning'); 48 | res.warning.should.equal('no matching index found, create an ' + 49 | 'index to optimize query time'); 50 | } 51 | }, function (err) { 52 | if (testConfig.output.res) { 53 | should.not.exist(err, 'should not have thrown an error'); 54 | } else { 55 | should.exist(err); 56 | } 57 | }); 58 | } 59 | 60 | var testName = 'kitchen sink 2 test #' + humanizeNum(i); 61 | 62 | it(testName, kitchenSinkTest); 63 | 64 | }); 65 | }); 66 | }; -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.sortById = function sortById(left, right) { 4 | return left._id < right._id ? -1 : 1; 5 | }; 6 | 7 | var chai = require('chai'); 8 | chai.use(require("chai-as-promised")); 9 | 10 | exports.should = chai.should(); 11 | var Promise = require('bluebird'); 12 | exports.Promise = Promise; 13 | 14 | exports.fin = function (promise, cb) { 15 | return promise.then(function (res) { 16 | var promise2 = cb(); 17 | if (typeof promise2.then === 'function') { 18 | return promise2.then(function () { 19 | return res; 20 | }); 21 | } 22 | return res; 23 | }, function (reason) { 24 | var promise2 = cb(); 25 | if (typeof promise2.then === 'function') { 26 | return promise2.then(function () { 27 | throw reason; 28 | }); 29 | } 30 | throw reason; 31 | }); 32 | }; 33 | 34 | exports.promisify = function (fun, context) { 35 | return function () { 36 | var args = []; 37 | for (var i = 0; i < arguments.length; i++) { 38 | args[i] = arguments[i]; 39 | } 40 | return new Promise(function (resolve, reject) { 41 | args.push(function (err, res) { 42 | if (err) { 43 | return reject(err); 44 | } 45 | return resolve(res); 46 | }); 47 | fun.apply(context, args); 48 | }); 49 | }; 50 | }; -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var queryString = require('query-string'); 4 | var Pouch = require('pouchdb-core') 5 | .plugin(require('pouchdb-adapter-memory')) 6 | .plugin(require('pouchdb-adapter-http')) 7 | .plugin(require('pouchdb-replication')); 8 | var thePlugin = require('../'); 9 | Pouch.plugin(thePlugin); 10 | 11 | require('./test-utils'); 12 | 13 | var couch; 14 | if (typeof process === 'undefined' || process.browser) { 15 | couch = queryString.parse(location.search).couchHost || 16 | 'http://127.0.0.1:5984'; 17 | } else { 18 | couch = process.env.COUCH_HOST || 'http://127.0.0.1:5984'; 19 | } 20 | 21 | var dbs = 'testdb_find,' + couch + '/testdb_find'; 22 | 23 | dbs.split(',').forEach(function (db) { 24 | var dbType = /^http/.test(db) ? 'http' : 'local'; 25 | tests(db, dbType); 26 | }); 27 | 28 | function tests(dbName, dbType) { 29 | 30 | require('./test-suite-1')(dbName, dbType, Pouch); 31 | require('./test-suite-2')(dbName, dbType, Pouch); 32 | 33 | if (dbType === 'local') { 34 | require('./test-abstract-mapreduce')(dbName, dbType, Pouch); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /test/webrunner.js: -------------------------------------------------------------------------------- 1 | /* global mocha: true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | var runner = mocha.run(); 6 | window.results = { 7 | browser: navigator.userAgent, 8 | lastPassed: '', 9 | passed: 0, 10 | failed: 0, 11 | failures: [] 12 | }; 13 | 14 | runner.on('pass', function (e) { 15 | window.results.lastPassed = e.title; 16 | window.results.passed++; 17 | }); 18 | 19 | runner.on('fail', function (e) { 20 | window.results.failed++; 21 | window.results.failures.push({ 22 | title: e.title, 23 | message: e.err.message, 24 | stack: e.err.stack 25 | }); 26 | }); 27 | 28 | runner.on('end', function () { 29 | window.results.completed = true; 30 | window.results.passed++; 31 | }); 32 | })(); 33 | 34 | 35 | -------------------------------------------------------------------------------- /www/ace/theme-xcode.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/xcode",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-xcode",t.cssText=".ace-xcode .ace_gutter {background: #e8e8e8;color: #333}.ace-xcode .ace_print-margin {width: 1px;background: #e8e8e8}.ace-xcode {background-color: #FFFFFF;color: #000000}.ace-xcode .ace_cursor {color: #000000}.ace-xcode .ace_marker-layer .ace_selection {background: #B5D5FF}.ace-xcode.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FFFFFF;border-radius: 2px}.ace-xcode .ace_marker-layer .ace_step {background: rgb(198, 219, 174)}.ace-xcode .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #BFBFBF}.ace-xcode .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.071)}.ace-xcode .ace_gutter-active-line {background-color: rgba(0, 0, 0, 0.071)}.ace-xcode .ace_marker-layer .ace_selected-word {border: 1px solid #B5D5FF}.ace-xcode .ace_constant.ace_language,.ace-xcode .ace_keyword,.ace-xcode .ace_meta,.ace-xcode .ace_variable.ace_language {color: #C800A4}.ace-xcode .ace_invisible {color: #BFBFBF}.ace-xcode .ace_constant.ace_character,.ace-xcode .ace_constant.ace_other {color: #275A5E}.ace-xcode .ace_constant.ace_numeric {color: #3A00DC}.ace-xcode .ace_entity.ace_other.ace_attribute-name,.ace-xcode .ace_support.ace_constant,.ace-xcode .ace_support.ace_function {color: #450084}.ace-xcode .ace_fold {background-color: #C800A4;border-color: #000000}.ace-xcode .ace_entity.ace_name.ace_tag,.ace-xcode .ace_support.ace_class,.ace-xcode .ace_support.ace_type {color: #790EAD}.ace-xcode .ace_storage {color: #C900A4}.ace-xcode .ace_string {color: #DF0002}.ace-xcode .ace_comment {color: #008E00}.ace-xcode .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) -------------------------------------------------------------------------------- /www/app.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 'use strict'; 3 | 4 | // set up editor 5 | var snippets = [ 6 | "// The _id field is the primary index. This query finds\n// all documents with _id greater than or equal to \"dk\"\ndb.find({\n selector: {_id: {$gte: 'dk'}},\n sort: ['_id']\n});", 7 | "// For other fields, you must create an index first.\n// This query sorts all documents by name.\ndb.createIndex({\n index: {fields: ['name']}\n}).then(function () {\n return db.find({\n selector: {name: {$gt: null}},\n sort: ['name']\n });\n});", 8 | "// Available selectors are $gt, $gte, $lt, $lte, \n// $eq, $ne, $exists, $type, and more\ndb.createIndex({\n index: {fields: ['debut']}\n}).then(function () {\n return db.find({\n selector: {debut: {$gte: 1990}}\n });\n});", 9 | "// Multi-field queries and sorting are also supported\ndb.createIndex({\n index: {fields: ['series', 'debut']}\n}).then(function () {\n return db.find({\n selector: {series: {$eq: 'Mario'}},\n sort: [{series: 'desc'}, {debut: 'desc'}]\n });\n});", 10 | "// You can also select certain fields.\n// Change this code to try it yourself!\ndb.createIndex({\n index: {fields: ['debut']}\n}).then(function () {\n return db.find({\n selector: {debut: {$gt: null}},\n fields: ['_id', 'debut'],\n sort: ['debut']\n });\n});" 11 | ]; 12 | 13 | var editor = ace.edit("editor"); 14 | editor.setValue(snippets[0]); 15 | editor.setTheme("ace/theme/xcode"); 16 | var session = editor.getSession(); 17 | session.setMode("ace/mode/javascript"); 18 | session.setTabSize(2); 19 | var editorDiv = $('#editor'); 20 | editorDiv.addClass('shown'); 21 | 22 | // set up pouch 23 | var template = Handlebars.compile($("#smashers-template").html()); 24 | var listDiv = $('#smashers'); 25 | var rawDiv = $('#smashers-raw'); 26 | 27 | function updateList(res) { 28 | rawDiv.empty().append(JSON.stringify(res, undefined, 2)).addClass('shown'); 29 | listDiv.empty().append(template({smashers: res.docs})).addClass('shown'); 30 | } 31 | 32 | function showError(err) { 33 | rawDiv.empty(); 34 | listDiv.empty().append($('
').append(err.stack)).addClass('shown');
35 |   }
36 | 
37 |   var smashers = [
38 |     { name: 'Mario', _id: 'mario', series: 'Mario', debut: 1981 },
39 |     { name: 'Jigglypuff', _id: 'puff', series: 'Pokemon', debut: 1996 },
40 |     { name: 'Link', _id: 'link', series: 'Zelda', debut: 1986 },
41 |     { name: 'Donkey Kong', _id: 'dk', series: 'Mario', debut: 1981 },
42 |     { name: 'Pikachu', series: 'Pokemon', _id: 'pikachu', debut: 1996 },
43 |     { name: 'Captain Falcon', _id: 'falcon', series: 'F-Zero', debut: 1990 },
44 |     { name: 'Luigi', _id: 'luigi', series: 'Mario', debut: 1983 },
45 |     { name: 'Fox', _id: 'fox', series: 'Star Fox', debut: 1993 },
46 |     { name: 'Ness', _id: 'ness', series: 'Earthbound', debut: 1994 },
47 |     { name: 'Samus', _id: 'samus', series: 'Metroid', debut: 1986 },
48 |     { name: 'Yoshi', _id: 'yoshi', series: 'Mario', debut: 1990 },
49 |     { name: 'Kirby', _id: 'kirby', series: 'Kirby', debut: 1992 }
50 |   ];
51 |   var db = new PouchDB('smashers');
52 |   db.info().then(function (info) {
53 |     if (info.update_seq === 0) {
54 |       return db.bulkDocs(smashers); // initial DB
55 |     }
56 |   }).then(function () {
57 |     return eval(snippets[0]);
58 |   }).then(updateList);
59 | 
60 |   // set up buttons
61 |   $('.sortable').each(function (i, btn) {
62 |     $(btn).click(function () {
63 |       editor.setValue(snippets[i]);
64 |       $('.sortable').each(function (j, otherBtn) {
65 |         $(otherBtn).toggleClass('btn-selected', i === j);
66 |       });
67 |     });
68 |   });
69 | 
70 |   // set up "Run code"
71 | 
72 |   $('.run-code').click(function () {
73 |     listDiv.removeClass('shown');
74 |     rawDiv.removeClass('shown');
75 |     setTimeout(function () {
76 |       var promise = eval(editor.getValue());
77 |       promise.then(updateList).catch(showError);
78 |     }, 200);
79 |   });
80 | 
81 | })(jQuery);


--------------------------------------------------------------------------------
/www/bootstrap/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/bootstrap/fonts/glyphicons-halflings-regular.eot


--------------------------------------------------------------------------------
/www/bootstrap/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/bootstrap/fonts/glyphicons-halflings-regular.ttf


--------------------------------------------------------------------------------
/www/bootstrap/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/bootstrap/fonts/glyphicons-halflings-regular.woff


--------------------------------------------------------------------------------
/www/bootstrap/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/bootstrap/fonts/glyphicons-halflings-regular.woff2


--------------------------------------------------------------------------------
/www/bootstrap/js/npm.js:
--------------------------------------------------------------------------------
 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
 2 | require('../../js/transition.js')
 3 | require('../../js/alert.js')
 4 | require('../../js/button.js')
 5 | require('../../js/carousel.js')
 6 | require('../../js/collapse.js')
 7 | require('../../js/dropdown.js')
 8 | require('../../js/modal.js')
 9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')


--------------------------------------------------------------------------------
/www/ribbon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/ribbon.png


--------------------------------------------------------------------------------
/www/smashers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolanlawson/pouchdb-find/a31c9e551c8db9d727daee2988eac251cedb6238/www/smashers.png


--------------------------------------------------------------------------------