summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith Bostic <keith.bostic@mongodb.com>2017-03-13 20:53:11 -0400
committerAlex Gorrod <alexander.gorrod@mongodb.com>2017-03-14 11:53:11 +1100
commit28a883f7b4acd020a8d92a733cb9df415a6be482 (patch)
treee6bef4d3be146c9abbac30473826ab4765649833
parentf72c78b74d42c9e89bc98ad56ba184536e8efcae (diff)
downloadmongo-28a883f7b4acd020a8d92a733cb9df415a6be482.tar.gz
WT-3211 WT_CURSOR.remove cannot always retain its position. (#3321)
-rw-r--r--.gitignore30
-rw-r--r--SConstruct1
-rw-r--r--dist/s_string.ok1
-rwxr-xr-xdist/s_void4
-rw-r--r--examples/c/Makefile.am1
-rw-r--r--examples/c/ex_scope.c217
-rw-r--r--src/btree/bt_cursor.c312
-rw-r--r--src/btree/bt_ret.c152
-rw-r--r--src/cursor/cur_file.c23
-rw-r--r--src/cursor/cur_table.c30
-rw-r--r--src/docs/cursor-ops.dox4
-rw-r--r--src/docs/upgrading.dox30
-rw-r--r--src/include/api.h37
-rw-r--r--src/include/buf.i37
-rw-r--r--src/include/cursor.i35
-rw-r--r--src/include/error.h6
-rw-r--r--src/include/extern.h1
-rw-r--r--src/include/misc.h1
-rw-r--r--src/include/session.h1
-rw-r--r--src/include/wiredtiger.in30
-rw-r--r--src/lsm/lsm_cursor.c42
-rw-r--r--src/txn/txn_ckpt.c1
-rw-r--r--test/csuite/Makefile.am7
-rw-r--r--test/csuite/scope/main.c288
-rw-r--r--test/suite/test_cursor10.py4
-rw-r--r--test/suite/test_cursor11.py159
26 files changed, 960 insertions, 494 deletions
diff --git a/.gitignore b/.gitignore
index c7b3ade9e87..4611f2aa98c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,24 +90,28 @@ _wiredtiger.pyd
**/examples/c/ex_pack
**/examples/c/ex_process
**/examples/c/ex_schema
-**/examples/c/ex_scope
**/examples/c/ex_stat
**/examples/c/ex_sync
**/examples/c/ex_thread
**/test/bloom/t
**/test/checkpoint/t
-**/test/csuite/test_wt1965_col_efficiency
-**/test/csuite/test_wt2246_col_append
-**/test/csuite/test_wt2323_join_visibility
-**/test/csuite/test_wt2403_lsm_workload
-**/test/csuite/test_wt2447_join_main_table
-**/test/csuite/test_wt2535_insert_race
-**/test/csuite/test_wt2592_join_schema
-**/test/csuite/test_wt2695_checksum
-**/test/csuite/test_wt2719_reconfig
-**/test/csuite/test_wt2834_join_bloom_fix
-**/test/csuite/test_wt2853_perf
-**/test/csuite/test_wt2999_join_extractor
+**/test_scope
+**/test_wt1965_col_efficiency
+**/test_wt2246_col_append
+**/test_wt2323_join_visibility
+**/test_wt2403_lsm_workload
+**/test_wt2447_join_main_table
+**/test_wt2535_insert_race
+**/test_wt2592_join_schema
+**/test_wt2695_checksum
+**/test_wt2719_reconfig
+**/test_wt2834_join_bloom_fix
+**/test_wt2853_perf
+**/test_wt2909_checkpoint_integrity
+**/test_wt2999_join_extractor
+**/test_wt3120_filesys
+**/test_wt3135_search_near_collator
+**/test_wt3184_dup_index_collator
**/test/cursor_order/cursor_order
**/test/fops/t
**/test/format/s_dumpcmp
diff --git a/SConstruct b/SConstruct
index e9e72630b11..b397f662be7 100644
--- a/SConstruct
+++ b/SConstruct
@@ -376,7 +376,6 @@ examples = [
"ex_pack",
"ex_process",
"ex_schema",
- "ex_scope",
"ex_stat",
"ex_thread",
]
diff --git a/dist/s_string.ok b/dist/s_string.ok
index e033f77327f..cdfa4aec968 100644
--- a/dist/s_string.ok
+++ b/dist/s_string.ok
@@ -63,6 +63,7 @@ CPUs
CRC
CSV
CStream
+CURFILE
CURSORs
CURSTD
CallsCustDate
diff --git a/dist/s_void b/dist/s_void
index 90425d5a718..249f043d029 100755
--- a/dist/s_void
+++ b/dist/s_void
@@ -137,7 +137,7 @@ for f in `find bench ext src test -name '*.[ci]'`; do
# form of return assignment or call.
file_parse $f |
sed -e 's/return ([^)]*); }$//' \
- -e '/[A-Z]*_API_CALL[A-Z_]*(/d' \
+ -e '/[_A-Z]*_API_CALL[_A-Z]*(/d' \
-e '/WT_CURSOR_NEEDKEY(/d' \
-e '/WT_CURSOR_NEEDVALUE(/d' \
-e '/WT_ERR[A-Z_]*(/d' \
@@ -166,7 +166,7 @@ for f in `find bench ext src test -name '*.[ci]'`; do
file_parse $f |
grep 'WT_DECL_RET' |
sed -e '/ret =/d' \
- -e '/API_END_RET/d' \
+ -e '/[_A-Z]*_API_CALL[_A-Z]*(/d' \
-e '/WT_CURSOR_NEEDKEY/d' \
-e '/WT_CURSOR_NEEDVALUE/d' \
-e '/WT_ERR/d' \
diff --git a/examples/c/Makefile.am b/examples/c/Makefile.am
index d5305eec5c8..20936661e06 100644
--- a/examples/c/Makefile.am
+++ b/examples/c/Makefile.am
@@ -20,7 +20,6 @@ noinst_PROGRAMS = \
ex_pack \
ex_process \
ex_schema \
- ex_scope \
ex_stat \
ex_sync \
ex_thread
diff --git a/examples/c/ex_scope.c b/examples/c/ex_scope.c
deleted file mode 100644
index 795ad85d57b..00000000000
--- a/examples/c/ex_scope.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/*-
- * Public Domain 2014-2016 MongoDB, Inc.
- * Public Domain 2008-2014 WiredTiger, Inc.
- *
- * This is free and unencumbered software released into the public domain.
- *
- * Anyone is free to copy, modify, publish, use, compile, sell, or
- * distribute this software, either in source code form or as a compiled
- * binary, for any purpose, commercial or non-commercial, and by any
- * means.
- *
- * In jurisdictions that recognize copyright laws, the author or authors
- * of this software dedicate any and all copyright interest in the
- * software to the public domain. We make this dedication for the benefit
- * of the public at large and to the detriment of our heirs and
- * successors. We intend this dedication to be an overt act of
- * relinquishment in perpetuity of all present and future rights to this
- * software under copyright law.
- *
- * 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 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.
- *
- * ex_scope.c
- * demonstrates the scope of buffers holding cursor keys and values.
- */
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <wiredtiger.h>
-
-#ifdef _WIN32
-/* snprintf is not supported on <= VS2013 */
-#define snprintf _snprintf
-#endif
-
-static const char *home;
-
-static int
-cursor_scope_ops(WT_CURSOR *cursor)
-{
- struct {
- const char *op;
- const char *key;
- const char *value;
- int (*apply)(WT_CURSOR *);
- } *op, ops[] = {
- { "insert", "key1", "value1", cursor->insert, },
- { "update", "key1", "value2", cursor->update, },
- { "search", "key1", "value2", cursor->search, },
- { "remove", "key1", "value2", cursor->remove, },
- { NULL, NULL, NULL, NULL }
- };
- WT_SESSION *session;
- const char *key, *value;
- char keybuf[10], valuebuf[10];
- int ret;
-
- session = cursor->session;
-
- for (op = ops; op->key != NULL; op++) {
- key = value = NULL;
-
- /*! [cursor scope operation] */
- (void)snprintf(keybuf, sizeof(keybuf), "%s", op->key);
- cursor->set_key(cursor, keybuf);
- (void)snprintf(valuebuf, sizeof(valuebuf), "%s", op->value);
- cursor->set_value(cursor, valuebuf);
-
- /*
- * The application must keep key and value memory valid until
- * the next operation that positions the cursor, modifies the
- * data, or resets or closes the cursor.
- *
- * Modifying either the key or value buffers is not permitted.
- */
-
- /* Apply the operation (insert, update, search or remove). */
- if ((ret = op->apply(cursor)) != 0) {
- fprintf(stderr,
- "%s: error performing the operation: %s\n",
- op->op, session->strerror(session, ret));
- return (ret);
- }
-
- /*
- * The cursor no longer references application memory, so
- * application buffers can be safely overwritten.
- */
- strcpy(keybuf, "no key");
- strcpy(valuebuf, "no value");
-
- /*
- * Check that get_key/value behave as expected after the
- * operation.
- */
- if (op->apply == cursor->insert) {
- /*
- * WT_CURSOR::insert no longer references application
- * memory, but as it does not position the cursor, it
- * doesn't reference memory owned by the cursor, either.
- */
- printf("ex_scope: "
- "expect two WiredTiger error messages:\n");
- if ((ret = cursor->get_key(cursor, &key)) == 0 ||
- (ret = cursor->get_value(cursor, &value)) == 0) {
- fprintf(stderr,
- "%s: error in get_key/value: %s\n",
- op->op, session->strerror(session, ret));
- return (ret);
- }
- continue;
- }
- if (op->apply == cursor->remove) {
- /*
- * WT_CURSOR::remove no longer references application
- * memory; as it does not position the cursor, it will
- * reference key memory owned by the cursor, but has no
- * value.
- */
- printf("ex_scope: "
- "expect one WiredTiger error message:\n");
- if ((ret = cursor->get_key(cursor, &key)) != 0 ||
- (ret = cursor->get_value(cursor, &value)) == 0) {
- fprintf(stderr,
- "%s: error in get_key/value: %s\n",
- op->op, session->strerror(session, ret));
- return (ret);
- }
- } else /* search, update */{
- /*
- * WT_CURSOR::search and WT_CURSOR::update no longer
- * reference application memory; as they position the
- * cursor, they will reference key/value memory owned
- * by the cursor.
- */
- if ((ret = cursor->get_key(cursor, &key)) != 0 ||
- (ret = cursor->get_value(cursor, &value)) != 0) {
- fprintf(stderr,
- "%s: error in get_key/value: %s\n",
- op->op, session->strerror(session, ret));
- return (ret);
- }
- }
-
- /*
- * Modifying the memory referenced by either key or value is
- * not permitted.
- *
- * Check that the cursor's key and value are what we expect.
- */
- if (key == keybuf ||
- (op->apply != cursor->remove && value == valuebuf)) {
- fprintf(stderr,
- "%s: cursor points at application memory!\n",
- op->op);
- return (EINVAL);
- }
-
- if (strcmp(key, op->key) != 0 ||
- (op->apply != cursor->remove &&
- strcmp(value, op->value) != 0)) {
- fprintf(stderr,
- "%s: unexpected key / value!\n", op->op);
- return (EINVAL);
- }
- /*! [cursor scope operation] */
- }
-
- return (0);
-}
-
-int
-main(void)
-{
- WT_CONNECTION *conn;
- WT_CURSOR *cursor;
- WT_SESSION *session;
- int ret;
-
- /*
- * Create a clean test directory for this run of the test program if the
- * environment variable isn't already set (as is done by make check).
- */
- if (getenv("WIREDTIGER_HOME") == NULL) {
- home = "WT_HOME";
- ret = system("rm -rf WT_HOME && mkdir WT_HOME");
- } else
- home = NULL;
-
- /* Open a connection, create a simple table, open a cursor. */
- if ((ret = wiredtiger_open(home, NULL, "create", &conn)) != 0 ||
- (ret = conn->open_session(conn, NULL, NULL, &session)) != 0) {
- fprintf(stderr, "Error connecting to %s: %s\n",
- home == NULL ? "." : home, wiredtiger_strerror(ret));
- return (EXIT_FAILURE);
- }
-
- ret = session->create(session,
- "table:scope", "key_format=S,value_format=S,columns=(k,v)");
-
- ret = session->open_cursor(session,
- "table:scope", NULL, NULL, &cursor);
-
- ret = cursor_scope_ops(cursor);
-
- /* Close the connection and clean up. */
- ret = conn->close(conn, NULL);
-
- return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
-}
diff --git a/src/btree/bt_cursor.c b/src/btree/bt_cursor.c
index 4634059589b..3ae6e022906 100644
--- a/src/btree/bt_cursor.c
+++ b/src/btree/bt_cursor.c
@@ -9,6 +9,70 @@
#include "wt_internal.h"
/*
+ * WT_CURFILE_OP_XXX
+ * If we're going to return an error, we need to restore the cursor to
+ * a valid state, the upper-level cursor code is likely to retry. The macros
+ * here are called to save and restore that state.
+ */
+#define WT_CURFILE_OP_DECL \
+ WT_ITEM __key_copy; \
+ WT_ITEM __value_copy; \
+ uint64_t __recno; \
+ uint32_t __flags
+#define WT_CURFILE_OP_PUSH do { \
+ WT_ITEM_SET(__key_copy, cursor->key); \
+ WT_ITEM_SET(__value_copy, cursor->value); \
+ __recno = cursor->recno; \
+ __flags = cursor->flags; \
+} while (0)
+#define WT_CURFILE_OP_POP do { \
+ cursor->recno = __recno; \
+ if (FLD_ISSET(__flags, WT_CURSTD_KEY_EXT)) \
+ WT_ITEM_SET(cursor->key, __key_copy); \
+ if (FLD_ISSET(__flags, WT_CURSTD_VALUE_EXT)) \
+ WT_ITEM_SET(cursor->value, __value_copy); \
+ F_CLR(cursor, WT_CURSTD_KEY_INT | WT_CURSTD_VALUE_INT); \
+ F_SET(cursor, \
+ FLD_MASK(__flags, WT_CURSTD_KEY_EXT | WT_CURSTD_VALUE_EXT));\
+} while (0)
+
+/*
+ * __cursor_page_pinned --
+ * Return if we have a page pinned and it's not been flagged for forced
+ * eviction (the forced eviction test is so we periodically release pages
+ * grown too large).
+ */
+static inline bool
+__cursor_page_pinned(WT_CURSOR_BTREE *cbt)
+{
+ return (F_ISSET(cbt, WT_CBT_ACTIVE) &&
+ cbt->ref->page->read_gen != WT_READGEN_OLDEST);
+}
+
+/*
+ * __cursor_copy_int_key --
+ * If we're pointing into the tree, save the key into local memory.
+ */
+static inline int
+__cursor_copy_int_key(WT_CURSOR *cursor)
+{
+ /*
+ * We're about to discard the cursor's position and the cursor layer
+ * might retry the operation. We discard pinned pages on error, which
+ * will invalidate pinned keys. Clear WT_CURSTD_KEY_INT in all cases,
+ * the underlying page is gone whether we can allocate memory or not.
+ */
+ if (F_ISSET(cursor, WT_CURSTD_KEY_INT)) {
+ F_CLR(cursor, WT_CURSTD_KEY_INT);
+ if (!WT_DATA_IN_ITEM(&cursor->key))
+ WT_RET(__wt_buf_set((WT_SESSION_IMPL *)cursor->session,
+ &cursor->key, cursor->key.data, cursor->key.size));
+ F_SET(cursor, WT_CURSTD_KEY_EXT);
+ }
+ return (0);
+}
+
+/*
* __cursor_size_chk --
* Return if an inserted item is too large.
*/
@@ -343,8 +407,7 @@ __wt_btcur_search(WT_CURSOR_BTREE *cbt)
* from the root.
*/
valid = false;
- if (F_ISSET(cbt, WT_CBT_ACTIVE) &&
- cbt->ref->page->read_gen != WT_READGEN_OLDEST) {
+ if (__cursor_page_pinned(cbt)) {
__wt_txn_cursor_op(session);
WT_ERR(btree->type == BTREE_ROW ?
@@ -422,9 +485,7 @@ __wt_btcur_search_near(WT_CURSOR_BTREE *cbt, int *exactp)
* existing record.
*/
valid = false;
- if (btree->type == BTREE_ROW &&
- F_ISSET(cbt, WT_CBT_ACTIVE) &&
- cbt->ref->page->read_gen != WT_READGEN_OLDEST) {
+ if (btree->type == BTREE_ROW && __cursor_page_pinned(cbt)) {
__wt_txn_cursor_op(session);
WT_ERR(__cursor_row_search(session, cbt, cbt->ref, true));
@@ -531,9 +592,18 @@ __wt_btcur_insert(WT_CURSOR_BTREE *cbt)
retry: WT_RET(__cursor_func_init(cbt, true));
- switch (btree->type) {
- case BTREE_COL_FIX:
- case BTREE_COL_VAR:
+ if (btree->type == BTREE_ROW) {
+ WT_ERR(__cursor_row_search(session, cbt, NULL, true));
+ /*
+ * If not overwriting, fail if the key exists, else insert the
+ * key/value pair.
+ */
+ if (!F_ISSET(cursor, WT_CURSTD_OVERWRITE) &&
+ cbt->compare == 0 && __wt_cursor_valid(cbt, NULL))
+ WT_ERR(WT_DUPLICATE_KEY);
+
+ ret = __cursor_row_modify(session, cbt, false);
+ } else {
/*
* If WT_CURSTD_APPEND is set, insert a new record (ignoring
* the application's record number). The real record number
@@ -558,19 +628,6 @@ retry: WT_RET(__cursor_func_init(cbt, true));
WT_ERR(__cursor_col_modify(session, cbt, false));
if (F_ISSET(cursor, WT_CURSTD_APPEND))
cbt->iface.recno = cbt->recno;
- break;
- case BTREE_ROW:
- WT_ERR(__cursor_row_search(session, cbt, NULL, true));
- /*
- * If not overwriting, fail if the key exists, else insert the
- * key/value pair.
- */
- if (!F_ISSET(cursor, WT_CURSTD_OVERWRITE) &&
- cbt->compare == 0 && __wt_cursor_valid(cbt, NULL))
- WT_ERR(WT_DUPLICATE_KEY);
-
- ret = __cursor_row_modify(session, cbt, false);
- break;
}
err: if (ret == WT_RESTART) {
@@ -578,11 +635,9 @@ err: if (ret == WT_RESTART) {
WT_STAT_DATA_INCR(session, cursor_restart);
goto retry;
}
+
/* Insert doesn't maintain a position across calls, clear resources. */
- if (ret == 0)
- WT_TRET(__curfile_leave(cbt));
- if (ret != 0)
- WT_TRET(__cursor_reset(cbt));
+ WT_TRET(__cursor_reset(cbt));
return (ret);
}
@@ -640,29 +695,24 @@ __wt_btcur_update_check(WT_CURSOR_BTREE *cbt)
retry: WT_RET(__cursor_func_init(cbt, true));
- switch (btree->type) {
- case BTREE_ROW:
+ if (btree->type == BTREE_ROW) {
WT_ERR(__cursor_row_search(session, cbt, NULL, true));
/*
* Just check for conflicts.
*/
ret = __curfile_update_check(cbt);
- break;
- case BTREE_COL_FIX:
- case BTREE_COL_VAR:
+ } else
WT_ERR(__wt_illegal_value(session, NULL));
- break;
- }
err: if (ret == WT_RESTART) {
WT_STAT_CONN_INCR(session, cursor_restart);
WT_STAT_DATA_INCR(session, cursor_restart);
goto retry;
}
- WT_TRET(__curfile_leave(cbt));
- if (ret != 0)
- WT_TRET(__cursor_reset(cbt));
+
+ /* Insert doesn't maintain a position across calls, clear resources. */
+ WT_TRET(__cursor_reset(cbt));
return (ret);
}
@@ -674,23 +724,83 @@ int
__wt_btcur_remove(WT_CURSOR_BTREE *cbt)
{
WT_BTREE *btree;
+ WT_CURFILE_OP_DECL;
WT_CURSOR *cursor;
WT_DECL_RET;
WT_SESSION_IMPL *session;
+ bool positioned;
btree = cbt->btree;
cursor = &cbt->iface;
session = (WT_SESSION_IMPL *)cursor->session;
+ WT_CURFILE_OP_PUSH;
+
WT_STAT_CONN_INCR(session, cursor_remove);
WT_STAT_DATA_INCR(session, cursor_remove);
WT_STAT_DATA_INCRV(session, cursor_remove_bytes, cursor->key.size);
-retry: WT_RET(__cursor_func_init(cbt, true));
+ /*
+ * WT_CURSOR.remove has a unique semantic, the cursor stays positioned
+ * if it starts positioned, otherwise clear the cursor on completion.
+ */
+ positioned = F_ISSET(cursor, WT_CURSTD_KEY_INT);
- switch (btree->type) {
- case BTREE_COL_FIX:
- case BTREE_COL_VAR:
+retry:
+ /*
+ * If removing with overwrite configured, and positioned to an on-page
+ * key, the update doesn't require another search. The cursor won't be
+ * positioned on a page with an external key set, but be sure.
+ */
+ if (__cursor_page_pinned(cbt) &&
+ F_ISSET_ALL(cursor, WT_CURSTD_KEY_INT | WT_CURSTD_OVERWRITE)) {
+ WT_ERR(__wt_txn_autocommit_check(session));
+
+ /*
+ * The cursor position may not be exact (the cursor's comparison
+ * value not equal to zero). Correct to an exact match so we can
+ * remove whatever we're pointing at.
+ */
+ cbt->compare = 0;
+ ret = btree->type == BTREE_ROW ?
+ __cursor_row_modify(session, cbt, true) :
+ __cursor_col_modify(session, cbt, true);
+
+ /*
+ * The pinned page goes away if we fail for any reason, make
+ * sure there's a local copy of any key. (Restart could still
+ * use the pinned page, but that's an unlikely path.) Re-save
+ * the cursor state: we may retry but eventually fail.
+ */
+ if (ret != 0) {
+ WT_TRET(__cursor_copy_int_key(cursor));
+ WT_CURFILE_OP_PUSH;
+ goto err;
+ }
+ goto done;
+ }
+
+ /*
+ * The pinned page goes away if we do a search, make sure there's a
+ * local copy of any key. Re-save the cursor state: we may retry but
+ * eventually fail.
+ */
+ WT_ERR(__cursor_copy_int_key(cursor));
+ WT_CURFILE_OP_PUSH;
+
+ WT_ERR(__cursor_func_init(cbt, true));
+
+ if (btree->type == BTREE_ROW) {
+ WT_ERR(__cursor_row_search(session, cbt, NULL, false));
+
+ /* Check whether an update would conflict. */
+ WT_ERR(__curfile_update_check(cbt));
+
+ if (cbt->compare != 0 || !__wt_cursor_valid(cbt, NULL))
+ WT_ERR(WT_NOTFOUND);
+
+ ret = __cursor_row_modify(session, cbt, true);
+ } else {
WT_ERR(__cursor_col_search(session, cbt, NULL));
/*
@@ -717,19 +827,6 @@ retry: WT_RET(__cursor_func_init(cbt, true));
cbt->recno = cursor->recno;
} else
ret = __cursor_col_modify(session, cbt, true);
- break;
- case BTREE_ROW:
- /* Remove the record if it exists. */
- WT_ERR(__cursor_row_search(session, cbt, NULL, false));
-
- /* Check whether an update would conflict. */
- WT_ERR(__curfile_update_check(cbt));
-
- if (cbt->compare != 0 || !__wt_cursor_valid(cbt, NULL))
- WT_ERR(WT_NOTFOUND);
-
- ret = __cursor_row_modify(session, cbt, true);
- break;
}
err: if (ret == WT_RESTART) {
@@ -737,15 +834,29 @@ err: if (ret == WT_RESTART) {
WT_STAT_DATA_INCR(session, cursor_restart);
goto retry;
}
+
/*
- * If the cursor is configured to overwrite and the record is not
- * found, that is exactly what we want.
+ * If the cursor is configured to overwrite and the record is not found,
+ * that is exactly what we want, return success.
*/
if (F_ISSET(cursor, WT_CURSTD_OVERWRITE) && ret == WT_NOTFOUND)
ret = 0;
- if (ret != 0)
+done: /*
+ * If the cursor was positioned, it stays positioned, point the cursor
+ * at an internal copy of the key. Otherwise, there's no position or
+ * key/value.
+ */
+ if (ret == 0)
+ F_CLR(cursor, WT_CURSTD_KEY_SET | WT_CURSTD_VALUE_SET);
+ if (ret == 0 && positioned) {
+ WT_TRET(__wt_key_return(session, cbt));
+ if (ret == 0)
+ F_SET(cursor, WT_CURSTD_KEY_INT);
+ } else
WT_TRET(__cursor_reset(cbt));
+ if (ret != 0)
+ WT_CURFILE_OP_POP;
return (ret);
}
@@ -779,9 +890,19 @@ __wt_btcur_update(WT_CURSOR_BTREE *cbt)
retry: WT_RET(__cursor_func_init(cbt, true));
- switch (btree->type) {
- case BTREE_COL_FIX:
- case BTREE_COL_VAR:
+ if (btree->type == BTREE_ROW) {
+ WT_ERR(__cursor_row_search(session, cbt, NULL, true));
+ /*
+ * If not overwriting, check for conflicts and fail if the key
+ * does not exist.
+ */
+ if (!F_ISSET(cursor, WT_CURSTD_OVERWRITE)) {
+ WT_ERR(__curfile_update_check(cbt));
+ if (cbt->compare != 0 || !__wt_cursor_valid(cbt, NULL))
+ WT_ERR(WT_NOTFOUND);
+ }
+ ret = __cursor_row_modify(session, cbt, false);
+ } else {
WT_ERR(__cursor_col_search(session, cbt, NULL));
/*
@@ -800,20 +921,6 @@ retry: WT_RET(__cursor_func_init(cbt, true));
WT_ERR(WT_NOTFOUND);
}
ret = __cursor_col_modify(session, cbt, false);
- break;
- case BTREE_ROW:
- WT_ERR(__cursor_row_search(session, cbt, NULL, true));
- /*
- * If not overwriting, check for conflicts and fail if the key
- * does not exist.
- */
- if (!F_ISSET(cursor, WT_CURSTD_OVERWRITE)) {
- WT_ERR(__curfile_update_check(cbt));
- if (cbt->compare != 0 || !__wt_cursor_valid(cbt, NULL))
- WT_ERR(WT_NOTFOUND);
- }
- ret = __cursor_row_modify(session, cbt, false);
- break;
}
err: if (ret == WT_RESTART) {
@@ -963,9 +1070,12 @@ __cursor_truncate(WT_SESSION_IMPL *session,
WT_DECL_RET;
/*
- * First, call the standard cursor remove method to do a full search and
- * re-position the cursor because we don't have a saved copy of the
- * page's write generation information, which we need to remove records.
+ * First, call the cursor search method to re-position the cursor: we
+ * may not have a cursor position (if the higher-level truncate code
+ * switched the cursors to have an "external" cursor key, and because
+ * we don't save a copy of the page's write generation information,
+ * which we need to remove records.
+ *
* Once that's done, we can delete records without a full search, unless
* we encounter a restart error because the page was modified by some
* other thread of control; in that case, repeat the full search to
@@ -978,20 +1088,31 @@ __cursor_truncate(WT_SESSION_IMPL *session,
* instantiated the end cursor, so we know that page is pinned in memory
* and we can proceed without concern.
*/
-retry: WT_RET(__wt_btcur_remove(start));
+retry: WT_RET(__wt_btcur_search(start));
+
+ /*
+ * XXX KEITH
+ * When the btree cursor code sets/clears the cursor flags (rather than
+ * the cursor layer), the set/clear goes away, only the assert remains.
+ */
+ F_CLR((WT_CURSOR *)start, WT_CURSTD_KEY_SET);
+ F_SET((WT_CURSOR *)start, WT_CURSTD_KEY_INT);
+ WT_ASSERT(session,
+ F_MASK((WT_CURSOR *)start, WT_CURSTD_KEY_SET) == WT_CURSTD_KEY_INT);
/*
* Reset ret each time through so that we don't loop forever in
* the cursor equals case.
*/
for (ret = 0;;) {
+ if ((ret = rmfunc(session, start, 1)) != 0)
+ break;
+
if (stop != NULL && __cursor_equals(start, stop))
break;
if ((ret = __wt_btcur_next(start, true)) != 0)
break;
- start->compare = 0; /* Exact match */
- if ((ret = rmfunc(session, start, 1)) != 0)
- break;
+ start->compare = 0; /* Exact match */
}
if (ret == WT_RESTART) {
@@ -1024,29 +1145,44 @@ __cursor_truncate_fix(WT_SESSION_IMPL *session,
* record 37, records 1-36 magically appear. Those records can't be
* deleted, which means we have to ignore already "deleted" records.
*
- * First, call the standard cursor remove method to do a full search and
- * re-position the cursor because we don't have a saved copy of the
- * page's write generation information, which we need to remove records.
+ * First, call the cursor search method to re-position the cursor: we
+ * may not have a cursor position (if the higher-level truncate code
+ * switched the cursors to have an "external" cursor key, and because
+ * we don't save a copy of the page's write generation information,
+ * which we need to remove records.
+ *
* Once that's done, we can delete records without a full search, unless
* we encounter a restart error because the page was modified by some
* other thread of control; in that case, repeat the full search to
* refresh the page's modification information.
*/
-retry: WT_RET(__wt_btcur_remove(start));
+retry: WT_RET(__wt_btcur_search(start));
+
+ /*
+ * XXX KEITH
+ * When the btree cursor code sets/clears the cursor flags (rather than
+ * the cursor layer), the set/clear goes away, only the assert remains.
+ */
+ F_CLR((WT_CURSOR *)start, WT_CURSTD_KEY_SET);
+ F_SET((WT_CURSOR *)start, WT_CURSTD_KEY_INT);
+ WT_ASSERT(session,
+ F_MASK((WT_CURSOR *)start, WT_CURSTD_KEY_SET) == WT_CURSTD_KEY_INT);
+
/*
* Reset ret each time through so that we don't loop forever in
* the cursor equals case.
*/
for (ret = 0;;) {
+ value = (const uint8_t *)start->iface.value.data;
+ if (*value != 0 &&
+ (ret = rmfunc(session, start, 1)) != 0)
+ break;
+
if (stop != NULL && __cursor_equals(start, stop))
break;
if ((ret = __wt_btcur_next(start, true)) != 0)
break;
start->compare = 0; /* Exact match */
- value = (const uint8_t *)start->iface.value.data;
- if (*value != 0 &&
- (ret = rmfunc(session, start, 1)) != 0)
- break;
}
if (ret == WT_RESTART) {
@@ -1166,7 +1302,7 @@ __wt_btcur_close(WT_CURSOR_BTREE *cbt, bool lowlevel)
* Skip the usual cursor tear-down in that case.
*/
if (!lowlevel)
- ret = __curfile_leave(cbt);
+ ret = __cursor_reset(cbt);
__wt_buf_free(session, &cbt->_row_key);
__wt_buf_free(session, &cbt->_tmp);
diff --git a/src/btree/bt_ret.c b/src/btree/bt_ret.c
index 6409a1a180c..9fc457e2297 100644
--- a/src/btree/bt_ret.c
+++ b/src/btree/bt_ret.c
@@ -9,64 +9,21 @@
#include "wt_internal.h"
/*
- * __wt_kv_return --
- * Return a page referenced key/value pair to the application.
+ * __key_return --
+ * Change the cursor to reference an internal return key.
*/
-int
-__wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
+static inline int
+__key_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt)
{
- WT_BTREE *btree;
- WT_CELL *cell;
- WT_CELL_UNPACK unpack;
WT_CURSOR *cursor;
WT_ITEM *tmp;
WT_PAGE *page;
WT_ROW *rip;
- uint8_t v;
-
- btree = S2BT(session);
page = cbt->ref->page;
cursor = &cbt->iface;
- switch (page->type) {
- case WT_PAGE_COL_FIX:
- /*
- * The interface cursor's record has usually been set, but that
- * isn't universally true, specifically, cursor.search_near may
- * call here without first setting the interface cursor.
- */
- cursor->recno = cbt->recno;
-
- /* If the cursor references a WT_UPDATE item, return it. */
- if (upd != NULL) {
- cursor->value.data = WT_UPDATE_DATA(upd);
- cursor->value.size = upd->size;
- return (0);
- }
-
- /* Take the value from the original page. */
- v = __bit_getv_recno(cbt->ref, cursor->recno, btree->bitcnt);
- return (__wt_buf_set(session, &cursor->value, &v, 1));
- case WT_PAGE_COL_VAR:
- /*
- * The interface cursor's record has usually been set, but that
- * isn't universally true, specifically, cursor.search_near may
- * call here without first setting the interface cursor.
- */
- cursor->recno = cbt->recno;
-
- /* If the cursor references a WT_UPDATE item, return it. */
- if (upd != NULL) {
- cursor->value.data = WT_UPDATE_DATA(upd);
- cursor->value.size = upd->size;
- return (0);
- }
-
- /* Take the value from the original page cell. */
- cell = WT_COL_PTR(page, &page->pg_var[cbt->slot]);
- break;
- case WT_PAGE_ROW_LEAF:
+ if (page->type == WT_PAGE_ROW_LEAF) {
rip = &page->pg_row[cbt->slot];
/*
@@ -79,7 +36,10 @@ __wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
if (cbt->ins != NULL) {
cursor->key.data = WT_INSERT_KEY(cbt->ins);
cursor->key.size = WT_INSERT_KEY_SIZE(cbt->ins);
- } else if (cbt->compare == 0) {
+ return (0);
+ }
+
+ if (cbt->compare == 0) {
/*
* If not in an insert list and there's an exact match,
* the row-store search function built the key we want
@@ -97,16 +57,51 @@ __wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
cursor->key.data = cbt->row_key->data;
cursor->key.size = cbt->row_key->size;
- } else
- WT_RET(__wt_row_leaf_key(
- session, page, rip, &cursor->key, false));
-
- /* If the cursor references a WT_UPDATE item, return it. */
- if (upd != NULL) {
- cursor->value.data = WT_UPDATE_DATA(upd);
- cursor->value.size = upd->size;
return (0);
}
+ return (__wt_row_leaf_key(
+ session, page, rip, &cursor->key, false));
+ }
+
+ /*
+ * WT_PAGE_COL_FIX, WT_PAGE_COL_VAR:
+ * The interface cursor's record has usually been set, but that
+ * isn't universally true, specifically, cursor.search_near may call
+ * here without first setting the interface cursor.
+ */
+ cursor->recno = cbt->recno;
+ return (0);
+}
+
+/*
+ * __value_return --
+ * Change the cursor to reference an internal return value.
+ */
+static inline int
+__value_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
+{
+ WT_BTREE *btree;
+ WT_CELL *cell;
+ WT_CELL_UNPACK unpack;
+ WT_CURSOR *cursor;
+ WT_PAGE *page;
+ WT_ROW *rip;
+ uint8_t v;
+
+ btree = S2BT(session);
+
+ page = cbt->ref->page;
+ cursor = &cbt->iface;
+
+ /* If the cursor references a WT_UPDATE item, return it. */
+ if (upd != NULL) {
+ cursor->value.data = WT_UPDATE_DATA(upd);
+ cursor->value.size = upd->size;
+ return (0);
+ }
+
+ if (page->type == WT_PAGE_ROW_LEAF) {
+ rip = &page->pg_row[cbt->slot];
/* Simple values have their location encoded in the WT_ROW. */
if (__wt_row_leaf_value(page, rip, &cursor->value))
@@ -121,13 +116,46 @@ __wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
cursor->value.size = 0;
return (0);
}
- break;
- WT_ILLEGAL_VALUE(session);
+ __wt_cell_unpack(cell, &unpack);
+ return (__wt_page_cell_data_ref(
+ session, page, &unpack, &cursor->value));
+
+ }
+
+ if (page->type == WT_PAGE_COL_VAR) {
+ /* Take the value from the original page cell. */
+ cell = WT_COL_PTR(page, &page->pg_var[cbt->slot]);
+ __wt_cell_unpack(cell, &unpack);
+ return (__wt_page_cell_data_ref(
+ session, page, &unpack, &cursor->value));
}
- /* The value is an on-page cell, unpack and expand it as necessary. */
- __wt_cell_unpack(cell, &unpack);
- WT_RET(__wt_page_cell_data_ref(session, page, &unpack, &cursor->value));
+ /* WT_PAGE_COL_FIX: Take the value from the original page. */
+ v = __bit_getv_recno(cbt->ref, cursor->recno, btree->bitcnt);
+ return (__wt_buf_set(session, &cursor->value, &v, 1));
+}
+
+/*
+ * __wt_key_return --
+ * Change the cursor to reference an internal return key.
+ */
+int
+__wt_key_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt)
+{
+ WT_RET(__key_return(session, cbt));
+
+ return (0);
+}
+
+/*
+ * __wt_kv_return --
+ * Return a page referenced key/value pair to the application.
+ */
+int
+__wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
+{
+ WT_RET(__wt_key_return(session, cbt));
+ WT_RET(__value_return(session, cbt, upd));
return (0);
}
diff --git a/src/cursor/cur_file.c b/src/cursor/cur_file.c
index 0ec917fbf95..274dc1e8f62 100644
--- a/src/cursor/cur_file.c
+++ b/src/cursor/cur_file.c
@@ -325,24 +325,21 @@ __curfile_remove(WT_CURSOR *cursor)
cbt = (WT_CURSOR_BTREE *)cursor;
CURSOR_REMOVE_API_CALL(cursor, session, cbt->btree);
- WT_CURSOR_NEEDKEY(cursor);
+ WT_CURSOR_CHECKKEY(cursor);
WT_CURSOR_NOVALUE(cursor);
- WT_BTREE_CURSOR_SAVE_AND_RESTORE(cursor, __wt_btcur_remove(cbt), ret);
+ WT_ERR(__wt_btcur_remove(cbt));
/*
- * After a successful remove, copy the key: the value is not available.
+ * Remove with a search-key is fire-and-forget, no position and no key.
+ * Remove starting from a position maintains the position and a key.
+ * We don't know which it was at this layer, so can only assert the key
+ * is not set at all, or internal. There's never a value.
*/
- if (ret == 0) {
- if (F_ISSET(cursor, WT_CURSTD_KEY_INT) &&
- !WT_DATA_IN_ITEM(&(cursor)->key)) {
- WT_ERR(__wt_buf_set(session, &cursor->key,
- cursor->key.data, cursor->key.size));
- F_CLR(cursor, WT_CURSTD_KEY_INT);
- F_SET(cursor, WT_CURSTD_KEY_EXT);
- }
- F_CLR(cursor, WT_CURSTD_VALUE_SET);
- }
+ WT_ASSERT(session,
+ F_MASK(cursor, WT_CURSTD_KEY_SET) == 0 ||
+ F_MASK(cursor, WT_CURSTD_KEY_SET) == WT_CURSTD_KEY_INT);
+ WT_ASSERT(session, F_MASK(cursor, WT_CURSTD_VALUE_SET) == 0);
err: CURSOR_UPDATE_API_END(session, ret);
return (ret);
diff --git a/src/cursor/cur_table.c b/src/cursor/cur_table.c
index 7e8cd153d2d..98dbbec8981 100644
--- a/src/cursor/cur_table.c
+++ b/src/cursor/cur_table.c
@@ -511,9 +511,16 @@ __curtable_insert(WT_CURSOR *cursor)
*/
F_SET(primary, flag_orig | WT_CURSTD_KEY_EXT | WT_CURSTD_VALUE_EXT);
- if (ret == WT_DUPLICATE_KEY && F_ISSET(cursor, WT_CURSTD_OVERWRITE))
+ if (ret == WT_DUPLICATE_KEY && F_ISSET(cursor, WT_CURSTD_OVERWRITE)) {
WT_ERR(__curtable_update(cursor));
- else {
+
+ /*
+ * The cursor is no longer positioned. This isn't just cosmetic,
+ * without a reset, iteration on this cursor won't start at the
+ * beginning/end of the table.
+ */
+ APPLY_CG(ctable, reset);
+ } else {
WT_ERR(ret);
for (i = 1; i < WT_COLGROUPS(ctable->table); i++, cp++) {
@@ -601,14 +608,20 @@ err: CURSOR_UPDATE_API_END(session, ret);
static int
__curtable_remove(WT_CURSOR *cursor)
{
+ WT_CURSOR *primary;
WT_CURSOR_TABLE *ctable;
WT_DECL_RET;
WT_SESSION_IMPL *session;
+ bool positioned;
ctable = (WT_CURSOR_TABLE *)cursor;
JOINABLE_CURSOR_REMOVE_API_CALL(cursor, session, NULL);
WT_ERR(__curtable_open_indices(ctable));
+ /* Check if the cursor was positioned. */
+ primary = *ctable->cg_cursors;
+ positioned = F_ISSET(primary, WT_CURSTD_KEY_INT);
+
/* Find the old record so it can be removed from indices */
if (ctable->table->nindices > 0) {
APPLY_CG(ctable, search);
@@ -617,6 +630,19 @@ __curtable_remove(WT_CURSOR *cursor)
}
APPLY_CG(ctable, remove);
+ WT_ERR(ret);
+
+ /*
+ * If the cursor was positioned, it stays positioned with a key but no
+ * no value, otherwise, there's no position, key or value. This isn't
+ * just cosmetic, without a reset, iteration on this cursor won't start
+ * at the beginning/end of the table.
+ */
+ F_CLR(primary, WT_CURSTD_KEY_SET | WT_CURSTD_VALUE_SET);
+ if (positioned)
+ F_SET(primary, WT_CURSTD_KEY_INT);
+ else
+ APPLY_CG(ctable, reset);
err: CURSOR_UPDATE_API_END(session, ret);
return (ret);
diff --git a/src/docs/cursor-ops.dox b/src/docs/cursor-ops.dox
index b743d81db57..e479ff29191 100644
--- a/src/docs/cursor-ops.dox
+++ b/src/docs/cursor-ops.dox
@@ -145,9 +145,5 @@ that may not be modified or freed by the application. If a longer scope
is required, the application must make a copy of the memory before the
cursor is re-used, closed or reset.
-The comments in this example code explain when the application can safely
-modify memory passed to WT_CURSOR::set_key or WT_CURSOR::set_value:
-
-@snippet ex_scope.c cursor scope operation
@m_endif
*/
diff --git a/src/docs/upgrading.dox b/src/docs/upgrading.dox
index 2894db0c126..e5fce3d0d5d 100644
--- a/src/docs/upgrading.dox
+++ b/src/docs/upgrading.dox
@@ -2,28 +2,45 @@
@section version_292 Upgrading to Version 2.9.2
<dl>
-<dt>WiredTiger Utility now supports truncate</dt>
+
+<dt>WiredTiger utility now supports truncate</dt>
<dd>
-The WiredTiger Utility can now \c truncate an object. Removing all contents
-from the specified object.
+The WiredTiger utility \c wt can now \c truncate objects, removing all
+contents from the specified object.
</dd>
+
<dt>Handle list lock statistics</dt>
<dd>
In the 2.9.1 release we added statistics tracking handle list lock timing, we
have switched that lock from a spin lock to a read-write lock, and consequently
changed the statistics tracking lock related wait time.
</dd>
+
<dt>Forced and named checkpoint error conditions changed</dt>
<dd>
There are new cases where checkpoints created with an explicit name or the
"force" configuration option can return an EBUSY error. This can happen if
the checkpoint overlaps with other schema operations, for example table create.
</dd>
-</dl>
-@section version_291 Upgrading to Version 2.9.1
+<dt>WT_CURSOR::remove may not return a positioned cursor</dt>
+<dd>
+The WT_CURSOR::remove method was previously documented to always return a
+positioned cursor on success, which is not possible when \c overwrite=true
+and the record does not exist.
+
+The documentation has been updated, and the method has been changed to
+never return a cursor position unless called with an existing cursor
+position. In other words, if the cursor is positioned and the
+WT_CURSOR::remove is called, the cursor will remain positioned; if the
+cursor is not positioned and the WT_CURSOR::remove method is called, the
+cursor will not be positioned on return.
+</dd>
+</dl><hr>
+@section version_291 Upgrading to Version 2.9.1
<dl>
+
<dt>Changes to hazard pointer configuration</dt>
<dd>
The \c hazard_max parameter to ::wiredtiger_open is now ignored. Memory is
@@ -39,10 +56,11 @@ have added a new \c access_pattern_hint configuration option available for
WT_SESSION::create that can be used to restore the old default by setting
the value to "random".
</dd>
-</dl>
+</dl><hr>
@section version_290 Upgrading to Version 2.9.0
<dl>
+
<dt>Changes to cursor behavior after WT_CURSOR::insert</dt>
<dd>
After a successful call to WT_CURSOR::insert, unless a cursor has record
diff --git a/src/include/api.h b/src/include/api.h
index 2783d17f825..1fa777ed5cc 100644
--- a/src/include/api.h
+++ b/src/include/api.h
@@ -7,22 +7,21 @@
*/
/* Standard entry points to the API: declares/initializes local variables. */
-#define API_SESSION_INIT(s, h, n, cur, dh) \
+#define API_SESSION_INIT(s, h, n, dh) \
WT_DATA_HANDLE *__olddh = (s)->dhandle; \
const char *__oldname = (s)->name; \
- (s)->cursor = (cur); \
(s)->dhandle = (dh); \
(s)->name = (s)->lastop = #h "." #n; \
-#define API_CALL_NOCONF(s, h, n, cur, dh) do { \
- API_SESSION_INIT(s, h, n, cur, dh); \
+#define API_CALL_NOCONF(s, h, n, dh) do { \
+ API_SESSION_INIT(s, h, n, dh); \
WT_ERR(WT_SESSION_CHECK_PANIC(s)); \
__wt_verbose((s), WT_VERB_API, "CALL: " #h ":" #n)
-#define API_CALL(s, h, n, cur, dh, config, cfg) do { \
+#define API_CALL(s, h, n, dh, config, cfg) do { \
const char *cfg[] = \
{ WT_CONFIG_BASE(s, h##_##n), config, NULL }; \
- API_SESSION_INIT(s, h, n, cur, dh); \
+ API_SESSION_INIT(s, h, n, dh); \
WT_ERR(WT_SESSION_CHECK_PANIC(s)); \
if ((config) != NULL) \
WT_ERR(__wt_config_check((s), \
@@ -42,17 +41,17 @@
} while (0)
/* An API call wrapped in a transaction if necessary. */
-#define TXN_API_CALL(s, h, n, cur, bt, config, cfg) do { \
+#define TXN_API_CALL(s, h, n, bt, config, cfg) do { \
bool __autotxn = false; \
- API_CALL(s, h, n, bt, cur, config, cfg); \
+ API_CALL(s, h, n, bt, config, cfg); \
__autotxn = !F_ISSET(&(s)->txn, WT_TXN_AUTOCOMMIT | WT_TXN_RUNNING);\
if (__autotxn) \
F_SET(&(s)->txn, WT_TXN_AUTOCOMMIT)
/* An API call wrapped in a transaction if necessary. */
-#define TXN_API_CALL_NOCONF(s, h, n, cur, bt) do { \
+#define TXN_API_CALL_NOCONF(s, h, n, bt) do { \
bool __autotxn = false; \
- API_CALL_NOCONF(s, h, n, cur, bt); \
+ API_CALL_NOCONF(s, h, n, bt); \
__autotxn = !F_ISSET(&(s)->txn, WT_TXN_AUTOCOMMIT | WT_TXN_RUNNING);\
if (__autotxn) \
F_SET(&(s)->txn, WT_TXN_AUTOCOMMIT)
@@ -98,24 +97,24 @@
#define CONNECTION_API_CALL(conn, s, n, config, cfg) \
s = (conn)->default_session; \
- API_CALL(s, WT_CONNECTION, n, NULL, NULL, config, cfg)
+ API_CALL(s, WT_CONNECTION, n, NULL, config, cfg)
#define CONNECTION_API_CALL_NOCONF(conn, s, n) \
s = (conn)->default_session; \
- API_CALL_NOCONF(s, WT_CONNECTION, n, NULL, NULL)
+ API_CALL_NOCONF(s, WT_CONNECTION, n, NULL)
#define SESSION_API_CALL(s, n, config, cfg) \
- API_CALL(s, WT_SESSION, n, NULL, NULL, config, cfg)
+ API_CALL(s, WT_SESSION, n, NULL, config, cfg)
#define SESSION_API_CALL_NOCONF(s, n) \
- API_CALL_NOCONF(s, WT_SESSION, n, NULL, NULL)
+ API_CALL_NOCONF(s, WT_SESSION, n, NULL)
#define SESSION_TXN_API_CALL(s, n, config, cfg) \
- TXN_API_CALL(s, WT_SESSION, n, NULL, NULL, config, cfg)
+ TXN_API_CALL(s, WT_SESSION, n, NULL, config, cfg)
#define CURSOR_API_CALL(cur, s, n, bt) \
(s) = (WT_SESSION_IMPL *)(cur)->session; \
- API_CALL_NOCONF(s, WT_CURSOR, n, cur, \
+ API_CALL_NOCONF(s, WT_CURSOR, n, \
((bt) == NULL) ? NULL : ((WT_BTREE *)(bt))->dhandle)
#define JOINABLE_CURSOR_CALL_CHECK(cur) \
@@ -128,7 +127,7 @@
#define CURSOR_REMOVE_API_CALL(cur, s, bt) \
(s) = (WT_SESSION_IMPL *)(cur)->session; \
- TXN_API_CALL_NOCONF(s, WT_CURSOR, remove, cur, \
+ TXN_API_CALL_NOCONF(s, WT_CURSOR, remove, \
((bt) == NULL) ? NULL : ((WT_BTREE *)(bt))->dhandle);
#define JOINABLE_CURSOR_REMOVE_API_CALL(cur, s, bt) \
@@ -137,7 +136,7 @@
#define CURSOR_UPDATE_API_CALL(cur, s, n, bt) \
(s) = (WT_SESSION_IMPL *)(cur)->session; \
- TXN_API_CALL_NOCONF(s, WT_CURSOR, n, cur, \
+ TXN_API_CALL_NOCONF(s, WT_CURSOR, n, \
((bt) == NULL) ? NULL : ((WT_BTREE *)(bt))->dhandle); \
if (F_ISSET(S2C(s), WT_CONN_IN_MEMORY) && \
!F_ISSET((WT_BTREE *)(bt), WT_BTREE_IGNORE_CACHE) && \
@@ -153,4 +152,4 @@
#define ASYNCOP_API_CALL(conn, s, n) \
s = (conn)->default_session; \
- API_CALL_NOCONF(s, asyncop, n, NULL, NULL)
+ API_CALL_NOCONF(s, asyncop, n, NULL)
diff --git a/src/include/buf.i b/src/include/buf.i
index ebbee6b4633..d192e292dcf 100644
--- a/src/include/buf.i
+++ b/src/include/buf.i
@@ -37,28 +37,30 @@ __wt_buf_extend(WT_SESSION_IMPL *session, WT_ITEM *buf, size_t size)
/*
* __wt_buf_init --
- * Initialize a buffer at a specific size.
+ * Create an empty buffer at a specific size.
*/
static inline int
__wt_buf_init(WT_SESSION_IMPL *session, WT_ITEM *buf, size_t size)
{
+ /*
+ * The buffer grow function does what we need, but anticipates data
+ * referenced by the buffer. Avoid any data copy by setting data to
+ * reference the buffer's allocated memory, and clearing it.
+ */
buf->data = buf->mem;
- buf->size = 0; /* Clear existing data length */
- WT_RET(__wt_buf_grow(session, buf, size));
-
- return (0);
+ buf->size = 0;
+ return (__wt_buf_grow(session, buf, size));
}
/*
* __wt_buf_initsize --
- * Initialize a buffer at a specific size, and set the data length.
+ * Create an empty buffer at a specific size, and set the data length.
*/
static inline int
__wt_buf_initsize(WT_SESSION_IMPL *session, WT_ITEM *buf, size_t size)
{
- buf->data = buf->mem;
- buf->size = 0; /* Clear existing data length */
- WT_RET(__wt_buf_grow(session, buf, size));
+ WT_RET(__wt_buf_init(session, buf, size));
+
buf->size = size; /* Set the data length. */
return (0);
@@ -72,14 +74,15 @@ static inline int
__wt_buf_set(
WT_SESSION_IMPL *session, WT_ITEM *buf, const void *data, size_t size)
{
- /* Ensure the buffer is large enough. */
- WT_RET(__wt_buf_initsize(session, buf, size));
-
- /* Copy the data, allowing for overlapping strings. */
- if (size != 0)
- memmove(buf->mem, data, size);
-
- return (0);
+ /*
+ * The buffer grow function does what we need, but expects the data to
+ * be referenced by the buffer. If we're copying data from outside the
+ * buffer, set it up so it makes sense to the buffer grow function. (No
+ * test needed, this works if WT_ITEM.data is already set to "data".)
+ */
+ buf->data = data;
+ buf->size = size;
+ return (__wt_buf_grow(session, buf, size));
}
/*
diff --git a/src/include/cursor.i b/src/include/cursor.i
index c3fcef9a13d..9cb9f5e7189 100644
--- a/src/include/cursor.i
+++ b/src/include/cursor.i
@@ -93,17 +93,19 @@ __curfile_enter(WT_CURSOR_BTREE *cbt)
}
/*
- * __curfile_leave --
- * Clear a file cursor's position.
+ * __cursor_reset --
+ * Reset the cursor, it no longer holds any position.
*/
static inline int
-__curfile_leave(WT_CURSOR_BTREE *cbt)
+__cursor_reset(WT_CURSOR_BTREE *cbt)
{
WT_DECL_RET;
WT_SESSION_IMPL *session;
session = (WT_SESSION_IMPL *)cbt->iface.session;
+ __cursor_pos_clear(cbt);
+
/* If the cursor was active, deactivate it. */
if (F_ISSET(cbt, WT_CBT_ACTIVE)) {
if (!F_ISSET(cbt, WT_CBT_NO_TXN))
@@ -111,12 +113,15 @@ __curfile_leave(WT_CURSOR_BTREE *cbt)
F_CLR(cbt, WT_CBT_ACTIVE);
}
+ /* If we're not holding a cursor reference, we're done. */
+ if (cbt->ref == NULL)
+ return (0);
+
/*
* If we were scanning and saw a lot of deleted records on this page,
* try to evict the page when we release it.
*/
- if (cbt->ref != NULL &&
- cbt->page_deleted_count > WT_BTREE_DELETE_THRESHOLD)
+ if (cbt->page_deleted_count > WT_BTREE_DELETE_THRESHOLD)
__wt_page_evict_soon(session, cbt->ref);
cbt->page_deleted_count = 0;
@@ -247,7 +252,7 @@ __cursor_func_init(WT_CURSOR_BTREE *cbt, bool reenter)
#ifdef HAVE_DIAGNOSTIC
__wt_cursor_key_order_reset(cbt);
#endif
- WT_RET(__curfile_leave(cbt));
+ WT_RET(__cursor_reset(cbt));
}
/*
@@ -272,24 +277,6 @@ __cursor_func_init(WT_CURSOR_BTREE *cbt, bool reenter)
}
/*
- * __cursor_reset --
- * Reset the cursor.
- */
-static inline int
-__cursor_reset(WT_CURSOR_BTREE *cbt)
-{
- WT_DECL_RET;
-
- /*
- * The cursor is leaving the API, and no longer holds any position,
- * generally called to clean up the cursor after an error.
- */
- ret = __curfile_leave(cbt);
- __cursor_pos_clear(cbt);
- return (ret);
-}
-
-/*
* __cursor_row_slot_return --
* Return a row-store leaf page slot's K/V pair.
*/
diff --git a/src/include/error.h b/src/include/error.h
index bbb7f989332..c338acb370f 100644
--- a/src/include/error.h
+++ b/src/include/error.h
@@ -67,14 +67,16 @@
int __ret; \
if ((__ret = (a)) != 0 && \
(__ret == WT_PANIC || \
- ret == 0 || ret == WT_DUPLICATE_KEY || ret == WT_NOTFOUND)) \
+ ret == 0 || ret == WT_DUPLICATE_KEY || \
+ ret == WT_NOTFOUND || ret == WT_RESTART)) \
ret = __ret; \
} while (0)
#define WT_TRET_ERROR_OK(a, e) do { \
int __ret; \
if ((__ret = (a)) != 0 && __ret != (e) && \
(__ret == WT_PANIC || \
- ret == 0 || ret == WT_DUPLICATE_KEY || ret == WT_NOTFOUND)) \
+ ret == 0 || ret == WT_DUPLICATE_KEY || \
+ ret == WT_NOTFOUND || ret == WT_RESTART)) \
ret = __ret; \
} while (0)
#define WT_TRET_NOTFOUND_OK(a) WT_TRET_ERROR_OK(a, WT_NOTFOUND)
diff --git a/src/include/extern.h b/src/include/extern.h
index db718966426..c0aa21b7f4c 100644
--- a/src/include/extern.h
+++ b/src/include/extern.h
@@ -161,6 +161,7 @@ __wt_page_in_func(WT_SESSION_IMPL *session, WT_REF *ref, uint32_t flags
#endif
) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
extern int __wt_bt_rebalance(WT_SESSION_IMPL *session, const char *cfg[]) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
+extern int __wt_key_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
extern int __wt_kv_return(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
extern int __wt_bt_salvage(WT_SESSION_IMPL *session, WT_CKPT *ckptbase, const char *cfg[]) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
extern bool __wt_split_obsolete(WT_SESSION_IMPL *session, uint64_t split_gen) WT_GCC_FUNC_DECL_ATTRIBUTE((visibility("hidden")));
diff --git a/src/include/misc.h b/src/include/misc.h
index 66d43496e93..7aba397e173 100644
--- a/src/include/misc.h
+++ b/src/include/misc.h
@@ -140,6 +140,7 @@
#define F_CLR(p, mask) FLD_CLR((p)->flags, mask)
#define F_ISSET(p, mask) FLD_ISSET((p)->flags, mask)
+#define F_ISSET_ALL(p, mask) (FLD_MASK((p)->flags, mask) == (mask))
#define F_MASK(p, mask) FLD_MASK((p)->flags, mask)
#define F_SET(p, mask) FLD_SET((p)->flags, mask)
diff --git a/src/include/session.h b/src/include/session.h
index f3092dc3c6c..dec97cff5d3 100644
--- a/src/include/session.h
+++ b/src/include/session.h
@@ -67,7 +67,6 @@ struct __wt_session_impl {
TAILQ_HEAD(__dhandles, __wt_data_handle_cache) dhandles;
time_t last_sweep; /* Last sweep for dead handles */
- WT_CURSOR *cursor; /* Current cursor */
/* Cursors closed with the session */
TAILQ_HEAD(__cursors, __wt_cursor) cursors;
diff --git a/src/include/wiredtiger.in b/src/include/wiredtiger.in
index c148e759299..5dd9a720e31 100644
--- a/src/include/wiredtiger.in
+++ b/src/include/wiredtiger.in
@@ -427,7 +427,7 @@ struct __wt_cursor {
*
* @param cursor the cursor handle
* @errors
- * In particular, if \c overwrite is not configured and a record with
+ * In particular, if \c overwrite=false is configured and a record with
* the specified key already exists, ::WT_DUPLICATE_KEY is returned.
* Also, if \c in_memory is configured for the database and the insert
* requires more than the configured cache size to complete,
@@ -452,7 +452,9 @@ struct __wt_cursor {
*
* On success, the cursor ends positioned at the modified record; to
* minimize cursor resources, the WT_CURSOR::reset method should be
- * called as soon as the cursor no longer needs that position.
+ * called as soon as the cursor no longer needs that position. (The
+ * WT_CURSOR::insert method never keeps a cursor position and may be
+ * more efficient for that reason.)
*
* The maximum length of a single column stored in a table is not fixed
* (as it partially depends on the underlying file configuration), but
@@ -460,7 +462,7 @@ struct __wt_cursor {
*
* @param cursor the cursor handle
* @errors
- * In particular, if \c overwrite is not configured and no record with
+ * In particular, if \c overwrite=false is configured and no record with
* the specified key exists, ::WT_NOTFOUND is returned.
* Also, if \c in_memory is configured for the database and the insert
* requires more than the configured cache size to complete,
@@ -477,8 +479,18 @@ struct __wt_cursor {
*
* @snippet ex_all.c Remove a record
*
- * If the cursor was not configured with "overwrite=true", the key must
- * be set and the key's record must exist; the record will be removed.
+ * If the cursor was configured with "overwrite=false" (not the
+ * default), the key must be set and the key's record must exist; the
+ * record will be removed.
+ *
+ * Any cursor position does not change: if the cursor was positioned
+ * before the WT_CURSOR::remove call, the cursor remains positioned
+ * at the removed record; to minimize cursor resources, the
+ * WT_CURSOR::reset method should be called as soon as the cursor no
+ * longer needs that position. If the cursor was not positioned before
+ * the WT_CURSOR::remove call, the cursor ends with no position, and a
+ * subsequent call to the WT_CURSOR::next (WT_CURSOR::prev) method will
+ * iterate from the beginning (end) of the table.
*
* @snippet ex_all.c Remove a record and fail if DNE
*
@@ -486,14 +498,10 @@ struct __wt_cursor {
* (that is, a store with an 'r' type key and 't' type value) is
* identical to setting the record's value to 0.
*
- * On success, the cursor ends positioned at the removed record; to
- * minimize cursor resources, the WT_CURSOR::reset method should be
- * called as soon as the cursor no longer needs that position.
- *
* @param cursor the cursor handle
* @errors
- * In particular, if \c overwrite is not configured and no record with
- * the specified key exists, ::WT_NOTFOUND is returned.
+ * In particular, if \c overwrite=false is configured and no record
+ * with the specified key exists, ::WT_NOTFOUND is returned.
*/
int __F(remove)(WT_CURSOR *cursor);
/*! @} */
diff --git a/src/lsm/lsm_cursor.c b/src/lsm/lsm_cursor.c
index 116740f8f0c..77fa96ebdfd 100644
--- a/src/lsm/lsm_cursor.c
+++ b/src/lsm/lsm_cursor.c
@@ -1565,12 +1565,23 @@ __clsm_update(WT_CURSOR *cursor)
WT_CURSOR_NEEDVALUE(cursor);
WT_ERR(__clsm_enter(clsm, false, true));
- if (F_ISSET(cursor, WT_CURSTD_OVERWRITE) ||
- (ret = __clsm_lookup(clsm, &value)) == 0) {
- WT_ERR(__clsm_deleted_encode(
- session, &cursor->value, &value, &buf));
- ret = __clsm_put(session, clsm, &cursor->key, &value, true);
- }
+ if (!F_ISSET(cursor, WT_CURSTD_OVERWRITE))
+ WT_ERR(__clsm_lookup(clsm, &value));
+ WT_ERR(__clsm_deleted_encode(session, &cursor->value, &value, &buf));
+ WT_ERR(__clsm_put(session, clsm, &cursor->key, &value, true));
+
+ /*
+ * Set the cursor to reference the internal key/value of the positioned
+ * cursor.
+ */
+ F_CLR(cursor, WT_CURSTD_KEY_SET | WT_CURSTD_VALUE_SET);
+ WT_ITEM_SET(cursor->key, clsm->current->key);
+ WT_ITEM_SET(cursor->value, clsm->current->value);
+ WT_ASSERT(session,
+ F_MASK(clsm->current, WT_CURSTD_KEY_SET) == WT_CURSTD_KEY_INT);
+ WT_ASSERT(session,
+ F_MASK(clsm->current, WT_CURSTD_VALUE_SET) == WT_CURSTD_VALUE_INT);
+ F_SET(cursor, WT_CURSTD_KEY_INT | WT_CURSTD_VALUE_INT);
err: __wt_scr_free(session, &buf);
__clsm_leave(clsm);
@@ -1589,9 +1600,13 @@ __clsm_remove(WT_CURSOR *cursor)
WT_DECL_RET;
WT_ITEM value;
WT_SESSION_IMPL *session;
+ bool positioned;
clsm = (WT_CURSOR_LSM *)cursor;
+ /* Check if the cursor is positioned. */
+ positioned = F_ISSET(cursor, WT_CURSTD_KEY_INT);
+
CURSOR_REMOVE_API_CALL(cursor, session, NULL);
WT_CURSOR_NEEDKEY(cursor);
WT_CURSOR_NOVALUE(cursor);
@@ -1600,9 +1615,22 @@ __clsm_remove(WT_CURSOR *cursor)
if (F_ISSET(cursor, WT_CURSTD_OVERWRITE) ||
(ret = __clsm_lookup(clsm, &value)) == 0)
ret = __clsm_put(
- session, clsm, &cursor->key, &__tombstone, true);
+ session, clsm, &cursor->key, &__tombstone, positioned);
err: __clsm_leave(clsm);
+
+ /*
+ * If the cursor was positioned, it stays positioned with a key but no
+ * no value, otherwise, there's no position, key or value. This isn't
+ * just cosmetic, without a reset, iteration on this cursor won't start
+ * at the beginning/end of the table.
+ */
+ F_CLR(cursor, WT_CURSTD_KEY_SET | WT_CURSTD_VALUE_SET);
+ if (positioned)
+ F_SET(cursor, WT_CURSTD_KEY_INT);
+ else
+ WT_TRET(cursor->reset(cursor));
+
CURSOR_UPDATE_API_END(session, ret);
return (ret);
}
diff --git a/src/txn/txn_ckpt.c b/src/txn/txn_ckpt.c
index 748f4aa2473..ec150f39fc5 100644
--- a/src/txn/txn_ckpt.c
+++ b/src/txn/txn_ckpt.c
@@ -274,7 +274,6 @@ __wt_checkpoint_get_handles(WT_SESSION_IMPL *session, const char *cfg[])
btree = S2BT(session);
/* Find out if we have to force a checkpoint. */
- force = false;
WT_RET(__wt_config_gets_def(session, cfg, "force", 0, &cval));
force = cval.val != 0;
if (!force) {
diff --git a/test/csuite/Makefile.am b/test/csuite/Makefile.am
index e2b72532703..10ab890f2f5 100644
--- a/test/csuite/Makefile.am
+++ b/test/csuite/Makefile.am
@@ -4,8 +4,13 @@ LDADD = $(top_builddir)/test/utility/libtest_util.la \
$(top_builddir)/libwiredtiger.la
AM_LDFLAGS = -static
+noinst_PROGRAMS=
+
+test_scope_SOURCES = scope/main.c
+noinst_PROGRAMS += test_scope
+
test_wt1965_col_efficiency_SOURCES = wt1965_col_efficiency/main.c
-noinst_PROGRAMS = test_wt1965_col_efficiency
+noinst_PROGRAMS += test_wt1965_col_efficiency
test_wt2403_lsm_workload_SOURCES = wt2403_lsm_workload/main.c
noinst_PROGRAMS += test_wt2403_lsm_workload
diff --git a/test/csuite/scope/main.c b/test/csuite/scope/main.c
new file mode 100644
index 00000000000..15dabd97c40
--- /dev/null
+++ b/test/csuite/scope/main.c
@@ -0,0 +1,288 @@
+/*-
+ * Public Domain 2014-2016 MongoDB, Inc.
+ * Public Domain 2008-2014 WiredTiger, Inc.
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * 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 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.
+ */
+#include "test_util.h"
+
+#define KEY "key"
+#define VALUE "value"
+
+static int ignore_errors;
+
+static int
+handle_error(WT_EVENT_HANDLER *handler,
+ WT_SESSION *session, int error, const char *message)
+{
+ (void)(handler);
+
+ /* Skip the error messages we're expecting to see. */
+ if (ignore_errors > 0 &&
+ (strstr(message, "requires key be set") != NULL ||
+ strstr(message, "requires value be set") != NULL)) {
+ --ignore_errors;
+ return (0);
+ }
+
+ (void)fprintf(stderr, "%s: %s\n",
+ message, session->strerror(session, error));
+ return (0);
+}
+
+static WT_EVENT_HANDLER event_handler = {
+ handle_error,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+cursor_scope_ops(WT_SESSION *session, const char *uri)
+{
+ struct {
+ const char *op;
+ enum { INSERT, SEARCH, SEARCH_NEAR,
+ REMOVE, REMOVE_POS, RESERVE, UPDATE } func;
+ const char *config;
+ } *op, ops[] = {
+ /*
+ * The ops order is fixed and shouldn't change, that is, insert
+ * has to happen first so search, update and remove operations
+ * are possible, and remove has to be last.
+ */
+ { "insert", INSERT, NULL, },
+ { "search", SEARCH, NULL, },
+ { "search", SEARCH_NEAR, NULL, },
+#if 0
+ { "reserve", RESERVE, NULL, },
+#endif
+ { "update", UPDATE, NULL, },
+ { "remove", REMOVE, NULL, },
+ { "remove", REMOVE_POS, NULL, },
+ { NULL, INSERT, NULL }
+ };
+ WT_CURSOR *cursor;
+ uint64_t keyr;
+ const char *key, *value;
+ char keybuf[100], valuebuf[100];
+ int exact;
+ bool recno;
+
+ /* Reserve requires a running transaction. */
+ testutil_check(session->begin_transaction(session, NULL));
+
+ cursor = NULL;
+ for (op = ops; op->op != NULL; op++) {
+ key = value = NULL;
+
+ /* Open a cursor. */
+ if (cursor != NULL)
+ testutil_check(cursor->close(cursor));
+ testutil_check(session->open_cursor(
+ session, uri, NULL, op->config, &cursor));
+ recno = strcmp(cursor->key_format, "r") == 0;
+
+ /*
+ * Set up application buffers so we can detect overwrites
+ * or failure to copy application information into library
+ * memory.
+ */
+ if (recno)
+ cursor->set_key(cursor, (uint64_t)1);
+ else {
+ strcpy(keybuf, KEY);
+ cursor->set_key(cursor, keybuf);
+ }
+ strcpy(valuebuf, VALUE);
+ cursor->set_value(cursor, valuebuf);
+
+ /*
+ * The application must keep key and value memory valid until
+ * the next operation that positions the cursor, modifies the
+ * data, or resets or closes the cursor.
+ *
+ * Modifying either the key or value buffers is not permitted.
+ */
+ switch (op->func) {
+ case INSERT:
+ testutil_check(cursor->insert(cursor));
+ break;
+ case SEARCH:
+ testutil_check(cursor->search(cursor));
+ break;
+ case SEARCH_NEAR:
+ testutil_check(cursor->search_near(cursor, &exact));
+ break;
+ case REMOVE_POS:
+ /*
+ * Remove has two modes, one where the remove is based
+ * on a cursor position, the other where it's based on
+ * a set key. The results are different, so test them
+ * separately.
+ */
+ testutil_check(cursor->search(cursor));
+ /* FALLTHROUGH */
+ case REMOVE:
+ testutil_check(cursor->remove(cursor));
+ break;
+ case RESERVE:
+#if 0
+ testutil_check(cursor->reserve(cursor));
+#endif
+ break;
+ case UPDATE:
+ testutil_check(cursor->update(cursor));
+ break;
+ }
+
+ /*
+ * The cursor should no longer reference application memory,
+ * and application buffers can be safely overwritten.
+ */
+ memset(keybuf, 'K', sizeof(keybuf));
+ memset(valuebuf, 'V', sizeof(valuebuf));
+
+ /*
+ * Check that get_key/get_value behave as expected after the
+ * operation.
+ */
+ switch (op->func) {
+ case INSERT:
+ case REMOVE:
+ /*
+ * Insert and remove configured with a search key do
+ * not position the cursor and have no key or value.
+ *
+ * There should be two error messages, ignore them.
+ */
+ ignore_errors = 2;
+ if (recno)
+ testutil_assert(
+ cursor->get_key(cursor, &keyr) != 0);
+ else
+ testutil_assert(
+ cursor->get_key(cursor, &key) != 0);
+ testutil_assert(cursor->get_value(cursor, &value) != 0);
+ testutil_assert(ignore_errors == 0);
+ break;
+ case REMOVE_POS:
+ /*
+ * Remove configured with a cursor position has a key,
+ * but no value.
+ *
+ * There should be one error message, ignore it.
+ */
+ if (recno) {
+ testutil_assert(
+ cursor->get_key(cursor, &keyr) == 0);
+ testutil_assert(keyr == 1);
+ } else {
+ testutil_assert(
+ cursor->get_key(cursor, &key) == 0);
+ testutil_assert(key != keybuf);
+ testutil_assert(strcmp(key, KEY) == 0);
+ }
+ ignore_errors = 1;
+ testutil_assert(cursor->get_value(cursor, &value) != 0);
+ testutil_assert(ignore_errors == 0);
+ break;
+ case RESERVE:
+ case SEARCH:
+ case SEARCH_NEAR:
+ case UPDATE:
+ /*
+ * Reserve, search, search-near and update position the
+ * cursor and have both a key and value.
+ *
+ * Any key/value should not reference application
+ * memory.
+ */
+ if (recno) {
+ testutil_assert(
+ cursor->get_key(cursor, &keyr) == 0);
+ testutil_assert(keyr == 1);
+ } else {
+ testutil_assert(
+ cursor->get_key(cursor, &key) == 0);
+ testutil_assert(key != keybuf);
+ testutil_assert(strcmp(key, KEY) == 0);
+ }
+ testutil_assert(cursor->get_value(cursor, &value) == 0);
+ testutil_assert(value != valuebuf);
+ testutil_assert(strcmp(value, VALUE) == 0);
+ break;
+ }
+
+ /*
+ * We have more than one remove operation, add the key back
+ * in.
+ */
+ if (op->func == REMOVE || op->func == REMOVE_POS) {
+ if (recno)
+ cursor->set_key(cursor, (uint64_t)1);
+ else {
+ cursor->set_key(cursor, KEY);
+ }
+ cursor->set_value(cursor, VALUE);
+ testutil_check(cursor->insert(cursor));
+ }
+ }
+}
+
+static void
+run(WT_CONNECTION *conn, const char *uri, const char *config)
+{
+ WT_SESSION *session;
+
+ testutil_check(conn->open_session(conn, NULL, NULL, &session));
+ testutil_check(session->create(session, uri, config));
+ cursor_scope_ops(session, uri);
+ testutil_check(session->close(session, NULL));
+}
+
+int
+main(int argc, char *argv[])
+{
+ TEST_OPTS *opts, _opts;
+
+ opts = &_opts;
+ memset(opts, 0, sizeof(*opts));
+ testutil_check(testutil_parse_opts(argc, argv, opts));
+ testutil_make_work_dir(opts->home);
+
+ testutil_check(
+ wiredtiger_open(opts->home, &event_handler, "create", &opts->conn));
+
+ run(opts->conn, "file:file.SS", "key_format=S,value_format=S");
+ run(opts->conn, "file:file.rS", "key_format=r,value_format=S");
+ run(opts->conn, "lsm:lsm.SS", "key_format=S,value_format=S");
+ run(opts->conn, "lsm:lsm.rS", "key_format=r,value_format=S");
+ run(opts->conn, "table:table.SS", "key_format=S,value_format=S");
+ run(opts->conn, "table:table.rS", "key_format=r,value_format=S");
+
+ testutil_cleanup(opts);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/test/suite/test_cursor10.py b/test/suite/test_cursor10.py
index b3cffeab4e9..6cabfde9f1f 100644
--- a/test/suite/test_cursor10.py
+++ b/test/suite/test_cursor10.py
@@ -31,11 +31,11 @@ from wtscenario import make_scenarios
# test_cursor10.py
# Cursors with projections.
-class test_cursor04(wttest.WiredTigerTestCase):
+class test_cursor10(wttest.WiredTigerTestCase):
"""
Test cursor search and search_near
"""
- table_name1 = 'test_cursor04'
+ table_name1 = 'test_cursor10'
nentries = 20
scenarios = make_scenarios([
diff --git a/test/suite/test_cursor11.py b/test/suite/test_cursor11.py
new file mode 100644
index 00000000000..e159ec499e6
--- /dev/null
+++ b/test/suite/test_cursor11.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+#
+# Public Domain 2014-2016 MongoDB, Inc.
+# Public Domain 2008-2014 WiredTiger, Inc.
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# 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 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.
+
+import wiredtiger, wttest
+from wtdataset import SimpleDataSet, SimpleIndexDataSet
+from wtdataset import SimpleLSMDataSet, ComplexDataSet, ComplexLSMDataSet
+from wtscenario import make_scenarios
+
+# test_cursor11.py
+# WT_CURSOR position tests: remove (if not already positioned), and insert
+# leave the cursor without position or information.
+class test_cursor11(wttest.WiredTigerTestCase):
+
+ keyfmt = [
+ ('integer', dict(keyfmt='i')),
+ ('recno', dict(keyfmt='r')),
+ ('string', dict(keyfmt='S')),
+ ]
+ types = [
+ ('file', dict(uri='file', ds=SimpleDataSet)),
+ ('lsm', dict(uri='lsm', ds=SimpleDataSet)),
+ ('table-complex', dict(uri='table', ds=ComplexDataSet)),
+ ('table-complex-lsm', dict(uri='table', ds=ComplexLSMDataSet)),
+ ('table-index', dict(uri='table', ds=SimpleIndexDataSet)),
+ ('table-simple', dict(uri='table', ds=SimpleDataSet)),
+ ('table-simple-lsm', dict(uri='table', ds=SimpleLSMDataSet)),
+ ]
+ scenarios = make_scenarios(types, keyfmt)
+
+ def skip(self):
+ return self.keyfmt == 'r' and \
+ (self.ds.is_lsm() or self.uri == 'lsm')
+
+ # Do a remove using the cursor after setting a position, and confirm
+ # the key and position remain set but no value.
+ def test_cursor_remove_with_position(self):
+ if self.skip():
+ return
+
+ # Build an object.
+ uri = self.uri + ':test_cursor11'
+ ds = self.ds(self, uri, 50, key_format=self.keyfmt)
+ ds.populate()
+ s = self.conn.open_session()
+ c = s.open_cursor(uri, None)
+
+ c.set_key(ds.key(25))
+ self.assertEquals(c.search(), 0)
+ self.assertEquals(c.next(), 0)
+ self.assertEquals(c.get_key(), ds.key(26))
+ c.remove()
+ self.assertEquals(c.get_key(), ds.key(26))
+ msg = '/requires value be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_value, msg)
+ self.assertEquals(c.next(), 0)
+ self.assertEquals(c.get_key(), ds.key(27))
+
+ # Do a remove using the cursor without setting a position, and confirm
+ # no key, value or position remains.
+ def test_cursor_remove_without_position(self):
+ if self.skip():
+ return
+
+ # Build an object.
+ uri = self.uri + ':test_cursor11'
+ ds = self.ds(self, uri, 50, key_format=self.keyfmt)
+ ds.populate()
+ s = self.conn.open_session()
+ c = s.open_cursor(uri, None)
+
+ c.set_key(ds.key(25))
+ c.remove()
+ msg = '/requires key be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_key, msg)
+ msg = '/requires value be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_value, msg)
+ self.assertEquals(c.next(), 0)
+ self.assertEquals(c.get_key(), ds.key(1))
+
+ # Do a remove using the key after also setting a position, and confirm
+ # no key, value or position remains.
+ def test_cursor_remove_with_key_and_position(self):
+ if self.skip():
+ return
+
+ # Build an object.
+ uri = self.uri + ':test_cursor11'
+ ds = self.ds(self, uri, 50, key_format=self.keyfmt)
+ ds.populate()
+ s = self.conn.open_session()
+ c = s.open_cursor(uri, None)
+
+ c.set_key(ds.key(25))
+ self.assertEquals(c.search(), 0)
+ c.set_key(ds.key(25))
+ c.remove()
+ msg = '/requires key be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_key, msg)
+ msg = '/requires value be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_value, msg)
+ self.assertEquals(c.next(), 0)
+ self.assertEquals(c.get_key(), ds.key(1))
+
+ # Do an insert and confirm no key, value or position remains.
+ def test_cursor_insert(self):
+ if self.skip():
+ return
+
+ # Build an object.
+ uri = self.uri + ':test_cursor11'
+ ds = self.ds(self, uri, 50, key_format=self.keyfmt)
+ ds.populate()
+ s = self.conn.open_session()
+ c = s.open_cursor(uri, None)
+
+ c.set_key(ds.key(25))
+ c.set_value(ds.value(300))
+ c.insert()
+ msg = '/requires key be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_key, msg)
+ msg = '/requires value be set/'
+ self.assertRaisesWithMessage(
+ wiredtiger.WiredTigerError, c.get_value, msg)
+ self.assertEquals(c.next(), 0)
+ self.assertEquals(c.get_key(), ds.key(1))
+
+if __name__ == '__main__':
+ wttest.run()