From f0da9e84188aa69c753ec50345e308f274224a27 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov <kostja.osipov@gmail.com> Date: Fri, 3 Feb 2012 00:13:50 +0400 Subject: [PATCH] Feature 'add-delete-fields': add/delete fields with UPDATE Implement blueprint 'add-delete-fields': https://blueprints.launchpad.net/tarantool/+spec/add-and-delete-fields-in-tuple Make it possible to add new fields with UPDATE, and delete existing fields. Rewrite the UPDATE algorithm to pre-compute new tuple length and do most of the work in place, without allocating temporary buffer for fields. Add Connector/C support for new UPDATE options. Add and update tests. Joint effort by Konstantin Shulgin (initial implementation), Dmitry Simonenko (Connector/C) and Konstantin Osipov (spec/code review). --- .gitignore | 1 + connector/c/include/tnt_proto.h | 100 ++++ connector/c/include/tnt_update.h | 131 +++++ connector/c/tnt/include/tnt_update.h | 4 + connector/c/tnt/tnt_update.c | 26 +- core/tarantool_lua.m | 4 +- include/errcode.h | 10 +- include/util.h | 4 +- mod/box/box.h | 16 + mod/box/box.m | 720 ++++++++++++++++++------ test/box/lua.result | 8 + test/box/lua.test | 3 + test/box/sql.result | 11 +- test/box/sql.test | 7 +- test/connector_c/CMakeLists.txt | 1 + test/connector_c/update.c | 794 +++++++++++++++++++++++++++ test/connector_c/update.result | 197 +++++++ test/connector_c/update.test | 15 + 18 files changed, 1861 insertions(+), 191 deletions(-) create mode 100644 connector/c/include/tnt_proto.h create mode 100644 connector/c/include/tnt_update.h create mode 100644 test/connector_c/update.c create mode 100644 test/connector_c/update.result create mode 100644 test/connector_c/update.test diff --git a/.gitignore b/.gitignore index b8e09d4551..1927572f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ third_party/luajit/src/lj_libdef.h third_party/luajit/src/lj_recdef.h third_party/luajit/src/lj_vm.s test/connector_c/tt +test/connector_c/update *.reject diff --git a/connector/c/include/tnt_proto.h b/connector/c/include/tnt_proto.h new file mode 100644 index 0000000000..c0fcafa45f --- /dev/null +++ b/connector/c/include/tnt_proto.h @@ -0,0 +1,100 @@ +#ifndef TNT_PROTO_H_INCLUDED +#define TNT_PROTO_H_INCLUDED + +/* + * Copyright (C) 2011 Mail.RU + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * 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. + */ + +#define TNT_PROTO_TYPE_INSERT 13 +#define TNT_PROTO_TYPE_SELECT 17 +#define TNT_PROTO_TYPE_UPDATE 19 +#define TNT_PROTO_TYPE_DELETE 20 +#define TNT_PROTO_TYPE_PING 65280 + +struct tnt_proto_header { + uint32_t type; + uint32_t len; + uint32_t reqid; +}; + +#define TNT_PROTO_IS_OK(V) ((V) == 0x0) + +struct tnt_proto_header_resp { + struct tnt_proto_header hdr; + uint32_t code; +}; + +struct tnt_proto_tuple { + uint32_t card; + unsigned char field[]; +}; + +#define TNT_PROTO_FLAG_RETURN 0x01 +#define TNT_PROTO_FLAG_ADD 0x02 +#define TNT_PROTO_FLAG_REPLACE 0x04 +#define TNT_PROTO_FLAG_BOX_QUIET 0x08 +#define TNT_PROTO_FLAG_NOT_STORE 0x10 + +struct tnt_proto_insert { + uint32_t ns; + uint32_t flags; + /* tuple data */ +}; + +#define TNT_PROTO_UPDATE_ASSIGN 0 +#define TNT_PROTO_UPDATE_ADD 1 +#define TNT_PROTO_UPDATE_AND 2 +#define TNT_PROTO_UPDATE_XOR 3 +#define TNT_PROTO_UPDATE_OR 4 +#define TNT_PROTO_UPDATE_SPLICE 5 +#define TNT_PROTO_UPDATE_DELETE_FIELD 6 + +struct tnt_proto_update { + uint32_t ns; + uint32_t flags; + /* tuple data */ + /* count */ + /* operation */ +}; + +struct tnt_proto_update_op { + uint32_t field; + unsigned char op; + /* op_arg */ +}; + +struct tnt_proto_delete { + uint32_t ns; + /* tuple data */ +}; + +struct tnt_proto_select { + uint32_t ns; + uint32_t index; + uint32_t offset; + uint32_t limit; + /* tuple data */ +}; + +#endif /* TNT_PROTO_H_INCLUDED */ diff --git a/connector/c/include/tnt_update.h b/connector/c/include/tnt_update.h new file mode 100644 index 0000000000..2b2d94f426 --- /dev/null +++ b/connector/c/include/tnt_update.h @@ -0,0 +1,131 @@ +#ifndef TNT_UPDATE_H_INCLUDED +#define TNT_UPDATE_H_INCLUDED + +/* + * Copyright (C) 2011 Mail.RU + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * 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. + */ + +/** + * @defgroup Update + * @ingroup Operations + * @brief Update operation + */ +enum tnt_update_type { + TNT_UPDATE_NONE, + TNT_UPDATE_ASSIGN, + TNT_UPDATE_ADD, + TNT_UPDATE_AND, + TNT_UPDATE_XOR, + TNT_UPDATE_OR, + TNT_UPDATE_SPLICE, + TNT_UPDATE_DELETE_FIELD +}; +/** @} */ + +struct tnt_update_op { + uint8_t op; + uint32_t field; + char *data; + uint32_t size; + uint32_t size_leb; + STAILQ_ENTRY(tnt_update_op) next; +}; + +struct tnt_update { + uint32_t count; + uint32_t size_enc; + STAILQ_HEAD(,tnt_update_op) list; +}; + +/** + * @defgroup Handler + * @ingroup Update + * @brief Update handler initizalization and operations + * + * @{ + */ +void tnt_update_init(struct tnt_update *update); +void tnt_update_free(struct tnt_update *update); + +enum tnt_error +tnt_update_assign(struct tnt_update *update, int field, + char *value, int value_size); +enum tnt_error +tnt_update_arith(struct tnt_update *update, int field, + enum tnt_update_type op, int value); +enum tnt_error +tnt_update_splice(struct tnt_update *update, int field, + int offset, int length, char *list, int list_size); + +enum tnt_error +tnt_update_delete_field(struct tnt_update *update, int field); +/** @} */ + +/** + * @defgroup Operation + * @ingroup Update + * @brief Update operation + * + * @{ + */ + +/** + * Update operation with tuple. + * + * If bufferization is in use, then request would be placed in + * internal buffer for later sending. Otherwise, operation + * would be processed immediately. + * + * @param t handler pointer + * @param reqid user supplied integer value + * @param ns namespace number + * @param flags update operation flags + * @param key tuple object pointer + * @param update update handler + * @returns 0 on success, -1 on error + */ +int tnt_update_tuple(struct tnt *t, int reqid, int ns, int flags, + struct tnt_tuple *key, struct tnt_update *update); + +/** + * Update operation. + * + * If bufferization is in use, then request would be placed in + * internal buffer for later sending. Otherwise, operation + * would be processed immediately. + * + * @param t handler pointer + * @param reqid user supplied integer value + * @param ns namespace number + * @param flags update operation flags + * @param key key data + * @param key key data size + * @param update update handler + * @returns 0 on success, -1 on error + */ +int tnt_update(struct tnt *t, int reqid, int ns, int flags, + char *key, int key_size, struct tnt_update *update); +/** @} */ + +#endif /* TNT_UPDATE_H_INCLUDED */ diff --git a/connector/c/tnt/include/tnt_update.h b/connector/c/tnt/include/tnt_update.h index ae30e35576..a73690b045 100644 --- a/connector/c/tnt/include/tnt_update.h +++ b/connector/c/tnt/include/tnt_update.h @@ -32,6 +32,7 @@ #define TNT_UPDATE_XOR 3 #define TNT_UPDATE_OR 4 #define TNT_UPDATE_SPLICE 5 +#define TNT_UPDATE_DELETE 6 ssize_t tnt_update_arith(struct tnt_stream *s, uint32_t field, @@ -46,6 +47,9 @@ tnt_update_splice(struct tnt_stream *s, uint32_t field, uint32_t offset, uint32_t length, char *data, size_t size); +ssize_t +tnt_update_delete(struct tnt_stream *s, uint32_t field); + ssize_t tnt_update(struct tnt_stream *s, uint32_t ns, uint32_t flags, struct tnt_tuple *k, diff --git a/connector/c/tnt/tnt_update.c b/connector/c/tnt/tnt_update.c index 31bb70e4bf..5242ff5fe8 100644 --- a/connector/c/tnt/tnt_update.c +++ b/connector/c/tnt/tnt_update.c @@ -46,6 +46,7 @@ tnt_update_op(struct tnt_stream *s, char enc[5]; tnt_enc_write(enc, size); struct iovec iov[4]; + int iovc = 3; /* field */ iov[0].iov_base = &field; iov[0].iov_len = 4; @@ -56,9 +57,12 @@ tnt_update_op(struct tnt_stream *s, iov[2].iov_base = enc; iov[2].iov_len = encs; /* data */ - iov[3].iov_base = data; - iov[3].iov_len = size; - return s->writev(s, iov, 4); + if (data) { + iov[3].iov_base = data; + iov[3].iov_len = size; + iovc++; + } + return s->writev(s, iov, iovc); } /* @@ -148,6 +152,22 @@ tnt_update_splice(struct tnt_stream *s, uint32_t field, return rc; } +/* + * tnt_update_delete() + * + * write update delete operation to buffer stream; + * + * s - stream buffer pointer + * field - field number + * + * returns number of bytes written, or -1 on error. +*/ +ssize_t +tnt_update_delete(struct tnt_stream *s, uint32_t field) +{ + return tnt_update_op(s, field, TNT_UPDATE_DELETE, NULL, 0); +} + struct tnt_header_update { uint32_t ns; uint32_t flags; diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m index 04f6dcb3de..ccfaebdd59 100644 --- a/core/tarantool_lua.m +++ b/core/tarantool_lua.m @@ -94,8 +94,8 @@ static char format_to_opcode(char format) case '=': return 0; case '+': return 1; case '&': return 2; - case '|': return 3; - case '^': return 4; + case '^': return 3; + case '|': return 4; case ':': return 5; default: return format; } diff --git a/include/errcode.h b/include/errcode.h index e5ccf33e4c..8c7a22bf22 100644 --- a/include/errcode.h +++ b/include/errcode.h @@ -94,11 +94,11 @@ enum { TNT_ERRMSG_MAX = 512 }; /* 38 */_(ER_WRONG_VERSION, 2, "Unsupported version of protocol") \ /* end of silversearch error codes */ \ /* 39 */_(ER_WAL_IO, 2, "Failed to write to disk") \ - /* 40 */_(ER_UNUSED40, 0, "Unused40") \ - /* 41 */_(ER_UNUSED41, 0, "Unused41") \ - /* 42 */_(ER_UNUSED42, 0, "Unused42") \ - /* 43 */_(ER_UNUSED43, 0, "Unused43") \ - /* 44 */_(ER_UNUSED44, 0, "Unused44") \ + /* 40 */_(ER_FIELD_TYPE, 2, "Field type does not match one required by operation: expected a %s") \ + /* 41 */_(ER_TYPE_MISMATCH, 2, "Argument type in operation does not match field type: expected a %s") \ + /* 42 */_(ER_SPLICE, 2, "Field SPLICE error: %s") \ + /* 43 */_(ER_TUPLE_IS_TOO_LONG, 2, "Tuple is too long %u") \ + /* 44 */_(ER_UNKNOWN_UPDATE_OP, 2, "Unknown UPDATE operation") \ /* 45 */_(ER_UNUSED45, 0, "Unused45") \ /* 46 */_(ER_UNUSED46, 0, "Unused46") \ /* 47 */_(ER_UNUSED47, 0, "Unused47") \ diff --git a/include/util.h b/include/util.h index 8e0aa7500d..91e710fcb7 100644 --- a/include/util.h +++ b/include/util.h @@ -31,8 +31,8 @@ #include <inttypes.h> #ifndef MAX -# define MAX(a, b) (a) > (b) ? (a) : (b) -# define MIN(a, b) (a) < (b) ? (a) : (b) +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +# define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif /* Macros to define enum and corresponding strings. */ diff --git a/mod/box/box.h b/mod/box/box.h index fcb031a079..4ec097ce71 100644 --- a/mod/box/box.h +++ b/mod/box/box.h @@ -39,6 +39,8 @@ enum { BOX_INDEX_MAX = 10, BOX_SPACE_MAX = 256, + /** A limit on how many operations a single UPDATE can have. */ + BOX_UPDATE_OP_CNT_MAX = 128, }; struct space { @@ -147,6 +149,20 @@ struct box_txn { ENUM(messages, MESSAGES); +/** UPDATE operation codes. */ +#define UPDATE_OP_CODES(_) \ + _(UPDATE_OP_SET, 0) \ + _(UPDATE_OP_ADD, 1) \ + _(UPDATE_OP_AND, 2) \ + _(UPDATE_OP_XOR, 3) \ + _(UPDATE_OP_OR, 4) \ + _(UPDATE_OP_SPLICE, 5) \ + _(UPDATE_OP_DELETE, 6) \ + _(UPDATE_OP_NONE, 7) \ + _(UPDATE_OP_MAX, 8) \ + +ENUM(update_op_codes, UPDATE_OP_CODES); + extern iproto_callback rw_callback; /** diff --git a/mod/box/box.m b/mod/box/box.m index 708830bd68..5d2373f383 100644 --- a/mod/box/box.m +++ b/mod/box/box.m @@ -286,218 +286,584 @@ rollback_replace(struct box_txn *txn) } } -static void -do_field_arith(u8 op, struct tbuf *field, void *arg, u32 arg_size) +/** {{{ UPDATE request implementation. + * UPDATE request is represented by a sequence of operations, + * each working with a single field. However, there + * can be more than one operation on the same field. + * Supported operations are: SET, ADD, bitwise AND, XOR and OR, + * SPLICE and DELETE. + * + * To ensure minimal use of intermediate memory, UPDATE is + * performed in a streaming fashion: all operations in the request + * are sorted by field number. The resulting tuple length is + * calculated. A new tuple is allocated. Operation are applied + * sequentially, each copying data from the old tuple to the new + * data. + * With this approach we have in most cases linear (tuple length) + * UPDATE complexity and copy data from the old tuple to the new + * one only once. + * + * There are complications in this scheme caused by multiple + * operations on the same field: for example, we may have + * SET(4, "aaaaaa"), SPLICE(4, 0, 5, 0, ""), resulting in + * zero increase of total tuple length, but requiring an + * intermediate buffer to store SET results. Please + * read the source of do_update() to see how these + * complications are worked around. + */ + +/** Argument of SET operation. */ +struct op_set_arg { + u32 length; + void *value; +}; + +/** Argument of ADD, AND, XOR, OR operations. */ +struct op_arith_arg { + i32 i32_val; +}; + +/** Argument of SPLICE. */ +struct op_splice_arg { + i32 offset; /** splice position */ + i32 cut_length; /** cut this many bytes. */ + void *paste; /** paste what? */ + i32 paste_length; /** paste this many bytes. */ + + /** Inferred data */ + i32 tail_offset; + i32 tail_length; +}; + +union update_op_arg { + struct op_set_arg set; + struct op_arith_arg arith; + struct op_splice_arg splice; +}; + +struct update_cmd; +struct update_field; +struct update_op; + +typedef void (*init_op_func)(struct update_cmd *cmd, + struct update_field *field, + struct update_op *op); +typedef void (*do_op_func)(union update_op_arg *arg, void *in, void *out); + +/** A set of functions and properties to initialize and do an op. */ +struct update_op_meta { + init_op_func init_op; + do_op_func do_op; + bool works_in_place; +}; + +/** A single UPDATE operation. */ +struct update_op { + struct update_op_meta *meta; + union update_op_arg arg; + u32 field_no; + u32 new_field_len; + u8 opcode; +}; + +/** + * We can have more than one operation on the same field. + * A descriptor of one changed field. + */ +struct update_field { + struct update_op *first; /** first operation on this field */ + struct update_op *end; /** points after last operation on + this field. */ + void *old; /** points at start of field *data* in old + tuple. */ + void *old_end; /** end of the old field. */ + u32 new_len; /** final length of the new field. */ + u32 tail_len; /** copy old data after this field */ + int tail_field_count; /** how many fields we're copying. */ +}; + +/** UPDATE command context. */ +struct update_cmd { + /** Search key */ + void *key; + /** Search key cardinality */ + u32 key_cardinality; + /** Operations. */ + struct update_op *op; + struct update_op *op_end; + /* Distinct fields affected by UPDATE. */ + struct update_field *field; + struct update_field *field_end; + /** new tuple length after all operations are applied. */ + u32 new_tuple_len; + u32 new_field_count; +}; + +static int +update_op_cmp(const void *op1_ptr, const void *op2_ptr) { - if (field->size != 4) - tnt_raise(IllegalParams, :"numeric operation on a field with length != 4"); + const struct update_op *op1 = op1_ptr; + const struct update_op *op2 = op2_ptr; - if (arg_size != 4) - tnt_raise(IllegalParams, :"the argument of a numeric operation is not a 4-byte int"); + if (op1->field_no < op2->field_no) + return -1; + if (op1->field_no > op2->field_no) + return 1; - switch (op) { - case 1: - *(i32 *)field->data += *(i32 *)arg; - break; - case 2: - *(u32 *)field->data &= *(u32 *)arg; - break; - case 3: - *(u32 *)field->data ^= *(u32 *)arg; - break; - case 4: - *(u32 *)field->data |= *(u32 *)arg; - break; - } + if (op1->arg.set.value < op2->arg.set.value) + return -1; + if (op1->arg.set.value > op2->arg.set.value) + return 1; + return 0; } static void -do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size) +do_update_op_set(struct op_set_arg *arg, void *in __attribute__((unused)), + void *out) { - struct tbuf args = { - .size = args_data_size, - .capacity = args_data_size, - .data = args_data, - .pool = NULL - }; - struct tbuf *new_field = NULL; - void *offset_field, *length_field, *list_field; - u32 offset_size, length_size, list_size; - i32 offset, length; - u32 noffset, nlength; /* normalized values */ - - new_field = tbuf_alloc(fiber->gc_pool); - - offset_field = read_field(&args); - length_field = read_field(&args); - list_field = read_field(&args); - if (args.size != 0) - tnt_raise(IllegalParams, :"field splice: bad arguments"); - - offset_size = load_varint32(&offset_field); - if (offset_size == 0) - noffset = 0; - else if (offset_size == sizeof(offset)) { - offset = pick_u32(offset_field, &offset_field); - if (offset < 0) { - if (field->size < -offset) - tnt_raise(IllegalParams, - :"field splice: offset is negative"); - noffset = offset + field->size; - } else - noffset = offset; - } else - tnt_raise(IllegalParams, :"field splice: wrong size of offset"); - - if (noffset > field->size) - noffset = field->size; - - length_size = load_varint32(&length_field); - if (length_size == 0) - nlength = field->size - noffset; - else if (length_size == sizeof(length)) { - if (offset_size == 0) - tnt_raise(IllegalParams, - :"field splice: offset is empty but length is not"); - - length = pick_u32(length_field, &length_field); - if (length < 0) { - if ((field->size - noffset) < -length) - nlength = 0; - else - nlength = length + field->size - noffset; - } else - nlength = length; - } else - tnt_raise(IllegalParams, :"field splice: wrong size of length"); - - if (nlength > (field->size - noffset)) - nlength = field->size - noffset; - - list_size = load_varint32(&list_field); - if (list_size > 0 && length_size == 0) - tnt_raise(IllegalParams, - :"field splice: length field is empty but list is not"); - if (list_size > (UINT32_MAX - (field->size - nlength))) - tnt_raise(IllegalParams, :"field splice: list_size is too long"); - - say_debug("do_field_splice: noffset = %i, nlength = %i, list_size = %u", - noffset, nlength, list_size); - - new_field->size = 0; - tbuf_append(new_field, field->data, noffset); - tbuf_append(new_field, list_field, list_size); - tbuf_append(new_field, field->data + noffset + nlength, field->size - (noffset + nlength)); - - *field = *new_field; + memcpy(out, arg->value, arg->length); } -static void __attribute__((noinline)) -prepare_update(struct box_txn *txn, struct tbuf *data) +static void +do_update_op_add(struct op_arith_arg *arg, void *in, void *out) { - struct tbuf **fields; - void *field; - int i; - void *key; - u32 op_cnt; - u32 tuples_affected = 1; + *(i32 *)out = *(i32 *)in + arg->i32_val; +} - u32 key_len = read_u32(data); - if (key_len != 1) - tnt_raise(IllegalParams, :"key must be single valued"); +static void +do_update_op_and(struct op_arith_arg *arg, void *in, void *out) +{ + *(i32 *)out = *(i32 *)in & arg->i32_val; +} - key = read_field(data); - op_cnt = read_u32(data); +static void +do_update_op_xor(struct op_arith_arg *arg, void *in, void *out) +{ + *(i32 *)out = *(i32 *)in ^ arg->i32_val; +} - if (op_cnt > 128) - tnt_raise(IllegalParams, :"too many operations for update"); - if (op_cnt == 0) - tnt_raise(IllegalParams, :"no operations for update"); - if (key == NULL) - tnt_raise(IllegalParams, :"invalid key"); +static void +do_update_op_or(struct op_arith_arg *arg, void *in, void *out) +{ + *(i32 *)out = *(i32 *)in | arg->i32_val; +} - txn->old_tuple = [txn->index find: key]; - if (txn->old_tuple == NULL) { - txn->flags |= BOX_NOT_STORE; +static void +do_update_op_splice(struct op_splice_arg *arg, void *in, void *out) +{ + memcpy(out, in, arg->offset); /* copy field head. */ + out += arg->offset; + memcpy(out, arg->paste, arg->paste_length); /* copy the paste */ + out += arg->paste_length; + memcpy(out, in + arg->tail_offset, arg->tail_length); /* copy tail */ +} - tuples_affected = 0; +static void +do_update_op_none(struct op_set_arg *arg, void *in, void *out) +{ + memcpy(out, in, arg->length); +} - goto out; +static void +init_update_op_set(struct update_cmd *cmd __attribute__((unused)), + struct update_field *field, struct update_op *op) +{ + /* Skip all previous ops. */ + field->first = op; + op->new_field_len = op->arg.set.length; +} + +static void +init_update_op_arith(struct update_cmd *cmd __attribute__((unused)), + struct update_field *field, struct update_op *op) +{ + /* Check the argument. */ + if (field->new_len != sizeof(i32)) + tnt_raise(ClientError, :ER_FIELD_TYPE, "32-bit int"); + + if (op->arg.set.length != sizeof(i32)) + tnt_raise(ClientError, :ER_TYPE_MISMATCH, "32-bit int"); + /* Parse the operands. */ + op->arg.arith.i32_val = *(i32 *)op->arg.set.value; + op->new_field_len = sizeof(i32); +} + +static void +init_update_op_splice(struct update_cmd *cmd __attribute__((unused)), + struct update_field *field, struct update_op *op) +{ + u32 field_len = field->new_len; + struct tbuf operands = { + .capacity = op->arg.set.length, + .size = op->arg.set.length, + .data = op->arg.set.value, + .pool = NULL + }; + struct op_splice_arg *arg = &op->arg.splice; + + /* Read the offset. */ + void *offset_field = read_field(&operands); + u32 len = load_varint32(&offset_field); + if (len != sizeof(i32)) + tnt_raise(IllegalParams, :"SPLICE offset"); + /* Sic: overwrite of op->arg.set.length. */ + arg->offset = *(i32 *)offset_field; + if (arg->offset < 0) { + if (-arg->offset > field_len) + tnt_raise(ClientError, :ER_SPLICE, + "offset is out of bound"); + arg->offset += field_len; + } else if (arg->offset > field_len) { + arg->offset = field_len; + } + assert(arg->offset >= 0 && arg->offset <= field_len); + + /* Read the cut length. */ + void *length_field = read_field(&operands); + len = load_varint32(&length_field); + if (len != sizeof(i32)) + tnt_raise(IllegalParams, :"SPLICE length"); + arg->cut_length = *(i32 *)length_field; + if (arg->cut_length < 0) { + if (-arg->cut_length > (field_len - arg->offset)) + arg->cut_length = 0; + else + arg->cut_length += field_len - arg->offset; + } else if (arg->cut_length > field_len - arg->offset) { + arg->cut_length = field_len - arg->offset; } - lock_tuple(txn, txn->old_tuple); + /* Read the paste. */ + void *paste_field = read_field(&operands); + arg->paste_length = load_varint32(&paste_field); + arg->paste = paste_field; - fields = palloc(fiber->gc_pool, (txn->old_tuple->cardinality + 1) * sizeof(struct tbuf *)); - memset(fields, 0, (txn->old_tuple->cardinality + 1) * sizeof(struct tbuf *)); + /* Fill tail part */ + arg->tail_offset = arg->offset + arg->cut_length; + arg->tail_length = field_len - arg->tail_offset; - for (i = 0, field = (uint8_t *)txn->old_tuple->data; i < txn->old_tuple->cardinality; i++) { - fields[i] = tbuf_alloc(fiber->gc_pool); + /* Check that the operands are fully read. */ + if (operands.size != 0) + tnt_raise(IllegalParams, :"field splice format error"); - u32 field_size = load_varint32(&field); - tbuf_append(fields[i], field, field_size); - field += field_size; - } + /* Record the new field length. */ + op->new_field_len = arg->offset + arg->paste_length + arg->tail_length; +} - while (op_cnt-- > 0) { - u8 op; - u32 field_no, arg_size; - void *arg; +static void +init_update_op_delete(struct update_cmd *cmd, + struct update_field *field, struct update_op *op) +{ + /* Either this is the last op on this field or next op is SET. */ + if (op + 1 < cmd->op_end && op[1].field_no == op->field_no && + op[1].opcode != UPDATE_OP_SET && op[1].opcode != UPDATE_OP_DELETE) + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, op->field_no); + + /* Skip all ops on this field, including this one. */ + field->first = op + 1; + op->new_field_len = 0; +} - field_no = read_u32(data); +static void +init_update_op_none(struct update_cmd *cmd __attribute__((unused)), + struct update_field *field, struct update_op *op) +{ + op->new_field_len = op->arg.set.length = field->new_len; +} - if (field_no >= txn->old_tuple->cardinality) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, field_no); +static void +init_update_op_error(struct update_cmd *cmd __attribute__((unused)), + struct update_field *field __attribute__((unused)), + struct update_op *op __attribute__((unused))) +{ + tnt_raise(ClientError, :ER_UNKNOWN_UPDATE_OP); +} - struct tbuf *sptr_field = fields[field_no]; +static struct update_op_meta update_op_meta[UPDATE_OP_MAX + 1] = { + { init_update_op_set, (do_op_func) do_update_op_set, true }, + { init_update_op_arith, (do_op_func) do_update_op_add, true }, + { init_update_op_arith, (do_op_func) do_update_op_and, true }, + { init_update_op_arith, (do_op_func) do_update_op_xor, true }, + { init_update_op_arith, (do_op_func) do_update_op_or, true }, + { init_update_op_splice, (do_op_func) do_update_op_splice, false }, + { init_update_op_delete, (do_op_func) NULL, true }, + { init_update_op_none, (do_op_func) do_update_op_none, false }, + { init_update_op_error, (do_op_func) NULL, true } +}; - op = read_u8(data); - if (op > 5) - tnt_raise(IllegalParams, :"op is not 0, 1, 2, 3, 4 or 5"); +/** + * Initial parse of update command. Unpack and record + * update operations. Do not do too much, since the subject + * tuple may not exist. + */ +static struct update_cmd * +parse_update_cmd(struct tbuf *data) +{ + struct update_cmd *cmd = palloc(fiber->gc_pool, + sizeof(struct update_cmd)); + /* key cardinality */ + cmd->key_cardinality = read_u32(data); + if (cmd->key_cardinality > 1) + tnt_raise(IllegalParams, :"key must be single valued"); + if (cmd->key_cardinality == 0) + tnt_raise(IllegalParams, :"key is not defined"); + + /* key */ + cmd->key = read_field(data); + /* number of operations */ + u32 op_cnt = read_u32(data); + if (op_cnt > BOX_UPDATE_OP_CNT_MAX) + tnt_raise(IllegalParams, :"too many operations for update"); + if (op_cnt == 0) + tnt_raise(IllegalParams, :"no operations for update"); + /* + * Read update operations. Allocate an extra dummy op to + * optionally "apply" to the first field. + */ + struct update_op *op = palloc(fiber->gc_pool, (op_cnt + 1) * + sizeof(struct update_op)); + cmd->op = ++op; /* Skip the extra op for now. */ + cmd->op_end = cmd->op + op_cnt; + for (; op < cmd->op_end; op++) { + /* read operation */ + op->field_no = read_u32(data); + op->opcode = read_u8(data); + if (op->opcode > UPDATE_OP_MAX) + op->opcode = UPDATE_OP_MAX; + op->meta = &update_op_meta[op->opcode]; + op->arg.set.value = read_field(data); + op->arg.set.length = load_varint32(&op->arg.set.value); + } + /* Check the remainder length, the request must be fully read. */ + if (data->size != 0) + tnt_raise(IllegalParams, :"can't unpack request"); - arg = read_field(data); - arg_size = load_varint32(&arg); + return cmd; +} - if (op == 0) { - tbuf_ensure(sptr_field, arg_size); - sptr_field->size = arg_size; - memcpy(sptr_field->data, arg, arg_size); - } else { - switch (op) { - case 1: - case 2: - case 3: - case 4: - do_field_arith(op, sptr_field, arg, arg_size); - break; - case 5: - do_field_splice(sptr_field, arg, arg_size); - break; +/** + * Skip fields unaffected by UPDATE. + * @return length of skipped data + */ +static u32 +skip_fields(u32 field_count, void **data) +{ + void *begin = *data; + while (field_count-- > 0) { + u32 len = load_varint32(data); + *data += len; + } + return *data - begin; +} + +static void +update_field_init(struct update_cmd *cmd, struct update_field *field, + struct update_op *op, void **old_data, int old_field_count) +{ + field->first = op; + if (op->field_no < old_field_count) { + /* + * Find out the new field length and + * shift the data pointer. + */ + field->new_len = load_varint32(old_data); + field->old = *old_data; + *old_data += field->new_len; + field->old_end = *old_data; + } else { + field->new_len = 0; + field->old = ""; /* Beyond old fields. */ + field->old_end = field->old; + /* + * Old tuple must have at least one field and we + * always have an op on the first field. + */ + assert(op->field_no > 0 && op > cmd->op); + } +} + +/** + * We found a tuple to do the update on. Prepare and optimize + * the operations. + */ +static void +init_update_operations(struct box_txn *txn, struct update_cmd *cmd) +{ + /* + * 1. Sort operations by field number and order within the + * request. + */ + qsort(cmd->op, cmd->op_end - cmd->op, sizeof(struct update_op), + update_op_cmp); + /* + * 2. Take care of the old tuple head. + */ + if (cmd->op->field_no != 0) { + /* + * We need to copy part of the old tuple to the + * new one. + * Prepend a no-op which copies the first field + * intact. We may also make use of its tail_len + * if next op field_no > 1. + */ + cmd->op--; + cmd->op->opcode = UPDATE_OP_NONE; + cmd->op->meta = &update_op_meta[UPDATE_OP_NONE]; + cmd->op->field_no = 0; + } + /* + * 3. Initialize and optimize the operations. + */ + assert(cmd->op < cmd->op_end); /* Ensured by parse_update_cmd(). */ + cmd->field = palloc(fiber->gc_pool, (cmd->op_end - cmd->op) * + sizeof(struct update_field)); + struct update_op *op = cmd->op; + struct update_field *field = cmd->field; + void *old_data = txn->old_tuple->data; + int old_field_count = txn->old_tuple->cardinality; + + update_field_init(cmd, field, op, &old_data, old_field_count); + do { + /* + * Various checks for added fields: + * 1) we can not do anything with a new field unless a + * previous field exists. + * 2) we can not do any op except SET on a field + * which does not exist. + */ + if (op->field_no >= old_field_count && + /* check case 1. */ + (op->field_no > MAX(old_field_count, + op[-1].field_no + 1) || + /* check case 2. */ + (op->opcode != UPDATE_OP_SET && + op[-1].field_no != op->field_no))) { + + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, op->field_no); + } + op->meta->init_op(cmd, field, op); + field->new_len = op->new_field_len; + + if (op + 1 >= cmd->op_end || op[1].field_no != op->field_no) { + /* Last op on this field. */ + int skip_to = old_field_count; + if (op + 1 < cmd->op_end && op[1].field_no < old_field_count) + skip_to = op[1].field_no; + if (skip_to > op->field_no + 1) { + /* Jumping over a gap. */ + field->tail_field_count = skip_to - op->field_no - 1; + field->tail_len = + skip_fields(field->tail_field_count, + &old_data); + } else { + field->tail_len = 0; + field->tail_field_count = 0; + } + field->end = op + 1; + if (field->first < field->end) { /* Field is not deleted. */ + cmd->new_tuple_len += varint32_sizeof(field->new_len); + cmd->new_tuple_len += field->new_len; } + cmd->new_tuple_len += field->tail_len; + field++; /** Move to the next field. */ + if (op + 1 < cmd->op_end) { + update_field_init(cmd, field, op + 1, + &old_data, old_field_count); + } + } + } while (++op < cmd->op_end); + + cmd->field_end = field; +} + +static void +do_update(struct box_txn *txn, struct update_cmd *cmd) +{ + void *new_data = txn->tuple->data; + void *new_data_end = new_data + txn->tuple->bsize; + struct update_field *field; + txn->tuple->cardinality = 0; + + for (field = cmd->field; field < cmd->field_end; field++) { + if (field->first < field->end) { /* -> field is not deleted. */ + new_data = save_varint32(new_data, field->new_len); + txn->tuple->cardinality++; + } + void *new_field = new_data; + void *old_field = field->old; + + struct update_op *op; + for (op = field->first; op < field->end; op++) { + /* + * Pre-allocate a temporary buffer when the + * subject operation requires it, i.e.: + * - op overwrites data while reading it thus + * can't work with in == out (SPLICE) + * - op result doesn't fit into the new tuple + * (can happen when a big SET is then + * shrunk by a SPLICE). + */ + if ((old_field == new_field && !op->meta->works_in_place) || + /* + * Sic: this predicate must function even if + * new_field != new_data. + */ + new_data + op->new_field_len > new_data_end) { + /* + * Since we don't know which of the two + * conditions above got us here, simply + * palloc a *new* buffer of sufficient + * size. + */ + new_field = palloc(fiber->gc_pool, + op->new_field_len); + } + op->meta->do_op(&op->arg, old_field, new_field); + /* Next op uses previous op output as its input. */ + old_field = new_field; + } + /* + * Make sure op results end up in the tuple, copy + * tail_len from the old tuple. + */ + if (new_field != new_data) + memcpy(new_data, new_field, field->new_len); + new_data += field->new_len; + if (field->tail_field_count) { + memcpy(new_data, field->old_end, field->tail_len); + new_data += field->tail_len; + txn->tuple->cardinality += field->tail_field_count; } } +} - if (data->size != 0) - tnt_raise(IllegalParams, :"can't unpack request"); +static void __attribute__((noinline)) +prepare_update(struct box_txn *txn, struct tbuf *data) +{ + u32 tuples_affected = 1; - size_t bsize = 0; - for (int i = 0; i < txn->old_tuple->cardinality; i++) - bsize += fields[i]->size + varint32_sizeof(fields[i]->size ); - txn->tuple = tuple_alloc(bsize); - tuple_txn_ref(txn, txn->tuple); - txn->tuple->cardinality = txn->old_tuple->cardinality; + /* Parse UPDATE request. */ + struct update_cmd *cmd = parse_update_cmd(data); + + /* Try to find the tuple. */ + txn->old_tuple = [txn->index find: cmd->key]; + if (txn->old_tuple == NULL) { + /* Not found. For simplicity, skip the logging. */ + txn->flags |= BOX_NOT_STORE; - uint8_t *p = txn->tuple->data; - for (int i = 0; i < txn->old_tuple->cardinality; i++) { - p = save_varint32(p, fields[i]->size); - memcpy(p, fields[i]->data, fields[i]->size); - p += fields[i]->size; + tuples_affected = 0; + + goto out; } + init_update_operations(txn, cmd); + /* allocate new tuple */ + txn->tuple = tuple_alloc(cmd->new_tuple_len); + tuple_txn_ref(txn, txn->tuple); + do_update(txn, cmd); + lock_tuple(txn, txn->old_tuple); validate_indexes(txn); - if (data->size != 0) - tnt_raise(IllegalParams, :"can't unpack request"); - out: txn->out->dup_u32(tuples_affected); @@ -505,6 +871,8 @@ out: txn->out->add_tuple(txn->tuple); } +/** }}} */ + static void __attribute__((noinline)) process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data) { @@ -1731,3 +2099,7 @@ mod_info(struct tbuf *out) recovery_state->recovery_last_update_tstamp); tbuf_printf(out, " status: %s" CRLF, status); } + +/** + * vim: foldmethod=marker + */ diff --git a/test/box/lua.result b/test/box/lua.result index adbcc02553..500697e8a3 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -692,6 +692,14 @@ lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 0, 2, 'every')) --- - 1953719668: {'every'} ... +lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 100, 2, 'every')) +--- + - 1953719668: {'everyevery'} +... +lua box.space[0]:update('test', ':p', 1, box.pack('ppp', -100, 2, 'every')) +--- +error: 'Field SPLICE error: offset is out of bound' +... lua box.space[0]:truncate() --- ... diff --git a/test/box/lua.test b/test/box/lua.test index 211e4679bf..350c34ed95 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -190,6 +190,9 @@ exec admin "lua box.fiber.find(920)" exec admin "lua box.space[0]:insert('test', 'something to splice')" exec admin "lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 0, 4, 'no'))" exec admin "lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 0, 2, 'every'))" +# check an incorrect offset +exec admin "lua box.space[0]:update('test', ':p', 1, box.pack('ppp', 100, 2, 'every'))" +exec admin "lua box.space[0]:update('test', ':p', 1, box.pack('ppp', -100, 2, 'every'))" exec admin "lua box.space[0]:truncate()" exec admin "lua box.space[0]:insert('test', 'hello', 'october', '20th'):unpack()" exec admin "lua box.space[0]:truncate()" diff --git a/test/box/sql.result b/test/box/sql.result index c3472bdc3a..a42097abff 100644 --- a/test/box/sql.result +++ b/test/box/sql.result @@ -43,11 +43,16 @@ Update OK, 1 row affected select * from t0 where k0 = 1 Found 1 tuple: [1, 'I am the newest tuple'] -update t0 set k1 = 'Huh', k2 = 'Oh-ho-ho' where k0=1 -An error occurred: ER_NO_SUCH_FIELD, 'Field 2 was not found in the tuple' +update t0 set k1 = 'Huh', k2 = 'I am a new field! I was added via append' where k0=1 +Update OK, 1 row affected select * from t0 where k0 = 1 Found 1 tuple: -[1, 'I am the newest tuple'] +[1, 'Huh', 'I am a new field! I was added via append'] +update t0 set k1 = 'Huh', k1000 = 'invalid field' where k0=1 +An error occurred: ER_NO_SUCH_FIELD, 'Field 1000 was not found in the tuple' +select * from t0 where k0 = 1 +Found 1 tuple: +[1, 'Huh', 'I am a new field! I was added via append'] insert into t0 values (1, 'I am a new tuple', 'stub') Insert OK, 1 row affected update t0 set k1 = 'Huh', k2 = 'Oh-ho-ho' where k0=1 diff --git a/test/box/sql.test b/test/box/sql.test index 3b2282cdec..28eec05198 100644 --- a/test/box/sql.test +++ b/test/box/sql.test @@ -24,8 +24,11 @@ exec sql "insert into t0 values (1, 'I am a new tuple')" exec sql "select * from t0 where k0 = 1" exec sql "update t0 set k1 = 'I am the newest tuple' where k0=1" exec sql "select * from t0 where k0 = 1" -# this is illegal, can't change tuple dimension with update -exec sql "update t0 set k1 = 'Huh', k2 = 'Oh-ho-ho' where k0=1" +# this is correct, can append field to tuple +exec sql "update t0 set k1 = 'Huh', k2 = 'I am a new field! I was added via append' where k0=1" +exec sql "select * from t0 where k0 = 1" +# this is illegal +exec sql "update t0 set k1 = 'Huh', k1000 = 'invalid field' where k0=1" exec sql "select * from t0 where k0 = 1" exec sql "insert into t0 values (1, 'I am a new tuple', 'stub')" exec sql "update t0 set k1 = 'Huh', k2 = 'Oh-ho-ho' where k0=1" diff --git a/test/connector_c/CMakeLists.txt b/test/connector_c/CMakeLists.txt index 91759d7325..cd27efddf2 100644 --- a/test/connector_c/CMakeLists.txt +++ b/test/connector_c/CMakeLists.txt @@ -1,2 +1,3 @@ tarantool_client("tt" tt.c) +tarantool_client("update" update.c) diff --git a/test/connector_c/update.c b/test/connector_c/update.c new file mode 100644 index 0000000000..2ced039588 --- /dev/null +++ b/test/connector_c/update.c @@ -0,0 +1,794 @@ + +/* + * Copyright (C) 2011 Mail.RU + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * 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. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <limits.h> + +#include <connector/c/tnt/include/tnt.h> +#include <connector/c/tntnet/include/tnt_net.h> +#include <connector/c/tntnet/include/tnt_io.h> + +#include <util.h> +#include <errcode.h> + +/*========================================================================== + * test variables + *==========================================================================*/ + +/** tarantool connector instance */ +static struct tnt_stream *tnt; + +static char *long_string = "A long time ago, in a galaxy far, far away...\n" + "It is a period of civil war. Rebel\n" + "spaceships, striking from a hidden\n" + "base, have won their first victory\n" + "against the evil Galactic Empire.\n" + "During the battle, Rebel spies managed\n" + "to steal secret plans to the Empire's\n" + "ultimate weapon, the Death Star, an\n" + "armored space station with enough\n" + "power to destroy an entire planet.\n" + "Pursued by the Empire's sinister agents,\n" + "Princess Leia races home aboard her\n" + "starship, custodian of the stolen plans\n" + "that can save her people and restore\n" + "freedom to the galaxy...."; + +/*========================================================================== + * function declaration + *==========================================================================*/ + +/*-------------------------------------------------------------------------- + * tarantool management functions + *--------------------------------------------------------------------------*/ + +/** insert tuple */ +void +insert_tuple(struct tnt_tuple *tuple); + +/** select tuple by key */ +void +select_tuple(i32 key); + +/** update fields */ +void +update(i32 key, struct tnt_stream *stream); + +/** add update fields operation: set int32 */ +void +update_set_i32(struct tnt_stream *stream, i32 field, i32 value); + +/** add update fields operation: set string */ +void +update_set_str(struct tnt_stream *stream, i32 field, char *str); + +/** add update fields operation: splice string */ +void +update_splice_str(struct tnt_stream *stream, i32 field, i32 offset, i32 length, char *list); + +/** add update fields operation: delete field */ +void +update_delete_field(struct tnt_stream *stream, i32 field); + + +/** receive reply from server */ +void +recv_command(char *command); + +/** print tuple */ +void +print_tuple(struct tnt_tuple *tuple); + +/*-------------------------------------------------------------------------- + * test suite functions + *--------------------------------------------------------------------------*/ + +/** setup test suite */ +void +test_suite_setup(); + +/** clean-up test suite */ +void +test_suite_tear_down(); + +/** print error message and exit */ +void +fail(char *msg); + +/** print tarantool error message and exit */ +void +fail_tnt_error(char *msg, int error_code); + +/** print tarantool error message and exit */ +void +fail_tnt_perror(char *msg); + + +/*-------------------------------------------------------------------------- + * test cases functions + *--------------------------------------------------------------------------*/ + +/** update fields test case: simple set operation test */ +void +test_simple_set(); + +/** update fields test case: long set operation test */ +void +test_long_set(); + +/** update fields test case: append(set) operation test */ +void +test_append(); + +/** update fields test case: simple arithmetics operations test */ +void +test_simple_arith(); + +/** update fields test case: multi arithmetics operations test */ +void +test_multi_arith(); + +/** update fields test case: splice operations test */ +void +test_splice(); + +/** update fields test case: set and spice operations test */ +void +test_set_and_splice(); + +/** update fields test case: delete field operations test */ +void +test_delete_field(); + + +/*========================================================================== + * function definition + *==========================================================================*/ + +int +main(void) +{ + /* initialize suite */ + test_suite_setup(); + /* run tests */ + test_simple_set(); + test_long_set(); + test_append(); + test_simple_arith(); + test_multi_arith(); + test_splice(); + test_set_and_splice(); + test_delete_field(); + /* clean-up suite */ + test_suite_tear_down(); + return EXIT_SUCCESS; +} + + +/*-------------------------------------------------------------------------- + * tarantool management functions + *--------------------------------------------------------------------------*/ + +void +insert_tuple(struct tnt_tuple *tuple) +{ + if (tnt_insert(tnt, 0, TNT_FLAG_RETURN, tuple) < 0) + fail_tnt_perror("tnt_insert"); + if (tnt_flush(tnt) < 0) + fail_tnt_perror("tnt_flush"); + recv_command("insert"); +} + +void +select_tuple(i32 key) +{ + struct tnt_list tuple_list; + tnt_list_init(&tuple_list); + struct tnt_tuple *tuple = tnt_list_at(&tuple_list, NULL); + tnt_tuple(tuple, "%d", key); + if (tnt_select(tnt, 0, 0, 0, 1, &tuple_list) < 0) + fail_tnt_perror("tnt_select"); + if (tnt_flush(tnt) < 0) + fail_tnt_perror("tnt_flush"); + recv_command("select"); + tnt_list_free(&tuple_list); +} + +void +update(i32 key, struct tnt_stream *stream) +{ + struct tnt_tuple *k = tnt_tuple(NULL, "%d", key); + if (tnt_update(tnt, 0, TNT_FLAG_RETURN, k, stream) < 0) + fail_tnt_perror("tnt_update"); + if (tnt_flush(tnt) < 0) + fail_tnt_perror("tnt_flush"); + tnt_tuple_free(k); + recv_command("update fields"); +} + +void +update_set_i32(struct tnt_stream *stream, i32 field, i32 value) +{ + int result = tnt_update_assign(stream, field, (char *)&value, sizeof(value)); + if (result < 0) + fail_tnt_error("tnt_update_assign", result); +} + +void +update_set_str(struct tnt_stream *stream, i32 field, char *str) +{ + int result = tnt_update_assign(stream, field, str, strlen(str)); + if (result < 0) + fail_tnt_error("tnt_update_delete_field", result); +} + +void +update_splice_str(struct tnt_stream *stream, i32 field, i32 offset, i32 length, char *list) +{ + int result = tnt_update_splice(stream, field, offset, length, list, strlen(list)); + if (result < 0) + fail_tnt_error("tnt_update_splice", result); +} + +void +update_delete_field(struct tnt_stream *stream, i32 field) +{ + int result = tnt_update_delete(stream, field); + if (result < 0) + fail_tnt_error("tnt_update_delete", result); +} + +void +recv_command(char *command) +{ + struct tnt_iter i; + tnt_iter_stream(&i, tnt); + while (tnt_next(&i)) { + struct tnt_reply *r = TNT_ISTREAM_REPLY(&i); + printf("%s: respond %s (op: %"PRIu32", reqid: %"PRIu32", code: %"PRIu32", count: %"PRIu32")\n", + command, tnt_strerror(tnt), + r->op, + r->reqid, + r->code, + r->count); + struct tnt_iter it; + tnt_iter_list(&it, TNT_REPLY_LIST(r)); + while (tnt_next(&it)) { + struct tnt_tuple *tu = TNT_ILIST_TUPLE(&it); + print_tuple(tu); + } + tnt_iter_free(&it); + } + if (i.status == TNT_ITER_FAIL) + fail_tnt_perror("tnt_next"); + tnt_iter_free(&i); +} + +void +print_tuple(struct tnt_tuple *tuple) +{ + bool is_first = true; + printf("("); + + struct tnt_iter ifl; + tnt_iter(&ifl, tuple); + while (tnt_next(&ifl)) { + char *data = TNT_IFIELD_DATA(&ifl); + uint32_t size = TNT_IFIELD_SIZE(&ifl); + if (!is_first) { + printf(", "); + } + is_first = false; + + switch(size) { + case 1: + printf("%"PRIi8" (0x%02"PRIx8")", *(i8 *)data, *(i8 *)data); + break; + case 2: + printf("%"PRIi16" (0x%04"PRIx16")", *(i16 *)data, *(i16 *)data); + break; + case 4: + printf("%"PRIi32" (0x%08"PRIx32")", *(i32 *)data, *(i32 *)data); + break; + case 8: + printf("%"PRIi64" (0x%016"PRIx64")", *(i64 *)data, *(i64 *)data); + break; + default: + printf("'%.*s'", size, data); + break; + } + } + if (ifl.status == TNT_ITER_FAIL) + fail("tuple parsing error"); + tnt_iter_free(&ifl); + printf(")\n"); +} + + +/*-------------------------------------------------------------------------- + * test suite functions + *--------------------------------------------------------------------------*/ + +void +test_suite_setup() +{ + tnt = tnt_net(NULL); + if (tnt == NULL) { + fail("tnt_alloc"); + } + + tnt_set(tnt, TNT_OPT_HOSTNAME, "localhost"); + tnt_set(tnt, TNT_OPT_PORT, 33013); + + if (tnt_init(tnt) == -1) + fail_tnt_perror("tnt_init"); + if (tnt_connect(tnt) == -1) + fail_tnt_perror("tnt_connect"); +} + +void +test_suite_tear_down() +{ + tnt_stream_free(tnt); +} + +void +fail(char *msg) +{ + printf("fail: %s\n", msg); + exit(EXIT_FAILURE); +} + +void +fail_tnt_error(char *msg, int error_code) +{ + printf("fail: %s: %i\n", msg, error_code); + exit(EXIT_FAILURE); +} + +void +fail_tnt_perror(char *msg) +{ + printf("fail: %s: %s\n", msg, tnt_strerror(tnt)); + exit(EXIT_FAILURE); +} + + +/*-------------------------------------------------------------------------- + * test cases functions + *--------------------------------------------------------------------------*/ + +void +test_simple_set() +{ + printf(">>> test simple set\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%d%d%s", 1, 2, 0, ""); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test simple set field */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test simple set field\n"); + update_set_str(stream, 1, "new field value"); + update_set_str(stream, 2, ""); + update_set_str(stream, 3, "fLaC"); + update(1, stream); + tnt_stream_free(stream); + + /* test useless set operations */ + stream = tnt_buf(NULL); + printf("# set field\n"); + update_set_str(stream, 1, "value?"); + update_set_str(stream, 1, "very very very very very long field value?"); + update_set_str(stream, 1, "field's new value"); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test simple set done\n"); +} + +void +test_long_set() +{ + printf(">>> test long set\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s%s%s", 1, "first", "", "third"); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test set long value in empty field */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test set big value in empty field\n"); + update_set_str(stream, 2, long_string); + update(1, stream); + tnt_stream_free(stream); + + /* test replace long value to short */ + stream = tnt_buf(NULL); + printf("# test replace long value to short\n"); + update_set_str(stream, 2, "short string"); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test long set done\n"); +} + +void +test_append() +{ + printf(">>> test append\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s", 1, "first"); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test append field */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test append field\n"); + update_set_str(stream, 2, "second"); + update(1, stream); + tnt_stream_free(stream); + + /* test multi append field */ + stream = tnt_buf(NULL); + printf("# test multi append\n"); + update_set_str(stream, 3, "3"); + update_set_str(stream, 3, "new field value"); + update_set_str(stream, 3, "other new field value"); + update_set_str(stream, 3, "third"); + update(1, stream); + tnt_stream_free(stream); + + /* test append many field */ + stream = tnt_buf(NULL); + printf("# test append many fields\n"); + update_set_str(stream, 4, "fourth"); + update_set_str(stream, 5, "fifth"); + update_set_str(stream, 6, "sixth"); + update_set_str(stream, 7, "seventh"); + update_set_str(stream, 8, long_string); + update(1, stream); + tnt_stream_free(stream); + + /* test append and change field */ + stream = tnt_buf(NULL); + printf("# test append and change field\n"); + update_set_str(stream, 9, long_string); + update_splice_str(stream, 9, 1, 544, "ac"); + tnt_update_arith(stream, 9, TNT_UPDATE_XOR, 0x3ffffff); + tnt_update_arith(stream, 9, TNT_UPDATE_ADD, 1024); + update(1, stream); + tnt_stream_free(stream); + + /* test set to not an exist field */ + stream = tnt_buf(NULL); + printf("# test set to not an exist field\n"); + update_set_str(stream, 0xDEADBEEF, "invalid!"); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test append done\n"); +} + +void +test_simple_arith() +{ + printf(">>> test simple arith\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%d%d%d", 1, 2, 0, 0); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test simple add */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test simple add\n"); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 16); + update(1, stream); + tnt_stream_free(stream); + + /* test overflow add */ + stream = tnt_buf(NULL); + printf("# test overflow add\n"); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, INT_MAX); + update(1, stream); + tnt_stream_free(stream); + + /* test overflow add */ + stream = tnt_buf(NULL); + printf("# test underflow add\n"); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, INT_MIN); + update(1, stream); + tnt_stream_free(stream); + + /* test or */ + stream = tnt_buf(NULL); + printf("# test simple or\n"); + tnt_update_arith(stream, 2, TNT_UPDATE_OR, 0xbacf); + tnt_update_arith(stream, 3, TNT_UPDATE_OR, 0xfabc); + update(1, stream); + tnt_stream_free(stream); + + /* test xor */ + stream = tnt_buf(NULL); + printf("# test simple xor\n"); + tnt_update_arith(stream, 2, TNT_UPDATE_XOR, 0xffff); + tnt_update_arith(stream, 3, TNT_UPDATE_XOR, 0xffff); + update(1, stream); + tnt_stream_free(stream); + + /* test and */ + stream = tnt_buf(NULL); + printf("# test simple and\n"); + tnt_update_arith(stream, 2, TNT_UPDATE_AND, 0xf0f0); + tnt_update_arith(stream, 3, TNT_UPDATE_AND, 0x0f0f); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test simple arith done\n"); +} + +void +test_multi_arith() +{ + printf(">>> test multi splice\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s%d%s", 1, "first", 128, "third"); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test and */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test simple and\n"); + update_set_i32(stream, 2, 0); + update_set_str(stream, 1, "first field new value"); + tnt_update_arith(stream, 2, TNT_UPDATE_XOR, 0xF00F); + update_set_str(stream, 3, "third field new value"); + tnt_update_arith(stream, 2, TNT_UPDATE_OR, 0xF00F); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test multi arith done\n"); +} + +void +test_splice() +{ + printf(">>> test simple splice\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s%s%s", 1, "first", "hi, this is a test string!", "third"); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test cut from begin */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test cut from begin\n"); + update_splice_str(stream, 2, 0, 4, ""); + update(1, stream); + tnt_stream_free(stream); + + /* test cut from middle */ + stream = tnt_buf(NULL); + printf("# test cut from middle\n"); + update_splice_str(stream, 2, 9, -8, ""); + update(1, stream); + tnt_stream_free(stream); + + /* test cut from end */ + stream = tnt_buf(NULL); + printf("# test cut from end\n"); + update_splice_str(stream, 2, -1, 1, ""); + update(1, stream); + tnt_stream_free(stream); + + /* test insert before begin */ + stream = tnt_buf(NULL); + printf("# test insert before begin\n"); + update_splice_str(stream, 2, 0, 0, "Bonjour, "); + update(1, stream); + tnt_stream_free(stream); + + /* test insert */ + stream = tnt_buf(NULL); + printf("# test insert after end\n"); + update_splice_str(stream, 2, 10000, 0, " o_O!?"); + update(1, stream); + tnt_stream_free(stream); + + /* test replace in begin */ + stream = tnt_buf(NULL); + printf("# test replace in begin\n"); + update_splice_str(stream, 2, 0, 7, "Hello"); + update(1, stream); + tnt_stream_free(stream); + + /* test replace in middle */ + stream = tnt_buf(NULL); + printf("# test replace in middle\n"); + update_splice_str(stream, 2, 17, -6, "field"); + update(1, stream); + tnt_stream_free(stream); + + /* test replace in end */ + stream = tnt_buf(NULL); + printf("# test replace in end\n"); + update_splice_str(stream, 2, -6, 4, "! Is this Sparta"); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test simple splice done\n"); +} + +void +test_set_and_splice() +{ + printf(">>> test set and splice\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s%s%s", 1, "first", "hi, this is a test string!", "third"); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test set long string and splice to short */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test set long string and splice to short\n"); + update_set_str(stream, 2, long_string); + update_splice_str(stream, 2, 45, 500, " away away away"); + update(1, stream); + tnt_stream_free(stream); + + /* test set short value and splice to long */ + stream = tnt_buf(NULL); + printf("# test set short value and splice to long\n"); + update_set_str(stream, 2, "test"); + update_splice_str(stream, 2, -4, 4, long_string); + update(1, stream); + tnt_stream_free(stream); + + printf("<<< test set and splice done\n"); +} + +/** update fields test case: delete field operations test */ +void +test_delete_field() +{ + printf(">>> test delete field\n"); + + /* insert tuple */ + printf("# insert tuple\n"); + struct tnt_tuple *tuple = tnt_tuple(NULL, "%d%s%s%s%d%d%d%d%d%d%d%d%d%d", 1, + "first", "hi, this is a test string!", "third", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + insert_tuple(tuple); + tnt_tuple_free(tuple); + + /* test simple delete fields */ + struct tnt_stream *stream = tnt_buf(NULL); + printf("# test simple delete fields\n"); + update_delete_field(stream, 2); + update(1, stream); + tnt_stream_free(stream); + + /* test useless operations with delete fields*/ + stream = tnt_buf(NULL); + printf("# test useless operations with delete fields\n"); + update_set_i32(stream, 1, 0); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + update_delete_field(stream, 1); + update(1, stream); + tnt_stream_free(stream); + + /* test multi delete fields */ + stream = tnt_buf(NULL); + printf("# test multi delete fields\n"); + update_delete_field(stream, 2); + update_delete_field(stream, 3); + update_delete_field(stream, 4); + update_delete_field(stream, 5); + update_delete_field(stream, 6); + update_delete_field(stream, 7); + update_delete_field(stream, 8); + update_delete_field(stream, 9); + update_delete_field(stream, 10); + update(1, stream); + tnt_stream_free(stream); + + /* test delete and set */ + stream = tnt_buf(NULL); + printf("# test multi delete fields\n"); + update_delete_field(stream, 1); + update_set_i32(stream, 1, 3); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + tnt_update_arith(stream, 1, TNT_UPDATE_ADD, 1); + update(1, stream); + tnt_stream_free(stream); + + /* test append and delete */ + stream = tnt_buf(NULL); + printf("# test append and delete\n"); + update_set_str(stream, 3, "second"); + update_delete_field(stream, 3); + update_set_str(stream, 3, "third"); + update_set_str(stream, 4, "third"); + update_delete_field(stream, 4); + update_set_str(stream, 4, "third"); + update_set_str(stream, 4, "fourth"); + update_set_str(stream, 5, "fifth"); + update_set_str(stream, 6, "sixth"); + update_set_str(stream, 7, "seventh"); + update_set_str(stream, 8, "eighth"); + update_set_str(stream, 9, "ninth"); + update_delete_field(stream, 7); + update_delete_field(stream, 6); + update(1, stream); + tnt_stream_free(stream); + + /* test double delete */ + stream = tnt_buf(NULL); + printf("# test double delete\n"); + update_delete_field(stream, 3); + update_delete_field(stream, 3); + update(1, stream); + tnt_stream_free(stream); + select_tuple(1); + + /* test delete not an exist field */ + stream = tnt_buf(NULL); + printf("# test delete not an exist field\n"); + update_delete_field(stream, 0xDEADBEEF); + update(1, stream); + tnt_stream_free(stream); + select_tuple(1); + + printf("<<< test delete field done\n"); +} diff --git a/test/connector_c/update.result b/test/connector_c/update.result new file mode 100644 index 0000000000..23aaf2eb21 --- /dev/null +++ b/test/connector_c/update.result @@ -0,0 +1,197 @@ +>>> test simple set +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 2 (0x00000002), 0 (0x00000000), '') +# test simple set field +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'new field value', '', 1130450022 (0x43614c66)) +# set field +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'field's new value', '', 1130450022 (0x43614c66)) +<<< test simple set done +>>> test long set +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', '', 'third') +# test set big value in empty field +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'A long time ago, in a galaxy far, far away... +It is a period of civil war. Rebel +spaceships, striking from a hidden +base, have won their first victory +against the evil Galactic Empire. +During the battle, Rebel spies managed +to steal secret plans to the Empire's +ultimate weapon, the Death Star, an +armored space station with enough +power to destroy an entire planet. +Pursued by the Empire's sinister agents, +Princess Leia races home aboard her +starship, custodian of the stolen plans +that can save her people and restore +freedom to the galaxy....', 'third') +# test replace long value to short +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'short string', 'third') +<<< test long set done +>>> test append +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first') +# test append field +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'second') +# test multi append +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'second', 'third') +# test append many fields +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'A long time ago, in a galaxy far, far away... +It is a period of civil war. Rebel +spaceships, striking from a hidden +base, have won their first victory +against the evil Galactic Empire. +During the battle, Rebel spies managed +to steal secret plans to the Empire's +ultimate weapon, the Death Star, an +armored space station with enough +power to destroy an entire planet. +Pursued by the Empire's sinister agents, +Princess Leia races home aboard her +starship, custodian of the stolen plans +that can save her people and restore +freedom to the galaxy....') +# test append and change field +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'A long time ago, in a galaxy far, far away... +It is a period of civil war. Rebel +spaceships, striking from a hidden +base, have won their first victory +against the evil Galactic Empire. +During the battle, Rebel spies managed +to steal secret plans to the Empire's +ultimate weapon, the Death Star, an +armored space station with enough +power to destroy an entire planet. +Pursued by the Empire's sinister agents, +Princess Leia races home aboard her +starship, custodian of the stolen plans +that can save her people and restore +freedom to the galaxy....', 765239998 (0x2d9ca2be)) +# test set to not an exist field +update fields: respond ok (op: 19, reqid: 0, code: 13826, count: 0) +<<< test append done +>>> test simple arith +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 2 (0x00000002), 0 (0x00000000), 0 (0x00000000)) +# test simple add +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 18 (0x00000012), 0 (0x00000000), 0 (0x00000000)) +# test overflow add +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), -2147483631 (0x80000011), 0 (0x00000000), 0 (0x00000000)) +# test underflow add +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 17 (0x00000011), 0 (0x00000000), 0 (0x00000000)) +# test simple or +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 17 (0x00000011), 47823 (0x0000bacf), 64188 (0x0000fabc)) +# test simple xor +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 17 (0x00000011), 17712 (0x00004530), 1347 (0x00000543)) +# test simple and +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 17 (0x00000011), 16432 (0x00004030), 1283 (0x00000503)) +<<< test simple arith done +>>> test multi splice +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 128 (0x00000080), 'third') +# test simple and +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first field new value', 61455 (0x0000f00f), 'third field new value') +<<< test multi arith done +>>> test simple splice +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'hi, this is a test string!', 'third') +# test cut from begin +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'this is a test string!', 'third') +# test cut from middle +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'this is a string!', 'third') +# test cut from end +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'this is a string', 'third') +# test insert before begin +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'Bonjour, this is a string', 'third') +# test insert after end +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'Bonjour, this is a string o_O!?', 'third') +# test replace in begin +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'Hello, this is a string o_O!?', 'third') +# test replace in middle +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'Hello, this is a field o_O!?', 'third') +# test replace in end +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'Hello, this is a field! Is this Sparta!?', 'third') +<<< test simple splice done +>>> test set and splice +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'hi, this is a test string!', 'third') +# test set long string and splice to short +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'A long time ago, in a galaxy far, far away... away away away.', 'third') +# test set short value and splice to long +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'A long time ago, in a galaxy far, far away... +It is a period of civil war. Rebel +spaceships, striking from a hidden +base, have won their first victory +against the evil Galactic Empire. +During the battle, Rebel spies managed +to steal secret plans to the Empire's +ultimate weapon, the Death Star, an +armored space station with enough +power to destroy an entire planet. +Pursued by the Empire's sinister agents, +Princess Leia races home aboard her +starship, custodian of the stolen plans +that can save her people and restore +freedom to the galaxy....', 'third') +<<< test set and splice done +>>> test delete field +# insert tuple +insert: respond ok (op: 13, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'hi, this is a test string!', 'third', 1 (0x00000001), 2 (0x00000002), 3 (0x00000003), 4 (0x00000004), 5 (0x00000005), 6 (0x00000006), 7 (0x00000007), 8 (0x00000008), 9 (0x00000009), 10 (0x0000000a)) +# test simple delete fields +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'first', 'third', 1 (0x00000001), 2 (0x00000002), 3 (0x00000003), 4 (0x00000004), 5 (0x00000005), 6 (0x00000006), 7 (0x00000007), 8 (0x00000008), 9 (0x00000009), 10 (0x0000000a)) +# test useless operations with delete fields +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'third', 1 (0x00000001), 2 (0x00000002), 3 (0x00000003), 4 (0x00000004), 5 (0x00000005), 6 (0x00000006), 7 (0x00000007), 8 (0x00000008), 9 (0x00000009), 10 (0x0000000a)) +# test multi delete fields +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 'third', 10 (0x0000000a)) +# test multi delete fields +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 9 (0x00000009), 10 (0x0000000a)) +# test append and delete +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 9 (0x00000009), 10 (0x0000000a), 'third', 'fourth', 'fifth', 'eighth', 'ninth') +# test double delete +update fields: respond ok (op: 19, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 9 (0x00000009), 10 (0x0000000a), 'fourth', 'fifth', 'eighth', 'ninth') +select: respond ok (op: 17, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 9 (0x00000009), 10 (0x0000000a), 'fourth', 'fifth', 'eighth', 'ninth') +# test delete not an exist field +update fields: respond ok (op: 19, reqid: 0, code: 13826, count: 0) +select: respond ok (op: 17, reqid: 0, code: 0, count: 1) +(1 (0x00000001), 9 (0x00000009), 10 (0x0000000a), 'fourth', 'fifth', 'eighth', 'ninth') +<<< test delete field done diff --git a/test/connector_c/update.test b/test/connector_c/update.test new file mode 100644 index 0000000000..75bf1f5986 --- /dev/null +++ b/test/connector_c/update.test @@ -0,0 +1,15 @@ +# encoding: tarantool +import subprocess +import sys +import os + +p = subprocess.Popen([ os.path.join(builddir, "test/connector_c/update") ], stdout=subprocess.PIPE) +p.wait() +for line in p.stdout.readlines(): + sys.stdout.write(line) + +# resore default suite +server.stop() +server.deploy(self.suite_ini["config"]) +server.start() +# vim: syntax=python -- GitLab