diff --git a/.gitignore b/.gitignore index fee89f1c6ed84af38adbf97c5b2c935b33f5bfd2..b8e09d45518dac401f70d0ae138d530997fe4dc6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ install_manifest.txt tarantool-*.tar.gz doc/tnt.ent doc/user/tarantool_user_guide.html +doc/developer/tarantool_developer_guide.html third_party/luajit/src/luajit third_party/luajit/lib/vmdef.lua third_party/luajit/src/buildvm diff --git a/CMakeLists.txt b/CMakeLists.txt index 7adcf4e1195b2ac95a1b0a12b14c29698c631929..6904f17f5f14f269c0a1b6c650b8bd68605b850e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,7 +125,7 @@ execute_process (COMMAND ${GIT} describe HEAD # set (CPACK_PACKAGE_VERSION_MAJOR "1") set (CPACK_PACKAGE_VERSION_MINOR "4") -set (CPACK_PACKAGE_VERSION_PATCH "3") +set (CPACK_PACKAGE_VERSION_PATCH "4") if (TARANTOOL_VERSION STREQUAL "") set (TARANTOOL_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") @@ -171,6 +171,16 @@ include_directories("${PROJECT_SOURCE_DIR}/connector/c/tnt/include") include_directories("${PROJECT_SOURCE_DIR}/connector/c/tntsql/include") include_directories("${PROJECT_SOURCE_DIR}/connector/c/tntnet/include") +# +# Specify prefixes +# +if (NOT DEFINED CMAKE_SYSCONF_DIR) + set (CMAKE_SYSCONF_DIR "${CMAKE_INSTALL_PREFIX}/etc") +endif() +if (NOT DEFINED CMAKE_LOCALSTATE_DIR) + set (CMAKE_LOCALSTATE_DIR "${CMAKE_INSTALL_PREFIX}/var") +endif() + # # Now handle all configuration options. # @@ -246,7 +256,7 @@ add_subdirectory(doc EXCLUDE_FROM_ALL) add_subdirectory(extra) install (FILES README LICENSE doc/box-protocol.txt - DESTINATION doc) + DESTINATION share/doc/tarantool) include (cmake/tarantool_cpack.cmake) # diff --git a/cfg/tarantool_box_cfg.c b/cfg/tarantool_box_cfg.c index 71301db96394f372497b2b45ad237f86af3a9cf1..b0d6e2747e52fb186fd95f22d0dc86b9d3cf25d0 100644 --- a/cfg/tarantool_box_cfg.c +++ b/cfg/tarantool_box_cfg.c @@ -1687,7 +1687,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char out_warning(CNF_NOMEMORY, "No memory to output value"); return NULL; } - sprintf(*v, "%s", c->space[i->idx_name__space]->enabled ? "true" : "false"); + sprintf(*v, "%s", c->space[i->idx_name__space]->enabled == -1 ? "false" : c->space[i->idx_name__space]->enabled ? "true" : "false"); snprintf(buf, PRINTBUFLEN-1, "space[%d].enabled", i->idx_name__space); i->state = S_name__space__cardinality; return buf; @@ -1740,7 +1740,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char out_warning(CNF_NOMEMORY, "No memory to output value"); return NULL; } - sprintf(*v, "%s", c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique ? "true" : "false"); + sprintf(*v, "%s", c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique == -1 ? "false" : c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique ? "true" : "false"); snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].unique", i->idx_name__space, i->idx_name__space__index); i->state = S_name__space__index__key_field; return buf; diff --git a/client/tarantool/CMakeLists.txt b/client/tarantool/CMakeLists.txt index 14254f5d11b8667949a5d618403e1f666ee9ea76..cdaf531fd4304ddcc02e994d060ef04f66b53392 100644 --- a/client/tarantool/CMakeLists.txt +++ b/client/tarantool/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set (cli_deps readline history) # trying to resolve readline dependencies +# find_library(WITH_NCURSES_LIB NAMES "ncurses") if (WITH_NCURSES_LIB) set(cli_deps ${cli_deps} ncurses) @@ -31,4 +32,4 @@ add_executable(${cli} ${cli_sources} ${CMAKE_SOURCE_DIR}/core/errcode.c) set_target_properties(${cli} PROPERTIES COMPILE_FLAGS "${core_cflags}") target_link_libraries (${cli} ${cli_libs}) -install (PROGRAMS ${cli} DESTINATION bin) +install (TARGETS ${cli} DESTINATION bin) diff --git a/client/tarantool/tnt.c b/client/tarantool/tnt.c index 563571feb7c7579a411e7c053075b56e92902594..1c80da4d5ba3eedb2f594659d8d3affda11f225b 100644 --- a/client/tarantool/tnt.c +++ b/client/tarantool/tnt.c @@ -36,6 +36,7 @@ #include <readline/readline.h> #include <readline/history.h> +#include <errcode.h> #include <third_party/gopt/gopt.h> #include <connector/c/tnt/include/tnt.h> #include <connector/c/tntsql/include/tnt_sql.h> @@ -73,14 +74,11 @@ static int query_reply_handle(struct tnt_stream *t, struct tnt_reply *r) { break; } if (tnt_error(t) != TNT_EOK) { - printf("FAIL, %s (op: %d, reqid: %d, code: %d, count: %d)\n", - tnt_strerror(t), - r->op, - r->reqid, - r->code, - r->count); - if (r->error) - printf("error: %s\n", r->error); + printf("ERROR, %s\n", tnt_strerror(t)); + return -1; + } else if (r->code != 0) { + printf("ERROR, %s (%s)\n", + ((r->error) ? r->error : ""), tnt_errcode_str(r->code >> 8)); return -1; } printf("OK, %d rows affected\n", r->count); @@ -137,15 +135,15 @@ static int query(struct tnt_stream *t, char *q) { char *e = NULL; - int ops = tnt_query(t, q, strlen(q), &e); - if (ops == -1) { + int rc = tnt_query(t, q, strlen(q), &e); + if (rc == -1) { if (e) { printf("error: %s", e); free(e); } return -1; } - int rc = tnt_flush(t); + rc = tnt_flush(t); if (rc < 0) { printf("error: %s\n", tnt_strerror(t)); return -1; diff --git a/connector/c/tnt/CMakeLists.txt b/connector/c/tnt/CMakeLists.txt index 4cb0291af8bfeb9bd54e1dea96ee48aa371eda9f..0aa3c6de65c096b4cac03b36f197a58d82216a69 100644 --- a/connector/c/tnt/CMakeLists.txt +++ b/connector/c/tnt/CMakeLists.txt @@ -58,7 +58,7 @@ set_target_properties(tnt PROPERTIES COMPILE_FLAGS "${tnt_cflags}") # Shared library # -project(tnt) +project(tnt_shared) add_library(tnt_shared SHARED ${tnt_sources}) set_target_properties(tnt_shared PROPERTIES OUTPUT_NAME tnt) set_target_properties(tnt_shared PROPERTIES COMPILE_FLAGS "${tnt_cflags}") diff --git a/connector/c/tnt/include/tnt.h b/connector/c/tnt/include/tnt.h index be0d1ca0ae5c116173e8d6d82e7c9e2e8d7eaeb7..cf50e685ca9a6a74ce3f47f7e3340d015132d645 100644 --- a/connector/c/tnt/include/tnt.h +++ b/connector/c/tnt/include/tnt.h @@ -26,6 +26,10 @@ * SUCH DAMAGE. */ +#ifdef __cplusplus +extern "C" { +#endif + #include <stdint.h> #include <stdarg.h> @@ -44,4 +48,8 @@ #include <tnt_call.h> #include <tnt_select.h> +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* TNT_H_INCLUDED */ diff --git a/connector/c/tnt/tnt_call.c b/connector/c/tnt/tnt_call.c index 13e0f89fc74ed9b1eceb404c05c918a753f16611..23bf002c717053ec229d7ff303c1ebf0219d5b75 100644 --- a/connector/c/tnt/tnt_call.c +++ b/connector/c/tnt/tnt_call.c @@ -65,6 +65,8 @@ tnt_call(struct tnt_stream *s, uint32_t flags, char *proc, hdr.type = TNT_OP_CALL; hdr.len = sizeof(struct tnt_header_call) + proc_enc_size + proc_len + args->size; + if (args->size == 0) + hdr.len += 4; hdr.reqid = s->reqid; /* filling call header */ struct tnt_header_call hdr_call; @@ -79,7 +81,13 @@ tnt_call(struct tnt_stream *s, uint32_t flags, char *proc, v[2].iov_len = proc_enc_size; v[3].iov_base = proc; v[3].iov_len = proc_len; - v[4].iov_base = args->data; - v[4].iov_len = args->size; + if (args->size == 0) { + uint32_t argc = 0; + v[4].iov_base = &argc; + v[4].iov_len = 4; + } else { + v[4].iov_base = args->data; + v[4].iov_len = args->size; + } return s->writev(s, v, 5); } diff --git a/connector/c/tnt/tnt_iter.c b/connector/c/tnt/tnt_iter.c index eb95cfbd0dbc9cc63ec015cf9c4e3c991be64ec7..0075ef929499c073dcd78a0a2d3643981bdc218a 100644 --- a/connector/c/tnt/tnt_iter.c +++ b/connector/c/tnt/tnt_iter.c @@ -196,7 +196,7 @@ struct tnt_iter *tnt_iter_stream(struct tnt_iter *i, struct tnt_stream *s) { i = tnt_iter_tryalloc(i); if (i == NULL) return NULL; - i->type = TNT_ITER_LIST; + i->type = TNT_ITER_STREAM; i->next = tnt_iter_stream_next; i->rewind = NULL; i->free = tnt_iter_stream_free; diff --git a/connector/c/tnt/tnt_reply.c b/connector/c/tnt/tnt_reply.c index 3f58b1210b715b9c2257f3cfe61cd16b6ba7fc32..f8313e1e2fb545b85af71a80ec6c0a8fb278a3b3 100644 --- a/connector/c/tnt/tnt_reply.c +++ b/connector/c/tnt/tnt_reply.c @@ -179,8 +179,10 @@ int tnt_reply_from(struct tnt_reply *r, tnt_replyf_t rcv, void *ptr) { * * returns zero on fully read reply, or NULL on error. */ -static ssize_t tnt_reply_cb(size_t *off, char *buf, ssize_t size) { - memcpy(buf, buf + *off, size); +static ssize_t tnt_reply_cb(void *ptr[2], char *buf, ssize_t size) { + char *src = ptr[0]; + ssize_t *off = ptr[1]; + memcpy(buf, src + *off, size); *off += size; return size; } @@ -195,13 +197,14 @@ int tnt_reply(struct tnt_reply *r, char *buf, size_t size, size_t *off) { return 1; } struct tnt_header *hdr = (struct tnt_header*)buf; - if (size < hdr->len) { + if (size < sizeof(struct tnt_header) + hdr->len) { if (off) - *off = hdr->len - size; + *off = (sizeof(struct tnt_header) + hdr->len) - size; return 1; } size_t offv = 0; - int rc = tnt_reply_from(r, (tnt_replyf_t)tnt_reply_cb, &offv); + void *ptr[2] = { buf, &offv }; + int rc = tnt_reply_from(r, (tnt_replyf_t)tnt_reply_cb, ptr); if (off) *off = offv; return rc; diff --git a/connector/c/tntnet/CMakeLists.txt b/connector/c/tntnet/CMakeLists.txt index 9fb73cf3202cc9dab6bbd2158363c8ae7ae4c386..2df1f3a72febc49db51171bda9452de3eb72be00 100644 --- a/connector/c/tntnet/CMakeLists.txt +++ b/connector/c/tntnet/CMakeLists.txt @@ -49,7 +49,7 @@ set_target_properties(tntnet PROPERTIES COMPILE_FLAGS "${tntnet_cflags}") # Shared library # -project(tntnet) +project(tntnet_shared) add_library(tntnet_shared SHARED ${tntnet_sources}) set_target_properties(tntnet_shared PROPERTIES OUTPUT_NAME tntnet) set_target_properties(tntnet_shared PROPERTIES COMPILE_FLAGS "${tntnet_cflags}") diff --git a/connector/c/tntnet/tnt_io.c b/connector/c/tntnet/tnt_io.c index ea04533311d5a1725022f5f0b1eb7fd3e5dff658..abee2ec67bf984e057745be8e79a6bfa745cb632 100644 --- a/connector/c/tntnet/tnt_io.c +++ b/connector/c/tntnet/tnt_io.c @@ -251,10 +251,10 @@ tnt_io_send_raw(struct tnt_stream_net *s, char *buf, size_t size, int all) do { ssize_t r; if (s->sbuf.tx) { - r = s->sbuf.tx(s->sbuf.buf, buf, size); + r = s->sbuf.tx(s->sbuf.buf, buf + off, size - off); } else { do { - r = send(s->fd, buf, size, 0); + r = send(s->fd, buf + off, size - off, 0); } while (r == -1 && (errno == EINTR)); } if (r <= 0) { @@ -368,10 +368,10 @@ tnt_io_recv_raw(struct tnt_stream_net *s, char *buf, size_t size, int all) do { ssize_t r; if (s->rbuf.tx) { - r = s->rbuf.tx(s->rbuf.buf, buf, size); + r = s->rbuf.tx(s->rbuf.buf, buf + off, size - off); } else { do { - r = recv(s->fd, buf, size, 0); + r = recv(s->fd, buf + off, size - off, 0); } while (r == -1 && (errno == EINTR)); } if (r <= 0) { diff --git a/connector/c/tntsql/CMakeLists.txt b/connector/c/tntsql/CMakeLists.txt index 9b011663aaea88320dce611bf08dac0d295d7510..49ac24c258eaa5d493e2a193f099d9afdcabcf90 100644 --- a/connector/c/tntsql/CMakeLists.txt +++ b/connector/c/tntsql/CMakeLists.txt @@ -48,7 +48,7 @@ set_target_properties(tntsql PROPERTIES COMPILE_FLAGS "${tntsql_cflags}") # Shared library # -project(tntsql) +project(tntsql_shared) add_library(tntsql_shared SHARED ${tntsql_sources}) set_target_properties(tntsql_shared PROPERTIES OUTPUT_NAME tntsql) set_target_properties(tntsql_shared PROPERTIES COMPILE_FLAGS "${tntsql_cflags}") diff --git a/connector/c/tntsql/include/tnt_lex.h b/connector/c/tntsql/include/tnt_lex.h index aca782458dc679b56b475ad2865b043cafce2fd1..921d01a32c6f70fd6f5464ae382f75e769828336 100644 --- a/connector/c/tntsql/include/tnt_lex.h +++ b/connector/c/tntsql/include/tnt_lex.h @@ -46,9 +46,11 @@ enum { TNT_TK_DELETE, TNT_TK_FROM, TNT_TK_INSERT, + TNT_TK_REPLACE, TNT_TK_INTO, TNT_TK_VALUES, TNT_TK_SELECT, + TNT_TK_LIMIT, TNT_TK_CALL, TNT_TK_OR }; diff --git a/connector/c/tntsql/tnt_lex.c b/connector/c/tntsql/tnt_lex.c index 3f1d1e70be9b86cd9798edfeabe30b1fcb33bb28..96ddc5715bea034406f34921df2cb14b7c8210a9 100644 --- a/connector/c/tntsql/tnt_lex.c +++ b/connector/c/tntsql/tnt_lex.c @@ -136,20 +136,22 @@ static struct { int tk; } tnt_keywords[] = { - { "PING", 4, TNT_TK_PING }, - { "UPDATE", 6, TNT_TK_UPDATE }, - { "SET", 3, TNT_TK_SET }, - { "WHERE", 5, TNT_TK_WHERE }, - { "SPLICE", 6, TNT_TK_SPLICE }, - { "DELETE", 6, TNT_TK_DELETE }, - { "FROM", 4, TNT_TK_FROM }, - { "INSERT", 6, TNT_TK_INSERT }, - { "INTO", 4, TNT_TK_INTO }, - { "VALUES", 6, TNT_TK_VALUES }, - { "SELECT", 6, TNT_TK_SELECT }, - { "OR", 2, TNT_TK_OR }, - { "CALL", 4, TNT_TK_CALL }, - { NULL, 0, TNT_TK_NONE } + { "PING", 4, TNT_TK_PING }, + { "UPDATE", 6, TNT_TK_UPDATE }, + { "SET", 3, TNT_TK_SET }, + { "WHERE", 5, TNT_TK_WHERE }, + { "SPLICE", 6, TNT_TK_SPLICE }, + { "DELETE", 6, TNT_TK_DELETE }, + { "FROM", 4, TNT_TK_FROM }, + { "INSERT", 6, TNT_TK_INSERT }, + { "REPLACE", 7, TNT_TK_REPLACE }, + { "INTO", 4, TNT_TK_INTO }, + { "VALUES", 6, TNT_TK_VALUES }, + { "SELECT", 6, TNT_TK_SELECT }, + { "OR", 2, TNT_TK_OR }, + { "LIMIT", 5, TNT_TK_LIMIT }, + { "CALL", 4, TNT_TK_CALL }, + { NULL, 0, TNT_TK_NONE } }; char* @@ -157,7 +159,7 @@ tnt_lex_nameof(int tk) { /* system tokens */ switch (tk) { - case TNT_TK_EOF: return "EOF"; + case TNT_TK_EOF: return "End-Of-Statement"; case TNT_TK_ERROR: return "ERROR"; case TNT_TK_NUM: return "NUM"; case TNT_TK_STRING: return "STRING"; diff --git a/connector/c/tntsql/tnt_sql.c b/connector/c/tntsql/tnt_sql.c index 0c3b5943f4c068e7dc3205524d6e0684175148a1..c8e9fc154e269dffa7650a969c52ac4c2793921c 100644 --- a/connector/c/tntsql/tnt_sql.c +++ b/connector/c/tntsql/tnt_sql.c @@ -41,7 +41,6 @@ struct tnt_sql { struct tnt_stream *s; struct tnt_lex *l; - int ops; char *error; }; @@ -115,12 +114,14 @@ tnt_sqltryv(struct tnt_sql *sql, int tk, struct tnt_tk **tkp) { /* key-value parsing for tuple operation. */ static bool -tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key) +tnt_sql_keyval(struct tnt_sql *sql, struct tnt_tuple *tu, bool key, struct tnt_tk **kt) { /* key */ struct tnt_tk *k = NULL; if (key && (!tnt_sqltkv(sql, TNT_TK_KEY, &k) || !tnt_sqltk(sql, '='))) return false; + if (kt) + *kt = k; /* value */ struct tnt_tk *v = NULL; if (tnt_lex(sql->l, &v) == TNT_TK_ERROR) @@ -135,6 +136,27 @@ tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key) return true; } +static bool +tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key) { + return tnt_sql_keyval(sql, tu, key, NULL); +} + +static bool +tnt_sql_kv_select(struct tnt_sql *sql, struct tnt_tuple *tu, int32_t *index) +{ + struct tnt_tk *key = NULL; + bool rc = tnt_sql_keyval(sql, tu, true, &key); + if (rc == false) + return false; + if (*index == -1) + *index = TNT_TK_I(key); + else + if (*index != TNT_TK_I(key)) + return tnt_sql_error(sql, key, + "select key values must refer to the same index"); + return true; +} + #define tnt_expect(a) \ do { if (!(a)) goto error; } while (0) @@ -223,6 +245,7 @@ tnt_sql_stmt_update(struct tnt_sql *sql, struct tnt_tuple *tu, struct tnt_stream tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE)); /* predicate */ tnt_expect(tnt_sql_kv(sql, tu, true)); + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); if (tnt_update(sql->s, TNT_TK_I(tn), 0, tu, u) == -1) { tnt_sql_error(sql, tn, "update failed"); goto error; @@ -244,11 +267,13 @@ tnt_sql_stmt(struct tnt_sql *sql) tnt_list_init(&tuples); tnt_buf(&update); + int flags = 0; struct tnt_tk *tk = NULL, *tn = NULL; bool rc = false; switch (tnt_lex(sql->l, &tk)) { - /* INSERT [INTO] TABLE VALUES ( list ) */ + /* <INSERT|REPLACE> [INTO] TABLE VALUES ( list ) */ case TNT_TK_INSERT: + case TNT_TK_REPLACE: tnt_sqltry(sql, TNT_TK_INTO); if (sql->error) goto error; @@ -263,18 +288,20 @@ tnt_sql_stmt(struct tnt_sql *sql) goto error; break; } + flags = TNT_FLAG_ADD; + if (tk->tk == TNT_TK_REPLACE) + flags = TNT_FLAG_REPLACE; tnt_expect(tnt_sqltk(sql, ')')); - if (tnt_insert(sql->s, TNT_TK_I(tn), 0, &tu) == -1) { + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); + if (tnt_insert(sql->s, TNT_TK_I(tn), flags, &tu) == -1) { tnt_sql_error(sql, tk, "insert failed"); goto error; } - sql->ops++; break; /* UPDATE TABLE SET operations WHERE predicate */ case TNT_TK_UPDATE: if (!tnt_sql_stmt_update(sql, &tu, &update)) goto error; - sql->ops++; break; /* DELETE FROM TABLE WHERE predicate */ case TNT_TK_DELETE: @@ -283,33 +310,43 @@ tnt_sql_stmt(struct tnt_sql *sql) tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE)); /* predicate */ tnt_expect(tnt_sql_kv(sql, &tu, true)); + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); if (tnt_delete(sql->s, TNT_TK_I(tn), 0, &tu) == -1) { tnt_sql_error(sql, tk, "delete failed"); goto error; } - sql->ops++; break; - /* SELECT * FROM TABLE WHERE predicate OR predicate... */ - case TNT_TK_SELECT: + /* SELECT * FROM TABLE WHERE predicate OR predicate... LIMIT NUM */ + case TNT_TK_SELECT: { tnt_expect(tnt_sqltk(sql, '*')); tnt_expect(tnt_sqltk(sql, TNT_TK_FROM)); tnt_expect(tnt_sqltkv(sql, TNT_TK_TABLE, &tn)); tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE)); + int32_t index = -1; while (1) { struct tnt_tuple *tup = tnt_list_at(&tuples, NULL); - tnt_expect(tnt_sql_kv(sql, tup, true)); + tnt_expect(tnt_sql_kv_select(sql, tup, &index)); if (tnt_sqltry(sql, TNT_TK_OR)) continue; if (sql->error) goto error; break; } - if (tnt_select(sql->s, TNT_TK_I(tn), 0, 0, 1000, &tuples) == -1) { + uint32_t limit = UINT32_MAX; + if (tnt_sqltry(sql, TNT_TK_LIMIT)) { + struct tnt_tk *ltk; + tnt_expect(tnt_sqltkv(sql, TNT_TK_NUM, <k)); + limit = TNT_TK_I(ltk); + } else + if (sql->error) + goto error; + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); + if (tnt_select(sql->s, TNT_TK_I(tn), index, 0, limit, &tuples) == -1) { tnt_sql_error(sql, tk, "select failed"); goto error; } - sql->ops++; break; + } /* CALL NAME[{.NAME}+](STRING [{,STRING}+]) */ case TNT_TK_CALL: { char proc[512]; @@ -342,26 +379,28 @@ tnt_sql_stmt(struct tnt_sql *sql) } tnt_expect(tnt_sqltk(sql, ')')); noargs: + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); if (tnt_call(sql->s, 0, proc, &tu) == -1) { tnt_sql_error(sql, tk, "call failed"); goto error; } - sql->ops++; break; } /* PING */ case TNT_TK_PING: + tnt_expect(tnt_sqltk(sql, TNT_TK_EOF)); if (tnt_ping(sql->s) == -1) { tnt_sql_error(sql, tk, "ping failed"); goto error; } - sql->ops++; + break; + case TNT_TK_EOF: break; case TNT_TK_ERROR: return tnt_sql_error(sql, tk, "%s", sql->l->error); default: return tnt_sql_error(sql, tk, - "insert, update, delete, select, call, ping are expected"); + "insert, replace, update, delete, select, call, ping are expected"); } rc = true; error: @@ -376,24 +415,7 @@ tnt_sql_stmt(struct tnt_sql *sql) /* primary sql grammar parsing function. */ static bool tnt_sql(struct tnt_sql *sql) { - struct tnt_tk *tk; - while (1) { - switch (tnt_lex(sql->l, &tk)) { - case TNT_TK_ERROR: - return tnt_sql_error(sql, NULL, "%s", sql->l->error); - case TNT_TK_EOF: - return true; - default: - tnt_lex_push(sql->l, tk); - if (!tnt_sql_stmt(sql)) - return false; - if (tnt_sqltry(sql, ';')) - continue; - if (sql->error) - return false; - break; - } - } + return tnt_sql_stmt(sql); } /* @@ -406,7 +428,7 @@ static bool tnt_sql(struct tnt_sql *sql) { * qsize - query size * e - error description string * - * returns number of operations processed on succes, or -1 on error + * returns 0 on success, or -1 on error * and string description returned (must be freed after use). */ int @@ -415,7 +437,7 @@ tnt_query(struct tnt_stream *s, char *q, size_t qsize, char **e) struct tnt_lex l; if (!tnt_lex_init(&l, (unsigned char*)q, qsize)) return -1; - struct tnt_sql sql = { s, &l, 0, NULL}; + struct tnt_sql sql = { s, &l, NULL }; bool ret = tnt_sql(&sql); if (e) { *e = sql.error; @@ -424,7 +446,7 @@ tnt_query(struct tnt_stream *s, char *q, size_t qsize, char **e) tnt_mem_free(sql.error); } tnt_lex_free(&l); - return (ret) ? sql.ops : -1; + return (ret) ? 0 : -1; } /* @@ -450,11 +472,12 @@ tnt_query_is(char *q, size_t qsize) case TNT_TK_EOF: break; default: - if (tk->tk == TNT_TK_PING || - tk->tk == TNT_TK_INSERT || - tk->tk == TNT_TK_UPDATE || - tk->tk == TNT_TK_SELECT || - tk->tk == TNT_TK_DELETE || + if (tk->tk == TNT_TK_PING || + tk->tk == TNT_TK_INSERT || + tk->tk == TNT_TK_REPLACE || + tk->tk == TNT_TK_UPDATE || + tk->tk == TNT_TK_SELECT || + tk->tk == TNT_TK_DELETE || tk->tk == TNT_TK_CALL) rc = 1; break; diff --git a/connector/perl/MANIFEST b/connector/perl/MANIFEST index a6c5f9954c52a8f15db2066861100b238c79a9e3..391d3da256473205118072f9920d131ebfed47a7 100644 --- a/connector/perl/MANIFEST +++ b/connector/perl/MANIFEST @@ -1,5 +1,15 @@ Makefile.PL MANIFEST lib/MR/IProto.pm +lib/MR/IProto/Cluster.pm +lib/MR/IProto/Cluster/Server.pm +lib/MR/IProto/Connection.pm +lib/MR/IProto/Connection/Async.pm +lib/MR/IProto/Connection/Sync.pm +lib/MR/IProto/Error.pm +lib/MR/IProto/Message.pm +lib/MR/IProto/Request.pm +lib/MR/IProto/Response.pm lib/MR/SilverBox.pm -lib/MR/Storage/Const.pm +lib/MR/Tarantool/Box.pm +lib/MR/Tarantool/Box/Singleton.pm diff --git a/connector/perl/Makefile.PL b/connector/perl/Makefile.PL index 08d6dabb058446d6641780f0d15dba3c88ece0ae..362a6cdb8a593079e02e451eb25e434fd51e2def 100644 --- a/connector/perl/Makefile.PL +++ b/connector/perl/Makefile.PL @@ -1,16 +1,19 @@ use ExtUtils::MakeMaker; WriteMakefile( - NAME => "MR::SilverBox", - VERSION_FROM => "lib/MR/SilverBox.pm", + NAME => "MR::Tarantool", + VERSION_FROM => "lib/MR/Tarantool/Box.pm", MAKEFILE => 'Makefile', PREREQ_PM => { 'Scalar::Util' => 0, + 'AnyEvent' => 0, 'List::Util' => 0, 'List::MoreUtils' => 0, - 'Time::HiRes' => 0, + 'Mouse' => 0, + 'MRO::Compat' => 0, 'String::CRC32' => 0, + 'Time::HiRes' => 0, 'Exporter' => 0, - 'Fcntl' => 0, + 'Class::Singleton' => 0, }, ); diff --git a/connector/perl/lib/MR/IProto.pm b/connector/perl/lib/MR/IProto.pm index f32ee14ac14824d4cc52e27b9e7599be19df10da..152b226620637bd80f53aadeb40cb7fd311f84e0 100644 --- a/connector/perl/lib/MR/IProto.pm +++ b/connector/perl/lib/MR/IProto.pm @@ -1,727 +1,661 @@ package MR::IProto; -use strict; +=head1 NAME -use Socket qw(PF_INET SOCK_STREAM SOL_SOCKET SO_SNDTIMEO SO_RCVTIMEO SO_KEEPALIVE TCP_NODELAY); -use String::CRC32 qw(crc32); -use Time::HiRes qw/time sleep/; -use Fcntl; +MR::IProto - iproto network protocol client -use vars qw($VERSION $PROTO_TCP %sockets); -$VERSION = 0; +=head1 SYNOPSIS -use overload '""' => sub { "$_[0]->{name}\[$_[0]->{_last_server}]" }; +IProto client can be created with full control of +its behaviour: -BEGIN { - if (eval {require 5.8.3}) { - require bytes; - import bytes; - require warnings; - import warnings; - } -} + my $client = MR::IProto->new( + cluster => MR::IProto::Cluster->new( + servers => [ + MR::IProto::Cluster::Server->new( + host => 'xxx.xxx.xxx.xxx', + port => xxxx, + ), + ... + ], + ), + ); -$PROTO_TCP = 0; -%sockets = (); +Or without it: -sub DEFAULT_RETRY_DELAY () { 0 } -sub DEFAULT_MAX_REQUEST_RETRIES () { 2 } -sub DEFAULT_TIMEOUT () { 2 } + my $client = MR::IProto->new( + servers => 'xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx', + ); -sub RR () { 1 } -sub HASH () { 2 } -sub KETAMA () { 3 } +Messages can be prepared and processed using objects (requires some more CPU): -sub confess { die @_ }; + my $request = MyProject::Message::MyOperation::Request->new( + arg1 => 1, + arg2 => 2, + ); + my $response = $client->send($request); + # $response isa My::Project::Message::MyOperation::Response. + # Of course, both message classes (request and reply) must + # be implemented by user. + +Or without them: + + my $response = $client->send({ + msg => x, + data => [...], + pack => 'xxx', + unpack => sub { + my ($data) = @_; + return (...); + }, + }); -sub DisconnectAll { - close $_ foreach (values %sockets); - %sockets = (); -} +Messages can be sent synchronously: -sub new { - my ($class, $args) = @_; - my $self = {}; - bless $self, $class; - - $self->{debug} = $args->{debug} || 0; - $self->{balance} = $args->{balance} && $args->{balance} eq 'hash-crc32' ? HASH() : RR(); - $self->{balance} = KETAMA() if $args->{balance} && $args->{balance} eq 'ketama'; - $self->{rotateservers} = 1 unless $args->{norotateservers}; - $self->{max_request_retries} = $args->{max_request_retries} || DEFAULT_MAX_REQUEST_RETRIES(); - $self->{retry_delay} = $args->{retry_delay} || DEFAULT_RETRY_DELAY(); - $self->{dump_no_ints} = 1 if $args->{dump_no_ints}; - $self->{tcp_nodelay} = 1 if $args->{tcp_nodelay}; - $self->{tcp_keepalive} = $args->{tcp_keepalive} || 0; - $self->{param} = $args->{param}; - $self->{_last_server} = ''; - - $self->{name} = $args->{name} or ($self->{name}) = caller; - - my $servers = $args->{servers} || confess("${class}->new: no servers given"); - _parse_servers $self 'servers', $servers; - - if ($self->{balance} == RR()) { - $self->{aviable_servers} = [@{$self->{servers}}]; #make copy - $servers = $args->{broadcast_servers} || ''; # confess("${class}->new: no broadcast servers given"); - _parse_servers $self 'broadcast_servers', $servers; - } elsif ($self->{balance} == KETAMA()) { - $self->{'ketama'} = []; - for my $s (@{$self->{'servers'}}) { - $s->{'ok'} = 1; - for my $i (0..10) { - push @{$self->{'ketama'}}, [crc32($s->{'repr'}.$i), $s]; - } - } - @{$self->{'ketama'}} = sort {$a->[0] cmp $b->[0]} @{$self->{'ketama'}}; - } + my $response = $client->send($response); + # exception is raised if error is occured + # besides $@ you can check $! to identify reason of error - my $timeout = exists $args->{timeout} ? $args->{timeout} : DEFAULT_TIMEOUT(); - SetTimeout $self $timeout; +Or asynchronously: - confess "${class}: no servers given" unless @{$self->{servers}}; - return $self; -} + use AnyEvent; + my $callback = sub { + my ($reply, $error) = @_; + # on error $error is defined and $! can be set + return; + }; + $client->send($request, $callback); + # callback is called when reply is received or error is occured -sub GetParam { - return $_[0]->{param}; -} +=head1 DESCRIPTION -sub Chat { - my ($self, %message) = @_; +This client is used to communicate with cluster of balanced servers using +iproto network protocol. - confess "wrong msg id" unless exists($message{msg}); - confess "wrong msg body" unless exists($message{payload}) or exists($message{data}); - confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack}); +To use it nicely you should to implement two subclasses of +L<MR::IProto::Message> for each message type, one for request message +and another for reply. +This classes must be named as C<prefix::*::suffix>, where I<prefix> +must be passed to constructor of L<MR::IProto> as value of L</prefix> +attribute and I<suffix> is either C<Request> or C<Response>. +This classes must be loaded before first message through client object +will be sent. - my $retries = $self->{max_request_retries}; +To send messages asyncronously you should to implement event loop by self. +L<AnyEvent> is recomended. - for (my $try = 1; $try <= $retries; $try++) { - sleep $self->{retry_delay} if $try > 1 && $self->{retry_delay}; - $self->{debug} >= 2 && _debug $self "chat msg=$message{msg} $try of $retries total"; - my $ret = $self->Chat1(%message); +=cut - if ($ret->{ok}) { - return (wantarray ? @{$ret->{ok}} : $ret->{ok}->[0]); - } - } - return undef; -} +use Mouse; +use Errno; +use MRO::Compat; +use Scalar::Util qw(weaken); +use Time::HiRes; +use MR::IProto::Cluster; +use MR::IProto::Error; -sub Chat1 { - my ($self, %message) = @_; +with 'MR::IProto::Role::Debuggable'; - confess "wrong msg id" unless exists($message{msg}); - confess "wrong msg body" unless exists($message{payload}) or exists($message{data}); - confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack}); +=head1 ATTRIBUTES - my $server = _select_server $self $message{key}; - unless($server) { - $self->{_error} ||= 'Could not find a valid server'; - return; - } +=over - my ($ret) = _chat $self $server, \%message; +=item prefix - if ($ret->{fail} && $self->{rotateservers}) { - $self->{debug} >= 2 && _debug $self "chat: failed"; - _mark_server_bad $self; - } +Prefix of the class name in which hierarchy subclasses of L<MR::IProto::Message> +are located. Used to find reply message classes. - return $ret; -} +=cut -# private methods -# order of method declaration is important, see perlobj - -sub _parse_servers { - my ($self, $key, $line) = @_; - my @servers; - my $weighted = $self->{balance} == HASH(); - foreach my $server (split(/,/, $line)) { - if ($weighted) { - my ($host, $port, $weight) = split /:/, $server; - $weight ||= 1; - push @servers, { host => $host, port => $port, ok => 1, repr => "$host:$port" } for (1..$weight); - } else { - my ($host, $port) = $server =~ /(.+):(\d+)/; - push @servers, { host => $host, port => $port, repr => "$host:$port" }; - } - } - $self->{$key} = \@servers; -} +has prefix => ( + is => 'ro', + isa => 'Str', + default => sub { ref shift }, +); -sub _chat { - my ($self, $server, $message) = @_; - _a_init $self $server, $message - and _a_send $self - and !$message->{no_reply} - and _a_recv $self; - return _a_close $self; -} +=item cluster -sub _a_init { - my ($self, $server, $message, $async) = @_; - - _a_clear $self; - $self->{_server} = $server; - $self->{_message} = $message; - $self->{_async} = !!$async; - - my $payload = $message->{payload} || do { no warnings 'uninitialized'; pack($message->{pack} || 'L*', @{$message->{data}}) }; - $self->{_sync} = exists $message->{sync} ? $message->{sync} : int(rand 0xffffffff); - my $header = pack('LLL', $message->{msg}, length($payload), $self->{_sync}); - $self->{_write_buf} = $header . $payload; - $self->{debug} >= 5 && _debug_dump $self '_pack_request: header ', $header; - $self->{debug} >= 5 && _debug_dump $self '_pack_request: payload ', $payload; - - $self->{_start_time} = time; - $self->{_connecting} = 1; - $self->{sock} = $sockets{$server->{repr}} || _connect $self $server, \$self->{_error}, $async; - _set_blocking($self->{sock},!$async) if $self->{sock}; - return $self->{sock}; -} +Instance of L<MR::IProto::Cluster>. Contains all servers between which +requests can be balanced. +Also can be specified in I<servers> parameter of constructor as a list of +C<host:port> pairs separated by comma. -sub _a_send { - my ($self) = @_; +=cut - $self->{_connecting} = 0; - $self->{_timedout} = 0; - while(length $self->{_write_buf}) { - local $! = 0; - my $res = syswrite($self->{sock}, $self->{_write_buf}); - if(defined $res && $res == 0) { - $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR}; - $self->{_error} .= "Server unexpectedly closed connection (${\length $self->{_write_buf}} bytes unwritten)"; - return; - } - if(!defined $res) { - $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR}; - $self->{_timedout} = !!$!{EAGAIN}; - $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN}; - return; - } - substr $self->{_write_buf}, 0, $res, ''; - } +has cluster => ( + is => 'ro', + isa => 'MR::IProto::Cluster', + required => 1, + coerce => 1, + handles => [qw( timeout )], +); - die "We should never get here" if length $self->{_write_buf}; +=item max_parallel - delete $self->{_write_buf}; - $self->{_read_header} = 1; - $self->{_to_read} = 12; +Max amount of simultaneous request to all servers. - return 1; -} +=cut -sub _a_recv { - my ($self) = @_; - $self->{_connecting} = 0; - $self->{_timedout} = 0; - while(1) { - local $! = 0; - my $res; - while($self->{_to_read} and $res = sysread($self->{sock}, my $buffer, $self->{_to_read} ) ) { - $self->{_to_read} -= $res; - $self->{_buf} .= $buffer; - $self->{debug} >= 6 && _debug_dump $self '_a_recv: ', $buffer; - } - if(defined $res && $res == 0 && $self->{_to_read}) { - $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR}; - $self->{_error} .= "Server unexpectedly closed connection ($self->{_to_read} bytes unread)"; - return; - } - if(!defined $res) { - $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR}; - $self->{_timedout} = !!$!{EAGAIN}; - $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN}; - return; - } +has max_parallel => ( + is => 'ro', + isa => 'Int', + default => 1000, +); - last unless $self->{_read_header}; - if($self->{_read_header} && length $self->{_buf} >= 12) { - my ($response_msg, $to_read, $response_sync) = unpack('L3', substr($self->{_buf},0,12,'')); - unless ($response_msg == $self->{_message}->{msg} and $response_sync == $self->{_sync}) { - $self->{_error} .= "unexpected reply msg($response_msg != $self->{_message}->{msg}) or sync($response_sync != $self->{_sync})"; - return; - } - $self->{_read_header} = 0; - $self->{_to_read} = $to_read - length $self->{_buf}; - } - } +=item max_request_retries - die "We should never get here" if $self->{_to_read}; - return 1; -} +Max amount of request retries which must be sent to different servers +before error is returned. -sub _a_close { - my ($self, $error) = @_; - $error = '' unless defined $error; - $self->{_timedout} ||= $error eq 'timeout'; - $self->{_error} ||= $error; - my $ret; - if ($self->{_error} || $self->{_timedout}) { - _close_sock $self $self->{_server}; # something went wrong, close socket just in case - $self->{_error} = "Timeout ($self->{_error})" if $self->{_timedout}; - $self->{debug} >= 1 && _debug $self "failed with: $self->{_error}"; - $ret = { fail => $self->{_error}, timeout => $self->{_timedout} }; - } else { - $self->{debug} >= 5 && _debug_dump $self '_unpack_body: ', $self->{_buf}; - $ret = { ok => [ $self->{_message}->{no_reply} ? (0) : $self->{_message}->{unpack}->($self->{_buf}) ] }; - } - _a_clear $self; - return $ret; -} +=cut -sub _a_is_reading { - my ($self) = @_; - return $self->{_to_read} && $self->{sock}; -} +has max_request_retries => ( + is => 'ro', + isa => 'Int', + default => 2, +); -sub _a_is_writing { - my ($self) = @_; - return exists $self->{_write_buf} && $self->{sock}; -} +=item retry_delay -sub _a_is_connecting { - my ($self) = @_; - return $self->{_connecting} && $self->{sock}; -} +Delay between request retries. -sub _a_clear { - my ($self) = @_; - $self->{_buf} = $self->{_error} = ''; - $self->{_to_read} = undef; - $self->{_data} = undef; - $self->{_server} = undef; - $self->{_message} = undef; - $self->{_sync} = undef; - $self->{_read_header} = undef; - $self->{_to_read} = undef; - $self->{_timedout} = undef; - $self->{_connecting} = undef; - delete $self->{_write_buf}; -} +=cut -sub _n_servers { - return scalar @{$_[0]->{servers}}; -} +has retry_delay => ( + is => 'ro', + isa => 'Num', + default => 0, +); -sub _select_server { - my ($self, $key) = @_; - my $n = @{$self->{servers}}; - while($n--) { - if ($self->{balance} == RR()) { - $self->{current_server} ||= $self->_balance_rr; - } elsif ($self->{balance} == KETAMA()) { - $self->{current_server} = $self->_balance_ketama($key); - } else { - $self->{current_server} = $self->_balance_hash($key); - } - last unless $self->{current_server}; - last if $self->{current_server}; - } - if($self->{current_server}) { - $self->{_last_server} = $self->{current_server}->{repr}; - } - return $self->{current_server}; -} +=back -sub _mark_server_bad { - my ($self, $remove_last_server) = @_; - if ($self->{balance} == HASH()) { - delete($self->{current_server}->{ok}); - } - delete($self->{current_server}); - $self->{_last_server} = '' if $remove_last_server; -} +=cut -sub _balance_rr { - my ($self) = @_; - if (scalar(@{$self->{servers}}) == 1) { - return $self->{servers}->[0]; - } else { - $self->{aviable_servers} = [@{$self->{servers}}] if (scalar(@{$self->{aviable_servers}}) == 0); - return splice(@{$self->{aviable_servers}}, int(rand(@{$self->{aviable_servers}})), 1); - } -} +has _reply_class => ( + is => 'ro', + isa => 'HashRef[ClassName]', + lazy_build => 1, +); -sub _balance_hash { - my ($self, $key) = @_; - my ($hash_, $hash, $server); +has _queue => ( + is => 'ro', + isa => 'ArrayRef', + lazy_build => 1, +); - $hash = $hash_ = crc32($key) >> 16; - for (0..19) { - $server = $self->{servers}->[$hash % @{$self->{servers}}]; - return $server if $server->{ok}; - $hash += crc32($_, $hash_) >> 16; - } +has _in_progress => ( + is => 'rw', + isa => 'Int', + default => 0, +); - return $self->{servers}->[rand @{$self->{servers}}]; #last resort -} +=head1 PUBLIC METHODS -sub _balance_ketama { - my ($self, $key) = @_; +=over - my $idx = crc32($key); +=item new( [ %args | \%args ] ) - for my $a (@{$self->{'ketama'}}) { - next unless ($a); - return $a->[1] if ($a->[0] >= $idx); - } +Constructor. +See L</ATTRIBUTES> and L</BUILDARGS> for more information about allowed arguments. - return $self->{'ketama'}->[0]->[1]; -} +=item send( [ $message | \%args ], $callback? ) -sub _set_sock_timeout ($$) { # not a class method! - my ($sock, $tv) = @_; - return ( - setsockopt $sock, SOL_SOCKET, SO_SNDTIMEO, $tv - and setsockopt $sock, SOL_SOCKET, SO_RCVTIMEO, $tv - ); -} +Send C<$message> to server and receive reply. -sub _set_blocking ($$) { - my ($sock, $blocking) = @_; - my $flags = 0; - fcntl($sock, F_GETFL, $flags) or die $!; - if($blocking) { - $flags &= ~O_NONBLOCK; - } else { - $flags |= O_NONBLOCK; - } - fcntl($sock, F_SETFL, $flags) or die $!; -} +If C<$callback> is passed then request is done asyncronously and reply is passed +to callback as first argument. +Method B<must> be called in void context to prevent possible errors. +Only client errors can be raised in async mode. All communication errors are +passed to callback as second argument. Additional information can be extracted +from C<$!> variable. -sub _connect { - my ($self, $server, $err, $async) = @_; +In sync mode (when C<$callback> argument is skipped) all errors are raised +and C<$!> is also set. Response is returned from method, so method B<must> +be called in scalar context. - $self->{_timedout} = 0; - $self->{debug} >= 4 && _debug $self "connecting"; +Request C<$message> can be instance of L<MR::IProto::Message> subclass. +In this case reply will be also subclass of L<MR::IProto::Message>. +Or it can be passed as C<\%args> hash reference with keys described +in L</_send>. - my $sock = "$server->{host}:$server->{port}"; - my $proto = $PROTO_TCP ||= getprotobyname('tcp'); - do { - no strict 'refs'; - socket($sock, PF_INET, SOCK_STREAM, $proto); - }; +=cut - if ($async) { - _set_blocking($sock,0); - } else { - _set_sock_timeout $sock, $self->{timeout_timeval}; +sub send { + my ($self, $message, $callback) = @_; + if($callback) { + die "Method must be called in void context if you want to use async" if defined wantarray; + $self->_send($message, $callback); + return; + } + else { + die "Method must be called in scalar context if you want to use sync" unless defined wantarray; + my $olddie = ref $SIG{__DIE__} eq 'CODE' ? $SIG{__DIE__} : ref $SIG{__DIE__} eq 'GLOB' ? *{$SIG{__DIE__}}{CODE} : undef; + local $SIG{__DIE__} = sub { local $! = 0; $olddie->(@_); } if $olddie; + my %servers; + my ($data, $error, $errno); + $self->_send_now($message, sub { + ($data, $error) = @_; + $errno = $!; + return; + }, \%servers); + $self->_recv_now(\%servers); + $! = $errno; + die $error if $error; + return $data; } +} - my $sin = Socket::sockaddr_in($server->{'port'}, Socket::inet_aton($server->{'host'})); - while(1) { - local $! = 0; - unless(connect($sock, $sin)) { - $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR}; - $self->{_timedout} = !!$!{EINPROGRESS}; - if (!$async || !$!{EINPROGRESS}) { - $$err .= "cannot connect: $!"; - close $sock; - return undef; - } +=item send_bulk( \@messages, $callback? ) + +Send all of messages in C<\@messages> and return result (sync-mode) or +call callback (async-mode) after all replies was received. +Result is returned as array reference, which values can be instances of +L<MR::IProto::Response> or L<MR::IProto::Error> if request was passed +as object, or hash with keys C<data> and C<error> if message was passed +as C<\%args>. +Replies in result can be returned in order different then order of requests. + +See L</_send> for more information about message data. Either +C<$message> or C<\%args> allowed as content of C<\@messages>. + +=cut + +sub send_bulk { + my ($self, $messages, $callback) = @_; + my @result; + if($callback) { + die "Method must be called in void context if you want to use async" if defined wantarray; + my $cv = AnyEvent->condvar(); + $cv->begin( sub { $callback->(\@result) } ); + foreach my $message ( @$messages ) { + $cv->begin(); + $self->_send($message, sub { + my ($data, $error) = @_; + push @result, blessed($data) ? $data + : { data => $data, error => $error }; + $cv->end(); + return; + }); } - last; + $cv->end(); + return; } - - if($self->{tcp_nodelay}) { - setsockopt($sock, $PROTO_TCP, TCP_NODELAY, 1); + else { + die "Method must be called in scalar context if you want to use sync" unless defined wantarray; + my $olddie = ref $SIG{__DIE__} eq 'CODE' ? $SIG{__DIE__} : ref $SIG{__DIE__} eq 'GLOB' ? *{$SIG{__DIE__}}{CODE} : undef; + local $SIG{__DIE__} = sub { local $! = 0; $olddie->(@_); } if $olddie; + my %servers; + foreach my $message ( @$messages ) { + $self->_send_now($message, sub { + my ($data, $error) = @_; + push @result, blessed($data) ? $data + : { data => $data, error => $error }; + return; + }, \%servers); + } + $self->_recv_now(\%servers); + return \@result; } +} - if($self->{tcp_keepalive}) { - setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1); - } +sub Chat { + my $self = shift; + my $message = @_ == 1 ? shift : { @_ }; + $message->{retry} = 1 if ref $message eq 'HASH'; + my $data; + eval { $data = $self->send($message); 1 } or return; + return wantarray ? @$data : $data->[0]; +} - $self->{debug} >= 2 && _debug $self "connected"; - return $sockets{$server->{repr}} = $sock; +sub Chat1 { + my $self = shift; + my $message = @_ == 1 ? shift : { @_ }; + my $data; + return eval { $data = $self->send($message); 1 } ? { ok => $data } + : { fail => $@ =~ /^(.*?) at \S+ line \d+/s ? $1 : $@, timeout => $! == Errno::ETIMEDOUT }; } sub SetTimeout { my ($self, $timeout) = @_; - $self->{timeout} = $timeout; + $self->timeout($timeout); + return; +} - my $sec = int $timeout; # seconds - my $usec = int( ($timeout - $sec) * 1_000_000 ); # micro-seconds - $self->{timeout_timeval} = pack "L!L!", $sec, $usec; # struct timeval; +=back - _set_sock_timeout $sockets{$_}, $self->{timeout_timeval} - for - grep {exists $sockets{$_}} - map {$_->{repr}} - @{ $self->{servers} }; -} +=head1 PROTECTED METHODS -sub _close_sock { - my ($self, $server) = @_; +=over - if ($sockets{$server->{repr}}) { - $self->{debug} >= 2 && _debug $self 'closing socket'; - close $sockets{$server->{repr}}; - delete $sockets{$server->{repr}}; - } -} +=item BUILDARGS( [ %args | \%args ] ) -sub _debug { - my ($self, $msg)= @_; - my $server = $self->{current_server} && $self->{current_server}->{repr} || $self->{_last_server} || ''; - my $sock = $self->{sock} || 'none'; - $server &&= "$server($sock) "; +For compatibility with previous version of client and simplicity +some additional arguments to constructor is allowed: - warn("$self->{name}: $server$msg\n"); - 1; -} +=over + +=item servers + +C<host:port> pairs separated by comma used to create +L<MR::IProto::Cluster::Server> objects. + +=item timeout, tcp_nodelay, tcp_keepalive, dump_no_ints + +Are passed directly to constructor of L<MR::IProto::Cluster::Server>. + +=item balance -sub _debug_dump { - my ($self, $msg, $datum) = @_; - my $server = $self->{current_server} && $self->{current_server}->{repr}; - my $sock = $self->{sock} || 'none'; - $server &&= "$server($sock) "; +Is passed directly to constructor of L<MR::IProto::Cluster>. - unless($self->{dump_no_ints}) { - $msg .= join(' ', unpack('L*', $datum)); - $msg .= ' > '; +=back + +See L<Mouse::Manual::Construction/BUILDARGS> for more information. + +=cut + +my %servers; +around BUILDARGS => sub { + my $orig = shift; + my $class = shift; + my %args = @_ == 1 ? %{shift()} : @_; + $args{prefix} = $args{name} if exists $args{name}; + if( $args{servers} ) { + my $cluster_class = $args{cluster_class} || 'MR::IProto::Cluster'; + my $server_class = $args{server_class} || 'MR::IProto::Cluster::Server'; + my %srvargs; + $srvargs{debug} = $args{debug} if exists $args{debug}; + $srvargs{timeout} = delete $args{timeout} if exists $args{timeout}; + $srvargs{tcp_nodelay} = delete $args{tcp_nodelay} if exists $args{tcp_nodelay}; + $srvargs{tcp_keepalive} = delete $args{tcp_keepalive} if exists $args{tcp_keepalive}; + $srvargs{dump_no_ints} = delete $args{dump_no_ints} if exists $args{dump_no_ints}; + my %clusterargs; + $clusterargs{balance} = delete $args{balance} if exists $args{balance}; + $clusterargs{servers} = [ + map { + my ($host, $port, $weight) = split /:/, $_; + $args{no_pool} ? my $server : $servers{"$host:$port"} ||= $server_class->new( + %srvargs, + host => $host, + port => $port, + defined $weight ? ( weight => $weight ) : (), + ); + } split /,/, delete $args{servers} + ]; + $args{cluster} = $cluster_class->new(%clusterargs); } - $msg .= join(' ', map { sprintf "%02x", $_ } unpack("C*", $datum)); - warn("$self->{name}: $server$msg\n"); -} + return $class->$orig(%args); +}; -package MR::IProto::Async; -use Time::HiRes qw/time/; -use List::Util qw/min shuffle/; - -sub DEFAULT_TIMEOUT() { 10 } -sub DEFAULT_TIMEOUT_SINGLE() { 4 } -sub DEFAULT_TIMEOUT_CONNECT() { 1 } -sub DEFAULT_RETRY() { 3 } -sub IPROTO_CLASS() { 'MR::IProto' } - -use overload '""' => sub { $_[0]->{name}.'[async]' }; - -BEGIN { *confess = \&MR::IProto::confess } - -sub new { - my ($class, %opt) = @_; - ($opt{name}) = caller unless $opt{name}; - my $self = bless { - name => $opt{name}, - iproto_class => $opt{iproto_class} || $class->IPROTO_CLASS, - timeout => $opt{timeout} || $class->DEFAULT_TIMEOUT(), # over-all-requests timeout - timeout_single => $opt{timeout_single} || $class->DEFAULT_TIMEOUT_SINGLE(), # per-request timeout - timeout_connect => $opt{timeout_connect} || $class->DEFAULT_TIMEOUT_CONNECT(), # timeout to do connect() - max_request_retries => $opt{max_request_retries} || $class->DEFAULT_RETRY(), - debug => $opt{debug} || 0, - servers => [ ], - requests => [ @{$opt{requests}||[]} ], - nreqs => $opt{requests} ? scalar(@{$opt{requests}}) : 0, - servers_used => {}, - nserver => 0, - working => {}, - }, $class; - - _parse_servers $self 'servers', $opt{servers}; - confess "no servers given" unless @{$self->{servers}}; - @{$self->{servers}} = shuffle @{$self->{servers}}; - - return $self; +sub _build_debug_cb { + my ($self) = @_; + my $prefix = $self->prefix; + return sub { + my ($msg) = @_; + chomp $msg; + warn sprintf "%s: %s\n", $prefix, $msg; + return; + }; } -sub Request { # MR::IProto::Chat()-compatible - my ($self, %message) = @_; - - confess "wrong msg id" unless exists($message{msg}); - confess "wrong msg body" unless exists($message{payload}) or exists($message{data}); - confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack}); +sub _build__callbacks { + my ($self) = @_; + return {}; +} - push @{$self->{requests}}, \%message; +sub _build__reply_class { + my ($self) = @_; + my $re = sprintf '^%s::', $self->prefix; + my %reply = map { $_->msg => $_ } + grep $_->can('msg'), + grep /$re/, + # MR::IProto::Response->meta->subclasses(); + @{ mro::get_isarev('MR::IProto::Response') }; + return \%reply; } -sub Results { +sub _build__queue { my ($self) = @_; + return []; +} - my (@done); - my $timeout_single = $self->{timeout_single}; - my $timeout_connect = $self->{timeout_connect}; - my $nreqs = @{$self->{requests}}; +=item _send( [ $message | \%args ], $callback? ) - unless ($nreqs) { - warn "No requests; return nothing;\n"; - return; - } +Pure asyncronious internal implementation of send. - my $t0 = time; +C<$message> is an instance of L<MR::IProto::Message>. +If C<\%args> hash reference is passed instead of C<$message> then it can +contain following keys: - my $err = 'timeout'; - my $working = $self->{working}; - confess "we have something what we should not" if %$working; +=over - while(scalar@{$self->{requests}}) { - int _init_req $self $self->{requests}->[0], $working - or last; - shift @{$self->{requests}}; - } +=item msg - unless (%$working) { - warn "Connection error; return nothing;\n"; - return; - } +Message code. - my $deadline = $self->{timeout} + time; - - MAINLOOP: - while((my $timeout = $deadline - time) > 0 && %$working) { - my ($r,$w) = ('',''); - vec($r, $_, 1) = 1 for grep { $working->{$_}->{_conn}->_a_is_reading } keys %$working; - vec($w, $_, 1) = 1 for grep { !$working->{$_}->{_conn}->_a_is_reading } keys %$working; - my $t1 = time; - $timeout = min $timeout, map { ($_->{_conn}->_a_is_connecting() ? $timeout_connect : $timeout_single) - ($t1 - $_->{_conn}->{_start_time}) } values %$working; - my $found = select($r, $w, undef, $timeout); - - my $time = time; - for my $fd (keys %$working) { - my $req = $working->{$fd}; - my $conn = $req->{_conn}; - - my $restart = 0; - if(vec($w, $fd, 1) && ($conn->_a_is_writing || $conn->_a_is_connecting)) { - $restart = 1 if !$conn->_a_send && !$conn->{_timedout}; - } elsif(vec($r, $fd, 1) && $conn->_a_is_reading) { - if($conn->_a_recv) { - delete $working->{$fd}; - _server_free $self $req->{_server}; - push @done, $req; - if(@{$self->{requests}}) { - _init_req $self shift@{$self->{requests}}, $working or _die $self "shit1"; - } - } elsif(!$conn->{_timedout}) { - $restart = 2; - } - } else { - if($conn->{_start_time} + ($conn->_a_is_connecting() ? $timeout_connect : $timeout_single) < $time) { - $restart = -1; - } - } +=item key - next unless $restart; - - delete $working->{$fd}; - $conn->_a_close($restart < 0 ? 'timeout' : 'shit happened!'); - my $oldserver = $req->{_server}; - my $res = _init_req $self $req, $working; - if( !$res ) { - $err = 'one of requests has failed, aborting'; - last MAINLOOP; - } elsif (! int $res ) { - push @{$self->{requests}}, $req; - } - _server_free $self $oldserver; - } - } +Depending on this value balancing between servers is implemented. - my $t9 = time; - warn sprintf "$self->{name}: fetched %d requests for %.4f sec (%d/%d done)\n", $self->{nreqs}, $t9-$t0, scalar@done, $nreqs; +=item data - if(%$working) { - warn "$self->{name}: ${\scalar values %$working} requests not fetched\n"; - $_->{_conn}->_a_close($err) for values %$working; - # warn "return nothing\n"; - # return; - } +Message data. Already packed or unpacked. Unpacked data must be passed as +array reference and additional parameter I<pack> must be passed. - for my $req (@done) { - my $result = $req->{_conn}->_a_close; - # warn "ret nothing\n" and return unless $result->{ok}; - $req = $result->{ok}; - } +=item pack - return grep { $_ } @done; -} +First argument of L<pack|perlfunc/pack> function. -sub _init_req { - my ($self, $req) = @_; +=item unpack - $req->{_try} ||= 0; - return if $req->{_try} >= $self->{max_request_retries}; +Code reference which is used to unpack reply. - $req->{_conn} = undef; - $req->{_server} = undef; +=item no_reply - my $server = _select_server $self or return '0E0'; - my $conn = $self->{iproto_class}->new({ - servers => $server->{repr}, - debug => $self->{debug}, - }); - $conn->_select_server; # fiction +Message have no reply. - my $sock = $conn->_a_init($server, $req, 'async') or do{ $conn->_a_close; warn "can't init connection: $server->{repr}"; return '0E0'; }; - my $fd = fileno $sock or do{ $conn->_a_close; _die $self "connection has no FD", $server->{repr}; }; +=item retry - my $working = $self->{working}; - _die $self "FD $fd already exists in workers", $server->{repr} if exists $working->{$fd}; - $working->{ $fd } = $req; - _server_use $self $server; +Is retry is allowed. Values of attributes L</max_request_retries> and +L</retry_delay> is used if retry is allowed. - $req->{_conn} = $conn; - $req->{_server} = $server; - ++$req->{_try}; - ++$self->{nreqs}; +=item is_retry - return $conn && 1; -} +Callback used to determine if server asks for retry. Unpacked data is passed +to it as a first argument. + +=back + +=cut -sub _select_server { - my ($self, $disable_servers) = @_; - $disable_servers ||= {}; - - my $servers = $self->{servers}; - my $servers_used = $self->{servers_used}; - my $nserver = $self->{nserver}; - - my $i = 1; - while($i <= @$servers) { - my $repr = $servers->[($i+$nserver)%@$servers]->{repr}; - next if $servers_used->{$repr}; - next if $disable_servers->{$repr}; - last; - } continue { - ++$i; +sub _send { + my ($self, $message, $callback) = @_; + if( $self->_in_progress < $self->max_parallel ) { + $self->_in_progress( $self->_in_progress + 1 ); + eval { $self->_send_now($message, $callback); 1 } + or $self->_report_error($message, $callback, $@); } + else { + push @{$self->_queue}, [ $message, $callback ]; + } + return; +} - return if $i > @$servers; +sub _finish_and_start { + my ($self) = @_; + if( my $task = shift @{$self->_queue} ) { + eval { $self->_send_now(@$task); 1 } + or $self->_report_error(@$task, $@); + } + else { + $self->_in_progress( $self->_in_progress - 1 ); + } + return; +} + +sub _send_now { + my ($self, $message, $callback, $sync) = @_; + my $args; + # MR::IProto::Message OO-API + if( ref $message ne 'HASH' ) { + my $msg = $message->msg; + my $response_class = $self->_reply_class->{$msg}; + die sprintf "Cannot find response class for message code %d\n", $msg unless $response_class; + $args = { + request => $message, + msg => $msg, + key => $message->key, + body => $message->data, + response_class => $response_class, + no_reply => $response_class->isa('MR::IProto::NoResponse'), + }; + } + # Old-style compatible API + else { + die "unpack or no_reply must be specified" unless $message->{unpack} || $message->{no_reply}; + $args = $message; + $args->{body} = exists $args->{payload} ? delete $args->{payload} + : ref $message->{data} ? pack delete $message->{pack} || 'L*', @{ delete $message->{data} } + : delete $message->{data}; + } - $self->{nserver} = $nserver = ($i+$nserver)%@$servers; - return $servers->[$nserver]; + my $try = 1; + weaken($self); + my $handler; + $handler = sub { + $self->_server_callback( + [\$handler, $args, $callback, $sync, \$try], + [@_], + ); + return; + }; + $self->_send_try($sync, $args, $handler, $try); + return; +} + +sub _send_try { + my ($self, $sync, $args, $handler, $try) = @_; + my $xsync = $sync ? 'sync' : 'async'; + $self->_debug(sprintf "send msg=%d try %d of %d total", $args->{msg}, $try, $self->max_request_retries ) if $self->debug >= 2; + my $server = $self->cluster->server( $args->{key} ); + my $connection = $server->$xsync(); + $connection->send($args->{msg}, $args->{body}, $handler, $args->{no_reply}, $args->{sync}); + $sync->{$connection} ||= $connection if $sync; + return; +} + +sub _send_retry { + my ($self, @in) = @_; + my ($sync) = @in; + if( $sync ) { + Time::HiRes::sleep($self->retry_delay); + $self->_send_try(@in); + } + else { + my $timer; + $timer = AnyEvent->timer( + after => $self->retry_delay, + cb => sub { + undef $timer; + $self->_send_try(@in); + return; + }, + ); + } + return; +} + +sub _server_callback { + my ($self, $req_args, $resp_args) = @_; + my ($handler, $args, $callback, $sync, $try) = @$req_args; + my ($resp_msg, $data, $error, $errno) = @$resp_args; + eval { + if ($error) { + $! = $errno; + $@ = $error; + my $retry = defined $args->{request} ? $args->{request}->retry() + : ref $args->{retry} eq 'CODE' ? $args->{retry}->() + : $args->{retry}; + $self->_debug("send: failed[@{[$retry, $$try+1, $self->max_request_retries]}]") if $self->debug >= 2; + if( $retry && $$try++ < $self->max_request_retries ) { + $self->_send_retry($sync, $args, $$handler, $$try); + } + else { + undef $$handler; + $self->_report_error($args->{request}, $callback, $error, $sync, $errno); + } + } + else { + my $ok = eval { + die "Request and reply message code is different: $resp_msg != $args->{msg}\n" + unless $args->{no_reply} || $resp_msg == $args->{msg}; + if( defined $args->{request} ) { + $data = $args->{response_class}->new( data => $data, request => $args->{request} ); + } + else { + $data = $args->{no_reply} ? [ 0 ] : [ ref $args->{unpack} eq 'CODE' ? $args->{unpack}->($data) : unpack $args->{unpack}, $data ]; + } + 1; + }; + if($ok) { + if( defined $args->{request} && $data->retry && $$try++ < $self->max_request_retries ) { + $self->_send_retry($sync, $args, $$handler, $$try); + } + elsif( defined $args->{is_retry} && $args->{is_retry}->($data) && $$try++ < $self->max_request_retries ) { + $self->_send_retry($sync, $args, $$handler, $$try); + } + else { + undef $$handler; + $self->_finish_and_start() unless $sync; + $callback->($data); + } + } + else { + undef $$handler; + $self->_report_error($args->{request}, $callback, $@, $sync); + } + } + 1; + } or do { + undef $$handler; + $self->_debug("unhandled fatal error: $@"); + }; + return; } -sub _server_use { - my ($self,$server) = @_; - _die $self "want to use used server:$server->{repr}:" if $self->{servers_used}->{$server->{repr}}; - ++ $self->{servers_used}->{$server->{repr}}; +sub _recv_now { + my ($self, $servers) = @_; + while(my @servers = values %$servers) { + %$servers = (); + $_->recv_all() foreach @servers; + } + return; } -sub _server_free { - my ($self,$server) = @_; - _die $self "want to free free server:$server->{repr}:" unless $self->{servers_used}->{$server->{repr}}; - $self->{servers_used}->{$server->{repr}} = 0; +sub _report_error { + my ($self, $request, $callback, $error, $sync, $errno) = @_; + my $errobj = defined $request && ref $request ne 'HASH' + ? MR::IProto::Error->new( + request => $request, + error => $error, + errno => defined $errno ? 0 + $errno : 0, + ) + : undef; + $self->_finish_and_start() unless $sync; + $! = $errno; + $@ = $error; + $callback->($errobj, $error, $errno); + return; } -sub _parse_servers { - my ($self, $key, $line) = @_; +=back - my @servers; - foreach my $server (split(/,/, $line)) { - my ($host, $port) = $server =~ /(.+):(\d+)/ or confess "bad server: $server!"; - push @servers, { host => $host, port => $port, repr => "$host:$port" } - } +=head1 SEE ALSO - $self->{$key} = \@servers; -} +L<MR::IProto::Cluster>, L<MR::IProto::Cluster::Server>, L<MR::IProto::Message>. -sub _die { - my ($self, @e) = @_; - die "$self->{name}: ".join('; ', @e); -} +=cut -1; +no Mouse; +__PACKAGE__->meta->make_immutable(); +1; diff --git a/connector/perl/lib/MR/IProto/Cluster.pm b/connector/perl/lib/MR/IProto/Cluster.pm new file mode 100644 index 0000000000000000000000000000000000000000..d5f46b8bdeffe20e559bfb387157597e234762c9 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Cluster.pm @@ -0,0 +1,253 @@ +package MR::IProto::Cluster; + +=head1 NAME + +MR::IProto::Cluster - cluster of servers + +=head1 DESCRIPTION + +This class is used to implement balancing between several servers. + +=cut + +use Mouse; +use Mouse::Util::TypeConstraints; +use MR::IProto::Cluster::Server; +use String::CRC32 qw(crc32); + +=head1 EXPORTED CONSTANTS + +=over + +=item RR + +Round robin algorithm + +=item HASH + +Hashing algorithm using CRC32 + +=item KETAMA + +Ketama algorithm + +=back + +=cut + +use Exporter 'import'; +our @EXPORT_OK = qw( RR HASH KETAMA ); + +coerce 'MR::IProto::Cluster' + => from 'Str' + => via { __PACKAGE__->new( servers => $_ ) }; + +subtype 'MR::IProto::Cluster::Servers' + => as 'ArrayRef[MR::IProto::Cluster::Server]' + => where { scalar @_ }; +coerce 'MR::IProto::Cluster::Servers' + => from 'Str' + => via { + my $type = find_type_constraint('MR::IProto::Cluster::Server'); + [ map $type->coerce($_), split /,/, $_ ]; + }; + +use constant { + RR => 1, + HASH => 2, + KETAMA => 3, +}; + +enum 'MR::IProto::Balance' => ( + RR, + HASH, + KETAMA, +); +coerce 'MR::IProto::Balance' + => from 'Str', + => via { + $_ eq 'hash-crc32' ? HASH + : $_ eq 'ketama' ? KETAMA + : RR; + }; + +=head1 ATTRIBUTES + +=over + +=item balance + +Balancing algorithms. +Possible values are constants: RR, HASH, KETAMA. +Or their string analogs: 'round-robin', 'hash-crc32', 'ketama'. + +=cut + +has balance => ( + is => 'ro', + isa => 'MR::IProto::Balance', + default => RR, + coerce => 1, +); + +=item servers + +ArrayRef of L<MR::IProto::Cluster::Server>. + +=cut + +has servers => ( + is => 'ro', + isa => 'MR::IProto::Cluster::Servers', + required => 1, + coerce => 1, +); + +=back + +=cut + +has _one => ( + is => 'ro', + isa => 'Maybe[MR::IProto::Cluster::Server]', + lazy_build => 1, +); + +has _ketama => ( + is => 'ro', + isa => 'ArrayRef[ArrayRef]', + lazy_build => 1, +); + +has _rr_servers => ( + is => 'rw', + isa => 'MR::IProto::Cluster::Servers', + lazy_build => 1, +); + +has _hash_servers => ( + is => 'rw', + isa => 'MR::IProto::Cluster::Servers', + lazy_build => 1, +); + +=head1 PUBLIC METHODS + +=over + +=item server( $key? ) + +Get server from balancing using C<$key>. + +=cut + +sub server { + my ($self, $key) = @_; + my $one = $self->_one; + return $one if defined $one; + my $method = $self->balance == RR ? '_balance_rr' + : $self->balance == KETAMA ? '_balance_ketama' + : '_balance_hash'; + return $self->$method($key); +} + +=item timeout( $new? ) + +Used to set C<$new> timeout value to all servers. +If argument is skipped and timeout is equal for all servers then returns +it value, if timeout is different then returns undef. + +=cut + +sub timeout { + my $self = shift; + if(@_) { + my $timeout = shift; + $_->timeout($timeout) foreach @{$self->servers}; + return $timeout; + } + else { + my $timeout; + foreach my $t ( map $_->timeout, @{$self->servers} ) { + return if defined $timeout && $timeout != $t; + $timeout = $t unless defined $timeout; + } + return $timeout; + } +} + +=back + +=cut + +sub _build__one { + my ($self) = @_; + return @{$self->servers} == 1 ? $self->servers->[0] : undef; +} + +sub _build__ketama { + my $self = shift; + my @ketama; + foreach my $server (@{$self->servers}) { + for my $i (0..10) { + push @ketama, [crc32($server->host.$server->port.$i), $server]; + } + } + return [ sort { $a->[0] cmp $b->[0] } @ketama ]; +} + +sub _build__rr_servers { + my ($self) = @_; + return [ @{ $self->servers } ]; +} + +sub _build__hash_servers { + my ($self) = @_; + return [ map { my @s; my $s = $_; push @s, $s for ( 1 .. $s->weight ); @s } @{ $self->servers } ]; +} + +sub _balance_rr { + my ($self) = @_; + $self->_clear_rr_servers() if @{$self->_rr_servers} == 0; + return splice(@{$self->_rr_servers}, int(rand(@{$self->_rr_servers})), 1); +} + +sub _balance_hash { + my ($self, $key) = @_; + my ($hash_, $hash, $server); + + die "Cannot balance hash without key" unless defined $key; + $hash = $hash_ = crc32($key) >> 16; + for (0..19) { + $server = $self->_hash_servers->[$hash % @{$self->_hash_servers}]; + return $server if $server->active; + $hash += crc32($_, $hash_) >> 16; + } + + return $self->_hash_servers->[rand @{$self->_hash_servers}]; #last resort +} + +sub _balance_ketama { + my ($self, $key) = @_; + + die "Cannot balance ketama without key" unless defined $key; + my $idx = crc32($key); + + foreach (@{$self->_ketama}) { + next unless ($_); + return $_->[1] if ($_->[0] >= $idx); + } + + return $self->_ketama->[0]->[1]; +} + +=head1 SEE ALSO + +L<MR::IProto>, L<MR::IProto::Cluster::Server>. + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Cluster/Server.pm b/connector/perl/lib/MR/IProto/Cluster/Server.pm new file mode 100644 index 0000000000000000000000000000000000000000..e22de5e00ef054e60043bb36881fe2f33573b582 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Cluster/Server.pm @@ -0,0 +1,232 @@ +package MR::IProto::Cluster::Server; + +=head1 NAME + +MR::IProto::Cluster::Server - server + +=head1 DESCRIPTION + +This class is used to implement all communication with one server. + +=cut + +use Mouse; +use Mouse::Util::TypeConstraints; +use MR::IProto::Connection::Async; +use MR::IProto::Connection::Sync; +use MR::IProto::Message; + +with 'MR::IProto::Role::Debuggable'; + +coerce 'MR::IProto::Cluster::Server' + => from 'Str' + => via { + my ($host, $port, $weight) = split /:/, $_; + __PACKAGE__->new( + host => $host, + port => $port, + defined $weight ? ( weight => $weight ) : (), + ); + }; + +=head1 ATTRIBUTES + +=over + +=item host + +Host name or IP address. + +=cut + +has host => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +=item port + +Port number. + +=cut + +has port => ( + is => 'ro', + isa => 'Int', + required => 1, +); + +=item weight + +Server weight. + +=cut + +has weight => ( + is => 'ro', + isa => 'Int', + default => 1, +); + +=item connect_timeout + +Timeout of connect operation. + +=cut + +has connect_timeout => ( + is => 'rw', + isa => 'Num', + default => 2, +); + +=item timeout + +Timeout of read and write operations. + +=cut + +has timeout => ( + is => 'rw', + isa => 'Num', + default => 2, + trigger => sub { + my ($self, $new) = @_; + $self->async->set_timeout($new) if $self->has_async(); + $self->sync->set_timeout($new) if $self->has_sync(); + return; + }, +); + +=item tcp_nodelay + +Enable TCP_NODELAY. + +=cut + +has tcp_nodelay => ( + is => 'ro', + isa => 'Int', + default => 1, +); + +=item tcp_keepalive + +Enable SO_KEEPALIVE. + +=cut + +has tcp_keepalive => ( + is => 'ro', + isa => 'Int', + default => 0, +); + +=item max_parallel + +Max amount of simultaneous request. + +=cut + +has max_parallel => ( + is => 'ro', + isa => 'Int', + default => 10, +); + +=item active + +Is server used in balancing. + +=cut + +has active => ( + is => 'rw', + isa => 'Bool', + default => 1, +); + +has on_close => ( + is => 'rw', + isa => 'CodeRef', +); + +has async => ( + is => 'ro', + isa => 'MR::IProto::Connection::Async', + lazy_build => 1, +); + +has sync => ( + is => 'ro', + isa => 'MR::IProto::Connection::Sync', + lazy_build => 1, +); + +=back + +=head1 PROTECTED METHODS + +=over + +=cut + +sub _build_async { + my ($self) = @_; + return MR::IProto::Connection::Async->new( server => $self ); +} + +sub _build_sync { + my ($self) = @_; + return MR::IProto::Connection::Sync->new( server => $self ); +} + +sub _build_debug_cb { + my ($self) = @_; + return sub { + my ($msg) = @_; + chomp $msg; + warn "MR::IProto: $msg\n"; + return; + }; +} + +=item _send_started( $sync, $message, $data ) + +This method is called when message is started to send. + +=cut + +sub _send_started { + return; +} + +=item _recv_finished( $sync, $message, $data, $error ) + +This method is called when message is received. + +=cut + +sub _recv_finished { + return; +} + +sub _debug { + my ($self, $msg) = @_; + $self->debug_cb->( sprintf "%s:%d: %s", $self->host, $self->port, $msg ); + return; +} + +=back + +=head1 SEE ALSO + +L<MR::IProto>, L<MR::IProto::Cluster>. + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Connection.pm b/connector/perl/lib/MR/IProto/Connection.pm new file mode 100644 index 0000000000000000000000000000000000000000..4cc0d66a2c2d8c89ee69075d083627ebd71a6191 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Connection.pm @@ -0,0 +1,67 @@ +package MR::IProto::Connection; + +=head1 NAME + +MR::IProto::Connection - base communication class + +=head1 DESCRIPTION + +Base class for sync and async connections. + +=cut + +use Mouse; + +=head1 server + +=over + +=item server + +Instanse of L<MR::IProto::Cluster::Server>. + +=cut + +has server => ( + is => 'ro', + isa => 'MR::IProto::Cluster::Server', + required => 1, + weak_ref => 1, + handles => [qw( + host + port + connect_timeout + timeout + tcp_nodelay + tcp_keepalive + max_parallel + _send_started + _recv_finished + _debug + _debug_dump + )], +); + +=back + +=cut + +sub _pack_header { + my ($self, $msg, $length, $sync) = @_; + return pack 'L3', $msg, $length, $sync; +} + +sub _unpack_header { + my ($self, $header) = @_; + return unpack 'L3', $header; +} + +sub _choose_sync { + my ($self) = @_; + return int(rand 0xffffffff); +} + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Connection/Async.pm b/connector/perl/lib/MR/IProto/Connection/Async.pm new file mode 100644 index 0000000000000000000000000000000000000000..d78b9180ff7eafcf10b7fac169b513c90bef57d4 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Connection/Async.pm @@ -0,0 +1,280 @@ +package MR::IProto::Connection::Async; + +=head1 NAME + +MR::IProto::Connection::Async - async communication + +=head1 DESCRIPTION + +Used to perform asynchronous communication. + +=cut + +use Mouse; +extends 'MR::IProto::Connection'; + +use AnyEvent::Handle; +use Scalar::Util qw(weaken); + +has _handle => ( + is => 'ro', + isa => 'AnyEvent::Handle', + lazy_build => 1, +); + +has _queue => ( + is => 'ro', + isa => 'ArrayRef', + lazy_build => 1, +); + +has _in_progress => ( + is => 'rw', + isa => 'Int', + default => 0, +); + +has _callbacks => ( + is => 'ro', + isa => 'HashRef', + lazy_build => 1, +); + +has _read_reply => ( + is => 'ro', + isa => 'CodeRef', + lazy_build => 1, +); + +has _no_reply => ( + is => 'ro', + isa => 'ArrayRef', + lazy_build => 1, +); + +has _on_drain => ( + is => 'ro', + isa => 'CodeRef', + lazy_build => 1, +); + +=head1 PUBLIC METHODS + +=over + +=item send + +Enqueue message send. +For list of arguments see L</_send>. + +=cut + +sub send { + my $self = shift; + if( $self->_in_progress < $self->max_parallel ) { + $self->_in_progress( $self->_in_progress + 1 ); + $self->_send(@_); + } + else { + push @{$self->_queue}, [@_]; + } + return; +} + +=item set_timeout( $timeout ) + +Set timeout value for existing connection. + +=cut + +sub set_timeout { + my ($self, $timeout) = @_; + $self->_handle->timeout($timeout) if $self->_has_handle(); + return; +} + +=back + +=head1 PROTECTED METHODS + +=over + +=item _send( $msg, $payload, $callback, $no_reply ) + +Send message to server. + +=cut + +sub _send { + my ($self, $msg, undef, $callback, $no_reply, $sync) = @_; + $sync = $self->_choose_sync() unless defined $sync; + my $header = $self->_pack_header($msg, length $_[2], $sync); + my $server = $self->server; + $self->_callbacks->{$sync} = $callback; + $server->_send_started($sync, $msg, $_[2]); + my $handle = $self->_handle; + if( $server->debug >= 5 ) { + $server->_debug_dump('send header: ', $header); + $server->_debug_dump('send payload: ', $_[2]); + } + $handle->push_write($header); + $handle->push_write($_[2]); + if( $no_reply ) { + push @{$self->_no_reply}, $sync; + $handle->on_drain( $self->_on_drain ) unless defined $handle->{on_drain}; + } + else { + $handle->push_read( chunk => 12, $self->_read_reply ); + } + return; +} + +sub _build__read_reply { + my ($self) = @_; + my $server = $self->server; + weaken($self); + weaken($server); + return sub { + my ($handle, $data) = @_; + my $dump_resp = $server->debug >= 6; + $server->_debug_dump('recv header: ', $data) if $dump_resp; + my ($msg, $payload_length, $sync) = $self->_unpack_header($data); + $handle->unshift_read( chunk => $payload_length, sub { + my ($handle, $data) = @_; + $server->_debug_dump('recv payload: ', $data) if $dump_resp; + $server->_recv_finished($sync, $msg, $data); + $self->_finish_and_start(); + delete($self->_callbacks->{$sync})->($msg, $data); + return; + }); + return; + }; +} + +sub _try_to_send { + my ($self) = @_; + while( $self->_in_progress < $self->max_parallel && (my $task = shift @{ $self->_queue }) ) { + $self->_in_progress( $self->_in_progress + 1 ); + $self->_send(@$task); + } + return; +} + +sub _finish_and_start { + my ($self) = @_; + if( my $task = shift @{$self->_queue} ) { + $self->_send(@$task); + } + else { + $self->_in_progress( $self->_in_progress - 1 ); + } + return; +} + +sub _build__handle { + my ($self) = @_; + my $server = $self->server; + $server->_debug("connecting") if $server->debug >= 4; + weaken($self); + weaken($server); + return AnyEvent::Handle->new( + connect => [ $self->host, $self->port ], + no_delay => $self->tcp_nodelay, + keepalive => $self->tcp_keepalive, + timeout => $self->timeout, + on_prepare => sub { + return $self->connect_timeout; + }, + on_connect => sub { + my ($handle) = @_; + $server->_debug("connected") if $server->debug >= 1; + return; + }, + on_error => sub { + my ($handle, $fatal, $message) = @_; + my $errno = $!; + $server->_debug(($fatal ? 'fatal ' : '') . 'error: ' . $message); + my @callbacks; + foreach my $sync ( keys %{$self->_callbacks} ) { + $server->_recv_finished($sync, undef, undef, $message, $errno); + $self->_in_progress( $self->_in_progress - 1 ); + push @callbacks, $self->_callbacks->{$sync}; + } + $server->active(0); + $self->_clear_handle(); + $self->_clear_callbacks(); + $self->_clear_no_reply(); + $server->_debug('closing socket') if $server->debug >= 1; + $handle->destroy(); + $self->_try_to_send(); + $_->(undef, undef, $message, $errno) foreach @callbacks; + return; + }, + on_timeout => sub { + my ($handle) = @_; + return unless keys %{$self->_callbacks}; + $handle->_error( Errno::ETIMEDOUT ) if keys %{$self->_callbacks}; + return; + }, + ); +} + +sub _build__on_drain { + my ($self) = @_; + my $server = $self->server; + weaken($self); + weaken($server); + return sub { + my ($handle) = @_; + if( $self->_has_no_reply() ) { + foreach my $sync ( @{$self->_no_reply} ) { + $server->_recv_finished($sync, undef, undef); + $self->_in_progress( $self->_in_progress - 1 ); + delete($self->_callbacks->{$sync})->(undef, undef); + } + $self->_clear_no_reply(); + $self->_try_to_send(); + $handle->on_drain(undef); + } + return; + }; +} + +sub _build__queue { + my ($self) = @_; + return []; +} + +sub _build__callbacks { + my ($self) = @_; + return {}; +} + +sub _build__no_reply { + my ($self) = @_; + return []; +} + +around _choose_sync => sub { + my ($orig, $self) = @_; + my $sync; + my $callbacks = $self->_callbacks; + for( 1 .. 50 ) { + $sync = $self->$orig(); + return $sync unless exists $callbacks->{$sync}; + } + die "Can't choose sync value after 50 iterations"; +}; + +=back + +=head1 SEE ALSO + +L<MR::IProto::Connection>, L<MR::IProto::Cluster::Server>. + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Connection/Sync.pm b/connector/perl/lib/MR/IProto/Connection/Sync.pm new file mode 100644 index 0000000000000000000000000000000000000000..7c3da90c46e690d3eaca9e31e2dc6d6b2e1b3d89 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Connection/Sync.pm @@ -0,0 +1,243 @@ +package MR::IProto::Connection::Sync; + +=head1 NAME + +MR::IProto::Connection::Sync - sync communication + +=head1 DESCRIPTION + +Used to perform synchronous communication. + +=cut + +use Mouse; +extends 'MR::IProto::Connection'; + +use Errno; +use IO::Socket::INET; +use Socket qw( TCP_NODELAY SO_KEEPALIVE SO_SNDTIMEO SO_RCVTIMEO ); + +has _socket => ( + is => 'ro', + isa => 'IO::Socket::INET', + lazy_build => 1, +); + +has _sent => ( + is => 'ro', + default => sub { [] }, +); + +=head1 PUBLIC METHODS + +=over + +=item send + +See L<MR::IProto::Connection/send> for more information. + +=cut + +sub send { + my ($self, $msg, $payload, $callback, $no_reply, $sync) = @_; + my $server = $self->server; + my $sent = $self->_sent; + my $ok = eval { + $sync = $self->_choose_sync() unless defined $sync; + $server->_send_started($sync, $msg, $payload); + + my $socket = $self->_socket; + unless (@$sent) { + vec((my $rin = ''), fileno($socket), 1) = 1; + if (select((my $rout = $rin), undef, undef, 0) > 0) { + if (sysread($socket, my $buf, 1)) { + die "More then 0 bytes was received when nothing was waited for"; + } else { + # Connection was closed by other side, socket is in CLOSE_WAIT state, reconnecting + $self->_clear_socket(); + $socket = $self->_socket; + } + } + } + + my $header = $self->_pack_header($msg, length $payload, $sync); + if( $server->debug >= 5 ) { + $server->_debug_dump('send header: ', $header); + $server->_debug_dump('send payload: ', $payload); + } + + my $write = $header . $payload; + while( length $write ) { + my $written = syswrite($socket, $write); + if (!defined $written) { + if ($! != Errno::EINTR) { + $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_SNDTIMEO behaviour + die "send: $!"; + } + } else { + substr $write, 0, $written, ''; + } + } + 1; + }; + if($ok) { + if ($no_reply) { + $callback->(undef, undef); + $server->_recv_finished($sync, undef, undef); + } else { + push @$sent, [$sync, $callback]; + } + } + else { + $self->_handle_error($sync, $callback, $@); + } + return; +} + +sub recv_all { + my ($self) = @_; + my $server = $self->server; + my $sent = $self->_sent; + my $dump_resp = $server->debug >= 6; + while (my $args = shift @$sent) { + my ($sync, $callback) = @$args; + my ($resp_msg, $resp_payload); + my $ok = eval { + my $socket = $self->_socket; + my $resp_header; + my $to_read = 12; + while( $to_read ) { + my $read = sysread($socket, my $buf, $to_read); + if (!defined $read) { + if ($! != Errno::EINTR) { + $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_RCVTIMEO behaviour + die "recv: $!"; + } + } elsif ($read == 0) { + die "recv: Unexpected end-of-file"; + } else { + $resp_header .= $buf; + $to_read -= $read; + } + } + $server->_debug_dump('recv header: ', $resp_header) if $dump_resp; + ($resp_msg, my $resp_length, my $resp_sync) = $self->_unpack_header($resp_header); + die "Request and reply sync is different: $resp_sync != $sync" unless $resp_sync == $sync; + + $to_read = $resp_length; + while( $to_read ) { + my $read = sysread($socket, my $buf, $to_read); + if (!defined $read) { + if ($! != Errno::EINTR) { + $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_RCVTIMEO behaviour + die "recv: $!"; + } + } elsif ($read == 0) { + die "recv: Unexpected end-of-file"; + } else { + $resp_payload .= $buf; + $to_read -= $read; + } + } + $server->_debug_dump('recv payload: ', $resp_payload) if $dump_resp; + 1; + }; + if($ok) { + $server->_recv_finished($sync, $resp_msg, $resp_payload); + $callback->($resp_msg, $resp_payload); + } + else { + $self->_handle_error($sync, $callback, $@); + } + } + return; +} + +=item set_timeout( $timeout ) + +Set timeout value for existing connection. + +=cut + +sub set_timeout { + my ($self, $timeout) = @_; + $self->_set_timeout($self->_socket, $timeout) if $self->_has_socket(); + return; +} + +=back + +=cut + +sub _build__socket { + my ($self) = @_; + my $server = $self->server; + $server->_debug("connecting") if $server->debug >= 4; + my $socket = IO::Socket::INET->new( + PeerHost => $self->host, + PeerPort => $self->port, + Proto => 'tcp', + Timeout => $self->connect_timeout, + ) or do { + $@ =~ s/^IO::Socket::INET: (?:connect: )?//; + if ($@ eq 'timeout') { + # Hack over IO::Socket behaviour + $! = Errno::ETIMEDOUT; + $@ = "$!"; + } + die "connect: $@"; + }; + $socket->sockopt(SO_KEEPALIVE, 1) if $self->tcp_keepalive; + $socket->setsockopt((getprotobyname('tcp'))[2], TCP_NODELAY, 1) if $self->tcp_nodelay; + $self->_set_timeout($socket, $self->timeout) if $self->timeout; + $server->_debug("connected") if $server->debug >= 4; + return $socket; +} + +sub _set_timeout { + my ($self, $socket, $timeout) = @_; + my $sec = int $timeout; # seconds + my $usec = int( ($timeout - $sec) * 1_000_000 ); # micro-seconds + my $timeval = pack "L!L!", $sec, $usec; # struct timeval; + $socket->sockopt(SO_SNDTIMEO, $timeval); + $socket->sockopt(SO_RCVTIMEO, $timeval); + return; +} + +sub _handle_error { + my ($self, $sync, $callback, $error) = @_; + my $errno = $!; + if (!$error) { + $error = 'Unknown error'; + } elsif ($error =~ /^(.+?) at \S+ line \d+/s) { + $error = $1; + } + my $server = $self->server; + $server->_debug("error: $error"); + if($self->_has_socket()) { + close($self->_socket); + $self->_clear_socket(); + } + $server->active(0); + my $sent = $self->_sent; + my @sent = splice @$sent, 0, scalar @$sent; + $server->_recv_finished($sync, undef, undef, $error, $errno); + $callback->(undef, undef, $error, $errno); + foreach my $args (@sent) { + my ($sync, $callback) = @$args; + $server->_recv_finished($sync, undef, undef, $error, $errno); + $callback->(undef, undef, $error, $errno); + } + return +} + +=head1 SEE ALSO + +L<MR::IProto::Connection>, L<MR::IProto::Cluster::Server>. + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Error.pm b/connector/perl/lib/MR/IProto/Error.pm new file mode 100644 index 0000000000000000000000000000000000000000..93eda1724bce7bcd5db2b9282c21ba653034dfd0 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Error.pm @@ -0,0 +1,89 @@ +package MR::IProto::Error; + +=head1 NAME + +MR::IProto::Error - iproto error + +=head1 DESCRIPTION + +Instance of this class is returned instead of L<MR::IProto::Response> +if error was occured. + +=cut + +use Mouse; + +=head1 PUBLIC ATTRIBUTES + +=over + +=item error + +Error string. + +=cut + +has error => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +=item errno + +Integer value of C<$!>. + +=cut + +has errno => ( + is => 'ro', + isa => 'Int', +); + +=item request + +Instance of L<MR::IProto::Request>. + +=cut + +has request => ( + is => 'ro', + isa => 'MR::IProto::Request', + required => 1, +); + +=back + +=head1 PUBLIC METHODS + +=over + +=item is_error + +Always returns true. + +=cut + +sub is_error { + return 1; +} + +=item error_message + +Error message text. + +=cut + +sub error_message { + my ($self) = @_; + return $self->error; +} + +=back + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Message.pm b/connector/perl/lib/MR/IProto/Message.pm new file mode 100644 index 0000000000000000000000000000000000000000..2282378871687493a24ea0545d4dc9b6b342c626 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Message.pm @@ -0,0 +1,41 @@ +package MR::IProto::Message; + +=head1 NAME + +MR::IProto::Message - iproto message + +=head1 DESCRIPTION + +Base class for all messages sent throught iproto protocol. + +=cut + +use Mouse; + +=head1 ATTRIBUTES + +=over + +=item data + +Binary representation of message data. + +=cut + +has data => ( + is => 'ro', + isa => 'Value', +); + +=back + +=head1 SEE ALSO + +L<MR::IProto>. + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/NoResponse.pm b/connector/perl/lib/MR/IProto/NoResponse.pm new file mode 100644 index 0000000000000000000000000000000000000000..e3a5be524f922826cb1d6279264184c05a55ef00 --- /dev/null +++ b/connector/perl/lib/MR/IProto/NoResponse.pm @@ -0,0 +1,23 @@ +package MR::IProto::NoResponse; + +=head1 NAME + +MR::IProto::NoResponse - no response + +=head1 DESCRIPTION + +Base class used to mark messages with no response. + +=cut + +use Mouse; +extends 'MR::IProto::Response'; + +has '+data' => ( + isa => 'Undef', +); + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Request.pm b/connector/perl/lib/MR/IProto/Request.pm new file mode 100644 index 0000000000000000000000000000000000000000..5c86e5842ff1999afe0eb99b36fdbf4e72d890c6 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Request.pm @@ -0,0 +1,73 @@ +package MR::IProto::Request; + +=head1 NAME + +MR::IProto::Request - request message + +=head1 DESCRIPTION + +Base class for all request messages. + +=cut + +use Mouse; +extends 'MR::IProto::Message'; + +has '+data' => ( + lazy_build => 1, +); + +=head1 PUBLIC METHODS + +=over + +=item key + +Returns key value. +You must reimplement this method in your subclass to use this feature. + +=cut + +sub key { + return undef; +} + +=item retry + +If request retry is allowed. + +=cut + +sub retry { + return 0; +} + +=back + +=head1 PROTECTED METHODS + +=over + +=item key_attr + +Class method which must return name of key attribute. + +=item _build_data + +You B<must> implement this method in you subclass to pack your object. +Returns packed data. + +=cut + +sub _build_data { + return ''; +} + +=back + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Response.pm b/connector/perl/lib/MR/IProto/Response.pm new file mode 100644 index 0000000000000000000000000000000000000000..49088530cd4b2b2e0829522c25ca54c080387f8a --- /dev/null +++ b/connector/perl/lib/MR/IProto/Response.pm @@ -0,0 +1,94 @@ +package MR::IProto::Response; + +=head1 NAME + +MR::IProto::Response - response message + +=head1 DESCRIPTION + +Base class for all response messages. + +=cut + +use Mouse; +extends 'MR::IProto::Message'; + +=head1 PUBLIC ATTRIBUTES + +=over + +=item request + +Instance of L<MR::IProto::Request>. + +=cut + +has request => ( + is => 'ro', + isa => 'MR::IProto::Request', + required => 1, +); + +=back + +=head1 PUBLIC METHODS + +=over + +=item retry + +Is request retry must be done. + +=cut + +sub retry { + return 0; +} + +=back + +=head1 PROTECTED METHODS + +=over + +=item BUILDARGS( %args | \%args | $data ) + +If C<$data> is passed as argument then it unpacked and object is +created based on information contained in it. + +See L<Mouse::Manual::Construction/BUILDARGS> for more information. + +=cut + +around BUILDARGS => sub { + my ($orig, $class, %args) = @_; + if( exists $args{data} ) { + my $parsed_args = $class->_parse_data($args{data}); + my $tail = delete $parsed_args->{data}; + warn "Not all data was parsed" if defined $tail && length $tail; + return $class->$orig(%$parsed_args, %args); + } + else { + return $class->$orig(%args); + } +}; + +=item _parse_data( $data ) + +You B<must> implement this method in you subclass to unpack your object. +Returns hashref of attributes which will be passed to constructor. + +=cut + +sub _parse_data { + return { data => $_[1] }; +} + +=back + +=cut + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Role/Debuggable.pm b/connector/perl/lib/MR/IProto/Role/Debuggable.pm new file mode 100644 index 0000000000000000000000000000000000000000..34a670ed2899b5b8f98dec42053b62df9d99c71a --- /dev/null +++ b/connector/perl/lib/MR/IProto/Role/Debuggable.pm @@ -0,0 +1,74 @@ +package MR::IProto::Role::Debuggable; + +=head1 NAME + +=head1 DESCRIPTION + +=cut + +use Mouse::Role; + +=item debug + +Deug level. + +=cut + +has debug => ( + is => 'rw', + isa => 'Int', + default => 0, +); + +=item debug_cb + +Callback which is called when debug message is written. + +=cut + +has debug_cb => ( + is => 'rw', + isa => 'CodeRef', + lazy_build => 1, +); + +=item dump_no_ints + +Skip print of integers in dump. + +=cut + +has dump_no_ints => ( + is => 'ro', + isa => 'Bool', +); + +sub _build_debug_cb { + my ($self) = @_; + my $prefix = ref $self; + return sub { + my ($msg) = @_; + chomp $msg; + warn sprintf "$prefix: $msg\n"; + return; + }; +} + +sub _debug { + $_[0]->debug_cb->($_[1]); +} + +sub _debug_dump { + my ($self, $msg, $datum) = @_; + unless($self->dump_no_ints) { + $msg .= join(' ', unpack('L*', $datum)); + $msg .= ' > '; + } + $msg .= join(' ', map { sprintf "%02x", $_ } unpack("C*", $datum)); + $self->_debug($msg); + return; +} + +no Mouse::Role; + +1; diff --git a/connector/perl/lib/MR/IProto/Server.pm b/connector/perl/lib/MR/IProto/Server.pm new file mode 100644 index 0000000000000000000000000000000000000000..4c8b121142dc5103d762801db4ef7f4985a785e8 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Server.pm @@ -0,0 +1,120 @@ +package MR::IProto::Server; + +=head1 NAME + +=head1 DESCRIPTION + +=cut + +use Mouse; +use AnyEvent::Handle; +use AnyEvent::Socket; +use Scalar::Util qw/weaken/; +use MR::IProto::Server::Connection; + +with 'MR::IProto::Role::Debuggable'; + +has prefix => ( + is => 'ro', + isa => 'Str', + default => sub { ref shift }, +); + +has host => ( + is => 'ro', + isa => 'Str', + default => '0.0.0.0', +); + +has port => ( + is => 'ro', + isa => 'Int', + required => 1, +); + +has handler => ( + is => 'ro', + isa => 'CodeRef', + required => 1, +); + +has on_accept => ( + is => 'ro', + isa => 'CodeRef', +); + +has on_close => ( + is => 'ro', + isa => 'CodeRef', +); + +has on_error => ( + is => 'ro', + isa => 'CodeRef', +); + +has _guard => ( + is => 'ro', + lazy_build => 1, +); + +has _connections => ( + is => 'ro', + isa => 'HashRef', + default => sub { {} }, +); + +has _recv_payload => ( + is => 'ro', + isa => 'CodeRef', + lazy_build => 1, +); + +sub run { + my ($self) = @_; + $self->_guard; + return; +} + +sub _build_debug_cb { + my ($self) = @_; + my $prefix = $self->prefix; + return sub { + my ($msg) = @_; + chomp $msg; + warn sprintf "%s: %s\n", $prefix, $msg; + return; + }; +} + +sub _build__guard { + my ($self) = @_; + weaken($self); + return tcp_server $self->host, $self->port, sub { + my ($fh, $host, $port) = @_; + my $connection = MR::IProto::Server::Connection->new( + fh => $fh, + host => $host, + port => $port, + handler => $self->handler, + on_accept => $self->on_accept, + on_close => sub { + my ($connection) = @_; + my $key = sprintf "%s:%d", $connection->host, $connection->port; + delete $self->_connections->{$key}; + $self->on_close->($connection) if $self->on_close; + return; + }, + on_error => $self->on_error, + debug => $self->debug, + debug_cb => $self->debug_cb, + ); + $self->_connections->{"$host:$port"} = $connection; + return; + }; +} + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/IProto/Server/Connection.pm b/connector/perl/lib/MR/IProto/Server/Connection.pm new file mode 100644 index 0000000000000000000000000000000000000000..eb8260fe4b47411c8848e5e479c21fb4d407ba80 --- /dev/null +++ b/connector/perl/lib/MR/IProto/Server/Connection.pm @@ -0,0 +1,186 @@ +package MR::IProto::Server::Connection; + +=head1 NAME + +=head1 DESCRIPTION + +=cut + +use Mouse; +use AnyEvent::DNS; +use Scalar::Util qw/weaken/; + +with 'MR::IProto::Role::Debuggable'; + +has handler => ( + is => 'ro', + isa => 'CodeRef', + required => 1, +); + +has on_accept => ( + is => 'ro', + isa => 'CodeRef', +); + +has on_close => ( + is => 'ro', + isa => 'CodeRef', +); + +has on_error => ( + is => 'ro', + isa => 'CodeRef', +); + +has fh => ( + is => 'ro', + isa => 'FileHandle', + required => 1, +); + +has host => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +has port => ( + is => 'ro', + isa => 'Int', + required => 1, +); + +has hostname => ( + is => 'ro', + isa => 'Str', + writer => '_hostname', + lazy_build => 1, +); + +has _handle => ( + is => 'ro', + isa => 'AnyEvent::Handle', + lazy_build => 1, +); + +has _recv_header => ( + is => 'ro', + isa => 'CodeRef', + lazy_build => 1, +); + +sub BUILD { + my ($self) = @_; + $self->_debug(sprintf "Connection accepted") if $self->debug >= 1; + weaken($self); + AnyEvent::DNS::reverse_verify $self->host, sub { + my ($hostname) = @_; + if ($hostname) { + $self->_hostname($hostname); + $self->_debug(sprintf "%s resolved as %s", $self->host, $hostname) if $self->debug >= 4; + } else { + $self->_hostname($self->host); + $self->_debug(sprintf "Can't resolve %s", $self->host); + } + $self->_handle; + $self->on_accept->($self) if $self->on_accept; + return; + }; + return; +} + +sub DEMOLISH { + my ($self) = @_; + $self->_debug(sprintf "Object for connection was destroyed\n") if $self->debug >= 1; + return; +} + +sub close { + my ($self) = @_; + $self->on_close->($self) if $self->on_close; + return; +} + +sub _build_hostname { + my ($self) = @_; + return $self->host; +} + +sub _build__handle { + my ($self) = @_; + weaken($self); + my $peername = join ':', $self->host, $self->port; + return AnyEvent::Handle->new( + fh => $self->fh, + peername => $peername, + on_read => sub { + my ($handle) = @_; + $handle->unshift_read( chunk => 12, $self->_recv_header ); + return; + }, + on_eof => sub { + my ($handle) = @_; + $self->_debug("Connection closed by foreign host\n") if $self->debug >= 1; + $handle->destroy(); + $self->on_close->($self) if $self->on_close; + return; + }, + on_error => sub { + my ($handle, $fatal, $message) = @_; + $handle->destroy(); + if ($self->on_error) { + $self->_debug("error: $message\n") if $self->debug >= 1; + $self->on_error->($self, $message); + } else { + $self->_debug("error: $message\n"); + } + $self->on_close->($self) if $self->on_close; + return; + } + ); + return; +} + +sub _build__recv_header { + my ($self) = @_; + weaken($self); + my $handler = $self->handler; + return sub { + my ($handle, $data) = @_; + $self->_debug_dump('recv header: ', $data) if $self->debug >= 5; + my ($cmd, $length, $sync) = unpack 'L3', $data; + $handle->unshift_read( + chunk => $length, + sub { + my ($handle, $data) = @_; + $self->_debug_dump('recv payload: ', $data) if $self->debug >= 5; + my $result; + if (eval { $result = $handler->($self, $cmd, $data); 1 }) { + my $header = pack 'L3', $cmd, length $result, $sync; + if ($self->debug >= 6) { + $self->_debug_dump('send header: ', $header); + $self->_debug_dump('send payload: ', $result); + } + $handle->push_write($header . $result); + } else { + warn $@; + $self->_debug("Failed to handle cmd=$cmd\n"); + } + return; + } + ); + return; + }; +} + +sub _debug { + my ($self, $msg) = @_; + $self->debug_cb->( sprintf "%s(%s:%d): %s", $self->hostname, $self->host, $self->port, $msg ); + return; +} + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/SilverBox.pm b/connector/perl/lib/MR/SilverBox.pm index cc8befc2d339714b612cdad081cb81e91551e107..8c0bfaf32a7930ab203ea5179447d50332ef49aa 100644 --- a/connector/perl/lib/MR/SilverBox.pm +++ b/connector/perl/lib/MR/SilverBox.pm @@ -1,646 +1,19 @@ package MR::SilverBox; +use MR::Tarantool::Box; +use base qw/MR::Tarantool::Box/; -use strict; -use warnings; -use Scalar::Util qw/looks_like_number/; -use List::MoreUtils qw/each_arrayref/; +=pod -use MR::IProto (); -use MR::Storage::Const (); +=head1 NAME -use constant { - WANT_RESULT => 1, - INSERT_ADD => 2, - INSERT_REPLACE => 4, -}; +MR::SilverBox +A backward-compatiblity module. Is it obsolete and is unsupported. Do not use. -sub IPROTOCLASS () { 'MR::IProto' } -sub ERRSTRCLASS () { 'MR::Storage::Const::Errors::SilverBox' } +=head1 SEE ALSO -use vars qw/$VERSION/; -$VERSION = 0; +L<MR::Tarantool::Box> -BEGIN { *confess = \&MR::IProto::confess } - -sub new { - my ($class, $arg) = @_; - my $self; - - $arg = { %$arg }; - $self->{name} = $arg->{name} || ref$class || $class; - $self->{timeout} = $arg->{timeout} || 23; - $self->{retry } = $arg->{retry} || 1; - $self->{select_retry} = $arg->{select_retry} || 3; - $self->{softretry} = $arg->{softretry} || 3; - $self->{debug} = $arg->{'debug'} || 0; - $self->{ipdebug} = $arg->{'ipdebug'} || 0; - $self->{raise} = 1; - $self->{raise} = $arg->{raise} if exists $arg->{raise}; - $self->{hashify} = $arg->{'hashify'} if exists $arg->{'hashify'}; - $self->{default_raw} = exists $arg->{default_raw} ? $arg->{default_raw} : !$self->{hashify}; - $self->{select_timeout} = $arg->{select_timeout} || $self->{timeout}; - $self->{iprotoclass} = $arg->{iprotoclass} || $class->IPROTOCLASS; - $self->{errstrclass} = $arg->{errstrclass} || $class->ERRSTRCLASS; - $self->{_last_error} = 0; - - $arg->{namespaces} = [@{ $arg->{namespaces} }]; - my %namespaces; - for my $ns (@{$arg->{namespaces}}) { - $ns = { %$ns }; - my $namespace = $ns->{namespace}; - confess "ns[?] `namespace' not set" unless defined $namespace; - confess "ns[$namespace] already defined" if $namespaces{$namespace} || $ns->{name}&&$namespaces{$ns->{name}}; - confess "ns[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}}; - $namespaces{$namespace} = $ns; - $namespaces{$ns->{name}} = $ns if $ns->{name}; - confess "ns[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScCqQ ]/; - $ns->{format} =~ s/\s+//g; - my @f = split //, $ns->{format}; - $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ]; - $ns->{field_format} = [ map { /&/ ? 'a*' : $_ } @f ]; - $ns->{unpack_format} = join('', @{$ns->{byfield_unpack_format}}); - $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack}; - $ns->{check_keys} = {}; - $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f }; - my $inames = $ns->{index_names} = {}; - my $i = -1; - for my $index (@{$ns->{indexes}}) { - ++$i; - confess "ns[$namespace]index[($i)] no name given" unless length $index->{index_name}; - my $index_name = $index->{index_name}; - confess "ns[$namespace]index[$index_name($i)] no indexes defined" unless $index->{keys} && @{$index->{keys}}; - confess "ns[$namespace]index[$index_name($i)] already defined" if $inames->{$index_name} || $inames->{$i}; - $index->{id} = $i unless defined $index->{id}; - $inames->{$i} = $inames->{$index_name} = $index; - int $_ == $_ and $_ >= 0 and $_ < @f or confess "ns[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}}; - $ns->{check_keys}->{$_} = int !! $ns->{string_keys}->{$_} for @{$index->{keys}}; - $index->{string_keys} ||= $ns->{string_keys}; - } - if( @{$ns->{indexes}} > 1 ) { - confess "ns[$namespace] default_index not given" unless defined $ns->{default_index}; - confess "ns[$namespace] default_index $ns->{default_index} does not exist" unless $inames->{$ns->{default_index}}; - } else { - $ns->{default_index} ||= 0; - } - } - $self->{namespaces} = \%namespaces; - if (values %namespaces > 1) { - confess "default_namespace not given" unless defined $arg->{default_namespace}; - confess "default_namespace $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}}; - $self->{default_namespace} = $arg->{default_namespace}; - } else { - $self->{default_namespace} = $arg->{default_namespace} || $arg->{namespaces}->[0]->{namespace}; - } - bless $self, $class; - $self->_connect($arg->{'servers'}); - return $self; -} - -sub _debug { - if($_[0]->{warn}) { - &{$_[0]->{warn}}; - } else { - warn "@_[1..$#_]\n"; - } -} - -sub _connect { - my ($self, $servers) = @_; - $self->{server} = $self->{iprotoclass}->new({ - servers => $servers, - name => $self->{name}, - debug => $self->{'ipdebug'}, - dump_no_ints => 1, - }); -} - -sub ErrorStr { - my ($self, $code) = @_; - return $self->{_last_error_msg} if $self->{_last_error} eq 'fail'; - return $self->{errstrclass}->ErrorStr($code || $self->{_last_error}); -} - -sub Error { - return $_[0]->{_last_error}; -} - -sub _chat { - my ($self, %param) = @_; - my $orig_unpack = delete $param{unpack}; - - $param{unpack} = sub { - my $data = $_[0]; - confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4; - my ($full_code, @err_code) = unpack('LX[L]CCCC', substr($data, 0, 4, '')); - # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent; - # $err_code[1] = description; - # $err_code[3] = da box project; - return (\@err_code, \$data, $full_code); - }; - - my $timeout = $param{timeout} || $self->{timeout}; - my $retry = $param{retry} || $self->{retry}; - my $soft_retry = $self->{softretry}; - my $retry_count = 0; - - while ($retry > 0) { - $retry_count++; - - $self->{_last_error} = 0x77777777; - $self->{server}->SetTimeout($timeout); - my $ret = $self->{server}->Chat1(%param); - my $message; - - if (exists $ret->{ok}) { - my ($ret_code, $data, $full_code) = @{$ret->{ok}}; - $self->{_last_error} = $full_code; - if ($ret_code->[0] == 0) { - my $ret = $orig_unpack->($$data,$ret_code->[3]); - confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0; - return $ret; - } - - $message = $self->{errstrclass}->ErrorStr($full_code); - $self->_debug("$self->{name}: $message") if $self->{debug} >= 1; - if ($ret_code->[0] == 2) { #fatal error - $self->_raise($message) if $self->{raise}; - return 0; - } - - # retry if error is soft even in case of update e.g. ROW_LOCK - if ($ret_code->[0] == 1 and --$soft_retry > 0) { - --$retry if $retry > 1; - sleep 1; - next; - } - } else { # timeout has caused the failure if $ret->{timeout} - $self->{_last_error} = 'fail'; - $message ||= $self->{_last_error_msg} = $ret->{fail}; - $self->_debug("$self->{name}: $message") if $self->{debug} >= 1; - } - - last unless --$retry; - - sleep 1; - }; - - $self->_raise("no success after $retry_count tries\n") if $self->{raise}; -} - -sub _raise { - my ($self, $msg) = @_; - die "$self->{name}: $msg"; -} - -sub _validate_param { - my ($self, $args, @pnames) = @_; - my $param = ref $args->[-1] eq 'HASH' ? {%{pop @$args}}: {}; - - foreach my $pname (keys %$param) { - confess "$self->{name}: unknown param $pname\n" if 0 == grep { $_ eq $pname } @pnames; - } - - $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace}; - confess "$self->{name}: bad namespace `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}}; - my $ns = $self->{namespaces}->{$param->{namespace}}; - $param->{use_index} = $ns->{default_index} unless defined $param->{use_index}; - confess "$self->{name}: bad index `$param->{use_index}'" unless exists $ns->{index_names}->{$param->{use_index}}; - $param->{index} = $ns->{index_names}->{$param->{use_index}}; - return ($param, map { /namespace/ ? $self->{namespaces}->{$param->{namespace}} : $param->{$_} } @pnames); -} - -sub Add { # store tuple if tuple identified by primary key _does_not_ exist - my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; - $param->{action} = 'add'; - $_[0]->Insert(@_[1..$#_], $param); -} - -sub Set { # store tuple _anyway_ - my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; - $param->{action} = 'set'; - $_[0]->Insert(@_[1..$#_], $param); -} - -sub Replace { # store tuple if tuple identified by primary key _does_ exist - my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; - $param->{action} = 'replace'; - $_[0]->Insert(@_[1..$#_], $param); -} - -sub Insert { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace _flags action/); - my ($self, @tuple) = @_; - - $self->_debug("$self->{name}: INSERT(@{[map {qq{`$_'}} @tuple]})") if $self->{debug} >= 3; - - my $flags = $param->{_flags} || 0; - $param->{action} ||= 'set'; - if ($param->{action} eq 'add') { - $flags |= INSERT_ADD; - } elsif ($param->{action} eq 'replace') { - $flags |= INSERT_REPLACE; - } elsif ($param->{action} ne 'set') { - confess "$self->{name}: Bad insert action `$param->{action}'"; - } - my $chkkey = $namespace->{check_keys}; - my $fmt = $namespace->{field_format}; - for (0..$#tuple) { - confess "$self->{name}: ref in tuple $_=`$tuple[$_]'" if ref $tuple[$_]; - no warnings 'uninitialized'; - if(exists $chkkey->{$_}) { - if($chkkey->{$_}) { - confess "$self->{name}: undefined key $_" unless defined $tuple[$_]; - } else { - confess "$self->{name}: not numeric key $_=`$tuple[$_]'" unless looks_like_number($tuple[$_]) && int($tuple[$_]) == $tuple[$_]; - } - } - $tuple[$_] = pack($fmt->[$_], $tuple[$_]); - } - - $self->_debug("$self->{name}: INSERT[${\join ' ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4; - - $self->_chat ( - msg => 13, - payload => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple), - unpack => sub { $self->_unpack_affected($flags, $namespace, @_) } - ); -} - -sub _unpack_select { - my ($self, $ns, $debug_prefix) = @_; - local *@; - $debug_prefix ||= "SELECT"; - confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 4; - my $result_count = unpack('L', substr($_[3], 0, 4, '')); - $self->_debug("$self->{name}: [$debug_prefix]: COUNT=[$result_count];") if $self->{debug} >= 3; - my (@res); - my $appe = $ns->{append_for_unpack}; - my $fmt = $ns->{unpack_format}; - for(my $i = 0; $i < $result_count; ++$i) { - confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 8; - my ($len, $cardinality) = unpack('LL', substr($_[3], 0, 8, '')); - $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: LEN=[$len]; NFIELD=[$cardinality];") if $self->{debug} >= 4; - confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < $len; - my $packed_tuple = substr($_[3], 0, $len, ''); - $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: DATA=[@{[unpack '(H2)*', $packed_tuple]}];") if $self->{debug} >= 6; - $packed_tuple .= $appe; - my @tuple = eval { unpack($fmt, $packed_tuple) }; - confess "$self->{name}: [$debug_prefix]: ROW[$i]: can't unpack tuple [@{[unpack('(H2)*', $packed_tuple)]}]" if !@tuple || $@; - $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: FIELDS=[@{[map { qq{`$_'} } @tuple]}];") if $self->{debug} >= 5; - push @res, \@tuple; - } - return \@res; -} - -sub _unpack_select_multi { - my ($self, $nss, $debug_prefix) = @_; - $debug_prefix ||= "SMULTI"; - my (@rsets); - my $i = 0; - for my $ns (@$nss) { - push @rsets, $self->_unpack_select($ns, "${debug_prefix}[$i]", $_[3]); - ++$i; - } - return \@rsets; -} - - -sub _unpack_affected { - my ($self, $flags, $ns) = @_; - - ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3])->[0] : unpack('L', substr($_[3],0,4,'')); -} - -sub NPRM () { 3 } -sub _pack_keys { - my ($self, $ns, $idx) = @_; - - my $keys = $idx->{keys}; - my $strkey = $ns->{string_keys}; - my $fmt = $ns->{field_format}; - - if (@$keys == 1) { - $fmt = $fmt->[$keys->[0]]; - $strkey = $strkey->{$keys->[0]}; - foreach (@_[NPRM..$#_]) { - ($_) = @$_ if ref $_ eq 'ARRAY'; - unless ($strkey) { - confess "$self->{name}: not numeric key [$_]" unless looks_like_number($_) && int($_) == $_; - $_ = pack($fmt, $_); - } - $_ = pack('L(w/a*)*', 1, $_); - } - } else { - foreach my $k (@_[NPRM..$#_]) { - confess "bad key [@$keys][$k][@{[ref $k eq 'ARRAY' ? (@$k) : ()]}]" unless ref $k eq 'ARRAY' and @$k and @$k <= @$keys; - for my $i (0..$#$k) { - unless ($strkey->{$keys->[$i]}) { - confess "$self->{name}: not numeric key [$i][$k->[$i]]" unless looks_like_number($k->[$i]) && int($k->[$i]) == $k->[$i]; - } - $k->[$i] = pack($fmt->[$keys->[$i]], $k->[$i]); - } - $k = pack('L(w/a*)*', scalar(@$k), @$k); - } - } -} - -sub _PackSelect { - my ($self, $param, $namespace, @keys) = @_; - return '' unless @keys; - $self->_pack_keys($namespace, $param->{index}, @keys); - my $format = ""; - if ($param->{format}) { - my $f = $namespace->{byfield_unpack_format}; - $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}}; - $format = pack 'l*', scalar @{$param->{format}}, map { - $_ = { %$_ }; - if($_->{full}) { - $_->{offset} = 0; - $_->{length} = 'max'; - } - $_->{length} = 0x7FFFFFFF if $_->{length} eq 'max'; - @$_{qw/field offset length/} - } @{$param->{format}}; - } - return pack("LLLL a* La*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), $format, scalar(@keys), join('',@keys)); -} - -sub _PostSelect { - my ($self, $r, $param) = @_; - if(!$param->{raw} && ref $param->{hashify} eq 'CODE') { - $param->{hashify}->($param->{namespace}->{namespace}, $r); - } -} - -my @select_param_ok = qw/namespace use_index raw want next_rows limit offset raise hashify timeout format hash_by/; -sub Select { - confess q/Select isnt callable in void context/ unless defined wantarray; - my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok); - my ($self, @keys) = @_; - local $self->{raise} = $param->{raise} if defined $param->{raise}; - @keys = @{$keys[0]} if ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || ref $keys[0]->[0] eq 'ARRAY'; - - $self->_debug("$self->{name}: SELECT($namespace->{namespace}/$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3; - - my ($msg,$payload); - if(exists $param->{next_rows}) { - confess "$self->{name}: One and only one key can be used to get N>0 rows after it" if @keys != 1 || !$param->{next_rows}; - $msg = 15; - $self->_pack_keys($namespace, $param->{index}, @keys); - $payload = pack("LL a*", $namespace->{namespace}, $param->{next_rows}, join('',@keys)), - } else { - $payload = $self->_PackSelect($param, $namespace, @keys); - $msg = $param->{format} ? 21 : 17; - } - - local $namespace->{unpack_format} = $param->{unpack_format} if $param->{unpack_format}; - - my $r = []; - if (@keys && $payload) { - $r = $self->_chat( - msg => $msg, - payload => $payload, - unpack => sub { $self->_unpack_select($namespace, "SELECT", @_) }, - retry => $self->{select_retry}, - timeout => $param->{timeout} || $self->{select_timeout}, - ) or return; - } - - $param->{raw} = $self->{default_raw} unless exists $param->{raw}; - $param->{want} ||= !1; - - $self->_PostSelect($r, { hashify => $param->{hashify}||$namespace->{hashify}||$self->{hashify}, %$param, namespace => $namespace }); - - if(defined(my $p = $param->{hash_by})) { - my %h; - if(@$r) { - if (ref $r->[0] eq 'HASH') { - confess "Bad hash_by `$p' for HASH" unless exists $r->[0]->{$p}; - $h{$_->{$p}} = $_ for @$r; - } elsif(ref $r->[0] eq 'ARRAY') { - confess "Bad hash_by `$p' for ARRAY" unless $p =~ m/^\d+$/ && $p >= 0 && $p < @{$r->[0]}; - $h{$_->[$p]} = $_ for @$r; - } else { - confess "i dont know how to hash_by ".ref($r->[0]); - } - } - return \%h; - } - - return $r if $param->{want} eq 'arrayref'; - - if (wantarray) { - return @{$r}; - } else { - confess "$self->{name}: too many keys in scalar context" if @keys > 1; - return $r->[0]; - } -} - -sub SelectUnion { - confess "not supported yet"; - my ($param) = $_[0]->_validate_param(\@_, qw/raw raise/); - my ($self, @reqs) = @_; - return [] unless @reqs; - local $self->{raise} = $param->{raise} if defined $param->{raise}; - confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs; - $param->{raw} = $self->{default_raw} unless exists $param->{raw}; - $param->{want} ||= 0; - for my $req (@reqs) { - my ($param, $namespace) = $self->_validate_param($req, @select_param_ok); - $req = { - payload => $self->_PackSelect($param, $namespace, $req), - param => $param, - namespace => $namespace, - }; - } - my $r = $self->_chat( - msg => 18, - payload => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs), - unpack => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) }, - retry => $self->{select_retry}, - timeout => $param->{timeout} || $self->{select_timeout}, - ) or return; - confess __LINE__."$self->{name}: something wrong" if @$r != @reqs; - my $ea = each_arrayref $r, \@reqs; - while(my ($res, $req) = $ea->()) { - $self->_PostSelect($res, { hashify => $req->{namespace}->{hashify}||$self->{hashify}, %$param, %{$req->{param}}, namespace => $req->{namespace} }); - } - return $r; -} - -sub Delete { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace/); - my ($self, $key) = @_; - - $self->_debug("$self->{name}: DELETE($key)") if $self->{debug} >= 3; - - confess "$self->{name}\->Delete: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; - $self->_pack_keys($namespace, $param->{index}, $key); - - $self->_chat ( - msg => 20, - payload => pack("L a*", $namespace->{namespace}, $key), - unpack => sub { $self->_unpack_affected(0, $namespace, @_) } - ); -} - -sub OP_SET () { 0 } -sub OP_ADD () { 1 } -sub OP_AND () { 2 } -sub OP_XOR () { 3 } -sub OP_OR () { 4 } -sub OP_SPLICE () { 5 } - -my %update_ops = ( - set => OP_SET, - add => OP_ADD, - and => OP_AND, - xor => OP_XOR, - or => OP_OR, - splice => sub { - confess "value for operation splice must be an ARRAYREF of <int[, int[, string]]>" if ref $_[0] ne 'ARRAY' || @{$_[0]} < 1; - $_[0]->[0] = 0x7FFFFFFF unless defined $_[0]->[0]; - $_[0]->[0] = pack 'l', $_[0]->[0]; - $_[0]->[1] = defined $_[0]->[1] ? pack 'l', $_[0]->[1] : ''; - $_[0]->[2] = '' unless defined $_[0]->[2]; - return (OP_SPLICE, [ pack '(w/a*)*', @{$_[0]} ]); - }, - append => sub { splice => [undef, 0, $_[0]] }, - prepend => sub { splice => [0, 0, $_[0]] }, - cutbeg => sub { splice => [0, $_[0], '' ] }, - cutend => sub { splice => [-$_[0], $_[0], '' ] }, - substr => 'splice', -); - -!ref $_ && m/^\D/ and $_ = $update_ops{$_} || die "bad link" for values %update_ops; - -my %update_arg_fmt = ( - (map { $_ => 'l' } OP_ADD), - (map { $_ => 'L' } OP_AND, OP_XOR, OP_OR), -); - -my %ops_type = ( - (map { $_ => 'any' } OP_SET), - (map { $_ => 'number' } OP_ADD, OP_AND, OP_XOR, OP_OR), - (map { $_ => 'string' } OP_SPLICE), -); - -BEGIN { - for my $op (qw/Append Prepend Cutbeg Cutend Substr/) { - eval q/ - sub /.$op.q/ { - my $param = ref $_[-1] eq 'HASH' ? pop : {}; - my ($self, $key, $field_num, $val) = @_; - $self->UpdateMulti($key, [ $field_num => /.lc($op).q/ => $val ], $param); - } - 1; - / or die $@; - } -} - -sub UpdateMulti { - my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace want_result _flags raw/); - my ($self, $key, @op) = @_; - - $self->_debug("$self->{name}: UPDATEMULTI($namespace->{namespace}=$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3; - - confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; - confess "$self->{name}: too many op" if scalar @op > 128; - - my $flags = $param->{_flags} || 0; - $flags |= WANT_RESULT if $param->{want_result}; - - my $fmt = $namespace->{field_format}; - - foreach (@op) { - confess "$self->{name}: bad op <$_>" if ref ne 'ARRAY' or @$_ != 3; - my ($field_num, $op, $value) = @$_; - my $field_type = $namespace->{string_keys}->{$field_num} ? 'string' : 'number'; - - my $is_array = 0; - if ($op eq 'bit_set') { - $op = OP_OR; - } elsif ($op eq 'bit_clear') { - $op = OP_AND; - $value = ~$value; - } elsif ($op =~ /^num_(add|sub)$/) { - $value = -$value if $1 eq 'sub'; - $op = OP_ADD; - } else { - confess "$self->{name}: bad op <$op>" unless exists $update_ops{$op}; - $op = $update_ops{$op}; - } - - while(ref $op eq 'CODE') { - ($op, $value) = &$op($value); - $op = $update_ops{$op} if exists $update_ops{$op}; - } - - confess "Are you sure you want to apply `$ops_type{$op}' operation to $field_type field?" if $ops_type{$op} ne $field_type && $ops_type{$op} ne 'any'; - - $value = [ $value ] unless ref $value; - confess "dunno what to do with ref `$value'" if ref $value ne 'ARRAY'; - - confess "bad fieldnum: $field_num" if $field_num >= @$fmt; - $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], @$value); - $_ = pack('LCw/a*', $field_num, $op, $value); - } - - $self->_pack_keys($namespace, $param->{index}, $key); - - $self->_chat( - msg => 19, - payload => pack("LL a* L (a*)*" , $namespace->{namespace}, $flags, $key, scalar(@op), @op), - unpack => sub { $self->_unpack_affected($flags, $namespace, @_) } - ); -} - -sub Update { - my $param = ref $_[-1] eq 'HASH' ? pop : {}; - my ($self, $key, $field_num, $value) = @_; - $self->UpdateMulti($key, [$field_num => set => $value ], $param); -} - -sub AndXorAdd { - my $param = ref $_[-1] eq 'HASH' ? pop : {}; - my ($self, $key, $field_num, $and, $xor, $add) = @_; - my @upd; - push @upd, [$field_num => and => $and] if defined $and; - push @upd, [$field_num => xor => $xor] if defined $xor; - push @upd, [$field_num => add => $add] if defined $add; - $self->UpdateMulti($key, @upd, $param); -} - -sub Bit { - my $param = ref $_[-1] eq 'HASH' ? pop : {}; - my ($self, $key, $field_num, %arg) = @_; - confess "$self->{name}: unknown op '@{[keys %arg]}'" if grep { not /^(bit_clear|bit_set|set)$/ } keys(%arg); - - $arg{bit_clear} ||= 0; - $arg{bit_set} ||= 0; - my @op; - push @op, [$field_num => set => $arg{set}] if exists $arg{set}; - push @op, [$field_num => bit_clear => $arg{bit_clear}] if $arg{bit_clear}; - push @op, [$field_num => bit_set => $arg{bit_set}] if $arg{bit_set}; - - $self->UpdateMulti($key, @op, $param); -} - -sub Num { - my $param = ref $_[-1] eq 'HASH' ? pop : {}; - my ($self, $key, $field_num, %arg) = @_; - confess "$self->{name}: unknown op '@{[keys %arg]}'" if grep { not /^(num_add|num_sub|set)$/ } keys(%arg); - - $arg{num_add} ||= 0; - $arg{num_sub} ||= 0; - - $arg{num_add} -= $arg{num_sub}; - my @op; - push @op, [$field_num => set => $arg{set}] if exists $arg{set}; - push @op, [$field_num => num_add => $arg{num_add}]; # if $arg{num_add}; - $self->UpdateMulti($key, @op, $param); -} +=cut 1; diff --git a/connector/perl/lib/MR/Storage/Const.pm b/connector/perl/lib/MR/Storage/Const.pm deleted file mode 100644 index 64f1deee76fb7439be5de49f449bac4d0fd03df0..0000000000000000000000000000000000000000 --- a/connector/perl/lib/MR/Storage/Const.pm +++ /dev/null @@ -1,109 +0,0 @@ -use strict; -use Exporter; - -package MR::Storage::Const; -use base qw/Exporter/; - -use vars qw/$VERSION @EXPORT_OK %EXPORT_TAGS %ERRORS/; -$VERSION = 0; - -my (@ERRORS); -$EXPORT_TAGS{all} = \@EXPORT_OK; -$EXPORT_TAGS{errors} = \@ERRORS; - -BEGIN { - sub constantiate($$;@) { - my ($const, @arrs) = @_; - my $pkg = (caller)[0]; - my @a; - eval qq{ sub $pkg\::$_ () { $const->{$_} }; push \@a, q{$_}; 'TRUE'.$_; } or die $@ - for keys %$const; - push @$_, @a for @arrs; - } - - # Errors. Numbers >=0 are returned by server, <0 are internals for the module. - constantiate { - SE_OK => 0, - SE_UNKNOWN => -1, - SE_COMM => -2, - }, \@ERRORS, \@EXPORT_OK; -} - -%ERRORS = ( - 0x00000000 => q{OK}, - 0x00000100 => q{Non master connection, but it should be}, - 0x00000200 => q{Illegal parametrs}, - 0x00000300 => q{Uid not from this storage range}, - 0x00000400 => q{Node is marked as read-only}, - 0x00000500 => q{Node isn't locked}, - 0x00000600 => q{Node is locked}, - 0x00000700 => q{Some memory issues}, - 0x00000800 => q{Bad integrity}, - 0x00000a00 => q{Unsupported command}, - - 0x00000b00 => q{Can't do select}, - 0x00000c00 => q{Silverspoon for first uid is not found}, - 0x00000d00 => q{Silverspoon for second uid is not found}, - 0x00000e00 => q{Silverspoons for both uids are not found}, - 0x00000f00 => q{Can't do 2pc prepare on first sp}, - 0x00001000 => q{Can't do 2pc prepare on second sp}, - 0x00001100 => q{Can't do 2pc prepare on both sps}, - 0x00001200 => q{Can't do 2pc commit on first sp}, - 0x00001300 => q{Can't do 2pc commit on second sp}, - 0x00001400 => q{Can't do 2pc commit on both sps}, - 0x00001500 => q{Can't do 2pc abort on first sp}, - 0x00001600 => q{Can't do 2pc abort on second sp}, - 0x00001700 => q{Can't do 2pc abort on both sps}, - - 0x00001800 => q{Can't register new user}, - 0x00001a00 => q{Can't generate alert id}, - 0x00001b00 => q{Can't del node}, - - 0x00001c00 => q{User isn't registered}, - 0x00001d00 => q{Syntax error in query}, - 0x00001e00 => q{Unknown field}, - 0x00001f00 => q{Number value is out of range}, - 0x00002000 => q{Insert already existing object}, - 0x00002200 => q{Can not order result}, - 0x00002300 => q{Multiple update/delete forbidden}, - 0x00002400 => q{Nothing affected}, - 0x00002500 => q{Primary key update forbidden}, - 0x00002600 => q{Incorrect protocol version}, - 0x00002700 => q{Bad record ID}, - 0x00002800 => q{WAL failed}, - 0x00002900 => q{Bad event ID}, - 0x00002a00 => q{Read-only mode}, - 0x00002b00 => q{Event is locked}, - 0x00002c00 => q{Event is already liked by this user}, - 0x00002d00 => q{Event is not liked by this user}, - 0x00002e00 => q{The message has been already deleted by this user}, - 0x00002f00 => q{User can't delete message}, - 0x00003100 => q{Node not found}, - 0x00003200 => q{User isn't member of the dialogue}, - 0x00003300 => q{Dialogue not found}, - 0x00003400 => q{Error during iconv}, - 0x00003500 => q{Event isn't contained in this node}, - 0x00003600 => q{Proxy reply: destination node timed out}, - 0x00003700 => q{Node found}, - 0x00003800 => q{Index violation}, -); - -sub ErrorText { - my $e = $_[1] & 0xFFFFFF00; - return $ERRORS{$e} if exists $ERRORS{$e}; - return 'Unknown error'; -} - -sub ErrorStr { - my $e = $_[1] ? ($_[1] % 0xFF) ? 'Error' : 'Warning' : ''; - $e &&= sprintf "$e %08X: ", $_[1]; - return sprintf "%s%s", $e, $_[0]->ErrorText($_[1]); -} - -package MR::Storage::Const::Errors; -use base qw/MR::Storage::Const/; - -package MR::Storage::Const::Errors::SilverBox; -use base qw/MR::Storage::Const::Errors/; - -1; diff --git a/connector/perl/lib/MR/Tarantool/Box.pm b/connector/perl/lib/MR/Tarantool/Box.pm new file mode 100644 index 0000000000000000000000000000000000000000..0e8279f4f1c7f40512ffc001b75553017e8a8a67 --- /dev/null +++ b/connector/perl/lib/MR/Tarantool/Box.pm @@ -0,0 +1,1280 @@ +package MR::Tarantool::Box; + +=pod + +=head1 NAME + +MR::Tarantool::Box - A driver for an efficient Tarantool/Box NoSQL in-memory storage. + +=head1 SYNOPSIS + + my $box = MR::Tarantool::Box->new({ + servers => "127.0.0.1:33013", + name => "My Box", # mostly used for debug purposes + spaces => [ { + indexes => [ { + index_name => 'idx1', + keys => [0], + }, { + index_name => 'idx2', + keys => [1,2], + }, ], + space => 1, # space id, as set in Tarantool/Box config + name => "primary", # self-descriptive space-id + format => "QqLlSsCc&", # pack()-compatible, Qq must be supported by perl itself, & stands for byte-string. + default_index => 'idx1', + fields => [qw/ id f2 field3 f4 f5 f6 f7 f8 misc_string /], # turn each tuple into hash, field names according to format + }, { + #... + } ], + default_space => "primary", + + timeout => 1.0, # seconds + retry => 3, + debug => 9, # output to STDERR some debugging info + raise => 0, # dont raise an exception in case of error + }); + + my $bool = $box->Insert(1, 2,3, 4,5,6,7,8,"asdf") or die $box->ErrorStr; + my $bool = $box->Insert(2, 2,4, 4,5,6,7,8,"asdf",{space => "primary"}) or die $box->ErrorStr; + my $tuple = $box->Insert(3, 3,3, 4,5,6,7,8,"asdf",{want_inserted_tuple => 1}) or die $box->ErrorStr; + + # Select by single-field key + my $tuple = $box->Select(1); # scalar context - scalar result: $tuple + my @tuples = $box->Select(1,2,3); # list context - list result: ($tuple, $tuple, ...) + my $tuples = $box->Select([1,2,3],{space => "primary", use_index => "idx1"}); # arrayref result: [$tuple, $tuple, ...] + + # Select by multi-field key + my $tuples = $box->Select([[2,3]],{use_index => "idx2"}); # by full key + my $tuples = $box->Select([[2]] ,{use_index => "idx2"}); # by partial key + + my $bool = $box->UpdateMulti(1,[ f4 => add => 3 ]); + my $bool = $box->UpdateMulti(2,[ f4 => add => 3 ],{space => "primary"}); + my $tuple = $box->UpdateMulti(3,[ f4 => add => 3 ],{want_updated_tuple => 1}); + + my $bool = $box->Delete(1); + my $tuple = $box->Delete(2, {want_deleted_tuple => 1}); + +=head1 DESCRIPTION + +=head2 METHODS + +=cut + +use strict; +use warnings; +use Scalar::Util qw/looks_like_number/; +use List::MoreUtils qw/each_arrayref zip/; +use Time::HiRes qw/sleep/; + +use MR::IProto (); + +use constant { + WANT_RESULT => 1, + INSERT_ADD => 2, + INSERT_REPLACE => 4, +}; + + +sub IPROTOCLASS () { 'MR::IProto' } + +use vars qw/$VERSION %ERRORS/; +$VERSION = 0.0.11; + +BEGIN { *confess = \&MR::IProto::confess } + +%ERRORS = ( + 0x00000000 => q{OK}, + 0x00000100 => q{Non master connection, but it should be}, + 0x00000200 => q{Illegal parametrs}, + 0x00000300 => q{Uid not from this storage range}, + 0x00000400 => q{Tuple is marked as read-only}, + 0x00000500 => q{Tuple isn't locked}, + 0x00000600 => q{Tuple is locked}, + 0x00000700 => q{Failed to allocate memory}, + 0x00000800 => q{Bad integrity}, + 0x00000a00 => q{Unsupported command}, + + 0x00000b00 => q{Can't do select}, + + 0x00001800 => q{Can't register new user}, + 0x00001a00 => q{Can't generate alert id}, + 0x00001b00 => q{Can't del node}, + + 0x00001c00 => q{User isn't registered}, + 0x00001d00 => q{Syntax error in query}, + 0x00001e00 => q{Unknown field}, + 0x00001f00 => q{Number value is out of range}, + 0x00002000 => q{Insert already existing object}, + 0x00002200 => q{Can not order result}, + 0x00002300 => q{Multiple update/delete forbidden}, + 0x00002400 => q{Nothing affected}, + 0x00002500 => q{Primary key update forbidden}, + 0x00002600 => q{Incorrect protocol version}, + 0x00002700 => q{WAL failed}, + 0x00003000 => q{Procedure return type is not supported in the binary protocol}, + 0x00003100 => q{Tuple doesn't exist}, + 0x00003200 => q{Procedure is not defined}, + 0x00003300 => q{Lua error}, + 0x00003400 => q{Space is disabled}, + 0x00003500 => q{No such index in space}, + 0x00003600 => q{Field was not found in the tuple}, + 0x00003700 => q{Tuple already exists}, + 0x00003800 => q{Duplicate key exists in a unique index}, + 0x00003900 => q{Space does not exists}, +); + + + +=pod + +=head3 new + + my $box = $class->new(\%args); + +%args: + +=over + +=item B<spaces> => [ \%space, ... ] + +%space: + +=over + +=item B<space> => $space_id_uint32 + +Space id as set in Tarantool/Box config. + +=item B<name> => $space_name_string + +Self-descriptive space id, which will be mapped into C<space>. + +=item B<format> => $format_string + +C<pack()>-compatible tuple format string, allowed formats: C<QqLlSsCc&>, +where C<&> stands for bytestring. C<Qq> usable only if perl supports +int64 itself. Tuples' fields are packed/unpacked according to this C<format>. + +=item B<hashify> => B<$coderef> + +Specify a callback to turn each tuple into a good-looking hash. +It receives C<space> id and resultset as arguments. No return value needed. + + $coderef = sub { + my ($space_id, $resultset) = @_; + $_ = { FieldName1 => $_->[0], FieldName2 => $_->[1], ... } for @$resultset; + }; + +=item B<fields> => B<$arrayref> + +Specify an arrayref of fields names according to C<format> to turn each +tuple into a good-looking hash. Names must begin with C<< [A-Za-z] >>. + +=item B<indexes> => [ \%index, ... ] + +%index: + +=over + +=item B<id> => $index_id_uint32 + +Index id as set in Tarantool/Box config within current C<space>. +If not set, order position in C<indexes> is theated as C<id>. + +=item B<name> => $index_name_string + +Self-descriptive index id, which will be mapped into C<index_id>. + +=item B<keys> => [ $field_no_uint32, ... ] + +Properly ordered arrayref of fields' numbers which are indexed. + +=back + +=item B<default_index> => $default_index_name_string_or_id_uint32 + +Index C<id> or C<name> to be used by default for the current C<space>. +Must be set if there are more than one C<\%index>es. + +=back + +=item B<default_space> => $default_space_name_string_or_id_uint32 + +Space C<space> or C<name> to be used by default. Must be set if there are +more than one C<\%space>s. + +=item B<timeout> => $timeout_fractional_seconds_float || 23 + +A common timeout for network operations. + +=item B<select_timeout> => $select_timeout_fractional_seconds_float || 2 + +Select queries timeout for network operations. See L</select_retry>. + +=item B<retry> => $retry_int || 1 + +A common retries number for network operations. + +=item B<select_retry> => $select_retry_int || 3 + +Select queries retries number for network operations. + +Sometimes we need short timeout for select's and long timeout for B<critical> update's, +because in case of timeout we B<don't know if the update has succeeded>. For the same +reason we B<can't retry> update operation. + +So increasing C<timeout> and setting C<< retry => 1 >> for updates lowers possibility of +such situations (but, of course, does not exclude them at all), and guarantees that +we dont do the same more then once. + +=item B<soft_retry> => $soft_retry_int || 3 + +A common retries number for Tarantool/Box B<temporary errors> (these marked by 1 in the +lowest byte of C<error_code>). In that case we B<know for sure> that the B<request was +declined> by Tarantool/Box for some reason (a tuple was locked for another update, for +example), and we B<can> try it again. + +This is also limited by C<retry>/C<select_retry> +(depending on query type). + +=item B<retry_delay> => $retry_delay_fractional_seconds_float || 1 + +Specify a delay between retries for network operations. + +=item B<raise> => $raise_bool || 1 + +Should we raise an exceptions? If so, exceptions are raised when no more retries left and +all tries failed (with timeout, fatal, or temporary error). + +=item B<debug> => $debug_level_int || 0 + +Debug level, 0 - print nothing, 9 - print everything + +=item B<name> => $name + +A string used for self-description. Mainly used for debugging purposes. + +=back + +=cut + +sub new { + my ($class, $arg) = @_; + my $self; + + $arg = { %$arg }; + $self->{name} = $arg->{name} || ref$class || $class; + $self->{timeout} = $arg->{timeout} || 23; + $self->{retry} = $arg->{retry} || 1; + $self->{retry_delay} = $arg->{retry_delay} || 1; + $self->{select_retry} = $arg->{select_retry} || 3; + $self->{softretry} = $arg->{soft_retry} || $arg->{softretry} || 3; + $self->{debug} = $arg->{'debug'} || 0; + $self->{ipdebug} = $arg->{'ipdebug'} || 0; + $self->{raise} = 1; + $self->{raise} = $arg->{raise} if exists $arg->{raise}; + $self->{select_timeout} = $arg->{select_timeout} || $self->{timeout}; + $self->{iprotoclass} = $arg->{iprotoclass} || $class->IPROTOCLASS; + $self->{_last_error} = 0; + + $self->{hashify} = $arg->{'hashify'} if exists $arg->{'hashify'}; + $self->{default_raw} = $arg->{default_raw}; + + $arg->{spaces} = $arg->{namespaces} = [@{ $arg->{spaces} ||= $arg->{namespaces} || confess "no spaces given" }]; + confess "no spaces given" unless @{$arg->{spaces}}; + my %namespaces; + for my $ns (@{$arg->{spaces}}) { + $ns = { %$ns }; + my $namespace = defined $ns->{space} ? $ns->{space} : $ns->{namespace}; + $ns->{space} = $ns->{namespace} = $namespace; + confess "space[?] `space' not set" unless defined $namespace; + confess "space[$namespace] already defined" if $namespaces{$namespace} or $ns->{name}&&$namespaces{$ns->{name}}; + confess "space[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}}; + $namespaces{$namespace} = $ns; + $namespaces{$ns->{name}} = $ns if $ns->{name}; + confess "space[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScCqQ ]/; + $ns->{format} =~ s/\s+//g; + my @f = split //, $ns->{format}; + $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ]; + $ns->{field_format} = [ map { /&/ ? 'a*' : $_ } @f ]; + $ns->{unpack_format} = join('', @{$ns->{byfield_unpack_format}}); + $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack}; + $ns->{check_keys} = {}; + $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f }; + my $inames = $ns->{index_names} = {}; + my $i = -1; + for my $index (@{$ns->{indexes}}) { + ++$i; + confess "space[$namespace]index[($i)] no name given" unless length $index->{index_name}; + my $index_name = $index->{index_name}; + confess "space[$namespace]index[$index_name($i)] no indexes defined" unless $index->{keys} && @{$index->{keys}}; + confess "space[$namespace]index[$index_name($i)] already defined" if $inames->{$index_name} || $inames->{$i}; + $index->{id} = $i unless defined $index->{id}; + $inames->{$i} = $inames->{$index_name} = $index; + int $_ == $_ and $_ >= 0 and $_ < @f or confess "space[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}}; + $ns->{check_keys}->{$_} = int !! $ns->{string_keys}->{$_} for @{$index->{keys}}; + $index->{string_keys} ||= $ns->{string_keys}; + } + if( @{$ns->{indexes}} > 1 ) { + confess "space[$namespace] default_index not given" unless defined $ns->{default_index}; + confess "space[$namespace] default_index $ns->{default_index} does not exist" unless $inames->{$ns->{default_index}}; + } else { + $ns->{default_index} ||= 0; + } + if($ns->{fields}) { + confess "space[$namespace] fields must be ARRAYREF" unless ref $ns->{fields} eq 'ARRAY'; + confess "space[$namespace] fields number must match format" if @{$ns->{fields}} != @f; + m/^[A-Za-z]/ or confess "space[$namespace] fields names must begin with [A-Za-z]: bad name $_" for @{$ns->{fields}}; + $ns->{fields_hash} = { map { $ns->{fields}->[$_] => $_ } 0..$#{$ns->{fields}} }; + } + } + $self->{namespaces} = \%namespaces; + if (@{$arg->{spaces}} > 1) { + $arg->{default_namespace} = $arg->{default_space} if defined $arg->{default_space}; + confess "default_space not given" unless defined $arg->{default_namespace}; + confess "default_space $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}}; + $self->{default_namespace} = $arg->{default_namespace}; + } else { + $self->{default_namespace} = $arg->{default_space} || $arg->{default_namespace} || $arg->{spaces}->[0]->{space}; + confess "default_space $self->{default_namespace} does not exist" unless $namespaces{$self->{default_namespace}}; + } + bless $self, $class; + $self->_connect($arg->{'servers'}); + return $self; +} + +sub _debug { + if($_[0]->{warn}) { + &{$_[0]->{warn}}; + } else { + warn "@_[1..$#_]\n"; + } +} + +sub _connect { + my ($self, $servers) = @_; + $self->{server} = $self->{iprotoclass}->new({ + servers => $servers, + name => $self->{name}, + debug => $self->{'ipdebug'}, + dump_no_ints => 1, + }); +} + +=pod + +=head3 Error + +Last error code, or 'fail' for some network reason, oftenly a timeout. + + $box->Insert(@tuple) or die sprintf "Error %X", $box->Error; # die "Error 202" + +=head3 ErrorStr + +Last error code and description in a single string. + + $box->Insert(@tuple) or die $box->ErrorStr; # die "Error 00000202: Illegal Parameters" + +=cut + +sub ErrorStr { + return $_[0]->{_last_error_msg}; +} + +sub Error { + return $_[0]->{_last_error}; +} + +sub _chat { + my ($self, %param) = @_; + my $orig_unpack = delete $param{unpack}; + + $param{unpack} = sub { + my $data = $_[0]; + confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4; + my ($full_code, @err_code) = unpack('LX[L]CSC', substr($data, 0, 4, '')); + # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent; + # $err_code[1] = description; + # $err_code[2] = da box project; + return (\@err_code, \$data, $full_code); + }; + + my $timeout = $param{timeout} || $self->{timeout}; + my $retry = $param{retry} || $self->{retry}; + my $soft_retry = $self->{softretry}; + my $retry_count = 0; + + while ($retry > 0) { + $retry_count++; + + $self->{_last_error} = 0x77777777; + $self->{server}->SetTimeout($timeout); + my $ret = $self->{server}->Chat1(%param); + my $message; + + if (exists $ret->{ok}) { + my ($ret_code, $data, $full_code) = @{$ret->{ok}}; + $self->{_last_error} = $full_code; + if ($ret_code->[0] == 0) { + my $ret = $orig_unpack->($$data,$ret_code->[2]); + confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0; + return $ret; + } + + $self->{_last_error_msg} = $message = $ret_code->[0] == 0 ? "ok" : sprintf "Error %08X: %s", $full_code, $$data || $ERRORS{$full_code & 0xFFFFFF00} || 'Unknown error'; + $self->_debug("$self->{name}: $message") if $self->{debug} >= 1; + if ($ret_code->[0] == 2) { #fatal error + $self->_raise($message) if $self->{raise}; + return 0; + } + + # retry if error is soft even in case of update e.g. ROW_LOCK + if ($ret_code->[0] == 1 and --$soft_retry > 0) { + --$retry if $retry > 1; + sleep $self->{retry_delay}; + next; + } + } else { # timeout has caused the failure if $ret->{timeout} + $self->{_last_error} = 'fail'; + $message ||= $self->{_last_error_msg} = $ret->{fail}; + $self->_debug("$self->{name}: $message") if $self->{debug} >= 1; + } + + last unless --$retry; + + sleep $self->{retry_delay}; + }; + + $self->_raise("no success after $retry_count tries\n") if $self->{raise}; +} + +sub _raise { + my ($self, $msg) = @_; + die "$self->{name}: $msg\n"; +} + +sub _validate_param { + my ($self, $args, @pnames) = @_; + my $param = $args && @$args && ref $args->[-1] eq 'HASH' ? {%{pop @$args}} : {}; + + my %pnames = map { $_ => 1 } @pnames; + $pnames{space} = 1; + $pnames{namespace} = 1; + foreach my $pname (keys %$param) { + confess "$self->{name}: unknown param $pname\n" unless exists $pnames{$pname}; + } + + $param->{namespace} = $param->{space} if exists $param->{space} and defined $param->{space}; + $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace}; + confess "$self->{name}: bad space `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}}; + + my $ns = $self->{namespaces}->{$param->{namespace}}; + $param->{use_index} = $ns->{default_index} unless defined $param->{use_index}; + confess "$self->{name}: bad index `$param->{use_index}'" unless exists $ns->{index_names}->{$param->{use_index}}; + $param->{index} = $ns->{index_names}->{$param->{use_index}}; + + if(exists $pnames{raw}) { + $param->{raw} = $ns->{default_raw} unless defined $param->{raw}; + $param->{raw} = $self->{default_raw} unless defined $param->{raw}; + } + + return ($param, $ns, map { $param->{$_} } @pnames); +} + +=pod + +=head3 Call + +Call a stored procedure. Returns an arrayref of the result tuple(s) upon success. + + my $results = $box->Call('stored_procedure_name', \@procedure_params, \%options) or die $box->ErrorStr; # Call failed + my $result_tuple = @$results && $results->[0] or warn "Call succeeded, but returned nothing"; + +=over + +=item B<@procedure_params> + +An array of bytestrings to be passed as is to the procecedure. + +=item B<%options> + +=over + +=item B<unpack_format> + +Format to unpack the result tuple, the same as C<format> option for C<new()> + +=back + +=back + +=cut + +sub Call { + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/flags raise unpack unpack_format/); + my ($self, $sp_name, $tuple) = @_; + + my $flags = $param->{flags} || 0; + local $self->{raise} = $param->{raise} if defined $param->{raise}; + + $self->_debug("$self->{name}: CALL($sp_name)[${\join ' ', map {join' ',unpack'(H2)*',$_} @$tuple}]") if $self->{debug} >= 4; + confess "All fields must be defined" if grep { !defined } @$tuple; + + confess "Bad `unpack_format` option" if exists $param->{unpack_format} and ref $param->{unpack_format} ne 'ARRAY'; + my $unpack_format = join '', map { /&/ ? 'w/a*' : "x$_" } @{$param->{unpack_format}}; + + local $namespace->{unpack_format} = $unpack_format if $unpack_format; # XXX + local $namespace->{append_for_unpack} = '' if $unpack_format; # shit... + + $self->_chat ( + msg => 22, + payload => pack("L w/a* L(w/a*)*", $flags, $sp_name, scalar(@$tuple), @$tuple), + unpack => $param->{unpack} || sub { $self->_unpack_select($namespace, "CALL", @_) }, + callback => $param->{callback}, + ); +} + +=pod + +=head3 Add, Insert, Replace + + $box->Add(@tuple) or die $box->ErrorStr; # only store a new tuple + $box->Replace(@tuple, { space => "secondary" }); # only store an existing tuple + $box->Insert(@tuple, { space => "main" }); # store anyway + +Insert a C<< @tuple >> into the storage into C<$options{space}> or C<default_space> space. +All of them return C<true> upon success. + +All of them have the same parameters: + +=over + +=item B<@tuple> + +A tuple to insert. All fields must be defined. All fields will be C<pack()>ed according to C<format> (see L</new>) + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space to work on. + +=back + +=back + +The difference between them is the behaviour concerning tuple with the same primary key: + +=over + +=item * + +B<Add> will succeed if and only if duplicate-key tuple B<does not exist> + +=item * + +B<Replace> will succeed if and only if a duplicate-key tuple B<exists> + +=item * + +B<Insert> will succeed B<anyway>. Duplicate-key tuple will be B<overwritten> + +=back + +=cut + +sub Add { # store tuple if tuple identified by primary key _does_not_ exist + my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; + $param->{action} = 'add'; + $_[0]->Insert(@_[1..$#_], $param); +} + +sub Set { # store tuple _anyway_ + my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; + $param->{action} = 'set'; + $_[0]->Insert(@_[1..$#_], $param); +} + +sub Replace { # store tuple if tuple identified by primary key _does_ exist + my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {}; + $param->{action} = 'replace'; + $_[0]->Insert(@_[1..$#_], $param); +} + +sub Insert { + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_result want_inserted_tuple _flags action raw/); + my ($self, @tuple) = @_; + + $self->_debug("$self->{name}: INSERT(NS:$namespace->{namespace},TUPLE:[@{[map {qq{`$_'}} @tuple]}])") if $self->{debug} >= 3; + + $param->{want_result} = $param->{want_inserted_tuple} if !defined $param->{want_result}; + + + my $flags = $param->{_flags} || 0; + $flags |= WANT_RESULT if $param->{want_result}; + + $param->{action} ||= 'set'; + if ($param->{action} eq 'add') { + $flags |= INSERT_ADD; + } elsif ($param->{action} eq 'replace') { + $flags |= INSERT_REPLACE; + } elsif ($param->{action} ne 'set') { + confess "$self->{name}: Bad insert action `$param->{action}'"; + } + my $chkkey = $namespace->{check_keys}; + my $fmt = $namespace->{field_format}; + confess "Wrong fields number in tuple" if @tuple != @$fmt; + for (0..$#tuple) { + confess "$self->{name}: ref in tuple $_=`$tuple[$_]'" if ref $tuple[$_]; + no warnings 'uninitialized'; + if(exists $chkkey->{$_}) { + if($chkkey->{$_}) { + confess "$self->{name}: undefined key $_" unless defined $tuple[$_]; + } else { + confess "$self->{name}: not numeric key $_=`$tuple[$_]'" unless looks_like_number($tuple[$_]) && int($tuple[$_]) == $tuple[$_]; + } + } + $tuple[$_] = pack($fmt->[$_], $tuple[$_]); + } + + $self->_debug("$self->{name}: INSERT[${\join ' ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4; + + my $r = $self->_chat ( + msg => 13, + payload => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple), + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, + ) or return; + + return $r unless $param->{want_result}; + + $self->_PostSelect($r, $param, $namespace); + return $r->[0]; +} + +sub _unpack_select { + my ($self, $ns, $debug_prefix) = @_; + $debug_prefix ||= "SELECT"; + confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 4; + my $result_count = unpack('L', substr($_[3], 0, 4, '')); + $self->_debug("$self->{name}: [$debug_prefix]: COUNT=[$result_count];") if $self->{debug} >= 3; + my (@res); + my $appe = $ns->{append_for_unpack}; + my $fmt = $ns->{unpack_format}; + for(my $i = 0; $i < $result_count; ++$i) { + confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 8; + my ($len, $cardinality) = unpack('LL', substr($_[3], 0, 8, '')); + $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: LEN=[$len]; NFIELD=[$cardinality];") if $self->{debug} >= 4; + confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < $len; + my $packed_tuple = substr($_[3], 0, $len, ''); + $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: DATA=[@{[unpack '(H2)*', $packed_tuple]}];") if $self->{debug} >= 6; + $packed_tuple .= $appe; + my @tuple = eval { unpack($fmt, $packed_tuple) }; + confess "$self->{name}: [$debug_prefix]: ROW[$i]: can't unpack tuple [@{[unpack('(H2)*', $packed_tuple)]}]: $@" if !@tuple || $@; + $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: FIELDS=[@{[map { qq{`$_'} } @tuple]}];") if $self->{debug} >= 5; + push @res, \@tuple; + } + return \@res; +} + +sub _unpack_select_multi { + my ($self, $nss, $debug_prefix) = @_; + $debug_prefix ||= "SMULTI"; + my (@rsets); + my $i = 0; + for my $ns (@$nss) { + push @rsets, $self->_unpack_select($ns, "${debug_prefix}[$i]", $_[3]); + ++$i; + } + return \@rsets; +} + + +sub _unpack_affected { + my ($self, $flags, $ns) = @_; + ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3]) : unpack('L', substr($_[3],0,4,''))||'0E0'; +} + +sub NPRM () { 3 } +sub _pack_keys { + my ($self, $ns, $idx) = @_; + + my $keys = $idx->{keys}; + my $strkey = $ns->{string_keys}; + my $fmt = $ns->{field_format}; + + if (@$keys == 1) { + $fmt = $fmt->[$keys->[0]]; + $strkey = $strkey->{$keys->[0]}; + foreach (@_[NPRM..$#_]) { + ($_) = @$_ if ref $_ eq 'ARRAY'; + unless ($strkey) { + confess "$self->{name}: not numeric key [$_]" unless looks_like_number($_) && int($_) == $_; + $_ = pack($fmt, $_); + } + $_ = pack('L(w/a*)*', 1, $_); + } + } else { + foreach my $k (@_[NPRM..$#_]) { + confess "bad key [@$keys][$k][@{[ref $k eq 'ARRAY' ? (@$k) : ()]}]" unless ref $k eq 'ARRAY' and @$k and @$k <= @$keys; + for my $i (0..$#$k) { + unless ($strkey->{$keys->[$i]}) { + confess "$self->{name}: not numeric key [$i][$k->[$i]]" unless looks_like_number($k->[$i]) && int($k->[$i]) == $k->[$i]; + } + $k->[$i] = pack($fmt->[$keys->[$i]], $k->[$i]); + } + $k = pack('L(w/a*)*', scalar(@$k), @$k); + } + } +} + +sub _PackSelect { + my ($self, $param, $namespace, @keys) = @_; + return '' unless @keys; + $self->_pack_keys($namespace, $param->{index}, @keys); + my $format = ""; + if ($param->{format}) { + my $f = $namespace->{byfield_unpack_format}; + $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}}; + $format = pack 'l*', scalar @{$param->{format}}, map { + $_ = { %$_ }; + if($_->{full}) { + $_->{offset} = 0; + $_->{length} = 'max'; + } + $_->{length} = 0x7FFFFFFF if $_->{length} eq 'max'; + @$_{qw/field offset length/} + } @{$param->{format}}; + } + return pack("LLLL a* La*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), $format, scalar(@keys), join('',@keys)); +} + +sub _PostSelect { + my ($self, $r, $param, $namespace) = @_; + if(!$param->{raw}) { + my $hashify = $param->{hashify} || $namespace->{hashify} || $self->{hashify}; + if($hashify) { + $hashify->($namespace->{namespace}, $r); + } elsif( $namespace->{fields} ) { + $_ = { zip @{$namespace->{fields}}, @$_ } for @$r; + } + } +} + +=pod + +=head3 Select + +Select tuple(s) from storage + + my $key = $id; + my $key = [ $firstname, $lastname ]; + my @keys = ($key, ...); + + my $tuple = $box->Select($key) or $box->Error && die $box->ErrorStr; + my $tuple = $box->Select($key, \%options) or $box->Error && die $box->ErrorStr; + + my @tuples = $box->Select(@keys) or $box->Error && die $box->ErrorStr; + my @tuples = $box->Select(@keys, \%options) or $box->Error && die $box->ErrorStr; + + my $tuples = $box->Select(\@keys) or die $box->ErrorStr; + my $tuples = $box->Select(\@keys, \%options) or die $box->ErrorStr; + +=over + +=item B<$key>, B<@keys>, B<\@keys> + +Specify keys to select. All keys must be defined. + +Contextual behaviour: + +=over + +=item * + +In scalar context, you can select one C<$key>, and the resulting tuple will be returned. +Check C<< $box->Error >> to see if there was an error or there is just no such key +in the storage + +=item * + +In list context, you can select several C<@keys>, and the resulting tuples will be returned. +Check C<< $box->Error >> to see if there was an error or there is just no such keys +in the storage + +=item * + +If you select C<< \@keys >> then C<< \@tuples >> will be returned upon success. C<< @tuples >> will +be empty if there are no such keys, and false will be returned in case of error. + +=back + +Other notes: + +=over + +=item * + +If you select using index on multiple fields each C<< $key >> should be given as a key-tuple C<< $key = [ $key_field1, $key_field2, ... ] >>. + +=back + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage (by id or name) space to select from. + +=item B<use_index> => $index_id_uint32_or_name_string + +Specify index (by id or name) to use. + +=item B<raw> => $bool + +Don't C<hashify> (see L</new>). + +=item B<hash_by> => $by + +Return a hashref of the resultset. If you C<hashify> the result set, +then C<$by> must be a field name of the hash you return, +otherwise it must be a number of field of the tuple. +C<False> will be returned in case of error. + +=back + +=back + +=cut + +my @select_param_ok = qw/use_index raw want next_rows limit offset raise hashify timeout format hash_by/; +sub Select { + confess q/Select isnt callable in void context/ unless defined wantarray; + my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok); + my ($self, @keys) = @_; + local $self->{raise} = $param->{raise} if defined $param->{raise}; + @keys = @{$keys[0]} if @keys && ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || (@keys && ref $keys[0]->[0] eq 'ARRAY'); + + $self->_debug("$self->{name}: SELECT(NS:$namespace->{namespace},IDX:$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3; + + my ($msg,$payload); + if(exists $param->{next_rows}) { + confess "$self->{name}: One and only one key can be used to get N>0 rows after it" if @keys != 1 || !$param->{next_rows}; + $msg = 15; + $self->_pack_keys($namespace, $param->{index}, @keys); + $payload = pack("LL a*", $namespace->{namespace}, $param->{next_rows}, join('',@keys)), + } else { + $payload = $self->_PackSelect($param, $namespace, @keys); + $msg = $param->{format} ? 21 : 17; + } + + local $namespace->{unpack_format} = $param->{unpack_format} if $param->{unpack_format}; + + my $r = []; + if (@keys && $payload) { + $r = $self->_chat( + msg => $msg, + payload => $payload, + unpack => sub { $self->_unpack_select($namespace, "SELECT", @_) }, + retry => $self->{select_retry}, + timeout => $param->{timeout} || $self->{select_timeout}, + callback => $param->{callback}, + ) or return; + } + + $param->{want} ||= !1; + + $self->_PostSelect($r, $param, $namespace); + + if(defined(my $p = $param->{hash_by})) { + my %h; + if(@$r) { + if (ref $r->[0] eq 'HASH') { + confess "Bad hash_by `$p' for HASH" unless exists $r->[0]->{$p}; + $h{$_->{$p}} = $_ for @$r; + } elsif(ref $r->[0] eq 'ARRAY') { + confess "Bad hash_by `$p' for ARRAY" unless $p =~ m/^\d+$/ && $p >= 0 && $p < @{$r->[0]}; + $h{$_->[$p]} = $_ for @$r; + } else { + confess "i dont know how to hash_by ".ref($r->[0]); + } + } + return \%h; + } + + return $r if $param->{want} eq 'arrayref'; + + if (wantarray) { + return @{$r}; + } else { + confess "$self->{name}: too many keys in scalar context" if @keys > 1; + return $r->[0]; + } +} + +sub SelectUnion { + confess "not supported yet"; + my ($param) = $_[0]->_validate_param(\@_, qw/raw raise/); + my ($self, @reqs) = @_; + return [] unless @reqs; + local $self->{raise} = $param->{raise} if defined $param->{raise}; + confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs; + $param->{want} ||= 0; + for my $req (@reqs) { + my ($param, $namespace) = $self->_validate_param($req, @select_param_ok); + $req = { + payload => $self->_PackSelect($param, $namespace, $req), + param => $param, + namespace => $namespace, + }; + } + my $r = $self->_chat( + msg => 18, + payload => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs), + unpack => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) }, + retry => $self->{select_retry}, + timeout => $param->{select_timeout} || $self->{timeout}, + callback => $param->{callback}, + ) or return; + confess __LINE__."$self->{name}: something wrong" if @$r != @reqs; + my $ea = each_arrayref $r, \@reqs; + while(my ($res, $req) = $ea->()) { + $self->_PostSelect($res, { %$param, %{$req->{param}} }, $req->{namespace}); + } + return $r; +} + +=pod + +=head3 Delete + +Delete tuple from storage. Return false upon error. + + my $n_deleted = $box->Delete($key) or die $box->ErrorStr; + my $n_deleted = $box->Delete($key, \%options) or die $box->ErrorStr; + warn "Nothing was deleted" unless int $n_deleted; + + my $deleted_tuple_set = $box->Delete($key, { want_deleted_tuples => 1 }) or die $box->ErrorStr; + warn "Nothing was deleted" unless @$deleted_tuple_set; + +=over + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space (by id or name) to work on. + +=item B<want_deleted_tuple> => $bool + +if C<$bool> then return deleted tuple. + +=back + +=back + +=cut + +sub Delete { + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_deleted_tuple want_result raw/); + my ($self, $key) = @_; + + $param->{want_result} = $param->{want_deleted_tuple} if !defined $param->{want_result}; + + my $flags = 0; + $flags |= WANT_RESULT if $param->{want_result}; + + $self->_debug("$self->{name}: DELETE(NS:$namespace->{namespace},KEY:$key,F:$flags)") if $self->{debug} >= 3; + + confess "$self->{name}\->Delete: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; + $self->_pack_keys($namespace, $param->{index}, $key); + + my $r = $self->_chat( + msg => $flags ? 21 : 20, + payload => $flags ? pack("L L a*", $namespace->{namespace}, $flags, $key) : pack("L a*", $namespace->{namespace}, $key), + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, + ) or return; + + return $r unless $param->{want_result}; + + $self->_PostSelect($r, $param, $namespace); + return $r->[0]; +} + +sub OP_SET () { 0 } +sub OP_ADD () { 1 } +sub OP_AND () { 2 } +sub OP_XOR () { 3 } +sub OP_OR () { 4 } +sub OP_SPLICE () { 5 } + +my %update_ops = ( + set => OP_SET, + add => OP_ADD, + and => OP_AND, + xor => OP_XOR, + or => OP_OR, + splice => sub { + confess "value for operation splice must be an ARRAYREF of <int[, int[, string]]>" if ref $_[0] ne 'ARRAY' || @{$_[0]} < 1; + $_[0]->[0] = 0x7FFFFFFF unless defined $_[0]->[0]; + $_[0]->[0] = pack 'l', $_[0]->[0]; + $_[0]->[1] = defined $_[0]->[1] ? pack 'l', $_[0]->[1] : ''; + $_[0]->[2] = '' unless defined $_[0]->[2]; + return (OP_SPLICE, [ pack '(w/a*)*', @{$_[0]} ]); + }, + append => sub { splice => [undef, 0, $_[0]] }, + prepend => sub { splice => [0, 0, $_[0]] }, + cutbeg => sub { splice => [0, $_[0], '' ] }, + cutend => sub { splice => [-$_[0], $_[0], '' ] }, + substr => 'splice', +); + +!ref $_ && m/^\D/ and $_ = $update_ops{$_} || die "bad link" for values %update_ops; + +my %update_arg_fmt = ( + (map { $_ => 'l' } OP_ADD), + (map { $_ => 'L' } OP_AND, OP_XOR, OP_OR), +); + +my %ops_type = ( + (map { $_ => 'any' } OP_SET), + (map { $_ => 'number' } OP_ADD, OP_AND, OP_XOR, OP_OR), + (map { $_ => 'string' } OP_SPLICE), +); + +BEGIN { + for my $op (qw/Append Prepend Cutbeg Cutend Substr/) { + eval q/ + sub /.$op.q/ { + my $param = ref $_[-1] eq 'HASH' ? pop : {}; + my ($self, $key, $field_num, $val) = @_; + $self->UpdateMulti($key, [ $field_num => /.lc($op).q/ => $val ], $param); + } + 1; + / or die $@; + } +} + +=pod + +=head3 UpdateMulti + +Apply several update operations to a tuple. + + my @op = ([ f1 => add => 10 ], [ f1 => and => 0xFF], [ f2 => set => time() ], [ misc_string => cutend => 3 ]); + + my $n_updated = $box->UpdateMulti($key, @op) or die $box->ErrorStr; + my $n_updated = $box->UpdateMulti($key, @op, \%options) or die $box->ErrorStr; + warn "Nothing was updated" unless int $n_updated; + + my $updated_tuple_set = $box->UpdateMulti($key, @op, { want_result => 1 }) or die $box->ErrorStr; + warn "Nothing was updated" unless @$updated_tuple_set; + +Different fields can be updated at one shot. +The same field can be updated more than once. +All update operations are done atomically. +Returns false upon error. + +=over + +=item B<@op> = ([ $field => $op => $value ], ...) + +=over + +=item B<$field> + +Field-to-update number or name (see L</fields>). + +=item B<$op> + +=over + +=item B<set> + +Set C<< $field >> to C<< $value >> + +=item B<add>, B<and>, B<xor>, B<or> + +Apply an arithmetic operation to C<< $field >> with argument C<< $value >> +Currently arithmetic operations are supported only for int32 (4-byte length) fields (and C<$value>s too) + +=item B<splice>, B<substr> + +Apply a perl-like L<splice|perlfunc/splice> operation to C<< $field >>. B<$value> = [$OFFSET, $LENGTH, $REPLACE_WITH]. +substr is just an alias. + +=item B<append>, B<prepend> + +Append or prepend C<< $field >> with C<$value> string. + +=item B<cutbeg>, B<cutend> + +Cut C<< $value >> bytes from beginning or end of C<< $field >>. + +=back + +=back + +=item B<%options> + +=over + +=item B<space> => $space_id_uint32_or_name_string + +Specify storage space (by id or name) to work on. + +=item B<want_updated_tuple> => $bool + +if C<$bool> then return updated tuple. + +=back + +=cut + +sub UpdateMulti { + my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_updated_tuple want_result _flags raw/); + my ($self, $key, @op) = @_; + + $self->_debug("$self->{name}: UPDATEMULTI(NS:$namespace->{namespace},KEY:$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3; + + confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}}; + confess "$self->{name}: too many op" if scalar @op > 128; + + $param->{want_result} = $param->{want_updated_tuple} if !defined $param->{want_result}; + + my $flags = $param->{_flags} || 0; + $flags |= WANT_RESULT if $param->{want_result}; + + my $fmt = $namespace->{field_format}; + my $fields_hash = $namespace->{fields_hash}; + + foreach (@op) { + confess "$self->{name}: bad op <$_>" if ref ne 'ARRAY' or @$_ != 3; + my ($field_num, $op, $value) = @$_; + + if($field_num =~ m/^[A-Za-z]/) { + confess "no such field $field_num in space $namespace->{name}($namespace->{space})" unless exists $fields_hash->{$field_num}; + $field_num = $fields_hash->{$field_num}; + } + + my $field_type = $namespace->{string_keys}->{$field_num} ? 'string' : 'number'; + + my $is_array = 0; + if ($op eq 'bit_set') { + $op = OP_OR; + } elsif ($op eq 'bit_clear') { + $op = OP_AND; + $value = ~$value; + } elsif ($op =~ /^num_(add|sub)$/) { + $value = -$value if $1 eq 'sub'; + $op = OP_ADD; + } else { + confess "$self->{name}: bad op <$op>" unless exists $update_ops{$op}; + $op = $update_ops{$op}; + } + + while(ref $op eq 'CODE') { + ($op, $value) = &$op($value); + $op = $update_ops{$op} if exists $update_ops{$op}; + } + + confess "Are you sure you want to apply `$ops_type{$op}' operation to $field_type field?" if $ops_type{$op} ne $field_type && $ops_type{$op} ne 'any'; + + $value = [ $value ] unless ref $value; + confess "dunno what to do with ref `$value'" if ref $value ne 'ARRAY'; + + confess "bad fieldnum: $field_num" if $field_num >= @$fmt; + $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], @$value); + $_ = pack('LCw/a*', $field_num, $op, $value); + } + + $self->_pack_keys($namespace, $param->{index}, $key); + + my $r = $self->_chat( + msg => 19, + payload => pack("LL a* L (a*)*" , $namespace->{namespace}, $flags, $key, scalar(@op), @op), + unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }, + callback => $param->{callback}, + ) or return; + + return $r unless $param->{want_result}; + + $self->_PostSelect($r, $param, $namespace); + return $r->[0]; +} + +sub Update { + my $param = ref $_[-1] eq 'HASH' ? pop : {}; + my ($self, $key, $field_num, $value) = @_; + $self->UpdateMulti($key, [$field_num => set => $value ], $param); +} + +sub AndXorAdd { + my $param = ref $_[-1] eq 'HASH' ? pop : {}; + my ($self, $key, $field_num, $and, $xor, $add) = @_; + my @upd; + push @upd, [$field_num => and => $and] if defined $and; + push @upd, [$field_num => xor => $xor] if defined $xor; + push @upd, [$field_num => add => $add] if defined $add; + $self->UpdateMulti($key, @upd, $param); +} + +sub Bit { + my $param = ref $_[-1] eq 'HASH' ? pop : {}; + my ($self, $key, $field_num, %arg) = @_; + confess "$self->{name}: unknown op '@{[keys %arg]}'" if grep { not /^(bit_clear|bit_set|set)$/ } keys(%arg); + + $arg{bit_clear} ||= 0; + $arg{bit_set} ||= 0; + my @op; + push @op, [$field_num => set => $arg{set}] if exists $arg{set}; + push @op, [$field_num => bit_clear => $arg{bit_clear}] if $arg{bit_clear}; + push @op, [$field_num => bit_set => $arg{bit_set}] if $arg{bit_set}; + + $self->UpdateMulti($key, @op, $param); +} + +sub Num { + my $param = ref $_[-1] eq 'HASH' ? pop : {}; + my ($self, $key, $field_num, %arg) = @_; + confess "$self->{name}: unknown op '@{[keys %arg]}'" if grep { not /^(num_add|num_sub|set)$/ } keys(%arg); + + $arg{num_add} ||= 0; + $arg{num_sub} ||= 0; + + $arg{num_add} -= $arg{num_sub}; + my @op; + push @op, [$field_num => set => $arg{set}] if exists $arg{set}; + push @op, [$field_num => num_add => $arg{num_add}]; # if $arg{num_add}; + $self->UpdateMulti($key, @op, $param); +} + +=head1 LICENCE AND COPYRIGHT + +This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. + +=head1 SEE ALSO + +=over + +=item * + +L<http://tarantool.org> + +=item * + +L<MR::Tarantool::Box::Singleton> + +=back + +=cut + + +1; diff --git a/connector/perl/lib/MR/Tarantool/Box/Singleton.pm b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm new file mode 100644 index 0000000000000000000000000000000000000000..3d106a1f800efd2c6ea7a805c9b071638d32ad72 --- /dev/null +++ b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm @@ -0,0 +1,484 @@ +package MR::Tarantool::Box::Singleton; + +=pod + +=head1 NAME + +MR::Tarantool::Box::Singleton - A singleton wrapper for L<MR::Tarantool::Box>. + +Provides connection-persistence and replica fallback. +Please read L<"MR::Tarantool::Box manual"|MR::Tarantool::Box> first. + +=head1 SYNOPSIS + + package Some::Tarantool::Box::Singleton; + use MR::Tarantool::Box::Singleton; + use base 'MR::Tarantool::Box::Singleton'; + + BEGIN { # generates "TUPLE_$field_name" constants, and methods: FIELDS, FIELDS_HASH + __PACKAGE__->mkfields(qw/ id f1 f2 f3 field4 f5 f6 f7 misc_string /); # applicable for DEFAULT_SPACE only + } + + sub SERVER { Some::Config->GetBoxServer() } + sub REPLICAS { Some::Config->GetBoxReplicas() } + + sub DEFAULT_SPACE { 0 } + + sub SPACES {[{ + space => 0, + indexes => [ { + index_name => 'primary_id', + keys => [TUPLE_id], + }, { + index_name => 'secondary_f1f2', + keys => [TUPLE_f1, TUPLE_f2], + }, ], + format => 'QqLlSsCc&', + default_index => 'primary_id', + }, { + space => 1, + indexes => [ { + index_name => 'primary_id', + keys => [0], + }, ], + format => '&&&&', + fields => [qw/ string1 str2 s3 s4 /], + }]} + +=head1 DESCRIPTION + +=head2 METHODS + +=cut + +use strict; +use warnings; + +use MR::Tarantool::Box; +use Class::Singleton; +use Carp qw/confess cluck/; +use List::Util qw/shuffle/; + +use base qw/Class::Singleton/; + +=pod + +=head3 mkfields + + BEGIN { + $CLASS->mkfields(@names); + } + +=over + +=item * + +Generates constants "TUPLE_$fieldname" => $fieldposition in C<$CLASS>. +Just Like if you say C<< use constant TUPLE_id => 0, TUPLE_f1 => 1, ...; >> + +=item * + +Generates C<$CLASS> variable C<< @fields >> containing field names, +and a C<$CLASS> method C<FIELDS> returning C<< @fields >>. + +=item * + +Generates C<$CLASS> variable C<< %fields >> containing field names mapping to positions, +and a C<$CLASS> method C<FIELDS_HASH> returning C<< \%fields >>. + +=item * + +These C<< @fields >> are applied to the C<< DEFAULT_SPACE >>, +if I<< fields >> were not set explicitly for that space. + +=back + +=cut + +sub mkfields { + my($class, @fields) = @_; + no strict 'refs'; + confess "Fields are already defined for $class" if @{"${class}::fields"}; + @{"${class}::fields"} = @fields; + %{"${class}::fields"} = map { $fields[$_] => $_ } 0..$#fields; + eval qq{ sub ${class}::TUPLE_$fields[$_] () { $_ } } for 0..$#fields; + eval qq{ sub ${class}::FIELDS () { \@${class}::fields } }; + eval qq{ sub ${class}::FIELDS_HASH () { \\\%${class}::fields } }; +} + +=pod + +=head3 declare_stored_procedure + + $CLASS->declare_stored_procedure(%args); + + $CLASS->declare_stored_procedure( + name => "box.do.something", # internal procedure name, in da box + method_name => "CallMyTestingStoredProcedure", # will generate method named + options => { default => options }, # MR::Tarantool::Box->Call \%options + params => [ qw{ P1 P2 P3 Param4 }], # names + + unpack_format => [qw/ & L S C /], + + params_format => [qw{ C S L a* }], + params_default => [ 1, 2, undef, 'the_default' ], # undef's are mandatory params + ); + + ... + + my $data = $CLASS->CallMyTestingStoredProcedure( + P1 => $val1, + P2 => $val2, + P3 => $val3, + Param4 => $val3, + { option => $value }, # optional + ) or warn $CLASS->ErrorStr; + +Declare a stored procedure. This generates C<$CLASS> method C<< $args{method_name} >> which +calls Tarantool/Box procedure C<< $args{name} >>, using C<< $args{options} >> as default +C<< \%options >> for C<< MR::Tarantool::Box->Call >> call. The generated method has the following +prototype: + + $CLASS->CallMyTestingStoredProcedure( %sp_params, \%optional_options ); + +Parameters description: + +=over + +=item B<%args>: + +=over + +=item B<name> => $tarantool_box_sp_name + +The name of procedure in Tarantool/Box to call. + +=item B<method_name> => $class_method_name + +Class method name to generate. + +=item B<options> => \%options + +Options to pass to L<MR::Taranatool::Box->Call|MR::Taranatool::Box/Call> method. + +=item B<params> => \@names + +Procedure input parameters' names + +=item B<params_default> => \@defaults + +Procedure input parameters default values. Undefined or absent value makes +its parameter mandatory. + +=item B<params_format> => \@format + +C<< pack() >>-compatible format to pack input parameters. Must match C<params>. + +=item B<unpack_format> => \@format + +C<< pack() >>-compatible format to unpack procedure output. + +=back + +=item B<%sp_params>: + +C<< Name => $value >> pairs. + +=item B<%optional_options>: + +Options to pass to L<MR::Taranatool::Box->Call|MR::Taranatool::Box/Call> method. +This overrides C<< %options >> values key-by-key. + +=back + +=cut + +sub declare_stored_procedure { + my($class, %opts) = @_; + my $name = delete $opts{name} or confess "No `name` given"; + my $options = $opts{options} || {}; + + confess "no `params` given; it must be an arrayref" if !exists $opts{params} or ref $opts{params} ne 'ARRAY'; + my @params = @{$opts{params}}; + + my $pack; + if(my $fn = $opts{pack}) { + confess "`params_format` and `params_default` are not applicable while `pack` is in use" if exists $opts{params_format} or exists $opts{params_default}; + if(ref $fn) { + confess "`pack` can be code ref or a method name, nothing else" unless ref $fn eq 'CODE'; + $pack = $fn; + } else { + confess "`pack` method $fn is not provided by class ${class}" unless $class->can($fn); + $pack = sub { $class->$fn(@_) }; + } + } else { + confess "no `pack` nor `params_format` given; it must be an arrayref with number of elements exactly as in `params`" if !exists $opts{params_format} or ref $opts{params_format} ne 'ARRAY' or @{$opts{params_format}} != @params; + confess "`params_default` is given but it must be an arrayref with number of elements no more then in `params`" if exists $opts{params_format} and (ref $opts{params_format} ne 'ARRAY' or @{$opts{params_format}} > @params); + my @fmt = @{$opts{params_format}}; + my @def = @{$opts{params_default}||[]}; + $pack = sub { + my $p = $_[0]; + for my $i (0..$#params) { + $p->[$i] = $def[$i] if !defined$p->[$i] and $i < @def; + confess "All params must be defined" unless defined $p->[$i]; + $p->[$i] = pack $fmt[$i], $p->[$i]; + } + return $p; + }; + } + + my $unpack; + if(my $fn = $opts{unpack}) { + if(ref $fn) { + confess "`unpack` can be code ref or a method name, nothing else" unless ref $fn eq 'CODE'; + $unpack = $fn; + } else { + confess "`unpack` method $fn is not provided by class ${class}" unless $class->can($fn); + $unpack = sub { $class->$fn(@_) }; + } + $options->{unpack_format} = [ "a*" ]; + } else { + confess "no `unpack` nor `unpack_format` given; it must be an arrayref" if !exists $opts{unpack_format} or ref $opts{unpack_format} ne 'ARRAY'; + my $f = $opts{unpack_format}; + $options->{unpack_format} = $f; + } + + my $method = $opts{method_name} or confess "`method_name` not given"; + confess "bad `method_name` $method" unless $method =~ m/^[a-zA-Z]\w*$/; + my $fn = "${class}::${method}"; + confess "Method $method id already defined in class $class" if defined &{$fn}; + do { + no strict 'refs'; + *$fn = sub { + my $p0 = @_ && ref $_[-1] eq 'HASH' ? pop : {}; + my $param = { %$options, %$p0 }; + my ($class, %params) = @_; + my $res = $class->Call($name, $pack->([@params{@params}]), $param) or return; + return $res unless $unpack; + return $unpack->($res); + } + }; + return $method; +} + +sub Param { + confess "bad Param call" unless $_[2]; + return $_[2] && @{$_[2]} && ref $_[2]->[-1] eq 'HASH' && pop @{$_[2]} || {}; +} + +=pod + +=head3 Configuration methods + +=over + +=item B<SERVER> + +Must return a string of ip:port of I<master> server. + +=item B<REPLICAS> + +Must return a comma separated string of ip:port pairs of I<replica> servers (see L</is_replica>). +Server is chosen from the list randomly. + +=item B<MR_TARANTOOL_BOX_CLASS> + +Must return name of the class implementing L<MR::Tarantool::Box> interface, or it's descendant. + +=item B<SPACES>, B<RAISE>, B<TIMEOUT>, B<SELECT_TIMEOUT>, B<RETRY>, B<SELECT_RETRY>, B<SOFT_RETRY>, B<DEBUG> + +See corresponding arguments of L<MR::Tarantool::Box->new|MR::Tarantool::Box/new> method. + +=back + +=cut + +sub DEBUG () { 0 } +sub IPDEBUG () { 0 } + +sub TIMEOUT () { 23 } +sub SELECT_TIMEOUT () { 2 } + +sub RAISE () { 1 } + +sub RETRY () { 1 } +sub SELECT_RETRY () { 3 } +sub SOFT_RETRY () { 3 } +sub RETRY_DELAY () { 1 } + +sub SERVER () { die } +sub REPLICAS () { [] } + +sub MR_TARANTOOL_BOX_CLASS () { 'MR::Tarantool::Box' } + +sub SPACES () { die } +sub DEFAULT_SPACE () { undef } + +sub _new_instance { + my ($class) = @_; + my ($config) = $class->can('_config') ? $class->_config : {}; + $config->{param} ||= {}; + + $config->{servers} ||= $class->SERVER; + + $config->{param}->{name} ||= $class; + + $config->{param}->{spaces} ||= my $sp = $class->SPACES; + $config->{param}->{default_space} ||= my $defsp = @$sp == 1 ? 0 : $class->DEFAULT_SPACE; + + $sp->[$defsp]->{fields} ||= [ $class->FIELDS ] if $class->can('FIELDS'); + + $config->{param}->{raise} = $class->RAISE unless defined $config->{param}->{raise}; + + $config->{param}->{timeout} ||= $class->TIMEOUT; + $config->{param}->{select_timeout} ||= $class->SELECT_TIMEOUT; + + $config->{param}->{debug} ||= $class->DEBUG; + $config->{param}->{ipdebug} ||= $class->IPDEBUG; + + $config->{param}->{retry} ||= $class->RETRY; + $config->{param}->{select_retry} ||= $class->SELECT_RETRY; + $config->{param}->{softretry} ||= $class->SOFT_RETRY; + $config->{param}->{retry_delay} ||= $class->RETRY_DELAY; + + $config->{param}->{fields} ||= [ $class->FIELDS ]; + + my $replicas = delete $config->{replicas} || $class->REPLICAS || []; + $replicas = [ split /,/, $replicas ] unless ref $replicas eq 'ARRAY'; + + return bless { + box => $class->MR_TARANTOOL_BOX_CLASS->new({ servers => $config->{servers}, %{$config->{param}} }), + replicas => [ map { $class->MR_TARANTOOL_BOX_CLASS->new({ servers => $_, %{$config->{param}} }) } shuffle @$replicas ], + }, $class; +} + +=pod + +=head3 Add, Insert, Replace, UpdateMulti, Delete + +These methods operate on C<< SERVER >> only. +See corresponding methods of L<MR::Tarantool::Box> class. + +=head3 Select, Call + +These methods operate on C<< SERVER >> at first, and then B<may> +try to query C<< REPLICAS >>. + +See corresponding methods of L<MR::Tarantool::Box> class. + +These methods have additional C<< %options >> params: + +=over + +=item B<is_replica> => \$is_result_from_replica + +If this option is set, then if the query to C<< SERVER >> fails, +C<< REPLICAS >> will be queried one-by-one until query succeeds or +the list ends, and C<< $is_result_from_replica >> will be set to +C<< true >>, no matter whether any query succeeds or not. + +=back + +=cut + +BEGIN { + + foreach my $method (qw/Insert UpdateMulti Delete Add Set Replace Bit Num AndXorAdd Update/) { + no strict 'refs'; + *$method = sub { + use strict; + my ($class, @args) = @_; + my $param = $class->Param($method, \@args); + my $self = $class->instance; + $self->{_last_box} = $self->{box}; + $self->{box}->$method(@args, $param); + }; + } + + foreach my $method (qw/Select SelectUnion Call/) { + no strict 'refs'; + *$method = sub { + use strict; + my ($class, @args) = @_; + my $param = $class->Param($method, \@args); + + if ($param->{format}) { + my @F; + my $F = $class->FIELDS_HASH; + my @format = ref $param->{format} eq 'ARRAY' ? @{$param->{format}} : %{$param->{format}}; + confess "Odd number of elements in format" if @format % 2; + $param->{format} = []; + while( my ($field, $fmt) = splice(@format, 0, 2) ) { + confess "Bad format for field `$field'" unless $fmt; + confess "Unknown field `$field'" unless exists $F->{$field}; + push @F, $field; + push @{$param->{format}}, { + field => $F->{$field}, + $fmt eq 'full' ? ( + full => 1, + ) : ( + offset => $fmt->{offset} || 0, + length => (exists $fmt->{length} ? $fmt->{length}||0 : 'max'), + ), + }; + } + $param->{hashify} = sub { $class->_hashify(\@F, @_) }; + } + + die "${class}\->${method}: is_replica must be a SCALARREF" if exists $param->{is_replica} && ref $param->{is_replica} ne 'SCALAR'; + my $is_rep = delete $param->{is_replica}; + $$is_rep = 0 if $is_rep; + my $self = $class->instance; + my @rep = $is_rep ? @{ $self->{replicas} } : (); + my ($ret,@ret); + for(my $box = $self->{box}; $box; $box = shift @rep) { + $self->{_last_box} = $box; + if(wantarray) { + @ret = $box->$method(@args, $param); + } elsif(defined wantarray) { + $ret = $box->$method(@args, $param); + } else { + $box->$method(@args, $param); + } + last if !$box->Error or !$is_rep or !@rep; + ++$$is_rep; + } + return wantarray ? @ret : $ret; + }; + } +} + +=pod + +=head3 B<Error>, B<ErrorStr> + +Return error code or description (see <MR::Tarantool::Box|MR::Tarantool::Box/Error>). + +=cut + +sub Error { + my ($class, @args) = @_; + $class->instance->{_last_box}->Error(@args); +} + +sub ErrorStr { + my ($class, @args) = @_; + $class->instance->{_last_box}->ErrorStr(@args); +} + +=pod + +=head1 LICENCE AND COPYRIGHT + +This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. + +=head1 SEE ALSO + +L<http://tarantool.org> + +L<MR::Tarantool::Box> + +=cut + + +1; diff --git a/connector/perl/t/00require.t b/connector/perl/t/00require.t new file mode 100644 index 0000000000000000000000000000000000000000..0b8fe3288e8a524e025b6dae1a4f1c47be343e3a --- /dev/null +++ b/connector/perl/t/00require.t @@ -0,0 +1,23 @@ +#!/usr/bin/perl +use warnings; +use strict; + +use Test::More tests => 16; + +require_ok 'MR::IProto'; +require_ok 'MR::IProto::Cluster'; +require_ok 'MR::IProto::Cluster::Server'; +require_ok 'MR::IProto::Connection'; +require_ok 'MR::IProto::Connection::Async'; +require_ok 'MR::IProto::Connection::Sync'; +require_ok 'MR::IProto::Error'; +require_ok 'MR::IProto::Message'; +require_ok 'MR::IProto::NoResponse'; +require_ok 'MR::IProto::Request'; +require_ok 'MR::IProto::Response'; +require_ok 'MR::IProto::Role::Debuggable'; +require_ok 'MR::IProto::Server'; +require_ok 'MR::IProto::Server::Connection'; +require_ok 'MR::Tarantool::Box'; +require_ok 'MR::Tarantool::Box::Singleton'; + diff --git a/connector/perl/t/TBox.pm b/connector/perl/t/TBox.pm deleted file mode 100644 index 6bd7cdfcd4953afc4fbec2b52e743216761a72f5..0000000000000000000000000000000000000000 --- a/connector/perl/t/TBox.pm +++ /dev/null @@ -1,15 +0,0 @@ -use FindBin qw($Bin); -use lib "$Bin/../client/perl/lib"; - -use MR::SilverBox; - -$main::box = MR::SilverBox->new({ servers => $ARGV[1] || q/localhost:15013/, - namespaces => [ { - indexes => [ { - index_name => 'primary_id', - keys => [0], - } ], - namespace => 1, - format => 'l& SSLL', - default_index => 'primary_id', - } ], }); diff --git a/connector/perl/t/box.pl b/connector/perl/t/box.pl index 810a919f14b0a17cf48e17fdbdc0fa088adfa960..b9e4f854def6c1200f94e21f27cd2b734d874193 100644 --- a/connector/perl/t/box.pl +++ b/connector/perl/t/box.pl @@ -1,3 +1,7 @@ +#!/usr/bin/perl + +# Tarantool/Box config below + use strict; use warnings; BEGIN { @@ -7,21 +11,22 @@ BEGIN { } use FindBin qw($Bin); use lib "$Bin"; -use TBox (); use Carp qw/confess/; -use Test::More tests => 218; +use Test::More tests => 233; use Test::Exception; +use List::MoreUtils qw/zip/; + local $SIG{__DIE__} = \&confess; our $CLASS; -BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::SilverBox'; eval "require $CLASS" or die $@; } +BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::Tarantool::Box'; eval "require $CLASS" or die $@; } -use constant ILL_PARAM => qr/$CLASS: Error 00000202/; -use constant TUPLE_NOT_EXISTS => qr/$CLASS: Error 00003102/; -use constant TUPLE_EXISTS => qr/$CLASS: Error 00003702/; -use constant INDEX_VIOLATION => qr/$CLASS: Error 00003802/; +use constant ILL_PARAM => qr/Error 00000202/; +use constant TUPLE_NOT_EXISTS => qr/Error 00003102/; +use constant TUPLE_EXISTS => qr/Error 00003702/; +use constant INDEX_VIOLATION => qr/Error 00003802/; use constant TOO_BIG_FIELD => qr/too big field/; @@ -30,6 +35,7 @@ my $server = (shift || $ENV{BOX}) or die; sub cleanup ($) { my ($id) = @_; + die unless defined $id; ok defined $box->Delete($id), 'delete of possible existing record'; ok $box->Delete($id) == 0, 'delete of non existing record'; } @@ -49,17 +55,20 @@ sub def_param { namespace => 0, format => $format, default_index => 'primary_id', - } ]} + name => 'main', + } ], + default_space => "main", + } } $box = $CLASS->new(def_param('l&SSLL&')); ok $box->isa($CLASS), 'connect'; cleanup 13; -ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777'), 'insert'; +ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777',{space => 'main'}), 'insert'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '777'], 'select/insert'; -ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666'), 'replace'; +ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666',{namespace => 'main'}), 'replace'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 2, 3, 4, '666'], 'select/replace'; ok $box->Update(13, 3 => 1) == 1, 'update of some field'; @@ -88,7 +97,7 @@ cleanup 13; ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'insert'; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/insert'; -throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, qr/Illegal parametrs/, "splice/bad_params_1"; +throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, ILL_PARAM, "splice/bad_params_1"; ok $box->UpdateMulti(13, [6 => splice => [100]]), "splice/big_offset"; is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select'; @@ -409,8 +418,8 @@ sub def_param1 { } ]} } -$box = MR::SilverBox->new(def_param1); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param1); +ok $box->isa($CLASS), 'connect'; my @tuple1 = (13, 'mail.ru', 123); cleanup $tuple1[0]; @@ -435,7 +444,7 @@ is_deeply [$box->Select([[$tuple2[1], $tuple2[2]]], { use_index => 'secondary_co sub def_param_bad { my $format = 'l&&'; return { servers => $server, - namespaces => [ { + spaces => [ { indexes => [ { index_name => 'primary_num1', keys => [0], @@ -452,12 +461,12 @@ sub def_param_bad { } ]} } -$box = MR::SilverBox->new(def_param_bad); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_bad); +ok $box->isa($CLASS), 'connect'; my @tuple_bad = (13, 'mail.ru', '123'); cleanup $tuple_bad[0]; -throws_ok sub { $box->Insert(@tuple_bad) }, qr/Illegal parametrs/, "index_constains/bad_field_type"; +throws_ok sub { $box->Insert(@tuple_bad) }, ILL_PARAM, "index_constains/bad_field_type"; ## Check unique tree index @@ -466,9 +475,9 @@ sub def_param_unique { return { servers => $server, namespaces => [ { indexes => [ { - index_name => 'id', - keys => [0], - }, { + index_name => 'id', + keys => [0], + }, { index_name => 'email', keys => [1], }, { @@ -478,17 +487,17 @@ sub def_param_unique { index_name => 'lastname', keys => [3], } , { - index_name => 'fullname', - keys => [2, 3] - } ], - namespace => 27, + index_name => 'fullname', + keys => [2, 3] + } ], + space => 27, format => $format, default_index => 'id', } ]} } -$box = MR::SilverBox->new(def_param_unique); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_unique); +ok $box->isa($CLASS), 'connect'; my $tuples = [ [1, 'rtokarev@corp.mail.ru', 'Roman', 'Tokarev'], [2, 'vostrikov@corp.mail.ru', 'Yuri', 'Vostrikov'], @@ -502,7 +511,7 @@ foreach my $tuple (@$tuples) { foreach my $tuple (@$tuples) { if ($tuple == $tuples->[-1] || $tuple == $tuples->[-2]) { - throws_ok sub { $box->Insert(@$tuple) }, qr/Index violation/, "unique_tree_index/insert \'$tuple->[0]\'"; + throws_ok sub { $box->Insert(@$tuple) }, INDEX_VIOLATION, "unique_tree_index/insert \'$tuple->[0]\'"; } else { ok $box->Insert(@$tuple), "unique_tree_index/insert \'$tuple->[0]\'"; } @@ -513,6 +522,54 @@ foreach my $r (@res) { ok sub { return $r != $tuples->[-1] && $r != $tuples->[-2] }; } +my $flds; +BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] } + { + package TestBox; + use MR::Tarantool::Box::Singleton; + use base 'MR::Tarantool::Box::Singleton'; + + BEGIN { + __PACKAGE__->mkfields(@$flds); + } + + sub SERVER { $server } + sub REPLICAS { '' } + + sub SPACES {[{ + space => 27, + indexes => [ { + index_name => 'primary_id', + keys => [TUPLE_f1], + } ], + format => 'l&&&', + default_index => 'primary_id', + }]} + + } + +$box = 'TestBox'; +#$box = $CLASS->new(def_param_flds); +#ok $box->isa($CLASS), 'connect'; + +do { + my $tuples = [ @$tuples[0..2] ]; + foreach my $tuple (@$tuples) { + cleanup $tuple->[0]; + } + + foreach my $tuple (@$tuples) { + is_deeply [$box->Insert(@$tuple, {want_inserted_tuple => 1})], [{zip @$flds, @$tuple}], "flds/insert \'$tuple->[0]\'"; + } + + is_deeply [$box->Select([[$tuples->[0]->[0]]])], [{zip @$flds, @{$tuples->[0]}}], 'select by primary_num1 index'; + is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update1'; + ok $box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ]), 'update2'; + is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ 3 => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3'; + ok $box->UpdateMulti($tuples->[0]->[0],[ 3 => set => $tuples->[0]->[3] ]), 'update4'; + + is_deeply [$box->Delete($tuples->[0]->[0],{want_deleted_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3'; +}; @@ -521,19 +578,21 @@ foreach my $r (@res) { sub def_param_u64 { my $format = '&&&&'; return { servers => $server, - namespaces => [ { + spaces => [ { indexes => [ { - index_name => 'id', - keys => [0], - } ], - namespace => 20, + index_name => 'id', + keys => [0], + } ], + space => 20, format => $format, default_index => 'id', - } ]} + } ], + debug => 0, + } } -$box = MR::SilverBox->new(def_param_u64); -ok $box->isa('MR::SilverBox'), 'connect'; +$box = $CLASS->new(def_param_u64); +ok $box->isa($CLASS), 'connect'; $_->[0] = pack('ll', $_->[0], 0) foreach @$tuples; @@ -546,3 +605,68 @@ foreach my $tuple (@$tuples) { } is_deeply($tuples, [$box->Select([map $_->[0], @$tuples])]); + + +__END__ + +space[0].enabled = 1 +space[0].index[0].type = "HASH" +space[0].index[0].unique = 1 +space[0].index[0].key_field[0].fieldno = 0 +space[0].index[0].key_field[0].type = "NUM" +space[0].index[1].type = "HASH" +space[0].index[1].unique = 1 +space[0].index[1].key_field[0].fieldno = 1 +space[0].index[1].key_field[0].type = "STR" + +space[20].enabled = 1 +space[20].index[0].type = "HASH" +space[20].index[0].unique = 1 +space[20].index[0].key_field[0].fieldno = 0 +space[20].index[0].key_field[0].type = "NUM64" + + +space[26].enabled = 1 +space[26].index[0].type = "HASH" +space[26].index[0].unique = 1 +space[26].index[0].key_field[0].fieldno = 0 +space[26].index[0].key_field[0].type = "NUM" +space[26].index[1].type = "TREE" +space[26].index[1].unique = 0 +space[26].index[1].key_field[0].fieldno = 1 +space[26].index[1].key_field[0].type = "STR" +space[26].index[2].type = "TREE" +space[26].index[2].unique = 0 +space[26].index[2].key_field[0].fieldno = 1 +space[26].index[2].key_field[0].type = "STR" +space[26].index[2].key_field[1].fieldno = 2 +space[26].index[2].key_field[1].type = "NUM" + + + +space[27].enabled = 1 +space[27].index[0].type = "HASH" +space[27].index[0].unique = 1 +space[27].index[0].key_field[0].fieldno = 0 +space[27].index[0].key_field[0].type = "NUM" +space[27].index[1].type = "HASH" +space[27].index[1].unique = 1 +space[27].index[1].key_field[0].fieldno = 1 +space[27].index[1].key_field[0].type = "STR" + +space[27].index[2].type = "TREE" +space[27].index[2].unique = 1 +space[27].index[2].key_field[0].fieldno = 2 +space[27].index[2].key_field[0].type = "STR" + +space[27].index[2].type = "TREE" +space[27].index[2].unique = 1 +space[27].index[2].key_field[0].fieldno = 3 +space[27].index[2].key_field[0].type = "STR" + +space[27].index[3].type = "TREE" +space[27].index[3].unique = 1 +space[27].index[3].key_field[0].fieldno = 2 +space[27].index[3].key_field[0].type = "STR" +space[27].index[3].key_field[1].fieldno = 3 +space[27].index[3].key_field[1].type = "STR" diff --git a/connector/perl/t/box_tree.pl b/connector/perl/t/box_tree.pl deleted file mode 100644 index dfd567403ca310f97e06b2ac2c4d4e3686128fb9..0000000000000000000000000000000000000000 --- a/connector/perl/t/box_tree.pl +++ /dev/null @@ -1,52 +0,0 @@ -use strict; -use warnings; -use Data::Dumper; -BEGIN { - sub mPOP::Config::GetValue ($) { - die; - } -} -use My::SilverBox (); - -use Test::More qw/no_plan/; -use Test::Exception; - -use constant ILL_PARAM => qr/Error 00000202: Illegal parametrs/; - -my $box; -my $server = q/alei7:13013/; - -sub cleanup ($) { - my ($id) = @_; - ok defined $box->Delete($id), 'delete of possible existing record'; - ok $box->Delete($id) == 0, 'delete of non existing record'; -} - - -$box = My::SilverBox->new({servers => $server, tuple_format_0 => q/l& SSLL/, tuple_format_1 => q/l& SSLL/}); -ok $box->isa('My::SilverBox'), 'connect'; - -for (my $from = 100; $from < 200; ++$from) { - ok $box->Insert($from, "$from\@test.mail.ru", 1, 2, 3, 4), 'insert'; -} - -my $from = 100; -while (1) { - my @ans; - my $num = int(rand 20) + 1; - - if ($from != 200) { - ok @ans = $box->Select($from, {next_rows => $num}), 'select'; - } else { - ok not (defined $box->Select($from, {next_rows => $num})), 'select'; - } - - last if scalar @ans == 0; - - my @tuples = (); - for (my $i = 0; $i < scalar @ans; ++$i) { - push @tuples, [$from, "$from\@test.mail.ru", 1, 2, 3, 4]; - $from++; - } - is_deeply \@ans, \@tuples, 'select deeply' -} diff --git a/connector/php/exampleCall.php b/connector/php/exampleCall.php deleted file mode 100644 index d02a7dda7264c23c580d4fd3a70318bc7393ba37..0000000000000000000000000000000000000000 --- a/connector/php/exampleCall.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -define("NMSPACE", 0); - -if(!extension_loaded('tarantool')) { - dl('tarantool.so'); -} - -/** - * Tarantool::constructor - * - * @param optional string host default 127.0.0.1 - * @param optional number port default 33013 - */ -$tnt = new Tarantool('localhost'); - -/** - * Tarantool::call - * - * @param procname procedure namei - * @param array procedure argumetns - * - * @return count of tuples - */ -$tuple = array('test', 'x', 'abd', 1023); - -$res = $tnt->insert(NMSPACE, $tuple); - -$tuple = array(NMSPACE, 0, 'test'); - -$res = $tnt->call("box.select", $tuple); - -var_dump($res); - -while(($res = $tnt->getTuple()) != false) { - var_dump($res); -} diff --git a/connector/php/exampleInsert.php b/connector/php/exampleInsert.php deleted file mode 100644 index 2766fd65ed8261278a288819351ff2ab340b289a..0000000000000000000000000000000000000000 --- a/connector/php/exampleInsert.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -if(!extension_loaded('tarantool')) { - dl('tarantool.so'); -} - -define("NMSPACE", 0); - -$tnt = new Tarantool('tfn24'); -$res=0; - -/** - * Tarantool::insert - * - * @param namespace - * @param array of tuple - * - * @return result - */ - - -$tuple = array(1, 'x','abd', 1023 ); -$res += $tnt->insert(NMSPACE, $tuple); - -$tuple = array(2, 'z','abd', 1023 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(3, 'z','abc', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(4, 'y','abd', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(5, 'y','abc', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(6, 'x','abd', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(7, 'x','abc', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(8, 'x','abd', 1025 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(9, 'x','abc', 1024 ); -$res += $tnt->insert(NMSPACE,$tuple); - -$tuple = array(9, 'x','abc', 1024 ); -$res += $tnt->insert(NMSPACE,$tuple); -echo "inserted $res tuples\n"; - -$tuple = array('9', 66,65, 1024 ); -$res = $tnt->insert(NMSPACE,$tuple); -if (!$res) - echo $tnt->getError(); -else - echo "inserted $res tuples\n"; - -$tuple = array(10, 66,65, 'xdf' ); -$res = $tnt->insert(NMSPACE,$tuple); - -if (!$res) - echo $tnt->getError(); -else - echo "inserted $res tuples\n"; - \ No newline at end of file diff --git a/connector/php/exampleSelect.php b/connector/php/exampleSelect.php deleted file mode 100644 index f3b3c1c58f748442826052f54b4e517b2519bfc2..0000000000000000000000000000000000000000 --- a/connector/php/exampleSelect.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php - -define("NMSPACE", 0); -define("PRIMARYINDEX", 0); -define("INDEX_1", 1); -define("INDEX_2", 2); -define("INDEX_3", 3); - - -if(!extension_loaded('tarantool')) { - dl('tarantool.so'); -} - -$res = true; - -/** - * Tarantool::constructor - * - * @param optional string host default 127.0.0.1 - * @param optional number port default 33013 - */ -$tnt = new Tarantool('tfn24'); - -echo 'single tuple select by primary key (key=1)', PHP_EOL; - -/** - * Tarantool::select - * - * @param number namespace - * @param number index No - * @param mixed key - * @param optional limit default all - * @param optional offset default 0 - * - * @return count of tuples - */ -$res = $tnt->select(NMSPACE,PRIMARYINDEX,1); // namespace,index, key -var_dump($res); -$res = $tnt->getTuple(); -var_dump($res); - - -echo "some tuple select by fielNo[1] = 'x'\n"; -$res = $tnt->select(NMSPACE,INDEX_1,'x'); // namespace,index, key -var_dump($res); - -while( ($res = $tnt->getTuple()) != false){ - var_dump($res); -} - -echo "some tuple select by index = 2 (fielNo[1] = 'x' and fielNo[2] = 'abc')\n"; -$res = $tnt->select(NMSPACE,INDEX_2,array('x', 'abc')); // namespace,index, key -var_dump($res); - -while( ($res = $tnt->getTuple()) != false){ - var_dump($res); -} - -echo "some tuple select by index = 3 (fielNo[3] = 1025)\n"; -$res = $tnt->select(NMSPACE,INDEX_3,1025); // namespace,index, key -var_dump($res); - -while( ($res = $tnt->getTuple()) != false){ - var_dump($res); -} - -echo "some tuple select by index = 3 (fielNo[3] = 1025) LIMIT=3\n"; -$res = $tnt->select(NMSPACE,INDEX_3,1025,3); // namespace,index, key, limit -var_dump($res); - -while( ($res = $tnt->getTuple()) != false){ - var_dump($res); -} - -echo "some tuple select by index = 3 (fielNo[3] = 1025) NEXT 3 (offset=3, limit=3)\n"; -$res = $tnt->select(NMSPACE,INDEX_3,1025,3,3); // namespace,index, key, limit, offset -var_dump($res); - -while( ($res = $tnt->getTuple()) != false){ - var_dump($res); -} - -// multi select -/// SELECT * FROM t0 WHERE k0 IN (1,2) -$count = $tnt->mselect(NMSPACE,PRIMARYINDEX, array(1,2)); // ns, idx , keys, [limit, offset] -print "count=$count\n"; -while ( false != ($res = $tnt->getTuple())) { - var_dump($res); -} - -/// SELECT * FROM t0 WHERE k1 IN ('x','z') -$count = $tnt->mselect(NMSPACE,INDEX_1, array('x','z')); // ns, idx , keys, [limit, offset] -print "count=$count\n"; -while ( false != ($res = $tnt->getTuple())) { - var_dump($res); -} - - - - diff --git a/connector/php/exampleUpdate.php b/connector/php/exampleUpdate.php deleted file mode 100644 index 0d07f62091f8e44f63f318473142ff4751038163..0000000000000000000000000000000000000000 --- a/connector/php/exampleUpdate.php +++ /dev/null @@ -1,98 +0,0 @@ -<?php - -define("NMSPACE", 0); -define("PRIMARYINDEX", 0); - -define("FIELD_1", 1); -define("FIELD_2", 2); -define("FIELD_3", 3); - -if(!extension_loaded('tarantool')) { - dl('tarantool.so'); -} - -$res = true; - -// constructor(host,port); -$tnt = new Tarantool('tfn24'); - -$tnt->select(NMSPACE,PRIMARYINDEX,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - -/** - * Tarantool::inc - * - * @param namespace - * @param primary key - * @param field No for increment - * @param optional incremental data default +1 - * - * @return bool result - */ -// increment -$tnt->inc(NMSPACE,1,FIELD_3); - -$tnt->select(NMSPACE,PRIMARYINDEX,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - - -// duble increment (+2) -$tnt->inc(NMSPACE,1,FIELD_3,2); - -$tnt->select(NMSPACE,PRIMARYINDEX,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - - -// decrement -$res = $tnt->inc(NMSPACE,1,FIELD_3,-1); -echo "increment tuple res=$res\n"; - -$tnt->select(NMSPACE,PRIMARYINDEX,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - -// error, type inxex must be NUM -$res = $tnt->inc(NMSPACE,1,FIELD_2); -echo "increment tuple res=$res\n"; -if (!$res) - echo $tnt->getError(); -$tnt->select(NMSPACE,PRIMARYINDEX,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - -/** - * Tarantool::update - * - * @param namespace - * @param primary key - * @param array of new data: - * array( fieldNo => newValue, ... ) - * - * @return bool result - */ -$res = $tnt->update(NMSPACE,1,array(2=>'y')); -echo "update tuple res=$res\n"; - -$tnt->select(NMSPACE,0,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - - -$res = $tnt->update(NMSPACE,1,array(1 => 'z', 2=>'abc')); -echo "update tuple res=$res\n"; - -$tnt->select(NMSPACE,0,1); -$tuple = $tnt->getTuple(); -var_dump($tuple); - - -// Error type inxex 0 must be NUM -$res = $tnt->update(NMSPACE,1,array(0 => 'z', 2=>'abc')); -echo "update tuple res=$res\n"; -if (!$res) - echo $tnt->getError(); - - diff --git a/connector/php/tarantool.c b/connector/php/tarantool.c index b9b917c95975ec752a0a3d0420288d2610478fca..b783ed37344582eb42daf86e0e2558cfdea99038 100644 --- a/connector/php/tarantool.c +++ b/connector/php/tarantool.c @@ -16,197 +16,156 @@ | Copyright (c) 2011 | +----------------------------------------------------------------------+ */ - -/* $Id: header 252479 2008-02-07 19:39:50Z iliaa $ */ - #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include <stdint.h> +#include <stdbool.h> #include <netinet/in.h> #include <netdb.h> +#include <inttypes.h> -#include "php.h" -#include "php_ini.h" -#include "ext/standard/info.h" -#include "zend_exceptions.h" +#include <php.h> +#include <php_ini.h> +#include <ext/standard/info.h> +#include <zend_exceptions.h> #include "tarantool.h" -/* If you declare any globals in php_tarantool.h uncomment this: -ZEND_DECLARE_MODULE_GLOBALS(tarantool) -*/ - -/* True global resources - no need for thread safety here */ -static int le_tarantool; - -zend_class_entry *tarantool_class_entry; - -typedef struct { - uint32_t type; - uint32_t len; - uint32_t request_id; -} Header; - -typedef struct _tarantool_object { - zend_object zo; - char * host; // tarantool host - int port; // tarantool port - int admin_port; // tarantool admin port - int bodyLen; // body len from tuples header - int countTuples; // count tuples - int readedTuples; // - int readed; // readed byte - uint32_t errorcode; // error code - php_stream * stream; - php_stream * admin_stream; -} tarantool_object; - -#define HEADER_SIZE sizeof(Header) - -typedef union { - u_char b; - uint32_t i; -} b2i; - -typedef struct { - uint32_t count; - u_char data[]; -} Tuple; - -typedef struct { - uint32_t fieldNo; - int8_t code; - u_char arg[]; -} Operation; - - -// sizeof(spaceNo) + sizeof(flag) + sizeof(tuple.count) -#define INSERT_REQUEST_SIZE 12 -typedef struct { - uint32_t spaceNo; - uint32_t flag; - Tuple tuple; -} InsertRequest; - -typedef struct { - uint32_t spaceNo; - Tuple tuple; -} DeleteRequest; - - -// sizeof(count) + sizeof(operation.fieldNo) + sizeof(operation.code) +1 // 10 -#define UPDATE_REQUEST_SIZE 10 -typedef struct { - uint32_t count; - Operation operation; -} UpdateRequest; - - -#define SELECT_REQUEST_SIZE 20 -typedef struct { - uint32_t spaceNo; - uint32_t indexNo; - uint32_t offset; - uint32_t limit; - uint32_t count; - char tuples[]; -} SelectRequest; - -typedef struct { - uint32_t len; - Tuple tuple; -} FTuple; - - -typedef struct { - uint32_t code; // request error code - uint32_t tuples_count; // count of tuples -} SelectResponseBody; - - -#define SELECT_RESPONSE_SIZE sizeof(SelectResponseTuple) -typedef struct { - uint32_t size; // tuple size in bytes - uint32_t count; // count elements in tuple -} SelectResponseTuple; - -typedef struct { - uint32_t code; - uint32_t count; - char data[]; -} Response; - -//typedef struct { -// uint32_t code; -// uint32_t count; -// char data[]; -//} UpdateResponse; - - -static void printLine( u_char *p ); -static void printLine3( u_char *p ); - -static void -leb128_write(char * buf, unsigned long value); - -static int -leb128_size(unsigned long value); - -static int -leb128_read(char * buf, int size, unsigned long * value); - -/* {{{ tarantool_functions[] - * - * Every user visible function must have an entry in tarantool_functions[]. - */ -const zend_function_entry tarantool_functions[] = { - PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC) - {NULL, NULL, NULL} +/*============================================================================* + * Tarantool extension structures defintion + *============================================================================*/ + +/* I/O buffer */ +struct io_buf { + /* buffer's size */ + size_t size; + /* buffer's capacity */ + size_t capacity; + /* read position in the I/O buffer */ + size_t readed; + /* buffer value */ + uint8_t *value; }; -zend_function_entry tarantool_class_functions[] = { - PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC) - - PHP_ME(tarantool_class, insert, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, select, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, mselect, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, call, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, getTuple, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, delete, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, update, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, inc, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, getError, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, getInfo, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, getStat, NULL, ZEND_ACC_PUBLIC) - PHP_ME(tarantool_class, getConf, NULL, ZEND_ACC_PUBLIC) +/* tarantool object */ +typedef struct tarantool_object { + zend_object zo; + /* host name */ + char *host; + /* tarantool primary port */ + int port; + /* tarantool admin port */ + int admin_port; + /* tarantool primary connection */ + php_stream *stream; + /* tarantool admin connecion */ + php_stream *admin_stream; + /* I/O buffer */ + struct io_buf *io_buf; + /* additional buffer for splice args */ + struct io_buf *splice_field; +} tarantool_object; + +/* iproto header */ +struct iproto_header { + /* command code */ + uint32_t type; + /* command length */ + uint32_t length; + /* request id */ + uint32_t request_id; +} __attribute__((packed)); + +/* tarantool select command request */ +struct tnt_select_request { + /* space number */ + int32_t space_no; + /* index number */ + int32_t index_no; + /* select offset from begining */ + int32_t offset; + /* maximail number tuples in responce */ + int32_t limit; +} __attribute__((packed)); + +/* tarantool insert command request */ +struct tnt_insert_request { + /* space number */ + int32_t space_no; + /* flags */ + int32_t flags; +} __attribute__((packed)); + +/* tarantool update fields command request */ +struct tnt_update_fields_request { + /* space number */ + int32_t space_no; + /* flags */ + int32_t flags; +} __attribute__((packed)); + +/* tarantool delete command request */ +struct tnt_delete_request { + /* space number */ + int32_t space_no; + /* flags */ + int32_t flags; +} __attribute__((packed)); + +/* tarantool call command request */ +struct tnt_call_request { + /* flags */ + int32_t flags; +} __attribute__((packed)); + +/* tarantool command response */ +struct tnt_response { + /* return code */ + int32_t return_code; + union { + /* count */ + int32_t count; + /* error message */ + char return_msg[0]; + }; +} __attribute__((packed)); + + +/*============================================================================* + * Global variables definition + *============================================================================*/ + + +/*----------------------------------------------------------------------------* + * Tarantool module variables + *----------------------------------------------------------------------------*/ + +/* module functions list */ +zend_function_entry tarantool_module_functions[] = { + PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; - -/* }}} */ - -/* {{{ tarantool_module_entry - */ +/* tarantool module struct */ zend_module_entry tarantool_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "tarantool", - tarantool_functions, + tarantool_module_functions, PHP_MINIT(tarantool), PHP_MSHUTDOWN(tarantool), NULL, NULL, PHP_MINFO(tarantool), #if ZEND_MODULE_API_NO >= 20010901 - "0.1", /* Replace with version number for your extension */ + "1.0", #endif STANDARD_MODULE_PROPERTIES }; -/* }}} */ - #ifdef COMPILE_DL_TARANTOOL @@ -214,1875 +173,1889 @@ ZEND_GET_MODULE(tarantool) #endif -static int php_tnt_connect( tarantool_object *ctx TSRMLS_DC) { - // open stream - struct timeval tv; - tv.tv_sec = TARANTOOL_TIMEOUT; - tv.tv_usec = 0; +/*----------------------------------------------------------------------------* + * Tarantool class variables + *----------------------------------------------------------------------------*/ - char * errstr = NULL, *hostname = NULL; - int err = 0, hostname_len; +/* tarantool class methods */ +const zend_function_entry tarantool_class_methods[] = { + PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, select, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, insert, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, update_fields, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, delete, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, call, NULL, ZEND_ACC_PUBLIC) + PHP_ME(tarantool_class, admin, NULL, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; - if (ctx->port) { - hostname_len = spprintf(&hostname, 0, "tcp://%s:%d", ctx->host, ctx->port); - } else { - php_printf("port undefined\n"); - } +/* tarantool class */ +zend_class_entry *tarantool_class_ptr; - ctx->stream = php_stream_xport_create( hostname, hostname_len, - ENFORCE_SAFE_MODE | REPORT_ERRORS, - STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, - NULL, &tv, NULL, &errstr, &err); +/*============================================================================* + * local functions declaration + *============================================================================*/ - efree(hostname); - if (err && errstr) { - php_printf("stream error: %s\n", errstr); - ctx->stream = NULL; - efree(errstr); - } +/*----------------------------------------------------------------------------* + * I/O buffer interface + *----------------------------------------------------------------------------*/ - if (!ctx->stream) { - return 1; - } +/* create I/O buffer instance */ +static struct io_buf * +io_buf_create(); - return 0; -} +/* destroy I/O buffer */ +static void +io_buf_destroy(struct io_buf *buf); -static int php_tnt_admin_connect( tarantool_object *ctx TSRMLS_DC) { - // open stream - struct timeval tv; - tv.tv_sec = TARANTOOL_TIMEOUT; - tv.tv_usec = 0; +/* reserv I/O buffer space */ +inline static bool +io_buf_reserve(struct io_buf *buf, size_t n); - char * errstr = NULL, *hostname = NULL; - int err = 0, hostname_len; +/* resize I/O buffer */ +inline static bool +io_buf_resize(struct io_buf *buf, size_t n); - if (ctx->admin_port) { - hostname_len = spprintf(&hostname, 0, "tcp://%s:%d", ctx->host, ctx->admin_port); - } else { - php_printf("admin port undefined\n"); - } +/* calculate next capacity for I/O buffer */ +inline static size_t +io_buf_next_capacity(size_t n); - ctx->admin_stream = php_stream_xport_create( hostname, hostname_len, - ENFORCE_SAFE_MODE | REPORT_ERRORS, - STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, - NULL, &tv, NULL, &errstr, &err); +/* clean I/O buffer */ +static void +io_buf_clean(struct io_buf *buf); - efree(hostname); +/* read struct from buffer */ +static bool +io_buf_read_struct(struct io_buf *buf, void **ptr, size_t n); - if (err && errstr) { - php_printf("stream error: %s\n", errstr); - ctx->admin_stream = NULL; - efree(errstr); - } +/* read 32-bit integer from buffer */ +static bool +io_buf_read_int32(struct io_buf *buf, int32_t *val); - if (!ctx->admin_stream) { - return 1; - } +/* read 64-bit integer from buffer */ +static bool +io_buf_read_int64(struct io_buf *buf, int64_t *val); - return 0; -} +/* read var integer from buffer */ +static bool +io_buf_read_varint(struct io_buf *buf, int32_t *val); -/* {{{ proto tarantool::__construct( string string host=localhost, int port=PORT, int admin_port=ADMIN_PORT) - tarantool constructor */ -PHP_METHOD(tarantool_class, __construct) -{ - zval *id; - tarantool_object *ctx; +/* read string from buffer */ +static bool +io_buf_read_str(struct io_buf *buf, char **str, size_t len); +/* read fied from buffer */ +static bool +io_buf_read_field(struct io_buf *buf, zval *tuple); - zval** zdata; //?????? +/* read tuple from buffer */ +static bool +io_buf_read_tuple(struct io_buf *buf, zval **tuple); - char * host = NULL; - int host_len = 0; - long port=0, admin_port=0; +/* + * Write to I/O buffer functions + */ - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sll", - &id, tarantool_class_entry, &host, &host_len, &port, &admin_port) == FAILURE) { - return; - } +/* write struct to I/O buffer */ +static void * +io_buf_write_struct(struct io_buf *buf, size_t n); - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - ctx->host = NULL; - ctx->port = 0; - ctx->admin_port = 0; - ctx->stream = NULL; - ctx->bodyLen = 0; - ctx->errorcode = 0; +/* write byte to I/O buffer */ +static bool +io_buf_write_byte(struct io_buf *buf, int8_t value); - if (host_len > 0) - ctx->host = estrdup(host); - else - ctx->host = estrdup(TARANTOOL_DEF_HOST); +/* write 32-bit integer to I/O buffer */ +static bool +io_buf_write_int32(struct io_buf *buf, int32_t value); - if (port) - ctx->port = port; - else - ctx->port = TARANTOOL_DEF_PORT; +/* write 64-bit integer to I/O buffer */ +static bool +io_buf_write_int64(struct io_buf *buf, int64_t value); - if (admin_port) - ctx->admin_port = admin_port; - else - ctx->admin_port = TARANTOOL_ADMIN_PORT; +/* write varint to I/O buffer */ +static bool +io_buf_write_varint(struct io_buf *buf, int32_t value); -} -/* }}} */ +/* write string to I/O buffer */ +static bool +io_buf_write_str(struct io_buf *buf, uint8_t *str, size_t len); +/* write 32-bit integer as tuple's field to I/O buffer */ +static bool +io_buf_write_field_int32(struct io_buf *buf, uint32_t value); +/* write 64-bit integer as tuple's field to I/O buffer */ +static bool +io_buf_write_field_int64(struct io_buf *buf, uint64_t value); -/* {{{ proto int tarantool::insert(int space, int index, zval tuple); -insert tuple -*/ -PHP_METHOD(tarantool_class, insert ) -{ - zval *id; - tarantool_object *ctx; - long space; - zval * tuple; - HashTable *pht; - HashPosition pos; - zval **curr; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ola", &id, - tarantool_class_entry, &space, &tuple) == FAILURE) { - return; - } +/* write string tuple's field to I/O buffer */ +static bool +io_buf_write_field_str(struct io_buf *buf, uint8_t *val, size_t len); - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); +/* write tuple to I/O buffer */ +static bool +io_buf_write_tuple_int(struct io_buf *buf, zval *tuple); - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } - } +/* write tuple (string) to I/O buffer */ +static bool +io_buf_write_tuple_str(struct io_buf *buf, zval *tuple); + +/* write tuple (array) to I/O buffer */ +static bool +io_buf_write_tuple_array(struct io_buf *buf, zval *tuple); - pht = Z_ARRVAL_P(tuple); - int num = zend_hash_num_elements(pht); +/* write tuple to I/O buffer */ +static bool +io_buf_write_tuple(struct io_buf *buf, zval *tuple); - char * out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); +/* write array of tuples to I/O buffer */ +static bool +io_buf_write_tuples_list_array(struct io_buf *buf, zval *tuples_list); - Header * header = (Header *) out_buf; - InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE); +/* write tuples list to I/O buffer */ +static bool +io_buf_write_tuples_list(struct io_buf *buf, zval *tuples_list); - header->type = TARANTOOL_INSERT; - header->request_id = TARANTOOL_REQUEST_ID; +/* + * I/O buffer send/recv + */ - insert->spaceNo = space; - insert->tuple.count = num; // count Tuples +/* send administation command request */ +static bool +io_buf_send_yaml(php_stream *stream, struct io_buf *buf); - u_char * p = (u_char *) insert->tuple.data; +/* receive administration command response */ +static bool +io_buf_recv_yaml(php_stream *stream, struct io_buf *buf); - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **) &curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht, &pos)) { +/* send request by iproto */ +static bool +io_buf_send_iproto(php_stream *stream, int32_t type, int32_t request_id, struct io_buf *buf); - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); +/* receive response by iproto */ +static bool +io_buf_recv_iproto(php_stream *stream, struct io_buf *buf); - u_char str_shortlen = (u_char)str_len; - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - -// u_char leb_size = (u_char)leb128_size( val); -// *(p++) = leb_size; -// leb128_write( (char *)p, val); -// p += leb_size; - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; - p += leb_size; - } - } +/*----------------------------------------------------------------------------* + * support local functions + *----------------------------------------------------------------------------*/ - header->len = INSERT_REQUEST_SIZE + (p-insert->tuple.data); // 12 = 3 * sizeof(int32) :ns, flag & cardinality +/* tarantool class instance allocator */ +static zend_object_value +alloc_tarantool_object(zend_class_entry *entry TSRMLS_DC); - // write header - int len = php_stream_write(ctx->stream, out_buf , sizeof(Header)); // 12 +/* free tarantool class instance */ +static void +free_tarantool_object(tarantool_object *tnt TSRMLS_DC); - // write tuple - p = (u_char*)insert; - len = php_stream_write(ctx->stream, (char*)p , header->len ); +/* establic connection */ +static php_stream * +establish_connection(char *host, int port); - bzero(out_buf, header->len + INSERT_REQUEST_SIZE); +/* find long by key in the hash table */ +static bool +hash_fing_long(HashTable *hash, char *key, long *value); - len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE); +/* find string by key in the hash table */ +static bool +hash_fing_str(HashTable *hash, char *key, char **value, int *value_length); - if ( *(out_buf+HEADER_SIZE) == '\0') { - efree(out_buf); - RETURN_TRUE; - } +/* find scalar by key in the hash table */ +static bool +hash_fing_scalar(HashTable *hash, char *key, zval ***value); + + +/*============================================================================* + * Interface definition + *============================================================================*/ - b2i* bb = (b2i*)(out_buf+HEADER_SIZE); - ctx->errorcode = bb->i; +/*----------------------------------------------------------------------------* + * Tarantool main module interface + *----------------------------------------------------------------------------*/ + +/* initialize module function */ +PHP_MINIT_FUNCTION(tarantool) +{ + /* register constants */ + + /* register tarantool flags */ + REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_RETURN_TUPLE", + TARANTOOL_FLAGS_RETURN_TUPLE, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_ADD", + TARANTOOL_FLAGS_ADD, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_REPLACE", + TARANTOOL_FLAGS_REPLACE, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_NOT_STORE", + TARANTOOL_FLAGS_NOT_STORE, + CONST_CS | CONST_PERSISTENT); + + /* register tarantool update fields operations */ + REGISTER_LONG_CONSTANT("TARANTOOL_OP_ASSIGN", + TARANTOOL_OP_ASSIGN, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_OP_ADD", + TARANTOOL_OP_ADD, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_OP_AND", + TARANTOOL_OP_AND, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_OP_XOR", + TARANTOOL_OP_XOR, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_OP_OR", + TARANTOOL_OP_OR, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("TARANTOOL_OP_SPLICE", + TARANTOOL_OP_SPLICE, + CONST_CS | CONST_PERSISTENT); + + /* register classes */ + + /* register tarantool class */ + zend_class_entry tarantool_class; + INIT_CLASS_ENTRY(tarantool_class, "Tarantool", tarantool_class_methods); + tarantool_class.create_object = alloc_tarantool_object; + tarantool_class_ptr = zend_register_internal_class(&tarantool_class TSRMLS_CC); + + return SUCCESS; +} - efree(out_buf); +/* shutdown module function */ +PHP_MSHUTDOWN_FUNCTION(tarantool) +{ + return SUCCESS; +} - RETURN_FALSE; +/* show information about this module */ +PHP_MINFO_FUNCTION(tarantool) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Tarantool/Box support", "enabled"); + php_info_print_table_row(2, "Extension version", TARANTOOL_EXTENSION_VERSION); + php_info_print_table_end(); } -/* }}} */ +/*----------------------------------------------------------------------------* + * Tarantool class interface + *----------------------------------------------------------------------------*/ -/* {{{ proto int tarantool::select(space, index, tuple [, limit=all, offset=0]); - select tuple use php_streams -*/ -PHP_METHOD(tarantool_class, select ) +PHP_METHOD(tarantool_class, __construct) { - zval *id, * tuple; - tarantool_object *ctx; - long ns = 0; - long idx = 0; - long limit = 0xffffffff; - long offset = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ollz|ll", &id, - tarantool_class_entry, &ns, &idx, &tuple, &limit, &offset) == FAILURE) { + /* + * parse method's parameters + */ + zval *id; + char *host = NULL; + int host_len = 0; + long port = 0; + long admin_port = 0; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Osl|l", + &id, + tarantool_class_ptr, + &host, + &host_len, + &port, + &admin_port) == FAILURE) { + return; + } + + /* + * validate parameters + */ + + /* check host name */ + if (host == NULL || host_len == 0) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid tarantool's hostname"); return; } - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); + /* validate port value */ + if (port <= 0 || port >= 65536) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid primary port value: %li", port); + return; + } - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); + /* check admin port */ + if (admin_port) { + /* validate port value */ + if (admin_port < 0 || admin_port >= 65536) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid admin port value: %li", admin_port); return; } - } - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - ctx->errorcode = 0; - - char * out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); + /* initialzie object structure */ + tarantool_object *object = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); + object->host = estrdup(host); + object->port = port; + object->admin_port = admin_port; + object->stream = NULL; + object->admin_stream = NULL; + object->io_buf = io_buf_create(); + if (!object->io_buf) { + return; + } + object->splice_field = io_buf_create(); + if (!object->splice_field) { + return; + } - Header * header = (Header *) out_buf; - SelectRequest * select = (SelectRequest *) out_buf + HEADER_SIZE; + return; +} - header->type = TARANTOOL_SELECT; - header->request_id = TARANTOOL_REQUEST_ID; +PHP_METHOD(tarantool_class, select) +{ + /* + * parse methods parameters + */ + zval *id; + long space_no = 0; + long index_no = 0; + zval *keys_list = NULL; + long limit = -1; + long offset = 0; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Ollz|ll", + &id, + tarantool_class_ptr, + &space_no, + &index_no, + &keys_list, + &limit, + &offset) == FAILURE) { + return; + } - select->spaceNo = ns; - select->indexNo = idx; - select->limit = limit; - select->offset = offset; - select->count = 1; // âðåìåííî îòëàæèâàåì - îäèí êîðòåæ + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - u_char * p = (u_char *)select->tuples ; + /* check connection */ + if (!tnt->stream) { + /* establis connection */ + tnt->stream = establish_connection(tnt->host, tnt->port); + if (!tnt->stream) + return; + } - int * count_elements = (int *)p; + /* + * send request + */ - switch (Z_TYPE_P(tuple)) { - case IS_STRING: { - *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå - p += sizeof(uint32_t); + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - char * strval = Z_STRVAL_P(tuple); - int str_len = Z_STRLEN_P(tuple); - u_char str_shortlen = (u_char)str_len; + /* fill select command */ + /* fill command header */ + struct tnt_select_request *request = (struct tnt_select_request *) io_buf_write_struct( + tnt->io_buf, sizeof(struct tnt_select_request)); + if (request == NULL) + return; + request->space_no = space_no; + request->index_no = index_no; + request->offset = offset; + request->limit = limit; + /* fill keys */ + if (!io_buf_write_tuples_list(tnt->io_buf, keys_list)) + return; - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; + /* send iproto request */ + if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_SELECT, 0, tnt->io_buf)) + return; -// printf("tuple: len=%d [%s]\n", str_len, strval ); - } - break; + /* + * receive response + */ - case IS_LONG: { - *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå - p += sizeof(uint32_t); + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - unsigned long val = Z_LVAL_P(tuple); -// u_char leb_size = (u_char)leb128_size( val); -// *(p++) = leb_size; -// leb128_write( (char *)p, val); + /* receive */ + if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf)) + return; - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; - p += leb_size; + /* read response */ + struct tnt_response *response; + if (!io_buf_read_struct(tnt->io_buf, + (void **) &response, + sizeof(struct tnt_response))) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "select failed: invalid response was received"); + return; + } -// printf("tuple: int %d\n", val ); - } - break; + /* check return code */ + if (response->return_code) { + /* error happen, throw exceprion */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "select failed: %"PRIi32"(0x%08"PRIx32"): %s", + response->return_code, + response->return_code, + response->return_msg); + return; + } + if (array_init(return_value) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "select failed: create array failed"); + return; + } - case IS_ARRAY: { - HashTable *pht; - HashPosition pos; - zval **curr; - - *count_elements = zend_hash_num_elements(Z_ARRVAL_P(tuple)); - p += sizeof(uint32_t); - - pht = Z_ARRVAL_P(tuple); - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht, &pos)) { - - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); - // printf("tuple: len=%d [%s]", str_len, strval ); - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - -// u_char leb_size = (u_char)leb128_size( val); -// *(p++) = leb_size; -// leb128_write( (char *)p, val); - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; - - p += leb_size; - } - } + /* put count to result array */ + add_assoc_long(return_value, "count", response->count); - } - break; - default : - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "tuple: unsuport tuple type" ,0 TSRMLS_CC); - return; + /* put tuple list to result array */ + zval *tuples_list; + MAKE_STD_ZVAL(tuples_list); + if (array_init(tuples_list) == FAILURE) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "select failed: create array failed"); + return; } - u_char * p_end = (u_char *)select->tuples ; - header->len = (p - p_end) + SELECT_REQUEST_SIZE; //+ HEADER_SIZE ; // 12 = 3 * sizeof(int32) :ns, flag & cardinality - - // write header - int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12 - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write header error" ,0 TSRMLS_CC); - efree(out_buf); + /* read tuples for responce */ + int i; + for (i = 0; i < response->count; ++i) { + zval *tuple; + if (!io_buf_read_tuple(tnt->io_buf, &tuple)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "select failed: invalid response was received"); return; + } + add_next_index_zval(tuples_list, tuple); } - len = php_stream_write(ctx->stream, (void*)select , header->len ); - if (len != header->len) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write request error" ,0 TSRMLS_CC); - efree(out_buf); - return; + add_assoc_zval(return_value, "tuples_list", tuples_list); +} + +PHP_METHOD(tarantool_class, insert) +{ + /* + * parse methods parameters + */ + zval *id; + long space_no = 0; + long flags = 0; + zval *tuple = NULL; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Ola|l", + &id, + tarantool_class_ptr, + &space_no, + &tuple, + &flags) == FAILURE) { + return; } - Header responseHeader; - bzero(&responseHeader, HEADER_SIZE); + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE); - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "read header error" ,0 TSRMLS_CC); - efree(out_buf); + /* check connection */ + if (!tnt->stream) { + /* establis connection */ + tnt->stream = establish_connection(tnt->host, tnt->port); + if (!tnt->stream) return; } - ctx->bodyLen = responseHeader.len; + /* + * send request + */ - uint32_t code; + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t)); - ctx->readed += len; - if (code > 0 || len != sizeof(uint32_t)) { - ctx->errorcode = code; /// need test - efree(out_buf); - RETURN_FALSE; - } -// - uint32_t tuples_count=0; - len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t)); - ctx->readed += len; - if (len != sizeof(uint32_t)) { - zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), - 0 TSRMLS_CC,"read body error"); - efree(out_buf); + /* fill insert command */ + struct tnt_insert_request *request = (struct tnt_insert_request *) io_buf_write_struct( + tnt->io_buf, sizeof(struct tnt_insert_request)); + if (request == NULL) return; - } - ctx->errorcode = code; - ctx->countTuples = tuples_count; - efree(out_buf); - - RETURN_LONG(ctx->countTuples); + /* space number */ + request->space_no = space_no; + /* flags */ + request->flags = flags; + /* tuple */ + if (!io_buf_write_tuple(tnt->io_buf, tuple)) + return; -} -/* }}} */ + /* send iproto request */ + if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_INSERT, 0, tnt->io_buf)) + return; -/* {{{ proto int tarantool::mselect(space, index, tuples [, limit=all, offset=0]); - select some tuples tuples = array(key1,key2,key3); - use only simple key -*/ -PHP_METHOD(tarantool_class, mselect ) -{ - zval *id, * tuple; - tarantool_object *ctx; - long ns = 0; - long idx = 0; - long limit = 0xffffffff; - long offset = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olla|ll", &id, - tarantool_class_entry, &ns, &idx, &tuple, &limit, &offset) == FAILURE) { - return; - } + /* + * receive response + */ - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } + /* receive */ + if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf)) + return; + /* read response */ + struct tnt_response *response; + if (!io_buf_read_struct(tnt->io_buf, + (void **) &response, + sizeof(struct tnt_response))) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "insert failed: invalid response was received"); + return; } - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - ctx->errorcode = 0; + /* check return code */ + if (response->return_code) { + /* error happen, throw exceprion */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "insert failed: %"PRIi32"(0x%08"PRIx32"): %s", + response->return_code, + response->return_code, + response->return_msg); + return; + } + + /* + * fill return value + */ - char * out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); + if (array_init(return_value) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "insert failed: create array failed"); + return; + } - Header * header = (Header *) out_buf; - SelectRequest * select = (SelectRequest *) out_buf + HEADER_SIZE; + /* put count to result array */ + add_assoc_long(return_value, "count", response->count); - header->type = TARANTOOL_SELECT; - header->request_id = TARANTOOL_REQUEST_ID; + /* check "return tuple" flag */ + if (flags & TARANTOOL_FLAGS_RETURN_TUPLE) { + /* ok, the responce should contain inserted tuple */ + if (!io_buf_read_tuple(tnt->io_buf, &tuple)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "insert failed: invalid response was received"); + return; + } - select->spaceNo = ns; - select->indexNo = idx; - select->limit = limit; - select->offset = offset; + /* put returned tuple to result array */ + add_assoc_zval(return_value, "tuple", tuple); + } +} +PHP_METHOD(tarantool_class, update_fields) +{ + /* + * parse methods parameters + */ + zval *id; + long space_no = 0; + long flags = 0; + zval *tuple = NULL; + zval *op_list = NULL; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Olza|l", + &id, + tarantool_class_ptr, + &space_no, + &tuple, + &op_list, + &flags) == FAILURE) { + return; + } - u_char * p = (u_char *)select->tuples ; - select->count = zend_hash_num_elements(Z_ARRVAL_P(tuple)); + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - // ôîðìèðóåì òàê êàê íàäî! + /* check connection */ + if (!tnt->stream) { + /* establis connection */ + tnt->stream = establish_connection(tnt->host, tnt->port); + if (!tnt->stream) + return; + } - HashTable *pht; - HashPosition pos; - zval **curr; + /* + * send request + */ + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - pht = Z_ARRVAL_P(tuple); - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht, &pos)) { + /* fill insert command */ + struct tnt_update_fields_request *request = (struct tnt_update_fields_request *) io_buf_write_struct( + tnt->io_buf, sizeof(struct tnt_update_fields_request)); + if (request == NULL) + return; - int32_t * count = (int32_t *)p; - *count = 1; - p += sizeof(int32_t); + /* space number */ + request->space_no = space_no; + /* flags */ + request->flags = flags; + /* tuple */ + if (!io_buf_write_tuple(tnt->io_buf, tuple)) + return; + HashTable *op_list_array = Z_ARRVAL_P(op_list); + int op_count = zend_hash_num_elements(op_list_array); - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); -// printf("tuple: len=%d [%s]", str_len, strval ); - u_char str_shortlen = (u_char)str_len; + /* write number of update fields operaion */ + if (!io_buf_write_int32(tnt->io_buf, op_count)) + return; - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; + HashPosition itr; + zval **op; + for (zend_hash_internal_pointer_reset_ex(op_list_array, &itr); + zend_hash_get_current_data_ex(op_list_array, (void **) &op, &itr) == SUCCESS; + zend_hash_move_forward_ex(op_list_array, &itr)) { + /* check operation type */ + if (Z_TYPE_PP(op) != IS_ARRAY) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid operations list"); + return; } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; + HashTable *op_array = Z_ARRVAL_PP(op); + long field_no; + long opcode; - p += leb_size; + if (!hash_fing_long(op_array, "field", &field_no)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'field' in the update field operation"); + return; } - } // end array + if (!hash_fing_long(op_array, "op", &opcode)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'op' in the update field operation"); + return; + } + /* write field number */ + if (!io_buf_write_int32(tnt->io_buf, field_no)) + return; - u_char * p_end = (u_char *)select->tuples ; - header->len = (p - p_end) + SELECT_REQUEST_SIZE; //+ HEADER_SIZE ; // 12 = 3 * sizeof(int32) :ns, flag & cardinality + /* write operation code */ + if (!io_buf_write_byte(tnt->io_buf, opcode)) + return; + zval **assing_arg = NULL; + long arith_arg; + long splice_offset; + long splice_length; + char *splice_list; + int splice_list_len; + switch (opcode) { + case TARANTOOL_OP_ASSIGN: + if (!hash_fing_scalar(op_array, "arg", &assing_arg)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'arg' in the update field operation"); + return; + } + if (Z_TYPE_PP(assing_arg) == IS_LONG) { + /* write as interger */ + if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) &Z_LVAL_PP(assing_arg), sizeof(int32_t))) + return; + } else { + /* write as string */ + if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) Z_STRVAL_PP(assing_arg), Z_STRLEN_PP(assing_arg))) + return; + } + break; + case TARANTOOL_OP_ADD: + case TARANTOOL_OP_AND: + case TARANTOOL_OP_XOR: + case TARANTOOL_OP_OR: + if (!hash_fing_long(op_array, "arg", &arith_arg)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'arg' in the update field operation"); + return; + } + /* write arith arg */ + if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) &arith_arg, sizeof(int32_t))) + return; + break; + case TARANTOOL_OP_SPLICE: + /* + * read splice args + */ + + /* read offset */ + if (!hash_fing_long(op_array, "offset", &splice_offset)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'offset' in the update field operation"); + return; + } + /* read length */ + if (!hash_fing_long(op_array, "length", &splice_length)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'length' in the update field operation"); + return; + } + /* read list */ + if (!hash_fing_str(op_array, "list", &splice_list, &splice_list_len)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "can't find 'list' in the update field operation"); + return; + } -// printf("body len %d\n", header->len); -// -// p = (u_char *)select; -// printLine3(p); -// printLine3(p+12); -// printLine3(p+24); -// printLine3(p+36); -// -// efree(out_buf); -// RETURN_FALSE; + /* + * write splice args + */ + io_buf_clean(tnt->splice_field); + + /* write offset to separate buffer */ + if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) &splice_offset, sizeof(int32_t))) + return; + /* write length to separate buffer */ + if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) &splice_length, sizeof(int32_t))) + return; + /* write list to separate buffer */ + if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) splice_list, splice_list_len)) + return; + + /* write splice args as alone field */ + if (!io_buf_write_field_str(tnt->io_buf, tnt->splice_field->value, tnt->splice_field->size)) + return; - // write header - int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12 - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write header error" ,0 TSRMLS_CC); - efree(out_buf); + break; + default: + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid operaion code %i", opcode); return; + } } - len = php_stream_write(ctx->stream, (void*)select , header->len ); - if (len != header->len) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write request error" ,0 TSRMLS_CC); - efree(out_buf); - return; - } + /* send iproto request */ + if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_UPDATE, 0, tnt->io_buf)) + return; - Header responseHeader; - bzero(&responseHeader, HEADER_SIZE); + /* + * receive response + */ - len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE); - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "read header error" ,0 TSRMLS_CC); - efree(out_buf); - return; - } + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - ctx->bodyLen = responseHeader.len; + /* receive */ + if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf)) + return; - uint32_t code; + /* read response */ + struct tnt_response *response; + if (!io_buf_read_struct(tnt->io_buf, + (void **) &response, + sizeof(struct tnt_response))) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "update fields failed: invalid response was received"); + return; + } - len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t)); - ctx->readed += len; - if (code > 0 || len != sizeof(uint32_t)) { - ctx->errorcode = code; /// need test - efree(out_buf); - RETURN_FALSE; + /* check return code */ + if (response->return_code) { + /* error happen, throw exceprion */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "update fields failed: %"PRIi32"(0x%08"PRIx32"): %s", + response->return_code, + response->return_code, + response->return_msg); + return; } -// - uint32_t tuples_count=0; - len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t)); - ctx->readed += len; - if (len != sizeof(uint32_t)) { - zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), - 0 TSRMLS_CC,"read body error"); - efree(out_buf); + + /* + * fill return value + */ + + if (array_init(return_value) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "update fields failed: create array failed"); return; } - ctx->errorcode = code; - ctx->countTuples = tuples_count; - efree(out_buf); + /* put count to result array */ + add_assoc_long(return_value, "count", response->count); - RETURN_LONG(ctx->countTuples); + /* check "return tuple" flag */ + if ((response->count > 0) && (flags & TARANTOOL_FLAGS_RETURN_TUPLE)) { + /* ok, the responce should contain inserted tuple */ + if (!io_buf_read_tuple(tnt->io_buf, &tuple)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "update fields failed: invalid response was received"); + return; + } + /* put returned tuple to result array */ + add_assoc_zval(return_value, "tuple", tuple); + } } -/* }}} */ -/* {{{ proto int tarantool::call(procname, tuple); - call a stored procedure by name -*/ -PHP_METHOD(tarantool_class, call) +PHP_METHOD(tarantool_class, delete) { - zval *id, * tuple; - tarantool_object *ctx; - const char *procname = NULL; - long procname_len = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &id, - tarantool_class_entry, &procname, &procname_len, &tuple) == FAILURE) { + /* + * parse methods parameters + */ + zval *id; + long space_no = 0; + long flags = 0; + zval *tuple = NULL; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Olz|l", + &id, + tarantool_class_ptr, + &space_no, + &tuple, + &flags) == FAILURE) { return; } - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "can't connect to remote host " ,0 TSRMLS_DC); + /* check connection */ + if (!tnt->stream) { + /* establis connection */ + tnt->stream = establish_connection(tnt->host, tnt->port); + if (!tnt->stream) return; - } } - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - ctx->errorcode = 0; - - char *out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); - - Header *header = (Header *) out_buf; - - header->type = TARANTOOL_CALL; - header->request_id = TARANTOOL_REQUEST_ID; - - u_char *call = (char *) out_buf + HEADER_SIZE; - u_char *p = call; + /* + * send request + */ - p+= 4; /* flags */ - *p++ = (u_char) procname_len; + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - memcpy(p, procname, procname_len); - p += procname_len; - - int * count_elements = (int *)p; - - switch (Z_TYPE_P(tuple)) { - case IS_STRING: { - *count_elements = 1; - p += sizeof(uint32_t); - - char * strval = Z_STRVAL_P(tuple); - int str_len = Z_STRLEN_P(tuple); - u_char str_shortlen = (u_char)str_len; + /* fill delete command */ + struct tnt_delete_request *request = (struct tnt_delete_request *) io_buf_write_struct( + tnt->io_buf, sizeof(struct tnt_delete_request)); + if (request == NULL) + return; - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - break; + /* space number */ + request->space_no = space_no; + /* flags */ + request->flags = flags; + /* tuple */ + if (!io_buf_write_tuple(tnt->io_buf, tuple)) + return; - case IS_LONG: { - *count_elements = 1; - p += sizeof(uint32_t); + /* send iproto request */ + if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_DELETE, 0, tnt->io_buf)) + return; - unsigned long val = Z_LVAL_P(tuple); + /* + * receive response + */ - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; - p += leb_size; + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - } - break; + /* receive */ + if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf)) + return; + /* read response */ + struct tnt_response *response; + if (!io_buf_read_struct(tnt->io_buf, + (void **) &response, + sizeof(struct tnt_response))) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "delete failed: invalid response was received"); + return; + } - case IS_ARRAY: { - HashTable *pht; - HashPosition pos; - zval **curr; - - *count_elements = zend_hash_num_elements(Z_ARRVAL_P(tuple)); - p += sizeof(uint32_t); - - pht = Z_ARRVAL_P(tuple); - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht, &pos)) { - - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); - // printf("tuple: len=%d [%s]", str_len, strval ); - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - -// u_char leb_size = (u_char)leb128_size( val); -// *(p++) = leb_size; -// leb128_write( (char *)p, val); - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pval = (b2i*) p; - pval->i = (int) val; - - p += leb_size; - } - } + /* check return code */ + if (response->return_code) { + /* error happen, throw exceprion */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "delete failed: %"PRIi32"(0x%08"PRIx32"): %s", + response->return_code, + response->return_code, + response->return_msg); + return; + } + + /* + * fill return value + */ - } - break; - default : - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "tuple: unsuport tuple type" ,0 TSRMLS_CC); - return; + if (array_init(return_value) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "delete failed: create array failed"); + return; } - header->len = p - call; + /* put count to result array */ + add_assoc_long(return_value, "count", response->count); - // write header - int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12 - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write header error" ,0 TSRMLS_CC); - efree(out_buf); + /* check "return tuple" flag */ + if ((response->count) > 0 && (flags & TARANTOOL_FLAGS_RETURN_TUPLE)) { + /* ok, the responce should contain inserted tuple */ + if (!io_buf_read_tuple(tnt->io_buf, &tuple)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "delete failed: invalid response was received"); return; + } + + /* put returned tuple to result array */ + add_assoc_zval(return_value, "tuple", tuple); } +} - len = php_stream_write(ctx->stream, call, header->len ); - if (len != header->len) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "write request error" ,0 TSRMLS_CC); - efree(out_buf); - return; +PHP_METHOD(tarantool_class, call) +{ + /* + * parse methods parameters + */ + zval *id; + char *proc_name = NULL; + int proc_name_len = 0; + zval *tuple = NULL; + long flags = 0; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Osz|l", + &id, + tarantool_class_ptr, + &proc_name, &proc_name_len, + &tuple, + &flags) == FAILURE) { + return; } - Header responseHeader; - bzero(&responseHeader, HEADER_SIZE); + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE); - if (len != HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "read header error" ,0 TSRMLS_CC); - efree(out_buf); + /* check connection */ + if (!tnt->stream) { + /* establis connection */ + tnt->stream = establish_connection(tnt->host, tnt->port); + if (!tnt->stream) return; } - ctx->bodyLen = responseHeader.len; + /* + * send request + */ - uint32_t code; + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t)); - ctx->readed += len; - if (code > 0 || len != sizeof(uint32_t)) { - ctx->errorcode = code; /// need test - efree(out_buf); - RETURN_FALSE; - } -// - uint32_t tuples_count=0; - len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t)); - ctx->readed += len; - if (len != sizeof(uint32_t)) { - zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), - 0 TSRMLS_CC,"read body error"); - efree(out_buf); + /* fill insert command */ + struct tnt_call_request *request = (struct tnt_call_request *) io_buf_write_struct( + tnt->io_buf, sizeof(struct tnt_call_request)); + if (request == NULL) return; - } - ctx->errorcode = code; - ctx->countTuples = tuples_count; - efree(out_buf); + /* flags */ + request->flags = flags; + /* proc name */ + if (!io_buf_write_field_str(tnt->io_buf, proc_name, proc_name_len)) + return; + /* tuple */ + if (!io_buf_write_tuple(tnt->io_buf, tuple)) + return; - RETURN_LONG(ctx->countTuples); + /* send iproto request */ + if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_CALL, 0, tnt->io_buf)) + return; -} -/* }}} */ + /* + * receive response + */ -/* {{{ proto array tarantool::getTuple() - return one tuple */ -PHP_METHOD(tarantool_class, getTuple ) -{ - zval *id; - tarantool_object *ctx; + /* clean-up buffer */ + io_buf_clean(tnt->io_buf); - int len; + /* receive */ + if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf)) + return; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id, - tarantool_class_entry) == FAILURE) { + /* read response */ + struct tnt_response *response; + if (!io_buf_read_struct(tnt->io_buf, + (void **) &response, + sizeof(struct tnt_response))) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "call failed: invalid response was received"); return; } - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); + /* check return code */ + if (response->return_code) { + /* error happen, throw exceprion */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "call failed: %"PRIi32"(0x%08"PRIx32"): %s", + response->return_code, + response->return_code, + response->return_msg); return; } - if (!ctx->bodyLen) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the response body is null" ,0 TSRMLS_CC); + if (array_init(return_value) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "call failed: create array failed"); return; } - if (++ctx->readedTuples > ctx->countTuples) { - RETURN_FALSE; - } + /* put count to result array */ + add_assoc_long(return_value, "count", response->count); - if (ctx->readed >= ctx->bodyLen) { - RETURN_FALSE; + /* put tuple list to result array */ + zval *tuples_list; + MAKE_STD_ZVAL(tuples_list); + if (array_init(tuples_list) == FAILURE) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "call failed: create array failed"); + return; } + /* read tuples for responce */ + int i; + for (i = 0; i < response->count; ++i) { + zval *tuple; + if (!io_buf_read_tuple(tnt->io_buf, &tuple)) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "call failed: invalid response was received"); + return; + } + add_next_index_zval(tuples_list, tuple); + } - SelectResponseTuple responseBody; + add_assoc_zval(return_value, "tuples_list", tuples_list); +} - if (php_stream_read(ctx->stream, (void*)&responseBody, sizeof(SelectResponseTuple)) !=sizeof(SelectResponseTuple)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the read body error" ,0 TSRMLS_CC); +PHP_METHOD(tarantool_class, admin) +{ + /* parse methods parameters */ + zval *id; + char *cmd = NULL; + int cmd_len = 0; + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + getThis(), + "Os", + &id, + tarantool_class_ptr, + &cmd, &cmd_len) == FAILURE) { return; } - ctx->readed += sizeof(SelectResponseTuple); + tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object( + id TSRMLS_CC); - u_char * buf = emalloc(responseBody.size); - - if (php_stream_read(ctx->stream, (char*) buf, responseBody.size) != responseBody.size) { - efree(buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the read tuple error" ,0 TSRMLS_CC); + /* check admin port */ + if (!tnt->admin_port) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "admin command not allowed for this commection"); return; } - ctx->readed += responseBody.size; - - array_init(return_value); - - char bb2[16]; - int i; - unsigned long value; - b2i b; - b2i * pb; - u_char* p = buf; - int is_string = 0; - - for(i=0; i < responseBody.count; i++) { // - b.i=0; - b.b = *p; - bzero(bb2,16); - len = b.i; - memcpy(bb2, p+1, len); - - switch (len) { - case 1 : { - if ( isprint(*bb2) ) { -// printf("tuple element '%s' len=%d\n",bb2, len); - is_string = 1; - } else { - is_string = 0; - leb128_read(bb2, len, &value); -// printf("tuple element(int) %d len=%d\n",value, len); - } - break; - } - case 2 : { - if ( isprint(*bb2) && isprint(*(bb2+1))) { -// printf("tuple element '%s' len=%d\n",bb2, len); - is_string = 1; - } else { - is_string = 0; - leb128_read(bb2, len, &value); -// printf("tuple element(int) %d len=%d\n",value, len); - } - break; - } - - case 3 : { - if ( isprint(*bb2) && isprint(*(bb2+1)) && isprint(*(bb2+2)) ) { -// printf("tuple element '%s' len=%d\n",bb2, len); - is_string = 1; - } else { - is_string = 0; - leb128_read(bb2, len, &value); -// printf("tuple element(int) %d len=%d\n",value, len); - } - break; - } - - case 4 : { - if ( isprint(*bb2) && isprint(*(bb2+1)) && isprint(*(bb2+2)) && isprint(*(bb2+3)) ) { - is_string = 1; - } else { - - pb = (b2i*) bb2; - value = (unsigned long) pb->i; -// php_printf("tuple element(int) %d len=%d\n",value, len); - is_string = 0; - } - break; - } - default : { - is_string = 0; -// printf("tuple element %s len=%d\n",bb2, len); - } - } // end switch - - - if (is_string) { -// php_printf("tuple element '%s' len=%d\n",bb2, len); - add_next_index_stringl( return_value, bb2 , len , 1); - } else -// php_printf("tuple element %d\n",bb2, value); - add_next_index_long( return_value, value); + /* check connection */ + if (!tnt->admin_stream) { + /* establis connection */ + tnt->admin_stream = establish_connection(tnt->host, tnt->admin_port); + if (!tnt->admin_stream) + return; - p += len+1; + /* set string eol */ + php_stream_locate_eol(tnt->admin_stream, + ADMIN_SEPARATOR, + strlen(ADMIN_SEPARATOR) TSRMLS_DC); } - efree(buf); -} -/* }}} */ - -/* {{{ proto int tarantool::delete(key) - tarantool delete tuple */ -PHP_METHOD(tarantool_class, delete) -{ - zval *id; - tarantool_object *ctx; - long space; - - zval* data; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olz", - &id, tarantool_class_entry, &space, &data) == FAILURE) { + /* send request */ + io_buf_clean(tnt->io_buf); + if (!io_buf_write_str(tnt->io_buf, cmd, cmd_len)) return; - } - - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); + if (!io_buf_write_str(tnt->io_buf, ADMIN_SEPARATOR, strlen(ADMIN_SEPARATOR))) + return; + if (!io_buf_send_yaml(tnt->admin_stream, tnt->io_buf)) return; - } - - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } - } - - char * buf = emalloc(TARANTOOL_BUFSIZE); - bzero(buf, TARANTOOL_BUFSIZE); - - Header * header = (Header *) buf; - DeleteRequest * delRequest = (DeleteRequest *) buf + HEADER_SIZE; - - header->type = TARANTOOL_DELETE; - header->request_id = TARANTOOL_REQUEST_ID; - - delRequest->spaceNo = space; - delRequest->tuple.count = 1; // count in the Tuples - - u_char * p = (u_char *) delRequest->tuple.data; - - int * count_elements = (int *)p; - switch (Z_TYPE_P(data)) { - case IS_STRING: { + /* recv response */ + io_buf_clean(tnt->io_buf); + if (!io_buf_recv_yaml(tnt->admin_stream, tnt->io_buf)) + return; - char * strval = Z_STRVAL_P(data); - int str_len = Z_STRLEN_P(data); - u_char str_shortlen = (u_char)str_len; + char *response = estrndup(tnt->io_buf->value, tnt->io_buf->size); + RETURN_STRING(response, 0); +} - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - break; - case IS_LONG: { +/*============================================================================* + * local functions definition + *============================================================================*/ - unsigned long val = Z_LVAL_P(data); - u_char leb_size = (u_char)leb128_size( val); - *(p++) = leb_size; - leb128_write( (char *)p, val); - p += leb_size; -// printf("tuple: int %d\n", val ); - } - break; +/*----------------------------------------------------------------------------* + * Buffer interface + *----------------------------------------------------------------------------*/ - case IS_ARRAY: { - HashTable *pht; - HashPosition pos; - zval **curr; - - delRequest->tuple.count = zend_hash_num_elements(Z_ARRVAL_P(data)); - pht = Z_ARRVAL_P(data); - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht, &pos)) { - - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - - u_char leb_size = (u_char)leb128_size( val); - *(p++) = leb_size; - leb128_write( (char *)p, val); - p += leb_size; - } - } - - } - break; - default : - efree(buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "tuple: unsuport tuple type" ,0 TSRMLS_CC); - return; +static struct io_buf * +io_buf_create() +{ + struct io_buf *buf = (struct io_buf *) emalloc(sizeof(struct io_buf)); + if (!buf) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "allocation memory fail: %s (%i)", strerror(errno), errno); + goto failure; } - header->len = (p-delRequest->tuple.data) + sizeof(uint32_t) *2 ; // sizeof(int) + sizeof(int) - - // write header - int len = php_stream_write(ctx->stream, buf , HEADER_SIZE); // 12 - - // write tuple - p = (u_char*)delRequest; - len = php_stream_write(ctx->stream, (char*)p , header->len ); - - if (header->len + HEADER_SIZE > TARANTOOL_BUFSIZE) { - efree(buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "out of memory: the tuple is very big" ,0 TSRMLS_CC); - return; + buf->size = 0; + buf->capacity = io_buf_next_capacity(buf->size); + buf->readed = 0; + buf->value = (uint8_t *) emalloc(buf->capacity); + if (!buf->value) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "allocation memory fail: %s (%i)", strerror(errno), errno); + goto failure; } - bzero(buf, header->len + HEADER_SIZE); + return buf; - len = php_stream_read(ctx->stream, buf, TARANTOOL_BUFSIZE); +failure: + if (buf) { + if (buf->value) + efree(buf->value); - if ( *(buf+HEADER_SIZE) == '\0') { - int deleted = *(buf+HEADER_SIZE + sizeof(uint32_t)); efree(buf); - RETURN_LONG(deleted); } - - b2i* bb =(b2i*)(buf+HEADER_SIZE); - ctx->errorcode = bb->i; - - efree(buf); - RETURN_FALSE; - + return NULL; } -/* }}} */ -/* {{{ proto int tarantool::update(int space, mixed key, array data); - tarantool update tuple */ -PHP_METHOD(tarantool_class, update) +static void +io_buf_destroy(struct io_buf *buf) { - zval *id; - tarantool_object *ctx; - long space; - - zval* key; - zval* data; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olza", - &id, tarantool_class_entry, &space, &key, &data) == FAILURE) { + if (!buf) return; - } - - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } - - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - ctx->errorcode = 0; - - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } - } - -// <insert_request_body> ::= <space_no><flags><tuple> -// <update_request_body> ::= <space_no><flags><tuple><count><operation>+ -// <operation> ::= <field_no><op_code><op_arg> - - char * out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); - - Header * header = (Header *) out_buf; - - header->type = TARANTOOL_UPDATE; - header->request_id = TARANTOOL_REQUEST_ID; - - InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE); - - insert->spaceNo = space; - insert->tuple.count = 1; - - u_char * p = (u_char *) insert->tuple.data; - - switch (Z_TYPE_P(key)) { - case IS_STRING: { -// *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå -// p += sizeof(uint32_t); - - char * strval = Z_STRVAL_P(key); - int str_len = Z_STRLEN_P(key); - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - -// printf("tuple: len=%d [%s]\n", str_len, strval ); - } - break; - - case IS_LONG: { -// *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå -// p += sizeof(uint32_t); - - unsigned long val = Z_LVAL_P(key); - - u_char leb_size = 4; //(u_char)leb128_size( val); - *(p++) = leb_size; - b2i * pb = (b2i*) p; - pb->i = (uint32_t) val; -// leb128_write( (char *)p, val); - p += leb_size; + if (buf->value) + efree(buf->value); -// printf("tuple: int %d\n", val ); - } - break; - - default : - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "unsupport key type" ,0 TSRMLS_CC); - return; - } - - const int insertLen = p-insert->tuple.data; - - - HashTable *pht = Z_ARRVAL_P(data); - - UpdateRequest * updateRequest = (UpdateRequest*) p; - updateRequest->count = zend_hash_num_elements(pht);; - - HashPosition pos; - zval** curr; - - char *ht_key; - ulong index; - uint ht_key_len; - - Operation * operation = &(updateRequest->operation); - - for(zend_hash_internal_pointer_reset_ex(pht, &pos); - zend_hash_get_current_data_ex(pht, (void **) &curr, &pos) == SUCCESS; - zend_hash_move_forward_ex(pht,&pos)) { - - if (HASH_KEY_IS_LONG != zend_hash_get_current_key_ex(pht, &ht_key, &ht_key_len, &index, 0, &pos)) { - efree(out_buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "key type error" ,0 TSRMLS_CC); - return; - } - - operation->code = TARANTOOL_OP_ASSIGN; - operation->fieldNo = index; - p = operation->arg; - - if (Z_TYPE_PP(curr) == IS_STRING) { - char * strval = Z_STRVAL_PP(curr); - int str_len = Z_STRLEN_PP(curr); - - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - } - if (Z_TYPE_PP(curr) == IS_LONG) { - unsigned long val = Z_LVAL_PP(curr); - - u_char leb_size = 4; - *(p++) = leb_size; - b2i * pb = (b2i*) p; - pb->i = (uint32_t) val; - p += leb_size; - } - - operation = (Operation*)p; - } - - u_char * p2 = (u_char *) insert; - header->len = (uint32_t) (p-p2); + efree(buf); +} - // write header - int len = php_stream_write(ctx->stream, out_buf , HEADER_SIZE); // 12 - if (len!=HEADER_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "error write header" ,0 TSRMLS_CC); - return; - } +inline static bool +io_buf_reserve(struct io_buf *buf, size_t n) +{ + if (buf->capacity > n) + return true; -// write tuple - p = (u_char*) out_buf+HEADER_SIZE; - len = php_stream_write(ctx->stream, (char*)p , header->len ); // - if (len != header->len) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "error write body" ,0 TSRMLS_CC); - return; + size_t new_capacity = io_buf_next_capacity(n); + uint8_t *new_value = (uint8_t *) erealloc(buf->value, new_capacity); + if (!new_value) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "allocation memory fail: %s (%i)", strerror(errno), errno); + return false; } - bzero(out_buf, header->len + HEADER_SIZE); -// - len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE); + buf->capacity = new_capacity; + buf->value = new_value; + return true; +} - if ( *(out_buf+HEADER_SIZE) == '\0') { - efree(out_buf); - RETURN_TRUE; - } +inline static bool +io_buf_resize(struct io_buf *buf, size_t n) +{ + io_buf_reserve(buf, n); + buf->size = n; + return true; +} - b2i* bb = (b2i*) out_buf+HEADER_SIZE; - ctx->errorcode = bb->i; +inline static size_t +io_buf_next_capacity(size_t n) +{ + size_t capacity = IO_BUF_CAPACITY_MIN; + while (capacity < n) + capacity *= IO_BUF_CAPACITY_FACTOR; + return capacity; +} - efree(out_buf); - RETURN_FALSE; +static void +io_buf_clean(struct io_buf *buf) +{ + buf->size = 0; + buf->readed = 0; +} +static bool +io_buf_read_struct(struct io_buf *buf, void **ptr, size_t n) +{ + size_t last = buf->size - buf->readed; + if (last < n) + return false; + *ptr = buf->value + buf->readed; + buf->readed += n; + return true; } -/* }}} */ +static bool +io_buf_read_int32(struct io_buf *buf, int32_t *val) +{ + size_t last = buf->size - buf->readed; + if (last < sizeof(int32_t)) + return false; + *val = *(int32_t *)(buf->value + buf->readed); + buf->readed += sizeof(int32_t); + return true; +} -/* {{{ proto int tarantool::inc(int space, mixed key, int fieldNo, [data = 1, flag=0]); - $tnt->inc($NS,$key, $fieldNo , $inc=1; ); - tarantool incremental tuple */ -PHP_METHOD(tarantool_class, inc) +static bool +io_buf_read_int64(struct io_buf *buf, int64_t *val) { - zval *id; - tarantool_object *ctx; - long space, fieldNo ; - zval* key; - long data = 1; - long flag = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olzl|lb", - &id, tarantool_class_entry, &space, &key, &fieldNo, &data, &flag) == FAILURE) { - return; - } + size_t last = buf->size - buf->readed; + if (last < sizeof(int64_t)) + return false; + *val = *(int64_t *)(buf->value + buf->readed); + buf->readed += sizeof(int64_t); + return true; +} - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } +static bool +io_buf_read_varint(struct io_buf *buf, int32_t *val) +{ + uint8_t *b = buf->value + buf->readed; + size_t size = buf->size - buf->readed; - ctx->bodyLen = 0; - ctx->countTuples = 0; - ctx->readedTuples = 0; - ctx->readed = 0; - ctx->errorcode = 0; + if (size < 1) + return false; - if (!ctx->stream) { - if (php_tnt_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } + if (!(b[0] & 0x80)) { + buf->readed += 1; + *val = (b[0] & 0x7f); + return true; } -// <insert_request_body> ::= <space_no><flags><tuple> -// <update_request_body> ::= <space_no><flags><tuple><count><operation>+ - -// <operation> ::= <field_no><op_code><op_arg> - - char * out_buf = emalloc(TARANTOOL_BUFSIZE); - bzero(out_buf, TARANTOOL_BUFSIZE); - - - Header * header = (Header *) out_buf; - - - header->type = TARANTOOL_UPDATE; - header->request_id = TARANTOOL_REQUEST_ID; - - InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE); - - insert->spaceNo = space; - insert->tuple.count = 1; - insert->flag = flag; - - u_char * p = (u_char *) insert->tuple.data; - - switch (Z_TYPE_P(key)) { - case IS_STRING: { -// *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå -// p += sizeof(uint32_t); - - char * strval = Z_STRVAL_P(key); - int str_len = Z_STRLEN_P(key); - u_char str_shortlen = (u_char)str_len; - - *(p++) = str_shortlen; - memcpy(p, strval, str_len); - p += str_len; - -// printf("tuple: len=%d [%s]\n", str_len, strval ); - } - break; - - case IS_LONG: { -// *count_elements = 1; //!!!! <------- êîë-âî ýëåìåíòîâ â êîðòåæå -// p += sizeof(uint32_t); - - unsigned long val = Z_LVAL_P(key); - - u_char leb_size = 4; //(u_char)leb128_size( val); - *(p++) = leb_size; - - b2i * pb = (b2i*) p; - pb->i = (uint32_t) val; -// leb128_write( (char *)p, val); - p += leb_size; - -// printf("tuple: int %d\n", val ); - } - break; + if (size < 2) + return false; - default : - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "unsupport key type" ,0 TSRMLS_CC); - return; + if (!(b[1] & 0x80)) { + buf->readed += 2; + *val = (b[0] & 0x7f) << 7 | (b[1] & 0x7f); + return true; } - const int insertLen = p-insert->tuple.data; - - UpdateRequest * incRequest = (UpdateRequest*) p; - incRequest->count = 1; - incRequest->operation.code = TARANTOOL_OP_ADD; - incRequest->operation.fieldNo = fieldNo; - - u_char leb_size = '\4'; - if (data == 1) { - incRequest->operation.arg[0] = leb_size; - incRequest->operation.arg[1] = '\1'; - incRequest->operation.arg[2] = '\0'; - incRequest->operation.arg[3] = '\0'; - incRequest->operation.arg[4] = '\0'; + if (size < 3) + return false; - } else { - p = incRequest->operation.arg; - *(p++) = leb_size; - b2i * pb = (b2i*) p; - pb->i = (int) data; - p += leb_size; + if (!(b[2] & 0x80)) { + buf->readed += 3; + *val = (b[0] & 0x7f) << 14 | (b[1] & 0x7f) << 7 | (b[2] & 0x7f); + return true; } - header->len = INSERT_REQUEST_SIZE + insertLen + UPDATE_REQUEST_SIZE + (int)leb_size; + if (size < 4) + return false; - // write header - int len = php_stream_write(ctx->stream, out_buf , HEADER_SIZE); // 12 - if (len!=HEADER_SIZE) { - efree(out_buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "error write header" ,0 TSRMLS_CC); - return; + if (!(b[3] & 0x80)) { + buf->readed += 4; + *val = (b[0] & 0x7f) << 21 | (b[1] & 0x7f) << 14 | + (b[2] & 0x7f) << 7 | (b[3] & 0x7f); + return true; } -// write tuple - p = (u_char*) out_buf+HEADER_SIZE; - len = php_stream_write(ctx->stream, (char*)p , header->len ); // - if (len != header->len) { - efree(out_buf); - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "error write body" ,0 TSRMLS_CC); - return; - } - - - bzero(out_buf, header->len + HEADER_SIZE); -// - len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE); + if (size < 5) + return false; - if ( *(out_buf+HEADER_SIZE) != '\0') { - b2i* bb = (b2i*)(out_buf+HEADER_SIZE); - ctx->errorcode = bb->i; - efree(out_buf); - RETURN_FALSE; + if (!(b[4] & 0x80)) { + buf->readed += 5; + *val = (b[0] & 0x7f) << 28 | (b[1] & 0x7f) << 21 | + (b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f); + return true; } - if( !flag) - RETURN_TRUE; + return false; +} - Response * responseBody = (Response*) (out_buf+HEADER_SIZE); - SelectResponseTuple * responseTuple = (SelectResponseTuple*) responseBody->data; +static bool +io_buf_read_str(struct io_buf *buf, char **str, size_t len) +{ + size_t last = buf->size - buf->readed; + if (last < len) + return false; + *str = (char *)(buf->value + buf->readed); + buf->readed += len; + return true; +} - p = responseBody->data + SELECT_RESPONSE_SIZE; +static bool +io_buf_read_field(struct io_buf *buf, zval *tuple) +{ + int32_t field_length; + + if (!io_buf_read_varint(buf, &field_length)) + return false; + + int32_t i32_val; + int64_t i64_val; + char *str_val; + switch (field_length) { + case sizeof(int32_t): + if (!io_buf_read_int32(buf, &i32_val)) + return false; + add_next_index_long(tuple, i32_val); + break; + case sizeof(int64_t): + if (!io_buf_read_int64(buf, &i64_val)) + return false; + add_next_index_long(tuple, i32_val); + break; + default: + if (!io_buf_read_str(buf, &str_val, field_length)) + return false; + add_next_index_stringl(tuple, str_val, field_length, true); + } + + return true; +} - int i=0; - int8_t * size; - while (i < fieldNo) { - size = (int8_t*) p; - p += *size+1; - i++; +static bool +io_buf_read_tuple(struct io_buf *buf, zval **tuple) +{ + MAKE_STD_ZVAL(*tuple); + if (array_init(*tuple) == FAILURE) { + return false; } - b2i* bb = (b2i*)(p+1); + int32_t size; + if (!io_buf_read_int32(buf, &size)) + return false; - efree(out_buf); - RETURN_LONG(bb->i); + int32_t cardinality; + if (!io_buf_read_int32(buf, &cardinality)) + return false; + while (cardinality > 0) { + if (!io_buf_read_field(buf, *tuple)) + return false; + cardinality -= 1; + } + return true; } -/* }}} */ +static void * +io_buf_write_struct(struct io_buf *buf, size_t n) +{ + if (!io_buf_reserve(buf, buf->size + n)) + return NULL; + void *ptr = buf->value + buf->size; + buf->size += n; + return ptr; +} -/* {{{ proto string tarantool::getError(); - returb tarantool error string */ +static bool +io_buf_write_byte(struct io_buf *buf, int8_t value) +{ + if (!io_buf_reserve(buf, buf->size + sizeof(int8_t))) + return false; + *(int8_t *)(buf->value + buf->size) = value; + buf->size += sizeof(uint8_t); + return true; +} -PHP_METHOD(tarantool_class, getError) +static bool +io_buf_write_int32(struct io_buf *buf, int32_t value) { - zval *id; - tarantool_object *ctx; + if (!io_buf_reserve(buf, buf->size + sizeof(int32_t))) + return false; + *(int32_t *)(buf->value + buf->size) = value; + buf->size += sizeof(int32_t); + return true; +} - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id, - tarantool_class_entry) == FAILURE) { - return; - } +static bool +io_buf_write_int64(struct io_buf *buf, int64_t value) +{ + if (!io_buf_reserve(buf, buf->size + sizeof(int64_t))) + return false; + *(int64_t *)(buf->value + buf->size) = value; + buf->size += sizeof(int64_t); + return true; +} - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } +static bool +io_buf_write_varint(struct io_buf *buf, int32_t value) +{ + if (!io_buf_reserve(buf, buf->size + 5)) + /* reseve maximal varint size (5 bytes) */ + return false; - switch(ctx->errorcode) { - case 0: { RETURN_STRING("Result Ok",1); break; } - case 258: {RETURN_STRING("Non master connection, but it should be",1); break; } - case 514: {RETURN_STRING("Illegal parametrs",1); break; } - case 770: {RETURN_STRING("Uid not from this storage range",1); break; } - case 1025: {RETURN_STRING("Node is marked as read-only",1); break; } - case 1281: {RETURN_STRING("Node isn't locked",1); break; } - case 1537: {RETURN_STRING("Node is locked",1); break; } - case 1793: {RETURN_STRING("Some memory issues",1); break; } - case 0x00000802: {RETURN_STRING("Bad graph integrity",1); break; } - case 0x00000a02: {RETURN_STRING("Unsupported command",1); break; } - case 0x00001801: {RETURN_STRING("Can't register new user",1); break; } - case 0x00001a01: {RETURN_STRING("Can't generate alert id",1); break; } - case 0x00001b02: {RETURN_STRING("Can't del node",1); break; } - case 0x00001c02: {RETURN_STRING("User isn't registered",1); break; } - case 0x00001d02: {RETURN_STRING("Syntax error in query",1); break; } - case 0x00001e02: {RETURN_STRING("Unknown field",1); break; } - case 0x00001f02: {RETURN_STRING("Number value is out of range",1); break; } - case 0x00002002: {RETURN_STRING("Insert already existing object",1); break; } - case 0x00002202: {RETURN_STRING("Can not order result",1); break; } - case 0x00002302: {RETURN_STRING("Multiple to update/delete",1); break; } - case 0x00002400: {RETURN_STRING("nothing to do (not an error)",1); break; } - case 0x00002502: {RETURN_STRING("id's update",1); break; } - case 0x00002602: {RETURN_STRING("unsupported version of protocol",1); break; } - - case 0x00002702: {RETURN_STRING("Unknow error",1); break; } - case 0x00003102: {RETURN_STRING("Node not found",1); break; } - - case 0x00003702: {RETURN_STRING("Node found",1); break; } - case 0x00003802: {RETURN_STRING("INDEX violation",1); break; } - case 0x00003902: {RETURN_STRING("No such space",1); break; } - - default : { - char * err_string; - int len = spprintf(&err_string, 0, "Unknow error code : %X\n", ctx->errorcode); - RETVAL_STRINGL(err_string, len,1); - efree(err_string); + if (value >= (1 << 7)) { + if (value >= (1 << 14)) { + if (value >= (1 << 21)) { + if (value >= (1 << 28)) + io_buf_write_byte(buf, (int8_t)(value >> 28) | 0x80); + io_buf_write_byte(buf, (int8_t)(value >> 21) | 0x80); } + io_buf_write_byte(buf, (int8_t)((value >> 14) | 0x80)); + } + io_buf_write_byte(buf, (int8_t)((value >> 7) | 0x80)); } + io_buf_write_byte(buf, (int8_t)((value) & 0x7F)); + return true; } -/* }}}*/ - -/* {{{ proto string tarantool::getInfo(); - return tarantool info string */ -PHP_METHOD(tarantool_class, getInfo) +static bool +io_buf_write_str(struct io_buf *buf, uint8_t *str, size_t len) { - zval *id; - tarantool_object *ctx; - size_t response_len=0; + if (!io_buf_reserve(buf, buf->size + len)) + return false; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id, - tarantool_class_entry) == FAILURE) { - return; - } - - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } - - if (!ctx->admin_stream) { - if (php_tnt_admin_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; - } - } + memcpy(buf->value + buf->size, str, len); + buf->size += len; + return true; +} - if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_INFO, TARANTOOL_SHOW_INFO_SIZE) != TARANTOOL_SHOW_INFO_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "Failed sending command" ,0 TSRMLS_DC); - return; - } +static bool +io_buf_write_field_int32(struct io_buf *buf, uint32_t value) +{ + /* write field length (4 bytes) */ + if (!io_buf_write_varint(buf, sizeof(int32_t))) + return false; + /* write field value */ + if (!io_buf_write_int32(buf, value)) + return false; + return true; +} - char * buf=emalloc(TARANTOOL_SMALL_BUFSIZE); - bzero(buf,TARANTOOL_SMALL_BUFSIZE); +static bool +io_buf_write_field_int64(struct io_buf *buf, uint64_t value) +{ + /* write field length (8 bytes) */ + if (!io_buf_write_varint(buf, sizeof(int64_t))) + return false; + /* write field value */ + if (!io_buf_write_int64(buf, value)) + return false; + return true; +} - response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_SMALL_BUFSIZE); +static bool +io_buf_write_field_str(struct io_buf *buf, uint8_t *field_value, size_t field_length) +{ + /* write field length (string length) */ + if (!io_buf_write_varint(buf, (int32_t)field_length)) + return false; + /* write field value (string) */ + if (!io_buf_write_str(buf, field_value, field_length)) + return false; + return true; +} - if (response_len) { - RETVAL_STRINGL(buf,response_len,1); - efree(buf); - return; +static bool +io_buf_write_tuple_int(struct io_buf *buf, zval *tuple) +{ + /* single field tuple: (int) */ + long long_value = Z_LVAL_P(tuple); + /* write tuple cardinality */ + if (!io_buf_write_int32(buf, 1)) + return false; + /* write field */ + if ((unsigned long)long_value <= 0xffffffffllu) { + if (!io_buf_write_field_int32(buf, (uint32_t)long_value)) + return false; + } else { + if (!io_buf_write_field_int64(buf, (uint64_t)long_value)) + return false; } - efree(buf); - RETURN_FALSE; - + return true; } -/* }}}*/ -/* {{{ proto string tarantool::getConf(); - returb tarantool info string */ -PHP_METHOD(tarantool_class, getConf) +static bool +io_buf_write_tuple_str(struct io_buf *buf, zval *tuple) { - zval *id; - tarantool_object *ctx; - size_t response_len=0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id, - tarantool_class_entry) == FAILURE) { - return; - } - - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } + /* single field tuple: (string) */ + char *str_value = Z_STRVAL_P(tuple); + size_t str_length = Z_STRLEN_P(tuple); + /* write tuple cardinality */ + if (!io_buf_write_int32(buf, 1)) + return false; + /* write field */ + if (!io_buf_write_field_str(buf, str_value, str_length)) + return false; + + return true; +} - if (!ctx->admin_stream) { - if (php_tnt_admin_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; +static bool +io_buf_write_tuple_array(struct io_buf *buf, zval *tuple) +{ + /* multyply tuple array */ + HashTable *hash = Z_ARRVAL_P(tuple); + HashPosition itr; + zval **field; + /* put tuple cardinality */ + io_buf_write_int32(buf, zend_hash_num_elements(hash)); + for (zend_hash_internal_pointer_reset_ex(hash, &itr); + zend_hash_get_current_data_ex(hash, (void **) &field, &itr) == SUCCESS; + zend_hash_move_forward_ex(hash, &itr)) { + char *str_value; + size_t str_length; + long long_value; + + switch (Z_TYPE_PP(field)) { + case IS_STRING: + /* string field */ + str_value = Z_STRVAL_PP(field); + str_length = Z_STRLEN_PP(field); + io_buf_write_field_str(buf, str_value, str_length); + break; + case IS_LONG: + /* integer field */ + long_value = Z_LVAL_PP(field); + io_buf_write_field_str(buf, (uint8_t *)&long_value, sizeof(int32_t)); + break; + default: + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "unsupported field type"); + return false; } } - if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_CONF, TARANTOOL_SHOW_CONF_SIZE) != TARANTOOL_SHOW_CONF_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "Failed sending command" ,0 TSRMLS_DC); - return; - } - - char * buf = emalloc(TARANTOOL_BUFSIZE); - response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_BUFSIZE); - - if (response_len) { - RETVAL_STRINGL(buf,response_len,1); - efree(buf); - return; - } - - efree(buf); - RETURN_FALSE; - + return true; } -/* }}}*/ -/* {{{ proto string tarantool::getStat(); - returb tarantool statistic string */ -PHP_METHOD(tarantool_class, getStat) +static bool +io_buf_write_tuple(struct io_buf *buf, zval *tuple) { - zval *id; - tarantool_object *ctx; - size_t response_len=0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id, - tarantool_class_entry) == FAILURE) { - return; - } - - ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC); - if (!ctx) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the context is null" ,0 TSRMLS_CC); - return; - } + /* write tuple by type */ + switch (Z_TYPE_P(tuple)) { + case IS_LONG: + /* write integer as tuple */ + return io_buf_write_tuple_int(buf, tuple); + case IS_STRING: + /* write string as tuple */ + return io_buf_write_tuple_str(buf, tuple); + case IS_ARRAY: + /* write array as tuple */ + return io_buf_write_tuple_array(buf, tuple); + default: + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "unsupported tuple type"); + return false; + } + + return true; +} - if (!ctx->admin_stream) { - if (php_tnt_admin_connect(ctx TSRMLS_CC)) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "the can't open remote host " ,0 TSRMLS_DC); - return; +static bool +io_buf_write_tuples_list_array(struct io_buf *buf, zval *tuples_list) +{ + HashTable *hash = Z_ARRVAL_P(tuples_list); + HashPosition itr; + zval **tuple; + + /* write number of tuples */ + if (!io_buf_write_int32(buf, zend_hash_num_elements(hash))) + return false; + + /* write tuples */ + for (zend_hash_internal_pointer_reset_ex(hash, &itr); + zend_hash_get_current_data_ex(hash, (void **) &tuple, &itr) == SUCCESS; + zend_hash_move_forward_ex(hash, &itr)) { + if (Z_TYPE_PP(tuple) != IS_ARRAY) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid tuples list: expected array of array"); + return false; } - } - if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_STAT, TARANTOOL_SHOW_STAT_SIZE) != TARANTOOL_SHOW_STAT_SIZE) { - zend_throw_exception(zend_exception_get_default(TSRMLS_C), - "Failed sending command" ,0 TSRMLS_DC); - return; + if (!io_buf_write_tuple_array(buf, *tuple)) + return false; } - char * buf = emalloc(TARANTOOL_SMALL_BUFSIZE); - bzero(buf,TARANTOOL_SMALL_BUFSIZE); - - response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_SMALL_BUFSIZE); + return true; +} - if (response_len) { - RETVAL_STRINGL(buf,response_len,1); - efree(buf); - return; - } - efree(buf); - RETURN_FALSE; +static bool +io_buf_write_tuples_list(struct io_buf *buf, zval *tuples_list) +{ + HashTable *hash; + HashPosition itr; + zval **tuple; + + switch (Z_TYPE_P(tuples_list)) { + case IS_LONG: + /* single tuple: long */ + /* write number of tuples */ + if (!io_buf_write_int32(buf, 1)) + return false; + /* write tuple */ + if (!io_buf_write_tuple_int(buf, tuples_list)) + return false; + break; + case IS_STRING: + /* single tuple: string */ + /* write number of tuples */ + if (!io_buf_write_int32(buf, 1)) + return false; + /* write tuple */ + if (!io_buf_write_tuple_str(buf, tuples_list)) + return false; + break; + case IS_ARRAY: + /* array: migth be single or multi tuples array */ + hash = Z_ARRVAL_P(tuples_list); + zend_hash_internal_pointer_reset_ex(hash, &itr); + if (zend_hash_get_current_data_ex(hash, (void **) &tuple, &itr) != SUCCESS) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "invalid tuples list: empty array"); + return false; + } -} -/* }}}*/ + /* check type of the first element */ + switch (Z_TYPE_PP(tuple)) { + case IS_STRING: + case IS_LONG: + /* single tuple: array */ + /* write tuples count */ + if (!io_buf_write_int32(buf, 1)) + return false; + /* write tuple */ + if (!io_buf_write_tuple_array(buf, tuples_list)) + return false; + break; + case IS_ARRAY: + /* multi tuples list */ + if (!io_buf_write_tuples_list_array(buf, tuples_list)) + return false; + break; + default: + /* invalid element type */ + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "unsupported tuple type"); + return false; + } -static void printLine( u_char *p ) { - u_char b[4]; - memcpy(b, p, 4); - php_printf("%x %x %x %x\t\t", b[0], b[1], b[2], b[3]); -} + break; + default: + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "unsupported tuple type"); + return false; + } -static void printLine3( u_char *p ) { - u_char b[12]; - memcpy(b, p, 12); - php_printf("%x %x %x %x\t\t", b[0], b[1], b[2], b[3]); - php_printf("%x %x %x %x\t\t", b[4], b[5], b[6], b[7]); - php_printf("%x %x %x %x\n", b[8], b[9], b[10], b[11]); + return true; } +/* + * I/O buffer send/recv + */ -static void -leb128_write(char * buf, unsigned long value) +static bool +io_buf_send_yaml(php_stream *stream, struct io_buf *buf) { - if (value >= (1 << 7)) { - if (value >= (1 << 14)) { - if (value >= (1 << 21)) { - if (value >= (1 << 28)) - *(buf++) = (value >> 28) | 0x80; - *(buf++) = (value >> 21) | 0x80; - } - *(buf++) = ((value >> 14) | 0x80); - } - *(buf++) = ((value >> 7) | 0x80); + if (php_stream_write(stream, + buf->value, + buf->size) != buf->size) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "send message fail"); + return false; } - *(buf++) = ((value) & 0x7F); + return true; } -static int -leb128_read(char * buf, int size, unsigned long * value) +static bool +io_buf_recv_yaml(php_stream *stream, struct io_buf *buf) { - *value = 0; - - if (size < 1) - return -1; - - if (!(buf[0] & 0x80)) { - - *value = buf[0] & 0x7f; - return 1; + char *line = php_stream_get_line(stream, NULL, 0, NULL); + while (strcmp(line, ADMIN_TOKEN_BEGIN) != 0) { + line = php_stream_get_line(stream, NULL, 0, NULL); } - if (size < 2) - return -1; - - if (!(buf[1] & 0x80)) { - - *value = (buf[0] & 0x7f) << 7 | - (buf[1] & 0x7f); - return 2; + line = php_stream_get_line(stream, NULL, 0, NULL); + while (strcmp(line, ADMIN_TOKEN_END) != 0) { + io_buf_write_str(buf, line, strlen(line)); + line = php_stream_get_line(stream, NULL, 0, NULL); } - if (size < 3) - return -1; + return true; +} - if (!(buf[2] & 0x80)) { +static bool +io_buf_send_iproto(php_stream *stream, int32_t type, int32_t request_id, struct io_buf *buf) +{ + /* send iproto header */ + struct iproto_header header; + header.type = type; + header.length = buf->size; + header.request_id = request_id; - *value = (buf[0] & 0x7f) << 14 | - (buf[1] & 0x7f) << 7 | - (buf[2] & 0x7f); - return 3; + size_t length = sizeof(struct iproto_header); + if (php_stream_write(stream, (char *) &header, length) != length) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "send requset failed"); + return false; } - if (size < 4) - return -1; - - if (!(buf[3] & 0x80)) { - - *value = (buf[0] & 0x7f) << 21 | - (buf[1] & 0x7f) << 14 | - (buf[2] & 0x7f) << 7 | - (buf[3] & 0x7f); - return 4; + /* send requets */ + if (php_stream_write(stream, buf->value, buf->size) != buf->size) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "send requset failed"); + return false; } - if (size < 5) - return -1; + return true; +} - if (!(buf[4] & 0x80)) { +static bool +io_buf_recv_iproto(php_stream *stream, struct io_buf *buf) +{ + /* receiving header */ + struct iproto_header header; + size_t length = sizeof(struct iproto_header); + if (php_stream_read(stream, (char *) &header, length) != length) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "receive response failed"); + return false; + } - *value = (buf[0] & 0x7f) << 28 | - (buf[1] & 0x7f) << 21 | - (buf[2] & 0x7f) << 14 | - (buf[3] & 0x7f) << 7 | - (buf[4] & 0x7f); - return 5; + /* receiving body */ + if (!io_buf_resize(buf, header.length)) + return false; + if (php_stream_read(stream, buf->value, buf->size) != buf->size) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "receive response failed"); + return false; } - return -1; + return true; } -static int -leb128_size(unsigned long value) -{ - if (value < (1 << 7)) - return 1; - - if (value < (1 << 14)) - return 2; +/*----------------------------------------------------------------------------* + * support local functions + *----------------------------------------------------------------------------*/ - if (value < (1 << 21)) - return 3; - - if (value < (1 << 28)) - return 4; +static zend_object_value +alloc_tarantool_object(zend_class_entry *entry TSRMLS_DC) +{ + zend_object_value new_value; - return 5; + /* allocate and clean-up instance */ + tarantool_object *tnt = (tarantool_object *) emalloc(sizeof(tarantool_object)); + /* TODO: emalloc result must be checked */ + memset(tnt, 0, sizeof(tarantool_object)); + + /* initialize class instance */ + zend_object_std_init(&tnt->zo, entry TSRMLS_CC); + new_value.handle = zend_objects_store_put( + tnt, + (zend_objects_store_dtor_t) zend_objects_destroy_object, + (zend_objects_free_object_storage_t) free_tarantool_object, + NULL TSRMLS_CC); + new_value.handlers = zend_get_std_object_handlers(); + return new_value; } static void -tarantool_dtor(void *object TSRMLS_DC) +free_tarantool_object(tarantool_object *tnt TSRMLS_DC) { - tarantool_object *ctx = (tarantool_object*)object; - - if (ctx) { - if (ctx->stream) { - php_stream_close(ctx->stream); - } - - if (ctx->admin_stream) { - php_stream_close(ctx->admin_stream); - } - - if (ctx->host) { - efree(ctx->host); - } - - } + if (tnt == NULL) + return; - zend_object_std_dtor(&ctx->zo TSRMLS_CC); + if (tnt->stream) + php_stream_close(tnt->stream); - efree(object); + if (tnt->admin_stream) + php_stream_close(tnt->admin_stream); + io_buf_destroy(tnt->io_buf); + io_buf_destroy(tnt->splice_field); + efree(tnt); } -static zend_object_value tarantool_ctor(zend_class_entry *ce TSRMLS_DC) +static php_stream * +establish_connection(char *host, int port) { - zend_object_value new_value; - tarantool_object* obj = (tarantool_object*)emalloc(sizeof(tarantool_object)); - memset(obj, 0, sizeof(tarantool_object)); - - zend_object_std_init(&obj->zo, ce TSRMLS_CC); - - new_value.handle = zend_objects_store_put(obj, (zend_objects_store_dtor_t)zend_objects_destroy_object, - (zend_objects_free_object_storage_t)tarantool_dtor, NULL TSRMLS_CC); - new_value.handlers = zend_get_std_object_handlers(); - - return new_value; + char *msg = NULL; + /* initialize connection parameters */ + char *dest_addr = NULL; + size_t dest_addr_len = spprintf(&dest_addr, 0, "tcp://%s:%d", host, port); + int options = ENFORCE_SAFE_MODE | REPORT_ERRORS; + int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT; + struct timeval timeout = { + .tv_sec = TARANTOOL_TIMEOUT_SEC, + .tv_usec = TARANTOOL_TIMEOUT_USEC, + }; + char *error_msg = NULL; + int error_code = 0; + + /* establish connection */ + php_stream *stream = php_stream_xport_create(dest_addr, dest_addr_len, + options, flags, + NULL, &timeout, NULL, + &error_msg, &error_code); + efree(dest_addr); + + /* check result */ + if (error_code && error_msg) { + zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC, + "establist connection fail: %s", error_msg); + efree(error_msg); + return NULL; + } + + return stream; } -/* {{{ PHP_MINIT_FUNCTION - */ -PHP_MINIT_FUNCTION(tarantool) +static bool +hash_fing_long(HashTable *hash, char *key, long *value) { - zend_class_entry ce; - - INIT_CLASS_ENTRY(ce, "Tarantool", tarantool_class_functions); - ce.create_object = tarantool_ctor; - tarantool_class_entry = zend_register_internal_class(&ce TSRMLS_CC); - - return SUCCESS; + zval **zvalue = NULL; + if (zend_hash_find(hash, key, strlen(key) + 1, (void **)&zvalue) != SUCCESS) + return false; + if (Z_TYPE_PP(zvalue) != IS_LONG) + return false; + *value = Z_LVAL_PP(zvalue); + return true; } -/* }}} */ -/* {{{ PHP_MSHUTDOWN_FUNCTION - */ -PHP_MSHUTDOWN_FUNCTION(tarantool) +static bool +hash_fing_str(HashTable *hash, char *key, char **value, int *value_length) { - /* uncomment this line if you have INI entries - UNREGISTER_INI_ENTRIES(); - */ - return SUCCESS; + zval **zvalue = NULL; + if (zend_hash_find(hash, key, strlen(key) + 1, (void **)&zvalue) != SUCCESS) + return false; + if (Z_TYPE_PP(zvalue) != IS_STRING) + return false; + *value = Z_STRVAL_PP(zvalue); + *value_length = Z_STRLEN_PP(zvalue); + return true; } -/* }}} */ - -/* {{{ PHP_MINFO_FUNCTION - */ -PHP_MINFO_FUNCTION(tarantool) +static bool +hash_fing_scalar(HashTable *hash, char *key, zval ***value) { - php_info_print_table_start(); - php_info_print_table_header(2, "tarantool_box support", "enabled"); - php_info_print_table_row(2, "default host", TARANTOOL_DEF_HOST); - php_info_print_table_row(2, "default port", TARANTOOL_DEF_PORT); - php_info_print_table_row(2, "admin port", TARANTOOL_ADMIN_PORT); - php_info_print_table_row(2, "timeout in sec",TARANTOOL_TIMEOUT); - php_info_print_table_end(); - - /* Remove comments if you have entries in php.ini - DISPLAY_INI_ENTRIES(); - */ + if (zend_hash_find(hash, key, strlen(key) + 1, (void **)value) != SUCCESS) + return false; + if (Z_TYPE_PP(*value) != IS_STRING && Z_TYPE_PP(*value) != IS_LONG) + return false; + return true; } -/* }}} */ + /* * Local variables: diff --git a/connector/php/tarantool.h b/connector/php/tarantool.h index a05dfeacfd5f7549c81e1e98b28f41c6b3049a7b..74a5635185dd2db3dbf7abe6098f799cd31d74e1 100644 --- a/connector/php/tarantool.h +++ b/connector/php/tarantool.h @@ -16,79 +16,165 @@ | Copyright (c) 2011 | +----------------------------------------------------------------------+ */ - -/* $Id: header 252479 2008-02-07 19:39:50Z iliaa $ */ - #ifndef PHP_TARANTOOL_H #define PHP_TARANTOOL_H -extern zend_module_entry tarantool_module_entry; -#define phpext_tarantool_ptr &tarantool_module_entry - -#ifdef PHP_WIN32 -# define PHP_TARANTOOL_API __declspec(dllexport) -#elif defined(__GNUC__) && __GNUC__ >= 4 -# define PHP_TARANTOOL_API __attribute__ ((visibility("default"))) -#else -# define PHP_TARANTOOL_API +#ifdef ZTS +#include "TSRM.h" #endif -#define TARANTOOL_TIMEOUT 5 // sec -#define TARANTOOL_DEF_PORT 33013 -#define TARANTOOL_ADMIN_PORT 33015 -#define TARANTOOL_DEF_HOST "localhost" -#define TARANTOOL_BUFSIZE 2048 -#define TARANTOOL_SMALL_BUFSIZE 256 +/*============================================================================* + * Constants + *============================================================================*/ + +#define TARANTOOL_EXTENSION_VERSION "1.0" + + +/*----------------------------------------------------------------------------* + * tbuf constants + *----------------------------------------------------------------------------*/ + +enum { + /* tbuf minimal capacity */ + IO_BUF_CAPACITY_MIN = 128, + /* tbuf factor */ + IO_BUF_CAPACITY_FACTOR = 2, +}; + + +/*----------------------------------------------------------------------------* + * Connections constants + *----------------------------------------------------------------------------*/ + +enum { + /* timeout: seconds */ + TARANTOOL_TIMEOUT_SEC = 5, + /* timeout: microseconds */ + TARANTOOL_TIMEOUT_USEC = 0, + /* tarantool default primary port */ + TARANTOOL_DEFAULT_PORT = 33013, + /* tarantool default readonly port */ + TARANTOOL_DEFAULT_RO_PORT = 33014, + /* tarantool default adnim port */ + TARANTOOL_DEFAULT_ADMIN_PORT = 33015, +}; + +#define TARANTOOL_DEFAULT_HOST "localhost" + + +/*----------------------------------------------------------------------------* + * Commands constants + *----------------------------------------------------------------------------*/ + +/* tarantool/box flags */ +enum { + /* return resulting tuples */ + TARANTOOL_FLAGS_RETURN_TUPLE = 0x01, + /* insert is add operation: errro will be raised if tuple exists */ + TARANTOOL_FLAGS_ADD = 0x02, + /* insert is replace operation: errro will be raised if tuple doesn't exist */ + TARANTOOL_FLAGS_REPLACE = 0x04, + /* doesn't write command to WAL */ + TARANTOOL_FLAGS_NOT_STORE = 0x10, +}; + +/* tarantool command codes */ +enum { + /* insert/replace command code */ + TARANTOOL_COMMAND_INSERT = 13, + /* select command code */ + TARANTOOL_COMMAND_SELECT = 17, + /* update fields command code */ + TARANTOOL_COMMAND_UPDATE = 19, + /* delete command code */ + TARANTOOL_COMMAND_DELETE = 21, + /* call lua function command code */ + TARANTOOL_COMMAND_CALL = 22, + /* pid command code */ + TARANTOOL_COMMAND_PING = 65280, +}; + +/* update fields operation codes */ +enum { + /* update fields: assing field value operation code */ + TARANTOOL_OP_ASSIGN = 0, + /* update fields: add operation code */ + TARANTOOL_OP_ADD = 1, + /* update fields: and operation code */ + TARANTOOL_OP_AND = 2, + /* update fields: xor operation code */ + TARANTOOL_OP_XOR = 3, + /* update fields: or operation code */ + TARANTOOL_OP_OR = 4, + /* update fields: splice operation code */ + TARANTOOL_OP_SPLICE = 5, +}; + + +/*----------------------------------------------------------------------------* + * Amdin commands + *----------------------------------------------------------------------------*/ + +/* admin protocol separator */ +#define ADMIN_SEPARATOR "\r\n" +/* admin command begin token */ +#define ADMIN_TOKEN_BEGIN "---"ADMIN_SEPARATOR +/* admin command end token */ +#define ADMIN_TOKEN_END "..."ADMIN_SEPARATOR + +/* show information admin command */ +#define ADMIN_COMMAND_SHOW_INFO "show info" +/* show statistic admin command */ +#define ADMIN_COMMAND_SHOW_STAT "show stat" +/* show configuration admin command */ +#define ADMIN_COMMAND_SHOW_CONF "show configuration" + + +/*============================================================================* + * Interaface decalaration + *============================================================================*/ + + +/*----------------------------------------------------------------------------* + * Tarantool module interface + *----------------------------------------------------------------------------*/ + +/* initialize module function */ +PHP_MINIT_FUNCTION(tarantool); + +/* shutdown module function */ +PHP_MSHUTDOWN_FUNCTION(tarantool); -#define TARANTOOL_INSERT 13 -#define TARANTOOL_SELECT 17 -#define TARANTOOL_UPDATE 19 -#define TARANTOOL_DELETE 20 -#define TARANTOOL_CALL 22 -#define TARANTOOL_PING 65280 +/* show information about this module */ +PHP_MINFO_FUNCTION(tarantool); -#define TARANTOOL_REQUEST_ID 8 -#define TARANTOOL_OP_ASSIGN 0 -#define TARANTOOL_OP_ADD 1 -#define TARANTOOL_OP_AND 2 -#define TARANTOOL_OP_XOR 3 -#define TARANTOOL_OP_OR 4 +/*----------------------------------------------------------------------------* + * Tarantool class interface + *----------------------------------------------------------------------------*/ -#define TARANTOOL_SHOW_INFO "show info\r\n" -#define TARANTOOL_SHOW_INFO_SIZE sizeof(TARANTOOL_SHOW_INFO) +/* class constructor */ +PHP_METHOD(tarantool_class, __construct); -#define TARANTOOL_SHOW_STAT "show stat\r\n" -#define TARANTOOL_SHOW_STAT_SIZE sizeof(TARANTOOL_SHOW_STAT) +/* do select operation */ +PHP_METHOD(tarantool_class, select); -#define TARANTOOL_SHOW_CONF "show configuration\n" -#define TARANTOOL_SHOW_CONF_SIZE sizeof(TARANTOOL_SHOW_CONF) +/* do insert operation */ +PHP_METHOD(tarantool_class, insert); +/* do update fields operation */ +PHP_METHOD(tarantool_class, update_fields); -#ifdef ZTS -#include "TSRM.h" -#endif +/* do delete operation */ +PHP_METHOD(tarantool_class, delete); -PHP_MINIT_FUNCTION(tarantool); -PHP_MSHUTDOWN_FUNCTION(tarantool); +/* call lua funtion operation */ +PHP_METHOD(tarantool_class, call); -PHP_MINFO_FUNCTION(tarantool); +/* do admin command */ +PHP_METHOD(tarantool_class, admin); -PHP_METHOD( tarantool_class, __construct); - -PHP_METHOD( tarantool_class, insert); -PHP_METHOD( tarantool_class, select); -PHP_METHOD( tarantool_class, mselect); -PHP_METHOD( tarantool_class, call); -PHP_METHOD( tarantool_class, getTuple); -PHP_METHOD( tarantool_class, delete); -PHP_METHOD( tarantool_class, update); -PHP_METHOD( tarantool_class, inc); -PHP_METHOD( tarantool_class, getError); -PHP_METHOD( tarantool_class, getInfo); -PHP_METHOD( tarantool_class, getStat); -PHP_METHOD( tarantool_class, getConf); #ifdef ZTS #define TARANTOOL_G(v) TSRMG(tarantool_globals_id, zend_tarantool_globals *, v) @@ -96,8 +182,7 @@ PHP_METHOD( tarantool_class, getConf); #define TARANTOOL_G(v) (tarantool_globals.v) #endif -#endif -/* PHP_TARANTOOL_H */ +#endif /* PHP_TARANTOOL_H */ /* * Local variables: diff --git a/connector/php/test/admin.phpt b/connector/php/test/admin.phpt new file mode 100644 index 0000000000000000000000000000000000000000..3880b11722b7b5733d216f001fd6a6955830893c --- /dev/null +++ b/connector/php/test/admin.phpt @@ -0,0 +1,92 @@ +--TEST-- +Tarantool/box administation commands test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); + +echo "---------- test begin ----------\n"; +echo "help\n"; +echo $tarantool->admin("help"); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "insert\n"; +for ($i = 0; $i < 10; ++$i) + echo $tarantool->admin("lua box.insert(0, $i, 'test_id1', 'test field #$i')"); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "select\n"; +echo $tarantool->admin("lua box.select(0, 1, 'test_id1')"); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "delete\n"; +for ($i = 0; $i < 10; ++$i) + echo $tarantool->admin("lua box.delete(0, $i)"); +echo "----------- test end -----------\n\n"; +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +help +available commands: + - help + - exit + - show info + - show fiber + - show configuration + - show slab + - show palloc + - show stat + - save coredump + - save snapshot + - lua command + - reload configuration +----------- test end ----------- + +---------- test begin ---------- +insert + - 0: {'test_id1', 'test field #0'} + - 1: {'test_id1', 'test field #1'} + - 2: {'test_id1', 'test field #2'} + - 3: {'test_id1', 'test field #3'} + - 4: {'test_id1', 'test field #4'} + - 5: {'test_id1', 'test field #5'} + - 6: {'test_id1', 'test field #6'} + - 7: {'test_id1', 'test field #7'} + - 8: {'test_id1', 'test field #8'} + - 9: {'test_id1', 'test field #9'} +----------- test end ----------- + +---------- test begin ---------- +select + - 0: {'test_id1', 'test field #0'} + - 1: {'test_id1', 'test field #1'} + - 2: {'test_id1', 'test field #2'} + - 3: {'test_id1', 'test field #3'} + - 4: {'test_id1', 'test field #4'} + - 5: {'test_id1', 'test field #5'} + - 6: {'test_id1', 'test field #6'} + - 7: {'test_id1', 'test field #7'} + - 8: {'test_id1', 'test field #8'} + - 9: {'test_id1', 'test field #9'} +----------- test end ----------- + +---------- test begin ---------- +delete + - 0: {'test_id1', 'test field #0'} + - 1: {'test_id1', 'test field #1'} + - 2: {'test_id1', 'test field #2'} + - 3: {'test_id1', 'test field #3'} + - 4: {'test_id1', 'test field #4'} + - 5: {'test_id1', 'test field #5'} + - 6: {'test_id1', 'test field #6'} + - 7: {'test_id1', 'test field #7'} + - 8: {'test_id1', 'test field #8'} + - 9: {'test_id1', 'test field #9'} +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/php/test/bin/run-test.bash b/connector/php/test/bin/run-test.bash new file mode 100755 index 0000000000000000000000000000000000000000..59b262d82b1c1da7f376fd9e0d16083e50e4e83a --- /dev/null +++ b/connector/php/test/bin/run-test.bash @@ -0,0 +1,206 @@ +#!/bin/bash +#==============================================================================# +# PHP tarantool test suite runner +#==============================================================================# + +#------------------------------------------------------------------------------# +# Constants +#------------------------------------------------------------------------------# + +# Success constant +SUCCESS=0 +# Failure constant +FAILURE=1 + +# test runner etc directory +TEST_RUNNER_ETC_DIR="etc" +# test runner var directory +TEST_RUNNER_VAR_DIR="var" +# test runner var directory +TEST_RUNNER_LIB_DIR="lib" + +# php.ini file +PHP_INI="$TEST_RUNNER_ETC_DIR/php.ini" + +# Tarantool/box binary file name +TARANTOOL_BOX_BIN="tarantool_box" +# Tarantool/box configuration file +TARANTOOL_BOX_CFG="$TEST_RUNNER_ETC_DIR/tarantool_box.cfg" +# Tarantool/box pid file +TARANTOOL_BOX_PID="$TEST_RUNNER_VAR_DIR/tarantool_box.pid" + + +#------------------------------------------------------------------------------# +# Suite runner functions +#------------------------------------------------------------------------------# + +# initialize suite +function init_suite() +{ + mkdir -p $TEST_RUNNER_VAR_DIR + + # check TARANTOOL_HOME variable + if [ ! -z $TARANTOOL_HOME ]; then + # Use user-defined path + tarantool_bin="$TARANTOOL_HOME/$TARANTOOL_BOX_BIN" + else + # try to find by standard paths + tarantool_bin="$TARANTOOL_BOX_BIN" + fi + + # check binary + if ! which $tarantool_bin > /dev/null 2>&1; then + echo "can't found Tarantool/Box binary file" + exit $FAILURE + fi + + # check pear + if ! which pear > /dev/null 2>&1; then + echo "can't found pear" + exit $FAILURE + fi + + # check tarantool module library + if [ ! -f ../modules/tarantool.so ]; then + echo "can't found tarantool module library" + exit $FAILURE + fi + if [ -f $TEST_RUNNER_LIB_DIR/tarantool.so ]; then + rm $TEST_RUNNER_LIB_DIR/tarantool.so + fi + ln -s ../../modules/tarantool.so $TEST_RUNNER_LIB_DIR + + if [ -f $TEST_RUNNER_VAR_DIR/init.lua ]; then + rm $TEST_RUNNER_VAR_DIR/init.lua + fi + ln -s ../$TEST_RUNNER_LIB_DIR/lua/sorted_array.lua $TEST_RUNNER_VAR_DIR/init.lua + + return $SUCCESS +} + +# initialize tarantool's storage +function tarantool_init_storage() +{ + $tarantool_bin --init-storage -c $TARANTOOL_BOX_CFG 1> /dev/null 2>&1 + return $SUCCESS +} + +# start tarantool +function tarantool_start() +{ + if [ -f $TARANTOOL_BOX_PID ]; then + tarantool_stop + tarantool_cleanup + fi + + # run tarantool to background + $tarantool_bin -c $TARANTOOL_BOX_CFG & + # wait pid file + for i in {1..500}; do + if [ -f $TARANTOOL_BOX_PID ]; then + break + fi + sleep 0.01 + done + + if [ ! -f $TARANTOOL_BOX_PID ]; then + echo "error: can't start tarantool" + tarantool_cleanup + exit $FAILURE + fi + + return $SUCCESS +} + +# stop tarantool +function tarantool_stop() +{ + if [ ! -f $TARANTOOL_BOX_PID ]; then + return $SUCCESS + fi + + # get tarantool pid form pid file + pid=`cat $TARANTOOL_BOX_PID` + # kill process via SIGTERM + kill -TERM $pid 1> /dev/null 2>&1 + + for i in {1..500}; do + if [ ! -f $TARANTOOL_BOX_PID ]; then + # tarantool successfully stopped + return $SUCCESS + fi + sleep 0.01 + done + + if [ -f $TARANTOOL_BOX_PID ]; then + kill -KILL $pid 1> /dev/null 2>&1 + fi + + return $SUCCESS +} + +# clean-up tarantool +function tarantool_cleanup() +{ + # delete pid + rm -f $TEST_RUNNER_VAR_DIR/*.pid + # delete xlogs + rm -f $TEST_RUNNER_VAR_DIR/*.xlog + # delete snaps + rm -f $TEST_RUNNER_VAR_DIR/*.snap +} + + +#------------------------------------------------------------------------------# +# run test scrip body +#------------------------------------------------------------------------------# + +# +# initialize +# + +printf "initialize tarantool ... " +# initializing suite +init_suite +# initializing storage +tarantool_init_storage +printf "done\n" + +printf "starting tarantool ... " +# start tarantool +tarantool_start +printf "done\n" + + +# +# run +# + +printf "\n" +printf "================================= PHP test ===============================\n" + +# running pear's regression test scrips +PHPRC=$PHP_INI pear run-tests $1 + +printf "==========================================================================\n" +printf "\n" + +# +# stop & clean-up +# + +printf "stopping tarantool ... " +# stop tarantool +tarantool_stop +# clean-up tarantool +tarantool_cleanup +printf "done\n" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: noet sw=4 ts=4 fdm=marker +# vim<600: noet sw=4 ts=4 +# diff --git a/connector/php/test/call.phpt b/connector/php/test/call.phpt new file mode 100644 index 0000000000000000000000000000000000000000..08640f9e1fe500ad97f13d2a14bfb9ff800128f7 --- /dev/null +++ b/connector/php/test/call.phpt @@ -0,0 +1,857 @@ +--TEST-- +Tarantool/box call commands test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); +test_init($tarantool, 0); + +echo "---------- test begin ----------\n"; +echo "test call: myselect by primary index\n"; +test_call($tarantool, "box.select", array(0, 0, 2), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: call undefined function (expected error exception)\n"; +test_call($tarantool, "fafagaga", array("fafa-gaga", "foo", "bar"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_insert to key_1\n"; +echo "sa_insert('1', 'key_1', '10')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "10"), 0); +echo "sa_insert('1', 'key_1', '11')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "11"), 0); +echo "sa_insert('1', 'key_1', '15')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "15"), 0); +echo "sa_insert('1', 'key_1', '101')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "101"), 0); +echo "sa_insert('1', 'key_1', '511')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "511"), 0); +echo "sa_insert('1', 'key_1', '16')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "16"), 0); +echo "sa_insert('1', 'key_1', '42')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_1", "42"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_select from key_1\n"; +echo "sa_select('1', 'key_1', '101', '3')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "3"), 0); +echo "sa_select('1', 'key_1', '101', '2')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "2"), 0); +echo "sa_select('1', 'key_1', '511', '4')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "511", "4"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_delete from key_1\n"; +echo "sa_delete('1', 'key_1', '11', '101', '511')\n"; +test_call($tarantool, "box.sa_delete", array("1", "key_1", "11", "101", "511"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_select from key_1\n"; +echo "sa_select('1', 'key_1', '101', '3')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "3"), 0); +echo "sa_select('1', 'key_1', '101', '2')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "2"), 0); +echo "sa_select('1', 'key_1', '511', '4')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_1", "511", "4"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_insert to key_2\n"; +echo "sa_insert('1', 'key_2', '10')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "10"), 0); +echo "sa_insert('1', 'key_2', '8')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "8"), 0); +echo "sa_insert('1', 'key_2', '500')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "500"), 0); +echo "sa_insert('1', 'key_2', '166')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "166"), 0); +echo "sa_insert('1', 'key_2', '233')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "233"), 0); +echo "sa_insert('1', 'key_2', '357')\n"; +test_call($tarantool, "box.sa_insert", array("1", "key_2", "357"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_select from key_2\n"; +echo "sa_select('1', 'key_2', '500', '100')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_2", "500", "100"), 0); +echo "sa_select('1', 'key_2', '18', '15')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_2", "18", "15"), 0); +echo "sa_select('1', 'key_2', '18', '1')\n"; +test_call($tarantool, "box.sa_select", array("1", "key_2", "18", "1"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_merge key_1 and key_2\n"; +echo "sa_merge('1', 'key_1', 'key_2')\n"; +test_call($tarantool, "box.sa_merge", array("1", "key_1", "key_2"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test call: sa_delete from key_1\n"; +echo "sa_delete('1', 'key_1', '42')\n"; +test_call($tarantool, "box.sa_delete", array("1", "key_1", "42"), 0); +echo "sa_delete('1', 'key_1', '16')\n"; +test_call($tarantool, "box.sa_delete", array("1", "key_1", "16"), 0); +echo "sa_delete('1', 'key_1', '10')\n"; +test_call($tarantool, "box.sa_delete", array("1", "key_1", "10"), 0); +echo "sa_delete('1', 'key_1', '15')\n"; +test_call($tarantool, "box.sa_delete", array("1", "key_1", "15"), 0); +echo "----------- test end -----------\n\n"; + + +test_clean($tarantool, 0); +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +test call: myselect by primary index +result: +array(2) { + ["count"]=> + int(1) + ["tuples_list"]=> + array(1) { + [0]=> + array(6) { + [0]=> + int(2) + [1]=> + string(9) "Star Wars" + [2]=> + int(1983) + [3]=> + string(18) "Return of the Jedi" + [4]=> + string(460) "Luke Skywalker has returned +to his home planet of +Tatooine in an attempt +to rescue his friend +Han Solo from the +clutches of the vile +gangster Jabba the Hutt. + +Little does Luke know +that the GALACTIC EMPIRE +has secretly begun construction +on a new armored space station +even more powerful than the +first dreaded Death Star. + +When completed, this ultimate +weapon will spell certain +doom for the small band of +rebels struggling to restore +freedom to the galaxy..." + [5]=> + int(-1091633149) + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: call undefined function (expected error exception) +catched exception: call failed: 12802(0x00003202): Procedure 'fafagaga' is not defined +----------- test end ----------- + +---------- test begin ---------- +test call: sa_insert to key_1 +sa_insert('1', 'key_1', '10') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(1) { + [0]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '11') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(2) { + [0]=> + string(2) "11" + [1]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '15') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(3) { + [0]=> + string(2) "15" + [1]=> + string(2) "11" + [2]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '101') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(4) { + [0]=> + string(3) "101" + [1]=> + string(2) "15" + [2]=> + string(2) "11" + [3]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '511') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(5) { + [0]=> + string(3) "511" + [1]=> + string(3) "101" + [2]=> + string(2) "15" + [3]=> + string(2) "11" + [4]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '16') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(6) { + [0]=> + string(3) "511" + [1]=> + string(3) "101" + [2]=> + string(2) "16" + [3]=> + string(2) "15" + [4]=> + string(2) "11" + [5]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_1', '42') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(7) { + [0]=> + string(3) "511" + [1]=> + string(3) "101" + [2]=> + string(2) "42" + [3]=> + string(2) "16" + [4]=> + string(2) "15" + [5]=> + string(2) "11" + [6]=> + string(2) "10" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_select from key_1 +sa_select('1', 'key_1', '101', '3') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(3) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + [2]=> + string(2) "15" + } + } +} +sa_select('1', 'key_1', '101', '2') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(2) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + } + } +} +sa_select('1', 'key_1', '511', '4') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(4) { + [0]=> + string(3) "101" + [1]=> + string(2) "42" + [2]=> + string(2) "16" + [3]=> + string(2) "15" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_delete from key_1 +sa_delete('1', 'key_1', '11', '101', '511') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(4) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + [2]=> + string(2) "15" + [3]=> + string(2) "10" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_select from key_1 +sa_select('1', 'key_1', '101', '3') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(3) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + [2]=> + string(2) "15" + } + } +} +sa_select('1', 'key_1', '101', '2') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(2) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + } + } +} +sa_select('1', 'key_1', '511', '4') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(4) { + [0]=> + string(2) "42" + [1]=> + string(2) "16" + [2]=> + string(2) "15" + [3]=> + string(2) "10" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_insert to key_2 +sa_insert('1', 'key_2', '10') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(1) { + [0]=> + string(2) "10" + } + } +} +sa_insert('1', 'key_2', '8') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(2) { + [0]=> + string(2) "10" + [1]=> + string(1) "8" + } + } +} +sa_insert('1', 'key_2', '500') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(3) { + [0]=> + string(3) "500" + [1]=> + string(2) "10" + [2]=> + string(1) "8" + } + } +} +sa_insert('1', 'key_2', '166') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(4) { + [0]=> + string(3) "500" + [1]=> + string(3) "166" + [2]=> + string(2) "10" + [3]=> + string(1) "8" + } + } +} +sa_insert('1', 'key_2', '233') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(5) { + [0]=> + string(3) "500" + [1]=> + string(3) "233" + [2]=> + string(3) "166" + [3]=> + string(2) "10" + [4]=> + string(1) "8" + } + } +} +sa_insert('1', 'key_2', '357') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(6) { + [0]=> + string(3) "500" + [1]=> + string(3) "357" + [2]=> + string(3) "233" + [3]=> + string(3) "166" + [4]=> + string(2) "10" + [5]=> + string(1) "8" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_select from key_2 +sa_select('1', 'key_2', '500', '100') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(5) { + [0]=> + string(3) "357" + [1]=> + string(3) "233" + [2]=> + string(3) "166" + [3]=> + string(2) "10" + [4]=> + string(1) "8" + } + } +} +sa_select('1', 'key_2', '18', '15') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(2) { + [0]=> + string(2) "10" + [1]=> + string(1) "8" + } + } +} +sa_select('1', 'key_2', '18', '1') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_2" + } + [1]=> + array(1) { + [0]=> + string(2) "10" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_merge key_1 and key_2 +sa_merge('1', 'key_1', 'key_2') +result: +array(2) { + ["count"]=> + int(1) + ["tuples_list"]=> + array(1) { + [0]=> + array(10) { + [0]=> + string(3) "500" + [1]=> + string(3) "357" + [2]=> + string(3) "233" + [3]=> + string(3) "166" + [4]=> + string(2) "42" + [5]=> + string(2) "16" + [6]=> + string(2) "15" + [7]=> + string(2) "10" + [8]=> + string(2) "10" + [9]=> + string(1) "8" + } + } +} +----------- test end ----------- + +---------- test begin ---------- +test call: sa_delete from key_1 +sa_delete('1', 'key_1', '42') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(3) { + [0]=> + string(2) "16" + [1]=> + string(2) "15" + [2]=> + string(2) "10" + } + } +} +sa_delete('1', 'key_1', '16') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(2) { + [0]=> + string(2) "15" + [1]=> + string(2) "10" + } + } +} +sa_delete('1', 'key_1', '10') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(1) { + [0]=> + string(2) "15" + } + } +} +sa_delete('1', 'key_1', '15') +result: +array(2) { + ["count"]=> + int(2) + ["tuples_list"]=> + array(2) { + [0]=> + array(1) { + [0]=> + string(5) "key_1" + } + [1]=> + array(0) { + } + } +} +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/php/test/delete.phpt b/connector/php/test/delete.phpt new file mode 100644 index 0000000000000000000000000000000000000000..e9b68a235a6bf839e45b5b874fd9d04c98e0f4a6 --- /dev/null +++ b/connector/php/test/delete.phpt @@ -0,0 +1,138 @@ +--TEST-- +Tarantool/box delete commands test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); + +test_init($tarantool, 0); + +echo "---------- test begin ----------\n"; +echo "test delete: invalid key (expected error exception)\n"; +test_delete($tarantool, 0, $tarantool, 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: invalid key (expected error exception)\n"; +test_delete($tarantool, 0, array($tarantool), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: invalid key (expected error exception)\n"; +test_delete($tarantool, 0, array(1, 2), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: delete key as interger\n"; +test_delete($tarantool, 0, 0, TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: delete key as array\n"; +test_delete($tarantool, 0, array(1), TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: delete key (tuple doesn't return)\n"; +test_delete($tarantool, 0, 2, 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test delete: delete not existing tuple w/ return tuple flag\n"; +test_delete($tarantool, 0, 5, TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +test_clean($tarantool, 0); +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +test delete: invalid key (expected error exception) +catched exception: unsupported tuple type +----------- test end ----------- + +---------- test begin ---------- +test delete: invalid key (expected error exception) +catched exception: unsupported field type +----------- test end ----------- + +---------- test begin ---------- +test delete: invalid key (expected error exception) +catched exception: delete failed: 514(0x00000202): Illegal parameters, key must be single valued +----------- test end ----------- + +---------- test begin ---------- +test delete: delete key as interger +result: +count = 1 +tuple: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +----------- test end ----------- + +---------- test begin ---------- +test delete: delete key as array +result: +count = 1 +tuple: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +----------- test end ----------- + +---------- test begin ---------- +test delete: delete key (tuple doesn't return) +result: +count = 1 +----------- test end ----------- + +---------- test begin ---------- +test delete: delete not existing tuple w/ return tuple flag +result: +count = 0 +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/php/test/errors.phpt b/connector/php/test/errors.phpt new file mode 100644 index 0000000000000000000000000000000000000000..2450b3efb6e53af297665d252a057e04888ab89d --- /dev/null +++ b/connector/php/test/errors.phpt @@ -0,0 +1,26 @@ +--TEST-- +Tarantool/box error commands test +--FILE-- +<?php + +try +{ + $tarantool = new Tarantool("localhost", -33013, 0); + echo "error: the exception didn't raise\n"; +} catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; +} + +try { + $tarantool = new Tarantool("localhost", 33013, 65537); + echo "error: the exception didn't raise\n"; +} catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; +} + +?> +===DONE=== +--EXPECT-- +catched exception: invalid primary port value: -33013 +catched exception: invalid admin port value: 65537 +===DONE=== diff --git a/connector/php/test/etc/php.ini b/connector/php/test/etc/php.ini new file mode 100644 index 0000000000000000000000000000000000000000..ae7b2ee849ad7c3d954530eedf871c5192769f1b --- /dev/null +++ b/connector/php/test/etc/php.ini @@ -0,0 +1,1851 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and Lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it's +; much more verbose when it comes to errors. We recommending using the +; development version only in development environments as errors shown to +; application users can inadvertently leak otherwise secure information. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; allow_call_time_pass_reference +; Default Value: On +; Development Value: Off +; Production Value: Off + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED + +; html_errors +; Default Value: On +; Development Value: On +; Production value: Off + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; magic_quotes_gpc +; Default Value: On +; Development Value: Off +; Production Value: Off + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; register_long_arrays +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.bug_compat_42 +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.bug_compat_warn +; Default Value: On +; Development Value: On +; Production Value: Off + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.hash_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; url_rewriter.tags +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; <? and ?> tags as PHP source which should be processed as such. It's been +; recommended for several years that you not use the short tag "short cut" and +; instead to use the full <?php and ?> tag combination. With the wide spread use +; of XML and use of these tags by other languages, the server can become easily +; confused and end up parsing the wrong code in the wrong context. But because +; this short cut has been a feature for such a long time, it's currently still +; supported for backwards compatibility, but we recommend you don't use them. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/short-open-tag +short_open_tag = On + +; Allow ASP-style <% %> tags. +; http://php.net/asp-tags +asp_tags = Off + +; The number of significant digits displayed in floating point numbers. +; http://php.net/precision +precision = 14 + +; Enforce year 2000 compliance (will cause problems with non-compliant browsers) +; http://php.net/y2k-compliance +y2k_compliance = On + +; Output buffering is a mechanism for controlling how much output data +; (excluding headers and cookies) PHP should keep internally before pushing that +; data to the client. If your application's output exceeds this setting, PHP +; will send that data in chunks of roughly the size you specify. +; Turning on this setting and managing its maximum buffer size can yield some +; interesting side-effects depending on your application and web server. +; You may be able to send headers and cookies after you've already sent output +; through print or echo. You also may see performance benefits if your server is +; emitting less packets due to buffered output versus PHP streaming the output +; as it gets it. On production servers, 4096 bytes is a good setting for performance +; reasons. +; Note: Output buffering can also be controlled via Output Buffering Control +; functions. +; Possible Values: +; On = Enabled and buffer is unlimited. (Use with caution) +; Off = Disabled +; Integer = Enables the buffer and sets its maximum size in bytes. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 +; http://php.net/output-buffering +output_buffering = 4096 + +; You can redirect all of the output of your scripts to a function. For +; example, if you set output_handler to "mb_output_handler", character +; encoding will be transparently converted to the specified encoding. +; Setting any output handler automatically turns on output buffering. +; Note: People who wrote portable scripts should not depend on this ini +; directive. Instead, explicitly set the output handler using ob_start(). +; Using this ini directive may cause problems unless you know what script +; is doing. +; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler" +; and you cannot use both "ob_gzhandler" and "zlib.output_compression". +; Note: output_handler must be empty if this is set 'On' !!!! +; Instead you must use zlib.output_handler. +; http://php.net/output-handler +;output_handler = + +; Transparent output compression using the zlib library +; Valid values for this option are 'off', 'on', or a specific buffer size +; to be used for compression (default is 4KB) +; Note: Resulting chunk size may vary due to nature of compression. PHP +; outputs chunks that are few hundreds bytes each as a result of +; compression. If you prefer a larger chunk size for better +; performance, enable output_buffering in addition. +; Note: You need to use zlib.output_handler instead of the standard +; output_handler, or otherwise the output will be corrupted. +; http://php.net/zlib.output-compression +zlib.output_compression = Off + +; http://php.net/zlib.output-compression-level +;zlib.output_compression_level = -1 + +; You cannot specify additional output handlers if zlib.output_compression +; is activated here. This setting does the same as output_handler but in +; a different order. +; http://php.net/zlib.output-handler +;zlib.output_handler = + +; Implicit flush tells PHP to tell the output layer to flush itself +; automatically after every output block. This is equivalent to calling the +; PHP function flush() after each and every call to print() or echo() and each +; and every HTML block. Turning this option on has serious performance +; implications and is generally recommended for debugging purposes only. +; http://php.net/implicit-flush +; Note: This directive is hardcoded to On for the CLI SAPI +implicit_flush = Off + +; The unserialize callback function will be called (with the undefined class' +; name as parameter), if the unserializer finds an undefined class +; which should be instantiated. A warning appears if the specified function is +; not defined, or if the function doesn't include/implement the missing class. +; So only set this entry, if you really want to implement such a +; callback-function. +unserialize_callback_func = + +; When floats & doubles are serialized store serialize_precision significant +; digits after the floating point. The default value ensures that when floats +; are decoded with unserialize, the data will remain the same. +serialize_precision = 17 + +; This directive allows you to enable and disable warnings which PHP will issue +; if you pass a value by reference at function call time. Passing values by +; reference at function call time is a deprecated feature which will be removed +; from PHP at some point in the near future. The acceptable method for passing a +; value by reference to a function is by declaring the reference in the functions +; definition, not at call time. This directive does not disable this feature, it +; only determines whether PHP will warn you about it or not. These warnings +; should enabled in development environments only. +; Default Value: On (Suppress warnings) +; Development Value: Off (Issue warnings) +; Production Value: Off (Issue warnings) +; http://php.net/allow-call-time-pass-reference +allow_call_time_pass_reference = Off + +; Safe Mode +; http://php.net/safe-mode +safe_mode = Off + +; By default, Safe Mode does a UID compare check when +; opening files. If you want to relax this to a GID compare, +; then turn on safe_mode_gid. +; http://php.net/safe-mode-gid +safe_mode_gid = Off + +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +; http://php.net/safe-mode-include-dir +safe_mode_include_dir = + +; When safe_mode is on, only executables located in the safe_mode_exec_dir +; will be allowed to be executed via the exec family of functions. +; http://php.net/safe-mode-exec-dir +safe_mode_exec_dir = + +; Setting certain environment variables may be a potential security breach. +; This directive contains a comma-delimited list of prefixes. In Safe Mode, +; the user may only alter environment variables whose names begin with the +; prefixes supplied here. By default, users will only be able to set +; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR). +; Note: If this directive is empty, PHP will let the user modify ANY +; environment variable! +; http://php.net/safe-mode-allowed-env-vars +safe_mode_allowed_env_vars = PHP_ + +; This directive contains a comma-delimited list of environment variables that +; the end user won't be able to change using putenv(). These variables will be +; protected even if safe_mode_allowed_env_vars is set to allow to change them. +; http://php.net/safe-mode-protected-env-vars +safe_mode_protected_env_vars = LD_LIBRARY_PATH + +; open_basedir, if set, limits all file operations to the defined directory +; and below. This directive makes most sense if used in a per-directory +; or per-virtualhost web server configuration file. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/open-basedir +;open_basedir = + +; This directive allows you to disable certain functions for security reasons. +; It receives a comma-delimited list of function names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-functions +disable_functions = + +; This directive allows you to disable certain classes for security reasons. +; It receives a comma-delimited list of class names. This directive is +; *NOT* affected by whether Safe Mode is turned On or Off. +; http://php.net/disable-classes +disable_classes = + +; Colors for Syntax Highlighting mode. Anything that's acceptable in +; <span style="color: ???????"> would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.bg = #FFFFFF +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 16k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = -1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 6.0.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it's automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL & ~E_NOTICE (Show all errors, except for notices and coding standards warnings.) +; E_ALL & ~E_NOTICE | E_STRICT (Show all errors, except for notices) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.) +; Default Value: E_ALL & ~E_NOTICE +; Development Value: E_ALL | E_STRICT +; Production Value: E_ALL & ~E_DEPRECATED +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; It's recommended that errors be logged on production servers rather than +; having the errors sent to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. But, it's strongly recommended that you +; leave this setting off on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of inserting html +; links to documentation related to that error. This directive controls whether +; those HTML links appear in error messages or not. For performance and security +; reasons, it's recommended you disable this on production servers. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: Off +; http://php.net/html-errors +html_errors = Off + +; If html_errors is set On PHP produces clickable error messages that direct +; to a page describing the error or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "<span style='color: #ff0000'>" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "</span>" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on NT, not valid in Windows 95). +;error_log = syslog + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. If the register_globals directive is enabled, it also determines +; what order variables are populated into the global space. G,P,C,E & S are +; abbreviations for the following respective super globals: GET, POST, COOKIE, +; ENV and SERVER. There is a performance penalty paid for the registration of +; these arrays and because ENV is not as commonly used as the others, ENV is +; is not recommended on productions servers. You can still get access to +; the environment variables through getenv() should you need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P,C,E & S) should +; be registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive are +; specified in the same manner as the variables_order directive, EXCEPT one. +; Leaving this value empty will cause PHP to use the value set in the +; variables_order directive. It does not mean it will leave the super globals +; array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; Whether or not to register the EGPCS variables as global variables. You may +; want to turn this off if you don't want to clutter your scripts' global scope +; with user data. +; You should do your best to write your scripts so that they do not require +; register_globals to be on; Using form variables as globals can easily lead +; to possible security problems, if the code is not very well thought of. +; http://php.net/register-globals +register_globals = Off + +; Determines whether the deprecated long $HTTP_*_VARS type predefined variables +; are registered by PHP or not. As they are deprecated, we obviously don't +; recommend you use them. They are on by default for compatibility reasons but +; they are not recommended on production servers. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-long-arrays +register_long_arrays = Off + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the SERVER and ENV variables are created when they're first +; used (Just In Time) instead of when the script starts. If these variables +; are not used within a script, having this directive on will result in a +; performance gain. The PHP directives register_globals, register_long_arrays, +; and register_argc_argv must be disabled for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Maximum size of POST data that PHP will accept. +; http://php.net/post-max-size +post_max_size = 8M + +; Magic quotes are a preprocessing feature of PHP where PHP will attempt to +; escape any character sequences in GET, POST, COOKIE and ENV data which might +; otherwise corrupt data being placed in resources such as databases before +; making that data available to you. Because of character encoding issues and +; non-standard SQL implementations across many databases, it's not currently +; possible for this feature to be 100% accurate. PHP's default behavior is to +; enable the feature. We strongly recommend you use the escaping mechanisms +; designed specifically for the database your using instead of relying on this +; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is +; scheduled for removal in PHP 6. +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/magic-quotes-gpc +magic_quotes_gpc = Off + +; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc. +; http://php.net/magic-quotes-runtime +magic_quotes_runtime = Off + +; Use Sybase-style magic quotes (escape ' with '' instead of \'). +; http://php.net/magic-quotes-sybase +magic_quotes_sybase = Off + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a character encoding using +; the Content-type: header. To disable sending of the charset, simply +; set it to be empty. +; +; PHP's built-in default is text/html +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to empty. +; http://php.net/default-charset +;default_charset = "iso-8859-1" + +; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is +; to disable this feature. +; http://php.net/always-populate-raw-post-data +;always_populate_raw_post_data = On + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/usr/share/php" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "lib/" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = ; + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1; + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If it's set 0 PHP sends Status: header that +; is supported by Apache. When this option is set to 1 PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +extension=./lib/tarantool.so +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=msql.dll +; +; ... or under UNIX: +; +; extension=msql.so +; +; ... or with a path: +; +; extension=/path/to/extension/msql.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +;iconv.input_encoding = ISO-8859-1 +;iconv.internal_encoding = ISO-8859-1 +;iconv.output_encoding = ISO-8859-1 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING + +[sqlite] +; http://php.net/sqlite.assoc-case +;sqlite.assoc_case = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[Syslog] +; Whether or not to define the various syslog variables (e.g. $LOG_PID, +; $LOG_CRON, etc.). Turning it off is a good idea performance-wise. In +; runtime, you can define these variables by calling define_syslog_variables(). +; http://php.net/define-syslog-variables +define_syslog_variables = Off + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(), even in safe mode. +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; The path to a log file that will log all mail() calls. Log entries include +; the full path of the script, line number, To address and headers. +;mail.log = + +[SQL] +; http://php.net/sql.safe-mode +sql.safe_mode = Off + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQL] +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysql.allow_local_infile +mysql.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysql.allow-persistent +mysql.allow_persistent = On + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysql.cache_size +mysql.cache_size = 2000 + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysql.max-persistent +mysql.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/mysql.max-links +mysql.max_links = -1 + +; Default port number for mysql_connect(). If unset, mysql_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysql.default-port +mysql.default_port = + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysql.default-socket +mysql.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-host +mysql.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysql.default-user +mysql.default_user = + +; Default password for mysql_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysql.default-password +mysql.default_password = + +; Maximum time (in seconds) for connect timeout. -1 means no limit +; http://php.net/mysql.connect-timeout +mysql.connect_timeout = 60 + +; Trace mode. When trace_mode is active (=On), warnings for table/index scans and +; SQL-Errors will be displayed. +; http://php.net/mysql.trace-mode +mysql.trace_mode = Off + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statstics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgresSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Notice message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[Sybase-CT] +; Allow or prevent persistent links. +; http://php.net/sybct.allow-persistent +sybct.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/sybct.max-persistent +sybct.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/sybct.max-links +sybct.max_links = -1 + +; Minimum server message severity to display. +; http://php.net/sybct.min-server-severity +sybct.min_server_severity = 10 + +; Minimum client message severity to display. +; http://php.net/sybct.min-client-severity +sybct.min_client_severity = 10 + +; Set per-context timeout +; http://php.net/sybct.timeout +;sybct.timeout= + +;sybct.packet_size + +; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. +; Default: one minute +;sybct.login_timeout= + +; The name of the host you claim to be connecting from, for display by sp_who. +; Default: none +;sybct.hostname= + +; Allows you to define how often deadlocks are to be retried. -1 means "forever". +; Default: 0 +;sybct.deadlock_retry_count= + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if you +; or your OS have problems with lots of files in one directory, and is +; a more efficient layout for servers that handle lots of sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combatting +; session hijacking when not specifying and managing your own session id. It is +; not the end all be all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 0 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; find /path/to/sessions -cmin +24 | xargs rm + +; PHP 4.2 and less have an undocumented feature/bug that allows you to +; to initialize a session variable in the global scope, even when register_globals +; is disabled. PHP 4.3 and later will warn you, if this feature is used. +; You can disable the feature and the warning separately. At this time, +; the warning is only displayed, if bug_compat_42 is enabled. This feature +; introduces some serious security problems if not handled correctly. It's +; recommended that you do not use this feature on production servers. But you +; should enable this on development servers and enable the warning as well. If you +; do not enable the feature on development servers, you won't be warned when it's +; used and debugging errors caused by this can be difficult to track down. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-42 +session.bug_compat_42 = Off + +; This setting controls whether or not you are warned by PHP when initializing a +; session value into the global space. session.bug_compat_42 must be enabled before +; these warnings can be issued by PHP. See the directive above for more information. +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/session.bug-compat-warn +session.bug_compat_warn = Off + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; How many bytes to read from the file. +; http://php.net/session.entropy-length +session.entropy_length = 0 + +; Specified here to create the session id. +; http://php.net/session.entropy-file +; On systems that don't have /dev/urandom /dev/arandom can be used +; On windows, setting the entropy_length setting will activate the +; Windows random source (using the CryptoAPI) +;session.entropy_file = /dev/urandom + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publically accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Select a hash function for use in generating session ids. +; Possible Values +; 0 (MD5 128 bits) +; 1 (SHA-1 160 bits) +; This option may also be set to the name of any hash function supported by +; the hash extension. A list of available hashes is returned by the hash_algos() +; function. +; http://php.net/session.hash-function +session.hash_function = 0 + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.hash_bits_per_character = 5 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +; form/fieldset are special; if you include them here, the rewriter will +; add a hidden <input> field with the info which is otherwise appended +; to URLs. If you want XHTML conformity, remove the form entry. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=,fieldset=" +; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" +; http://php.net/url-rewriter.tags +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + +[MSSQL] +; Allow or prevent persistent links. +mssql.allow_persistent = On + +; Maximum number of persistent links. -1 means no limit. +mssql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +mssql.max_links = -1 + +; Minimum error severity to display. +mssql.min_error_severity = 10 + +; Minimum message severity to display. +mssql.min_message_severity = 10 + +; Compatibility mode with old versions of PHP 3.0. +mssql.compatability_mode = Off + +; Connect timeout +;mssql.connect_timeout = 5 + +; Query timeout +;mssql.timeout = 60 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textlimit = 4096 + +; Valid range 0 - 2147483647. Default = 4096. +;mssql.textsize = 4096 + +; Limits the number of records in each batch. 0 = all records in one batch. +;mssql.batchsize = 0 + +; Specify how datetime and datetim4 columns are returned +; On => Returns data converted to SQL server settings +; Off => Returns values as YYYY-MM-DD hh:mm:ss +;mssql.datetimeconvert = On + +; Use NT authentication when connecting to the server +mssql.secure_connection = Off + +; Specify max number of processes. -1 = library default +; msdlib defaults to 25 +; FreeTDS defaults to 4096 +;mssql.max_procs = -1 + +; Specify client character set. +; If empty or not set the client charset from freetds.comf is used +; This is only used when compiled with FreeTDS +;mssql.charset = "ISO-8859-1" + +[Assertion] +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Issue a PHP warning for each failed assertion. +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; internal/script encoding. +; Some encoding cannot work as internal encoding. +; (e.g. SJIS, BIG5, ISO-2022-*) +; http://php.net/mbstring.internal-encoding +;mbstring.internal_encoding = EUC-JP + +; http input encoding. +; http://php.net/mbstring.http-input +;mbstring.http_input = auto + +; http output encoding. mb_output_handler must be +; registered as output buffer to function +; http://php.net/mbstring.http-output +;mbstring.http_output = SJIS + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; auto means +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none; + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte +; Default: "" +;mbstring.script_encoding= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 0 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[mcrypt] +; For more information about mcrypt settings see http://php.net/mcrypt-module-open + +; Directory where to load mcrypt algorithms +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.algorithms_dir= + +; Directory where to load mcrypt modes +; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) +;mcrypt.modes_dir= + +[dba] +;dba.default_handler= + +; Local Variables: +; tab-width: 4 +; End: diff --git a/connector/php/test/etc/tarantool_box.cfg b/connector/php/test/etc/tarantool_box.cfg new file mode 100644 index 0000000000000000000000000000000000000000..39be5f4f3a58c00e1b25b548a66f5cd40cd39165 --- /dev/null +++ b/connector/php/test/etc/tarantool_box.cfg @@ -0,0 +1,61 @@ +# Limit of memory used to store tuples to 100MB +# (0.1 GB) +# This effectively limits the memory, used by +# Tarantool. However, index and connection memory +# is stored outside the slab allocator, hence +# the effective memory usage can be higher (sometimes +# twice as high). +slab_alloc_arena = 0.1 + +# Tarantool executable directory +work_dir = "var" + +# Store the pid in this file. Relative to startup dir. +pid_file = "tarantool_box.pid" + +# Snapshot directory (where snapshots get saved/read) +snap_dir="." + +# WAL directory (where WALs get saved/read) +wal_dir="." + +# Pipe the logs into the following process. +logger="cat - >> tarantool_box.log" + +# Read only and read-write port. +primary_port = 33013 + +# Read-only port. +secondary_port = 33014 + +# The port for administrative commands. +admin_port = 33015 + +# Log level +log_level = 4 + +# Each write ahead log contains this many rows. +# When the limit is reached, Tarantool closes +# the WAL and starts a new one. +rows_per_wal = 5000000 + +wal_fsync_delay = 0.1 +wal_writer_inbox_size=1024 + +# Define a simple space with 1 HASH-based +# primary key. +space[0].enabled = 1 +space[0].index[0].type = "HASH" +space[0].index[0].unique = 1 +space[0].index[0].key_field[0].fieldno = 0 +space[0].index[0].key_field[0].type = "NUM" +space[0].index[1].type = "TREE" +space[0].index[1].unique = 0 +space[0].index[1].key_field[0].fieldno = 1 +space[0].index[1].key_field[0].type = "STR" + +space[1].enabled = 1 +space[1].index[0].type = "HASH" +space[1].index[0].unique = 1 +space[1].index[0].key_field[0].fieldno = 0 +space[1].index[0].key_field[0].type = "STR" diff --git a/connector/php/test/insert.phpt b/connector/php/test/insert.phpt new file mode 100644 index 0000000000000000000000000000000000000000..6338ab1a10ce539f98e7d4a844810bee33ec58a7 --- /dev/null +++ b/connector/php/test/insert.phpt @@ -0,0 +1,176 @@ +--TEST-- +Tarantool/box insert command test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); + +echo "---------- test begin ----------\n"; +echo "test insert: invalid tuple (expected error exception)\n"; +test_insert($tarantool, 0, array(0, array(1, 2, 3), "str"), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: invalid tuple (expected error exception)\n"; +test_insert($tarantool, 0, array(0, $tarantool), 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: assign tuple"; +test_insert($tarantool, 0, $sw4, + TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: assign tuple (tuple doesn't return)\n"; +test_insert($tarantool, 0, $sw6, 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: add existed tuple (expected error exception)\n"; +test_insert($tarantool, 0, $sw4, + TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_ADD); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: replace not existed tuple (expected error exception)\n"; +test_insert($tarantool, 0, $sw5, + TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_REPLACE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: add not existed tuple\n"; +test_insert($tarantool, 0, $sw5, + TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_ADD); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test insert: replace existed tuple\n"; +test_insert($tarantool, 0, $sw5, + TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_REPLACE); +echo "----------- test end -----------\n\n"; + +test_clean($tarantool, 0); +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +test insert: invalid tuple (expected error exception) +catched exception: unsupported field type +----------- test end ----------- + +---------- test begin ---------- +test insert: invalid tuple (expected error exception) +catched exception: unsupported field type +----------- test end ----------- + +---------- test begin ---------- +test insert: assign tupleresult: +count = 1 +tuple: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +----------- test end ----------- + +---------- test begin ---------- +test insert: assign tuple (tuple doesn't return) +result: +count = 1 +----------- test end ----------- + +---------- test begin ---------- +test insert: add existed tuple (expected error exception) +catched exception: insert failed: 14082(0x00003702): Tuple already exists +----------- test end ----------- + +---------- test begin ---------- +test insert: replace not existed tuple (expected error exception) +catched exception: insert failed: 12546(0x00003102): Tuple doesn't exist +----------- test end ----------- + +---------- test begin ---------- +test insert: add not existed tuple +result: +count = 1 +tuple: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +----------- test end ----------- + +---------- test begin ---------- +test insert: replace existed tuple +result: +count = 1 +tuple: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/php/test/lib/lua/sorted_array.lua b/connector/php/test/lib/lua/sorted_array.lua new file mode 100644 index 0000000000000000000000000000000000000000..1d8c3fb597495727166ceebecc99df537fb48d9b --- /dev/null +++ b/connector/php/test/lib/lua/sorted_array.lua @@ -0,0 +1,173 @@ +local bit = require("bit") +local table = require("table") +local string = require("string") +local floor = require("math").floor + +local function s_to_a(data) + local a = {} + local bytes = {string.byte(data, i, #data)} + for i = 1, #data / 8 do + a[i] = 0 + for c = 0, 7 do + a[i] = bit.lshift(a[i], 8) + a[i] = a[i] + bytes[i * 8 - c] + end + end + return a +end + +local function a_to_s(a) + local bytes = {} + local shift = {} + for i = 1, #a do + val = a[i] + for c = 0, 7 do + table.insert(bytes, bit.band(val, 0xff)) + val = bit.rshift(val, 8) + end + end + return string.char(unpack(bytes)) +end + +local max_limit = 500 +local function amerge(a, b) + local r = {} + local n = max_limit + while #a > 0 and #b > 0 and n > 0 do + if a[1] > b[1] then + table.insert(r, table.remove(a, 1)) + else + table.insert(r, table.remove(b, 1)) + end + n = n - 1 + end + while #a > 0 and n > 0 do + table.insert(r, table.remove(a, 1)) + n = n - 1 + end + while #b > 0 and n > 0 do + table.insert(r, table.remove(b, 1)) + n = n - 1 + end + return r +end + +local function afind_ge(a, x) + if #a == 0 then + return 1 + elseif a[1] < x then + return 1 + elseif a[#a] > x then + return #a + 1 + end + + local first, last = 1, #a + 1 + local mid + while first < last do + mid = floor(first + (last - first) / 2) + if x >= a[mid] then + last = mid + else + first = mid + 1 + end + end + + return last +end + +local function ains(a, key) + key = tonumber(key) + + local i = afind_ge(a, key) + if a[i] and a[i] >= key then + table.insert(a, i + 1, key) -- next to equal or greater + else + table.insert(a, i, key) + end + + while #a > max_limit do + table.remove(a) + end +end + +local function adel(a, key) + key = tonumber(key) + local i = afind_ge(a, key) + print("key = ", key) + print("i = ", i) + if a[i] == key then + table.remove(a, i) + end +end + +local function get(space, key) + local tuple = box.select(space, 0, key) + if tuple then + return s_to_a(tuple[1]) + else + return {} + end +end + +local function store(space, key, a) + box.replace(space, key, a_to_s(a)) + return key, a +end + +function box.sa_insert(space, key, value) + local a = get(space, key) + ains(a, value) + print(unpack(a)) + return store(space, key, a) +end + +function box.sa_delete(space, key, ...) + local a = get(space, key) + for i = 1, #a do + print("a[", i, "] = ", a[i]) + end + for i, d in pairs({...}) do + adel(a, d) + end + return store(space, key, a) +end + +function box.sa_select(space, key, from, limit) + local a = get(space, key) + + if from ~= nil then + from = tonumber(from) + index = afind_ge(a, from) + if a[index] == from then + index = index + 1 + end + else + index = 1 + end + + if limit ~= nil then + limit = tonumber(limit) + else + limit = max_limit + end + + local r = {} + for i = index, #a do + if a[i] == nil then + break + end + table.insert(r, a[i]) + limit = limit - 1 + if limit == 0 then + break + end + end + return key, r +end + +function box.sa_merge(space, key_a, key_b) + local a = get(space, key_a) + local b = get(space, key_b) + local r = amerge(a, b) + return r +end diff --git a/connector/php/test/lib/php/tarantool_utest.php b/connector/php/test/lib/php/tarantool_utest.php new file mode 100644 index 0000000000000000000000000000000000000000..011d375843af8c61d0902f4748bbd859cd3b5372 --- /dev/null +++ b/connector/php/test/lib/php/tarantool_utest.php @@ -0,0 +1,180 @@ +<?php + +$sw4 = array(0, "Star Wars", 1977, "A New Hope", + "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" . + "\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" . + "\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....", + 0xf10dbeef0001); + +$sw5 = array(1, "Star Wars", 1980, "The Empire Strikes Back", + "It is a dark time for the\n" . + "Rebellion. Although the Death\n" . + "Star has been destroyed.\n" . + "Imperial troops have driven the\n" . + "Rebel forces from their hidden\n" . + "base and pursued them across\n" . + "the galaxy.\n" . + "\n" . + "Evading the dreaded Imperial\n" . + "Starfleet, a group of freedom\n" . + "fighters led by Luke Skywalker\n" . + "have established a new secret base\n" . + "on the remote ice world\n" . + "of Hoth.\n" . + "\n" . + "The evil lord Darth Vader,\n" . + "obsessed with finding young\n" . + "Skywalker, has dispatched\n" . + "thousands of remote probes\n" . + "into the far reaches of space....", + 0xf10dbeef0002); + +$sw6 = array(2, "Star Wars", 1983, "Return of the Jedi", + "Luke Skywalker has returned\n" . + "to his home planet of\n" . + "Tatooine in an attempt\n" . + "to rescue his friend\n" . + "Han Solo from the\n" . + "clutches of the vile\n" . + "gangster Jabba the Hutt.\n" . + "\n" . + "Little does Luke know\n" . + "that the GALACTIC EMPIRE\n" . + "has secretly begun construction\n" . + "on a new armored space station\n" . + "even more powerful than the\n" . + "first dreaded Death Star.\n" . + "\n" . + "When completed, this ultimate\n" . + "weapon will spell certain\n" . + "doom for the small band of\n" . + "rebels struggling to restore\n" . + "freedom to the galaxy...", + 0xf10dbeef0003); + +function test_init($tarantool, $space_no) { + try { + global $sw4, $sw5, $sw6; + $tarantool->insert($space_no, $sw4); + $tarantool->insert($space_no, $sw5); + $tarantool->insert($space_no, $sw6); + } catch (Exception $e) { + echo "init failed: ", $e->getMessage(), "\n"; + throw $e; + } +} + +function test_clean($tarantool, $space_no) { + try { + $tarantool->delete($space_no, 0); + $tarantool->delete($space_no, 1); + $tarantool->delete($space_no, 2); + } catch (Exception $e) { + echo "clean-up failed: ", $e->getMessage(), "\n"; + throw $e; + } +} + +function test_select($tarantool, $space_no, $index_no, $key) { + try { + $result = $tarantool->select($space_no, $index_no, $key); + echo "result:\n"; + echo "count = ", $result["count"], "\n"; + $tuples_list = $result["tuples_list"]; + sort($tuples_list); + for ($i = 0; $i < $result["count"]; $i++) { + echo "tuple[", $i, "]:", "\n"; + echo " id = ", $tuples_list[$i][0], "\n"; + echo " series = ", $tuples_list[$i][1], "\n"; + echo " year = ", $tuples_list[$i][2], "\n"; + echo " name = ", $tuples_list[$i][3], "\n"; + echo " crawl = ", $tuples_list[$i][4], "\n"; + echo " uuid = ", $tuples_list[$i][5], "\n"; + } + } catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; + } +} + +function test_insert($tarantool, $space_no, $tuple, $flags) { + try { + $result = $tarantool->insert($space_no, $tuple, $flags); + echo "result:\n"; + echo "count = ", $result["count"], "\n"; + if (array_key_exists("tuple", $result)) { + echo "tuple:", "\n"; + echo " id = ", $result["tuple"][0], "\n"; + echo " series = ", $result["tuple"][1], "\n"; + echo " year = ", $result["tuple"][2], "\n"; + echo " name = ", $result["tuple"][3], "\n"; + echo " crawl = ", $result["tuple"][4], "\n"; + echo " uuid = ", $result["tuple"][5], "\n"; + } + } catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; + } +} + +function test_update_fields($tarantool, $space_no, $key, $ops, $flags) { + try { + $result = $tarantool->update_fields($space_no, $key, $ops, $flags); + echo "result:\n"; + echo "count = ", $result["count"], "\n"; + if (array_key_exists("tuple", $result)) { + echo "tuple:", "\n"; + echo " id = ", $result["tuple"][0], "\n"; + echo " series = ", $result["tuple"][1], "\n"; + echo " year = ", $result["tuple"][2], "\n"; + echo " name = ", $result["tuple"][3], "\n"; + echo " crawl = ", $result["tuple"][4], "\n"; + echo " uuid = ", $result["tuple"][5], "\n"; + } + } catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; + } +} + +function test_delete($tarantool, $space_no, $key, $flags) { + try { + $result = $tarantool->delete($space_no, $key, $flags); + echo "result:\n"; + echo "count = ", $result["count"], "\n"; + if (array_key_exists("tuple", $result)) { + echo "tuple:", "\n"; + echo " id = ", $result["tuple"][0], "\n"; + echo " series = ", $result["tuple"][1], "\n"; + echo " year = ", $result["tuple"][2], "\n"; + echo " name = ", $result["tuple"][3], "\n"; + echo " crawl = ", $result["tuple"][4], "\n"; + echo " uuid = ", $result["tuple"][5], "\n"; + } + } catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; + } +} + +function test_call($tarantool, $proc, $tuple_args, $flags) { + try { + $result = $tarantool->call($proc, $tuple_args, $flags); + echo "result:\n"; + var_dump($result); + } catch (Exception $e) { + echo "catched exception: ", $e->getMessage(), "\n"; + } +} + +?> diff --git a/connector/php/test/run-test b/connector/php/test/run-test new file mode 120000 index 0000000000000000000000000000000000000000..1993bbd700ce69abe07bca7b482359f16c41bfbd --- /dev/null +++ b/connector/php/test/run-test @@ -0,0 +1 @@ +bin/run-test.bash \ No newline at end of file diff --git a/connector/php/test/select.phpt b/connector/php/test/select.phpt new file mode 100644 index 0000000000000000000000000000000000000000..668306928079dcc2c4e8d74383310405ebdb15af --- /dev/null +++ b/connector/php/test/select.phpt @@ -0,0 +1,356 @@ +--TEST-- +Tarantool/box select commands test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); +test_init($tarantool, 0); + +echo "---------- test begin ----------\n"; +echo "test select: key is integer\n"; +test_select($tarantool, 0, 0, 0); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is string \n"; +test_select($tarantool, 0, 1, "Star Wars"); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is object (expected error exception)\n"; +test_select($tarantool, 0, 0, $tarantool); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is array\n"; +test_select($tarantool, 0, 1, array("Star Wars")); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is empty array (expected error exception)\n"; +test_select($tarantool, 0, 0, array()); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is array of objects (expected error exception)\n"; +test_select($tarantool, 0, 0, array($tarantool, 1, 2)); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: multi keys select\n"; +test_select($tarantool, 0, 0, array(array(0), array(1), array(2))); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is array of array of objects (expected error exception)\n"; +test_select($tarantool, 0, 0, array(array(1, $tarantool))); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test select: key is array of diffirent elements (expected error exception)\n"; +test_select($tarantool, 0, 0, array(array(1, 2), 2)); +echo "----------- test end -----------\n\n"; + +test_clean($tarantool, 0); +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +test select: key is integer +result: +count = 1 +tuple[0]: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +----------- test end ----------- + +---------- test begin ---------- +test select: key is string +result: +count = 3 +tuple[0]: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +tuple[1]: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +tuple[2]: + id = 2 + series = Star Wars + year = 1983 + name = Return of the Jedi + crawl = Luke Skywalker has returned +to his home planet of +Tatooine in an attempt +to rescue his friend +Han Solo from the +clutches of the vile +gangster Jabba the Hutt. + +Little does Luke know +that the GALACTIC EMPIRE +has secretly begun construction +on a new armored space station +even more powerful than the +first dreaded Death Star. + +When completed, this ultimate +weapon will spell certain +doom for the small band of +rebels struggling to restore +freedom to the galaxy... + uuid = -1091633149 +----------- test end ----------- + +---------- test begin ---------- +test select: key is object (expected error exception) +catched exception: unsupported tuple type +----------- test end ----------- + +---------- test begin ---------- +test select: key is array +result: +count = 3 +tuple[0]: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +tuple[1]: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +tuple[2]: + id = 2 + series = Star Wars + year = 1983 + name = Return of the Jedi + crawl = Luke Skywalker has returned +to his home planet of +Tatooine in an attempt +to rescue his friend +Han Solo from the +clutches of the vile +gangster Jabba the Hutt. + +Little does Luke know +that the GALACTIC EMPIRE +has secretly begun construction +on a new armored space station +even more powerful than the +first dreaded Death Star. + +When completed, this ultimate +weapon will spell certain +doom for the small band of +rebels struggling to restore +freedom to the galaxy... + uuid = -1091633149 +----------- test end ----------- + +---------- test begin ---------- +test select: key is empty array (expected error exception) +catched exception: invalid tuples list: empty array +----------- test end ----------- + +---------- test begin ---------- +test select: key is array of objects (expected error exception) +catched exception: unsupported tuple type +----------- test end ----------- + +---------- test begin ---------- +test select: multi keys select +result: +count = 3 +tuple[0]: + id = 0 + series = Star Wars + year = 1977 + name = A New Hope + crawl = 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.... + uuid = -1091633151 +tuple[1]: + id = 1 + series = Star Wars + year = 1980 + name = The Empire Strikes Back + crawl = It is a dark time for the +Rebellion. Although the Death +Star has been destroyed. +Imperial troops have driven the +Rebel forces from their hidden +base and pursued them across +the galaxy. + +Evading the dreaded Imperial +Starfleet, a group of freedom +fighters led by Luke Skywalker +have established a new secret base +on the remote ice world +of Hoth. + +The evil lord Darth Vader, +obsessed with finding young +Skywalker, has dispatched +thousands of remote probes +into the far reaches of space.... + uuid = -1091633150 +tuple[2]: + id = 2 + series = Star Wars + year = 1983 + name = Return of the Jedi + crawl = Luke Skywalker has returned +to his home planet of +Tatooine in an attempt +to rescue his friend +Han Solo from the +clutches of the vile +gangster Jabba the Hutt. + +Little does Luke know +that the GALACTIC EMPIRE +has secretly begun construction +on a new armored space station +even more powerful than the +first dreaded Death Star. + +When completed, this ultimate +weapon will spell certain +doom for the small band of +rebels struggling to restore +freedom to the galaxy... + uuid = -1091633149 +----------- test end ----------- + +---------- test begin ---------- +test select: key is array of array of objects (expected error exception) +catched exception: unsupported field type +----------- test end ----------- + +---------- test begin ---------- +test select: key is array of diffirent elements (expected error exception) +catched exception: invalid tuples list: expected array of array +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/php/test/update_fields.phpt b/connector/php/test/update_fields.phpt new file mode 100644 index 0000000000000000000000000000000000000000..91287232df5c4c6010c5c08c4c505363000f5f05 --- /dev/null +++ b/connector/php/test/update_fields.phpt @@ -0,0 +1,115 @@ +--TEST-- +Tarantool/box update fields commands test +--FILE-- +<?php +include "lib/php/tarantool_utest.php"; + +$tarantool = new Tarantool("localhost", 33013, 33015); +test_init($tarantool, 0); + +echo "---------- test begin ----------\n"; +echo "test update fields: do update w/o operations (expected error exception)\n"; +test_update_fields($tarantool, 0, 0, array(), TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test update fields: invalid operation list (expected error exception)\n"; +test_update_fields($tarantool, 0, 0, array($tarantool), TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test update fields: do update arith operation\n"; +test_update_fields($tarantool, 0, 0, + array( + array( + "field" => 2, + "op" => TARANTOOL_OP_ADD, + "arg" => 30, + ), + array( + "field" => 0, + "op" => TARANTOOL_OP_ASSIGN, + "arg" => 5, + ), + array( + "field" => 1, + "op" => TARANTOOL_OP_ASSIGN, + "arg" => "", + ), + array( + "field" => 3, + "op" => TARANTOOL_OP_ASSIGN, + "arg" => "return", + ), + array( + "field" => 4, + "op" => TARANTOOL_OP_SPLICE, + "offset" => 1, + "length" => 64, + "list" => " <<splice string>> ", + ), + ), TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +echo "---------- test begin ----------\n"; +echo "test update fields: delete not existing tuple w/ return tuple flag\n"; +test_update_fields($tarantool, 0, 0, + array( + array( + "field" => 2, + "op" => TARANTOOL_OP_ADD, + "arg" => 30, + ), + ), + TARANTOOL_FLAGS_RETURN_TUPLE); +echo "----------- test end -----------\n\n"; + +test_clean($tarantool, 0); +?> +===DONE=== +--EXPECT-- +---------- test begin ---------- +test update fields: do update w/o operations (expected error exception) +catched exception: update fields failed: 514(0x00000202): Illegal parameters, no operations for update +----------- test end ----------- + +---------- test begin ---------- +test update fields: invalid operation list (expected error exception) +catched exception: invalid operations list +----------- test end ----------- + +---------- test begin ---------- +test update fields: do update arith operation +result: +count = 1 +tuple: + id = 5 + series = + year = 2007 + name = return + crawl = A <<splice string>> ivil 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.... + uuid = -1091633151 +----------- test end ----------- + +---------- test begin ---------- +test update fields: delete not existing tuple w/ return tuple flag +result: +count = 0 +----------- test end ----------- + +===DONE=== \ No newline at end of file diff --git a/connector/python/README.rst b/connector/python/README.rst index 622d0565aef509b73f836d441a002457af62960c..5671b4ee8b829283bd4370ab1b2fda795114abb7 100644 --- a/connector/python/README.rst +++ b/connector/python/README.rst @@ -4,7 +4,10 @@ Python interface to the `Tarantool database <https://github.com/mailru/tarantool>`_. -Tarantool is a damn fast key/value data store originally designed by `Mail.Ru <http://mail.ru>`_ +The latest version of this package is found at +`<https://github.com/mailru/tarnatool-python`_. + +Tarantool is a fast key/value data store originally designed by `Mail.Ru <http://mail.ru>`_ and released under the terms of `BSD license <http://www.gnu.org/licenses/license-list.html#ModifiedBSD>`_. Tarantool is production-ready and actively used at `Mail.Ru <http://mail.ru>`_, one of the leading Russian web content providers. diff --git a/connector/python/setup.py b/connector/python/setup.py deleted file mode 100644 index 95090dc2eb84efdc0a129e2e36f20cf710a73754..0000000000000000000000000000000000000000 --- a/connector/python/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -from distutils.core import setup -import os.path - -setup( - name = "tarantool-python", - packages = ["tarantool"], - package_dir = {"tarantool": os.path.join("src", "tarantool")}, - version = "0.1.1-dev", - platforms = ["all"], - author = "Konstantin Cherkasoff", - author_email = "k.cherkasoff@gmail.com", - url = "https://github.com/coxx/tarantool-python", - description = "Python client library for Tarantool Database", -) diff --git a/connector/python/src/tarantool/__init__.py b/connector/python/src/tarantool/__init__.py deleted file mode 100644 index 836a633575a9fcde65dbc651281bc18628688025..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 - -from tarantool.connection import Connection -from tarantool.const import * -from tarantool.error import * - -def connect(host="localhost", port=33013): - return Connection(host, port, - socket_timeout=SOCKET_TIMEOUT, - reconnect_max_attempts=RECONNECT_MAX_ATTEMPTS, - reconnect_delay=RECONNECT_DELAY, - connect_now=True) diff --git a/connector/python/src/tarantool/connection.py b/connector/python/src/tarantool/connection.py deleted file mode 100644 index 06e713b0d85865b0d4c7bd3e17412eb9d04feb82..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/connection.py +++ /dev/null @@ -1,337 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 -''' -This module provides low-level API for Tarantool -''' - -import socket -import sys -import time - -from tarantool.response import Response -from tarantool.request import ( - Request, - RequestCall, - RequestDelete, - RequestInsert, - RequestSelect, - RequestUpdate) -from tarantool.space import Space -from tarantool.const import * -from tarantool.error import * - - - -class Connection(object): - '''\ - Represents low-level interface to the Tarantool server. - This class can be used directly or using object-oriented wrappers. - ''' - - def __init__(self, host, port, - socket_timeout=SOCKET_TIMEOUT, - reconnect_max_attempts=RECONNECT_MAX_ATTEMPTS, - reconnect_delay=RECONNECT_DELAY, - connect_now=True): - '''\ - Initialize an connection to the server. - - :param str host: Server hostname or IP-address - :param int port: Server port - :param bool connect_now: if True (default) than __init__() actually creates network connection. - if False than you have to call connect() manualy. - ''' - self.host = host - self.port = port - self.socket_timeout = socket_timeout - self.reconnect_delay = reconnect_delay - self.reconnect_max_attempts = reconnect_max_attempts - self._socket = None - if connect_now: - self.connect() - - - def connect(self): - '''\ - Create connection to the host and port specified in __init__(). - Usually there is no need to call this method directly, - since it is called when you create an `Connection` instance. - - :raise: `NetworkError` - ''' - - try: - # If old socket already exists - close it and re-create - if self._socket: - self._socket.close() - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) - self._socket.connect((self.host, self.port)) - # It is important to set socket timeout *after* connection. - # Otherwise the timeout exception will rised, even if the server does not listen - self._socket.settimeout(self.socket_timeout) - except socket.error as e: - raise NetworkError(e) - - - def _send_request_wo_reconnect(self, request, field_types=None): - '''\ - :rtype: `Response` instance - - :raise: NetworkError - ''' - assert isinstance(request, Request) - - # Repeat request in a loop if the server returns completion_status == 1 (try again) - for attempt in xrange(RETRY_MAX_ATTEMPTS): # pylint: disable=W0612 - try: - self._socket.sendall(bytes(request)) - response = Response(self._socket, field_types) - except socket.error as e: - raise NetworkError(e) - - if response.completion_status != 1: - return response - warn(response.return_message, RetryWarning) - - # Raise an error if the maximum number of attempts have been made - raise DatabaseError(response.return_code, response.return_message) - - - def _send_request(self, request, field_types=None): - '''\ - Send the request to the server through the socket. - Return an instance of `Response` class. - - :param request: object representing a request - :type request: `Request` instance - - :rtype: `Response` instance - ''' - assert isinstance(request, Request) - - connected = True - attempt = 1 - while True: - try: - if not connected: - time.sleep(self.reconnect_delay) - self.connect() - connected = True - warn("Successfully reconnected", NetworkWarning) - response = self._send_request_wo_reconnect(request, field_types) - break - except NetworkError as e: - if attempt > self.reconnect_max_attempts: - raise - warn("%s : Reconnect attempt %d of %d"%(e.message, attempt, self.reconnect_max_attempts), NetworkWarning) - attempt += 1 - connected = False - - return response - - def call(self, func_name, *args, **kwargs): - '''\ - Execute CALL request. Call stored Lua function. - - :param func_name: stored Lua function name - :type func_name: str - :param args: list of function arguments - :type args: list or tuple - :param return_tuple: True indicates that it is required to return the inserted tuple back - :type return_tuple: bool - - :rtype: `Response` instance - ''' - assert isinstance(func_name, str) - assert len(args) != 0 - - # This allows to use a tuple or list as an argument - if isinstance(args[0], (list, tuple)): - args = args[0] - - # Check if 'field_types' keyword argument is passed - field_types = kwargs.get("field_types", None) - - request = RequestCall(func_name, args, return_tuple=True) - response = self._send_request(request, field_types=field_types) - return response - - - def insert(self, space_no, values, return_tuple=False, field_types=None): - '''\ - Execute INSERT request. - Insert single record into a space `space_no`. - - :param int space_no: space id to insert a record - :type space_no: int - :param values: record to be inserted. The tuple must contain only scalar (integer or strings) values - :type values: tuple - :param return_tuple: True indicates that it is required to return the inserted tuple back - :type return_tuple: bool - - :rtype: `Response` instance - ''' - assert isinstance(values, tuple) - - request = RequestInsert(space_no, values, return_tuple) - return self._send_request(request, field_types=field_types) - - - def delete(self, space_no, key, return_tuple=False, field_types=None): - '''\ - Execute DELETE request. - Delete single record identified by `key` (using primary index). - - :param space_no: space id to delete a record - :type space_no: int - :param key: key that identifies a record - :type key: int or str - :param return_tuple: indicates that it is required to return the deleted tuple back - :type return_tuple: bool - - :rtype: `Response` instance - ''' - assert isinstance(key, (int, basestring)) - - request = RequestDelete(space_no, key, return_tuple) - return self._send_request(request, field_types=field_types) - - - def update(self, space_no, key, op_list, return_tuple=False, field_types=None): - '''\ - Execute UPDATE request. - Update single record identified by `key` (using primary index). - - List of operations allows to update individual fields. - - :param space_no: space id to update a record - :type space_no: int - :param key: key that identifies a record - :type key: int or str - :param op_list: list of operations. Each operation is tuple of three values - :type op_list: a list of the form [(field_1, symbol_1, arg_1), (field_2, symbol_2, arg_2),...] - :param return_tuple: indicates that it is required to return the updated tuple back - :type return_tuple: bool - - :rtype: `Response` instance - ''' - assert isinstance(key, (int, basestring)) - - request = RequestUpdate(space_no, key, op_list, return_tuple) - return self._send_request(request, field_types=field_types) - - - def ping(self): - '''\ - Execute PING request. - Send empty request and receive empty response from server. - - :return: response time in seconds - :rtype: float - ''' - t0 = time.time() - self._socket.sendall(struct_LLL.pack(0xff00, 0, 0)) - request_type, body_length, request_id = struct_LLL.unpack(self._socket.recv(12)) # pylint: disable=W0612 - t1 = time.time() - assert request_type == 0xff00 - assert body_length == 0 - return t1 - t0 - - - def _select(self, space_no, index_no, values, offset=0, limit=0xffffffff, field_types=None): - '''\ - Low level version of select() method. - - :param space_no: space id to select data - :type space_no: int - :param index_no: index id to use - :type index_no: int - :param values: list of values to search over the index - :type values: list of tuples - :param offset: offset in the resulting tuple set - :type offset: int - :param limit: limits the total number of returned tuples - :type limit: int - - :rtype: `Response` instance - ''' - - # 'values' argument must be a list of tuples - assert isinstance(values, (list, tuple)) - assert len(values) != 0 - assert isinstance(values[0], (list, tuple)) - - request = RequestSelect(space_no, index_no, values, offset, limit) - response = self._send_request(request, field_types=field_types) - return response - - - def select(self, space_no, index_no, values, offset=0, limit=0xffffffff, field_types=None): - '''\ - Execute SELECT request. - Select and retrieve data from the database. - - :param space_no: specifies which space to query - :type space_no: int - :param index_no: specifies which index to use - :type index_no: int - :param values: list of values to search over the index - :type values: list of tuples - :param offset: offset in the resulting tuple set - :type offset: int - :param limit: limits the total number of returned tuples - :type limit: int - - :rtype: `Response` instance - - Select one single record (from space=0 and using index=0) - >>> select(0, 0, 1) - - Select several records using single-valued index - >>> select(0, 0, [1, 2, 3]) - >>> select(0, 0, [(1,), (2,), (3,)]) # the same as above - - Select serveral records using composite index - >>> select(0, 1, [(1,'2'), (2,'3'), (3,'4')]) - - Select single record using composite index - >>> select(0, 1, [(1,'2')]) - This is incorrect - >>> select(0, 1, (1,'2')) - ''' - - # Perform smart type cheching (scalar / list of scalars / list of tuples) - if isinstance(values, (int, basestring)): # scalar - # This request is looking for one single record - values = [(values, )] - elif isinstance(values, (list, tuple, set, frozenset)): - assert len(values) > 0 - if isinstance(values[0], (int, basestring)): # list of scalars - # This request is looking for several records using single-valued index - # Ex: select(space_no, index_no, [1, 2, 3]) - # Transform a list of scalar values to a list of tuples - values = [(v, ) for v in values] - elif isinstance(values[0], (list, tuple)): # list of tuples - # This request is looking for serveral records using composite index - pass - else: - raise ValueError("Invalid value type, expected one of scalar (int or str) / list of scalars / list of tuples ") - - return self._select(space_no, index_no, values, offset, limit, field_types=field_types) - - - def space(self, space_no, field_types=None): - '''\ - Create `Space` instance for particular space - - `Space` instance encapsulates the identifier of the space and provides more convenient syntax - for accessing the database space. - - :param space_no: identifier of the space - :type space_no: int - - :rtype: `Space` instance - ''' - return Space(self, space_no, field_types) - diff --git a/connector/python/src/tarantool/const.py b/connector/python/src/tarantool/const.py deleted file mode 100644 index 60cad84b8cc278a86f7591d33191deccdba1d6fc..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/const.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 - -import struct - - -# pylint: disable=C0103 -struct_BL = struct.Struct("<BL") -struct_LB = struct.Struct("<LB") -struct_L = struct.Struct("<L") -struct_LL = struct.Struct("<LL") -struct_LLL = struct.Struct("<LLL") -struct_LLLL = struct.Struct("<LLLL") -struct_LLLLL = struct.Struct("<LLLLL") -struct_Q = struct.Struct("<Q") - - -REQUEST_TYPE_CALL = 22 -REQUEST_TYPE_DELETE = 21 -REQUEST_TYPE_INSERT = 13 -REQUEST_TYPE_SELECT = 17 -REQUEST_TYPE_UPDATE = 19 - - -UPDATE_OPERATION_CODE = {'=': 0, '+': 1, '&': 2, '^': 3, '|': 4, 'splice': 5} - -# Default value for socket timeout (seconds) -SOCKET_TIMEOUT = 1 -# Default maximum number of attempts to reconnect -RECONNECT_MAX_ATTEMPTS = 10 -# Default delay between attempts to reconnect (seconds) -RECONNECT_DELAY = 0.1 -# Number of reattempts in case of server return completion_status == 1 (try again) -RETRY_MAX_ATTEMPTS = 10 diff --git a/connector/python/src/tarantool/error.py b/connector/python/src/tarantool/error.py deleted file mode 100644 index 75f7899a6646741ce0384d9b13ced86bb42e76a5..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/error.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 -''' -Python DB API compatible exceptions -http://www.python.org/dev/peps/pep-0249/ - -The PEP-249 says that database related exceptions must be inherited as follows: - - StandardError - |__Warning - |__Error - |__InterfaceError - |__DatabaseError - |__DataError - |__OperationalError - |__IntegrityError - |__InternalError - |__ProgrammingError - |__NotSupportedError -''' - -import os -import socket -import sys -import warnings - - -class Error(StandardError): - '''Base class for error exceptions''' - - -class DatabaseError(Error): - '''Error related to the database engine''' - - -class InterfaceError(Error): - '''Error related to the database interface rather than the database itself''' - - - -# Monkey patch os.strerror for win32 -if sys.platform == "win32": - # Windows Sockets Error Codes (not all, but related on network errors) - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx - _code2str = { - 10004: "Interrupted system call", - 10009: "Bad file descriptor", - 10013: "Permission denied", - 10014: "Bad address", - 10022: "Invalid argument", - 10024: "Too many open files", - 10035: "Resource temporarily unavailable", - 10036: "Operation now in progress", - 10037: "Operation already in progress", - 10038: "Socket operation on nonsocket", - 10039: "Destination address required", - 10040: "Message too long", - 10041: "Protocol wrong type for socket", - 10042: "Bad protocol option", - 10043: "Protocol not supported", - 10044: "Socket type not supported", - 10045: "Operation not supported", - 10046: "Protocol family not supported", - 10047: "Address family not supported by protocol family", - 10048: "Address already in use", - 10049: "Cannot assign requested address", - 10050: "Network is down", - 10051: "Network is unreachable", - 10052: "Network dropped connection on reset", - 10053: "Software caused connection abort", - 10054: "Connection reset by peer", - 10055: "No buffer space available", - 10056: "Socket is already connected", - 10057: "Socket is not connected", - 10058: "Cannot send after transport endpoint shutdown", - 10060: "Connection timed out", - 10061: "Connection refused", - 10062: "Cannot translate name", - 10063: "File name too long", - 10064: "Host is down", - 10065: "No route to host", - 11001: "Host not found", - 11004: "Name or service not known" - } - - - os_strerror_orig = os.strerror - - def os_strerror_patched(code): - '''\ - Return cross-platform message about socket-related errors - - This function exists because under Windows os.strerror returns 'Unknown error' on all socket-related errors. - And socket-related exception contain broken non-ascii encoded messages. - ''' - message = os_strerror_orig(code) - if not message.startswith("Unknown"): - return message - else: - return _code2str.get(code, "Unknown error %s"%code) - - os.strerror = os_strerror_patched - - -class NetworkError(DatabaseError): - '''Error related to network''' - def __init__(self, orig_exception=None, *args): - if orig_exception: - if isinstance(orig_exception, socket.timeout): - self.message = "Socket timeout" - super(NetworkError, self).__init__(0, self.message) - elif isinstance(orig_exception, socket.error): - self.message = os.strerror(orig_exception.errno) - super(NetworkError, self).__init__(orig_exception.errno, self.message) - else: - super(NetworkError, self).__init__(orig_exception, *args) - - -class NetworkWarning(UserWarning): - '''Warning related to network''' - pass - - -class RetryWarning(UserWarning): - '''Warning is emited in case of server return completion_status == 1 (try again)''' - pass - - -# always print this warnings -warnings.filterwarnings("always", category=NetworkWarning) -warnings.filterwarnings("always", category=RetryWarning) - - -def warn(message, warning_class): - '''\ - Emit warinig message. - Just like standard warnings.warn() but don't output full filename. - ''' - frame = sys._getframe(2) # pylint: disable=W0212 - module_name = frame.f_globals.get("__name__") - line_no = frame.f_lineno - warnings.warn_explicit(message, warning_class, module_name, line_no) - diff --git a/connector/python/src/tarantool/request.py b/connector/python/src/tarantool/request.py deleted file mode 100644 index 6cd4e81d60f7dbc16f7b98b7f5bf176194b1ea7b..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/request.py +++ /dev/null @@ -1,277 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 -''' -Request types definitions -''' - -import struct - -from tarantool.const import * - - -class Request(object): - '''\ - Represents a single request to the server in compliance with the Tarantool protocol. - Responsible for data encapsulation and builds binary packet to be sent to the server. - - This is the abstract base class. Specific request types are implemented by the inherited classes. - ''' - request_type = None - - def __init__(self): - self._bytes = None - raise NotImplementedError("Abstract method must be overridden") - - - def __bytes__(self): - return self._bytes - __str__ = __bytes__ - - - @classmethod - def header(cls, body_length): - return struct_LLL.pack(cls.request_type, body_length, 0) - - - @staticmethod - def pack_int(value): - '''\ - Pack integer field - <field> ::= <int32_varint><data> - - :param value: integer value to be packed - :type value: int - - :return: packed value - :rtype: bytes - ''' - if __debug__: - if not isinstance(value, int): - raise TypeError("Invalid argument type '%s', 'int' expected"%type(value).__name__) - return struct_BL.pack(4, value) - - - @staticmethod - def pack_int_base128(value): - """Implement Perl pack's 'w' option, aka base 128 encoding.""" - res = '' - if value >= 1 << 7: - if value >= 1 << 14: - if value >= 1 << 21: - if value >= 1 << 28: - res += chr(value >> 28 & 0xff | 0x80) - res += chr(value >> 21 & 0xff | 0x80) - res += chr(value >> 14 & 0xff | 0x80) - res += chr(value >> 7 & 0xff | 0x80) - res += chr(value & 0x7F) - return res - - - @classmethod - def pack_str(cls, value): - '''\ - Pack string field - <field> ::= <int32_varint><data> - - :param value: string to be packed - :type value: bytes or str - - :return: packed value - :rtype: bytes - ''' - if __debug__: - if not isinstance(value, basestring): - raise TypeError("Invalid argument type '%s', 'str' expected"%type(value).__name__) - value_len_packed = cls.pack_int_base128(len(value)) - return struct.pack("<%ds%ds"%(len(value_len_packed), len(value)), value_len_packed, value) - - - @classmethod - def pack_field(cls, value): - '''\ - Pack single field (string or integer value) - <field> ::= <int32_varint><data> - - :param value: value to be packed - :type value: bytes, str or int - - :return: packed value - :rtype: bytes - ''' - if isinstance(value, basestring): - return cls.pack_str(value) - elif isinstance(value, (int, long)): - return cls.pack_int(value) - else: - raise TypeError("Invalid argument type '%s', 'str' or 'int' expected"%type(value).__name__) - - - @classmethod - def pack_tuple(cls, values): - '''\ - Pack tuple of values - <tuple> ::= <cardinality><field>+ - - :param value: tuple to be packed - :type value: tuple of scalar values (bytes, str or int) - - :return: packed tuple - :rtype: bytes - ''' - assert isinstance(values, (tuple, list)) - cardinality = struct_L.pack(len(values)) - packed_items = [cls.pack_field(v) for v in values] - packed_items.insert(0, cardinality) - return b"".join(packed_items) - - - -class RequestInsert(Request): - '''\ - Represents INSERT request - - <insert_request_body> ::= <space_no><flags><tuple> - |--------------- header ----------------|--------- body ---------| - <request_type><body_length><request_id> <space_no><flags><tuple> - | - items to add (multiple values) -----+ - ''' - request_type = REQUEST_TYPE_INSERT - - def __init__(self, space_no, values, return_tuple): # pylint: disable=W0231 - '''\ - ''' - assert isinstance(values, (tuple, list)) - flags = 1 if return_tuple else 0 - - request_body = \ - struct_LL.pack(space_no, flags) + \ - self.pack_tuple(values) - - self._bytes = self.header(len(request_body)) + request_body - - - -class RequestDelete(Request): - ''' - Represents DELETE request - - <delete_request_body> ::= <space_no><flags><tuple> - |--------------- header ----------------|--------- body ---------| - <request_type><body_length><request_id> <space_no><flags><tuple> - | - key to search in primary index -----+ - (tuple with single value) - ''' - request_type = REQUEST_TYPE_DELETE - - def __init__(self, space_no, key, return_tuple): # pylint: disable=W0231 - ''' - ''' - flags = 1 if return_tuple else 0 - - request_body = \ - struct_LL.pack(space_no, flags) + \ - self.pack_tuple((key,)) - - self._bytes = self.header(len(request_body)) + request_body - - - -class RequestSelect(Request): - '''\ - Represents SELECT request - - <select_request_body> ::= <space_no><index_no><offset><limit><count><tuple>+ - - |--------------- header ----------------|---------------request_body ---------------------...| - <request_type><body_length><request_id> <space_no><index_no><offset><limit><count><tuple>+ - ^^^^^^^^ ^^^^^^^^^^^^ - | | - Index to use ----+ | - | - List of tuples to search in the index ---------------------+ - (tuple cardinality can be > 1 when using composite indexes) - ''' - request_type = REQUEST_TYPE_SELECT - - def __init__(self, space_no, index_no, tuple_list, offset, limit): # pylint: disable=W0231 - - assert isinstance(tuple_list, (list, tuple)) - - request_body = \ - struct_LLLLL.pack(space_no, index_no, offset, limit, len(tuple_list)) + \ - b"".join([self.pack_tuple(t) for t in tuple_list]) - - self._bytes = self.header(len(request_body)) + request_body - - - -class RequestUpdate(Request): - ''' - <update_request_body> ::= <space_no><flags><tuple><count><operation>+ - <operation> ::= <field_no><op_code><op_arg> - - |--------------- header ----------------|---------------request_body --------------...| - <request_type><body_length><request_id> <space_no><flags><tuple><count><operation>+ - | | | - Key to search in primary index -----+ | +-- list of operations - (tuple with cardinality=1) +-- number of operations - ''' - - request_type = REQUEST_TYPE_UPDATE - - def __init__(self, space_no, key, op_list, return_tuple): # pylint: disable=W0231 - flags = 1 if return_tuple else 0 - assert isinstance(key, (int, basestring)) - - request_body = \ - struct_LL.pack(space_no, flags) + \ - self.pack_tuple((key,)) + \ - struct_L.pack(len(op_list)) +\ - self.pack_operations(op_list) - - self._bytes = self.header(len(request_body)) + request_body - - - @classmethod - def pack_operations(cls, op_list): - result = [] - for op in op_list: - try: - field_no, op_symbol, op_arg = op - except ValueError: - raise ValueError("Operation must be a tuple of 3 elements (field_id, op, value)") - try: - op_code = UPDATE_OPERATION_CODE[op_symbol] - except KeyError: - raise ValueError("Invalid operaction symbol '%s', expected one of %s"\ - %(op_symbol, ', '.join(["'%s'"%c for c in sorted(UPDATE_OPERATION_CODE.keys())]))) - data = b"".join([struct_LB.pack(field_no, op_code), cls.pack_field(op_arg)]) - result.append(data) - return b"".join(result) - - - -class RequestCall(Request): - ''' - <call_request_body> ::= <flags><proc_name><tuple> - <proc_name> ::= <field> - - |--------------- header ----------------|-----request_body -------| - <request_type><body_length><request_id> <flags><proc_name><tuple> - | - Lua function arguments -----+ - ''' - request_type = REQUEST_TYPE_CALL - - def __init__(self, proc_name, args, return_tuple): # pylint: disable=W0231 - flags = 1 if return_tuple else 0 - assert isinstance(args, (list, tuple)) - - request_body = \ - struct_L.pack(flags) + \ - self.pack_field(proc_name) +\ - self.pack_tuple(args) - - self._bytes = self.header(len(request_body)) + request_body diff --git a/connector/python/src/tarantool/response.py b/connector/python/src/tarantool/response.py deleted file mode 100644 index 047133e3206ebd355c7aa470802a64ca11e36f26..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/response.py +++ /dev/null @@ -1,296 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0301,W0105,W0401,W0614 - -import ctypes -import socket -import struct -import sys - -from tarantool.const import * -from tarantool.error import * - - - -if sys.version_info < (2, 6): - bytes = str # pylint: disable=W0622 - -class field(bytes): - '''\ - Represents a single element of the Tarantool's tuple - ''' - def __new__(cls, value): - '''\ - Create new instance of Tarantool field (single tuple element) - ''' - # Since parent class is immutable, we should override __new__, not __init__ - - if isinstance(value, unicode): - return super(field, cls).__new__(cls, value.encode("utf-8", "replace")) - if sys.version_info.major < 3 and isinstance(value, str): - return super(field, cls).__new__(cls, value) - if isinstance(value, (bytearray, bytes)): - return super(field, cls).__new__(cls, value) - if isinstance(value, int): - if value <= 0xFFFFFFFF: - # 32 bit integer - return super(field, cls).__new__(cls, struct_L.pack(value)) - else: - # 64 bit integer - return super(field, cls).__new__(cls, struct_Q.pack(value)) - # NOTE: It is posible to implement float - raise TypeError("Unsupported argument type '%s'"%(type(value).__name__)) - - - def __int__(self): - '''\ - Cast filed to int - ''' - if len(self) == 4: - return struct_L.unpack(self)[0] - elif len(self) == 8: - return struct_Q.unpack(self)[0] - else: - raise ValueError("Unable to cast field to int: length must be 4 or 8 bytes, field length is %d"%len(self)) - - - if sys.version_info.major > 2: - def __str__(self): - '''\ - Cast filed to str - ''' - return self.decode("utf-8", "replace") - else: - def __unicode__(self): - '''\ - Cast filed to unicode - ''' - return self.decode("utf-8", "replace") - - - -class Response(list): - '''\ - Represents a single response from the server in compliance with the Tarantool protocol. - Responsible for data encapsulation (i.e. received list of tuples) and parses binary - packet received from the server. - ''' - - def __init__(self, _socket, field_types=None): - '''\ - Create an instance of `Response` using data received from the server. - - __init__() itself reads data from the socket, parses response body and - sets appropriate instance attributes. - - :params _socket: socket connected to the server - :type _socket: instance of socket.socket class (from stdlib) - ''' - # This is not necessary, because underlying list data structures are created in the __new__(). But let it be. - super(Response, self).__init__() - - self._body_length = None - self._request_id = None - self._request_type = None - self._completion_status = None - self._return_code = None - self._return_message = None - self._rowcount = None - self.field_types = field_types - - # Read response header - buff = ctypes.create_string_buffer(16) - nbytes = _socket.recv_into(buff, 16, ) - - # Immediately raises an exception if the data cannot be read - if nbytes != 16: - raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort") - - # Unpack header (including <return_code> attribute) - self._request_type, self._body_length, self._request_id, self._return_code = struct_LLLL.unpack(buff) - - # Separate return_code and completion_code - self._completion_status = self._return_code & 0x00ff - self._return_code = self._return_code >> 8 - - # Unpack body if there is one (i.e. not PING) - if self._body_length != 0: - - # In the protocol description <body_length> includes 4 bytes of <return_code> - self._body_length -= 4 - - # Read response body - buff = ctypes.create_string_buffer(self._body_length) - nbytes = _socket.recv_into(buff) - - # Immediately raises an exception if the data cannot be read - if nbytes != self._body_length: - raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort") - - if self._return_code == 0: - # If no errors, unpack response body - self._unpack_body(buff) - else: - # In case of error unpack body as error message - self._unpack_message(buff) - if self._completion_status == 2: - raise DatabaseError(self._return_code, self._return_message) - - - def _unpack_message(self, buff): - '''\ - Extract error message from response body - Called when return_code! = 0. - - :param buff: buffer containing request body - :type byff: ctypes buffer - :return: error message - :rtype: str - ''' - - self._return_message = unicode(buff.value, "utf8", "replace") - - - @staticmethod - def _unpack_int_base128(varint, offset): - """Implement Perl unpack's 'w' option, aka base 128 decoding.""" - res = ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - return res, offset + 1 - - - def _unpack_tuple(self, buff): - '''\ - Unpacks the tuple from byte buffer - <tuple> ::= <cardinality><field>+ - - :param buff: byte array of the form <cardinality><field>+ - :type buff: ctypes buffer or bytes - - :return: tuple of unpacked values - :rtype: tuple - ''' - - cardinality = struct_L.unpack_from(buff)[0] - _tuple = ['']*cardinality - offset = 4 # The first 4 bytes in the response body is the <count> we have already read - for i in xrange(cardinality): - field_size, offset = self._unpack_int_base128(buff, offset) - field_data = struct.unpack_from("<%ds"%field_size, buff, offset)[0] - _tuple[i] = field(field_data) - offset += field_size - - return tuple(_tuple) - - - def _unpack_body(self, buff): - '''\ - Parse the response body. - After body unpacking its data available as python list of tuples - - For each request type the response body has the same format: - <insert_response_body> ::= <count> | <count><fq_tuple> - <update_response_body> ::= <count> | <count><fq_tuple> - <delete_response_body> ::= <count> | <count><fq_tuple> - <select_response_body> ::= <count><fq_tuple>* - <call_response_body> ::= <count><fq_tuple> - - :param buff: buffer containing request body - :type byff: ctypes buffer - ''' - - # Unpack <count> (first 4 bytes) - how many records returned - self._rowcount = struct_L.unpack_from(buff)[0] - - # If the response body contains only <count> - there is no tuples to unpack - if self._body_length == 4: - return - - # Parse response tuples (<fq_tuple>) - if self._rowcount > 0: - offset = 4 # The first 4 bytes in the response body is the <count> we have already read - while offset < self._body_length: - ''' - # In resonse tuples have the form <size><tuple> (<fq_tuple> ::= <size><tuple>). - # Attribute <size> takes into account only size of tuple's <field> payload, - # but does not include 4-byte of <cardinality> field. - # Therefore the actual size of the <tuple> is greater to 4 bytes. - ''' - tuple_size = struct.unpack_from("<L", buff, offset)[0] + 4 - tuple_data = struct.unpack_from("<%ds"%(tuple_size), buff, offset+4)[0] - tuple_value = self._unpack_tuple(tuple_data) - if self.field_types: - self.append(self._cast_tuple(tuple_value)) - else: - self.append(tuple_value) - - offset = offset + tuple_size + 4 # This '4' is a size of <size> attribute - - - @property - def completion_status(self): - return self._completion_status - - @property - def rowcount(self): - return self._rowcount - - @property - def return_code(self): - return self._return_code - - @property - def return_message(self): - return self._return_message - - - @staticmethod - def _cast_field(cast_to, value): - '''\ - Convert field type from raw bytes to native python type - - :param cast_to: native python type to cast to - :type cast_to: a type object (one of bytes, int, unicode (str for py3k)) - :param value: raw value from the database - :type value: bytes - - :return: converted value - :rtype: value of native python type (one of bytes, int, unicode (str for py3k)) - ''' - - if cast_to in (int, unicode): - return cast_to(value) - elif cast_to in (any, bytes): - return value - else: - raise TypeError("Invalid field type %s"%(cast_to)) - - - def _cast_tuple(self, values): - '''\ - Convert values of the tuple from raw bytes to native python types - - :param values: tuple of the raw database values - :type value: tuple of bytes - - :return: converted tuple value - :rtype: value of native python types (bytes, int, unicode (or str for py3k)) - ''' - result = [] - for i in xrange(len(values)): - if i < len(self.field_types): - result.append(self._cast_field(self.field_types[i], values[i])) - else: - result.append(self._cast_field(self.field_types[-1], values[i])) - - return tuple(result) diff --git a/connector/python/src/tarantool/space.py b/connector/python/src/tarantool/space.py deleted file mode 100644 index 2010f5436b8bc8fc575444bb61743c6f3d084d25..0000000000000000000000000000000000000000 --- a/connector/python/src/tarantool/space.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -### pylint: disable=C0301,W0105,W0401,W0614 -''' -This module provides object-oriented wrapper for accessing a particular Tarantool space -''' -import sys - - - -class Space(object): - '''\ - Object-oriented wrapper for accessing a particular space. - Encapsulates the identifier of the space and provides more convenient syntax - for database operations. - ''' - def __init__(self, connection, space_no, field_types=None): - if __debug__: - if field_types and not all([(t is bytes) or (t is int) or (t is unicode) for t in field_types]): - raise TypeError("Argument field_types can contain only bytes, int or %s"\ - %('str' if sys.version_info.major > 2 else 'unicode')) - - self.connection = connection - self.space_no = space_no - self.field_types = field_types - - def insert(self, values, return_tuple=False): - return self.connection.insert(self.space_no, values, return_tuple, self.field_types) - - def delete(self, key, return_tuple=False): - return self.connection.delete(self.space_no, key, return_tuple, self.field_types) - - def update(self, key, op_list, return_tuple=False): - return self.connection.update(self.space_no, key, op_list, return_tuple, self.field_types) - - def select(self, index_no, values, offset=0, limit=0xffffffff): - return self.connection.select(self.space_no, index_no, values, offset, limit, self.field_types) diff --git a/connector/ruby/README b/connector/ruby/README index 826e0c9b6a37d9569997b84e41452bd2e5817556..651b8f1d4a2e2b15ae7912d8ce8107d0ae634b1a 100644 --- a/connector/ruby/README +++ b/connector/ruby/README @@ -1 +1,2 @@ -A toy implementation of ruby client library for IProto/Box binary protocol +Tarantool Ruby driver can be found at +https://github.com/mailru/tarantool-ruby diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d39ef1a804857b25545df8f8de5d90fbaf169d5d..861e9e11b50625991fc72c715cceddf83b09b631 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -61,7 +61,7 @@ set (recompiled_core_sources ${CMAKE_SOURCE_DIR}/core/fiber.m PARENT_SCOPE) set (common_sources tbuf.m palloc.m util.m - salloc.m pickle.m coro.m stat.m log_io.m + salloc.m pickle.m coro.m stat.m log_io.m cpu_feature.m log_io_remote.m iproto.m exception.m errcode.c latch.m) if (ENABLE_TRACE) diff --git a/core/cpu_feature.m b/core/cpu_feature.m new file mode 100644 index 0000000000000000000000000000000000000000..01eb71b490f804e1c5eaec1c3c3337a3b3edf748 --- /dev/null +++ b/core/cpu_feature.m @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2010 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 <sys/types.h> +#include <errno.h> + +#include "cpu_feature.h" + +enum { eAX=0, eBX, eCX, eDX }; + +static const struct cpuid_feature { + unsigned int ri; + u_int32_t mask; +} cpu_ftr[] = { + {eDX, (1 << 28)}, /* HT */ + {eCX, (1 << 19)}, /* SSE 4.1 */ + {eCX, (1 << 20)}, /* SSE 4.2 */ + {eCX, (1 << 31)} /* HYPERV */ +}; +static const size_t LEN_cpu_ftr = sizeof(cpu_ftr) / sizeof (cpu_ftr[0]); + +#define SCALE_F sizeof(unsigned long) + +#if defined (__x86_64__) + #define REX_PRE "0x48, " +#elif defined (__i386__) + #define REX_PRE +#else + #error "Only x86 and x86_64 architectures supported" +#endif + + +/* hw-calculate CRC32 per byte (for the unaligned portion of data buffer) + */ +static u_int32_t +crc32c_hw_byte(u_int32_t crc, unsigned char const *data, size_t length) +{ + while (length--) { + __asm__ __volatile__( + ".byte 0xf2, 0xf, 0x38, 0xf0, 0xf1" + :"=S"(crc) + :"0"(crc), "c"(*data) + ); + data++; + } + + return crc; +} + + +/* hw-calculate CRC32 for the given data buffer + */ +u_int32_t +crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len) +{ + unsigned int iquotient = len / SCALE_F; + unsigned int iremainder = len % SCALE_F; + unsigned long *ptmp = (unsigned long *)p; + + while (iquotient--) { + __asm__ __volatile__( + ".byte 0xf2, " REX_PRE "0xf, 0x38, 0xf1, 0xf1;" + :"=S"(crc) + :"0"(crc), "c"(*ptmp) + ); + ptmp++; + } + + if (iremainder) { + crc = crc32c_hw_byte(crc, (unsigned char *)ptmp, + iremainder); + } + + return crc; +} + + +/* toggle x86 flag-register bits, as per mask + return flags both in original and toggled state + */ +static void +toggle_x86_flags (long mask, long* orig, long* toggled) +{ + long forig = 0, fres = 0; + +#if defined (__i386__) + asm ( + "pushfl; popl %%eax; movl %%eax, %0; xorl %2, %%eax; " + "pushl %%eax; popfl; pushfl; popl %%eax; pushl %0; popfl " + : "=r" (forig), "=a" (fres) + : "m" (mask) + ); +#elif __x86_64__ + asm ( + "pushfq; popq %%rax; movq %%rax, %0; xorq %2, %%rax; " + "pushq %%rax; popfq; pushfq; popq %%rax; pushq %0; popfq " + : "=r" (forig), "=a" (fres) + : "m" (mask) + ); +#endif + + if (orig) *orig = forig; + if (toggled) *toggled = fres; + return; +} + + +/* is CPUID instruction available ? */ +static int +can_cpuid () +{ + long of = -1, tf = -1; + + /* x86 flag register masks */ + enum { + cpuf_AC = (1 << 18), /* bit 18 */ + cpuf_ID = (1 << 21) /* bit 21 */ + }; + + + /* check if AC (alignment) flag could be toggled: + if not - it's i386, thus no CPUID + */ + toggle_x86_flags (cpuf_AC, &of, &tf); + if ((of & cpuf_AC) == (tf & cpuf_AC)) { + return 0; + } + + /* next try toggling CPUID (ID) flag */ + toggle_x86_flags (cpuf_ID, &of, &tf); + if ((of & cpuf_ID) == (tf & cpuf_ID)) { + return 0; + } + + return 1; +} + + +/* retrieve CPUID data using info as the EAX key */ +static void +get_cpuid (long info, long* eax, long* ebx, long* ecx, long *edx) +{ + *eax = info; + +#if defined (__i386__) + asm __volatile__ ( + "movl %%ebx, %%edi; " /* must save ebx for 32-bit PIC code */ + "cpuid; " + "movl %%ebx, %%esi; " + "movl %%edi, %%ebx; " + : "+a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx) + : + : "%edi" + ); +#elif defined (__x86_64__) + asm __volatile__ ( + "cpuid; " + : "+a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) + ); +#endif +} + + +/* return 1=feature is available, 0=unavailable, 0>(errno) = error */ +int +cpu_has (unsigned int feature) +{ + long info = 1, reg[4] = {0,0,0,0}; + + if (!can_cpuid ()) + return -EINVAL; + + if (feature > LEN_cpu_ftr) + return -ERANGE; + + get_cpuid (info, ®[eAX], ®[eBX], ®[eCX], ®[eDX]); + + return (reg[cpu_ftr[feature].ri] & cpu_ftr[feature].mask) ? 1 : 0; +} + + +/* __EOF__ */ + diff --git a/core/fiber.m b/core/fiber.m index ed8091aebd04ec09a837fb48312c81ce84ac51e2..32f36d7be157bb2afe3e3b52b58e69782e4e2626 100644 --- a/core/fiber.m +++ b/core/fiber.m @@ -689,7 +689,7 @@ read_inbox(void) ssize_t fiber_bread(struct tbuf *buf, size_t at_least) { - ssize_t r; + ssize_t r = 0; tbuf_ensure(buf, MAX(cfg.readahead, at_least)); size_t stop_at = buf->size + at_least; diff --git a/core/log_io_remote.m b/core/log_io_remote.m index 4666796049a987faf17bf3f8c4d6aad90c0eaa5f..4b14427f11e57bf39662f34e5bc3209ffaabbe24 100644 --- a/core/log_io_remote.m +++ b/core/log_io_remote.m @@ -190,6 +190,7 @@ recovery_follow_remote(struct recovery_state *r, const char *remote) rc = sscanf(remote, "%31[^:]:%i", ip_addr, &port); assert(rc == 2); + (void)rc; if (inet_aton(ip_addr, &server) < 0) { say_syserror("inet_aton: %s", ip_addr); diff --git a/core/palloc.m b/core/palloc.m index 3b4bd325bd5366541848bd1dd7915b6007ad3ef8..3dfe17b2ddbb51060c96fee0597f8d4976b0afbd 100644 --- a/core/palloc.m +++ b/core/palloc.m @@ -366,19 +366,7 @@ palloc_stat(struct tbuf *buf) int chunks[class_count]; tbuf_printf(buf, "palloc statistic:" CRLF); - tbuf_printf(buf, " classes:" CRLF); - TAILQ_FOREACH(class, &classes, link) { - int free_chunks = 0; - SLIST_FOREACH(chunk, &class->chunks, free_link) - free_chunks++; - - tbuf_printf(buf, - " - { size: %"PRIu32 - ", free_chunks: %- 6i, busy_chunks: %- 6i }" CRLF, class->allocated_size, - free_chunks, class->chunks_count - free_chunks); - } tbuf_printf(buf, " pools:" CRLF); - SLIST_FOREACH(pool, &pools, link) { for (int i = 0; i < class_count; i++) chunks[i] = 0; @@ -392,18 +380,39 @@ palloc_stat(struct tbuf *buf) SLIST_FOREACH(chunk, &pool->chunks, busy_link) chunks[chunk->class->i]++; - int indent = 0; TAILQ_FOREACH(class, &classes, link) { if (chunks[class->i] == 0) continue; tbuf_printf(buf, " - { size: %"PRIu32", used: %i }" CRLF, class->allocated_size, chunks[class->i]); - - if (indent == 0) - indent = 19; } } } + tbuf_printf(buf, " classes:" CRLF); + TAILQ_FOREACH(class, &classes, link) { + int free_chunks = 0; + SLIST_FOREACH(chunk, &class->chunks, free_link) + free_chunks++; + + tbuf_printf(buf, + " - { size: %"PRIu32 + ", free_chunks: %- 6i, busy_chunks: %- 6i }" CRLF, class->allocated_size, + free_chunks, class->chunks_count - free_chunks); + } + u64 palloc_total = 0; + u64 palloc_used = 0; + SLIST_FOREACH(pool, &pools, link) { + SLIST_FOREACH(chunk, &pool->chunks, busy_link) { + palloc_total += chunk->size; + palloc_used += chunk->size - chunk->free; + } + SLIST_FOREACH(chunk, &pool->chunks, free_link) + palloc_total += chunk->size; + } + tbuf_printf(buf, " total:" CRLF); + tbuf_printf(buf, " - { occupied: %"PRIu64", used: %"PRIu64" }"CRLF, + palloc_total, palloc_used); + } void diff --git a/core/tarantool.m b/core/tarantool.m index c59e0da8f4843df5ed411607d9931f9bde98d38c..90ef0082a2ac22f005c22b395039728a3b1c9367 100644 --- a/core/tarantool.m +++ b/core/tarantool.m @@ -60,7 +60,7 @@ static pid_t master_pid; #define DEFAULT_CFG_FILENAME "tarantool.cfg" -#define DEFAULT_CFG INSTALL_PREFIX "/etc/" DEFAULT_CFG_FILENAME +#define DEFAULT_CFG SYSCONF_DIR "/" DEFAULT_CFG_FILENAME const char *cfg_filename = NULL; char *cfg_filename_fullpath = NULL; char *binary_filename; @@ -374,6 +374,8 @@ tarantool_free(void) #ifdef HAVE_BFD symbols_free(); #endif + if (tarantool_L) + tarantool_lua_close(tarantool_L); } static void @@ -464,6 +466,15 @@ main(int argc, char **argv) return 0; } + if (gopt_arg(opt, 'C', &cat_filename)) { + initialize_minimal(); + if (access(cat_filename, R_OK) == -1) { + panic("access(\"%s\"): %s", cat_filename, strerror(errno)); + exit(EX_OSFILE); + } + return mod_cat(cat_filename); + } + gopt_arg(opt, 'c', &cfg_filename); /* if config is not specified trying ./tarantool.cfg then /etc/tarantool.cfg */ if (cfg_filename == NULL) { @@ -568,15 +579,6 @@ main(int argc, char **argv) #endif } - if (gopt_arg(opt, 'C', &cat_filename)) { - initialize_minimal(); - if (access(cat_filename, R_OK) == -1) { - say_syserror("access(\"%s\")", cat_filename); - exit(EX_OSFILE); - } - return mod_cat(cat_filename); - } - if (gopt(opt, 'I')) { init_storage = true; initialize_minimal(); diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m index 52a78a1fe5b20696fd7b5fcf23a2900330a9f8d6..04f6dcb3de185da917a825c5aafdcf5b2db48716 100644 --- a/core/tarantool_lua.m +++ b/core/tarantool_lua.m @@ -36,6 +36,7 @@ #include "pickle.h" #include "fiber.h" +#include <ctype.h> #include TARANTOOL_CONFIG struct lua_State *tarantool_L; @@ -434,7 +435,7 @@ box_lua_fiber_run(void *arg __attribute__((unused))) * cancel() is synchronous. */ lua_settop(L, 0); /* pop any possible garbage */ - lua_pushboolean(L, 0); /* completion status */ + lua_pushboolean(L, false); /* completion status */ lua_pushstring(L, e->errmsg); /* error message */ } @finally { /* @@ -698,6 +699,37 @@ lbox_print(struct lua_State *L) return 0; } +/** + * Redefine lua 'pcall' built-in to correctly handle exceptions, + * produced by 'box' C functions. + * + * See Lua documentation on 'pcall' for additional information. + */ + +static int +lbox_pcall(struct lua_State *L) +{ + /* + * Lua pcall() returns true/false for completion status + * plus whatever the called function returns. + */ + @try { + lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); + lua_pushboolean(L, true); /* push completion status */ + lua_insert(L, 1); /* move 'true' to stack start */ + } @catch (ClientError *e) { + /* + * Note: FiberCancelException passes through this + * catch and thus leaves garbage on coroutine + * stack. + */ + lua_settop(L, 0); /* pop any possible garbage */ + lua_pushboolean(L, false); /* completion status */ + lua_pushstring(L, e->errmsg); /* error message */ + } + return lua_gettop(L); +} + /** A helper to register a single type metatable. */ void tarantool_lua_register_type(struct lua_State *L, const char *type_name, @@ -730,11 +762,18 @@ tarantool_lua_init() lua_pop(L, 1); tarantool_lua_register_type(L, fiberlib_name, lbox_fiber_meta); lua_register(L, "print", lbox_print); + lua_register(L, "pcall", lbox_pcall); L = mod_lua_init(L); lua_settop(L, 0); /* clear possible left-overs of init */ return L; } +void +tarantool_lua_close(struct lua_State *L) +{ + lua_close(L); /* collects garbage, invoking userdata gc */ +} + /** * Attempt to append 'return ' before the chunk: if the chunk is * an expression, this pushes results of the expression onto the @@ -790,6 +829,8 @@ is_string(const char *str) { if (strcmp(str, "true") == 0 || strcmp(str, "false") == 0) return false; + if (! isdigit(*str)) + return true; char *endptr; (void) strtod(str, &endptr); return *endptr != '\0'; diff --git a/core/util.m b/core/util.m index 83299dbb18f718fa9def70049968bc97b7a8c577..b78b43061ea3a9ad74ad3012bd5513474643bce1 100644 --- a/core/util.m +++ b/core/util.m @@ -219,7 +219,7 @@ assert_fail(const char *assertion, const char *file, unsigned int line, const ch #ifdef HAVE_BFD static struct symbol *symbols; -static size_t symbol_count; +static ssize_t symbol_count; int compare_symbol(const void *_a, const void *_b) @@ -321,29 +321,31 @@ void symbols_free() free(symbols); } +/** + * Sic: this assumes stack direction is from lowest to + * highest. + */ struct symbol * addr2symbol(void *addr) { - int low = 0, high = symbol_count, middle = -1; - struct symbol *ret, key = {.addr = addr}; + struct symbol key = {.addr = addr, .name = NULL, .end = NULL}; + struct symbol *low = symbols; + struct symbol *high = symbols + symbol_count; - while(low < high) { - middle = low + ((high - low) >> 1); - int diff = compare_symbol(symbols + middle, &key); + while (low + 1 < high) { /* there are at least two to choose from. */ + struct symbol *middle = low + ((high - low) >> 1); - if (diff < 0) { - low = middle + 1; - } else if (diff > 0) { + int diff = compare_symbol(&key, middle); + if (diff < 0) { /* key < middle. */ high = middle; - } else { - ret = symbols + middle; - goto out; + } else {/* key >= middle */ + low = middle; + if (diff == 0) + break; } } - ret = symbols + high - 1; -out: - if (middle != -1 && ret->addr <= key.addr && key.addr <= ret->end) - return ret; + if (low->addr <= key.addr && low->end >= key.addr) + return low; return NULL; } diff --git a/doc/box-protocol.txt b/doc/box-protocol.txt index d42c078fe931efce3e9ae9b5dac15403564cc0e0..c5e090da7b3fefd89bbb1a5fdd437ddacd1e393c 100644 --- a/doc/box-protocol.txt +++ b/doc/box-protocol.txt @@ -1,4 +1,4 @@ -; Mail.RU IPROTO protocol, Tarantool/Box subset. +Tarantool/Box IPROTO protocol. ; ; The latest version of this document can be found in ; tarantool source tree, doc/box-protocol.txt @@ -13,7 +13,7 @@ ; ; int8 - a single 8-bit byte (i.e. an octet) ; -; int32 - a 32-bit integer in big-endian format (Intel x86) +; int32 - a 32-bit integer in little-endian format (Intel x86) ; ; int32_varint - a 1 to 5 byte representation of an integer ; @@ -21,8 +21,8 @@ ; integer (BER stands for Basic Encoding Rules, but in reality it ; has little to do with ASN.1 standard). ; See http://en.wikipedia.org/wiki/LEB128 -; for encoding description, or core/pickle.c for implementation -; in tarantool. +; for encoding description, or core/pickle.m for implementation +; in Tarantool. ; All requests and responses utilize the same basic structure: @@ -193,6 +193,7 @@ <int32_varint> ::= <int8>+ ; ; SELECT may return zero, one or several tuples. +; CALL response is identical to one for SELECT. ; <select_response_body> starts with the number of found ; tuples: ; @@ -214,6 +215,7 @@ <fq_tuple> ::= <size><tuple> +; length of the variable part of the tuple (all tuple fields) <size> ::= <int32> ; @@ -274,7 +276,7 @@ ; on strings: ; 5 - implementation of Perl 'splice' command -<op_code> ::= <int8> # 0 | 1 | 2 | 3 +<op_code> ::= <int8> # 0 | 1 | 2 | 3 | 4 | 5 ; ; It's an error to specify an argument of a type that @@ -340,7 +342,7 @@ ; <return_code>. If it's not 0, it is followed by an ; error message. Otherwise, the response, just like in case of ; SELECT, is a sequence of <fq_tuple>s. -<call_response_body> ::= <count><fq_tuple> +<call_response_body> ::= <select_response_body> ; ; The server response, in addition to response header and body, @@ -351,7 +353,7 @@ <return_code> ::= <int32> -; Currently, the completion status is complementary: +; The completion status is complementary: ; it can be deduced from the error code. There are only ; 3 completion status codes in use: ; 0 - success; The only possible error code with this status is @@ -406,8 +408,8 @@ ; 0x00002702 -- ER_WAL_IO ; WAL I/O error ; -; Convenience that define hexadecimal constants for <int32> -; return codes (completion status + code) can be found in -; include/iproto.h. +; Convenience macros which define hexadecimal constants for +; <int32> return codes (completion status + code) can be found +; in include/iproto.h. ; ; vim: syntax=bnf diff --git a/doc/coding-style-shell.txt b/doc/coding-style-shell.txt new file mode 100644 index 0000000000000000000000000000000000000000..4ed2bc6776873d9352dc0893c495874f69682366 --- /dev/null +++ b/doc/coding-style-shell.txt @@ -0,0 +1,259 @@ + +Bourne Shell Coding Conventions +-------------------------------- + +Original version by Mike Shapiro and OpenSolaris Shell Project. +This document describes the shell coding style used for all the ON shell +script changes integrated into Solaris. + +All new shell code should conform to this coding standard, which is intended +to match our existing C coding standard. + +When in doubt, think "what would be the C-Style equivalent?" + +Basic Format +------------ + +Similar to cstyle, the basic format is that all lines are indented by TABs, +and continuation lines (which in the shell end with "\") are indented by +an equivalent number of TABs and then an additional four spaces, e.g. + +cp foo bar +cp some_realllllllllllllllly_realllllllllllllly_long_path \ + to_another_really_long_path + +If, For, and While +------------------ + +To match cstyle, the sh token equivalent to the C "{" should appear on +the same line, separated by a ";", as in: + +if [ $x = hello ]; then + echo $x +fi + +for i in 1 2 3; do + echo $i +done + +while [ $# -gt 0 ]; do + echo $1 + shift +done + +Test Built-in +------------- + +DO NOT use the test built-in. Sorry, executive decision. In our Bourne shell, +the test built-in is the same as the "[" built-in (if you don't believe me, +try "type test" or refer to usr/src/cmd/sh/msg.c). So please do not write: + +if test $# -gt 0; then + +instead use: + +if [ $# -gt 0 ]; then + +Single-line if-statements +------------------------- + +It is permissible to use && and || to construct shorthand for an "if" +statement in the case where the if statement has a single consequent line: + +[ $# -eq 0 ] && exit 0 + +instead of the longer: + +if [ $# -eq 0 ]; then + exit 0 +fi + +DO NOT combine && with { }, as in: + +[ $# -eq 0 ] && { + do something + do something else +} + +Use a complete "if-then-fi" construct for this instead. + +Infinite Loops +-------------- + +The proper way to write an infinite loop in the Bourne shell is to use +the ":" built-in, which evaluates to true (exit status 0). +This is better than using "true", because that is *not* a built-in in the +Bourne shell and thus runs /bin/true. + +while :; do + echo infinite loop +done + +Exit Status and If/While Statements +----------------------------------- + +Recall that "if" and "while" operate on the exit status of the statement +to be executed. In the shell, zero (0) means true and non-zero means false. +The exit status of the last command which was executed is available in +the $? variable. When using "if" and "while", it is typically not necessary +to use $? explicitly, as in: + +grep foo /etc/passwd >/dev/null 2>&1 +if [ $? -eq 0 ]; then + echo found +fi + +Instead, you can more concisely write: + +if grep foo /etc/passwd >/dev/null 2>&1; then + echo found +fi + +Or, when appropriate: +grep foo /etc/passwd >/dev/null 2>&1 && echo found + +DO NOT attempt to make pseudo-booleans by setting variables to "true" +and "false" and then running the variable as a command instead of using a +comparison test. This is non-idiomatic and confusing to many long-time +shell programmers. + +Use: + +good=true +if [[ $good = "true" ]] ; then + +Not: + +good=false +if $good ; then + +Variable References +------------------- + +Variable references begin with $ and *may* have their name enclosed in {}'s. +We prefer to only see the {}'s when required. +Do not spuriously enclose all your variable names in braces, like this: +foo=${bar} + +This is kind of like writing all your C variable assignments like this: +foo = (bar); + +It compiles, but it looks stupid. + +Braces are required around variable names in two specific cases: + +(1) when you are forming the string concatenation of your variable with +another string: + +[ $install = yes ] && root="/a/" || root="/" +hosts=${root}etc/inet/hosts + +and (2) when you are using one of the various substitution/assignment operators: + +echo ${BASEDIR:-/a} + +Variable Naming +--------------- + +We prefer that you adopt a shell variable naming scheme where capitalization +provides additional meaning (as in our C style): use CAPITAL letters for +variables that are exported into the environment, or are equivalent to C +constants or #defines. Use lowercase letters for other variable names: +BASEDIR=/a; export BASEDIR +argc=$# + +This helps your reader immediately understand the implication of modifying a +given variable (i.e. whether it will be inherited by child processes). + +Quoting +------- + +Quick review of the quoting basics: + +Single quotes ('') mean quote but do not expand variable or backquote +substitutions. +Double quotes ("") mean quote but allow expansion. +Backquotes (``) mean execute the command and substitute its standard output +(note: stderr is unchanged and may "leak" through unless properly redirected) + +Use whatever quotes are appropriate for your situation, but please do not +unnecessarily quote everything (also see 7 above). + +For example, references to variables controlled by your script do not have to +be quoted unless you are expecting your variable to expand to multiple tokens, +or to the empty string. + +However, any variable which contains values from outside the script, such as +user input or filenames, should be quoted to avoid errors from special +characters, including whitespace + +Testing for (Non-)Empty Strings +------------------------------- + +DO NOT test for (non-)/empty strings by comparing to "" or ''. ALWAYS use the +test operators -n (non-zero-length string) and -z (zero-length string): + +if [ -z "$foo" ]; then + echo 'you forgot to set $foo' +fi + +if [ -n "$BASEDIR" ]; then + echo "\$BASEDIR is set to $BASEDIR" +fi + +Commenting +---------- + +Shell comments are preceded by the '#' character. Place single-line comments +in the right-hand margin. Use an extra '#' above and below the comment in the +case of multi-line comments: +cp foo bar # Copy foo to bar + +# +# Modify the permissions on bar. We need to set them to root/sys +# in order to match the package prototype. +# +chown root bar +chgrp sys bar + +Pathnames +--------- + +It is always a good idea to be careful about $PATH settings and pathnames when +writing shell scripts. This allows them to function correctly even when the +user invoking your script has some strange $PATH set in their environment. + +There are two acceptable ways to do this: + +(1) make *all* command references in your script use explicit pathnames: +/usr/bin/chown root bar +/usr/bin/chgrp sys bar + +or (2) explicitly reset $PATH in your script: +PATH=/usr/bin; export PATH + +chown root bar +chgrp sys bar + +DO NOT use a mixture of (1) and (2) in the same script. +Pick one method and use it consistently. + +Command arguments +----------------- + +When passing user input to commands, if the first operand of a command is a +variable, use -- for any command that accepts this to flag the end of +arguments to avoid problems if the variable expands to a value startingwith -. + +Interpreter Magic +----------------- + +The proper interpreter magic for a shell script should be simply #!/bin/sh. + +End of file +----------- + +Following 2 lines should be placed at the end of file: + +# __EOF__ +<empty line> diff --git a/doc/developer/CMakeLists.txt b/doc/developer/CMakeLists.txt index 691e16c82e2c17018469853a29576e56238a4f06..d4cd5d356bea6d223fb13811f1965042579a862d 100644 --- a/doc/developer/CMakeLists.txt +++ b/doc/developer/CMakeLists.txt @@ -5,4 +5,7 @@ add_custom_target(dev-html ALL ${CMAKE_SOURCE_DIR}/doc/user/tnt-html.xsl developer.xml) +add_custom_target(dev-check ALL + COMMAND jing http://docbook.org/xml/5.0/rng/docbookxi.rng developer.xml + ) diff --git a/doc/developer/developer.xml b/doc/developer/developer.xml index 2b54754e3f2a9c49c69124e22f27001fec3b03f2..b6eea619c39e799225f888b190e3d6e75d8f320c 100644 --- a/doc/developer/developer.xml +++ b/doc/developer/developer.xml @@ -7,10 +7,24 @@ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0"> <title>Tarantool/Box Developer Guide</title> +<preface> + <title>What documentation there is</title> + <para> + Tarantool documentation consists of: + <itemizedlist> + <listitem><para>a user guide</para></listitem> + <listitem><para>a developer guide (you're reading it)</para></listitem> + <listitem><para>protocol description box-protocol.txt</para></listitem> + <listitem><para>coding style guides for Lua, Objective C, C, Python (for + other connectors, we use conventions of the connector programming + language community)</para></listitem> + </itemizedlist> + </para> +</preface> <chapter> <title>Compiling</title> <section> -<title>How to fix the compile time error about missing confetti</title> +<title>How to fix a compile time error about missing confetti</title> <para> An error about missing confetti: <programlisting> @@ -34,58 +48,81 @@ The other alternative, if you have actually modified </para> </section> -</chapter> - -To build documentation, you'll need: - -xsltproc -docbook5-xml -docbook-xsl-ns -libsaxon-java- for saxon processing -libxml-commons-resolver1.1-java -libxerces2-java -libxslthl-java -<!-- - TOC: - - Introduction - What it can basically do; - - - Installation and Running - Command line options reference - - Data console and administrative console - Data operations - Administrative console operations - Format of response - Setting up replication - Master-slave replication - - Support of memcached protocol - - Configuration reference - - Log file messages - - Connectors - C - Ruby - Example - Perl +<section> +<title>How to build the XML manual</title> +<para> +To build XML manual, you'll need: +<itemizedlist> - Frequently Asked Questions - Q. What's on your roadmap? - Q. I found a bug. What to do? +<listitem><para>xsltproc</para></listitem> +<listitem><para>docbook5-xml</para></listitem> +<listitem><para>docbook-xsl-ns</para></listitem> +<listitem><para>libsaxon-java- for saxon processing</para></listitem> +<listitem><para>libxml-commons-resolver1.1-java</para></listitem> +<listitem><para>libxerces2-java</para></listitem> +<listitem><para>libxslthl-java</para></listitem> +</itemizedlist> +Once all pre-requisites are met, <computeroutput>make html</computeroutput> +to build the user guide. +</para> +</section> +</chapter> +<chapter> +<title>Release management</title> + <section><title>How to make a minor release</title> + <para> + <programlisting>git tag -a 1.4.4 -m "Next minor in 1.4 series" +vim CMakeLists.txt # edit CPACK_PACKAGE_VERSION_PATCH +git push --tags +</programlisting> +Update the Web site in doc/www-data. +</para> +<para> +Go to launchpad and move all "Fix committed" bugs to "Fix released". +</para> +<para> +Update all blueprints, upload the ChangeLog, based on <prompt>git log</prompt>output. +The ChangeLog must only include items which are mentioned as bugs +or blueprints on Launchpad. If anything significant is there, +which is not mentioned, something went wrong in release planning +and the release should be held up until this is cleared. + </para> +<para> +Click 'Release milestone'. Create a milestone for the next minor +release. Alert the driver to target bugs and blueprints +to the new milestone. +</para> +<para> +Upload the milestone from http://tarantool.org/dist to Launchpad. Use the .src.tar.gz build. +</para> +<para> +</para> + </section> +</chapter> +<chapter> +<title>Developer guidelines</title> +<section> +<title>How to work on a bug</title> +<para>Any defect, even minor, if it changes the user-visible +server behavior, needs a bug report. Report a bug at +bugs.launchpad.net/tarantool. +When reporting a bug, try to come up with a test case right away. +Set the current maintenance milestone for the bug fix, and specify the series. +Assign the bug to yourself. Put the status to 'In progress' +Once the patch is ready, put the bug the bug to 'In review' and solicit a review for the fix. +Once there is a positive code review, push the patch +and set the status to 'Fix committed' - Appendixes: - Configuration parameters index - Server error codes - Glossary of terms ---> +Patches for bugs should contain a reference to the respective +Launchpad bug page or at least bug id. Each patch should have a +test, unless coming up with one is difficult in the current +framework, in which case QA should be alerted. +</para> +</section> +</chapter> </book> <!-- diff --git a/doc/sql.txt b/doc/sql.txt index 9cbbd9a7de20e3aab1203d77a8356fb59a1169f8..ccf121755c3c5f9a74fb2ef4de5e119dca0b6317 100644 --- a/doc/sql.txt +++ b/doc/sql.txt @@ -9,10 +9,12 @@ ; case-insensitive, so this convention is present only to imporve ; legibility of the BNF. -<sql> ::= <insert> | <update> | <delete> | <select> +<sql> ::= <insert> | <replace> | <update> | <delete> | <select> <insert> ::= INSERT [INTO] <ident> VALUES <value_list> +<insert> ::= REPLACE [INTO] <ident> VALUES <value_list> + <update> ::= UPDATE <ident> SET <update_list> <simple_where> <delete> ::= DELETE FROM <ident> <simple_where> diff --git a/doc/user/CMakeLists.txt b/doc/user/CMakeLists.txt index 5421db1da1d7f97e0f7c52cb9c77299d452d92cf..1750c45970e16b55eefaa03388d8764543f1cc79 100644 --- a/doc/user/CMakeLists.txt +++ b/doc/user/CMakeLists.txt @@ -21,7 +21,7 @@ add_custom_target(relink user.xml) add_custom_target(html-saxon - COMMAND java -cp "/usr/share/java/saxon.jar:/usr/share/java/xml-resolver.jar:/usr/share/java/docbook-xsl-saxon.jar:/usr/share/java/xercesImpl.jar:/etc/xml/resolver:/usr/share/java/xslthl.jar" + COMMAND java -cp "/usr/share/java/saxon.jar:/usr/share/java/xml-resolver.jar:/usr/share/java/docbook-xsl-saxon.jar:/usr/share/java/xercesImpl.jar:/etc/xml/resolver:/usr/share/java/xslthl.jar:/usr/share/java/xml-commons-resolver-1.1.jar" -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration diff --git a/doc/user/configuration-reference.xml b/doc/user/configuration-reference.xml index 843d9bb6d3ac4fb960551015eb185d7305aea958..311ab5cf7be3ccf9d19d79560226862762deff09 100644 --- a/doc/user/configuration-reference.xml +++ b/doc/user/configuration-reference.xml @@ -501,7 +501,7 @@ tarantool_box: primary@sessions pri:33013 sec:33014 adm:33015</programlisting> </row> <row> - <entry>rows_per_wal</entry> + <entry xml:id="rows_per_wal" xreflabel="rows_per_wal">rows_per_wal</entry> <entry>integer</entry> <entry>500000</entry> <entry>no</entry> @@ -530,7 +530,7 @@ tarantool_box: primary@sessions pri:33013 sec:33014 adm:33015</programlisting> </row> <row> - <entry>wal_writer_inbox_size</entry> + <entry xml:id="wal_writer_inbox_size" xreflabel="wal_writer_inbox_size">wal_writer_inbox_size</entry> <entry>integer</entry> <entry>128</entry> <entry>no</entry> diff --git a/doc/user/connectors.xml b/doc/user/connectors.xml index b97c6eaec6a949785bb200ca86033474292d6041..fb56717dc01f20aedb4c0862faa2105ee42abcfc 100644 --- a/doc/user/connectors.xml +++ b/doc/user/connectors.xml @@ -12,7 +12,7 @@ C, Perl, Ruby, PHP and Python. </para></blockquote> -<para>All connectors are located in srcdir/connector directory. Apart from the native Tarantool client driver, you can always use a <emphasis role="strong">Memcached</emphasis> driver of your choice, after enabling Memcached protocol in the configuration file.</para> +<para>Apart from the native Tarantool client driver, you can always use a <emphasis role="strong">Memcached</emphasis> driver of your choice, after enabling Memcached protocol in the configuration file.</para> <section> <title>C</title> @@ -25,129 +25,23 @@ <section> <title>Perl</title> <para> - The perl client is located in <link - xlink:href="https://github.com/mailru/tarantool/blob/master/connector/perl/lib/"><filename>connector/perl/lib/</filename></link>. - <orderedlist> - - <listitem> - <simpara><emphasis role="strong">new</emphasis></simpara> -<programlisting language="perl"><![CDATA[ - my $box = MR::SilverBox->new({ - spaces => [ { - indexes => [ { - index_name => 'primary_id', - keys => [0], - }, { - index_name => 'primary_email', - keys => [1], - }, ], - space => 0, - format => 'l& SSLL', - default_index => 'primary_id', - } ], - }, - servers => $server}) -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Insert</emphasis></simpara> -<programlisting language="perl"> - $box->Insert(@tuple); -</programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Select</emphasis></simpara> -<programlisting language="perl"><![CDATA[ - [\%tuple1, \%tuple2, ...] = $box->Select(@id); - \%tuple = $box->Select($id); - [\@tuple1, \@tuple2, ...] = $box->Select(@id, {raw => 1}); - \@tuple = $box->Select($id, {raw => 1}); - $box->Select($email, {use_index => 1}); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Update</emphasis> - accepts parameters in the last argument just like Select:</simpara> -<programlisting language="perl"><![CDATA[ - $key = 1; # key, ID of user by default - $field_num = 2; # posititon in tuple, starts from 0, must be >= 1 - $value = pack('L', 123); # integer values must be packed - $box->Update($key, $field_num, $value); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Delete</emphasis></simpara> -<programlisting language="perl"><![CDATA[ - $box->Delete($key); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">AndXorAdd</emphasis> - transforms the tuple field at position <quote>$field_num</quote> - according to formula <quote>field= ((field & $and_mask) ^ $xor_mask) + $add_value</quote>. - </simpara> -<programlisting language="perl"><![CDATA[ - $box->AndXorAdd($key, $field_num, $and_mask, $xor_mask, $add_value); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Bit</emphasis> - performs a bitwise operation on field at position - <quote>$field_num</quote>. Unused arguments can be omitted. - Note: <quote>set</quote> has a higher precedence than - <quote>bit_set</quote> and <quote>bit_clear</quote>. - </simpara> -<programlisting language="perl"><![CDATA[ - $box->Bit($key, $field_num, bit_clear => $clear_mask, bit_set => $set_mask, set => $set_value); - $box->Bit($key, $field_num, bit_set => $set_mask); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Num</emphasis> - performs a numeric update operation on field at position - <quote>$field_num</quote>. Unused arguments can be omitted. - Note: again, <quote>set</quote> has a higher precedence than - <quote>num_add</quote> and <quote>num_sub</quote>. - </simpara> -<programlisting language="perl"><![CDATA[ - $box->Num($key, $field_num, set => $set_value, num_add => $add_value, num_sub => $sub_value); -]]></programlisting> - </listitem> - - <listitem> - <simpara><emphasis role="strong">Flag</emphasis> - sets or clears flags on a tuple, the calling convention - is the same as for <quote>Bit</quote> and <quote>Num</quote> - operations. - </simpara> -<programlisting language="perl"><![CDATA[ - $box->Flags(bit_set => $set_mask, bit_clear => $clear_mask); -]]></programlisting> - </listitem> - - </orderedlist> - + Please refer to CPAN module <link xlink:href='http://search.cpan.org/search?query=Tarantool&mode=all'>MR::Tarantool::Box</link>. </para> </section> <section> <title>PHP</title> <para> - @tba + Please see <link + xlink:href="https://github.com/mailru/tarantool/blob/master/connector/php"><filename>connector/php</filename></link> in the source tree. </para> </section> <section> <title>Python</title> <para> - @tba + Please see <link + xlink:href="https://github.com/mailru/tarantool-python"><filename>http://github.com/mailru/tarantool-python</filename></link>. </para> </section> @@ -156,105 +50,8 @@ <para> You need <emphasis role="strong">Ruby 1.9</emphasis> or later to use this connector. Connector sources are located in <link - xlink:href="https://github.com/mailru/tarantool/blob/master/connector/ruby/box.rb"><filename>connector/ruby/box.rb</filename></link>. + xlink:href="https://github.com/mailru/tarantool-ruby"><filename>http://github.com/mailru/tarantool-ruby</filename></link>. </para> - <para> - Assume, for the sake of example, that Tarantool has the - following space configuration: -<programlisting language="c"> -primary_port = 33013 -admin_port = 33015 -log_level = 3 -slab_alloc_arena = 0.1 - -space[0].enabled = 1 -space[0].index[0].type = "NUM" -space[0].index[0].key_fields[0].fieldno = 0 - -space[0].index[1].type = "STR" -space[0].index[1].key_fields[0].fieldno = 1 -</programlisting> - The only defined space will be used to store user - account information, such as id, name, email, and other - properties. User ID is used for the primary key, but - it's also possible to find a user by name. - </para> - <para>In Ruby, a helper class is defined to present - Tarantool to the rest of the application as a typical object - container. -<example> -<title>userbox.rb</title> -<programlisting language="ruby"><![CDATA[require 'box' - -class UserBox < Box - def initialize(host) - super(host, :space => 0) - end - - def insert(user) - case user - when Hash then super [user[:uid], user[:email], user[:name], user[:apple_count]] - when Array then super user - else fail "don't know what to do with #{user.class}" - end - end - - def update_fields(key, *ops) - mapping = {:uid => 0, :email => 1, :name => 2, :apple_count => 3} - ops.map do |op| - op[0] = mapping[op[0]] if op.is_a? Array - end - - super key, *ops - end - - def unpack_tuple!(data) - tuple = super data - { :uid => tuple[0].unpack(?L)[0], - :email => tuple[1], - :name => tuple[2], - :apple_count => tuple[3].unpack(?L)[0] - } - end -end]]></programlisting></example> - Here's how this helper class can be used: -<programlisting><prompt>kostja@shmita:~$ </prompt><command>irb</command> -<![CDATA[>> # Connect to the server ->> require 'userbox' -=> true ->> b = UserBox.new 'localhost:33013' -=> #<UserBox:0x870fd48 @space=1, @end_point=["localhost", 33013], @sock=#<TCPSocket:0x870f85c> ->> # Insert a few users ->> b.insert :uid => 1, :email => 'pupkin@mail.ru', :name => 'Vasya', :apple_count => 1 -=> 1 ->> b.insert :uid => 2, :email => 'masha@mail.ru', :name => 'Masha', :apple_count => 0 -=> 1 ->> b.insert :uid => 3, :email => 'petya@mail.ru', :name => 'Petya', :apple_count => 3 -=> 1 ->> # Perform selects ->> b.select 1 -=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}] ->> b.select 1,2,3 -=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>2, :email=>"masha@mail.ru", :name=>"Masha", :apple_count=>0}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}] ->> # It's possible to retrieve records by email using second index ->> b.select 'pupkin@mail.ru', 'petya@mail.ru', :index => 1 -=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}] -Delete ->> b.delete 2 -=> 1 ->> # Update values ->> b.update_fields 1, [:apple_count, :add, 2 ] -=> 1 ->> b.select 1 -=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>3}] ->> # It's possible to do several updates in a single atomic command ->> b.update_fields 3, [:apple_count, :add, 10], [:name, :set, "foobar"] -=> 1 ->> b.select 3 -=> [{:uid=>3, :email=>"petya@mail.ru", :name=>"foobar", :apple_count=>13}] -]]> -</programlisting> - </para> </section> </chapter> diff --git a/doc/user/data-and-persistence.xml b/doc/user/data-and-persistence.xml new file mode 100644 index 0000000000000000000000000000000000000000..b2a61856ad8d3ce9ff50d5d99a1732bedfa84816 --- /dev/null +++ b/doc/user/data-and-persistence.xml @@ -0,0 +1,22 @@ +<!DOCTYPE chapter [ +<!ENTITY % tnt SYSTEM "../tnt.ent"> +%tnt; +]> +<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + xml:id="data-and-persistence"> +<title>Data model and data persitence</title> +<blockquote><para> + This chapter describes how Tarantool stores + values and what operations with data it supports. +</para></blockquote> + +<xi:include href="data-model.xml"/> +<xi:include href="persistence-architecture.xml"/> + +</chapter> +<!-- +vim: tw=66 syntax=docbk +vim: spell spelllang=en_us +--> diff --git a/doc/user/data-model.xml b/doc/user/data-model.xml index 17268baff21ee31b1d8e7059d0f412621e389981..d8d193e5e95bde5677451fb343d0624a45903398 100644 --- a/doc/user/data-model.xml +++ b/doc/user/data-model.xml @@ -1,15 +1,10 @@ -<!DOCTYPE chapter [ +<!DOCTYPE section [ <!ENTITY % tnt SYSTEM "../tnt.ent"> %tnt; ]> -<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" +<section xmlns="http://docbook.org/ns/docbook" version="5.0" xmlns:xlink="http://www.w3.org/1999/xlink"> <title>Dynamic data model</title> -<blockquote><para> - This chapter describes how Tarantool stores - values and what operations with data it supports. -</para></blockquote> - <para> Tarantool data is organized in <emphasis>tuples</emphasis>. Tuple length is varying: a tuple can contain any number @@ -62,8 +57,8 @@ [1] localhost> insert into t0 values ('hello') An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters' - localhost> insert into t0 values (1, 'hello') - Insert OK, 1 row affected + localhost> replace into t0 values (1, 'hello') + Replace OK, 1 row affected localhost> select * from t0 where k0=1 Found 1 tuple: [1, 'hello'] @@ -93,9 +88,9 @@ is, therefore, mandatory. </para></listitem> <listitem><para> - INSERT replaces data when a tuple with given - primary key already exists. Such replace can insert - a tuple of different cardinality. + REPLACE replaces data when a + tuple with given primary key already exists. Such replace + can insert a tuple of different cardinality. </para></listitem> </itemizedlist> </para> @@ -115,7 +110,7 @@ xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt" xlink:title="A complete BNF of Tarantool client/server protocol">doc/protocol.txt</filename>. </para> -</chapter> +</section> <!-- vim: tw=66 syntax=docbk vim: spell spelllang=en_us diff --git a/doc/user/language-reference.xml b/doc/user/language-reference.xml index 519fa3346164cb677161882b7db163718fc47730..8a8bcb7b8a18fdb62ee09abe69790ae3983d9194 100644 --- a/doc/user/language-reference.xml +++ b/doc/user/language-reference.xml @@ -164,8 +164,26 @@ updates, there are going to be page splits, and therefore you need to have some extra free memory to run this command. 15%-30% of <olink targetptr="slab_alloc_arena"/> - is, on average, sufficient. - </para></listitem> + is, on average, sufficient. This statement waits until a + snapshot is taken and returns operation result. For + example: +<programlisting>localhost> show info +--- +info: + version: "1.4.4" + lsn: 843301 +... +localhost> save snapshot +--- +ok +... +localhost> save snapshot +--- +fail: can't save snapshot, errno 17 (File exists) +... +</programlisting> + </para> + </listitem> </varlistentry> @@ -202,7 +220,7 @@ localhost> show info --- info: - version: "1.4.0-30-ge500b95" + version: "1.4.4" uptime: 441524 pid: 16180 wal_writer_pid: 16182 @@ -224,11 +242,12 @@ info: <para> <emphasis role="strong">recovery_last_update</emphasis> is the wall clock time of the last change recorded in the - write ahead log. + write ahead log. To convert it to human-readable time, + you can use <command>date -d@<replaceable>1306964594.980</replaceable></command>. </para> <para> <emphasis role="strong">status</emphasis> is - either "primary" or "hot_standby/<hostname>". + either "primary" or "replica/<hostname>". </para> </listitem> diff --git a/doc/user/persistence-architecture.xml b/doc/user/persistence-architecture.xml new file mode 100644 index 0000000000000000000000000000000000000000..2147c536581e3efa0937e4c862bc6f48bd524e38 --- /dev/null +++ b/doc/user/persistence-architecture.xml @@ -0,0 +1,85 @@ +<!DOCTYPE section [ +<!ENTITY % tnt SYSTEM "../tnt.ent"> +%tnt; +]> +<section xmlns="http://docbook.org/ns/docbook" version="5.0" + xmlns:xlink="http://www.w3.org/1999/xlink"> +<title>Data persistence</title> +<para> +To maintain data persistence, Tarantool writes each data change +request (INSERT, UPDATE, DELETE) into a write ahead log. WAL +files have extension <filename>.xlog</filename> and are stored in <olink +targetptr="wal_dir"/>. A new WAL file is created for every <olink +targetptr="rows_per_wal"/> records. Each INSERT, UPDATE or DELETE +gets assigned a continuously growing 64-bit log sequence number. The name of the log file is based on the log sequence +number of the first record this file contains. +</para> + +<para>Apart from a log sequence number and the data change request +(its format is the same as in the binary protocol and is described +in <link +xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt"><filename>doc/box-protocol.txt</filename></link>), +each WAL record contains a checksum and a UNIX time stamp. +</para> + +<para> +Tarantool proceeds requests atomically: a change is either +accepted and recorded in the WAL, or is rejected wholesale. +Let's clarify how this happens, using REPLACE command as an +example: +<orderedlist> + <listitem><para> + The server tries to locate an old tuple identified by + the same primary key. If found, the tuple is remembered for + later. + </para></listitem> + <listitem><para> + The new tuple is <emphasis>validated</emphasis>. If it + violates any of the unique key constraints, misses + any of indexed fields, or an indexed field type does not + match index type, the change is aborted. + </para></listitem> + <listitem><para> + The new tuple is marked 'invisible' and is added to + the primary and secondary indexes. + </para></listitem> + <listitem><para> + A message is sent to a separate <quote>wal_writer</quote> + process requesting that the change is recorded in the WAL. + The fiber associate with the current connection is scheduled + off CPU until an acknowledgment is received from the WAL + writer. + </para></listitem> + <listitem><para> + Upon success, 'invisible' flag is cleared + and the old tuple is deleted. A response is sent to the + client. Upon failure, the new tuple is removed and <olink + targetptr="ER_WAL_IO"/> error is sent to the client. + </para></listitem> +</orderedlist> +</para> + +<para> +Communication between master and WAL writer processes is asynchronous. +It is implemented using 'inbox' paradigm, similar to +process inboxes in Erlang, from which it was derived. +Asynchronous but reliable message passing between processes +allows Tarantool to continue handling requests regardless of disk +throughput. SELECT performance, provided SELECTs are run in their +own connections, is unaffected by disk load. +</para> + +<para> +The size of each process' inbox is fixed. In particular, +the WAL writer inbox can hold only <olink targetptr="wal_writer_inbox_size"/> +messages. This can pose a practical problem when thousands of +connections perform updates — the WAL writer inbox can become full. +Once this happens, the server aborts any update for which +sending a WAL message has failed with <olink targetptr="ER_WAL_IO"/> +error. +</para> +</section> +<!-- +vim: tw=66 syntax=docbk +vim: spell spelllang=en_us +--> diff --git a/doc/user/preface.xml b/doc/user/preface.xml index b0d37a00c86ffe8d4b5eaf58f18213ffb7e30fff..29b9f3f26102dd380f63add747911fa2bddc0e7e 100644 --- a/doc/user/preface.xml +++ b/doc/user/preface.xml @@ -67,6 +67,14 @@ under 10%. </para> + <para> + The key feature of Tarantool is <emphasis role="strong">support + for stored procedures</emphasis>, which + can access and modify data atomically. With stored procedures, + it's possible to turn Tarantool into a highly + flexible and extremely fast social web application server. + </para> + <para> <emphasis role="strong">The software is production-ready</emphasis>. Tarantool has been developed and is actively used at @@ -96,7 +104,7 @@ This manual is written in <citetitle xlink:href="http://www.docbook.org/tdg5/en/html/docbook.html">DocBook 5</citetitle> XML markup language and is using the standard <citetitle - xlink:href="http://docbook.sourceforge.net/release/xsl/current/doc/">DocbBook + xlink:href="http://docbook.sourceforge.net/release/xsl/current/doc/">DocBook XSL</citetitle> formatting conventions:</para> <para> UNIX shell command input is prefixed with '$ ' and is diff --git a/doc/user/stored-programs.xml b/doc/user/stored-programs.xml index 6f1710f500c90212148ed0f01e13e760eb089698..d6a10205d034a75d9d68945014485fa5f18a77d7 100644 --- a/doc/user/stored-programs.xml +++ b/doc/user/stored-programs.xml @@ -9,7 +9,8 @@ <title>Writing stored procedures in Lua</title> <blockquote> <para> - Lua is a light-weight, multi-paradigm embeddable language. + <link xlink:href="http://www.lua.org">Lua</link> + is a light-weight, multi-paradigm, embeddable language. Stored procedures in Lua can be used to implement data manipulation patterns or data structures. A server-side procedure written in Lua can select and modify @@ -18,13 +19,10 @@ alter and drop Lua procedures. </para> </blockquote> - <para> Procedures can be invoked both from the administrative console and using the binary protocol, for example: -<programlisting> -<computeroutput> -localhost> lua function f1() return 'hello' end +<programlisting><computeroutput>localhost> lua function f1() return 'hello' end --- ... localhost> call f1() @@ -32,9 +30,9 @@ Found 1 tuple: ['hello'] </computeroutput> </programlisting> - In the language of the administrative console + In the language of the administrative console <olink targetptr="lua-command" /> evaluates an arbitrary - Lua chunk. "CALL" is the SQL standard statement used + Lua chunk. CALL is the SQL standard statement used to represent CALL command of the binary protocol. In the example above, a Lua procedure is first defined @@ -45,9 +43,7 @@ Found 1 tuple: administrative console, the newly created <code language="Pascal">function f1()</code> can be called there too: -<programlisting> -<computeroutput> -localhost> lua f1() +<programlisting><computeroutput>localhost> lua f1() --- - hello ... @@ -59,38 +55,110 @@ localhost> lua "hello".." world" --- - hello world ... -</computeroutput> -</programlisting> +</computeroutput></programlisting> + </para> + <para> + There is a single global instance of Lua interpreter, which is + shared across all connections. Anything prefixed with + <code>lua </code> on the administrative console is sent + directly to this interpreter. Any change of the interpreter + state is immediately available to all client connections. + </para> + <para> + Each connection, however, is using its own Lua + <emphasis>coroutine</emphasis> — a mechanism, akin to + Tarantool <emphasis>fibers</emphasis>. A coroutine has an + own execution stack and a Lua <emphasis>closure</emphasis> + — set of local variables and definitions. </para> <para> - There is a single global Lua interpreter state, which is - shared across all connections. Each connection, however, is - running in its own Lua <emphasis>thread</emphasis> — a mechanism, akin to - Tarantool <emphasis>fibers</emphasis>. - Anything prefixed with <code>lua </code> on the administrative console - is sent directly to the interpreter. In the binary protocol, - however, it is only possible to invoke Lua functions, but not - define or modify them. - A special command code designates invocation of a stored - program in the binary protocol. The tuple, sent as argument - of the command, is passed into the stored procedure, each - field of the tuple converted to a string parameter of the - procedure. As long as currently Tarantool tuples are - type-agnostic, Lua strings are chosen as the transport media - between the server and the interpreter. + In the binary protocol, it's only possible to <emphasis + role="strong">invoke</emphasis> existing + procedures, but not <emphasis role="strong">define</emphasis> + or <emphasis role="strong">alter</emphasis> them. + CALL request packet contains CALL command code (22), the name + of a procedure to be called, and a tuple for procedure + arguments. Currently, Tarantool tuples are type-agnostic, + thus each field of the tuple is passed into the procedure + as an argument of type <quote>string</quote>. For example: +<programlisting><computeroutput>kostja@atlas:~$ cat arg.lua +function f1(a) + local s = a + if type(a) == 'string' then + s = '' + for i=1, #a, 1 do + s = s..string.format('0x%x ', string.byte(a, i)) + end + end + return type(a), s +end +kostja@atlas:~$ tarantool +localhost> lua dofile('arg.lua') +--- +... +localhost> lua f1('1234') +--- + - string + - 0x31 0x32 0x33 0x34 +... +localhost> call f1('1234') +Call OK, 2 rows affected +['string'] +['0x31 0x32 0x33 0x34 '] +localhost> lua f1(1234) +--- + - number + - 1234 +... +localhost> call f1(1234) +Call OK, 2 rows affected +['string'] +['0xd2 0x4 0x0 0x0 ']</computeroutput></programlisting> + In the above example, the way the procedure receives its + argument is identical in two protocols, when the argument is a + string. A numeric field, however, when submitted via the + binary protocol, is seen by the procedure as + a 4-byte blob, not as a Lua <quote>number</quote> type. + </para> + <para>In addition to conventional method invocation, + Lua provides object-oriented syntax. Access to the latter is + available on the administrative console only: +<programlisting><computeroutput>localhost> lua box.space[0]:truncate() +--- +... +localhost> call box.space[0]:truncate() +error: 1:15 expected '(' +</computeroutput></programlisting> </para> <para> Every value, returned from a stored function by means of - <code>return</code> clause, is converted to Tarantool/Box tuple - and sent back to the client in binary form. + <code>return</code> clause, is converted to a Tarantool/Box tuple. + Tuples are returned as such, in binary form; a Lua scalar, such as + a string or an integer, is converted to a tuple with only + one field. When the returned value is a <emphasis>Lua + table</emphasis>, the resulting tuple contains only table + values, but not keys. </para> <para> - When a function in Lua terminates with an error, it is - returned to the client as <olink targetptr="ER_PROC_LUA" /> + When a function in Lua terminates with an error, the error + is sent to the client as <olink targetptr="ER_PROC_LUA" /> return code, with the original error message preserved. Similarly, an error occurred inside Tarantool (observed on the client as an error code), when happens during execution of a - Lua procedure, produces a genuine Lua exception. + Lua procedure, produces a genuine Lua error: +<programlisting><computeroutput>localhost> lua function f1() error("oops") end +--- +... +localhost> call f1() +Call ERROR, Lua error: [string "function f1() error("oops") end"]:1: oops (ER_PROC_LUA) +localhost> call box.insert('99', 1, 'test') +Call ERROR, Space 99 is disabled (ER_SPACE_DISABLED) +localhost> lua pcall(box.insert, 99, 1, 'test') +--- + - false + - Space 99 is disabled +... +</computeroutput></programlisting> </para> <para> It's possible not only to invoke trivial Lua code, but call @@ -126,54 +194,57 @@ pack: function <listitem><para> libraries, such as <code>cfg, space, fiber, index, tuple</code>, to access server configuration, create, resume and - interrupt fibers, inspect content of spaces, indexes + interrupt fibers, inspect contents of spaces, indexes and tuples. </para></listitem> </itemizedlist> </para> <variablelist> - <title>Package <code>box</code> function index</title> - + <title>Package <code xml:id="box" xreflabel="box">box</code> function index</title> <varlistentry> <term> <emphasis role="lua">box.process(op, request)</emphasis> </term> <listitem> <para> - The main extension provided to Lua by - Tarantool/Box — ability to call - INSERT/UPDATE/SELECT/DELETE from within a Lua - procedure. + Process a request passed in as a binary string. + This is an entry point into the server request + processor. It allows to insert, update, + select and delete tuples from within a Lua procedure. </para> <para> This is a low-level API, and it expects all arguments to be packed in accordance - with the binary protocol format (iproto - header excluded). Normally there is no need + with the binary protocol (iproto + header excluded). Normally, there is no need to use <code>box.process()</code> directly: - <code>box.select(), box.update(), ...</code> + <code>box.select(), box.update()</code> and other convenience wrappers invoke <code>box.process()</code> with correctly packed arguments. <bridgehead renderas="sect4">Parameters</bridgehead> <simplelist> - <member><code>op</code> — number, Tarantool/Box command code, see + <member><code>op</code> — number, any + Tarantool/Box command code, except 22 (CALL). See <link xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt"> - <filename>doc/box-protocol.txt</filename></link>, + <filename>doc/box-protocol.txt</filename></link>. </member> - <member><code>request</code> — a request packed in binary format</member> + <member><code>request</code> — command + arguments packed in binary format.</member> </simplelist> <bridgehead renderas="sect4">Returns</bridgehead> This function returns zero or more tuples. In Lua, a - tuple is represented by + tuple is represented by a <emphasis>userdata</emphasis> object of type - <code>box.tuple</code>. If a Lua procedure - is called from the administrative console, tuples - are converted to YAML. When called from the binary + <code xlink:href="#box.tuple">box.tuple</code>. If + a Lua procedure is called from the administrative + console, returned tuples are printed out in YAML + format. When called from the binary protocol, the binary format is used. <bridgehead renderas="sect4">Errors</bridgehead> - Any server error produced by the executed command. + Any server error produced by the executed + command. </para> </listitem> </varlistentry> @@ -184,19 +255,23 @@ pack: function </term> <listitem> <para> - Select a tuple in the given namespace by key. A + Select a tuple in the given space. A wrapper around <code>box.process()</code>. <bridgehead renderas="sect4">Parameters</bridgehead> <simplelist> - <member><code>space_no</code> — namespace id, + <member><code>space_no</code> — space id, </member> <member><code>index_no</code> — index number in the - namespace,</member> - <member><code>...</code> ‐ possibly compound key. + space,</member> + <member><code>...</code>— index key, + possibly compound. </member> </simplelist> <bridgehead renderas="sect4">Returns</bridgehead> Returns zero or more tuples. + <bridgehead renderas="sect4">Errors</bridgehead> + Same as in <code>box.process()</code>. Any error + results in a Lua exception. <bridgehead renderas="sect4">Example</bridgehead> <programlisting> localhost> call box.insert(0, 'test', 'my first tuple') @@ -224,6 +299,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname') </term> <listitem><simpara></simpara></listitem> </varlistentry> + <varlistentry> <term> <emphasis role="lua">box.replace(space_no, ...)</emphasis> @@ -235,7 +311,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname') the same primary key already exists, <code>box.insert()</code> returns an error, while <code>box.replace()</code> replaces the existing - tuple with the new one. These functions are + tuple with a new one. These functions are wrappers around <code>box.process()</code> <bridgehead renderas="sect4">Returns</bridgehead> Returns the inserted tuple. @@ -243,56 +319,217 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname') </listitem> </varlistentry> + <varlistentry> + <term> + <emphasis role="lua">box.update(space_no, key, format, ...)</emphasis> + </term> + <listitem> + <para> + Update a tuple identified by a primary + <code>key</code>. Update arguments follow, + described by <code>format</code>. + The format and arguments are passed to + <code>box.pack()</code> and the result is sent + to <code>box.process()</code>. + A correct <code>format</code> is a sequence of + pairs: update operation, operation arguments. A + single character of format describes either an + operation which needs to take place or operation + argument. A format specifier also works as a + placeholder for the number of field, which needs + to be updated, or argument value. + For example: + <simplelist> + <member><code>+p=p</code> — add a value + to one field and assign another, + </member> + <member><code>:p</code> — splice a + field: start at offset, cut length bytes, and add a + string.</member> + </simplelist> + Possible format specifiers are: <quote>+</quote> + for addition, <quote>-</quote> for subtraction, + <quote>&</quote> for bitwise AND, + <quote>|</quote> for bitwise OR, <quote>^</quote> + for bitwise exclusive OR (XOR), <quote>:</quote> + for string splice and <quote>p</quote> for + operation argument. + <bridgehead renderas="sect4">Returns</bridgehead> + Returns the updated tuple. + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting> +localhost> lua box.insert(0, 0, 'hello world') +--- + - 0: {'hello world'} +... +localhost> lua box.update(0, 0, '+p', 1, 1) -- add value 1 to field #1 +--- +error: 'Illegal parameters, numeric operation on a field with length != 4' +... +localhost> lua box.update(0, 0, '=p', 1, 1) -- assign field #1 to value 1 +--- + - 0: {1} +... +localhost> lua box.update(0, 0, '+p', 1, 1) +--- + - 0: {2} +... +</programlisting> + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <emphasis role="lua">box.delete(space_no, key)</emphasis> </term> <listitem><para> - Delete a tuple, identified by a primary key. + Delete a tuple identified by a primary key. <bridgehead renderas="sect4">Returns</bridgehead> Returns the deleted tuple. + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting> +localhost> call box.delete(0, 'test') +Call OK, 1 rows affected +['test', 'my first tuple'] +localhost> call box.delete(0, 'test') +Call OK, 0 rows affected +localhost> call box.delete(0, 'tes') +Call ERROR, Illegal parameters, key is not u32 (ER_ILLEGAL_PARAMS) +</programlisting> </para></listitem> </varlistentry> <varlistentry> <term> - <emphasis role="lua">box.select_range(space_no, index_no limit, ...)</emphasis> + <emphasis role="lua">box.select_range(space_no, index_no, limit, key, ...)</emphasis> </term> <listitem><para> Select a range of tuples, starting from offset - specified by the key. + specified by <code>key</code>. The key can be + multipart. Limit selection with at most <code>limit</code> tuples. If no key is specified, start from the first key in the index. - </para><para> - For TREE indexes, this returns tuples in sorted order, - and can be used to iterate over the entire space. - For HASH indexes, this returns at most one tuple, - unless <code>key</code> is nil or unspecified, in which case it - returns all tuples. + </para> + <para> + For TREE indexes, this returns tuples in sorted order. + For HASH indexes, the order of tuples is unspecified, and + can change significantly if data is inserted or deleted + between two calls to <code>box.select_range()</code>. + If <code>key</code> is <code>nil</code> or unspecified, + the selection starts from start of the index. + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting>localhost> show configuration +--- +... + space[4].cardinality: "-1" + space[4].estimated_rows: "0" + space[4].index[0].type: "HASH" + space[4].index[0].unique: "true" + space[4].index[0].key_field[0].fieldno: "0" + space[4].index[0].key_field[0].type: "STR" + space[4].index[1].type: "TREE" + space[4].index[1].unique: "false" + space[4].index[1].key_field[0].fieldno: "1" + space[4].index[1].key_field[0].type: "STR" +... +localhost> insert into t4 values ('0', '0') +Insert OK, 1 rows affected +localhost> insert into t4 values ('1', '1') +Insert OK, 1 rows affected +localhost> insert into t4 values ('2', '2') +Insert OK, 1 rows affected +localhost> insert into t4 values ('3', '3') +Insert OK, 1 rows affected +ocalhost> lua box.select_range(4, 0, 10) +--- + - '3': {'3'} + - '0': {'0'} + - '1': {'1'} + - '2': {'2'} +... +localhost> lua box.select_range(4, 1, 10) +--- + - '0': {'0'} + - '1': {'1'} + - '2': {'2'} + - '3': {'3'} +... +localhost> lua box.select_range(4, 1, 2) +--- + - '0': {'0'} + - '1': {'1'} +... +localhost> lua box.select_range(4, 1, 2, '1') +--- + - '1': {'1'} + - '2': {'2'} +... +</programlisting> </para></listitem> </varlistentry> - <varlistentry> <term><emphasis role="lua">box.pack(format, ...)</emphasis></term> <listitem><para> - To use Tarantool/Box binary protocol primitives from Lua, - it's necessary to pack Lua variables into a binary representation. - This is a helper function to do it. - It's prototyped aftre Perl 'pack', which takes a format and a list of - arguments, and returns a binary string with all arguments - packed according to the format. See also doc/box-protocol.txt, - the binary protocol description. + To use Tarantool binary protocol primitives from Lua, + it's necessary to convert Lua variables to binary + format. This helper function is prototyped after Perl + 'pack'. It takes a format and a list of arguments, and + returns a binary string with all arguments packed + according to the format. + <bridgehead renderas="sect4">Format specifiers</bridgehead> + <simplelist> + <member><code>i</code> — converts Lua + variable to a 4-byte + integer, and stores the integer in the resulting + string, low byte first, + </member> + <member><code>p</code> — stores the length + of the argument as a 4-byte int, low byte first, + followed by the argument itself: a 4-byte int, low + byte first, for integers, or a binary blob for + anything else, + </member> + <member><code>=, +, &, |, ^, : </code>— + stores the corresponding Tarantool UPDATE + operation code: field assignment, addition, + conjunction, disjunction, exclusive disjunction, + splice (from Perl SPLICE function). Expects + field number to update as an argument. These format + specifiers only store the corresponding operation + code and field number to update, but do not + describe operation arguments. + </member> + </simplelist> + <bridgehead renderas="sect4">Errors</bridgehead> + Unknown format specifier. <bridgehead renderas="sect4">Example</bridgehead> <programlisting> - pkt = box.pack("iiiiiip", -- pack format - 0, -- space id - 0, -- index id - 0, -- offset - 2^32, -- limit - 1, -- number of SELECT arguments - 1, -- tuple cardinality - key) -- the key to use for SELECT +localhost> lua box.insert(0, 0, 'hello world') +--- + - 0: {'hello world'} +... +localhost> lua box.update(0, 0, "=p", 1, 'bye world') +--- + - 0: {'bye world'} +... +localhost> lua box.update(0, 0, ":p", 1, box.pack('ppp', 0, 3, 'hello')) +--- + - 0: {'hello world'} +... +localhost> lua box.update(0, 0, "=p", 1, 4) +--- + - 0: {4} +... +localhost> lua box.update(0, 0, "+p", 1, 4) +--- + - 0: {8} +... +localhost> lua box.update(0, 0, "^p", 1, 4) +--- + - 0: {12} +... </programlisting> </para></listitem> </varlistentry> @@ -300,7 +537,22 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname') <varlistentry> <term><emphasis role="lua">box.unpack(format, ...)</emphasis></term> <listitem><para> - Counterpart to <code>box.pack().</code> + Counterpart to <code>box.pack()</code>. Only supports + <code>'i'</code> format specifier, and can be used + to convert packed integers to Lua numbers. + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting>localhost> lua tuple=box.replace(2, 0) +--- +... +localhost> lua string.len(tuple[0]) +--- + - 4 +... +localhost> lua box.unpack('i', tuple[0]) +--- + - 0 +... +</programlisting> </para></listitem> </varlistentry> @@ -309,7 +561,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname') <emphasis role="lua">box.print(...)</emphasis> </term> <listitem><para> -Redefine Lua <code>print()</code> built-in to print either to the log file +Redefines Lua <code>print()</code> built-in to print either to the log file (when Lua is used from the binary port) or back to the user (for the administrative console). </para><para> @@ -323,58 +575,402 @@ Note: the administrative console output must be YAML-compatible. </variablelist> <variablelist> - <title>Packages <code>box.space</code> and <code>box.index</code></title> - <para>These packages combine access to space and index - configuration, such as <code>enabled</code>, <code>cardinality</code>, - etc, with object-oriented access to space and index functions - (<code>insert(), update(), select(), ...</code>. Each space - entry is a container for all space indexes, which are - available in array box.space[].index[].</para> - <varlistentry> - <term><emphasis role="lua"> - box.space[i].n - </emphasis></term> + <title>Package <code xml:id="box.tuple" xreflabel="box.tuple">box.tuple</code></title> + <para>The package contains no functions, but stands for + <code>box.tuple</code> userdata type. It is possible to access individual + tuple fields using an index, iterate over all fields in a + tuple or convert a tuple to a Lua table. Tuples are immutable.</para> + <varlistentry> + <term><emphasis role="lua"> </emphasis></term> + <listitem><para> + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting> +localhost> lua t=box.insert(0, 1, 'abc', 'cde', 'efg', 'ghq', 'qkl') +--- +... +localhost> lua #t +--- + - 6 +... +localhost> lua t[1], t[5] +--- + - abc + - qkl +... +localhost> lua t[6] +--- +error: 'Lua error: [string "return t[6]"]:1: box.tuple: index 6 is out of bounds (0..5)' +... +localhost> lua for k,v in t:pairs() do print(v) end +--- + +abc +cde +efg +ghq +qkl +... +localhost> lua t:unpack() +--- + - + - abc + - cde + - efg + - ghq + - qkl +... +</programlisting> + + </para></listitem> + </varlistentry> +</variablelist> + +<variablelist> + <title>Package <code xml:id="box.space" xreflabel="box.space">box.space</code></title> + <para>This package is a container for all + configured spaces. A space object provides access to space + attributes, such as id, whether or not a space is + enabled, space cardinality, estimated number of rows. It also + contains object-oriented versions of <code>box</code> + functions. For example, instead of <code>box.insert(0, ...)</code> + one can write <code>box.space[0]:insert(...)</code>. + Package source code is available in file <filename + xlink:href="https://github.com/mailru/tarantool/blob/master/mod/box/box.lua">mod/box/box.lua</filename></para> + <para>A list of all <code>space</code> members follows.</para> + <varlistentry> + <term><emphasis role="lua">space.n</emphasis></term> + <listitem><simpara>Ordinal space number, <code>box.space[i].n == i</code></simpara></listitem> + </varlistentry> + <varlistentry> + <term><emphasis role="lua">space.enabled</emphasis></term> + <listitem><simpara> + Whether or not this space is enabled in the + configuration file. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space.cardinality</emphasis> + </term> + <listitem><simpara> + A limit on tuple cardinality for tuples in this space. + This limit can be set in the configuration file. Value 0 + stands for <quote>unlimited</quote>. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space.index[]</emphasis> + </term> + <listitem><simpara> + A container for all defined indexes. An index is a Lua object + of type <code xlink:href="#box.index">box.index</code> which + allows to search tuples and iterate over them in predefined order. + </simpara></listitem> + </varlistentry> + <varlistentry> + <term> + <emphasis role="lua">space:select(index_no, ...)</emphasis> + </term> + <listitem><simpara></simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:select_range(index_no, limit, key, ...)</emphasis> + </term> + <listitem><simpara></simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:insert(...)</emphasis> + </term> + <listitem><simpara></simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:replace(...)</emphasis> + </term> + <listitem><simpara></simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:delete(key)</emphasis> + </term> <listitem><simpara></simpara></listitem> </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:update(key, format, ...)</emphasis> + </term> + <listitem><simpara></simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:insert(...)</emphasis> + </term> + <listitem><simpara> + Object-oriented forms of respective <code + xlink:href="#box">box</code> methods. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:len()</emphasis> + </term> + <listitem><simpara> + Returns number of tuples in the space. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:truncate()</emphasis> + </term> + <listitem><simpara> + Deletes all tuples. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:pairs()</emphasis> + </term> + <listitem><simpara> + A helper function to iterate over all space tuples, + Lua style. + </simpara> + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting> +localhost> lua for k,v in box.space[0]:pairs() do print(v) end +--- +1: {'hello'} +2: {'my '} +3: {'Lua '} +4: {'world'} +... +</programlisting> + </listitem> + </varlistentry> +</variablelist> + +<variablelist> + <title>Package <code xml:id="box.index" +xreflabel="box.index">box.index</code></title> + <para> + This package implements methods of type <code>box.index</code>. + Indexes are contained in <code + xlink:href="#box.space">box.space[i].index[]</code> array + within each space object. They provide an API for + ordered iteration over tuples. + </para> + <varlistentry> + <term><emphasis role="lua">index.unique</emphasis></term> + <listitem><simpara> + Boolean, true if the index is unique. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index.type</emphasis> + </term> + <listitem><simpara> + A string for index type, either 'TREE' or 'HASH'. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index.key_field[]</emphasis> + </term> + <listitem><simpara> + An array describing index key fields. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index.idx</emphasis> + </term> + <listitem><simpara> + The underlying userdata which does all the magic. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">space:select_range(limit, key)</emphasis> + </term> + <listitem><simpara>Select a range of tuples, limited by + <code>limit</code>, starting from <code>key</code>. + </simpara></listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index:pairs()</emphasis> + </term> + <listitem><simpara> + A helper function to iterate over all tuples in the index. + </simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index:min()</emphasis> + </term> + <listitem><simpara> + The smallest value in the index. Available only for + indexes of type 'TREE'. + </simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index:max()</emphasis> + </term> + <listitem><simpara> + The biggest value in the index. Available only for + indexes of type 'TREE'. + </simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index:pairs()</emphasis> + </term> + <listitem><simpara> + A helper function to iterate over all tuples in an + index. + </simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <emphasis role="lua">index:next(iteration_state, key)</emphasis> + </term> + <listitem><simpara> + This function can be used for positioned iteration, or + resuming iteration from a given key. It follows the + <link xlink:href='http://pgl.yoyo.org/luai/i/next'>Lua + iteration pattern</link> and returns a pair + <code>>iteration_state, tuple<</code>. + When called with no arguments, it starts iteration + from the beginning. If called with userdata + <code>iteration_state</code>, it returns a tuple + corresponding to iterator position, plus a new + <code>iteration_state</code>. When called with a key, + it positions the iterator on the key, and returns the + respective tuple and <code>iteration_state</code>. + </simpara> + <bridgehead renderas="sect4">Example</bridgehead> +<programlisting> +localhost> insert into t0 values (1, 'Russia') +Insert OK, 1 rows affected +localhost> insert into t0 values (2, 'Serbia') +Insert OK, 1 rows affected +localhost> insert into t0 values (3, 'Bulgaria') +Insert OK, 1 rows affected +localhost> lua i = box.space[0].index[0] +--- +... +localhost> lua k,v=i:next() +--- +... +localhost> lua print(v) +-- +1: {'Russia'} +... +localhost> lua k,v=i:next(k) +--- +... +localhost> lua print(v) +--- +2: {'Serbia'} +... +localhost> lua k,v=i:next(k) +--- +... +localhost> lua print(v) +--- +3: {'Bulgaria'} +... +localhost> lua k,v=i:next(k) +--- +... +localhost> lua print(v) +--- +nil +... +localhost> lua k,v=i:next(2) +--- +... +localhost> lua print(v) +--- +2: {'Serbia'} +... +localhost> lua for k,v in i.next, i, nil do print(v) end +--- +1: {'Russia'} +2: {'Serbia'} +3: {'Bulgaria'} +... +</programlisting> + </listitem> + </varlistentry> + </variablelist> <variablelist> <title>Package <code>box.fiber</code></title> - <para>Create, run and manage existing <emphasis>fibers</emphasis>. + <para>Functions in this package allow to create, run and + manage existing <emphasis>fibers</emphasis>. </para> <para> -A fiber is an independent execution thread, implemented -using the mechanism of cooperative multitasking. +A fiber is an independent execution thread implemented +using a mechanism of cooperative multitasking. Each fiber can be running, suspended or dead. A fiber is created (<code>box.fiber.create()</code>) suspended. It can be started with <code>box.fiber.resume()</code>, yield -the control back with <code>box.fiber.yield()</code> -end with <code>return</code> or just by reaching the end of the -function. +control back to the caller with <code>box.fiber.yield()</code> +end with <code>return</code> or just by reaching the end of +fiber function. </para> <para> A fiber can also be attached or detached. An attached fiber is a child of the creator, and is running only if the creator has called <code>box.fiber.resume()</code>. A detached fiber is a child of -Tarntool/Box internal <quote>sched</quote> fiber, and is gets +Tarantool internal <quote>sched</quote> fiber, and gets scheduled only if there is a libev event associated with it. </para> <para> -To detach, a running fiber must invoke box.fiber.detach(). -A detached fiber loses connection with its parent -forever. +To detach, a running fiber must invoke <code>box.fiber.detach()</code>. +A detached fiber loses connection with its parent forever. </para> <para> -All fibers are part of the fiber registry, box.fiber. +All fibers are part of the fiber registry, <code>box.fiber</code>. This registry can be searched (<code>box.fiber.find()</code>) either by fiber id (fid), which is numeric, or by fiber name, which is a string. If there is more than one fiber with the given name, the first fiber that matches is returned. </para> <para> -Once fiber chunk is done or calls <code>return</code>, +Once fiber function is done or calls <code>return</code>, the fiber is considered dead. Its carcass is put into a fiber pool, and can be reused when another fiber is created. @@ -388,21 +984,20 @@ or <code>box.update()</code>, are calling <code>box.fiber.testcancel()</code>. <code>box.select()</code> doesn't. </para> <para> -A runaway fiber can really only become cuckoo +In practice, a runaway fiber can only become unresponsive if it does a lot of computations and doesn't check -whether it's been cancelled. In addition -to the advisory cancellation, configuration parameter lua_timeout +whether it's been canceled. In addition +to the advisory cancellation, configuration parameter <code>lua_timeout</code> can be used to cancel runaway Lua procedures. </para> <para> The other potential problem comes from detached -fibers which never get scheduled, because are subscribed -or get no events. Such morphing fibers can be killed -with <code>box.fiber.cancel()</code> at any time, +fibers which never get scheduled, because are not subscribed +to any events, or no relevant events occur. Such morphing fibers +can be killed with <code>box.fiber.cancel()</code> at any time, since <code>box.fiber.cancel()</code> sends an asynchronous wakeup event to the fiber, -and when returning from <code>box.fiber.yield()</code> -<code>box.fiber.testcancel()</code> is invoked. +and <code>box.fiber.testcancel()</code> is checked whenever such an event occurs. </para> <para>Like all Lua objects, dead fibers are garbage collected.</para> @@ -410,23 +1005,22 @@ and when returning from <code>box.fiber.yield()</code> <term> <emphasis role="lua">box.fiber.id(fiber) </emphasis> </term> - <listitem><simpara>Returns a numeric id of the fiber.</simpara></listitem> + <listitem><simpara>Return a numeric id of the fiber.</simpara></listitem> </varlistentry> <varlistentry> <term> <emphasis role="lua">box.fiber.self() </emphasis> </term> - <listitem><simpara>Returns <code>box.fiber</code> userdata - object for the currently scheduled - fiber.</simpara></listitem> + <listitem><simpara>Return <code>box.fiber</code> userdata + object for the currently scheduled fiber.</simpara></listitem> </varlistentry> <varlistentry> <term> <emphasis role="lua">box.fiber.find(id) </emphasis> </term> - <listitem><simpara></simpara></listitem> + <listitem><simpara>Locate a fiber userdata object by id.</simpara></listitem> </varlistentry> <varlistentry> @@ -434,10 +1028,11 @@ and when returning from <code>box.fiber.yield()</code> <emphasis role="lua">box.fiber.create(function) </emphasis> </term> <listitem><simpara> - Create a fiber for the passed change. - Can hit a recursion limit. Is a cancellation - point. - </simpara></listitem> + Create a fiber for <code>function</code>. + </simpara> + <bridgehead renderas="sect4">Errors</bridgehead> + <simpara>Can hit a recursion limit.</simpara> + </listitem> </varlistentry> <varlistentry> @@ -447,26 +1042,20 @@ and when returning from <code>box.fiber.yield()</code> <listitem><simpara>Resume a created or suspended fiber.</simpara></listitem> </varlistentry> - + <varlistentry> <term> <emphasis role="lua">box.fiber.yield(...) </emphasis> </term> <listitem><para> - Yield control to the calling fiber — if the fiber + Yield control to the calling fiber, if the fiber is attached, or to sched otherwise. </para> <para> If the fiber is attached, whatever arguments are passed to this call, are passed on to the calling fiber. - If the fiber is detached, simply returns everything back. - Yield to the caller. The caller will take care of - whatever arguments are taken. - fiber_testcancel(); /* throws an error if we were cancelled. */ - * Got resumed. Return whatever the caller has passed - * to us with box.fiber.resume(). - * As a side effect, the detached fiber which yields - * to sched always gets back whatever it yields. + If the fiber is detached, <code>box.fiber.yield()</code> + returns back everything passed into it. </para></listitem> </varlistentry> @@ -484,8 +1073,7 @@ and when returning from <code>box.fiber.yield()</code> <emphasis role="lua">box.fiber.sleep(time)</emphasis> </term> <listitem><simpara> - Yield to the sched fiber and sleep. - @param[in] amount of time to sleep (double) + Yield to the sched fiber and sleep <code>time</code> seconds. Only the current fiber can be made to sleep. </simpara></listitem> </varlistentry> @@ -495,19 +1083,19 @@ and when returning from <code>box.fiber.yield()</code> <emphasis role="lua">box.fiber.cancel(fiber)</emphasis> </term> <listitem><simpara> -Running and suspended fibers can be cancelled. -Zombie fibers can't. Returns an error if -subject fiber does not permit cancel. + Cancel a <code>fiber</code>. + Running and suspended fibers can be canceled. + Returns an error if the subject fiber does not permit cancel. </simpara></listitem> </varlistentry> - + <varlistentry> <term> <emphasis role="lua">box.fiber.testcancel()</emphasis> </term> <listitem><simpara> -Check if this current fiber has been cancelled and -throw an exception if this is the case. + Check if the current fiber has been canceled and + throw an exception if this is the case. </simpara></listitem> </varlistentry> @@ -515,7 +1103,7 @@ throw an exception if this is the case. <variablelist> <title>Package <code>box.cfg</code></title> - <para>This package provides read-only access to + <para>This package provides read-only access to all server configuration parameters.</para> <varlistentry> <term><emphasis role="lua">box.cfg</emphasis></term> @@ -535,11 +1123,72 @@ logger = cat - >> tarantool.log </variablelist> <para> - Additional examples can be found in the open source Lua stored - procedures repository and in the server test suite. + Additional examples can be found in the open source <link + xlink:href="https://github.com/mailru/tntlua">Lua stored + procedures repository</link> and in the server test suite. +</para> + +<section> +<title>Limitation of stored programs</title> + +<para> + There are two limitations in stored program support one should + be aware of: execution atomicity and lack of typing. +</para> +<bridgehead renderas="sect4">Cooperative multitasking environment</bridgehead> +<para> + Tarantool core is built around cooperative multi-tasking + paradigm: unless a running fiber deliberately yields control + to some other fiber, it is not preempted. + <quote>Yield points</quote> are built into all + calls from Tarantool core to the operating system. + Any system call which can block is performed in + asynchronous manner and the fiber waiting + on the system call is preempted with a fiber ready to + run. This model makes all programmatic locks unnecessary: + cooperative multitasking ensures that there is no concurrency + around a resource, no race conditions and no memory + consistency issues. +</para> +<para> + When requests are small, e.g. simple UPDATE, INSERT, DELETE, + SELECT, fiber scheduling is fair: it takes only a little time + to process the request, schedule a disk write, and yield to + a fiber serving the next client. +</para> +<para> + A stored procedure, however, can perform complex computations, + or be written in such a way that control is not given away for a + long time. This can lead to unfair scheduling, when a single + client throttles the rest of the system, or to apparent stalls + in request processing. + Avoiding this situation is responsibility of the stored procedure + author. Most of <code>box</code> calls, such as + <code>box.insert()</code>, <code>box.update()</code>, + <code>box.delete()</code> are yield points; <code>box.select()</code> + and <code>box.select_range()</code>, however, are not. +</para> +<para> + It should also be noted, that in absence of transactions, + any yield in a stored procedure is a potential change in the + database state. Effectively, it's only possible + to have CAS (compare-and-swap) -like atomic stored + procedures: i.e. procedures which select and then modify a record. + + Multiple data change requests always run through a built-in + yield point. +</para> +<bridgehead renderas="sect4">Lack of field types</bridgehead> +<para> + When invoking a stored procedure from the binary protocol, + it's not possible to convey types of arguments. Tuples are + type-agnostic. The conventional workaround is to use + strings to pass all (textual and numeric) data. </para> </section> +</section> + <!-- vim: tw=66 syntax=docbk vim: spell spelllang=en_us diff --git a/doc/user/target.db b/doc/user/target.db index b7be49cea9d93101926b3a9981907b62437e27be..ee73c37c4bb101c8d69b5bed3f1858d03bd65d96 100644 --- a/doc/user/target.db +++ b/doc/user/target.db @@ -1 +1 @@ -<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.3-9-g472a615</ttl><xreftext>Tarantool/Box User Guide, version 1.4.3-9-g472a615</xreftext><div element="chapter" href="#idm34816" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overviewâ€</xreftext></div><div element="section" href="#idp17776" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventionsâ€</xreftext></div><div element="section" href="#idp26672" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugsâ€</xreftext></div></div><div element="chapter" href="#idp220784" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#idp221040" number="3"><ttl>Dynamic data model</ttl><xreftext>Chapter 3, <i>Dynamic data model</i></xreftext></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#idp384848" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulationâ€</xreftext><div element="section" href="#idp378896" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocolâ€</xreftext></div></div><div element="section" href="#idp376016" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative consoleâ€</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Luaâ€</xreftext></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#idp348448" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architectureâ€</xreftext></div><div element="section" href="#idp353184" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the masterâ€</xreftext></div><div element="section" href="#idp262720" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replicaâ€</xreftext></div><div element="section" href="#idp269168" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded stateâ€</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#idp679424" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line optionsâ€</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#idp692032" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parametersâ€</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#idp740672" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storageâ€</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option fileâ€</xreftext></obj><obj element="table" href="#idp776688" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshotsâ€</xreftext></obj><obj element="table" href="#idp808096" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replicationâ€</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#idp827072" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networkingâ€</xreftext></obj><obj element="table" href="#idp845840" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Loggingâ€</xreftext></obj><obj element="table" href="#idp870736" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol supportâ€</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#idp247104" number=""><ttl>C</ttl><xreftext>the section called “Câ€</xreftext></div><div element="section" href="#idp253312" number=""><ttl>Perl</ttl><xreftext>the section called “Perlâ€</xreftext></div><div element="section" href="#idp645056" number=""><ttl>PHP</ttl><xreftext>the section called “PHPâ€</xreftext></div><div element="section" href="#idp646400" number=""><ttl>Python</ttl><xreftext>the section called “Pythonâ€</xreftext></div><div element="section" href="#idp647744" number=""><ttl>Ruby</ttl><xreftext>the section called “Rubyâ€</xreftext><obj element="example" href="#idp652656" number="7.1"><ttl>userbox.rb</ttl><xreftext>Example 7.1, “userbox.rbâ€</xreftext></obj></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div> +<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</ttl><xreftext>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</xreftext><div element="chapter" href="#idm34816" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overviewâ€</xreftext></div><div element="section" href="#idp200384" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventionsâ€</xreftext></div><div element="section" href="#idp209280" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugsâ€</xreftext></div></div><div element="chapter" href="#idp229968" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#data-and-persistence" number="3" targetptr="data-and-persistence"><ttl>Data model and data persitence</ttl><xreftext>Chapter 3, <i>Data model and data persitence</i></xreftext><div element="section" href="#idp312384" number=""><ttl>Dynamic data model</ttl><xreftext>the section called “Dynamic data modelâ€</xreftext></div><div element="section" href="#idp403344" number=""><ttl>Data persistence</ttl><xreftext>the section called “Data persistenceâ€</xreftext></div></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#idp643840" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulationâ€</xreftext><div element="section" href="#idp637888" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocolâ€</xreftext></div></div><div element="section" href="#idp636960" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative consoleâ€</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Luaâ€</xreftext><obj element="code" href="#box" number="" targetptr="box"><ttl>???TITLE???</ttl><xreftext>box</xreftext></obj><obj element="code" href="#box.tuple" number="" targetptr="box.tuple"><ttl>???TITLE???</ttl><xreftext>box.tuple</xreftext></obj><obj element="code" href="#box.space" number="" targetptr="box.space"><ttl>???TITLE???</ttl><xreftext>box.space</xreftext></obj><obj element="code" href="#box.index" number="" targetptr="box.index"><ttl>???TITLE???</ttl><xreftext>box.index</xreftext></obj><div element="section" href="#idp844528" number=""><ttl>Limitation of stored programs</ttl><xreftext>the section called “Limitation of stored programsâ€</xreftext></div></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#idp31184" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architectureâ€</xreftext></div><div element="section" href="#idp304320" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the masterâ€</xreftext></div><div element="section" href="#idp307200" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replicaâ€</xreftext></div><div element="section" href="#idp393936" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded stateâ€</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#idp890800" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line optionsâ€</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#idp903568" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parametersâ€</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#idp952208" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storageâ€</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option fileâ€</xreftext></obj><obj element="table" href="#idp988224" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshotsâ€</xreftext></obj><obj element="entry" href="#rows_per_wal" number="" targetptr="rows_per_wal"><ttl>???TITLE???</ttl><xreftext>rows_per_wal</xreftext></obj><obj element="entry" href="#wal_writer_inbox_size" number="" targetptr="wal_writer_inbox_size"><ttl>???TITLE???</ttl><xreftext>wal_writer_inbox_size</xreftext></obj><obj element="table" href="#idp1020912" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replicationâ€</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#idp1039888" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networkingâ€</xreftext></obj><obj element="table" href="#idp1058656" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Loggingâ€</xreftext></obj><obj element="table" href="#idp1083552" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol supportâ€</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#idp191920" number=""><ttl>C</ttl><xreftext>the section called “Câ€</xreftext></div><div element="section" href="#idp678512" number=""><ttl>Perl</ttl><xreftext>the section called “Perlâ€</xreftext></div><div element="section" href="#idp680512" number=""><ttl>PHP</ttl><xreftext>the section called “PHPâ€</xreftext></div><div element="section" href="#idp734112" number=""><ttl>Python</ttl><xreftext>the section called “Pythonâ€</xreftext></div><div element="section" href="#idp757312" number=""><ttl>Ruby</ttl><xreftext>the section called “Rubyâ€</xreftext></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div> diff --git a/doc/user/user.xml b/doc/user/user.xml index 2ae51a5595c943134b669b9915ee4ff5a08947aa..fec0f7bc3d87e378a3f005b33ff3e47468dbaa5b 100644 --- a/doc/user/user.xml +++ b/doc/user/user.xml @@ -10,7 +10,7 @@ <title>Tarantool/Box User Guide, version &tnt_version;</title> <xi:include href="preface.xml"/> <xi:include href="tutorial.xml"/> -<xi:include href="data-model.xml"/> +<xi:include href="data-and-persistence.xml"/> <xi:include href="language-reference.xml"/> <xi:include href="replication.xml"/> <xi:include href="configuration-reference.xml"/> diff --git a/doc/www-data/index.html b/doc/www-data/index.html index b072a087296dec9c5f2d305ec9e5455f48f6be6a..91c52bc9a8cc6815a4691b4d4ef3360c7d837318 100644 --- a/doc/www-data/index.html +++ b/doc/www-data/index.html @@ -111,7 +111,7 @@ <div class="mBox w280"> <br /><br /> <h3>What's new</h3> - <p>2011-10-07: <a href="http://tarantool.org/dist/tarantool-1.4.3-3-g45804c6-src.tar.gz">Tarantool 1.4.3</a>, stable version of 1.4 branch.</p> + <p>2011-12-14: <a href="http://tarantool.org/dist/tarantool-1.4.4-2-g9b208cb-src.tar.gz">Tarantool 1.4.4</a>, a maintenace version of stable 1.4 branch.</p> <p>2011-08-25: Tarantool 1.4.2, featuring Lua stored prcoedures, is out.</p> <p>2011-06-05: <a href="http://tarantool.org/tarantool_user_guide.html">Tarantool User Guide</a> is published.</p> <p>2011-05-14: <a href="http://launchpad.net/tarantool/1.3/1.3.5/+download/tarantool-1.3.5-src.tar.gz">tarantool-1.3.5</a> (stable) is out.</p> diff --git a/doc/www-data/index.ru.html b/doc/www-data/index.ru.html index b07bc6165749855952c8421ae09aae0686275efd..a34379a4e379cca687c1136440030324c600ab15 100644 --- a/doc/www-data/index.ru.html +++ b/doc/www-data/index.ru.html @@ -100,7 +100,7 @@ <div class="mBox w280"> <br /><br /> <h3>ÐовоÑти</h3> - <p>2011-10-07: <a href="http://tarantool.org/dist/tarantool-1.4.3-3-g45804c6-src.tar.gz">Tarantool 1.4.3</a>, ÑÑ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð²ÐµÑ‚ÐºÐ¸ 1.4.</p> + <p>2011-12-14: <a href="http://tarantool.org/dist/tarantool-1.4.4-2-g9b208cb-src.tar.gz">Tarantool 1.4.4</a>, ÑÑ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð²ÐµÑ‚ÐºÐ¸ 1.4.</p> <p>2011-08-25: Tarantool 1.4.2, Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¾Ð¹ Lua.</p> <p>2011-06-05: Опубликован <a href="http://tarantool.org/tarantool_user_guide.html">tarantool user guide</a>.</p> <p>2011-05-14: <a href="http://launchpad.net/tarantool/1.3/1.3.5/+download/tarantool-1.3.5-src.tar.gz">tarantool-1.3.5</a>, ÑÑ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ.</p> diff --git a/include/config.h.cmake b/include/config.h.cmake index 69fd01096760d7bdb936d973f08efdcb32531577..80ae5a6c5d0a2c109c5e97ae928c8d4d90b80a2c 100644 --- a/include/config.h.cmake +++ b/include/config.h.cmake @@ -51,7 +51,7 @@ */ #cmakedefine HAVE_BYTE_ORDER_BIG_ENDIAN 1 -#define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" +#define SYSCONF_DIR "@CMAKE_SYSCONF_DIR@" /* * vim: syntax=c */ diff --git a/include/cpu_feature.h b/include/cpu_feature.h new file mode 100644 index 0000000000000000000000000000000000000000..7a97590861a2fe6c63d619940bd3138b97d7cb7f --- /dev/null +++ b/include/cpu_feature.h @@ -0,0 +1,51 @@ +#ifndef TARANTOOL_CPU_FEATURES_H +#define TARANTOOL_CPU_FEATURES_H +/* + * Copyright (C) 2010 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 <sys/types.h> + +/* CPU feature capabilities to use with cpu_has (feature) */ +enum { + cpuf_ht = 0, cpuf_sse4_1, cpuf_sse4_2, cpuf_hypervisor +}; + +/* return 1=feature is available, 0=unavailable, -EINVAL = unsupported CPU, + -ERANGE = invalid feature +*/ +int cpu_has (unsigned int feature); + + +/* hardware-calculate CRC32 for the given data buffer + * NB: requires 1 == cpu_has (cpuf_sse4_2), + * CALLING IT W/O CHECKING for sse4_2 CAN CAUSE SIGABRT + */ +u_int32_t crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len); + + +#endif /* TARANTOOL_CPU_FEATURES_H */ + +/* __EOF__ */ + diff --git a/include/tarantool.h b/include/tarantool.h index 9ba2910d824036f0bba351a69e4eb4534fab56ff..9e63e889fd8ad7fe8f2ec1373d87c7964d4a8c91 100644 --- a/include/tarantool.h +++ b/include/tarantool.h @@ -66,6 +66,7 @@ tarantool_lua_register_type(struct lua_State *L, const char *type_name, * @return L on success, 0 if out of memory */ struct lua_State *tarantool_lua_init(); +void tarantool_lua_close(struct lua_State *L); /* * Single global lua_State shared by core and modules. diff --git a/mod/box/CMakeLists.txt b/mod/box/CMakeLists.txt index 71f40692e03a321ca1294e3637fa624a782af1c6..e0ad40a566d9027293863dba6d4f2f9ee598bf44 100644 --- a/mod/box/CMakeLists.txt +++ b/mod/box/CMakeLists.txt @@ -16,7 +16,7 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.m) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o - COMMAND ${LD} -r -b binary box.lua -o ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o + COMMAND cd ${CMAKE_SOURCE_DIR}/mod/box && ${LD} -r -b binary box.lua -o ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o DEPENDS box.lua) set_source_files_properties(box.lua.o diff --git a/mod/box/box.lua b/mod/box/box.lua index 9a1a0cd8f641ec7cfd35285a0291d3896c364a6d..1556becb922fc018ae3118a5c439e29b95687607 100644 --- a/mod/box/box.lua +++ b/mod/box/box.lua @@ -20,7 +20,7 @@ end -- starts from the key. -- function box.select_range(sno, ino, limit, ...) - return box.space[tonumber(sno)].index[tonumber(ino)]:range(tonumber(limit), ...) + return box.space[tonumber(sno)].index[tonumber(ino)]:select_range(tonumber(limit), ...) end -- @@ -57,7 +57,7 @@ function box.insert(space, ...) unpack(tuple))) end --- +-- function box.update(space, key, format, ...) local ops = {...} return box.process(19, @@ -84,7 +84,10 @@ function box.on_reload_configuration() index_mt.pairs = function(index) return index.idx.next, index.idx, nil end -- - index_mt.range = function(index, limit, ...) + index_mt.next = function(index, ...) + return index.idx:next(...) end + -- + index_mt.select_range = function(index, limit, ...) local range = {} for k, v in index.idx.next, index.idx, ... do if #range >= limit then @@ -99,6 +102,9 @@ function box.on_reload_configuration() space_mt.len = function(space) return space.index[0]:len() end space_mt.__newindex = index_mt.__newindex space_mt.select = function(space, ...) return box.select(space.n, ...) end + space_mt.select_range = function(space, ino, limit, ...) + return space.index[ino]:select_range(limit, ...) + end space_mt.insert = function(space, ...) return box.insert(space.n, ...) end space_mt.update = function(space, ...) return box.update(space.n, ...) end space_mt.replace = function(space, ...) return box.replace(space.n, ...) end @@ -128,3 +134,11 @@ if initfile ~= nil then io.close(initfile) dofile("init.lua") end +-- security: nullify some of the most serious os.* holes +-- +os.execute = nil +os.exit = nil +os.rename = nil +os.tmpname = nil +os.remove = nil +require = nil diff --git a/mod/box/box.m b/mod/box/box.m index c62a3c6b3a24d65fecd4255e69f2f8cfb7bf067b..e50d4ba9c858129a0098015b79d4905184399746 100644 --- a/mod/box/box.m +++ b/mod/box/box.m @@ -126,27 +126,27 @@ validate_indexes(struct box_txn *txn) /* There is more than one index. */ foreach_index(txn->n, index) { /* XXX: skip the first index here! */ - for (u32 f = 0; f < index->key.part_count; ++f) { - if (index->key.parts[f].fieldno >= txn->tuple->cardinality) + for (u32 f = 0; f < index->key_def.part_count; ++f) { + if (index->key_def.parts[f].fieldno >= txn->tuple->cardinality) tnt_raise(IllegalParams, :"tuple must have all indexed fields"); - if (index->key.parts[f].type == STRING) + if (index->key_def.parts[f].type == STRING) continue; - void *field = tuple_field(txn->tuple, index->key.parts[f].fieldno); + void *field = tuple_field(txn->tuple, index->key_def.parts[f].fieldno); u32 len = load_varint32(&field); - if (index->key.parts[f].type == NUM && len != sizeof(u32)) + if (index->key_def.parts[f].type == NUM && len != sizeof(u32)) tnt_raise(IllegalParams, :"field must be NUM"); - if (index->key.parts[f].type == NUM64 && len != sizeof(u64)) + if (index->key_def.parts[f].type == NUM64 && len != sizeof(u64)) tnt_raise(IllegalParams, :"field must be NUM64"); } - if (index->type == TREE && index->key.is_unique == false) + if (index->type == TREE && index->key_def.is_unique == false) /* Don't check non unique indexes */ continue; - struct box_tuple *tuple = [index findBy: txn->tuple]; + struct box_tuple *tuple = [index findByTuple: txn->tuple]; if (tuple != NULL && tuple != txn->old_tuple) tnt_raise(ClientError, :ER_INDEX_VIOLATION); @@ -168,7 +168,7 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data) txn->tuple->cardinality = cardinality; memcpy(txn->tuple->data, data->data, data->size); - txn->old_tuple = [txn->index findBy: txn->tuple]; + txn->old_tuple = [txn->index findByTuple: txn->tuple]; if (txn->old_tuple != NULL) tuple_txn_ref(txn, txn->old_tuple); @@ -184,8 +184,8 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data) if (txn->old_tuple != NULL) { #ifndef NDEBUG void *ka, *kb; - ka = tuple_field(txn->tuple, txn->index->key.parts[0].fieldno); - kb = tuple_field(txn->old_tuple, txn->index->key.parts[0].fieldno); + ka = tuple_field(txn->tuple, txn->index->key_def.parts[0].fieldno); + kb = tuple_field(txn->old_tuple, txn->index->key_def.parts[0].fieldno); int kal, kab; kal = load_varint32(&ka); kab = load_varint32(&kb); @@ -937,12 +937,12 @@ space_free(void) } static void -key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index) +key_init(struct key_def *def, struct tarantool_cfg_space_index *cfg_index) { - key->max_fieldno = 0; - key->part_count = 0; + def->max_fieldno = 0; + def->part_count = 0; - /* calculate key cardinality and maximal field number */ + /* Calculate key cardinality and maximal field number. */ for (int k = 0; cfg_index->key_field[k] != NULL; ++k) { typeof(cfg_index->key_field[k]) cfg_key = cfg_index->key_field[k]; @@ -951,23 +951,23 @@ key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index) break; } - key->max_fieldno = MAX(key->max_fieldno, cfg_key->fieldno); - key->part_count++; + def->max_fieldno = MAX(def->max_fieldno, cfg_key->fieldno); + def->part_count++; } - /* init key array */ - key->parts = salloc(sizeof(struct key_part) * key->part_count); - if (key->parts == NULL) { - panic("can't allocate key parts array for index"); + /* init def array */ + def->parts = salloc(sizeof(struct key_part) * def->part_count); + if (def->parts == NULL) { + panic("can't allocate def parts array for index"); } /* init compare order array */ - key->max_fieldno++; - key->cmp_order = salloc(key->max_fieldno * sizeof(u32)); - if (key->cmp_order == NULL) { - panic("can't allocate key cmp_order array for index"); + def->max_fieldno++; + def->cmp_order = salloc(def->max_fieldno * sizeof(u32)); + if (def->cmp_order == NULL) { + panic("can't allocate def cmp_order array for index"); } - memset(key->cmp_order, -1, key->max_fieldno * sizeof(u32)); + memset(def->cmp_order, -1, def->max_fieldno * sizeof(u32)); /* fill fields and compare order */ for (int k = 0; cfg_index->key_field[k] != NULL; ++k) { @@ -979,12 +979,12 @@ key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index) } /* fill keys */ - key->parts[k].fieldno = cfg_key->fieldno; - key->parts[k].type = STR2ENUM(field_data_type, cfg_key->type); + def->parts[k].fieldno = cfg_key->fieldno; + def->parts[k].type = STR2ENUM(field_data_type, cfg_key->type); /* fill compare order */ - key->cmp_order[cfg_key->fieldno] = k; + def->cmp_order[cfg_key->fieldno] = k; } - key->is_unique = cfg_index->unique; + def->is_unique = cfg_index->unique; } static void @@ -1010,11 +1010,11 @@ space_config(void) /* fill space indexes */ for (int j = 0; cfg_space->index[j] != NULL; ++j) { typeof(cfg_space->index[j]) cfg_index = cfg_space->index[j]; - struct key info; - key_init(&info, cfg_index); + struct key_def key_def; + key_init(&key_def, cfg_index); enum index_type type = STR2ENUM(index_type, cfg_index->type); - Index *index = [Index alloc: type :&info]; - [index init: type :&info :space + i :j]; + Index *index = [Index alloc: type :&key_def]; + [index init: type :&key_def:space + i :j]; space[i].index[j] = index; } @@ -1428,6 +1428,7 @@ void mod_free(void) { space_free(); + memcached_free(); } void diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m index d68efa40fdf0562f9b663a706988c7de18fe9d7e..b8a1364f502880b97fcbd7d7782271e8f2846a77 100644 --- a/mod/box/box_lua.m +++ b/mod/box/box_lua.m @@ -38,6 +38,7 @@ #include "pickle.h" #include "tuple.h" +#include "salloc.h" /* contents of box.lua */ extern const char _binary_box_lua_start; @@ -182,7 +183,7 @@ lbox_tuple_next(struct lua_State *L) { struct box_tuple *tuple = lua_checktuple(L, 1); int argc = lua_gettop(L) - 1; - u8 *field; + u8 *field = NULL; size_t len; if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) @@ -192,6 +193,7 @@ lbox_tuple_next(struct lua_State *L) else luaL_error(L, "tuple.next(): bad arguments"); + (void)field; assert(field >= tuple->data); if (field < tuple->data + tuple->bsize) { len = load_varint32((void **) &field); @@ -232,6 +234,32 @@ static const struct luaL_reg lbox_tuple_meta [] = { */ static const char *indexlib_name = "box.index"; +static const char *iteratorlib_name = "box.index.iterator"; + +static struct iterator * +lua_checkiterator(struct lua_State *L, int i) +{ + struct iterator **it = luaL_checkudata(L, i, iteratorlib_name); + assert(it != NULL); + return *it; +} + +static void +lbox_pushiterator(struct lua_State *L, struct iterator *it) +{ + void **ptr = lua_newuserdata(L, sizeof(void *)); + luaL_getmetatable(L, iteratorlib_name); + lua_setmetatable(L, -2); + *ptr = it; +} + +static int +lbox_iterator_gc(struct lua_State *L) +{ + struct iterator *it = lua_checkiterator(L, -1); + it->free(it); + return 0; +} static Index * lua_checkindex(struct lua_State *L, int i) @@ -363,18 +391,20 @@ lbox_index_next(struct lua_State *L) { Index *index = lua_checkindex(L, 1); int argc = lua_gettop(L) - 1; + struct iterator *it = NULL; if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) { /* * If there is nothing or nil on top of the stack, * start iteration from the beginning. */ - [index initIterator: index->position]; - } else if (argc > 1 || !lua_islightuserdata(L, 2)) { + it = [index allocIterator]; + [index initIterator: it]; + lbox_pushiterator(L, it); + } else if (argc > 1 || lua_type(L, 2) != LUA_TUSERDATA) { /* * We've got something different from iterator's - * light userdata: must be a key - * to start iteration from an offset. Seed the - * iterator with this key. + * userdata: must be a key to start iteration from + * an offset. Seed the iterator with this key. */ int cardinality; void *key; @@ -390,7 +420,7 @@ lbox_index_next(struct lua_State *L) struct tbuf *data = tbuf_alloc(fiber->gc_pool); for (int i = 0; i < argc; ++i) append_key_part(L, i + 2, data, - index->key.parts[i].type); + index->key_def.parts[i].type); key = data->data; } /* @@ -399,15 +429,17 @@ lbox_index_next(struct lua_State *L) * keys. */ assert(cardinality != 0); - if (cardinality > index->key.part_count) + if (cardinality > index->key_def.part_count) luaL_error(L, "index.next(): key part count (%d) " "does not match index cardinality (%d)", - cardinality, index->key.part_count); - [index initIterator: index->position :key :cardinality]; + cardinality, index->key_def.part_count); + it = [index allocIterator]; + [index initIterator: it :key :cardinality]; + lbox_pushiterator(L, it); + } else { /* 1 item on the stack and it's a userdata. */ + it = lua_checkiterator(L, 2); } - struct box_tuple *tuple = index->position->next(index->position); - if (tuple) - lua_pushlightuserdata(L, index->position); + struct box_tuple *tuple = it->next(it); /* If tuple is NULL, pushes nil as end indicator. */ lbox_pushtuple(L, tuple); return tuple ? 2 : 1; @@ -427,6 +459,11 @@ static const struct luaL_reg indexlib [] = { {NULL, NULL} }; +static const struct luaL_reg lbox_iterator_meta[] = { + {"__gc", lbox_iterator_gc}, + {NULL, NULL} +}; + /* }}} */ /** {{{ Lua I/O: facilities to intercept box output @@ -713,6 +750,7 @@ mod_lua_init(struct lua_State *L) tarantool_lua_register_type(L, indexlib_name, lbox_index_meta); luaL_register(L, "box.index", indexlib); lua_pop(L, 1); + tarantool_lua_register_type(L, iteratorlib_name, lbox_iterator_meta); /* Load box.lua */ if (luaL_dostring(L, &_binary_box_lua_start)) panic("Error loading box.lua: %s", lua_tostring(L, -1)); diff --git a/mod/box/index.h b/mod/box/index.h index 05a59250a6e497ef9f2084d3adefa724ecae7b55..0cc6e85297219e7e4058c073699bad43b06de74b 100644 --- a/mod/box/index.h +++ b/mod/box/index.h @@ -51,7 +51,7 @@ struct key_part { }; /* Descriptor of a multipart key. */ -struct key { +struct key_def { /* Description of parts of a multipart index. */ struct key_part *parts; /* @@ -85,21 +85,21 @@ struct key { */ struct iterator *position; /* Description of a possibly multipart key. */ - struct key key; + struct key_def key_def; enum index_type type; bool enabled; /* Relative offset of the index in its namespace. */ u32 n; }; -+ (Index *) alloc: (enum index_type) type_arg :(struct key *) key_arg; ++ (Index *) alloc: (enum index_type) type_arg :(struct key_def *) key_def_arg; /** * Initialize index instance. * * @param space space the index belongs to * @param key key part description */ -- (id) init: (enum index_type) type_arg :(struct key *) key_arg +- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg :(struct space *) space_arg :(u32) n_arg; /** Destroy and free index instance. */ - (void) free; @@ -111,7 +111,7 @@ struct key { - (struct box_tuple *) min; - (struct box_tuple *) max; - (struct box_tuple *) find: (void *) key_arg; /* only for unique lookups */ -- (struct box_tuple *) findBy: (struct box_tuple *) tuple; +- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple; - (void) remove: (struct box_tuple *) tuple; - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple; /** @@ -120,13 +120,14 @@ struct key { */ - (struct iterator *) allocIterator; - (void) initIterator: (struct iterator *) iterator; -- (void) initIterator: (struct iterator *) iterator :(void *) key_arg +- (void) initIterator: (struct iterator *) iterator :(void *) key :(int) part_count; @end struct iterator { struct box_tuple *(*next)(struct iterator *); struct box_tuple *(*next_equal)(struct iterator *); + void (*free)(struct iterator *); }; #define foreach_index(n, index_var) \ diff --git a/mod/box/index.m b/mod/box/index.m index 6c75ed9b6ab942bf035e3decf7f9d2d912445747..7786efb7f70f0d356034526005b473267bc75828 100644 --- a/mod/box/index.m +++ b/mod/box/index.m @@ -57,14 +57,14 @@ iterator_first_equal(struct iterator *it) @class HashStrIndex; @class TreeIndex; -+ (Index *) alloc: (enum index_type) type :(struct key *) key ++ (Index *) alloc: (enum index_type) type :(struct key_def *) key_def { switch (type) { case HASH: /* Hash index, check key type. * Hash indes always has a single-field key. */ - switch (key->parts[0].type) { + switch (key_def->parts[0].type) { case NUM: return [Hash32Index alloc]; /* 32-bit integer hash */ case NUM64: @@ -83,11 +83,11 @@ iterator_first_equal(struct iterator *it) panic("unsupported index type"); } -- (id) init: (enum index_type) type_arg :(struct key *) key_arg +- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg :(struct space *) space_arg :(u32) n_arg; { self = [super init]; - key = *key_arg; + key_def = *key_def_arg; type = type_arg; n = n_arg; space = space_arg; @@ -98,9 +98,9 @@ iterator_first_equal(struct iterator *it) - (void) free { - sfree(key.parts); - sfree(key.cmp_order); - sfree(position); + sfree(key_def.parts); + sfree(key_def.cmp_order); + position->free(position); [super free]; } @@ -127,14 +127,14 @@ iterator_first_equal(struct iterator *it) return NULL; } -- (struct box_tuple *) find: (void *) key_arg +- (struct box_tuple *) find: (void *) key { - (void) key_arg; + (void) key; [self subclassResponsibility: _cmd]; return NULL; } -- (struct box_tuple *) findBy: (struct box_tuple *) pattern +- (struct box_tuple *) findByTuple: (struct box_tuple *) pattern { (void) pattern; [self subclassResponsibility: _cmd]; @@ -167,12 +167,12 @@ iterator_first_equal(struct iterator *it) [self subclassResponsibility: _cmd]; } -- (void) initIterator: (struct iterator *) iterator :(void *) key_arg +- (void) initIterator: (struct iterator *) iterator :(void *) key :(int) part_count { (void) iterator; (void) part_count; - (void) key_arg; + (void) key; [self subclassResponsibility: _cmd]; } @end @@ -211,6 +211,13 @@ hash_iterator_next(struct iterator *iterator) return NULL; } +void +hash_iterator_free(struct iterator *iterator) +{ + assert(iterator->next = hash_iterator_next); + sfree(iterator); +} + @implementation HashIndex - (void) free @@ -230,21 +237,22 @@ hash_iterator_next(struct iterator *iterator) return NULL; } -- (struct box_tuple *) findBy: (struct box_tuple *) tuple +- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple { /* Hash index currently is always single-part. */ - void *field = tuple_field(tuple, key.parts[0].fieldno); + void *field = tuple_field(tuple, key_def.parts[0].fieldno); if (field == NULL) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.parts[0].fieldno); + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key_def.parts[0].fieldno); return [self find: field]; } - (struct iterator *) allocIterator { - struct hash_iterator *it = salloc(sizeof(hash_iterator)); + struct hash_iterator *it = salloc(sizeof(struct hash_iterator)); if (it) { memset(it, 0, sizeof(struct hash_iterator)); it->base.next = hash_iterator_next; + it->base.free = hash_iterator_free; } return (struct iterator *) it; } @@ -290,14 +298,14 @@ hash_iterator_next(struct iterator *iterator) if (k != mh_end(int_hash)) ret = mh_value(int_hash, k); #ifdef DEBUG - say_debug("index_hash_num_find(self:%p, key:%i) = %p", self, num, ret); + say_debug("Hash32Index find(self:%p, key:%i) = %p", self, num, ret); #endif return ret; } - (void) remove: (struct box_tuple *) tuple { - void *field = tuple_field(tuple, self->key.parts[0].fieldno); + void *field = tuple_field(tuple, key_def.parts[0].fieldno); unsigned int field_size = load_varint32(&field); u32 num = *(u32 *)field; @@ -308,14 +316,14 @@ hash_iterator_next(struct iterator *iterator) if (k != mh_end(int_hash)) mh_i32ptr_del(int_hash, k); #ifdef DEBUG - say_debug("index_hash_num_remove(self:%p, key:%i)", self, num); + say_debug("Hash32Index remove(self:%p, key:%i)", self, num); #endif } - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple { - void *field = tuple_field(new_tuple, key.parts[0].fieldno); + void *field = tuple_field(new_tuple, key_def.parts[0].fieldno); u32 field_size = load_varint32(&field); u32 num = *(u32 *)field; @@ -323,7 +331,7 @@ hash_iterator_next(struct iterator *iterator) tnt_raise(IllegalParams, :"key is not u32"); if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, key.parts[0].fieldno); + void *old_field = tuple_field(old_tuple, key_def.parts[0].fieldno); load_varint32(&old_field); u32 old_num = *(u32 *)old_field; mh_int_t k = mh_i32ptr_get(int_hash, old_num); @@ -334,7 +342,7 @@ hash_iterator_next(struct iterator *iterator) mh_i32ptr_put(int_hash, num, new_tuple, NULL); #ifdef DEBUG - say_debug("index_hash_num_replace(self:%p, old_tuple:%p, new_tuple:%p) key:%i", + say_debug("Hash32Index replace(self:%p, old_tuple:%p, new_tuple:%p) key:%i", self, old_tuple, new_tuple, num); #endif } @@ -350,16 +358,17 @@ hash_iterator_next(struct iterator *iterator) it->hash = int_hash; } -- (void) initIterator: (struct iterator *) iterator :(void *) key_arg +- (void) initIterator: (struct iterator *) iterator :(void *) key :(int) part_count { struct hash_iterator *it = hash_iterator(iterator); + (void) part_count; assert(part_count == 1); assert(iterator->next = hash_iterator_next); - u32 field_size = load_varint32(&key_arg); - u32 num = *(u32 *)key_arg; + u32 field_size = load_varint32(&key); + u32 num = *(u32 *)key; if (field_size != 4) tnt_raise(IllegalParams, :"key is not u32"); @@ -410,14 +419,14 @@ hash_iterator_next(struct iterator *iterator) if (k != mh_end(int64_hash)) ret = mh_value(int64_hash, k); #ifdef DEBUG - say_debug("index_hash_num64_find(self:%p, key:%"PRIu64") = %p", self, num, ret); + say_debug("Hash64Index find(self:%p, key:%"PRIu64") = %p", self, num, ret); #endif return ret; } - (void) remove: (struct box_tuple *) tuple { - void *field = tuple_field(tuple, key.parts[0].fieldno); + void *field = tuple_field(tuple, key_def.parts[0].fieldno); unsigned int field_size = load_varint32(&field); u64 num = *(u64 *)field; @@ -428,14 +437,14 @@ hash_iterator_next(struct iterator *iterator) if (k != mh_end(int64_hash)) mh_i64ptr_del(int64_hash, k); #ifdef DEBUG - say_debug("index_hash_num64_remove(self:%p, key:%"PRIu64")", self, num); + say_debug("Hash64Index remove(self:%p, key:%"PRIu64")", self, num); #endif } - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple { - void *field = tuple_field(new_tuple, key.parts[0].fieldno); + void *field = tuple_field(new_tuple, key_def.parts[0].fieldno); u32 field_size = load_varint32(&field); u64 num = *(u64 *)field; @@ -443,7 +452,8 @@ hash_iterator_next(struct iterator *iterator) tnt_raise(IllegalParams, :"key is not u64"); if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, key.parts[0].fieldno); + void *old_field = tuple_field(old_tuple, + key_def.parts[0].fieldno); load_varint32(&old_field); u64 old_num = *(u64 *)old_field; mh_int_t k = mh_i64ptr_get(int64_hash, old_num); @@ -453,7 +463,7 @@ hash_iterator_next(struct iterator *iterator) mh_i64ptr_put(int64_hash, num, new_tuple, NULL); #ifdef DEBUG - say_debug("index_hash_num64_replace(self:%p, old_tuple:%p, tuple:%p) key:%"PRIu64, + say_debug("Hash64Index replace(self:%p, old_tuple:%p, tuple:%p) key:%"PRIu64, self, old_tuple, new_tuple, num); #endif } @@ -475,6 +485,7 @@ hash_iterator_next(struct iterator *iterator) { assert(iterator->next = hash_iterator_next); assert(part_count == 1); + (void) part_count; struct hash_iterator *it = hash_iterator(iterator); @@ -526,7 +537,7 @@ hash_iterator_next(struct iterator *iterator) ret = mh_value(str_hash, k); #ifdef DEBUG u32 field_size = load_varint32(&field); - say_debug("index_hash_str_find(self:%p, key:(%i)'%.*s') = %p", + say_debug("HashStrIndex find(self:%p, key:(%i)'%.*s') = %p", self, field_size, field_size, (u8 *)field, ret); #endif return ret; @@ -534,14 +545,14 @@ hash_iterator_next(struct iterator *iterator) - (void) remove: (struct box_tuple *) tuple { - void *field = tuple_field(tuple, key.parts[0].fieldno); + void *field = tuple_field(tuple, key_def.parts[0].fieldno); mh_int_t k = mh_lstrptr_get(str_hash, field); if (k != mh_end(str_hash)) mh_lstrptr_del(str_hash, k); #ifdef DEBUG u32 field_size = load_varint32(&field); - say_debug("index_hash_str_remove(self:%p, key:'%.*s')", + say_debug("HashStrIndex remove(self:%p, key:'%.*s')", self, field_size, (u8 *)field); #endif } @@ -549,13 +560,15 @@ hash_iterator_next(struct iterator *iterator) - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple { - void *field = tuple_field(new_tuple, key.parts[0].fieldno); + void *field = tuple_field(new_tuple, key_def.parts[0].fieldno); if (field == NULL) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.parts[0].fieldno); + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, + key_def.parts[0].fieldno); if (old_tuple != NULL) { - void *old_field = tuple_field(old_tuple, key.parts[0].fieldno); + void *old_field = tuple_field(old_tuple, + key_def.parts[0].fieldno); mh_int_t k = mh_lstrptr_get(str_hash, old_field); if (k != mh_end(str_hash)) mh_lstrptr_del(str_hash, k); @@ -564,7 +577,7 @@ hash_iterator_next(struct iterator *iterator) mh_lstrptr_put(str_hash, field, new_tuple, NULL); #ifdef DEBUG u32 field_size = load_varint32(&field); - say_debug("index_hash_str_replace(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'", + say_debug("HashStrIndex replace(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'", self, old_tuple, new_tuple, field_size, (u8 *)field); #endif } @@ -580,16 +593,17 @@ hash_iterator_next(struct iterator *iterator) it->hash = (struct mh_i32ptr_t *) str_hash; } -- (void) initIterator: (struct iterator *) iterator :(void *) field +- (void) initIterator: (struct iterator *) iterator :(void *) key :(int) part_count { assert(iterator->next = hash_iterator_next); assert(part_count== 1); + (void) part_count; struct hash_iterator *it = hash_iterator(iterator); it->base.next_equal = iterator_first_equal; - it->h_pos = mh_lstrptr_get(str_hash, field); + it->h_pos = mh_lstrptr_get(str_hash, key); it->hash = (struct mh_i32ptr_t *) str_hash; } @end @@ -672,21 +686,21 @@ field_compare(struct field *f1, struct field *f2, enum field_data_type type) panic("impossible happened"); } -struct index_tree_el { +struct tree_el { struct box_tuple *tuple; struct field key[]; }; -#define INDEX_TREE_EL_SIZE(key) \ - (sizeof(struct index_tree_el) + sizeof(struct field) * (key)->part_count) +#define TREE_EL_SIZE(key) \ + (sizeof(struct tree_el) + sizeof(struct field) * (key)->part_count) void -index_tree_el_init(struct index_tree_el *elem, - struct key *key, struct box_tuple *tuple) +tree_el_init(struct tree_el *elem, + struct key_def *key_def, struct box_tuple *tuple) { void *tuple_data = tuple->data; - for (i32 i = 0; i < key->max_fieldno; ++i) { + for (i32 i = 0; i < key_def->max_fieldno; ++i) { struct field f; if (i < tuple->cardinality) { @@ -700,48 +714,48 @@ index_tree_el_init(struct index_tree_el *elem, } else f = ASTERISK; - u32 key_field_no = key->cmp_order[i]; + u32 fieldno = key_def->cmp_order[i]; - if (key_field_no == -1) + if (fieldno == -1) continue; - if (key->parts[key_field_no].type == NUM) { + if (key_def->parts[fieldno].type == NUM) { if (f.len != 4) tnt_raise(IllegalParams, :"key is not u32"); - } else if (key->parts[key_field_no].type == NUM64 && f.len != 8) { + } else if (key_def->parts[fieldno].type == NUM64 && f.len != 8) { tnt_raise(IllegalParams, :"key is not u64"); } - elem->key[key_field_no] = f; + elem->key[fieldno] = f; } elem->tuple = tuple; } void -init_search_pattern(struct index_tree_el *pattern, - struct key *key, int part_count, void *key_field) +init_search_pattern(struct tree_el *pattern, + struct key_def *key_def, int part_count, void *key) { - assert(part_count <= key->part_count); + assert(part_count <= key_def->part_count); - for (i32 i = 0; i < key->part_count; ++i) + for (i32 i = 0; i < key_def->part_count; ++i) pattern->key[i] = ASTERISK; for (int i = 0; i < part_count; i++) { u32 len; - len = pattern->key[i].len = load_varint32(&key_field); - if (key->parts[i].type == NUM) { + len = pattern->key[i].len = load_varint32(&key); + if (key_def->parts[i].type == NUM) { if (len != 4) tnt_raise(IllegalParams, :"key is not u32"); - } else if (key->parts[i].type == NUM64 && len != 8) { + } else if (key_def->parts[i].type == NUM64 && len != 8) { tnt_raise(IllegalParams, :"key is not u64"); } if (len <= sizeof(pattern->key[i].data)) { memset(pattern->key[i].data, 0, sizeof(pattern->key[i].data)); - memcpy(pattern->key[i].data, key_field, len); + memcpy(pattern->key[i].data, key, len); } else - pattern->key[i].data_ptr = key_field; + pattern->key[i].data_ptr = key; - key_field += len; + key += len; } pattern->tuple = NULL; @@ -756,14 +770,14 @@ init_search_pattern(struct index_tree_el *pattern, * > 0 - a is greater than b */ static int -index_tree_el_unique_cmp(struct index_tree_el *elem_a, - struct index_tree_el *elem_b, - struct key *key) +tree_el_unique_cmp(struct tree_el *elem_a, + struct tree_el *elem_b, + struct key_def *key_def) { int r = 0; - for (i32 i = 0, end = key->part_count; i < end; ++i) { + for (i32 i = 0, end = key_def->part_count; i < end; ++i) { r = field_compare(&elem_a->key[i], &elem_b->key[i], - key->parts[i].type); + key_def->parts[i].type); if (r != 0) break; } @@ -771,10 +785,10 @@ index_tree_el_unique_cmp(struct index_tree_el *elem_a, } static int -index_tree_el_cmp(struct index_tree_el *elem_a, struct index_tree_el *elem_b, - struct key *key) +tree_el_cmp(struct tree_el *elem_a, struct tree_el *elem_b, + struct key_def *key_def) { - int r = index_tree_el_unique_cmp(elem_a, elem_b, key); + int r = tree_el_unique_cmp(elem_a, elem_b, key_def); if (r == 0 && elem_a->tuple && elem_b->tuple) r = (elem_a->tuple < elem_b->tuple ? -1 : elem_a->tuple > elem_b->tuple); @@ -786,7 +800,7 @@ SPTREE_DEF(str_t, realloc); @interface TreeIndex: Index { sptree_str_t *tree; - struct index_tree_el *pattern; + struct tree_el *pattern; }; - (void) build: (Index *) pk; @end @@ -794,9 +808,8 @@ SPTREE_DEF(str_t, realloc); struct tree_iterator { struct iterator base; struct sptree_str_t_iterator *t_iter; - struct index_tree_el *pattern; - sptree_str_t *tree; - struct key *key; + struct tree_el *pattern; + struct key_def *key_def; }; static inline struct tree_iterator * @@ -812,11 +825,22 @@ tree_iterator_next(struct iterator *iterator) struct tree_iterator *it = tree_iterator(iterator); - struct index_tree_el *elem = sptree_str_t_iterator_next(it->t_iter); + struct tree_el *elem = sptree_str_t_iterator_next(it->t_iter); return elem ? elem->tuple : NULL; } +void +tree_iterator_free(struct iterator *iterator) +{ + assert(iterator->next == tree_iterator_next); + struct tree_iterator *it = tree_iterator(iterator); + + if (it->t_iter) + sptree_str_t_iterator_free(it->t_iter); + sfree(it); +} + static struct box_tuple * tree_iterator_next_equal(struct iterator *iterator) { @@ -824,11 +848,13 @@ tree_iterator_next_equal(struct iterator *iterator) struct tree_iterator *it = tree_iterator(iterator); - struct index_tree_el *elem = + struct tree_el *elem = sptree_str_t_iterator_next(it->t_iter); - if (elem != NULL && index_tree_el_unique_cmp(it->pattern, elem, it->key) == 0) + if (elem != NULL && + tree_el_unique_cmp(it->pattern, elem, it->key_def) == 0) { return elem->tuple; + } return NULL; } @@ -838,6 +864,7 @@ tree_iterator_next_equal(struct iterator *iterator) - (void) free { sfree(pattern); + sptree_str_t_destroy(tree); sfree(tree); [super free]; } @@ -845,14 +872,14 @@ tree_iterator_next_equal(struct iterator *iterator) - (void) enable { enabled = false; - pattern = salloc(INDEX_TREE_EL_SIZE(&key)); + pattern = salloc(TREE_EL_SIZE(&key_def)); tree = salloc(sizeof(*tree)); memset(tree, 0, sizeof(*tree)); if (n == 0) {/* pk */ sptree_str_t_init(tree, - INDEX_TREE_EL_SIZE(&key), + TREE_EL_SIZE(&key_def), NULL, 0, 0, - (void *)index_tree_el_unique_cmp, &key); + (void *)tree_el_unique_cmp, &key_def); enabled = true; } } @@ -864,84 +891,88 @@ tree_iterator_next_equal(struct iterator *iterator) - (struct box_tuple *) min { - struct index_tree_el *elem = sptree_str_t_first(tree); + struct tree_el *elem = sptree_str_t_first(tree); return elem ? elem->tuple : NULL; } - (struct box_tuple *) max { - struct index_tree_el *elem = sptree_str_t_last(tree); + struct tree_el *elem = sptree_str_t_last(tree); return elem ? elem->tuple : NULL; } -- (struct box_tuple *) find: (void *) key_arg +- (struct box_tuple *) find: (void *) key { - init_search_pattern(pattern, &key, 1, key_arg); - struct index_tree_el *elem = sptree_str_t_find(tree, pattern); + init_search_pattern(pattern, &key_def, 1, key); + struct tree_el *elem = sptree_str_t_find(tree, pattern); return elem ? elem->tuple : NULL; } -- (struct box_tuple *) findBy: (struct box_tuple *) tuple +- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple { - index_tree_el_init(pattern, &key, tuple); + tree_el_init(pattern, &key_def, tuple); - struct index_tree_el *elem = sptree_str_t_find(tree, pattern); + struct tree_el *elem = sptree_str_t_find(tree, pattern); return elem ? elem->tuple : NULL; } - (void) remove: (struct box_tuple *) tuple { - index_tree_el_init(pattern, &key, tuple); + tree_el_init(pattern, &key_def, tuple); sptree_str_t_delete(tree, pattern); } - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple { - if (new_tuple->cardinality < key.max_fieldno) - tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.max_fieldno); + if (new_tuple->cardinality < key_def.max_fieldno) + tnt_raise(ClientError, :ER_NO_SUCH_FIELD, + key_def.max_fieldno); if (old_tuple) { - index_tree_el_init(pattern, &key, old_tuple); + tree_el_init(pattern, &key_def, old_tuple); sptree_str_t_delete(tree, pattern); } - index_tree_el_init(pattern, &key, new_tuple); + tree_el_init(pattern, &key_def, new_tuple); sptree_str_t_insert(tree, pattern); } - (struct iterator *) allocIterator { struct tree_iterator *it = salloc(sizeof(struct tree_iterator) + - INDEX_TREE_EL_SIZE(&key)); - it->pattern = (struct index_tree_el *) (it + 1); - it->base.next = tree_iterator_next; - it->key = &key; - it->tree = tree; + TREE_EL_SIZE(&key_def)); + if (it) { + memset(it, 0, sizeof(struct tree_iterator)); + it->base.next = tree_iterator_next; + it->base.free = tree_iterator_free; + it->pattern = (struct tree_el *) (it + 1); + it->key_def = &key_def; + } return (struct iterator *) it; } -- (void) initIterator: (struct iterator *) iterator +- (void) initIterator: (struct iterator *) it { - [self initIterator: iterator :NULL :0]; + [self initIterator: it :NULL :0]; } -- (void) initIterator: (struct iterator *) iterator :(void *) key_arg +- (void) initIterator: (struct iterator *) iterator :(void *) key :(int) part_count { assert(iterator->next == tree_iterator_next); struct tree_iterator *it = tree_iterator(iterator); - if (key.is_unique && part_count == key.part_count) + if (key_def.is_unique && part_count == key_def.part_count) it->base.next_equal = iterator_first_equal; else it->base.next_equal = tree_iterator_next_equal; - init_search_pattern(it->pattern, &key, part_count, key_arg); + init_search_pattern(it->pattern, &key_def, part_count, key); sptree_str_t_iterator_init_set(tree, &it->t_iter, it->pattern); } @@ -952,19 +983,19 @@ tree_iterator_next_equal(struct iterator *iterator) assert(enabled == false); - struct index_tree_el *elem = NULL; + struct tree_el *elem = NULL; if (n_tuples) { /* * Allocate a little extra to avoid * unnecessary realloc() when more data is * inserted. */ - size_t sz = estimated_tuples * INDEX_TREE_EL_SIZE(&key); + size_t sz = estimated_tuples * TREE_EL_SIZE(&key_def); elem = malloc(sz); if (elem == NULL) panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz); } - struct index_tree_el *m; + struct tree_el *m; u32 i = 0; struct iterator *it = pk->position; @@ -972,10 +1003,10 @@ tree_iterator_next_equal(struct iterator *iterator) struct box_tuple *tuple; while ((tuple = it->next(it))) { - m = (struct index_tree_el *) - ((char *)elem + i * INDEX_TREE_EL_SIZE(&key)); + m = (struct tree_el *) + ((char *)elem + i * TREE_EL_SIZE(&key_def)); - index_tree_el_init(m, &key, tuple); + tree_el_init(m, &key_def, tuple); ++i; } @@ -983,10 +1014,10 @@ tree_iterator_next_equal(struct iterator *iterator) say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples, self->n); /* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */ - sptree_str_t_init(tree, INDEX_TREE_EL_SIZE(&key), + sptree_str_t_init(tree, TREE_EL_SIZE(&key_def), elem, n_tuples, estimated_tuples, - (void *) (key.is_unique ? index_tree_el_unique_cmp : - index_tree_el_cmp), &key); + (void *) (key_def.is_unique ? tree_el_unique_cmp + : tree_el_cmp), &key_def); enabled = true; } @end diff --git a/mod/box/memcached.h b/mod/box/memcached.h index b5ec2c64bba420b162e36a4cee1204817aecff59..10d4df108c2bd1a8c1fd9e6a581c018195dccc59 100644 --- a/mod/box/memcached.h +++ b/mod/box/memcached.h @@ -33,6 +33,9 @@ struct tarantool_cfg; void memcached_init(); +void +memcached_free(); + void memcached_space_init(); diff --git a/mod/box/memcached.m b/mod/box/memcached.m index 0b873e4501471fa4f791f5bc7be8b2ee612dc03a..1209cb3cbe495f603b495117b1e457bd60125866 100644 --- a/mod/box/memcached.m +++ b/mod/box/memcached.m @@ -47,6 +47,7 @@ static int stat_base; static struct fiber *memcached_expire = NULL; static Index *memcached_index; +static struct iterator *memcached_it; /* memcached tuple format: <key, meta, data> */ @@ -288,7 +289,7 @@ flush_all(void *data) while ((tuple = it->next(it))) { meta(tuple)->exptime = 1; } - sfree(it); + it->free(it); } #define STORE \ @@ -418,6 +419,13 @@ memcached_init(void) memcached_index = space[cfg.memcached_space].index[0]; } +void +memcached_free() +{ + if (memcached_it) + memcached_it->free(memcached_it); +} + void memcached_space_init() { @@ -430,26 +438,26 @@ memcached_space_init() memc_s->cardinality = 4; memc_s->n = cfg.memcached_space; - struct key key; + struct key_def key_def; /* Configure memcached index key. */ - key.part_count = 1; - key.is_unique = true; + key_def.part_count = 1; + key_def.is_unique = true; - key.parts = salloc(sizeof(struct key_part)); - key.cmp_order = salloc(sizeof(u32)); + key_def.parts = salloc(sizeof(struct key_part)); + key_def.cmp_order = salloc(sizeof(u32)); - if (key.parts == NULL || key.cmp_order == NULL) + if (key_def.parts == NULL || key_def.cmp_order == NULL) panic("out of memory when configuring memcached_space"); - key.parts[0].fieldno = 0; - key.parts[0].type = STRING; + key_def.parts[0].fieldno = 0; + key_def.parts[0].type = STRING; - key.max_fieldno = 1; - key.cmp_order[0] = 0; + key_def.max_fieldno = 1; + key_def.cmp_order[0] = 0; /* Configure memcached index. */ - Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key]; - [memc_index init: HASH :&key :memc_s :0]; + Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key_def]; + [memc_index init: HASH :&key_def :memc_s :0]; } /** Delete a bunch of expired keys. */ @@ -488,17 +496,17 @@ memcached_expire_loop(void *data __attribute__((unused))) struct box_tuple *tuple = NULL; say_info("memcached expire fiber started"); - struct iterator *it = [memcached_index allocIterator]; + memcached_it = [memcached_index allocIterator]; @try { restart: if (tuple == NULL) - [memcached_index initIterator: it]; + [memcached_index initIterator: memcached_it]; struct tbuf *keys_to_delete = tbuf_alloc(fiber->gc_pool); for (int j = 0; j < cfg.memcached_expire_per_loop; j++) { - tuple = it->next(it); + tuple = memcached_it->next(memcached_it); if (tuple == NULL) break; @@ -513,7 +521,8 @@ restart: fiber_gc(); goto restart; } @finally { - sfree(it); + memcached_it->free(memcached_it); + memcached_it = NULL; } } @@ -523,7 +532,7 @@ void memcached_start_expire() return; assert(memcached_expire == NULL); - memcached_expire = fiber_create("memecached_expire", -1, + memcached_expire = fiber_create("memcached_expire", -1, -1, memcached_expire_loop, NULL); if (memcached_expire == NULL) say_error("can't start the expire fiber"); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1179fee969e67128fa66d3d920c3f72d818f0dcc..e7e1fadb89454ac36f68cb6cfca8444139fda4fd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,8 @@ add_custom_target(test - COMMAND python ${PROJECT_SOURCE_DIR}/test/test-run.py --builddir=${PROJECT_BINARY_DIR} --vardir=${PROJECT_BINARY_DIR}/test/var - ) + COMMAND python ${PROJECT_SOURCE_DIR}/test/test-run.py --builddir=${PROJECT_BINARY_DIR} --vardir=${PROJECT_BINARY_DIR}/test/var) -tarantool_client("box/protocol" ${CMAKE_SOURCE_DIR}/test/box/protocol.c) -tarantool_client("connector_c/tt" ${CMAKE_SOURCE_DIR}/test/connector_c/tt.c) +add_subdirectory(box) +add_subdirectory(connector_c) if ("${CPACK_GENERATOR}" STREQUAL "RPM") install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg @@ -12,7 +11,7 @@ if ("${CPACK_GENERATOR}" STREQUAL "RPM") DESTINATION share/tarantool) else() install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg - DESTINATION bin) + DESTINATION "${CMAKE_SYSCONF_DIR}") install (FILES ${CMAKE_SOURCE_DIR}/test/box/00000000000000000001.snap - DESTINATION bin) + DESTINATION "${CMAKE_LOCALSTATE_DIR}/lib/tarantool") endif() diff --git a/test/box/CMakeLists.txt b/test/box/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..40f4fa20edf3c7c199f5f52de0a233474884ff36 --- /dev/null +++ b/test/box/CMakeLists.txt @@ -0,0 +1,2 @@ + +tarantool_client("protocol" protocol.c) diff --git a/test/box/args.result b/test/box/args.result index 7bf5ec89e516da4773186aabf15654d84593f3f4..eaf9313bfbb06245c6f0cbd792e53912e94e04b6 100644 --- a/test/box/args.result +++ b/test/box/args.result @@ -94,3 +94,10 @@ tarantool_box: creating `snapshots/00000000000000000001.snap.inprogress' tarantool_box: saving snapshot `snapshots/00000000000000000001.snap' tarantool_box: done +# +# A test case for Bug#897162, cat command should +# not require a configuration file. + +tarantool_box --config=nonexists.cfg --cat=nonexists.xlog +tarantool_box: access("nonexists.xlog"): No such file or directory + diff --git a/test/box/args.test b/test/box/args.test index f51766a25af318a735dc407b3dbb6b6a1eb7d9db..2d014d162aafd4ee67245817bf08200b0a61b069 100644 --- a/test/box/args.test +++ b/test/box/args.test @@ -56,5 +56,13 @@ sys.stdout.pop_filter() os.unlink(cfg) shutil.rmtree(os.path.join(vardir, "bug726778")) +print """# +# A test case for Bug#897162, cat command should +# not require a configuration file. +""" +sys.stdout.push_filter("(/\S+)+/tarantool", "tarantool") +server.test_option("--config=nonexists.cfg --cat=nonexists.xlog") +sys.stdout.pop_filter() + # Args filter cleanup # vim: syntax=python diff --git a/test/box/configuration.result b/test/box/configuration.result index 58aec176101693a755a7ee80568904ad5c404c9a..f4c527e1c6d978c5ab8adda89bc2853fcd41689a 100644 --- a/test/box/configuration.result +++ b/test/box/configuration.result @@ -4,15 +4,62 @@ # compatibility # (https://bugs.launchpad.net/bugs/708685) -show stat +show configuration --- -statistics: - REPLACE: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } +configuration: + username: (null) + bind_ipaddr: "INADDR_ANY" + coredump: "false" + admin_port: "33015" + replication_port: "0" + log_level: "4" + slab_alloc_arena: "0.1" + slab_alloc_minimal: "64" + slab_alloc_factor: "2" + work_dir: (null) + pid_file: "box.pid" + logger: "cat - >> tarantool.log" + logger_nonblock: "true" + io_collect_interval: "0" + backlog: "1024" + readahead: "16320" + snap_dir: "." + wal_dir: "." + primary_port: "33013" + secondary_port: "33014" + too_long_threshold: "0.5" + custom_proc_title: (null) + memcached_port: "0" + memcached_space: "23" + memcached_expire: "false" + memcached_expire_per_loop: "1024" + memcached_expire_full_sweep: "3600" + snap_io_rate_limit: "0" + rows_per_wal: "50" + wal_fsync_delay: "0" + wal_writer_inbox_size: "128" + local_hot_standby: "false" + wal_dir_rescan_delay: "0.1" + panic_on_snap_error: "true" + panic_on_wal_error: "false" + replication_source: (null) + space[0].enabled: "true" + space[0].cardinality: "-1" + space[0].estimated_rows: "0" + space[0].index[0].type: "HASH" + space[0].index[0].unique: "true" + space[0].index[0].key_field[0].fieldno: "0" + space[0].index[0].key_field[0].type: "NUM" + space[1].enabled: "false" + space[1].cardinality: "-1" + space[1].estimated_rows: "0" + space[2].enabled: "true" + space[2].cardinality: "-1" + space[2].estimated_rows: "0" + space[2].index[0].type: "HASH" + space[2].index[0].unique: "true" + space[2].index[0].key_field[0].fieldno: "0" + space[2].index[0].key_field[0].type: "NUM" ... # Bug #884768: @@ -129,3 +176,12 @@ configuration: space[9].index[0].key_field[0].fieldno: "0" space[9].index[0].key_field[0].type: "NUM" ... + +# Bug #876541: +# Test floating point values (wal_fsync_delay) with fractional part +# (https://bugs.launchpad.net/bugs/876541) + +lua box.cfg.wal_fsync_delay +--- + - 0.01 +... diff --git a/test/box/configuration.test b/test/box/configuration.test index ae6ee0a5d13a492aedf5cff092bd6eae85cc94d3..e58585a34a5781b38b5184994da019897d7b8289 100644 --- a/test/box/configuration.test +++ b/test/box/configuration.test @@ -11,7 +11,7 @@ server.stop() # start server from config with holes in spaces server.deploy("box/tarantool_bug708685.cfg") # check connection -exec admin "show stat" +exec admin "show configuration" print """ # Bug #884768: @@ -25,6 +25,18 @@ server.deploy("box/tarantool_bug884768.cfg") # check values exec admin "show configuration" +print """ +# Bug #876541: +# Test floating point values (wal_fsync_delay) with fractional part +# (https://bugs.launchpad.net/bugs/876541) +""" +# stop current server +server.stop() +# start server from config with different boolean represenation +server.deploy("box/tarantool_bug876541.cfg") +# check values +exec admin "lua box.cfg.wal_fsync_delay" + # restore default server server.stop() server.deploy(self.suite_ini["config"]) diff --git a/test/box/lua.result b/test/box/lua.result index 878660e0dc46e0c68111a2bcec51ec423ca82c05..adbcc025534e4c945dbd2b1668538a9f52b0b316 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -727,3 +727,112 @@ Found 1 tuple: call f1('jason', 1, 'test', 2, 'stewart') Found 1 tuple: ['jason', '1', 1953719668, '2', 'stewart'] +lua function box.crossjoin(space0, space1, limit) space0 = tonumber(space0) space1 = tonumber(space1) limit = tonumber(limit) local result = {} for k0, v0 in box.space[space0]:pairs() do for k1, v1 in box.space[space1]:pairs() do if limit <= 0 then return unpack(result) end newtuple = {v0:unpack()} for _, v in v1:pairs() do table.insert(newtuple, v) end table.insert(result, newtuple) limit = limit - 1 end end return unpack(result) end +--- +... +lua box.crossjoin(0, 0, 0) +--- +... +lua box.crossjoin(0, 0, 10000) +--- +... +lua box.space[0]:insert(1) +--- + - 1: {} +... +call box.crossjoin('0', '0', '10000') +Found 1 tuple: +[1, 1] +lua box.space[0]:insert(2) +--- + - 2: {} +... +call box.crossjoin('0', '0', '10000') +Found 4 tuples: +[1, 1] +[1, 2] +[2, 1] +[2, 2] +lua box.space[0]:insert(3, 'hello') +--- + - 3: {'hello'} +... +call box.crossjoin('0', '0', '10000') +Found 9 tuples: +[1, 1] +[1, 2] +[1, 3, 'hello'] +[2, 1] +[2, 2] +[2, 3, 'hello'] +[3, 'hello', 1] +[3, 'hello', 2] +[3, 'hello', 3, 'hello'] +lua box.space[0]:insert(4, 'world') +--- + - 4: {'world'} +... +lua box.space[0]:insert(5, 'hello world') +--- + - 5: {'hello world'} +... +call box.crossjoin('0', '0', '10000') +Found 25 tuples: +[1, 1] +[1, 2] +[1, 3, 'hello'] +[1, 4, 'world'] +[1, 5, 'hello world'] +[2, 1] +[2, 2] +[2, 3, 'hello'] +[2, 4, 'world'] +[2, 5, 'hello world'] +[3, 'hello', 1] +[3, 'hello', 2] +[3, 'hello', 3, 'hello'] +[3, 'hello', 4, 'world'] +[3, 'hello', 5, 'hello world'] +[4, 'world', 1] +[4, 'world', 2] +[4, 'world', 3, 'hello'] +[4, 'world', 4, 'world'] +[4, 'world', 5, 'hello world'] +[5, 'hello world', 1] +[5, 'hello world', 2] +[5, 'hello world', 3, 'hello'] +[5, 'hello world', 4, 'world'] +[5, 'hello world', 5, 'hello world'] +lua box.space[0]:truncate() +--- +... + +# A test case for Bug#901674 +# No way to inspect exceptions from Box in Lua + +lua pcall(box.insert, 99, 1, 'test') +--- + - false + - Space 99 is disabled +... +lua pcall(box.insert, 0, 1, 'hello') +--- + - true + - 1: {'hello'} +... +lua pcall(box.insert, 0, 1, 'hello') +--- + - false + - Tuple already exists +... +lua box.space[0]:truncate() +--- +... + +# A test case for Bug#908094 +# Lua provides access to os.execute() + +lua os.execute('ls') +--- +error: 'Lua error: [string "return os.execute(''ls'')"]:1: attempt to call field ''execute'' (a nil value)' +... diff --git a/test/box/lua.test b/test/box/lua.test index 9ae9819358ab9d57b6cf00bdca1ccc9786cca115..211e4679bf1a471695baa4b2f5f87f0b94d1c016 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -202,4 +202,48 @@ exec sql "call f1()" exec sql "call f2()" exec sql "call f1('jason')" exec sql "call f1('jason', 1, 'test', 2, 'stewart')" - +lua = """ +function box.crossjoin(space0, space1, limit) + space0 = tonumber(space0) + space1 = tonumber(space1) + limit = tonumber(limit) + local result = {} + for k0, v0 in box.space[space0]:pairs() do + for k1, v1 in box.space[space1]:pairs() do + if limit <= 0 then + return unpack(result) + end + newtuple = {v0:unpack()} + for _, v in v1:pairs() do table.insert(newtuple, v) end + table.insert(result, newtuple) + limit = limit - 1 + end + end + return unpack(result) +end""" +exec admin "lua " + lua.replace('\n', ' ') +exec admin "lua box.crossjoin(0, 0, 0)" +exec admin "lua box.crossjoin(0, 0, 10000)" +exec admin "lua box.space[0]:insert(1)" +exec sql "call box.crossjoin('0', '0', '10000')" +exec admin "lua box.space[0]:insert(2)" +exec sql "call box.crossjoin('0', '0', '10000')" +exec admin "lua box.space[0]:insert(3, 'hello')" +exec sql "call box.crossjoin('0', '0', '10000')" +exec admin "lua box.space[0]:insert(4, 'world')" +exec admin "lua box.space[0]:insert(5, 'hello world')" +exec sql "call box.crossjoin('0', '0', '10000')" +exec admin "lua box.space[0]:truncate()" +print """ +# A test case for Bug#901674 +# No way to inspect exceptions from Box in Lua +""" +exec admin "lua pcall(box.insert, 99, 1, 'test')" +exec admin "lua pcall(box.insert, 0, 1, 'hello')" +exec admin "lua pcall(box.insert, 0, 1, 'hello')" +exec admin "lua box.space[0]:truncate()" +print """ +# A test case for Bug#908094 +# Lua provides access to os.execute() +""" +exec admin "lua os.execute('ls')" diff --git a/test/box/suite.ini b/test/box/suite.ini index ebc1e935aa1536eaeee1d3b2978609188bb0719c..ae4bde328a080541bcb84acc35210c39573e5727 100644 --- a/test/box/suite.ini +++ b/test/box/suite.ini @@ -2,6 +2,6 @@ description = tarantool/box, minimal configuration config = tarantool.cfg # put disabled tests here -#disabled = sql.test +#disabled = xlog.test # put disabled in valgrind test here valgrind_disabled = admin_coredump.test diff --git a/test/box/tarantool_bug876541.cfg b/test/box/tarantool_bug876541.cfg new file mode 100644 index 0000000000000000000000000000000000000000..44163ffc275aeeddb9ac469f39d39a28f6a4819f --- /dev/null +++ b/test/box/tarantool_bug876541.cfg @@ -0,0 +1,19 @@ +slab_alloc_arena = 0.1 + +pid_file = "box.pid" + +logger="cat - >> tarantool.log" + +primary_port = 33013 +secondary_port = 33014 +admin_port = 33015 + +rows_per_wal = 50 + +space[0].enabled = 1 +space[0].index[0].type = "HASH" +space[0].index[0].unique = 1 +space[0].index[0].key_field[0].fieldno = 0 +space[0].index[0].key_field[0].type = "NUM" + +wal_fsync_delay = 0.01 diff --git a/test/box_big/sql.result b/test/box_big/sql.result index e8c0ce6c7da6a6d6529d5a8374cbcbe06a9c1eaa..4c99702d1afdd11e930acd415e99ad4869609616 100644 --- a/test/box_big/sql.result +++ b/test/box_big/sql.result @@ -154,8 +154,8 @@ Found 1 tuple: ['21234567', 'part1', 'part2_a'] select * from t5 where k1='part1' Found 3 tuples: -['11234567', 'part1', 'part2'] ['01234567', 'part1', 'part2'] +['11234567', 'part1', 'part2'] ['21234567', 'part1', 'part2_a'] select * from t5 where k1='part1_a' Found 2 tuples: @@ -165,8 +165,8 @@ select * from t5 where k1='part_none' No match lua box.space[5]:select(1, 'part1', 'part2') --- - - '11234567': {'part1', 'part2'} - '01234567': {'part1', 'part2'} + - '11234567': {'part1', 'part2'} ... select * from t1 where k0='key1' Found 1 tuple: diff --git a/test/box_big/sql.test b/test/box_big/sql.test index 76afa77bb9235cd82e291fa229b1b9f0ef31bf30..fb163ba2de54990dbaeaea17a62176f29d0a30d2 100644 --- a/test/box_big/sql.test +++ b/test/box_big/sql.test @@ -76,10 +76,12 @@ exec admin "lua for k, v in box.space[5]:pairs() do print(v) end" exec sql "select * from t5 where k0='01234567'" exec sql "select * from t5 where k0='11234567'" exec sql "select * from t5 where k0='21234567'" +sql.sort = True exec sql "select * from t5 where k1='part1'" exec sql "select * from t5 where k1='part1_a'" exec sql "select * from t5 where k1='part_none'" exec admin "lua box.space[5]:select(1, 'part1', 'part2')" +sql.sort = False # Check how build_idnexes() works server.stop() server.start() diff --git a/test/box_big/tree_pk.result b/test/box_big/tree_pk.result index 4a3e468081aeacb3f0b5650073d65d6707314c8b..319e07a0bbb177c52eb5dbb1a88ef716b741b113 100644 --- a/test/box_big/tree_pk.result +++ b/test/box_big/tree_pk.result @@ -63,3 +63,33 @@ delete from t3 where k0 = 'second' Delete OK, 1 row affected delete from t3 where k0 = 'third' Delete OK, 1 row affected +insert into t2 values (1, 'tuple') +Insert OK, 1 row affected +insert into t3 values (1, 'tuple') +Insert OK, 1 row affected +insert into t3 values (2, 'tuple') +Insert OK, 1 row affected +lua function box.crossjoin(space0, space1, limit) space0 = tonumber(space0) space1 = tonumber(space1) limit = tonumber(limit) local result = {} for k0, v0 in box.space[space0]:pairs() do for k1, v1 in box.space[space1]:pairs() do if limit <= 0 then return unpack(result) end newtuple = {v0:unpack()} for _, v in v1:pairs() do table.insert(newtuple, v) end table.insert(result, newtuple) limit = limit - 1 end end return unpack(result) end +--- +... +call box.crossjoin(3, 3, 0) +No match +call box.crossjoin(3, 3, 5) +Found 4 tuples: +[1, 'tuple', 1, 'tuple'] +[1, 'tuple', 2, 'tuple'] +[2, 'tuple', 1, 'tuple'] +[2, 'tuple', 2, 'tuple'] +call box.crossjoin(3, 3, 10000) +Found 4 tuples: +[1, 'tuple', 1, 'tuple'] +[1, 'tuple', 2, 'tuple'] +[2, 'tuple', 1, 'tuple'] +[2, 'tuple', 2, 'tuple'] +call box.crossjoin(3, 2, 10000) +Found 2 tuples: +[1, 'tuple', 1, 'tuple'] +[2, 'tuple', 1, 'tuple'] +lua box.space[3]:truncate() +--- +... diff --git a/test/box_big/tree_pk.test b/test/box_big/tree_pk.test index 3792956c9f97b7f74ca1b73b9a6fe9dafac4777f..090a90182aa1da98fb4f17f2dbfd314e629c343d 100644 --- a/test/box_big/tree_pk.test +++ b/test/box_big/tree_pk.test @@ -32,3 +32,31 @@ exec sql "select * from t3 where k0 = 'third'" exec sql "delete from t3 where k0 = 'identifier'" exec sql "delete from t3 where k0 = 'second'" exec sql "delete from t3 where k0 = 'third'" +lua = """ +function box.crossjoin(space0, space1, limit) + space0 = tonumber(space0) + space1 = tonumber(space1) + limit = tonumber(limit) + local result = {} + for k0, v0 in box.space[space0]:pairs() do + for k1, v1 in box.space[space1]:pairs() do + if limit <= 0 then + return unpack(result) + end + newtuple = {v0:unpack()} + for _, v in v1:pairs() do table.insert(newtuple, v) end + table.insert(result, newtuple) + limit = limit - 1 + end + end + return unpack(result) +end""" +exec sql "insert into t2 values (1, 'tuple')" +exec sql "insert into t3 values (1, 'tuple')" +exec sql "insert into t3 values (2, 'tuple')" +exec admin "lua " + lua.replace('\n', ' ') +exec sql "call box.crossjoin(3, 3, 0)" +exec sql "call box.crossjoin(3, 3, 5)" +exec sql "call box.crossjoin(3, 3, 10000)" +exec sql "call box.crossjoin(3, 2, 10000)" +exec admin "lua box.space[3]:truncate()" diff --git a/test/connector_c/CMakeLists.txt b/test/connector_c/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..91759d7325d700e2f21674d0533955465a9e10b3 --- /dev/null +++ b/test/connector_c/CMakeLists.txt @@ -0,0 +1,2 @@ + +tarantool_client("tt" tt.c) diff --git a/test/connector_c/connector.result b/test/connector_c/connector.result index 227948c4cb9aea4ac1921c05ac311f1028e20e60..922997eee87c659cfea02bb647230287e17758c9 100644 --- a/test/connector_c/connector.result +++ b/test/connector_c/connector.result @@ -3,7 +3,8 @@ > list [OK] > stream buffer [OK] > iterator tuple [OK] -> iterator tuple field [OK] +> iterator tuple (single field) [OK] +> iterator tuple (tnt_field) [OK] > iterator list [OK] > connect [OK] > ping [OK] @@ -12,6 +13,8 @@ > select [OK] > delete [OK] > call [OK] +> call (no args) [OK] +> reply [OK] > lex ws [OK] > lex integer [OK] > lex string [OK] @@ -26,5 +29,6 @@ > sql insert [OK] > sql update [OK] > sql select [OK] +> sql select limit [OK] > sql delete [OK] > sql call [OK] diff --git a/test/connector_c/connector.test b/test/connector_c/connector.test index 6ee376c95c1e5ff7904be314a387e20075e3c982..01e0b108455c44a83fa9deb6a2c5c82a26639de4 100644 --- a/test/connector_c/connector.test +++ b/test/connector_c/connector.test @@ -1,7 +1,9 @@ import subprocess import sys +import os -p = subprocess.Popen([ "connector_c/tt" ], stdout=subprocess.PIPE) +p = subprocess.Popen([ os.path.join(builddir, "test/connector_c/tt") ], + stdout=subprocess.PIPE) p.wait() for line in p.stdout.readlines(): sys.stdout.write(line) diff --git a/test/connector_c/tt.c b/test/connector_c/tt.c index 05b4e2da4856785278c7d705e78738c03e52881f..0a26916796ce20a0d9a1c915cf3fd089e0c3d6ce 100644 --- a/test/connector_c/tt.c +++ b/test/connector_c/tt.c @@ -32,6 +32,7 @@ #include <tnt.h> #include <tnt_net.h> +#include <tnt_io.h> #include <tnt_queue.h> #include <tnt_utf8.h> #include <tnt_lex.h> @@ -424,6 +425,112 @@ static void tt_tnt_net_call(struct tt_test *test) { tnt_iter_free(&i); } +/* call (no args) */ +static void tt_tnt_net_call_na(struct tt_test *test) { + struct tnt_tuple args; + tnt_tuple_init(&args); + TT_ASSERT(tnt_call(&net, 0, "box.insert", &args) > 0); + TT_ASSERT(tnt_flush(&net) > 0); + tnt_tuple_free(&args); + struct tnt_iter i; + tnt_iter_stream(&i, &net); + while (tnt_next(&i)) { + struct tnt_reply *r = TNT_ISTREAM_REPLY(&i); + TT_ASSERT(r->code != 0); + TT_ASSERT(strcmp(r->error, "Illegal parameters, tuple cardinality is 0") == 0); + } + tnt_iter_free(&i); +} + +/* reply */ +static void tt_tnt_net_reply(struct tt_test *test) { + struct tnt_tuple kv1, kv2; + tnt_tuple_init(&kv1); + tnt_tuple(&kv1, "%d%s", 587, "foo"); + TT_ASSERT(tnt_insert(&net, 0, TNT_FLAG_RETURN, &kv1) > 0); + tnt_tuple_free(&kv1); + tnt_tuple_init(&kv2); + tnt_tuple(&kv2, "%d%s", 785, "bar"); + TT_ASSERT(tnt_insert(&net, 0, TNT_FLAG_RETURN, &kv2) > 0); + tnt_tuple_free(&kv2); + TT_ASSERT(tnt_flush(&net) > 0); + + struct tnt_stream_net *s = TNT_SNET_CAST(&net); + int current = 0; + size_t off = 0; + ssize_t size = 0; + char buffer[512]; + + while (current != 2) { + struct tnt_reply r; + tnt_reply_init(&r); + int rc = tnt_reply(&r, buffer, size, &off); + TT_ASSERT(rc != -1); + if (rc == 1) { + ssize_t res = tnt_io_recv_raw(s, buffer + size, off, 1); + TT_ASSERT(res > 0); + size += off; + continue; + } + TT_ASSERT(rc == 0); + TT_ASSERT(r.code == 0); + TT_ASSERT(r.op == TNT_OP_INSERT); + TT_ASSERT(r.count == 1); + if (current == 0) { + struct tnt_iter il; + tnt_iter_list(&il, TNT_REPLY_LIST(&r)); + TT_ASSERT(tnt_next(&il) == 1); + struct tnt_tuple *tp = TNT_ILIST_TUPLE(&il); + TT_ASSERT(tp->cardinality == 2); + TT_ASSERT(tp->alloc == 1); + TT_ASSERT(tp->data != NULL); + TT_ASSERT(tp->size != 0); + struct tnt_iter ifl; + tnt_iter(&ifl, tp); + TT_ASSERT(tnt_next(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 0); + TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 4); + TT_ASSERT(*(uint32_t*)TNT_IFIELD_DATA(&ifl) == 587); + TT_ASSERT(tnt_next(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 3); + TT_ASSERT(memcmp(TNT_IFIELD_DATA(&ifl), "foo", 3) == 0); + TT_ASSERT(tnt_next(&ifl) == 0); + tnt_iter_free(&ifl); + tnt_iter_free(&il); + off = 0; + size = 0; + } else + if (current == 1) { + struct tnt_iter il; + tnt_iter_list(&il, TNT_REPLY_LIST(&r)); + TT_ASSERT(tnt_next(&il) == 1); + struct tnt_tuple *tp = TNT_ILIST_TUPLE(&il); + TT_ASSERT(tp->cardinality == 2); + TT_ASSERT(tp->alloc == 1); + TT_ASSERT(tp->data != NULL); + TT_ASSERT(tp->size != 0); + struct tnt_iter ifl; + tnt_iter(&ifl, tp); + TT_ASSERT(tnt_next(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 0); + TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 4); + TT_ASSERT(*(uint32_t*)TNT_IFIELD_DATA(&ifl) == 785); + TT_ASSERT(tnt_next(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 1); + TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 3); + TT_ASSERT(memcmp(TNT_IFIELD_DATA(&ifl), "bar", 3) == 0); + TT_ASSERT(tnt_next(&ifl) == 0); + tnt_iter_free(&ifl); + tnt_iter_free(&il); + } + tnt_reply_free(&r); + current++; + } + + net.wrcnt -= 2; +} + /* lex ws */ static void tt_tnt_lex_ws(struct tt_test *test) { unsigned char sz[] = " # abcde fghjk ## hh\n # zzz\n"; @@ -597,7 +704,7 @@ static void tt_tnt_lex_badstr2(struct tt_test *test) { static void tt_tnt_sql_ping(struct tt_test *test) { char *e = NULL; char q[] = "PING"; - TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); struct tnt_iter i; tnt_iter_stream(&i, &net); @@ -613,7 +720,7 @@ static void tt_tnt_sql_ping(struct tt_test *test) { static void tt_tnt_sql_insert(struct tt_test *test) { char *e = NULL; char q[] = "insert into t0 values (222, 'baz')"; - TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); struct tnt_iter i; tnt_iter_stream(&i, &net); @@ -630,25 +737,25 @@ static void tt_tnt_sql_insert(struct tt_test *test) { static void tt_tnt_sql_update(struct tt_test *test) { char *e; char q1[] = "update t0 set k0 = 7 where k0 = 222"; - TT_ASSERT(tnt_query(&net, q1, sizeof(q1) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q1, sizeof(q1) - 1, &e) == 0); /* 7 + 1 = 8 */ char q2[] = "update t0 set k0 = k0 + 1 where k0 = 7"; - TT_ASSERT(tnt_query(&net, q2, sizeof(q2) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q2, sizeof(q2) - 1, &e) == 0); /* 8 | 2 = 10 */ char q3[] = "update t0 set k0 = k0 | 2 where k0 = 8"; - TT_ASSERT(tnt_query(&net, q3, sizeof(q3) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q3, sizeof(q3) - 1, &e) == 0); /* 10 & 2 = 2 */ char q4[] = "update t0 set k0 = k0 & 2 where k0 = 10"; - TT_ASSERT(tnt_query(&net, q4, sizeof(q4) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q4, sizeof(q4) - 1, &e) == 0); /* 2 ^ 123 = 121 */ char q5[] = "update t0 set k0 = k0 ^ 123 where k0 = 2"; - TT_ASSERT(tnt_query(&net, q5, sizeof(q5) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q5, sizeof(q5) - 1, &e) == 0); /* assign */ char q6[] = "update t0 set k0 = 222, k1 = 'hello world' where k0 = 121"; - TT_ASSERT(tnt_query(&net, q6, sizeof(q6) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q6, sizeof(q6) - 1, &e) == 0); /* splice */ char q7[] = "update t0 set k1 = splice(k1, 0, 2, 'AB') where k0 = 222"; - TT_ASSERT(tnt_query(&net, q7, sizeof(q7) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q7, sizeof(q7) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); struct tnt_iter i; tnt_iter_stream(&i, &net); @@ -663,18 +770,17 @@ static void tt_tnt_sql_update(struct tt_test *test) { /* sql select */ static void tt_tnt_sql_select(struct tt_test *test) { - struct tnt_list *search = - tnt_list(NULL, tnt_tuple(NULL, "%d", 222), NULL); - TT_ASSERT(tnt_select(&net, 0, 0, 0, 1, search) > 0); + char *e = NULL; + char q[] = "select * from t0 where k0 = 222 or k0 = 222"; + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); - tnt_list_free(search); struct tnt_iter i; tnt_iter_stream(&i, &net); while (tnt_next(&i)) { struct tnt_reply *r = TNT_ISTREAM_REPLY(&i); TT_ASSERT(r->code == 0); TT_ASSERT(r->op == TNT_OP_SELECT); - TT_ASSERT(r->count == 1); + TT_ASSERT(r->count == 2); struct tnt_iter il; tnt_iter_list(&il, TNT_REPLY_LIST(r)); TT_ASSERT(tnt_next(&il) == 1); @@ -698,13 +804,31 @@ static void tt_tnt_sql_select(struct tt_test *test) { tnt_iter_free(&ifl); tnt_iter_free(&il); } + tnt_iter_free(&i); +} + +/* sql select limit */ +static void tt_tnt_sql_select_limit(struct tt_test *test) { + char *e = NULL; + char q[] = "select * from t0 where k0 = 222 limit 0"; + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); + TT_ASSERT(tnt_flush(&net) > 0); + struct tnt_iter i; + tnt_iter_stream(&i, &net); + while (tnt_next(&i)) { + struct tnt_reply *r = TNT_ISTREAM_REPLY(&i); + TT_ASSERT(r->code == 0); + TT_ASSERT(r->op == TNT_OP_SELECT); + TT_ASSERT(r->count == 0); + } + tnt_iter_free(&i); } /* sql delete */ static void tt_tnt_sql_delete(struct tt_test *test) { char *e = NULL; char q[] = "delete from t0 where k0 = 222"; - TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); struct tnt_iter i; tnt_iter_stream(&i, &net); @@ -721,7 +845,7 @@ static void tt_tnt_sql_delete(struct tt_test *test) { static void tt_tnt_sql_call(struct tt_test *test) { char *e = NULL; char q[] = "call box.insert(0, 454, 'abc', 'cba')"; - TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1); + TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0); TT_ASSERT(tnt_flush(&net) > 0); struct tnt_iter i; tnt_iter_stream(&i, &net); @@ -759,6 +883,8 @@ main(int argc, char * argv[]) tt_test(&t, "select", tt_tnt_net_select); tt_test(&t, "delete", tt_tnt_net_delete); tt_test(&t, "call", tt_tnt_net_call); + tt_test(&t, "call (no args)", tt_tnt_net_call_na); + tt_test(&t, "reply", tt_tnt_net_reply); /* sql lexer */ tt_test(&t, "lex ws", tt_tnt_lex_ws); tt_test(&t, "lex integer", tt_tnt_lex_int); @@ -775,6 +901,7 @@ main(int argc, char * argv[]) tt_test(&t, "sql insert", tt_tnt_sql_insert); tt_test(&t, "sql update", tt_tnt_sql_update); tt_test(&t, "sql select", tt_tnt_sql_select); + tt_test(&t, "sql select limit", tt_tnt_sql_select_limit); tt_test(&t, "sql delete", tt_tnt_sql_delete); tt_test(&t, "sql call", tt_tnt_sql_call); diff --git a/test/lib/test_suite.py b/test/lib/test_suite.py index 3273a836a4476fbeb4e1d5a5c88f096c6fb80d7c..69a2657bfa94ed5377e6f575b6a31a93ba1fe4dd 100644 --- a/test/lib/test_suite.py +++ b/test/lib/test_suite.py @@ -79,7 +79,8 @@ class Test: self.result = name.replace(".test", ".result") self.tmp_result = os.path.join(self.args.vardir, os.path.basename(self.result)) - self.reject = name.replace(".test", ".reject") + self.reject = "{0}/test/{1}".format(self.args.builddir, + name.replace(".test", ".reject")) self.is_executed = False self.is_executed_ok = None self.is_equal_result = None diff --git a/test/share/tarantool_box.sup b/test/share/tarantool_box.sup index 1efcdd908c28cd6bd6ae5cb1789a030fa68fe0ee..b7fbfebafa622462f916b7d16f4267604947e3de 100644 --- a/test/share/tarantool_box.sup +++ b/test/share/tarantool_box.sup @@ -139,6 +139,17 @@ ... } +{ + <box upadte command> + Memcheck:Leak + fun:salloc + fun:tuple_alloc + fun:prepare_update + fun:box_dispatch + fun:box_process* + ... +} + ## ## tarantool/lua suppressions ## @@ -243,32 +254,22 @@ ... } -## sptree -## - -## third_party sptree implementation doesn't have -## destroy function. +# third_party/luajit/src/lj.supp -- add as recommended in +# http://lua-users.org/lists/lua-l/2011-08/msg00736.html +# Valgrind suppression file for LuaJIT 2.x. { - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:realloc - fun:sptree_str_t_init - fun:build_indexes - ... + Optimized string compare + Memcheck:Addr4 + fun:lj_str_cmp } - -# iterator_init_set calls realloc only if iterator value not -# initialized or have lower depth. - { - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:realloc - fun:sptree_str_t_iterator_init_set - fun:index_iterator_init_tree_str - fun:process_select - ... + Optimized string compare + Memcheck:Addr4 + fun:lj_str_new +} +{ + Optimized string compare + Memcheck:Cond + fun:lj_str_new } diff --git a/third_party/sptree.h b/third_party/sptree.h index b8428042efe71e47b6c07227cdf4f82dfdf5c0fc..cb5551ba10def7d753eb907f62092cc923599f2e 100644 --- a/third_party/sptree.h +++ b/third_party/sptree.h @@ -159,6 +159,13 @@ sptree_##name##_init(sptree_##name *t, size_t elemsize, void *m, } \ } \ \ +static inline void \ +sptree_##name##_destroy(sptree_##name *t) { \ + if (t == NULL) return; \ + free(t->members); \ + free(t->lrpointers); \ +} \ + \ static inline void* \ sptree_##name##_find(sptree_##name *t, void *k) { \ spnode_t node = t->root; \ @@ -520,12 +527,15 @@ sptree_##name##_iterator_init_set(sptree_##name *t, sptree_##name##_iterator **i spnode_t node; \ int lastLevelEq = -1, cmp; \ \ - if ((*i) == NULL || t->max_depth > (*i)->max_depth) \ + if ((*i) == NULL || t->max_depth > (*i)->max_depth) \ *i = realloc(*i, sizeof(**i) + sizeof(spnode_t) * (t->max_depth + 1)); \ \ (*i)->t = t; \ (*i)->level = -1; \ - if (t->root == SPNIL) return; \ + if (t->root == SPNIL) { \ + (*i)->max_depth = 0; /* valgrind points out it's used in the check above ^.*/ \ + return; \ + } \ \ (*i)->max_depth = t->max_depth; \ (*i)->stack[0] = t->root; \