Merge branch 'master' into 1278-add-clustered-db-info
diff --git a/.gitignore b/.gitignore
index faa07f9..a1cba1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@
src/rebar/
src/snappy/
src/triq/
+src/hyper/
tmp/
src/couch/*.o
diff --git a/.travis.yml b/.travis.yml
index fe84f87..b2e7ff0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,6 @@
- 19.3
- 18.3
- 17.5
- - R16B03-1
addons:
apt:
@@ -64,6 +63,14 @@
after_failure:
- build-aux/logfile-uploader.py
+# start a push build on master and release branches + PRs build on every branch
+# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
+branches:
+ only:
+ - master
+ - /^\d+\.x\.x$/
+ - /^\d+\.\d+\.x$/
+
# Re-enable once test suite is reliable
#notifications:
# email: false
diff --git a/INSTALL.Unix.md b/INSTALL.Unix.md
index b2d4fbd..bfd9c89 100644
--- a/INSTALL.Unix.md
+++ b/INSTALL.Unix.md
@@ -137,8 +137,10 @@
You can install the remaining dependencies by running:
- pkg install npm4 help2man openssl icu curl git \
- autoconf automake libtool node spidermonkey185
+ pkg install help2man openssl icu curl git bash \
+ autoconf automake libtool node spidermonkey185 \
+ erlang node8 npm-node8 lang/python py27-sphinx py27-pip
+ pip install --upgrade sphinx_rtd_theme nose requests hypothesis
## Installing
diff --git a/Jenkinsfile b/Jenkinsfile
index 905a85f..46fb723 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -39,13 +39,13 @@
}
steps {
// This image has the oldest Erlang we support, 16B03
- sh 'docker pull couchdbdev/ubuntu-14.04-erlang-default:latest'
+ sh 'docker pull couchdbdev/debian-jessie-erlang-17.5.3:latest'
timeout(time: 15, unit: "MINUTES") {
// https://github.com/jenkins-infra/jenkins.io/blob/master/Jenkinsfile#64
// We need the jenkins user mapped inside of the image
// npm config cache below is required because /home/jenkins doesn't
// ACTUALLY exist in the image
- withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-default', args: '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group') {
+ withDockerContainer(image: 'couchdbdev/debian-jessie-erlang-17.5.3', args: '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group') {
sh '''
set
rm -rf apache-couchdb-*
@@ -78,24 +78,48 @@
// Build packages on supported platforms using esl's erlang
stage('Test') {
steps {
- parallel(centos6erlang183: {
+ parallel(freebsd: {
+ node(label: 'couchdb && freebsd') {
+ timeout(time: 60, unit: "MINUTES") {
+ deleteDir()
+ unstash 'tarball'
+ withEnv(['HOME='+pwd()]) {
+ sh '''
+ cwd=$(pwd)
+ mkdir -p $COUCHDB_IO_LOG_DIR
+
+ # Build CouchDB from tarball & test
+ builddir=$(mktemp -d)
+ cd $builddir
+ tar -xf $cwd/apache-couchdb-*.tar.gz
+ cd apache-couchdb-*
+ ./configure --with-curl
+ gmake check || (build-aux/logfile-uploader.py && false)
+
+ # No package build for FreeBSD at this time
+ '''
+ } // withEnv
+ } // timeout
+ deleteDir()
+ } // node
+ },
+ centos6: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/centos-6-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/centos-6-erlang-18.3', args: '-e LD_LIBRARY_PATH=/usr/local/bin') {
+ sh 'docker pull couchdbdev/centos-6-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/centos-6-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -118,24 +142,23 @@
deleteDir()
} // node
},
- centos7erlang183: {
+ centos7: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/centos-7-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/centos-7-erlang-18.3', args: '-e LD_LIBRARY_PATH=/usr/local/bin') {
+ sh 'docker pull couchdbdev/centos-7-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/centos-7-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -158,24 +181,23 @@
deleteDir()
} // node
},
- ubuntu1404erlang183: {
+ ubuntutrusty: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-14.04-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/ubuntu-14.04-erlang-18.3') {
+ sh 'docker pull couchdbdev/ubuntu-trusty-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/ubuntu-trusty-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -190,7 +212,7 @@
# Cleanup & save for posterity
rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
- mv ../couchdb/*deb $cwd/pkgs/$platform || true
+ mv ../couchdb/*.deb $cwd/pkgs/$platform || true
'''
} // withDocker
} // timeout
@@ -198,24 +220,23 @@
deleteDir()
} // node
},
- ubuntu1604erlang183: {
+ ubuntuxenial: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/ubuntu-16.04-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/ubuntu-16.04-erlang-18.3') {
+ sh 'docker pull couchdbdev/ubuntu-xenial-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/ubuntu-xenial-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -230,7 +251,7 @@
# Cleanup & save for posterity
rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
- mv ../couchdb/*deb $cwd/pkgs/$platform || true
+ mv ../couchdb/*.deb $cwd/pkgs/$platform || true
'''
} // withDocker
} // timeout
@@ -238,24 +259,23 @@
deleteDir()
} // node
},
- debian8erlang183: {
+ ubuntubionic: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/debian-8-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/debian-8-erlang-18.3') {
+ sh 'docker pull couchdbdev/ubuntu-bionic-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/ubuntu-bionic-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -270,7 +290,7 @@
# Cleanup & save for posterity
rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
- mv ../couchdb/*deb $cwd/pkgs/$platform || true
+ mv ../couchdb/*.deb $cwd/pkgs/$platform || true
'''
} // withDocker
} // timeout
@@ -278,24 +298,23 @@
deleteDir()
} // node
},
- debian9erlang183: {
+ debianjessie: {
node(label: 'ubuntu') {
timeout(time: 60, unit: "MINUTES") {
- sh 'docker pull couchdbdev/debian-9-erlang-18.3'
- withDockerContainer(image: 'couchdbdev/debian-9-erlang-18.3') {
+ sh 'docker pull couchdbdev/debian-jessie-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/debian-jessie-erlang-19.3.6') {
sh 'rm -f apache-couchdb-*.tar.gz'
unstash 'tarball'
sh '''
cwd=$(pwd)
mkdir -p $COUCHDB_IO_LOG_DIR
- # Build CouchDB from tarball
+ # Build CouchDB from tarball & test
builddir=$(mktemp -d)
cd $builddir
tar -xf $cwd/apache-couchdb-*.tar.gz
cd apache-couchdb-*
./configure --with-curl
- make all
make check || (build-aux/logfile-uploader.py && false)
# Build CouchDB packages
@@ -310,7 +329,46 @@
# Cleanup & save for posterity
rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
- mv ../couchdb/*deb $cwd/pkgs/$platform || true
+ mv ../couchdb/*.deb $cwd/pkgs/$platform || true
+ '''
+ } // withDocker
+ } // timeout
+ archiveArtifacts artifacts: 'pkgs/**', fingerprint: true
+ deleteDir()
+ } // node
+ },
+ debianstretch: {
+ node(label: 'ubuntu') {
+ timeout(time: 60, unit: "MINUTES") {
+ sh 'docker pull couchdbdev/debian-stretch-erlang-19.3.6'
+ withDockerContainer(image: 'couchdbdev/debian-stretch-erlang-19.3.6') {
+ sh 'rm -f apache-couchdb-*.tar.gz'
+ unstash 'tarball'
+ sh '''
+ cwd=$(pwd)
+ mkdir -p $COUCHDB_IO_LOG_DIR
+
+ # Build CouchDB from tarball & test
+ builddir=$(mktemp -d)
+ cd $builddir
+ tar -xf $cwd/apache-couchdb-*.tar.gz
+ cd apache-couchdb-*
+ ./configure --with-curl
+ make check || (build-aux/logfile-uploader.py && false)
+
+ # Build CouchDB packages
+ cd $builddir
+ git clone https://github.com/apache/couchdb-pkg
+ mkdir couchdb
+ cp $cwd/apache-couchdb-*.tar.gz couchdb
+ tar -xf $cwd/apache-couchdb-*.tar.gz -C couchdb
+ cd couchdb-pkg
+ platform=$(lsb_release -cs)
+ make $platform PLATFORM=$platform
+
+ # Cleanup & save for posterity
+ rm -rf $cwd/pkgs/$platform && mkdir -p $cwd/pkgs/$platform
+ mv ../couchdb/*.deb $cwd/pkgs/$platform || true
'''
} // withDocker
} // timeout
@@ -335,8 +393,8 @@
}
}
steps {
- sh 'docker pull couchdbdev/debian-8-base:latest'
- withDockerContainer(image: 'couchdbdev/debian-8-base:latest', args: '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group') {
+ sh 'docker pull couchdbdev/debian-stretch-erlang-19.3.6:latest'
+ withDockerContainer(image: 'couchdbdev/debian-stretch-erlang-19.3.6:latest', args: '-e npm_config_cache=npm-cache -e HOME=. -v=/etc/passwd:/etc/passwd -v /etc/group:/etc/group') {
withCredentials([file(credentialsId: 'jenkins-key', variable: 'KEY')]) {
sh 'rm -rf pkgs *.tar.gz'
unarchive mapping: ['pkgs/' : '.']
@@ -346,19 +404,28 @@
rsync -avz -e "ssh -o StrictHostKeyChecking=no -i $KEY" jenkins@couchdb-vm2.apache.org:/var/www/html/$BRANCH_NAME . || mkdir -p $BRANCH_NAME
rm -rf $BRANCH_NAME/debian/* $BRANCH_NAME/el6/* $BRANCH_NAME/el7/*
mkdir -p $BRANCH_NAME/debian $BRANCH_NAME/el6 $BRANCH_NAME/el7 $BRANCH_NAME/source
+ rsync -avz -e "ssh -o StrictHostKeyChecking=no -i $KEY" jenkins@couchdb-vm2.apache.org:/var/www/html/js .
'''
echo 'Building Debian repo...'
sh '''
git clone https://github.com/apache/couchdb-pkg
- reprepro -b couchdb-pkg/repo includedeb jessie pkgs/jessie/*deb
- reprepro -b couchdb-pkg/repo includedeb trusty pkgs/trusty/*deb
- reprepro -b couchdb-pkg/repo includedeb xenial pkgs/xenial/*deb
- reprepro -b couchdb-pkg/repo includedeb stretch pkgs/stretch/*deb
+ cp js/debian-jessie/*.deb pkgs/jessie
+ reprepro -b couchdb-pkg/repo includedeb jessie pkgs/jessie/*.deb
+ cp js/debian-stretch/*.deb pkgs/stretch
+ reprepro -b couchdb-pkg/repo includedeb stretch pkgs/stretch/*.deb
+ cp js/ubuntu-trusty/*.deb pkgs/trusty
+ reprepro -b couchdb-pkg/repo includedeb trusty pkgs/trusty/*.deb
+ cp js/ubuntu-xenial/*.deb pkgs/xenial
+ reprepro -b couchdb-pkg/repo includedeb xenial pkgs/xenial/*.deb
+ cp js/ubuntu-bionic/*.deb pkgs/bionic
+ reprepro -b couchdb-pkg/repo includedeb bionic pkgs/bionic/*.deb
'''
echo 'Building CentOS repos...'
sh '''
+ cp js/centos-6/*rpm pkgs/centos6
+ cp js/centos-7/*rpm pkgs/centos7
cd pkgs/centos6 && createrepo --database .
- cd ../centos7 && rm -f js* && createrepo --database .
+ cd ../centos7 && createrepo --database .
'''
echo 'Building tree to upload...'
sh '''
diff --git a/LICENSE b/LICENSE
index a209352..6034c71 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2274,3 +2274,27 @@
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
+
+For the src/hyper component:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Game Analytics ApS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
index d56d4e8..27d9531 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,11 @@
ifeq ($(IN_RELEASE), true)
COUCHDB_VERSION = $(vsn_major).$(vsn_minor).$(vsn_patch)
else
-RELTAG = $(shell git describe | grep -E '^[0-9]+\.[0-9]\.[0-9]+(-RC[0-9]+)?$$')
+# IN_RC generates a tarball that has the -RCx suffix in the name if needed
+IN_RC = $(shell git describe --tags --always --first-parent \
+ | grep -Eo -- '-RC[0-9]+' 2>/dev/null)
+RELTAG = $(shell git describe --dirty --abbrev=0 --tags --always --first-parent \
+ | grep -Eo '^[0-9]+\.[0-9]\.[0-9]+')
ifeq ($(RELTAG),)
COUCHDB_VERSION_SUFFIX = $(shell git rev-parse --short --verify HEAD)
COUCHDB_VERSION = $(vsn_major).$(vsn_minor).$(vsn_patch)-$(COUCHDB_VERSION_SUFFIX)
@@ -30,20 +34,23 @@
# Rebar options
apps=
-skip_deps=folsom,meck,mochiweb,triq,snappy
+skip_deps=folsom,meck,mochiweb,triq,snappy,bcrypt,hyper
suites=
tests=
+COMPILE_OPTS=$(shell echo "\
+ apps=$(apps) \
+ " | sed -e 's/[a-z_]\{1,\}= / /g')
EUNIT_OPTS=$(shell echo "\
apps=$(apps) \
skip_deps=$(skip_deps) \
suites=$(suites) \
tests=$(tests) \
- " | sed -e 's/[a-z]\+= / /g')
+ " | sed -e 's/[a-z]\{1,\}= / /g')
DIALYZE_OPTS=$(shell echo "\
apps=$(apps) \
skip_deps=$(skip_deps) \
- " | sed -e 's/[a-z]\+= / /g')
+ " | sed -e 's/[a-z]\{1,\}= / /g')
#ignore javascript tests
ignore_js_suites=
@@ -73,9 +80,9 @@
.PHONY: couch
-# target: couch - Build CouchDB core
+# target: couch - Build CouchDB core, use ERL_OPTS to provide custom compiler's options
couch: config.erl
- @COUCHDB_VERSION=$(COUCHDB_VERSION) $(REBAR) compile
+ @COUCHDB_VERSION=$(COUCHDB_VERSION) $(REBAR) compile $(COMPILE_OPTS)
@cp src/couch/priv/couchjs bin/
@@ -289,8 +296,8 @@
@mkdir -p apache-couchdb-$(COUCHDB_VERSION)/share/docs/man
@cp src/docs/build/man/apachecouchdb.1 apache-couchdb-$(COUCHDB_VERSION)/share/docs/man/
- @tar czf apache-couchdb-$(COUCHDB_VERSION).tar.gz apache-couchdb-$(COUCHDB_VERSION)
- @echo "Done: apache-couchdb-$(COUCHDB_VERSION).tar.gz"
+ @tar czf apache-couchdb-$(COUCHDB_VERSION)$(IN_RC).tar.gz apache-couchdb-$(COUCHDB_VERSION)
+ @echo "Done: apache-couchdb-$(COUCHDB_VERSION)$(IN_RC).tar.gz"
.PHONY: release
diff --git a/NOTICE b/NOTICE
index c040338..481e755 100644
--- a/NOTICE
+++ b/NOTICE
@@ -184,3 +184,7 @@
1997 Niels Provos <provos@physnet.uni-hamburg.de>
- The asynchronous queue code (c_src/async_queue.c and c_src/async_queue.h)
is from the esnappy project, copyright 2011 Konstantin V. Sorokin.
+
+* hyper
+
+ Copyright (c) 2014 Game Analytics ApS
diff --git a/rebar.config.script b/rebar.config.script
index a1be24f..1411752 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -47,20 +47,21 @@
DepDescs = [
%% Independent Apps
-{config, "config", {tag, "1.0.3"}},
+{config, "config", {tag, "1.0.4"}},
{b64url, "b64url", {tag, "1.0.1"}},
{ets_lru, "ets-lru", {tag, "1.0.0"}},
{khash, "khash", {tag, "1.0.1"}},
-{snappy, "snappy", {tag, "CouchDB-1.0.0"}},
+{snappy, "snappy", {tag, "CouchDB-1.0.1"}},
{ioq, "ioq", {tag, "1.0.1"}},
%% Non-Erlang deps
{docs, {url, "https://github.com/apache/couchdb-documentation"},
- {tag, "2.1.0"}, [raw]},
+ {tag, "2.1.2"}, [raw]},
{fauxton, {url, "https://github.com/apache/couchdb-fauxton"},
- {tag, "v1.1.13"}, [raw]},
+ {tag, "v1.1.15"}, [raw]},
%% Third party deps
{folsom, "folsom", {tag, "CouchDB-0.8.2"}},
+{hyper, "hyper", {tag, "CouchDB-2.2.0-3"}},
{ibrowse, "ibrowse", {tag, "CouchDB-4.0.1"}},
{jiffy, "jiffy", {tag, "CouchDB-0.14.11-2"}},
{mochiweb, "mochiweb", {tag, "v2.17.0"}},
@@ -86,13 +87,18 @@
{AppName, ".*", {git, Url, Version}, Options}
end,
+ErlOpts = case os:getenv("ERL_OPTS") of
+ false -> [];
+ Opts -> [list_to_atom(O) || O <- string:tokens(Opts, ",")]
+end,
+
AddConfig = [
- {require_otp_vsn, "R16B03|R16B03-1|17|18|19|20"},
+ {require_otp_vsn, "17|18|19|20"},
{deps_dir, "src"},
{deps, lists:map(MakeDep, DepDescs)},
{sub_dirs, SubDirs},
{lib_dirs, ["src"]},
- {erl_opts, [bin_opt_info, debug_info, {i, "../"}]},
+ {erl_opts, [{i, "../"} | ErlOpts]},
{eunit_opts, [verbose]},
{plugins, [eunit_plugin]},
{dialyzer, [
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 66ade00..0f0d547 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -8,7 +8,7 @@
view_index_dir = {{view_index_dir}}
; util_driver_dir =
; plugin_dir =
-os_process_timeout = 5000 ; 5 seconds. for view and external servers.
+os_process_timeout = 5000 ; 5 seconds. for view servers.
max_dbs_open = 500
delayed_commits = false
; Method used to compress everything that is appended to database and view index files, except
@@ -58,6 +58,12 @@
; The default storage engine to use when creating databases
; is set as a key into the [couchdb_engines] section.
default_engine = couch
+;
+; Enable this to only "soft-delete" databases when DELETE /{db} requests are
+; made. This will place a .recovery directory in your data directory and
+; move deleted databases/shards there instead. You can then manually delete
+; these files later, as desired.
+;enable_database_recovery = false
[couchdb_engines]
; The keys in this section are the filename extension that
@@ -87,6 +93,11 @@
; _dbs_info in a request
max_db_number_for_dbs_info_req = 100
+; authentication handlers
+; authentication_handlers = {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler}
+; uncomment the next line to enable proxy authentication
+; authentication_handlers = {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler}
+
[database_compaction]
; larger buffer sizes can originate smaller files
doc_buffer_size = 524288 ; value in bytes
@@ -271,7 +282,6 @@
[daemons]
index_server={couch_index_server, start_link, []}
-external_manager={couch_external_manager, start_link, []}
query_servers={couch_proc_manager, start_link, []}
vhosts={couch_httpd_vhost, start_link, []}
httpd={couch_httpd, start_link, []}
@@ -316,12 +326,6 @@
_temp_view = {couch_mrview_http, handle_temp_view_req}
_view_cleanup = {couch_mrview_http, handle_cleanup_req}
-; The external module takes an optional argument allowing you to narrow it to a
-; single script. Otherwise the script name is inferred from the first path section
-; after _external's own path.
-; _mypath = {couch_httpd_external, handle_external_req, <<"mykey">>}
-; _external = {couch_httpd_external, handle_external_req}
-
[httpd_design_handlers]
_compact = {couch_mrview_http, handle_compact_req}
_info = {couch_mrview_http, handle_info_req}
@@ -332,21 +336,6 @@
_view = {couch_mrview_http, handle_view_req}
_view_changes = {couch_mrview_http, handle_view_changes_req}
-; enable external as an httpd handler, then link it with commands here.
-; note, this api is still under consideration.
-; [external]
-; mykey = /path/to/mycommand
-
-; Here you can setup commands for CouchDB to manage
-; while it is alive. It will attempt to keep each command
-; alive if it exits.
-; [os_daemons]
-; some_daemon_name = /path/to/script -with args
-; [os_daemon_settings]
-; max_retries = 3
-; retry_time = 5
-
-
[uuids]
; Known algorithms:
; random - 128 bits of random awesome
diff --git a/rel/overlay/etc/local.ini b/rel/overlay/etc/local.ini
index 6b46f0f..e3b7b15 100644
--- a/rel/overlay/etc/local.ini
+++ b/rel/overlay/etc/local.ini
@@ -46,23 +46,12 @@
[query_servers]
;nodejs = /usr/local/bin/couchjs-node /path/to/couchdb/share/server/main.js
-
-[httpd_global_handlers]
-;_google = {couch_httpd_proxy, handle_proxy_req, <<"http://www.google.com">>}
-
[couch_httpd_auth]
; If you set this to true, you should also uncomment the WWW-Authenticate line
; above. If you don't configure a WWW-Authenticate header, CouchDB will send
; Basic realm="server" in order to prevent you getting logged out.
; require_valid_user = false
-[os_daemons]
-; For any commands listed here, CouchDB will attempt to ensure that
-; the process remains alive. Daemons should monitor their environment
-; to know when to exit. This can most easily be accomplished by exiting
-; when stdin is closed.
-;foo = /path/to/command -with args
-
[daemons]
; enable SSL support by uncommenting the following line and supply the PEM's below.
; the default ssl port CouchDB listens on is 6984
@@ -103,9 +92,6 @@
[vhosts]
;example.com = /database/
-[update_notification]
-;unique notifier name=/full/path/to/exe -with "cmd line arg"
-
; To create an admin account uncomment the '[admins]' section below and add a
; line in the format 'username = password'. When you next start CouchDB, it
; will change the password to a hash (so that your passwords don't linger
diff --git a/rel/overlay/etc/vm.args b/rel/overlay/etc/vm.args
index acb4571..e9f0737 100644
--- a/rel/overlay/etc/vm.args
+++ b/rel/overlay/etc/vm.args
@@ -45,3 +45,9 @@
# Comment this line out to enable the interactive Erlang shell on startup
+Bd -noinput
+
+# Force use of the smp scheduler, fixes #1296
+-smp enable
+
+# Set maximum SSL session lifetime to reap terminated replication readers
+-ssl session_lifetime 300
diff --git a/rel/reltool.config b/rel/reltool.config
index aa31006..5e86d96 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -47,6 +47,7 @@
fabric,
folsom,
global_changes,
+ hyper,
ibrowse,
ioq,
jiffy,
@@ -101,6 +102,7 @@
{app, fabric, [{incl_cond, include}]},
{app, folsom, [{incl_cond, include}]},
{app, global_changes, [{incl_cond, include}]},
+ {app, hyper, [{incl_cond, include}]},
{app, ibrowse, [{incl_cond, include}]},
{app, ioq, [{incl_cond, include}]},
{app, jiffy, [{incl_cond, include}]},
diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
index 6be0d18..c0179ba 100644
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -104,10 +104,12 @@
end,
SslOpts = ServerOpts ++ ClientOpts,
- Options =
+ Options0 =
[{port, Port},
{ssl, true},
{ssl_opts, SslOpts}],
+ CustomServerOpts = get_server_options("httpsd"),
+ Options = merge_server_options(Options0, CustomServerOpts),
start_link(https, Options).
start_link(Name, Options) ->
@@ -124,9 +126,8 @@
{name, Name},
{ip, IP}
],
- ServerOptsCfg = config:get("chttpd", "server_options", "[]"),
- {ok, ServerOpts} = couch_util:parse_term(ServerOptsCfg),
- Options2 = lists:keymerge(1, lists:sort(Options1), lists:sort(ServerOpts)),
+ ServerOpts = get_server_options("chttpd"),
+ Options2 = merge_server_options(Options1, ServerOpts),
case mochiweb_http:start(Options2) of
{ok, Pid} ->
{ok, Pid};
@@ -135,6 +136,14 @@
{error, Reason}
end.
+get_server_options(Module) ->
+ ServerOptsCfg = config:get(Module, "server_options", "[]"),
+ {ok, ServerOpts} = couch_util:parse_term(ServerOptsCfg),
+ ServerOpts.
+
+merge_server_options(A, B) ->
+ lists:keymerge(1, lists:sort(A), lists:sort(B)).
+
stop() ->
catch mochiweb_http:stop(https),
mochiweb_http:stop(?MODULE).
@@ -288,11 +297,7 @@
not_preflight ->
case chttpd_auth:authenticate(HttpReq, fun authenticate_request/1) of
#httpd{} = Req ->
- HandlerFun = chttpd_handlers:url_handler(
- HandlerKey, fun chttpd_db:handle_request/1),
- AuthorizedReq = chttpd_auth:authorize(possibly_hack(Req),
- fun chttpd_auth_request:authorize_request/1),
- {AuthorizedReq, HandlerFun(AuthorizedReq)};
+ handle_req_after_auth(HandlerKey, Req);
Response ->
{HttpReq, Response}
end;
@@ -303,6 +308,17 @@
{HttpReq, catch_error(HttpReq, Tag, Error)}
end.
+handle_req_after_auth(HandlerKey, HttpReq) ->
+ try
+ HandlerFun = chttpd_handlers:url_handler(HandlerKey,
+ fun chttpd_db:handle_request/1),
+ AuthorizedReq = chttpd_auth:authorize(possibly_hack(HttpReq),
+ fun chttpd_auth_request:authorize_request/1),
+ {AuthorizedReq, HandlerFun(AuthorizedReq)}
+ catch Tag:Error ->
+ {HttpReq, catch_error(HttpReq, Tag, Error)}
+ end.
+
catch_error(_HttpReq, throw, {http_head_abort, Resp}) ->
{ok, Resp};
catch_error(_HttpReq, throw, {http_abort, Resp, Reason}) ->
@@ -1238,4 +1254,38 @@
ok = meck:unload(couch_log),
Message.
+handle_req_after_auth_test() ->
+ Headers = mochiweb_headers:make([{"HOST", "127.0.0.1:15984"}]),
+ MochiReq = mochiweb_request:new(socket, [], 'PUT', "/newdb", version,
+ Headers),
+ UserCtx = #user_ctx{name = <<"retain_user">>},
+ Roles = [<<"_reader">>],
+ AuthorizedCtx = #user_ctx{name = <<"retain_user">>, roles = Roles},
+ Req = #httpd{
+ mochi_req = MochiReq,
+ begin_ts = {1458,588713,124003},
+ original_method = 'PUT',
+ peer = "127.0.0.1",
+ nonce = "nonce",
+ user_ctx = UserCtx
+ },
+ AuthorizedReq = Req#httpd{user_ctx = AuthorizedCtx},
+ ok = meck:new(chttpd_handlers, [passthrough]),
+ ok = meck:new(chttpd_auth, [passthrough]),
+ ok = meck:expect(chttpd_handlers, url_handler, fun(_Key, _Fun) ->
+ fun(_Req) -> handled_authorized_req end
+ end),
+ ok = meck:expect(chttpd_auth, authorize, fun(_Req, _Fun) ->
+ AuthorizedReq
+ end),
+ ?assertEqual({AuthorizedReq, handled_authorized_req},
+ handle_req_after_auth(foo_key, Req)),
+ ok = meck:expect(chttpd_auth, authorize, fun(_Req, _Fun) ->
+ meck:exception(throw, {http_abort, resp, some_reason})
+ end),
+ ?assertEqual({Req, {aborted, resp, some_reason}},
+ handle_req_after_auth(foo_key, Req)),
+ ok = meck:unload(chttpd_handlers),
+ ok = meck:unload(chttpd_auth).
+
-endif.
diff --git a/src/chttpd/src/chttpd_auth.erl b/src/chttpd/src/chttpd_auth.erl
index be12148..6602468 100644
--- a/src/chttpd/src/chttpd_auth.erl
+++ b/src/chttpd/src/chttpd_auth.erl
@@ -17,6 +17,7 @@
-export([default_authentication_handler/1]).
-export([cookie_authentication_handler/1]).
+-export([proxy_authentication_handler/1]).
-export([party_mode_handler/1]).
-export([handle_session_req/1]).
@@ -47,6 +48,9 @@
cookie_authentication_handler(Req) ->
couch_httpd_auth:cookie_authentication_handler(Req, chttpd_auth_cache).
+proxy_authentication_handler(Req) ->
+ couch_httpd_auth:proxy_authentication_handler(Req).
+
party_mode_handler(Req) ->
case config:get("chttpd", "require_valid_user", "false") of
"true" ->
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
index eaf1ed3..5610779 100644
--- a/src/chttpd/src/chttpd_db.erl
+++ b/src/chttpd/src/chttpd_db.erl
@@ -314,7 +314,6 @@
end.
do_db_req(#httpd{path_parts=[DbName|_], user_ctx=Ctx}=Req, Fun) ->
- fabric:get_security(DbName, [{user_ctx,Ctx}]), % calls check_is_reader
{ok, Db} = couch_db:clustered_db(DbName, Ctx),
Fun(Req, Db).
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
index 253da23..95345d4 100644
--- a/src/chttpd/src/chttpd_misc.erl
+++ b/src/chttpd/src/chttpd_misc.erl
@@ -293,11 +293,15 @@
% "value"
handle_node_req(#httpd{method='PUT', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
couch_util:check_config_blacklist(Section),
- Value = chttpd:json_body(Req),
+ Value = couch_util:trim(chttpd:json_body(Req)),
Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
OldValue = call_node(Node, config, get, [Section, Key, ""]),
- ok = call_node(Node, config, set, [Section, Key, ?b2l(Value), Persist]),
- send_json(Req, 200, list_to_binary(OldValue));
+ case call_node(Node, config, set, [Section, Key, ?b2l(Value), Persist]) of
+ ok ->
+ send_json(Req, 200, list_to_binary(OldValue));
+ {error, Reason} ->
+ chttpd:send_error(Req, {bad_request, Reason})
+ end;
% GET /_node/$node/_config/Section/Key
handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
case call_node(Node, config, get, [Section, Key, undefined]) of
diff --git a/src/couch/src/couch.app.src b/src/couch/src/couch.app.src
index 524b728..cf5cee6 100644
--- a/src/couch/src/couch.app.src
+++ b/src/couch/src/couch.app.src
@@ -47,6 +47,7 @@
couch_log,
couch_event,
ioq,
- couch_stats
+ couch_stats,
+ hyper
]}
]}.
diff --git a/src/couch/src/couch_bt_engine.erl b/src/couch/src/couch_bt_engine.erl
index 43a77b0..a42d116 100644
--- a/src/couch/src/couch_bt_engine.erl
+++ b/src/couch/src/couch_bt_engine.erl
@@ -114,7 +114,7 @@
%% Delete any leftover compaction files. If we don't do this a
%% subsequent request for this DB will try to open them to use
%% as a recovery.
- delete_compaction_files(RootDir, FilePath, [{context, delete}]),
+ delete_compaction_files(RootDir, FilePath, [{context, compaction}]),
% Delete the actual database file
couch_file:delete(RootDir, FilePath, Async).
@@ -765,7 +765,7 @@
delete_compaction_files(FilePath) ->
RootDir = config:get("couchdb", "database_dir", "."),
- DelOpts = [{context, delete}],
+ DelOpts = [{context, compaction}],
delete_compaction_files(RootDir, FilePath, DelOpts).
diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 93ea07e..b47cc7e 100644
--- a/src/couch/src/couch_db.erl
+++ b/src/couch/src/couch_db.erl
@@ -870,16 +870,10 @@
{[], AccErrors}, Bucket),
prep_and_validate_replicated_updates(Db, RestBuckets, RestOldInfo, [ValidatedBucket | AccPrepped], AccErrors3);
#full_doc_info{rev_tree=OldTree} ->
- RevsLimit = get_revs_limit(Db),
OldLeafs = couch_key_tree:get_all_leafs_full(OldTree),
OldLeafsLU = [{Start, RevId} || {Start, [{RevId, _}|_]} <- OldLeafs],
- NewRevTree = lists:foldl(
- fun(NewDoc, AccTree) ->
- {NewTree, _} = couch_key_tree:merge(AccTree,
- couch_doc:to_path(NewDoc), RevsLimit),
- NewTree
- end,
- OldTree, Bucket),
+ NewPaths = lists:map(fun couch_doc:to_path/1, Bucket),
+ NewRevTree = couch_key_tree:multi_merge(OldTree, NewPaths),
Leafs = couch_key_tree:get_all_leafs_full(NewRevTree),
LeafRevsFullDict = dict:from_list( [{{Start, RevId}, FullPath} || {Start, [{RevId, _}|_]}=FullPath <- Leafs]),
{ValidatedBucket, AccErrors3} =
diff --git a/src/couch/src/couch_db_updater.erl b/src/couch/src/couch_db_updater.erl
index 79567e9..acb9ec1 100644
--- a/src/couch/src/couch_db_updater.erl
+++ b/src/couch/src/couch_db_updater.erl
@@ -82,16 +82,17 @@
handle_call({set_security, NewSec}, _From, #db{} = Db) ->
{ok, NewDb} = couch_db_engine:set_security(Db, NewSec),
- NewSecDb = NewDb#db{
+ NewSecDb = commit_data(NewDb#db{
security = NewSec
- },
+ }),
ok = gen_server:call(couch_server, {db_updated, NewSecDb}, infinity),
{reply, ok, NewSecDb, idle_limit()};
handle_call({set_revs_limit, Limit}, _From, Db) ->
{ok, Db2} = couch_db_engine:set_revs_limit(Db, Limit),
- ok = gen_server:call(couch_server, {db_updated, Db2}, infinity),
- {reply, ok, Db2, idle_limit()};
+ Db3 = commit_data(Db2),
+ ok = gen_server:call(couch_server, {db_updated, Db3}, infinity),
+ {reply, ok, Db3, idle_limit()};
handle_call({purge_docs, _IdRevs}, _From,
#db{compactor_pid=Pid}=Db) when Pid /= nil ->
@@ -160,12 +161,12 @@
Pairs = pair_purge_info(PreviousFDIs, FDIs),
{ok, Db2} = couch_db_engine:write_doc_infos(Db, Pairs, [], PurgedIdRevs),
-
- ok = gen_server:call(couch_server, {db_updated, Db2}, infinity),
+ Db3 = commit_data(Db2),
+ ok = gen_server:call(couch_server, {db_updated, Db3}, infinity),
couch_event:notify(Db#db.name, updated),
- PurgeSeq = couch_db_engine:get_purge_seq(Db2),
- {reply, {ok, PurgeSeq, PurgedIdRevs}, Db2, idle_limit()};
+ PurgeSeq = couch_db_engine:get_purge_seq(Db3),
+ {reply, {ok, PurgeSeq, PurgedIdRevs}, Db3, idle_limit()};
handle_call(Msg, From, Db) ->
case couch_db_engine:handle_db_updater_call(Msg, From, Db) of
@@ -503,23 +504,24 @@
[OldDocInfo|RestOldInfo], AccNewInfos, AccRemoveSeqs, AccSeq) ->
erlang:put(last_id_merged, OldDocInfo#full_doc_info.id), % for debugging
NewDocInfo0 = lists:foldl(fun({Client, NewDoc}, OldInfoAcc) ->
- merge_rev_tree(OldInfoAcc, NewDoc, Client, Limit, MergeConflicts)
+ merge_rev_tree(OldInfoAcc, NewDoc, Client, MergeConflicts)
end, OldDocInfo, NewDocs),
+ NewDocInfo1 = maybe_stem_full_doc_info(NewDocInfo0, Limit),
% When MergeConflicts is false, we updated #full_doc_info.deleted on every
% iteration of merge_rev_tree. However, merge_rev_tree does not update
% #full_doc_info.deleted when MergeConflicts is true, since we don't need
% to know whether the doc is deleted between iterations. Since we still
% need to know if the doc is deleted after the merge happens, we have to
% set it here.
- NewDocInfo1 = case MergeConflicts of
+ NewDocInfo2 = case MergeConflicts of
true ->
- NewDocInfo0#full_doc_info{
- deleted = couch_doc:is_deleted(NewDocInfo0)
+ NewDocInfo1#full_doc_info{
+ deleted = couch_doc:is_deleted(NewDocInfo1)
};
false ->
- NewDocInfo0
+ NewDocInfo1
end,
- if NewDocInfo1 == OldDocInfo ->
+ if NewDocInfo2 == OldDocInfo ->
% nothing changed
merge_rev_trees(Limit, MergeConflicts, RestDocsList, RestOldInfo,
AccNewInfos, AccRemoveSeqs, AccSeq);
@@ -528,7 +530,7 @@
% important to note that the update_seq on OldDocInfo should
% be identical to the value on NewDocInfo1.
OldSeq = OldDocInfo#full_doc_info.update_seq,
- NewDocInfo2 = NewDocInfo1#full_doc_info{
+ NewDocInfo3 = NewDocInfo2#full_doc_info{
update_seq = AccSeq + 1
},
RemoveSeqs = case OldSeq of
@@ -536,10 +538,10 @@
_ -> [OldSeq | AccRemoveSeqs]
end,
merge_rev_trees(Limit, MergeConflicts, RestDocsList, RestOldInfo,
- [NewDocInfo2|AccNewInfos], RemoveSeqs, AccSeq+1)
+ [NewDocInfo3|AccNewInfos], RemoveSeqs, AccSeq+1)
end.
-merge_rev_tree(OldInfo, NewDoc, Client, Limit, false)
+merge_rev_tree(OldInfo, NewDoc, Client, false)
when OldInfo#full_doc_info.deleted ->
% We're recreating a document that was previously
% deleted. To check that this is a recreation from
@@ -573,7 +575,7 @@
% Merge our modified new doc into the tree
#full_doc_info{rev_tree=OldTree} = OldInfo,
NewTree0 = couch_doc:to_path(NewDoc2),
- case couch_key_tree:merge(OldTree, NewTree0, Limit) of
+ case couch_key_tree:merge(OldTree, NewTree0) of
{NewTree1, new_leaf} ->
% We changed the revision id so inform the caller
send_result(Client, NewDoc, {ok, {OldPos+1, NewRevId}}),
@@ -588,7 +590,7 @@
send_result(Client, NewDoc, conflict),
OldInfo
end;
-merge_rev_tree(OldInfo, NewDoc, Client, Limit, false) ->
+merge_rev_tree(OldInfo, NewDoc, Client, false) ->
% We're attempting to merge a new revision into an
% undeleted document. To not be a conflict we require
% that the merge results in extending a branch.
@@ -596,7 +598,7 @@
OldTree = OldInfo#full_doc_info.rev_tree,
NewTree0 = couch_doc:to_path(NewDoc),
NewDeleted = NewDoc#doc.deleted,
- case couch_key_tree:merge(OldTree, NewTree0, Limit) of
+ case couch_key_tree:merge(OldTree, NewTree0) of
{NewTree, new_leaf} when not NewDeleted ->
OldInfo#full_doc_info{
rev_tree = NewTree,
@@ -614,14 +616,23 @@
send_result(Client, NewDoc, conflict),
OldInfo
end;
-merge_rev_tree(OldInfo, NewDoc, _Client, Limit, true) ->
+merge_rev_tree(OldInfo, NewDoc, _Client, true) ->
% We're merging in revisions without caring about
% conflicts. Most likely this is a replication update.
OldTree = OldInfo#full_doc_info.rev_tree,
NewTree0 = couch_doc:to_path(NewDoc),
- {NewTree, _} = couch_key_tree:merge(OldTree, NewTree0, Limit),
+ {NewTree, _} = couch_key_tree:merge(OldTree, NewTree0),
OldInfo#full_doc_info{rev_tree = NewTree}.
+maybe_stem_full_doc_info(#full_doc_info{rev_tree = Tree} = Info, Limit) ->
+ case config:get_boolean("couchdb", "stem_interactive_updates", true) of
+ true ->
+ Stemmed = couch_key_tree:stem(Tree, Limit),
+ Info#full_doc_info{rev_tree = Stemmed};
+ false ->
+ Info
+ end.
+
update_docs_int(Db, DocsList, LocalDocs, MergeConflicts, FullCommit) ->
UpdateSeq = couch_db_engine:get_update_seq(Db),
RevsLimit = couch_db_engine:get_revs_limit(Db),
diff --git a/src/couch/src/couch_httpd_misc_handlers.erl b/src/couch/src/couch_httpd_misc_handlers.erl
index e2fc9f2..0c70bcb 100644
--- a/src/couch/src/couch_httpd_misc_handlers.erl
+++ b/src/couch/src/couch_httpd_misc_handlers.erl
@@ -262,7 +262,7 @@
<<"admins">> ->
couch_passwords:hash_admin_password(RawValue);
_ ->
- RawValue
+ couch_util:trim(RawValue)
end
end,
OldValue = config:get(Section, Key, ""),
diff --git a/src/couch/src/couch_httpd_multipart.erl b/src/couch/src/couch_httpd_multipart.erl
index e556b28..33795a3 100644
--- a/src/couch/src/couch_httpd_multipart.erl
+++ b/src/couch/src/couch_httpd_multipart.erl
@@ -208,7 +208,8 @@
{ok, {WriterRef, _}} ->
case num_mp_writers() of
N when N > 1 ->
- num_mp_writers(N - 1);
+ num_mp_writers(N - 1),
+ orddict:erase(WriterPid, Counters);
_ ->
abort_parsing
end;
diff --git a/src/couch/src/couch_key_tree.erl b/src/couch/src/couch_key_tree.erl
index cd661e2..9415041 100644
--- a/src/couch/src/couch_key_tree.erl
+++ b/src/couch/src/couch_key_tree.erl
@@ -59,7 +59,7 @@
map/2,
map_leafs/2,
mapfold/3,
-merge/3,
+multi_merge/2,
merge/2,
remove_leafs/2,
stem/2
@@ -71,16 +71,13 @@
-type revtree() :: [tree()].
-%% @doc Merge a path into the given tree and then stem the result.
-%% Although Tree is of type tree(), it must not contain any branches.
--spec merge(revtree(), tree() | path(), pos_integer()) ->
- {revtree(), new_leaf | new_branch | internal_node}.
-merge(RevTree, Tree, StemDepth) ->
- {Merged, Result} = merge(RevTree, Tree),
- case config:get("couchdb", "stem_interactive_updates", "true") of
- "true" -> {stem(Merged, StemDepth), Result};
- _ -> {Merged, Result}
- end.
+%% @doc Merge multiple paths into the given tree.
+-spec multi_merge(revtree(), tree()) -> revtree().
+multi_merge(RevTree, Trees) ->
+ lists:foldl(fun(Tree, RevTreeAcc) ->
+ {NewRevTree, _} = merge(RevTreeAcc, Tree),
+ NewRevTree
+ end, RevTree, lists:sort(Trees)).
%% @doc Merge a path into a tree.
@@ -470,6 +467,70 @@
stem(Trees, Limit) ->
+ try
+ {_, Branches} = lists:foldl(fun(Tree, {Seen, TreeAcc}) ->
+ {NewSeen, NewBranches} = stem_tree(Tree, Limit, Seen),
+ {NewSeen, NewBranches ++ TreeAcc}
+ end, {sets:new(), []}, Trees),
+ lists:sort(Branches)
+ catch throw:dupe_keys ->
+ repair_tree(Trees, Limit)
+ end.
+
+
+stem_tree({Depth, Child}, Limit, Seen) ->
+ case stem_tree(Depth, Child, Limit, Seen) of
+ {NewSeen, _, NewChild, NewBranches} ->
+ {NewSeen, [{Depth, NewChild} | NewBranches]};
+ {NewSeen, _, NewBranches} ->
+ {NewSeen, NewBranches}
+ end.
+
+
+stem_tree(_Depth, {Key, _Val, []} = Leaf, Limit, Seen) ->
+ {check_key(Key, Seen), Limit - 1, Leaf, []};
+
+stem_tree(Depth, {Key, Val, Children}, Limit, Seen0) ->
+ Seen1 = check_key(Key, Seen0),
+ FinalAcc = lists:foldl(fun(Child, Acc) ->
+ {SeenAcc, LimitPosAcc, ChildAcc, BranchAcc} = Acc,
+ case stem_tree(Depth + 1, Child, Limit, SeenAcc) of
+ {NewSeenAcc, LimitPos, NewChild, NewBranches} ->
+ NewLimitPosAcc = erlang:max(LimitPos, LimitPosAcc),
+ NewChildAcc = [NewChild | ChildAcc],
+ NewBranchAcc = NewBranches ++ BranchAcc,
+ {NewSeenAcc, NewLimitPosAcc, NewChildAcc, NewBranchAcc};
+ {NewSeenAcc, LimitPos, NewBranches} ->
+ NewLimitPosAcc = erlang:max(LimitPos, LimitPosAcc),
+ NewBranchAcc = NewBranches ++ BranchAcc,
+ {NewSeenAcc, NewLimitPosAcc, ChildAcc, NewBranchAcc}
+ end
+ end, {Seen1, -1, [], []}, Children),
+ {FinalSeen, FinalLimitPos, FinalChildren, FinalBranches} = FinalAcc,
+ case FinalLimitPos of
+ N when N > 0, length(FinalChildren) > 0 ->
+ FinalNode = {Key, Val, lists:reverse(FinalChildren)},
+ {FinalSeen, FinalLimitPos - 1, FinalNode, FinalBranches};
+ 0 when length(FinalChildren) > 0 ->
+ NewBranches = lists:map(fun(Child) ->
+ {Depth + 1, Child}
+ end, lists:reverse(FinalChildren)),
+ {FinalSeen, -1, NewBranches ++ FinalBranches};
+ N when N < 0, length(FinalChildren) == 0 ->
+ {FinalSeen, FinalLimitPos - 1, FinalBranches}
+ end.
+
+
+check_key(Key, Seen) ->
+ case sets:is_element(Key, Seen) of
+ true ->
+ throw(dupe_keys);
+ false ->
+ sets:add_element(Key, Seen)
+ end.
+
+
+repair_tree(Trees, Limit) ->
% flatten each branch in a tree into a tree path, sort by starting rev #
Paths = lists:sort(lists:map(fun({Pos, Path}) ->
StemmedPath = lists:sublist(Path, Limit),
diff --git a/src/couch/src/couch_native_process.erl b/src/couch/src/couch_native_process.erl
index 6d66c93..8f8ce8b 100644
--- a/src/couch/src/couch_native_process.erl
+++ b/src/couch/src/couch_native_process.erl
@@ -226,6 +226,18 @@
end,
Resp = lists:map(FilterFunWrapper, Docs),
{State, [true, Resp]};
+ddoc(State, {_, Fun}, [<<"views">>|_], [Docs]) ->
+ MapFunWrapper = fun(Doc) ->
+ case catch Fun(Doc) of
+ undefined -> true;
+ ok -> false;
+ false -> false;
+ [_|_] -> true;
+ {'EXIT', Error} -> couch_log:error("~p", [Error])
+ end
+ end,
+ Resp = lists:map(MapFunWrapper, Docs),
+ {State, [true, Resp]};
ddoc(State, {_, Fun}, [<<"shows">>|_], Args) ->
Resp = case (catch apply(Fun, Args)) of
FunResp when is_list(FunResp) ->
diff --git a/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index f31d24c..de8ef1e 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -17,6 +17,7 @@
-export([reduce/3, rereduce/3,validate_doc_update/5]).
-export([filter_docs/5]).
-export([filter_view/3]).
+-export([finalize/2]).
-export([rewrite/3]).
-export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3, json_doc/1]).
@@ -86,6 +87,17 @@
[Heads | group_reductions_results(Tails)]
end.
+finalize(<<"_approx_count_distinct",_/binary>>, Reduction) ->
+ true = hyper:is_hyper(Reduction),
+ {ok, round(hyper:card(Reduction))};
+finalize(<<"_stats",_/binary>>, {_, _, _, _, _} = Unpacked) ->
+ {ok, pack_stats(Unpacked)};
+finalize(<<"_stats",_/binary>>, {Packed}) ->
+ % Legacy code path before we had the finalize operation
+ {ok, {Packed}};
+finalize(_RedSrc, Reduction) ->
+ {ok, Reduction}.
+
rereduce(_Lang, [], _ReducedValues) ->
{ok, []};
rereduce(Lang, RedSrcs, ReducedValues) ->
@@ -171,7 +183,10 @@
builtin_reduce(rereduce, BuiltinReds, KVs, [Count|Acc]);
builtin_reduce(Re, [<<"_stats",_/binary>>|BuiltinReds], KVs, Acc) ->
Stats = builtin_stats(Re, KVs),
- builtin_reduce(Re, BuiltinReds, KVs, [Stats|Acc]).
+ builtin_reduce(Re, BuiltinReds, KVs, [Stats|Acc]);
+builtin_reduce(Re, [<<"_approx_count_distinct",_/binary>>|BuiltinReds], KVs, Acc) ->
+ Distinct = approx_count_distinct(Re, KVs),
+ builtin_reduce(Re, BuiltinReds, KVs, [Distinct|Acc]).
builtin_sum_rows([], Acc) ->
@@ -236,11 +251,11 @@
throw_sum_error(Else).
builtin_stats(_, []) ->
- {[{sum,0}, {count,0}, {min,0}, {max,0}, {sumsqr,0}]};
+ {0, 0, 0, 0, 0};
builtin_stats(_, [[_,First]|Rest]) ->
- Unpacked = lists:foldl(fun([_Key, Value], Acc) -> stat_values(Value, Acc) end,
- build_initial_accumulator(First), Rest),
- pack_stats(Unpacked).
+ lists:foldl(fun([_Key, Value], Acc) ->
+ stat_values(Value, Acc)
+ end, build_initial_accumulator(First), Rest).
stat_values(Value, Acc) when is_list(Value), is_list(Acc) ->
lists:zipwith(fun stat_values/2, Value, Acc);
@@ -267,6 +282,8 @@
[build_initial_accumulator(X) || X <- L];
build_initial_accumulator(X) when is_number(X) ->
{X, 1, X, X, X*X};
+build_initial_accumulator({_, _, _, _, _} = AlreadyUnpacked) ->
+ AlreadyUnpacked;
build_initial_accumulator({Props}) ->
unpack_stats({Props});
build_initial_accumulator(Else) ->
@@ -303,6 +320,13 @@
throw({invalid_value, iolist_to_binary(Msg)})
end.
+% TODO allow customization of precision in the ddoc.
+approx_count_distinct(reduce, KVs) ->
+ lists:foldl(fun([[Key, _Id], _Value], Filter) ->
+ hyper:insert(term_to_binary(Key), Filter)
+ end, hyper:new(11), KVs);
+approx_count_distinct(rereduce, Reds) ->
+ hyper:union([Filter || [_, Filter] <- Reds]).
% use the function stored in ddoc.validate_doc_update to test an update.
-spec validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) -> ok when
diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 05af0ed..002f08e 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -210,6 +210,8 @@
init([]) ->
+ couch_util:set_mqd_off_heap(),
+
% Mark pluggable storage engines as a supported feature
config:enable_feature('pluggable-storage-engines'),
@@ -523,7 +525,7 @@
DelOpt = [{context, delete} | Options],
% Make sure and remove all compaction data
- delete_compaction_files(DbNameList, DelOpt),
+ delete_compaction_files(DbNameList, Options),
{ok, {Engine, FilePath}} = get_engine(Server, DbNameList),
RootDir = Server#server.root_dir,
diff --git a/src/couch/src/couch_util.erl b/src/couch/src/couch_util.erl
index f3a9249..936b562 100644
--- a/src/couch/src/couch_util.erl
+++ b/src/couch/src/couch_util.erl
@@ -37,6 +37,7 @@
-export([unique_monotonic_integer/0]).
-export([check_config_blacklist/1]).
-export([check_md5/2]).
+-export([set_mqd_off_heap/0]).
-include_lib("couch/include/couch_db.hrl").
@@ -301,15 +302,45 @@
separate_cmd_args([Char|Rest], CmdAcc) ->
separate_cmd_args(Rest, [Char | CmdAcc]).
-% Is a character whitespace?
-is_whitespace($\s) -> true;
-is_whitespace($\t) -> true;
-is_whitespace($\n) -> true;
-is_whitespace($\r) -> true;
+% Is a character whitespace (from https://en.wikipedia.org/wiki/Whitespace_character#Unicode)?
+is_whitespace(9) -> true;
+is_whitespace(10) -> true;
+is_whitespace(11) -> true;
+is_whitespace(12) -> true;
+is_whitespace(13) -> true;
+is_whitespace(32) -> true;
+is_whitespace(133) -> true;
+is_whitespace(160) -> true;
+is_whitespace(5760) -> true;
+is_whitespace(8192) -> true;
+is_whitespace(8193) -> true;
+is_whitespace(8194) -> true;
+is_whitespace(8195) -> true;
+is_whitespace(8196) -> true;
+is_whitespace(8197) -> true;
+is_whitespace(8198) -> true;
+is_whitespace(8199) -> true;
+is_whitespace(8200) -> true;
+is_whitespace(8201) -> true;
+is_whitespace(8202) -> true;
+is_whitespace(8232) -> true;
+is_whitespace(8233) -> true;
+is_whitespace(8239) -> true;
+is_whitespace(8287) -> true;
+is_whitespace(12288) -> true;
+is_whitespace(6158) -> true;
+is_whitespace(8203) -> true;
+is_whitespace(8204) -> true;
+is_whitespace(8205) -> true;
+is_whitespace(8288) -> true;
+is_whitespace(65279) -> true;
is_whitespace(_Else) -> false.
% removes leading and trailing whitespace from a string
+trim(String) when is_binary(String) ->
+ % mirror string:trim() behaviour of returning a binary when a binary is passed in
+ ?l2b(trim(?b2l(String)));
trim(String) ->
String2 = lists:dropwhile(fun is_whitespace/1, String),
lists:reverse(lists:dropwhile(fun is_whitespace/1, lists:reverse(String2))).
@@ -639,6 +670,15 @@
check_md5(_, _) -> throw(md5_mismatch).
+set_mqd_off_heap() ->
+ try
+ erlang:process_flag(message_queue_data, off_heap),
+ ok
+ catch error:badarg ->
+ ok
+ end.
+
+
ensure_loaded(Module) when is_atom(Module) ->
case code:ensure_loaded(Module) of
{module, Module} ->
diff --git a/src/couch/src/test_engine_util.erl b/src/couch/src/test_engine_util.erl
index 8999753..fef9e9f 100644
--- a/src/couch/src/test_engine_util.erl
+++ b/src/couch/src/test_engine_util.erl
@@ -309,7 +309,8 @@
conflict -> new_branch;
_ -> new_leaf
end,
- {NewTree, NodeType} = couch_key_tree:merge(PrevRevTree, Path, RevsLimit),
+ {MergedTree, NodeType} = couch_key_tree:merge(PrevRevTree, Path),
+ NewTree = couch_key_tree:stem(MergedTree, RevsLimit),
NewFDI = PrevFDI#full_doc_info{
deleted = couch_doc:is_deleted(NewTree),
diff --git a/src/couch/src/test_util.erl b/src/couch/src/test_util.erl
index e0a53a6..738e9a3 100644
--- a/src/couch/src/test_util.erl
+++ b/src/couch/src/test_util.erl
@@ -15,6 +15,7 @@
-include_lib("couch/include/couch_eunit.hrl").
-include("couch_db.hrl").
-include("couch_db_int.hrl").
+-include("couch_bt_engine.hrl").
-export([init_code_path/0]).
-export([source_file/1, build_file/1]).
@@ -234,7 +235,8 @@
meck:unload(Mocked),
stop_applications(Apps).
-fake_db(Fields) ->
+fake_db(Fields0) ->
+ {ok, Db, Fields} = maybe_set_engine(Fields0),
Indexes = lists:zip(
record_info(fields, db),
lists:seq(2, record_info(size, db))
@@ -242,7 +244,27 @@
lists:foldl(fun({FieldName, Value}, Acc) ->
Idx = couch_util:get_value(FieldName, Indexes),
setelement(Idx, Acc, Value)
- end, #db{}, Fields).
+ end, Db, Fields).
+
+maybe_set_engine(Fields0) ->
+ case lists:member(engine, Fields0) of
+ true ->
+ {ok, #db{}, Fields0};
+ false ->
+ {ok, Header, Fields} = get_engine_header(Fields0),
+ Db = #db{engine = {couch_bt_engine, #st{header = Header}}},
+ {ok, Db, Fields}
+ end.
+
+get_engine_header(Fields) ->
+ Keys = [disk_version, update_seq, unused, id_tree_state,
+ seq_tree_state, local_tree_state, purge_seq, purged_docs,
+ security_ptr, revs_limit, uuid, epochs, compacted_seq],
+ {HeadFields, RestFields} = lists:partition(
+ fun({K, _}) -> lists:member(K, Keys) end, Fields),
+ Header0 = couch_bt_engine_header:new(),
+ Header = couch_bt_engine_header:set(Header0, HeadFields),
+ {ok, Header, RestFields}.
now_us() ->
{MegaSecs, Secs, MicroSecs} = os:timestamp(),
diff --git a/src/couch/test/couch_changes_tests.erl b/src/couch/test/couch_changes_tests.erl
index 673f2fa..e4ea761 100644
--- a/src/couch/test/couch_changes_tests.erl
+++ b/src/couch/test/couch_changes_tests.erl
@@ -47,9 +47,11 @@
save_doc(Db1, {[{<<"_id">>, <<"doc7">>}]}),
save_doc(Db1, {[{<<"_id">>, <<"doc8">>}]})
]],
+ config:set("native_query_servers", "erlang", "{couch_native_process, start_link, []}", _Persist=false),
{DbName, list_to_tuple(Revs2)}.
teardown({DbName, _}) ->
+ config:delete("native_query_servers", "erlang", _Persist=false),
delete_db(DbName),
ok.
@@ -153,7 +155,8 @@
fun setup/0, fun teardown/1,
[
fun should_filter_by_view/1,
- fun should_filter_by_fast_view/1
+ fun should_filter_by_fast_view/1,
+ fun should_filter_by_erlang_view/1
]
}
}.
@@ -733,6 +736,39 @@
?assertEqual(UpSeq, ViewUpSeq)
end).
+should_filter_by_erlang_view({DbName, _}) ->
+ ?_test(
+ begin
+ DDocId = <<"_design/app">>,
+ DDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, DDocId},
+ {<<"language">>, <<"erlang">>},
+ {<<"views">>, {[
+ {<<"valid">>, {[
+ {<<"map">>, <<"fun({Doc}) ->"
+ " case lists:keyfind(<<\"_id\">>, 1, Doc) of"
+ " {<<\"_id\">>, <<\"doc3\">>} -> Emit(Doc, null); "
+ " false -> ok"
+ " end "
+ "end.">>}
+ ]}}
+ ]}}
+ ]}),
+ ChArgs = #changes_args{filter = "_view"},
+ Req = {json_req, {[{
+ <<"query">>, {[
+ {<<"view">>, <<"app/valid">>}
+ ]}
+ }]}},
+ ok = update_ddoc(DbName, DDoc),
+ {Rows, LastSeq, UpSeq} = run_changes_query(DbName, ChArgs, Req),
+ ?assertEqual(1, length(Rows)),
+ [#row{seq = Seq, id = Id}] = Rows,
+ ?assertEqual(<<"doc3">>, Id),
+ ?assertEqual(6, Seq),
+ ?assertEqual(UpSeq, LastSeq)
+ end).
+
update_ddoc(DbName, DDoc) ->
{ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]),
{ok, _} = couch_db:update_doc(Db, DDoc, []),
diff --git a/src/couch/test/couch_key_tree_tests.erl b/src/couch/test/couch_key_tree_tests.erl
index 88d9203..5d9cc83 100644
--- a/src/couch/test/couch_key_tree_tests.erl
+++ b/src/couch/test/couch_key_tree_tests.erl
@@ -16,138 +16,108 @@
-define(DEPTH, 10).
-setup() ->
- meck:new(config),
- meck:expect(config, get, fun(_, _, Default) -> Default end).
-
-teardown(_) ->
- meck:unload(config).
key_tree_merge_test_()->
{
"Key tree merge",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_merge_with_empty_tree(),
- should_merge_reflexive(),
- should_merge_prefix_of_a_tree_with_tree(),
- should_produce_conflict_on_merge_with_unrelated_branch(),
- should_merge_reflexive_for_child_nodes(),
- should_merge_tree_to_itself(),
- should_merge_tree_of_odd_length(),
- should_merge_tree_with_stem(),
- should_merge_with_stem_at_deeper_level(),
- should_merge_with_stem_at_deeper_level_with_deeper_paths(),
- should_merge_single_tree_with_deeper_stem(),
- should_merge_tree_with_large_stem(),
- should_merge_stems(),
- should_create_conflicts_on_merge(),
- should_create_no_conflicts_on_merge(),
- should_ignore_conflicting_branch()
- ]
- }
+ [
+ should_merge_with_empty_tree(),
+ should_merge_reflexive(),
+ should_merge_prefix_of_a_tree_with_tree(),
+ should_produce_conflict_on_merge_with_unrelated_branch(),
+ should_merge_reflexive_for_child_nodes(),
+ should_merge_tree_to_itself(),
+ should_merge_tree_of_odd_length(),
+ should_merge_tree_with_stem(),
+ should_merge_with_stem_at_deeper_level(),
+ should_merge_with_stem_at_deeper_level_with_deeper_paths(),
+ should_merge_single_tree_with_deeper_stem(),
+ should_merge_tree_with_large_stem(),
+ should_merge_stems(),
+ should_create_conflicts_on_merge(),
+ should_create_no_conflicts_on_merge(),
+ should_ignore_conflicting_branch()
+ ]
}.
key_tree_missing_leaves_test_()->
{
- "Missing tree leaves",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_not_find_missing_leaves(),
- should_find_missing_leaves()
- ]
- }
+ "Missing tree leaves",
+ [
+ should_not_find_missing_leaves(),
+ should_find_missing_leaves()
+ ]
}.
key_tree_remove_leaves_test_()->
{
"Remove tree leaves",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_have_no_effect_on_removing_no_leaves(),
- should_have_no_effect_on_removing_non_existant_branch(),
- should_remove_leaf(),
- should_produce_empty_tree_on_removing_all_leaves(),
- should_have_no_effect_on_removing_non_existant_node(),
- should_produce_empty_tree_on_removing_last_leaf()
- ]
- }
+ [
+ should_have_no_effect_on_removing_no_leaves(),
+ should_have_no_effect_on_removing_non_existant_branch(),
+ should_remove_leaf(),
+ should_produce_empty_tree_on_removing_all_leaves(),
+ should_have_no_effect_on_removing_non_existant_node(),
+ should_produce_empty_tree_on_removing_last_leaf()
+ ]
}.
key_tree_get_leaves_test_()->
{
"Leaves retrieving",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_extract_subtree(),
- should_extract_subsubtree(),
- should_gather_non_existant_leaf(),
- should_gather_leaf(),
- shoul_gather_multiple_leaves(),
- should_gather_single_leaf_for_multiple_revs(),
- should_gather_multiple_for_multiple_revs(),
- should_retrieve_full_key_path(),
- should_retrieve_full_key_path_for_node(),
- should_retrieve_leaves_with_parent_node(),
- should_retrieve_all_leaves()
- ]
- }
+ [
+ should_extract_subtree(),
+ should_extract_subsubtree(),
+ should_gather_non_existant_leaf(),
+ should_gather_leaf(),
+ shoul_gather_multiple_leaves(),
+ should_gather_single_leaf_for_multiple_revs(),
+ should_gather_multiple_for_multiple_revs(),
+ should_retrieve_full_key_path(),
+ should_retrieve_full_key_path_for_node(),
+ should_retrieve_leaves_with_parent_node(),
+ should_retrieve_all_leaves()
+ ]
}.
key_tree_leaf_counting_test_()->
{
"Leaf counting",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_have_no_leaves_for_empty_tree(),
- should_have_single_leaf_for_tree_with_single_node(),
- should_have_two_leaves_for_tree_with_chindler_siblings(),
- should_not_affect_on_leaf_counting_for_stemmed_tree()
- ]
- }
+ [
+ should_have_no_leaves_for_empty_tree(),
+ should_have_single_leaf_for_tree_with_single_node(),
+ should_have_two_leaves_for_tree_with_chindler_siblings(),
+ should_not_affect_on_leaf_counting_for_stemmed_tree()
+ ]
}.
key_tree_stemming_test_()->
{
"Stemming",
- {
- setup,
- fun setup/0, fun teardown/1,
- [
- should_have_no_effect_for_stemming_more_levels_than_exists(),
- should_return_one_deepest_node(),
- should_return_two_deepest_nodes()
- ]
- }
+ [
+ should_have_no_effect_for_stemming_more_levels_than_exists(),
+ should_return_one_deepest_node(),
+ should_return_two_deepest_nodes()
+ ]
}.
should_merge_with_empty_tree()->
One = {1, {"1","foo",[]}},
?_assertEqual({[One], new_leaf},
- couch_key_tree:merge([], One, ?DEPTH)).
+ merge_and_stem([], One)).
should_merge_reflexive()->
One = {1, {"1","foo",[]}},
?_assertEqual({[One], internal_node},
- couch_key_tree:merge([One], One, ?DEPTH)).
+ merge_and_stem([One], One)).
should_merge_prefix_of_a_tree_with_tree()->
One = {1, {"1","foo",[]}},
TwoSibs = [{1, {"1","foo",[]}},
{1, {"2","foo",[]}}],
?_assertEqual({TwoSibs, internal_node},
- couch_key_tree:merge(TwoSibs, One, ?DEPTH)).
+ merge_and_stem(TwoSibs, One)).
should_produce_conflict_on_merge_with_unrelated_branch()->
TwoSibs = [{1, {"1","foo",[]}},
@@ -157,18 +127,33 @@
{1, {"2","foo",[]}},
{1, {"3","foo",[]}}],
?_assertEqual({ThreeSibs, new_branch},
- couch_key_tree:merge(TwoSibs, Three, ?DEPTH)).
+ merge_and_stem(TwoSibs, Three)).
should_merge_reflexive_for_child_nodes()->
TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
?_assertEqual({[TwoChild], internal_node},
- couch_key_tree:merge([TwoChild], TwoChild, ?DEPTH)).
+ merge_and_stem([TwoChild], TwoChild)).
should_merge_tree_to_itself()->
TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", []}]}},
- ?_assertEqual({[TwoChildSibs], new_branch},
- couch_key_tree:merge([TwoChildSibs], TwoChildSibs, ?DEPTH)).
+ Leafs = couch_key_tree:get_all_leafs([TwoChildSibs]),
+ Paths = lists:map(fun leaf_to_path/1, Leafs),
+ FinalTree = lists:foldl(fun(Path, TreeAcc) ->
+ {NewTree, internal_node} = merge_and_stem(TreeAcc, Path),
+ NewTree
+ end, [TwoChildSibs], Paths),
+ ?_assertEqual([TwoChildSibs], FinalTree).
+
+leaf_to_path({Value, {Start, Keys}}) ->
+ [Branch] = to_branch(Value, lists:reverse(Keys)),
+ {Start - length(Keys) + 1, Branch}.
+
+to_branch(Value, [Key]) ->
+ [{Key, Value, []}];
+to_branch(Value, [Key | RestKeys]) ->
+ [{Key, [], to_branch(Value, RestKeys)}].
+
should_merge_tree_of_odd_length()->
TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
@@ -176,9 +161,8 @@
{"1b", "bar", []}]}},
TwoChildPlusSibs = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]},
{"1b", "bar", []}]}},
-
- ?_assertEqual({[TwoChildPlusSibs], new_branch},
- couch_key_tree:merge([TwoChild], TwoChildSibs, ?DEPTH)).
+ ?_assertEqual({[TwoChildPlusSibs], new_leaf},
+ merge_and_stem([TwoChildSibs], TwoChild)).
should_merge_tree_with_stem()->
Stemmed = {2, {"1a", "bar", []}},
@@ -186,52 +170,52 @@
{"1b", "bar", []}]}},
?_assertEqual({[TwoChildSibs], internal_node},
- couch_key_tree:merge([TwoChildSibs], Stemmed, ?DEPTH)).
+ merge_and_stem([TwoChildSibs], Stemmed)).
should_merge_with_stem_at_deeper_level()->
Stemmed = {3, {"1bb", "boo", []}},
TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", [{"1bb", "boo", []}]}]}},
?_assertEqual({[TwoChildSibs], internal_node},
- couch_key_tree:merge([TwoChildSibs], Stemmed, ?DEPTH)).
+ merge_and_stem([TwoChildSibs], Stemmed)).
should_merge_with_stem_at_deeper_level_with_deeper_paths()->
Stemmed = {3, {"1bb", "boo", []}},
StemmedTwoChildSibs = [{2,{"1a", "bar", []}},
{2,{"1b", "bar", [{"1bb", "boo", []}]}}],
?_assertEqual({StemmedTwoChildSibs, internal_node},
- couch_key_tree:merge(StemmedTwoChildSibs, Stemmed, ?DEPTH)).
+ merge_and_stem(StemmedTwoChildSibs, Stemmed)).
should_merge_single_tree_with_deeper_stem()->
Stemmed = {3, {"1aa", "bar", []}},
TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
?_assertEqual({[TwoChild], internal_node},
- couch_key_tree:merge([TwoChild], Stemmed, ?DEPTH)).
+ merge_and_stem([TwoChild], Stemmed)).
should_merge_tree_with_large_stem()->
Stemmed = {2, {"1a", "bar", [{"1aa", "bar", []}]}},
TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
?_assertEqual({[TwoChild], internal_node},
- couch_key_tree:merge([TwoChild], Stemmed, ?DEPTH)).
+ merge_and_stem([TwoChild], Stemmed)).
should_merge_stems()->
StemmedA = {2, {"1a", "bar", [{"1aa", "bar", []}]}},
StemmedB = {3, {"1aa", "bar", []}},
?_assertEqual({[StemmedA], internal_node},
- couch_key_tree:merge([StemmedA], StemmedB, ?DEPTH)).
+ merge_and_stem([StemmedA], StemmedB)).
should_create_conflicts_on_merge()->
OneChild = {1, {"1","foo",[{"1a", "bar", []}]}},
Stemmed = {3, {"1aa", "bar", []}},
?_assertEqual({[OneChild, Stemmed], new_branch},
- couch_key_tree:merge([OneChild], Stemmed, ?DEPTH)).
+ merge_and_stem([OneChild], Stemmed)).
should_create_no_conflicts_on_merge()->
OneChild = {1, {"1","foo",[{"1a", "bar", []}]}},
Stemmed = {3, {"1aa", "bar", []}},
TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
?_assertEqual({[TwoChild], new_leaf},
- couch_key_tree:merge([OneChild, Stemmed], TwoChild, ?DEPTH)).
+ merge_and_stem([OneChild, Stemmed], TwoChild)).
should_ignore_conflicting_branch()->
%% this test is based on couch-902-test-case2.py
@@ -260,7 +244,7 @@
{
"COUCHDB-902",
?_assertEqual({[FooBar], new_leaf},
- couch_key_tree:merge([Foo], Bar, ?DEPTH))
+ merge_and_stem([Foo], Bar))
}.
should_not_find_missing_leaves()->
@@ -422,3 +406,8 @@
TwoChild = [{0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}}],
Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
?_assertEqual(Stemmed, couch_key_tree:stem(TwoChild, 2)).
+
+
+merge_and_stem(RevTree, Tree) ->
+ {Merged, Result} = couch_key_tree:merge(RevTree, Tree),
+ {couch_key_tree:stem(Merged, ?DEPTH), Result}.
diff --git a/src/couch/test/couchdb_attachments_tests.erl b/src/couch/test/couchdb_attachments_tests.erl
index d9efac5..a85a01f 100644
--- a/src/couch/test/couchdb_attachments_tests.erl
+++ b/src/couch/test/couchdb_attachments_tests.erl
@@ -21,9 +21,9 @@
-define(ATT_TXT_NAME, <<"file.erl">>).
-define(FIXTURE_PNG, filename:join([?FIXTURESDIR, "logo.png"])).
-define(FIXTURE_TXT, ?ABS_PATH(?FILE)).
--define(TIMEOUT, 1000).
--define(TIMEOUT_EUNIT, 10).
--define(TIMEWAIT, 100).
+-define(TIMEOUT, 5000).
+-define(TIMEOUT_EUNIT, 100).
+-define(TIMEWAIT, 1000).
-define(i2l(I), integer_to_list(I)).
diff --git a/src/couch_log/src/couch_log_server.erl b/src/couch_log/src/couch_log_server.erl
index be44af8..ea5def8 100644
--- a/src/couch_log/src/couch_log_server.erl
+++ b/src/couch_log/src/couch_log_server.erl
@@ -58,6 +58,7 @@
init(_) ->
+ couch_util:set_mqd_off_heap(),
process_flag(trap_exit, true),
{ok, #st{
writer = couch_log_writer:init()
diff --git a/src/couch_mrview/src/couch_mrview.erl b/src/couch_mrview/src/couch_mrview.erl
index a099f37..82bbd79 100644
--- a/src/couch_mrview/src/couch_mrview.erl
+++ b/src/couch_mrview/src/couch_mrview.erl
@@ -41,6 +41,7 @@
user_acc,
last_go=ok,
reduce_fun,
+ finalizer,
update_seq,
args
}).
@@ -184,6 +185,8 @@
ok;
({_RedName, <<"_stats", _/binary>>}) ->
ok;
+ ({_RedName, <<"_approx_count_distinct", _/binary>>}) ->
+ ok;
({_RedName, <<"_", _/binary>> = Bad}) ->
Msg = ["`", Bad, "` is not a supported reduce function."],
throw({invalid_design_doc, Msg});
@@ -577,7 +580,14 @@
last_go=Go
}}.
-red_fold(Db, {_Nth, _Lang, View}=RedView, Args, Callback, UAcc) ->
+red_fold(Db, {NthRed, _Lang, View}=RedView, Args, Callback, UAcc) ->
+ Finalizer = case couch_util:get_value(finalizer, Args#mrargs.extra) of
+ undefined ->
+ {_, FunSrc} = lists:nth(NthRed, View#mrview.reduce_funs),
+ FunSrc;
+ CustomFun->
+ CustomFun
+ end,
Acc = #mracc{
db=Db,
total_rows=null,
@@ -587,6 +597,7 @@
callback=Callback,
user_acc=UAcc,
update_seq=View#mrview.update_seq,
+ finalizer=Finalizer,
args=Args
},
Grouping = {key_group_level, Args#mrargs.group_level},
@@ -618,41 +629,50 @@
{stop, Acc};
red_fold(_Key, Red, #mracc{group_level=0} = Acc) ->
#mracc{
+ finalizer=Finalizer,
limit=Limit,
callback=Callback,
user_acc=UAcc0
} = Acc,
- Row = [{key, null}, {value, Red}],
+ Row = [{key, null}, {value, maybe_finalize(Red, Finalizer)}],
{Go, UAcc1} = Callback({row, Row}, UAcc0),
{Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}};
red_fold(Key, Red, #mracc{group_level=exact} = Acc) ->
#mracc{
+ finalizer=Finalizer,
limit=Limit,
callback=Callback,
user_acc=UAcc0
} = Acc,
- Row = [{key, Key}, {value, Red}],
+ Row = [{key, Key}, {value, maybe_finalize(Red, Finalizer)}],
{Go, UAcc1} = Callback({row, Row}, UAcc0),
{Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}};
red_fold(K, Red, #mracc{group_level=I} = Acc) when I > 0, is_list(K) ->
#mracc{
+ finalizer=Finalizer,
limit=Limit,
callback=Callback,
user_acc=UAcc0
} = Acc,
- Row = [{key, lists:sublist(K, I)}, {value, Red}],
+ Row = [{key, lists:sublist(K, I)}, {value, maybe_finalize(Red, Finalizer)}],
{Go, UAcc1} = Callback({row, Row}, UAcc0),
{Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}};
red_fold(K, Red, #mracc{group_level=I} = Acc) when I > 0 ->
#mracc{
+ finalizer=Finalizer,
limit=Limit,
callback=Callback,
user_acc=UAcc0
} = Acc,
- Row = [{key, K}, {value, Red}],
+ Row = [{key, K}, {value, maybe_finalize(Red, Finalizer)}],
{Go, UAcc1} = Callback({row, Row}, UAcc0),
{Go, Acc#mracc{user_acc=UAcc1, limit=Limit-1, last_go=Go}}.
+maybe_finalize(Red, null) ->
+ Red;
+maybe_finalize(Red, RedSrc) ->
+ {ok, Finalized} = couch_query_servers:finalize(RedSrc, Red),
+ Finalized.
finish_fold(#mracc{last_go=ok, update_seq=UpdateSeq}=Acc, ExtraMeta) ->
#mracc{callback=Callback, user_acc=UAcc, args=Args}=Acc,
diff --git a/src/couch_mrview/src/couch_mrview_compactor.erl b/src/couch_mrview/src/couch_mrview_compactor.erl
index e9be89c..3ef1180 100644
--- a/src/couch_mrview/src/couch_mrview_compactor.erl
+++ b/src/couch_mrview/src/couch_mrview_compactor.erl
@@ -233,6 +233,8 @@
{EmptyView#mrview{btree=NewBt,
seq_btree=NewSeqBt,
+ update_seq=View#mrview.update_seq,
+ purge_seq=View#mrview.purge_seq,
key_byseq_btree=NewKeyBySeqBt}, FinalAcc}.
compact_view_btree(Btree, EmptyBtree, VID, BufferSize, Acc0) ->
diff --git a/src/couch_mrview/src/couch_mrview_index.erl b/src/couch_mrview/src/couch_mrview_index.erl
index aa1ee27..5d285d6 100644
--- a/src/couch_mrview/src/couch_mrview_index.erl
+++ b/src/couch_mrview/src/couch_mrview_index.erl
@@ -61,13 +61,14 @@
} = State,
{ok, FileSize} = couch_file:bytes(Fd),
{ok, ExternalSize} = couch_mrview_util:calculate_external_size(Views),
+ {ok, ActiveViewSize} = couch_mrview_util:calculate_active_size(Views),
LogBtSize = case LogBtree of
nil ->
0;
_ ->
couch_btree:size(LogBtree)
end,
- ActiveSize = couch_btree:size(IdBtree) + LogBtSize + ExternalSize,
+ ActiveSize = couch_btree:size(IdBtree) + LogBtSize + ActiveViewSize,
UpdateOptions0 = get(update_options, State),
UpdateOptions = [atom_to_binary(O, latin1) || O <- UpdateOptions0],
diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl
index 0c6e5fc..eb461d0 100644
--- a/src/couch_mrview/src/couch_mrview_util.erl
+++ b/src/couch_mrview/src/couch_mrview_util.erl
@@ -23,6 +23,7 @@
-export([fold/4, fold_reduce/4]).
-export([temp_view_to_ddoc/1]).
-export([calculate_external_size/1]).
+-export([calculate_active_size/1]).
-export([validate_args/1]).
-export([maybe_load_doc/3, maybe_load_doc/4]).
-export([maybe_update_index_file/1]).
@@ -830,6 +831,22 @@
{ok, lists:foldl(SumFun, 0, Views)}.
+calculate_active_size(Views) ->
+ BtSize = fun
+ (nil) -> 0;
+ (Bt) -> couch_btree:size(Bt)
+ end,
+ FoldFun = fun(View, Acc) ->
+ Sizes = [
+ BtSize(View#mrview.btree),
+ BtSize(View#mrview.seq_btree),
+ BtSize(View#mrview.key_byseq_btree)
+ ],
+ Acc + lists:sum([S || S <- Sizes, is_integer(S)])
+ end,
+ {ok, lists:foldl(FoldFun, 0, Views)}.
+
+
sum_btree_sizes(nil, _) ->
0;
sum_btree_sizes(_, nil) ->
diff --git a/src/couch_mrview/test/couch_mrview_index_info_tests.erl b/src/couch_mrview/test/couch_mrview_index_info_tests.erl
index c994df9..efa03e7 100644
--- a/src/couch_mrview/test/couch_mrview_index_info_tests.erl
+++ b/src/couch_mrview/test/couch_mrview_index_info_tests.erl
@@ -18,14 +18,13 @@
-define(TIMEOUT, 1000).
--ifdef(run_broken_tests).
-
setup() ->
{ok, Db} = couch_mrview_test_util:init_db(?tempdb(), map),
couch_mrview:query_view(Db, <<"_design/bar">>, <<"baz">>),
{ok, Info} = couch_mrview:get_info(Db, <<"_design/bar">>),
{Db, Info}.
+
teardown({Db, _}) ->
couch_db:close(Db),
couch_server:delete(couch_db:name(Db), [?ADMIN_CTX]),
@@ -37,39 +36,86 @@
"Views index tests",
{
setup,
- fun test_util:start_couch/0, fun test_util:stop_couch/1,
+ fun test_util:start_couch/0,
+ fun test_util:stop_couch/1,
{
foreach,
- fun setup/0, fun teardown/1,
+ fun setup/0,
+ fun teardown/1,
[
- fun should_get_property/1
+ fun sig_is_binary/1,
+ fun language_is_js/1,
+ fun file_size_is_non_neg_int/1,
+ fun active_size_is_non_neg_int/1,
+ fun external_size_is_non_neg_int/1,
+ fun disk_size_is_file_size/1,
+ fun data_size_is_external_size/1,
+ fun active_size_less_than_file_size/1,
+ fun update_seq_is_non_neg_int/1,
+ fun purge_seq_is_non_neg_int/1,
+ fun update_opts_is_bin_list/1
]
}
}
}.
-should_get_property({_, Info}) ->
- InfoProps = [
- {signature, <<"276df562b152b3c4e5d34024f62672ed">>},
- {language, <<"javascript">>},
- {disk_size, 314},
- {data_size, 263},
- {update_seq, 11},
- {purge_seq, 0},
- {updater_running, false},
- {compact_running, false},
- {waiting_clients, 0}
- ],
- [
- {atom_to_list(Key), ?_assertEqual(Val, getval(Key, Info))}
- || {Key, Val} <- InfoProps
- ].
+sig_is_binary({_, Info}) ->
+ ?_assert(is_binary(prop(signature, Info))).
-getval(Key, PL) ->
- {value, {Key, Val}} = lists:keysearch(Key, 1, PL),
- Val.
+language_is_js({_, Info}) ->
+ ?_assertEqual(<<"javascript">>, prop(language, Info)).
--endif.
+file_size_is_non_neg_int({_, Info}) ->
+ ?_assert(check_non_neg_int([sizes, file], Info)).
+
+
+active_size_is_non_neg_int({_, Info}) ->
+ ?_assert(check_non_neg_int([sizes, active], Info)).
+
+
+external_size_is_non_neg_int({_, Info}) ->
+ ?_assert(check_non_neg_int([sizes, external], Info)).
+
+
+disk_size_is_file_size({_, Info}) ->
+ ?_assertEqual(prop([sizes, file], Info), prop(disk_size, Info)).
+
+
+data_size_is_external_size({_, Info}) ->
+ ?_assertEqual(prop([sizes, external], Info), prop(data_size, Info)).
+
+
+active_size_less_than_file_size({_, Info}) ->
+ ?_assert(prop([sizes, active], Info) < prop([sizes, file], Info)).
+
+
+update_seq_is_non_neg_int({_, Info}) ->
+ ?_assert(check_non_neg_int(update_seq, Info)).
+
+
+purge_seq_is_non_neg_int({_, Info}) ->
+ ?_assert(check_non_neg_int(purge_seq, Info)).
+
+
+update_opts_is_bin_list({_, Info}) ->
+ Opts = prop(update_options, Info),
+ ?_assert(is_list(Opts) andalso
+ (Opts == [] orelse lists:all([is_binary(B) || B <- Opts]))).
+
+
+check_non_neg_int(Key, Info) ->
+ Size = prop(Key, Info),
+ is_integer(Size) andalso Size >= 0.
+
+
+prop(Key, {Props}) when is_list(Props) ->
+ prop(Key, Props);
+prop([Key], Info) ->
+ prop(Key, Info);
+prop([Key | Rest], Info) ->
+ prop(Rest, prop(Key, Info));
+prop(Key, Info) when is_atom(Key), is_list(Info) ->
+ couch_util:get_value(Key, Info).
diff --git a/src/couch_replicator/src/couch_replicator_scheduler.erl b/src/couch_replicator/src/couch_replicator_scheduler.erl
index 0b39634..50896c5 100644
--- a/src/couch_replicator/src/couch_replicator_scheduler.erl
+++ b/src/couch_replicator/src/couch_replicator_scheduler.erl
@@ -138,11 +138,15 @@
ErrorCount = consecutive_crashes(History, HealthThreshold),
{State, Info} = case {Pid, ErrorCount} of
{undefined, 0} ->
- {pending, null};
+ case History of
+ [{{crashed, Error}, _When} | _] ->
+ {crashing, crash_reason_json(Error)};
+ [_ | _] ->
+ {pending, null}
+ end;
{undefined, ErrorCount} when ErrorCount > 0 ->
[{{crashed, Error}, _When} | _] = History,
- ErrMsg = couch_replicator_utils:rep_error_to_binary(Error),
- {crashing, ErrMsg};
+ {crashing, crash_reason_json(Error)};
{Pid, ErrorCount} when is_pid(Pid) ->
{running, null}
end,
@@ -1021,7 +1025,11 @@
t_oneshot_will_hog_the_scheduler(),
t_if_excess_is_trimmed_rotation_doesnt_happen(),
t_if_transient_job_crashes_it_gets_removed(),
- t_if_permanent_job_crashes_it_stays_in_ets()
+ t_if_permanent_job_crashes_it_stays_in_ets(),
+ t_job_summary_running(),
+ t_job_summary_pending(),
+ t_job_summary_crashing_once(),
+ t_job_summary_crashing_many_times()
]
}.
@@ -1300,6 +1308,72 @@
end).
+t_job_summary_running() ->
+ ?_test(begin
+ Job = #job{
+ id = job1,
+ pid = mock_pid(),
+ history = [added()],
+ rep = #rep{
+ db_name = <<"db1">>,
+ source = <<"s">>,
+ target = <<"t">>
+ }
+ },
+ setup_jobs([Job]),
+ Summary = job_summary(job1, ?DEFAULT_HEALTH_THRESHOLD_SEC),
+ ?assertEqual(running, proplists:get_value(state, Summary)),
+ ?assertEqual(null, proplists:get_value(info, Summary)),
+ ?assertEqual(0, proplists:get_value(error_count, Summary))
+ end).
+
+
+t_job_summary_pending() ->
+ ?_test(begin
+ Job = #job{
+ id = job1,
+ pid = undefined,
+ history = [stopped(20), started(10), added()],
+ rep = #rep{source = <<"s">>, target = <<"t">>}
+ },
+ setup_jobs([Job]),
+ Summary = job_summary(job1, ?DEFAULT_HEALTH_THRESHOLD_SEC),
+ ?assertEqual(pending, proplists:get_value(state, Summary)),
+ ?assertEqual(null, proplists:get_value(info, Summary)),
+ ?assertEqual(0, proplists:get_value(error_count, Summary))
+ end).
+
+
+t_job_summary_crashing_once() ->
+ ?_test(begin
+ Job = #job{
+ id = job1,
+ history = [crashed(?DEFAULT_HEALTH_THRESHOLD_SEC + 1), started(0)],
+ rep = #rep{source = <<"s">>, target = <<"t">>}
+ },
+ setup_jobs([Job]),
+ Summary = job_summary(job1, ?DEFAULT_HEALTH_THRESHOLD_SEC),
+ ?assertEqual(crashing, proplists:get_value(state, Summary)),
+ ?assertEqual(<<"some_reason">>, proplists:get_value(info, Summary)),
+ ?assertEqual(0, proplists:get_value(error_count, Summary))
+ end).
+
+
+t_job_summary_crashing_many_times() ->
+ ?_test(begin
+ Job = #job{
+ id = job1,
+ history = [crashed(4), started(3), crashed(2), started(1)],
+ rep = #rep{source = <<"s">>, target = <<"t">>}
+ },
+ setup_jobs([Job]),
+ Summary = job_summary(job1, ?DEFAULT_HEALTH_THRESHOLD_SEC),
+ ?assertEqual(crashing, proplists:get_value(state, Summary)),
+ ?assertEqual(<<"some_reason">>, proplists:get_value(info, Summary)),
+ ?assertEqual(2, proplists:get_value(error_count, Summary))
+ end).
+
+
% Test helper functions
setup() ->
diff --git a/src/ddoc_cache/src/ddoc_cache_lru.erl b/src/ddoc_cache/src/ddoc_cache_lru.erl
index e94934d..248a76d 100644
--- a/src/ddoc_cache/src/ddoc_cache_lru.erl
+++ b/src/ddoc_cache/src/ddoc_cache_lru.erl
@@ -87,6 +87,7 @@
init(_) ->
+ couch_util:set_mqd_off_heap(),
process_flag(trap_exit, true),
BaseOpts = [public, named_table],
CacheOpts = [
diff --git a/src/fabric/src/fabric_db_delete.erl b/src/fabric/src/fabric_db_delete.erl
index 9ba55fb..c146cb6 100644
--- a/src/fabric/src/fabric_db_delete.erl
+++ b/src/fabric/src/fabric_db_delete.erl
@@ -79,12 +79,12 @@
case {Ok + NotFound, Ok, NotFound} of
{W, 0, W} ->
{#shard{dbname=Name}, _} = hd(Counters),
- couch_log:warning("~p not_found ~s", [?MODULE, Name]),
+ couch_log:warning("~p not_found ~d", [?MODULE, Name]),
{stop, not_found};
{W, _, _} ->
{stop, ok};
- {N, M, _} when N >= (W div 2 + 1), M > 0 ->
- {stop, accepted};
+ {_, M, _} when M > 0 ->
+ {stop,accepted};
_ ->
{error, internal_server_error}
end
diff --git a/src/fabric/src/fabric_doc_atts.erl b/src/fabric/src/fabric_doc_atts.erl
new file mode 100644
index 0000000..7ef5dd8
--- /dev/null
+++ b/src/fabric/src/fabric_doc_atts.erl
@@ -0,0 +1,168 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(fabric_doc_atts).
+
+-include_lib("fabric/include/fabric.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+-export([
+ receiver/2,
+ receiver_callback/2
+]).
+
+
+receiver(_Req, undefined) ->
+ <<"">>;
+receiver(_Req, {unknown_transfer_encoding, Unknown}) ->
+ exit({unknown_transfer_encoding, Unknown});
+receiver(Req, chunked) ->
+ MiddleMan = spawn(fun() -> middleman(Req, chunked) end),
+ {fabric_attachment_receiver, MiddleMan, chunked};
+receiver(_Req, 0) ->
+ <<"">>;
+receiver(Req, Length) when is_integer(Length) ->
+ maybe_send_continue(Req),
+ Middleman = spawn(fun() -> middleman(Req, Length) end),
+ {fabric_attachment_receiver, Middleman, Length};
+receiver(_Req, Length) ->
+ exit({length_not_integer, Length}).
+
+
+receiver_callback(Middleman, chunked) ->
+ fun(4096, ChunkFun, State) ->
+ write_chunks(Middleman, ChunkFun, State)
+ end;
+receiver_callback(Middleman, Length) when is_integer(Length) ->
+ fun() ->
+ Middleman ! {self(), gimme_data},
+ Timeout = fabric_util:attachments_timeout(),
+ receive
+ {Middleman, Data} ->
+ rexi:reply(attachment_chunk_received),
+ Data
+ after Timeout ->
+ exit(timeout)
+ end
+ end.
+
+
+%%
+%% internal
+%%
+
+maybe_send_continue(#httpd{mochi_req = MochiReq} = Req) ->
+ case couch_httpd:header_value(Req, "expect") of
+ undefined ->
+ ok;
+ Expect ->
+ case string:to_lower(Expect) of
+ "100-continue" ->
+ MochiReq:start_raw_response({100, gb_trees:empty()});
+ _ ->
+ ok
+ end
+ end.
+
+write_chunks(MiddleMan, ChunkFun, State) ->
+ MiddleMan ! {self(), gimme_data},
+ Timeout = fabric_util:attachments_timeout(),
+ receive
+ {MiddleMan, ChunkRecordList} ->
+ rexi:reply(attachment_chunk_received),
+ case flush_chunks(ChunkRecordList, ChunkFun, State) of
+ {continue, NewState} ->
+ write_chunks(MiddleMan, ChunkFun, NewState);
+ {done, NewState} ->
+ NewState
+ end
+ after Timeout ->
+ exit(timeout)
+ end.
+
+flush_chunks([], _ChunkFun, State) ->
+ {continue, State};
+flush_chunks([{0, _}], _ChunkFun, State) ->
+ {done, State};
+flush_chunks([Chunk | Rest], ChunkFun, State) ->
+ NewState = ChunkFun(Chunk, State),
+ flush_chunks(Rest, ChunkFun, NewState).
+
+receive_unchunked_attachment(_Req, 0) ->
+ ok;
+receive_unchunked_attachment(Req, Length) ->
+ receive {MiddleMan, go} ->
+ Data = couch_httpd:recv(Req, 0),
+ MiddleMan ! {self(), Data}
+ end,
+ receive_unchunked_attachment(Req, Length - size(Data)).
+
+middleman(Req, chunked) ->
+ % spawn a process to actually receive the uploaded data
+ RcvFun = fun(ChunkRecord, ok) ->
+ receive {From, go} -> From ! {self(), ChunkRecord} end, ok
+ end,
+ Receiver = spawn(fun() -> couch_httpd:recv_chunked(Req,4096,RcvFun,ok) end),
+
+ % take requests from the DB writers and get data from the receiver
+ N = erlang:list_to_integer(config:get("cluster","n")),
+ Timeout = fabric_util:attachments_timeout(),
+ middleman_loop(Receiver, N, [], [], Timeout);
+
+middleman(Req, Length) ->
+ Receiver = spawn(fun() -> receive_unchunked_attachment(Req, Length) end),
+ N = erlang:list_to_integer(config:get("cluster","n")),
+ Timeout = fabric_util:attachments_timeout(),
+ middleman_loop(Receiver, N, [], [], Timeout).
+
+middleman_loop(Receiver, N, Counters0, ChunkList0, Timeout) ->
+ receive {From, gimme_data} ->
+ % Figure out how far along this writer (From) is in the list
+ ListIndex = case fabric_dict:lookup_element(From, Counters0) of
+ undefined -> 0;
+ I -> I
+ end,
+
+ % Talk to the receiver to get another chunk if necessary
+ ChunkList1 = if ListIndex == length(ChunkList0) ->
+ Receiver ! {self(), go},
+ receive
+ {Receiver, ChunkRecord} ->
+ ChunkList0 ++ [ChunkRecord]
+ end;
+ true -> ChunkList0 end,
+
+ % reply to the writer
+ Reply = lists:nthtail(ListIndex, ChunkList1),
+ From ! {self(), Reply},
+
+ % Update the counter for this writer
+ Counters1 = fabric_dict:update_counter(From, length(Reply), Counters0),
+
+ % Drop any chunks that have been sent to all writers
+ Size = fabric_dict:size(Counters1),
+ NumToDrop = lists:min([I || {_, I} <- Counters1]),
+
+ {ChunkList3, Counters3} =
+ if Size == N andalso NumToDrop > 0 ->
+ ChunkList2 = lists:nthtail(NumToDrop, ChunkList1),
+ Counters2 = [{F, I-NumToDrop} || {F, I} <- Counters1],
+ {ChunkList2, Counters2};
+ true ->
+ {ChunkList1, Counters1}
+ end,
+
+ middleman_loop(Receiver, N, Counters3, ChunkList3, Timeout)
+ after Timeout ->
+ exit(Receiver, kill),
+ ok
+ end.
diff --git a/src/fabric/src/fabric_rpc.erl b/src/fabric/src/fabric_rpc.erl
index 4a69e7e..60526f4 100644
--- a/src/fabric/src/fabric_rpc.erl
+++ b/src/fabric/src/fabric_rpc.erl
@@ -142,8 +142,9 @@
couch_mrview:query_view(Db, DDoc, ViewName, Args, fun reduce_cb/2, VAcc0).
fix_skip_and_limit(Args) ->
- #mrargs{skip=Skip, limit=Limit}=Args,
- Args#mrargs{skip=0, limit=Skip+Limit}.
+ #mrargs{skip=Skip, limit=Limit, extra=Extra}=Args,
+ % the coordinator needs to finalize each row, so make sure the shards don't
+ Args#mrargs{skip=0, limit=Skip+Limit, extra=[{finalizer,null} | Extra]}.
create_db(DbName) ->
create_db(DbName, []).
@@ -439,6 +440,8 @@
throw({mp_parser_died, Reason})
end
end;
+make_att_reader({fabric_attachment_receiver, Middleman, Length}) ->
+ fabric_doc_atts:receiver_callback(Middleman, Length);
make_att_reader(Else) ->
Else.
diff --git a/src/fabric/src/fabric_view.erl b/src/fabric/src/fabric_view.erl
index dd0fcfd..69f4290 100644
--- a/src/fabric/src/fabric_view.erl
+++ b/src/fabric/src/fabric_view.erl
@@ -230,8 +230,9 @@
end, Counters0, Records),
Wrapped = [[V] || #view_row{value=V} <- Records],
{ok, [Reduced]} = couch_query_servers:rereduce(Lang, [RedSrc], Wrapped),
+ {ok, Finalized} = couch_query_servers:finalize(RedSrc, Reduced),
NewSt = St#collector{keys=RestKeys, rows=NewRowDict, counters=Counters},
- {#view_row{key=Key, id=reduced, value=Reduced}, NewSt};
+ {#view_row{key=Key, id=reduced, value=Finalized}, NewSt};
error ->
get_next_row(St#collector{keys=RestKeys})
end;
diff --git a/src/mango/src/mango_cursor.erl b/src/mango/src/mango_cursor.erl
index 5108d36..5d2ea71 100644
--- a/src/mango/src/mango_cursor.erl
+++ b/src/mango/src/mango_cursor.erl
@@ -48,18 +48,12 @@
create(Db, Selector0, Opts) ->
Selector = mango_selector:normalize(Selector0),
UsableIndexes = mango_idx:get_usable_indexes(Db, Selector, Opts),
- case length(UsableIndexes) of
- 0 ->
- AllDocs = mango_idx:special(Db),
- create_cursor(Db, AllDocs, Selector, Opts);
- _ ->
- case mango_cursor:maybe_filter_indexes_by_ddoc(UsableIndexes, Opts) of
- [] ->
- % use_index doesn't match a valid index - fall back to a valid one
- create_cursor(Db, UsableIndexes, Selector, Opts);
- UserSpecifiedIndex ->
- create_cursor(Db, UserSpecifiedIndex, Selector, Opts)
- end
+ case mango_cursor:maybe_filter_indexes_by_ddoc(UsableIndexes, Opts) of
+ [] ->
+ % use_index doesn't match a valid index - fall back to a valid one
+ create_cursor(Db, UsableIndexes, Selector, Opts);
+ UserSpecifiedIndex ->
+ create_cursor(Db, UserSpecifiedIndex, Selector, Opts)
end.
diff --git a/src/mango/src/mango_cursor_view.erl b/src/mango/src/mango_cursor_view.erl
index 1e2108b..dbea36e 100644
--- a/src/mango/src/mango_cursor_view.erl
+++ b/src/mango/src/mango_cursor_view.erl
@@ -70,7 +70,8 @@
{end_key, maybe_replace_max_json(Args#mrargs.end_key)},
{direction, Args#mrargs.direction},
{stable, Args#mrargs.stable},
- {update, Args#mrargs.update}
+ {update, Args#mrargs.update},
+ {conflicts, Args#mrargs.conflicts}
]}}].
@@ -283,9 +284,8 @@
NewArgs = Args#mrargs{include_docs = IncludeDocs},
apply_opts(Rest, NewArgs);
apply_opts([{conflicts, true} | Rest], Args) ->
- % I need to patch things so that views can specify
- % parameters when loading the docs from disk
- apply_opts(Rest, Args);
+ NewArgs = Args#mrargs{conflicts = true},
+ apply_opts(Rest, NewArgs);
apply_opts([{conflicts, false} | Rest], Args) ->
% Ignored cause default
apply_opts(Rest, Args);
diff --git a/src/mango/src/mango_error.erl b/src/mango/src/mango_error.erl
index ad665e2..b2bbb39 100644
--- a/src/mango/src/mango_error.erl
+++ b/src/mango/src/mango_error.erl
@@ -308,7 +308,7 @@
{
400,
<<"invalid_sort_json">>,
- fmt("Sort must be an array of sort specs, not: ~w", [BadSort])
+ fmt("Sort must be an array of sort specs, not: ~p", [BadSort])
};
info(mango_sort, {invalid_sort_dir, BadSpec}) ->
{
@@ -320,7 +320,7 @@
{
400,
<<"invalid_sort_field">>,
- fmt("Invalid sort field: ~w", [BadField])
+ fmt("Invalid sort field: ~p", [BadField])
};
info(mango_sort, {unsupported, mixed_sort}) ->
{
diff --git a/src/mango/src/mango_idx.erl b/src/mango/src/mango_idx.erl
index ea5949c..8af92b9 100644
--- a/src/mango/src/mango_idx.erl
+++ b/src/mango/src/mango_idx.erl
@@ -66,13 +66,12 @@
SortFields = get_sort_fields(Opts),
UsableFilter = fun(I) -> is_usable(I, Selector, SortFields) end,
- UsableIndexes1 = lists:filter(UsableFilter, UsableIndexes0),
- case maybe_filter_by_sort_fields(UsableIndexes1, SortFields) of
- {ok, SortIndexes} ->
- SortIndexes;
- {error, no_usable_index} ->
- ?MANGO_ERROR({no_usable_index, missing_sort_index})
+ case lists:filter(UsableFilter, UsableIndexes0) of
+ [] ->
+ ?MANGO_ERROR({no_usable_index, missing_sort_index});
+ UsableIndexes ->
+ UsableIndexes
end.
@@ -100,31 +99,6 @@
end.
-maybe_filter_by_sort_fields(Indexes, []) ->
- {ok, Indexes};
-
-maybe_filter_by_sort_fields(Indexes, SortFields) ->
- FilterFun = fun(Idx) ->
- Cols = mango_idx:columns(Idx),
- case {mango_idx:type(Idx), Cols} of
- {_, all_fields} ->
- true;
- {<<"text">>, _} ->
- sets:is_subset(sets:from_list(SortFields), sets:from_list(Cols));
- {<<"json">>, _} ->
- lists:prefix(SortFields, Cols);
- {<<"special">>, _} ->
- lists:prefix(SortFields, Cols)
- end
- end,
- case lists:filter(FilterFun, Indexes) of
- [] ->
- {error, no_usable_index};
- FilteredIndexes ->
- {ok, FilteredIndexes}
- end.
-
-
new(Db, Opts) ->
Def = get_idx_def(Opts),
Type = get_idx_type(Opts),
diff --git a/src/mango/src/mango_idx_special.erl b/src/mango/src/mango_idx_special.erl
index 12da1cb..ac6efc7 100644
--- a/src/mango/src/mango_idx_special.erl
+++ b/src/mango/src/mango_idx_special.erl
@@ -63,9 +63,11 @@
[<<"_id">>].
-is_usable(#idx{def=all_docs}, Selector, _) ->
+is_usable(#idx{def=all_docs}, _Selector, []) ->
+ true;
+is_usable(#idx{def=all_docs} = Idx, Selector, SortFields) ->
Fields = mango_idx_view:indexable_fields(Selector),
- lists:member(<<"_id">>, Fields).
+ lists:member(<<"_id">>, Fields) and can_use_sort(Idx, SortFields, Selector).
start_key([{'$gt', Key, _, _}]) ->
@@ -96,3 +98,10 @@
end_key([{'$eq', Key, '$eq', Key}]) ->
false = mango_json:special(Key),
Key.
+
+
+can_use_sort(_Idx, [], _Selector) ->
+ true;
+can_use_sort(Idx, SortFields, _Selector) ->
+ Cols = columns(Idx),
+ lists:prefix(SortFields, Cols).
diff --git a/src/mango/src/mango_idx_view.erl b/src/mango/src/mango_idx_view.erl
index c9fe4c8..2d784b6 100644
--- a/src/mango/src/mango_idx_view.erl
+++ b/src/mango/src/mango_idx_view.erl
@@ -124,8 +124,15 @@
% we don't need to check the selector for these
RequiredFields1 = ordsets:subtract(lists:usort(RequiredFields), lists:usort(SortFields)),
- mango_selector:has_required_fields(Selector, RequiredFields1)
- andalso not is_text_search(Selector).
+ % _id and _rev are implicitly in every document so
+ % we don't need to check the selector for these either
+ RequiredFields2 = ordsets:subtract(
+ RequiredFields1,
+ [<<"_id">>, <<"_rev">>]),
+
+ mango_selector:has_required_fields(Selector, RequiredFields2)
+ andalso not is_text_search(Selector)
+ andalso can_use_sort(RequiredFields, SortFields, Selector).
is_text_search({[]}) ->
@@ -505,3 +512,30 @@
max
end
end.
+
+
+% Can_use_sort works as follows:
+%
+% * no sort fields then we can use this
+% * Run out index columns we can't use this index
+% * If the current column is the start of the sort, return if sort is a prefix
+% * If the current column is constant, drop it and continue, else return false
+%
+% A constant column is a something that won't affect the sort
+% for example A: {$eq: 21}}
+%
+% Currently we only look at constant fields that are prefixes to the sort fields
+% set by the user. We considered adding in constant fields after sort fields
+% but were not 100% sure that it would not affect the sorting of the query.
+
+can_use_sort(_Cols, [], _Selector) ->
+ true;
+can_use_sort([], _SortFields, _Selector) ->
+ false;
+can_use_sort([Col | _] = Cols, [Col | _] = SortFields, _Selector) ->
+ lists:prefix(SortFields, Cols);
+can_use_sort([Col | RestCols], SortFields, Selector) ->
+ case mango_selector:is_constant_field(Selector, Col) of
+ true -> can_use_sort(RestCols, SortFields, Selector);
+ false -> false
+ end.
diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index 968dc3c..fffadcd 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -16,7 +16,8 @@
-export([
normalize/1,
match/2,
- has_required_fields/2
+ has_required_fields/2,
+ is_constant_field/2
]).
@@ -638,11 +639,121 @@
end.
+% Returns true if a field in the selector is a constant value e.g. {a: {$eq: 1}}
+is_constant_field({[]}, _Field) ->
+ false;
+
+is_constant_field(Selector, Field) when not is_list(Selector) ->
+ is_constant_field([Selector], Field);
+
+is_constant_field([], _Field) ->
+ false;
+
+is_constant_field([{[{<<"$and">>, Args}]}], Field) when is_list(Args) ->
+ lists:any(fun(Arg) -> is_constant_field(Arg, Field) end, Args);
+
+is_constant_field([{[{<<"$and">>, Args}]}], Field) ->
+ is_constant_field(Args, Field);
+
+is_constant_field([{[{Field, {[{Cond, _Val}]}}]} | _Rest], Field) ->
+ Cond =:= <<"$eq">>;
+
+is_constant_field([{[{_UnMatched, _}]} | Rest], Field) ->
+ is_constant_field(Rest, Field).
+
+
%%%%%%%% module tests below %%%%%%%%
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
+is_constant_field_basic_test() ->
+ Selector = normalize({[{<<"A">>, <<"foo">>}]}),
+ Field = <<"A">>,
+ ?assertEqual(true, is_constant_field(Selector, Field)).
+
+is_constant_field_basic_two_test() ->
+ Selector = normalize({[{<<"$and">>,
+ [
+ {[{<<"cars">>,{[{<<"$eq">>,<<"2">>}]}}]},
+ {[{<<"age">>,{[{<<"$gt">>,10}]}}]}
+ ]
+ }]}),
+ Field = <<"cars">>,
+ ?assertEqual(true, is_constant_field(Selector, Field)).
+
+is_constant_field_not_eq_test() ->
+ Selector = normalize({[{<<"$and">>,
+ [
+ {[{<<"cars">>,{[{<<"$eq">>,<<"2">>}]}}]},
+ {[{<<"age">>,{[{<<"$gt">>,10}]}}]}
+ ]
+ }]}),
+ Field = <<"age">>,
+ ?assertEqual(false, is_constant_field(Selector, Field)).
+
+is_constant_field_missing_field_test() ->
+ Selector = normalize({[{<<"$and">>,
+ [
+ {[{<<"cars">>,{[{<<"$eq">>,<<"2">>}]}}]},
+ {[{<<"age">>,{[{<<"$gt">>,10}]}}]}
+ ]
+ }]}),
+ Field = <<"wrong">>,
+ ?assertEqual(false, is_constant_field(Selector, Field)).
+
+is_constant_field_or_field_test() ->
+ Selector = {[{<<"$or">>,
+ [
+ {[{<<"A">>, <<"foo">>}]},
+ {[{<<"B">>, <<"foo">>}]}
+ ]
+ }]},
+ Normalized = normalize(Selector),
+ Field = <<"A">>,
+ ?assertEqual(false, is_constant_field(Normalized, Field)).
+
+is_constant_field_empty_selector_test() ->
+ Selector = normalize({[]}),
+ Field = <<"wrong">>,
+ ?assertEqual(false, is_constant_field(Selector, Field)).
+
+is_constant_nested_and_test() ->
+ Selector1 = {[{<<"$and">>,
+ [
+ {[{<<"A">>, <<"foo">>}]}
+ ]
+ }]},
+ Selector2 = {[{<<"$and">>,
+ [
+ {[{<<"B">>, {[{<<"$gt">>,10}]}}]}
+ ]
+ }]},
+ Selector = {[{<<"$and">>,
+ [
+ Selector1,
+ Selector2
+ ]
+ }]},
+
+ Normalized = normalize(Selector),
+ ?assertEqual(true, is_constant_field(Normalized, <<"A">>)),
+ ?assertEqual(false, is_constant_field(Normalized, <<"B">>)).
+
+is_constant_combined_or_and_equals_test() ->
+ Selector = {[{<<"A">>, "foo"},
+ {<<"$or">>,
+ [
+ {[{<<"B">>, <<"bar">>}]},
+ {[{<<"B">>, <<"baz">>}]}
+ ]
+ },
+ {<<"C">>, "qux"}
+ ]},
+ Normalized = normalize(Selector),
+ ?assertEqual(true, is_constant_field(Normalized, <<"C">>)),
+ ?assertEqual(false, is_constant_field(Normalized, <<"B">>)).
+
has_required_fields_basic_test() ->
RequiredFields = [<<"A">>],
Selector = {[{<<"A">>, <<"foo">>}]},
diff --git a/src/mango/test/02-basic-find-test.py b/src/mango/test/02-basic-find-test.py
index f7e151a..6a31d33 100644
--- a/src/mango/test/02-basic-find-test.py
+++ b/src/mango/test/02-basic-find-test.py
@@ -333,3 +333,10 @@
assert explain["mrargs"]["start_key"] == [0]
assert explain["mrargs"]["end_key"] == ["<MAX>"]
assert explain["mrargs"]["include_docs"] == True
+
+ def test_sort_with_all_docs(self):
+ explain = self.db.find({
+ "_id": {"$gt": 0},
+ "age": {"$gt": 0}
+ }, sort=["_id"], explain=True)
+ self.assertEquals(explain["index"]["type"], "special")
diff --git a/src/mango/test/12-use-correct-index-test.py b/src/mango/test/12-use-correct-index-test.py
index 5a2b24d..7bb90eb 100644
--- a/src/mango/test/12-use-correct-index-test.py
+++ b/src/mango/test/12-use-correct-index-test.py
@@ -114,3 +114,16 @@
explain = self.db.find(selector, explain=True)
self.assertEqual(explain["index"]["ddoc"], "_design/bbb")
self.assertEqual(explain["mrargs"]["end_key"], [10, '<MAX>'])
+
+ # all documents contain an _id and _rev field they
+ # should not be used to restrict indexes based on the
+ # fields required by the selector
+ def test_choose_index_with_id(self):
+ self.db.create_index(["name", "_id"], ddoc="aaa")
+ explain = self.db.find({"name": "Eddie"}, explain=True)
+ self.assertEqual(explain["index"]["ddoc"], '_design/aaa')
+
+ def test_choose_index_with_rev(self):
+ self.db.create_index(["name", "_rev"], ddoc="aaa")
+ explain = self.db.find({"name": "Eddie"}, explain=True)
+ self.assertEqual(explain["index"]["ddoc"], '_design/aaa')
diff --git a/src/mango/test/18-json-sort.py b/src/mango/test/18-json-sort.py
new file mode 100644
index 0000000..f8d2abe
--- /dev/null
+++ b/src/mango/test/18-json-sort.py
@@ -0,0 +1,222 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import mango
+import copy
+import unittest
+
+DOCS = [
+ {
+ "_id": "1",
+ "name": "Jimi",
+ "age": 10,
+ "cars": 1
+ },
+ {
+ "_id": "2",
+ "name": "Eddie",
+ "age": 20,
+ "cars": 1
+ },
+ {
+ "_id": "3",
+ "name": "Jane",
+ "age": 30,
+ "cars": 2
+ },
+ {
+ "_id": "4",
+ "name": "Mary",
+ "age": 40,
+ "cars": 2
+ },
+ {
+ "_id": "5",
+ "name": "Sam",
+ "age": 50,
+ "cars": 3
+ }
+]
+
+class JSONIndexSortOptimisations(mango.DbPerClass):
+ def setUp(self):
+ self.db.recreate()
+ self.db.save_docs(copy.deepcopy(DOCS))
+
+ def test_works_for_basic_case(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "cars": "2",
+ "age": {
+ "$gt": 10
+ }
+ }
+ explain = self.db.find(selector, sort=["age"], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age")
+ self.assertEqual(explain["mrargs"]["direction"], "fwd")
+
+ def test_works_for_all_fields_specified(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "cars": "2",
+ "age": {
+ "$gt": 10
+ }
+ }
+ explain = self.db.find(selector, sort=["cars", "age"], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age")
+
+ def test_works_for_no_sort_fields_specified(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "cars": {
+ "$gt": 10
+ },
+ "age": {
+ "$gt": 10
+ }
+ }
+ explain = self.db.find(selector, explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age")
+
+ def test_works_for_opp_dir_sort(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "cars": "2",
+ "age": {
+ "$gt": 10
+ }
+ }
+ explain = self.db.find(selector, sort=[{"age": "desc"}], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age")
+ self.assertEqual(explain["mrargs"]["direction"], "rev")
+
+ def test_not_work_for_non_constant_field(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "cars": {
+ "$gt": 10
+ },
+ "age": {
+ "$gt": 10
+ }
+ }
+ try:
+ self.db.find(selector, explain=True, sort=["age"])
+ raise Exception("Should not get here")
+ except Exception as e:
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
+
+ def test_three_index_one(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "cars": "2",
+ "age": 10,
+ "name": {
+ "$gt": "AA"
+ }
+ }
+ explain = self.db.find(selector, sort=["name"], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age-name")
+
+ def test_three_index_two(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "cars": "2",
+ "name": "Eddie",
+ "age": {
+ "$gt": 10
+ }
+ }
+ explain = self.db.find(selector, sort=["age"], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age-name")
+
+ def test_three_index_fails(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "name": "Eddie",
+ "age": {
+ "$gt": 1
+ },
+ "cars": {
+ "$gt": "1"
+ }
+ }
+ try:
+ self.db.find(selector, explain=True, sort=["name"])
+ raise Exception("Should not get here")
+ except Exception as e:
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
+
+ def test_empty_sort(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "name": {
+ "$gt": "Eddie",
+ },
+ "age": 10,
+ "cars": {
+ "$gt": "1"
+ }
+ }
+ explain = self.db.find(selector, explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age-name")
+
+ def test_in_between(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "name": "Eddie",
+ "age": 10,
+ "cars": {
+ "$gt": "1"
+ }
+ }
+ explain = self.db.find(selector, explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age-name")
+
+ try:
+ self.db.find(selector, sort=["cars", "name"], explain=True)
+ raise Exception("Should not get here")
+ except Exception as e:
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
+
+ def test_ignore_after_set_sort_value(self):
+ self.db.create_index(["cars", "age", "name"], name="cars-age-name")
+ selector = {
+ "age": {
+ "$gt": 10
+ },
+ "cars": 2,
+ "name": {
+ "$gt": "A"
+ }
+ }
+ explain = self.db.find(selector, sort=["age"], explain=True)
+ self.assertEqual(explain["index"]["name"], "cars-age-name")
+
+ def test_not_use_index_if_other_fields_in_sort(self):
+ self.db.create_index(["cars", "age"], name="cars-age")
+ selector = {
+ "age": 10,
+ "cars": {
+ "$gt": "1"
+ }
+ }
+ try:
+ self.db.find(selector, sort=["cars", "name"], explain=True)
+ raise Exception("Should not get here")
+ except Exception as e:
+ resp = e.response.json()
+ self.assertEqual(resp["error"], "no_usable_index")
diff --git a/src/mango/test/19-find-conflicts.py b/src/mango/test/19-find-conflicts.py
new file mode 100644
index 0000000..c6d59f0
--- /dev/null
+++ b/src/mango/test/19-find-conflicts.py
@@ -0,0 +1,41 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import mango
+import copy
+
+DOC = [
+ {
+ "_id": "doc",
+ "a": 2
+ }
+]
+
+CONFLICT = [
+ {
+ "_id": "doc",
+ "_rev": "1-23202479633c2b380f79507a776743d5",
+ "a": 1
+ }
+]
+
+class ChooseCorrectIndexForDocs(mango.DbPerClass):
+ def setUp(self):
+ self.db.recreate()
+ self.db.save_docs(copy.deepcopy(DOC))
+ self.db.save_docs_with_conflicts(copy.deepcopy(CONFLICT))
+
+ def test_retrieve_conflicts(self):
+ self.db.create_index(["_conflicts"])
+ result = self.db.find({"_conflicts": { "$exists": True}}, conflicts=True)
+ self.assertEqual(result[0]['_conflicts'][0], '1-23202479633c2b380f79507a776743d5')
+ self.assertEqual(result[0]['_rev'], '1-3975759ccff3842adf690a5c10caee42')
diff --git a/src/mango/test/mango.py b/src/mango/test/mango.py
index 9b6b998..bc12bbc 100644
--- a/src/mango/test/mango.py
+++ b/src/mango/test/mango.py
@@ -95,6 +95,11 @@
def save_doc(self, doc):
self.save_docs([doc])
+ def save_docs_with_conflicts(self, docs, **kwargs):
+ body = json.dumps({"docs": docs, "new_edits": False})
+ r = self.sess.post(self.path("_bulk_docs"), data=body, params=kwargs)
+ r.raise_for_status()
+
def save_docs(self, docs, **kwargs):
body = json.dumps({"docs": docs})
r = self.sess.post(self.path("_bulk_docs"), data=body, params=kwargs)
diff --git a/src/mem3/src/mem3_shards.erl b/src/mem3/src/mem3_shards.erl
index 0975d2f..da3b69a 100644
--- a/src/mem3/src/mem3_shards.erl
+++ b/src/mem3/src/mem3_shards.erl
@@ -184,6 +184,7 @@
erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
init([]) ->
+ couch_util:set_mqd_off_heap(),
ets:new(?SHARDS, [
bag,
public,
@@ -724,42 +725,25 @@
mem3_shards_changes_test_() -> {
- "Test mem3_shards changes listener", {
- foreach,
- fun setup_changes/0, fun teardown_changes/1,
+ "Test mem3_shards changes listener",
+ {
+ setup,
+ fun test_util:start_couch/0, fun test_util:stop_couch/1,
[
- fun should_kill_changes_listener_on_shutdown/1
+ fun should_kill_changes_listener_on_shutdown/0
]
}
}.
-setup_changes() ->
- RespDb = test_util:fake_db([{name, <<"dbs">>}, {update_seq, 0}]),
- ok = meck:expect(mem3_util, ensure_exists, ['_'], {ok, RespDb}),
- ok = meck:expect(couch_db, close, ['_'], ok),
- ok = application:start(config),
+should_kill_changes_listener_on_shutdown() ->
{ok, Pid} = ?MODULE:start_link(),
+ {ok, ChangesPid} = get_changes_pid(),
+ ?assert(is_process_alive(ChangesPid)),
true = erlang:unlink(Pid),
- Pid.
-
-
-teardown_changes(Pid) ->
- true = exit(Pid, shutdown),
- ok = application:stop(config),
- meck:unload().
-
-
-should_kill_changes_listener_on_shutdown(Pid) ->
- ?_test(begin
- ?assert(is_process_alive(Pid)),
- {ok, ChangesPid} = get_changes_pid(),
- ?assert(is_process_alive(ChangesPid)),
- true = test_util:stop_sync_throw(
- ChangesPid, fun() -> exit(Pid, shutdown) end, wait_timeout),
- ?assertNot(is_process_alive(ChangesPid)),
- ok
- end).
-
+ true = test_util:stop_sync_throw(
+ ChangesPid, fun() -> exit(Pid, shutdown) end, wait_timeout),
+ ?assertNot(is_process_alive(ChangesPid)),
+ exit(Pid, shutdown).
-endif.
diff --git a/src/mem3/test/01-config-default.ini b/src/mem3/test/01-config-default.ini
deleted file mode 100644
index dde92ce..0000000
--- a/src/mem3/test/01-config-default.ini
+++ /dev/null
@@ -1,14 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-[cluster]
-n=3
diff --git a/src/mem3/test/mem3_sync_security_test.erl b/src/mem3/test/mem3_sync_security_test.erl
index 8b6af3c..4e06dd8 100644
--- a/src/mem3/test/mem3_sync_security_test.erl
+++ b/src/mem3/test/mem3_sync_security_test.erl
@@ -19,11 +19,14 @@
go_test() ->
Ctx = test_util:start_couch([fabric, mem3]),
- ok = meck:new(fabric, [passthrough]),
- meck:expect(fabric, all_dbs, fun() ->
- {ok, [<<"NoExistDb1">>, <<"NoExistDb2">>]}
- end),
- Result = mem3_sync_security:go(),
- meck:unload(fabric),
- test_util:stop_couch(Ctx),
- ?assertEqual(ok, Result).
+ try
+ ok = meck:new(fabric, [passthrough]),
+ meck:expect(fabric, all_dbs, fun() ->
+ {ok, [<<"NoExistDb1">>, <<"NoExistDb2">>]}
+ end),
+ Result = mem3_sync_security:go(),
+ ?assertEqual(ok, Result)
+ after
+ meck:unload(),
+ test_util:stop_couch(Ctx)
+ end.
diff --git a/src/mem3/test/mem3_util_test.erl b/src/mem3/test/mem3_util_test.erl
index 163580c..214217e 100644
--- a/src/mem3/test/mem3_util_test.erl
+++ b/src/mem3/test/mem3_util_test.erl
@@ -121,47 +121,18 @@
%% n_val tests
nval_test_() ->
- {"n_val tests explicit",
- [
- {setup,
- fun () ->
- meck:new([couch_log]),
- meck:expect(couch_log, error, fun(_, _) -> ok end),
- ok
- end,
- fun (_) -> meck:unload([couch_log]) end,
- [
- ?_assertEqual(2, mem3_util:n_val(2,4)),
- ?_assertEqual(1, mem3_util:n_val(-1,4)),
- ?_assertEqual(4, mem3_util:n_val(6,4))
+ {
+ setup,
+ fun() ->
+ meck:new([config, couch_log]),
+ meck:expect(couch_log, error, 2, ok),
+ meck:expect(config, get, 3, "5")
+ end,
+ fun(_) -> meck:unload() end,
+ [
+ ?_assertEqual(2, mem3_util:n_val(2, 4)),
+ ?_assertEqual(1, mem3_util:n_val(-1, 4)),
+ ?_assertEqual(4, mem3_util:n_val(6, 4)),
+ ?_assertEqual(5, mem3_util:n_val(undefined, 6))
]
- }
- ]
}.
-
-
-config_01_setup() ->
- Ini = filename:join([code:lib_dir(mem3, test), "01-config-default.ini"]),
- {ok, Pid} = config:start_link([Ini]),
- Pid.
-
-config_teardown(Pid) ->
- test_util:stop_config(Pid).
-
-
-n_val_test_() ->
- {"n_val tests with config",
- [
- {setup,
- fun config_01_setup/0,
- fun config_teardown/1,
- fun(Pid) ->
- {with, Pid, [
- fun n_val_1/1
- ]}
- end}
- ]
- }.
-
-n_val_1(_Pid) ->
- ?assertEqual(3, mem3_util:n_val(undefined, 4)).
diff --git a/src/rexi/src/rexi_server.erl b/src/rexi/src/rexi_server.erl
index 3d3f272..954ca88 100644
--- a/src/rexi/src/rexi_server.erl
+++ b/src/rexi/src/rexi_server.erl
@@ -39,6 +39,7 @@
gen_server:start_link({local, ServerId}, ?MODULE, [], []).
init([]) ->
+ couch_util:set_mqd_off_heap(),
{ok, #st{}}.
handle_call(get_errors, _From, #st{errors = Errors} = St) ->
diff --git a/test/javascript/run b/test/javascript/run
index 8ae4244..ca69e1f 100755
--- a/test/javascript/run
+++ b/test/javascript/run
@@ -134,10 +134,11 @@
tmp.append(name)
tests = tmp
- fmt = mkformatter(tests)
passed = 0
failed = 0
- for test in tests:
+ if len(tests) > 0 :
+ fmt = mkformatter(tests)
+ for test in tests:
result = run_couchjs(test, fmt)
if result == 0:
passed += 1
@@ -169,8 +170,7 @@
elif os.path.isfile(pname + ".js"):
tests.append(pname + ".js")
else:
- sys.stderr.write("Unknown test: " + name + os.linesep)
- exit(1)
+ sys.stderr.write("Waring - Unknown test: " + name + os.linesep)
return tests
diff --git a/test/javascript/tests-cluster/with-quorum/attachments.js b/test/javascript/tests-cluster/with-quorum/attachments.js
new file mode 100644
index 0000000..f578f87
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/attachments.js
@@ -0,0 +1,36 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 201,"Should return 201");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + rev, {
+ body:"This is no base64 encoded text-2",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 201,"Should return 201");
+
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/with-quorum/attachments_delete.js b/test/javascript/tests-cluster/with-quorum/attachments_delete.js
new file mode 100644
index 0000000..ed7d2db
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/attachments_delete.js
@@ -0,0 +1,32 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments_delete= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 201,"Should return 201 Accepted");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/dummy/foo.txt?rev=" + rev);
+ T(xhr.status == 200,"Should return 200 Ok but returns "+xhr.status);
+
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/with-quorum/attachments_delete_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/attachments_delete_overridden_quorum.js
new file mode 100644
index 0000000..1994a0a
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/attachments_delete_overridden_quorum.js
@@ -0,0 +1,36 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments_delete_overridden_quorum= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/dummy/foo.txt?rev=" + rev);
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 202->"+xhr.status);
+ // TODO: Define correct behaviour
+ //T(xhr.status == 202,"Should return 202 but returns "+xhr.status);
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests-cluster/with-quorum/attachments_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/attachments_overridden_quorum.js
new file mode 100644
index 0000000..22c8a4c
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/attachments_overridden_quorum.js
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+//Test attachments operations with an overridden quorum parameter
+couchTests.attachments_overriden_quorum= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ //TODO: Define correct behaviour
+ //T(xhr.status == 202,"Should return 202");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + rev, {
+ body:"This is no base64 encoded text-2",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 202->"+xhr.status);
+ //TODO: Define correct behaviour
+ //T(xhr.status == 202,"Should return 202");
+
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/with-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/db_creation.js
similarity index 100%
rename from test/javascript/tests-cluster/with-quorum/db-creation.js
rename to test/javascript/tests-cluster/with-quorum/db_creation.js
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/db_creation_overridden_quorum.js
similarity index 60%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/with-quorum/db_creation_overridden_quorum.js
index 0d8ff83..14d319c 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/with-quorum/db_creation_overridden_quorum.js
@@ -10,19 +10,19 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
+// Do DB creation under cluster with quorum conditions but overriding write quorum.
+couchTests.db_creation_overridden_quorum = function(debug) {
if (debug) debugger;
var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
- // DB Creation should return 202- Accepted
+ // DB Creation should return 202 - Accepted
xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 202->"+xhr.status)
+ //T(xhr.status == 202,"Should return 202");
// cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+ db.deleteDb();
};
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/db_deletion.js
similarity index 70%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/with-quorum/db_deletion.js
index 0d8ff83..079fb49 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/with-quorum/db_deletion.js
@@ -10,19 +10,21 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
+// Do DB deletion under cluster with quorum conditions.
+couchTests.db_deletion = function(debug) {
if (debug) debugger;
var db_name = get_random_db_name()
var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
+ db.createDb();
+
+ // DB Deletion should return 202 - Acceted as the custer is not complete
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
T(xhr.status == 202);
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+// DB Deletion should return 404 - Not found
+ xhr = CouchDB.request("DELETE", "/not-existing-db/");
+ T(xhr.status == 404);
};
diff --git a/test/javascript/tests-cluster/with-quorum/db_deletion_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/db_deletion_overridden_quorum.js
new file mode 100644
index 0000000..01417eb
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/db_deletion_overridden_quorum.js
@@ -0,0 +1,23 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Do DB deletion in a cluster with quorum conditions.
+couchTests.db_deletion_overridden_quorum = function(debug) {
+
+ if (debug) debugger;
+
+ var db_name = get_random_db_name()
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ db.deleteDb();
+ T(db.last_req.status="202","Should return 202");
+};
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/doc_bulk.js
similarity index 62%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/with-quorum/doc_bulk.js
index 0d8ff83..4bdd3c8 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/with-quorum/doc_bulk.js
@@ -10,19 +10,16 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_bulk = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var docs = makeDocs(5);
+ // Create the docs
+ var results = db.bulkSave(docs);
+ T(db.last_req.status="201","Should return 201")
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
-
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/with-quorum/doc_bulk_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/doc_bulk_overridden_quorum.js
new file mode 100644
index 0000000..0cf9a7e
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/doc_bulk_overridden_quorum.js
@@ -0,0 +1,25 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.doc_bulk_overridden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(5);
+ // Create the docs
+ var results = db.bulkSave(docs);
+ T(db.last_req.status="202","Should return 202")
+
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/doc_copy.js
similarity index 62%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/with-quorum/doc_copy.js
index 0d8ff83..386ca56 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/with-quorum/doc_copy.js
@@ -10,19 +10,18 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_copy = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.save({_id:"dummy"});
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ var xhr = CouchDB.request("COPY", "/" + db_name + "/dummy", {
+ headers: {"Destination":"dummy2"}
+ });
+ T(xhr.status=="201","Should return 201 ");
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/with-quorum/doc_copy_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/doc_copy_overridden_quorum.js
new file mode 100644
index 0000000..23fbc97
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/doc_copy_overridden_quorum.js
@@ -0,0 +1,30 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.doc_copy_overriden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ if (debug) debugger;
+
+ db.save({_id:"dummy"});
+
+ var xhr = CouchDB.request("COPY", "/" + db_name + "/dummy", {
+ headers: {"Destination":"dummy2"}
+ });
+ //TODO: Define correct behaviour
+ //T(xhr.status=="202","Should return 202");
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 202->"+xhr.status);
+
+ db.deleteDb();
+
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/with-quorum/doc_crud.js
similarity index 62%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/with-quorum/doc_crud.js
index 0d8ff83..f016cef 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/with-quorum/doc_crud.js
@@ -10,19 +10,22 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_crud = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.save({_id:"0",a:1});
+ T(db.last_req.status=="201");
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ var doc = db.open("0");
+ db.save(doc);
+ T(db.last_req.status=="201");
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+ doc = db.open("0");
+ db.deleteDoc(doc);
+ T(db.last_req.status="200");
+ db.deleteDb();
+
+}
diff --git a/test/javascript/tests-cluster/with-quorum/doc_crud_overridden_quorum.js b/test/javascript/tests-cluster/with-quorum/doc_crud_overridden_quorum.js
new file mode 100644
index 0000000..41502ca
--- /dev/null
+++ b/test/javascript/tests-cluster/with-quorum/doc_crud_overridden_quorum.js
@@ -0,0 +1,31 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.doc_crud_overridden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":3});
+ db.createDb();
+ if (debug) debugger;
+
+ db.save({_id:"0",a:1});
+ T(db.last_req.status=="202","Should return 202 status");
+
+ var doc = db.open("0");
+ db.save(doc);
+ T(db.last_req.status=="202","Should return 202 status");
+
+ doc = db.open("0");
+ db.deleteDoc(doc);
+ T(db.last_req.status="202","Should return 202 status");
+
+ db.deleteDb();
+}
diff --git a/test/javascript/tests-cluster/without-quorum/attachments.js b/test/javascript/tests-cluster/without-quorum/attachments.js
new file mode 100644
index 0000000..5756343
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/attachments.js
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 202,"Should return 202 Accepted");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + rev, {
+ body:"This is no base64 encoded text-2",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 202,"Should return 202 Accepted");
+ rev = JSON.parse(xhr.responseText).rev;
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests-cluster/without-quorum/attachments_delete.js b/test/javascript/tests-cluster/without-quorum/attachments_delete.js
new file mode 100644
index 0000000..d05fcaf
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/attachments_delete.js
@@ -0,0 +1,37 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments_delete= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ T(xhr.status == 202,"Should return 202 Accepted");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/dummy/foo.txt?rev=" + rev);
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering quorum. 202->"+xhr.status);
+ //TODO: Define correct behaviour
+ //T(xhr.status == 202,"Should return 202 Accepted but returns "+xhr.status);
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests-cluster/without-quorum/attachments_delete_overridden_quorum.js b/test/javascript/tests-cluster/without-quorum/attachments_delete_overridden_quorum.js
new file mode 100644
index 0000000..906391a
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/attachments_delete_overridden_quorum.js
@@ -0,0 +1,36 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments_delete_overridden_quorum= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/dummy/foo.txt?rev=" + rev);
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering quorum. 202->"+xhr.status);
+ //TODO: Define correct behaviour
+ //T(xhr.status == 200,"Should return 200 but returns "+xhr.status);
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests-cluster/without-quorum/attachments_overridden_quorum.js b/test/javascript/tests-cluster/without-quorum/attachments_overridden_quorum.js
new file mode 100644
index 0000000..434578f
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/attachments_overridden_quorum.js
@@ -0,0 +1,42 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+//Test attachments operations with an overridden quorum parameter
+couchTests.attachments_overriden_quorum= function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = db.save({_id:"dummy"});
+ T(doc.ok);
+
+ var xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + doc.rev, {
+ body:"This is no base64 encoded text",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ //TODO: Define correct behaviour
+ //T(xhr.status == 201,"Should return 201");
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ xhr = CouchDB.request("PUT", "/" + db_name + "/dummy/foo.txt?rev=" + rev, {
+ body:"This is no base64 encoded text-2",
+ headers:{"Content-Type": "text/plain;charset=utf-8"}
+ });
+ //TODO: Define correct behaviour
+ //T(xhr.status == 201,"Should return 201");
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/db_creation.js
similarity index 89%
rename from test/javascript/tests-cluster/without-quorum/db-creation.js
rename to test/javascript/tests-cluster/without-quorum/db_creation.js
index 0d8ff83..a21d377 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/db_creation.js
@@ -23,6 +23,5 @@
T(xhr.status == 202);
// cleanup
- // TODO DB deletions fails if the quorum is not met.
- xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+ db.deleteDb();
};
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/db_creation_overridden_quorum.js
similarity index 64%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/db_creation_overridden_quorum.js
index 0d8ff83..6d5d798 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/db_creation_overridden_quorum.js
@@ -10,18 +10,20 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
+// Do DB creation under cluster with quorum conditions but overriding write quorum.
+couchTests.db_creation_overridden_quorum = function(debug) {
if (debug) debugger;
var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
- // DB Creation should return 202- Accepted
+ // DB Creation should return 201 - Created
xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 201->"+xhr.status)
+ //T(xhr.status == 201,"Should return 201");
+ //db.deleteDb();
// cleanup
// TODO DB deletions fails if the quorum is not met.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/db_deletion.js
similarity index 72%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/db_deletion.js
index 0d8ff83..006345e 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/db_deletion.js
@@ -10,19 +10,21 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
+// Do DB creation under cluster with quorum conditions.
+couchTests.db_deletion = function(debug) {
if (debug) debugger;
var db_name = get_random_db_name()
var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
-
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
+ db.createDb();
+
+ // DB Deletion should return 202 - Acepted
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+ T(xhr.status == 202);
+
+ // DB Deletion should return 404 - Not found
+ xhr = CouchDB.request("DELETE", "/not-existing-db/");
+ T(xhr.status == 404);
};
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/db_deletion_overridden_quorum.js
similarity index 65%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/db_deletion_overridden_quorum.js
index 0d8ff83..11b344c 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/db_deletion_overridden_quorum.js
@@ -10,19 +10,16 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
+// Do DB deletion in a cluster with quorum conditions.
+couchTests.db_deletion_overridden_quorum = function(debug) {
if (debug) debugger;
var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
-
- // cleanup
- // TODO DB deletions fails if the quorum is not met.
+ // DB deletions does not consider overriden quorum param.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+ T(db.last_req.status="202","Should return 202");
};
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/doc_bulk.js
similarity index 74%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/doc_bulk.js
index 0d8ff83..91578d8 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/doc_bulk.js
@@ -10,19 +10,19 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_bulk = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var docs = makeDocs(5);
+ // Create the docs
+ var results = db.bulkSave(docs);
+ T(db.last_req.status="202","Should return 202")
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
-
+ //db.deleteDb();
// cleanup
// TODO DB deletions fails if the quorum is not met.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/doc_bulk_overridden_quorum.js
similarity index 67%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/doc_bulk_overridden_quorum.js
index 0d8ff83..56fb11e 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/doc_bulk_overridden_quorum.js
@@ -10,19 +10,19 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_bulk_overridden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ var docs = makeDocs(5);
+ // Create the docs
+ var results = db.bulkSave(docs);
+ T(db.last_req.status="201","Should return 201")
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
-
+ //db.deleteDb();
// cleanup
// TODO DB deletions fails if the quorum is not met.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/doc_copy.js
similarity index 72%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/doc_copy.js
index 0d8ff83..7d7c35f 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/doc_copy.js
@@ -10,19 +10,21 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_copy = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.save({_id:"dummy"});
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ var xhr = CouchDB.request("COPY", "/" + db_name + "/dummy", {
+ headers: {"Destination":"dummy2"}
+ });
+ T(xhr.status=="202","Should return 202 ");
+ //db.deleteDb();
// cleanup
// TODO DB deletions fails if the quorum is not met.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+}
diff --git a/test/javascript/tests-cluster/without-quorum/doc_copy_overridden_quorum.js b/test/javascript/tests-cluster/without-quorum/doc_copy_overridden_quorum.js
new file mode 100644
index 0000000..e72425d
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/doc_copy_overridden_quorum.js
@@ -0,0 +1,33 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.doc_copy_overriden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
+ if (debug) debugger;
+
+ db.save({_id:"dummy"});
+
+ var xhr = CouchDB.request("COPY", "/" + db_name + "/dummy", {
+ headers: {"Destination":"dummy2"}
+ });
+ console.log("Skipped-TODO: Clarify correct behaviour. Is not considering overridden quorum. 201->"+xhr.status);
+ //TODO Defie correct behaviour
+ //T(xhr.status=="201","Should return 201");
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+
+}
diff --git a/test/javascript/tests-cluster/without-quorum/db-creation.js b/test/javascript/tests-cluster/without-quorum/doc_crud.js
similarity index 65%
copy from test/javascript/tests-cluster/without-quorum/db-creation.js
copy to test/javascript/tests-cluster/without-quorum/doc_crud.js
index 0d8ff83..aa70697 100644
--- a/test/javascript/tests-cluster/without-quorum/db-creation.js
+++ b/test/javascript/tests-cluster/without-quorum/doc_crud.js
@@ -10,19 +10,26 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Do DB creation under cluster without quorum conditions.
-couchTests.db_creation = function(debug) {
-
+couchTests.doc_crud = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.createDb();
if (debug) debugger;
- var db_name = get_random_db_name()
- var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
+ db.save({_id:"0",a:1});
+ T(db.last_req.status=="202","Should return 202 status");
- // DB Creation should return 202- Accepted
- xhr = CouchDB.request("PUT", "/" + db_name + "/");
- T(xhr.status == 202);
+ var doc = db.open("0");
+ db.save(doc);
+ T(db.last_req.status=="202","Should return 202 status");
+ doc = db.open("0");
+ db.deleteDoc(doc);
+ T(db.last_req.status="202","Should return 202 status");
+
+ //db.deleteDb();
// cleanup
// TODO DB deletions fails if the quorum is not met.
xhr = CouchDB.request("DELETE", "/" + db_name + "/");
-};
+
+}
diff --git a/test/javascript/tests-cluster/without-quorum/doc_crud_overridden_quorum.js b/test/javascript/tests-cluster/without-quorum/doc_crud_overridden_quorum.js
new file mode 100644
index 0000000..44ab86e
--- /dev/null
+++ b/test/javascript/tests-cluster/without-quorum/doc_crud_overridden_quorum.js
@@ -0,0 +1,34 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.doc_crud_overridden_quorum = function(debug) {
+ var db_name = get_random_db_name();
+ var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"},{"w":1});
+ db.createDb();
+ if (debug) debugger;
+
+ db.save({_id:"0",a:1});
+ T(db.last_req.status=="201","Should return 201 status");
+
+ var doc = db.open("0");
+ db.save(doc);
+ T(db.last_req.status=="201","Should return 201 status");
+
+ doc = db.open("0");
+ db.deleteDoc(doc);
+ T(db.last_req.status="200","Should return 200 status");
+
+ //db.deleteDb();
+ // cleanup
+ // TODO DB deletions fails if the quorum is not met.
+ xhr = CouchDB.request("DELETE", "/" + db_name + "/");
+}
diff --git a/test/javascript/tests/reduce_builtin.js b/test/javascript/tests/reduce_builtin.js
index 9c455e4..4686841 100644
--- a/test/javascript/tests/reduce_builtin.js
+++ b/test/javascript/tests/reduce_builtin.js
@@ -37,6 +37,12 @@
emit(doc.integer, doc.integer);
};
+ var check_approx_distinct = function(expected, estimated) {
+ // see https://en.wikipedia.org/wiki/HyperLogLog
+ var err = 1.04 / Math.sqrt(Math.pow(2, 11 - 1));
+ return Math.abs(expected - estimated) < expected * err;
+ };
+
var result = db.query(map, "_sum");
T(result.rows[0].value == 2*summate(numDocs));
result = db.query(map, "_count");
@@ -47,27 +53,41 @@
T(result.rows[0].value.min == 1);
T(result.rows[0].value.max == 500);
T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs));
+ result = db.query(map, "_approx_count_distinct");
+ T(check_approx_distinct(numDocs, result.rows[0].value));
result = db.query(map, "_sum", {startkey: 4, endkey: 4});
T(result.rows[0].value == 8);
result = db.query(map, "_count", {startkey: 4, endkey: 4});
T(result.rows[0].value == 2);
+ result = db.query(map, "_approx_count_distinct", {startkey:4, endkey:4});
+ T(check_approx_distinct(1, result.rows[0].value));
result = db.query(map, "_sum", {startkey: 4, endkey: 5});
T(result.rows[0].value == 18);
result = db.query(map, "_count", {startkey: 4, endkey: 5});
T(result.rows[0].value == 4);
+ result = db.query(map, "_approx_count_distinct", {startkey:4, endkey:5});
+ T(check_approx_distinct(2, result.rows[0].value));
+
result = db.query(map, "_sum", {startkey: 4, endkey: 6});
T(result.rows[0].value == 30);
result = db.query(map, "_count", {startkey: 4, endkey: 6});
T(result.rows[0].value == 6);
+ result = db.query(map, "_approx_count_distinct", {startkey: 4, endkey: 6});
+ T(check_approx_distinct(3, result.rows[0].value));
result = db.query(map, "_sum", {group:true, limit:3});
T(result.rows[0].value == 2);
T(result.rows[1].value == 4);
T(result.rows[2].value == 6);
+ result = db.query(map, "_approx_count_distinct", {group:true, limit:3});
+ T(check_approx_distinct(1, result.rows[0].value));
+ T(check_approx_distinct(1, result.rows[1].value));
+ T(check_approx_distinct(1, result.rows[2].value));
+
for(var i=1; i<numDocs/2; i+=30) {
result = db.query(map, "_sum", {startkey: i, endkey: numDocs - i});
T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
diff --git a/test/javascript/tests/users_db.js b/test/javascript/tests/users_db.js
index 34a7bad..20be325 100644
--- a/test/javascript/tests/users_db.js
+++ b/test/javascript/tests/users_db.js
@@ -205,6 +205,13 @@
} finally {
CouchDB.login("jan", "apple");
usersDb.deleteDb(); // cleanup
+ waitForSuccess(function() {
+ var req = CouchDB.request("GET", usersDb.name);
+ if (req.status == 404) {
+ return true
+ }
+ throw({});
+ }, "usersdb.deleteDb")
usersDb.createDb();
usersDbAlt.deleteDb(); // cleanup
}