├── debian ├── compat ├── source │ └── format ├── lua5.2.dh-lua.conf ├── gbp.conf ├── rules ├── lua5.1.dh-lua.conf ├── watch ├── changelog ├── control └── copyright ├── .gitignore ├── lua-resty-tarantool-scm-3.rockspec ├── html ├── ldoc.css └── resty-tarantool.html.html ├── README.md └── lib └── resty └── tarantool.lua /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | encapinfo 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/lua5.2.dh-lua.conf: -------------------------------------------------------------------------------- 1 | lua5.1.dh-lua.conf -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | pristine-tar = False 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --buildsystem=lua --with lua 5 | 6 | -------------------------------------------------------------------------------- /debian/lua5.1.dh-lua.conf: -------------------------------------------------------------------------------- 1 | LUA_VERSION= 2 | PKG_NAME=resty-tarantool 3 | 4 | CLIB_CFLAGS= 5 | CLIB_LDFLAGS= 6 | CLIB_OBJS= 7 | 8 | LUA_HEADER= 9 | LUA_SOURCES=$(wildcard lib/resty/*.lua) 10 | LUA_MODNAME= 11 | LUA_SOURCES_MANGLER=sed 's?^lib/resty?resty?' 12 | 13 | PKG_VERSION=$(shell dpkg-parsechangelog|grep ^Ver|cut -d ' ' -f 2|cut -d '-' -f 1) 14 | PKG_LIBS_PRIVATE= 15 | PKG_URL= 16 | PKG_REQUIRES= 17 | PKG_CONFLICTS= 18 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | # Compulsory line, this is a version 3 file 2 | version=3 3 | 4 | # For GitHub projects you can use the tags or releases page. Since the archive 5 | # URLs use only the version as the name, it is recommended to use a 6 | # filenamemangle to adjust the name of the downloaded file: 7 | opts="filenamemangle=s/(?:.*)?v?(\d[\d\.]*)\.tar\.gz/lua-resty-tarantool-$1.tar.gz/" \ 8 | https://github.com/perusio/lua-resty-tarantool/tags (?:.*/)?v?(\d[\d\.]*)\.tar\.gz 9 | -------------------------------------------------------------------------------- /lua-resty-tarantool-scm-3.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-tarantool" 2 | version = "scm-3" 3 | source = { 4 | url = "git+ssh://git@github.com/perusio/lua-resty-tarantool" 5 | } 6 | description = { 7 | summary = "Tarantool integration for Openresty", 8 | detailed = "Library for working with tarantool from nginx with the embedded Lua module for Openresty.", 9 | homepage = "github.com/perusio/lua-resty-tarantool", 10 | license = "MIT" 11 | } 12 | dependencies = { 13 | "lua-messagepack ~> 0.5"; 14 | } 15 | build = { 16 | type = "builtin", 17 | modules = { 18 | ["resty.tarantool"] = "lib/resty/tarantool.lua" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | lua-resty-tarantool (0.3-perusio.1.0) unstable; urgency=medium 2 | 3 | * New upstream version. 4 | 5 | -- António P. P. Almeida Wed, 21 Oct 2015 16:03:58 +0200 6 | 7 | lua-resty-tarantool (0.2-perusio.1.1) unstable; urgency=medium 8 | 9 | * Fixed lintian errors. 10 | 11 | -- António P. P. Almeida Wed, 30 Sep 2015 02:35:39 +0200 12 | 13 | lua-resty-tarantool (0.2-perusio.1.0) unstable; urgency=medium 14 | 15 | * New upstream version. 16 | 17 | -- António P. P. Almeida Wed, 30 Sep 2015 02:04:33 +0200 18 | 19 | lua-resty-tarantool (0.1-perusio.1.0) unstable; urgency=medium 20 | 21 | * First Debian package build. 22 | 23 | -- António P. P. Almeida Wed, 30 Sep 2015 01:55:01 +0200 24 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: lua-resty-tarantool 2 | Maintainer: António P. P. Almeida 3 | Uploaders: António P. P. Almeida 4 | Section: interpreters 5 | Priority: optional 6 | Build-Depends: debhelper (>= 9), 7 | dh-lua 8 | Standards-Version: 3.9.6 9 | Vcs-Browser: https://github.com/perusio/lua-resty-tarantool 10 | Vcs-Git: git://git@github.com:perusio/lua-resty-tarantool.git 11 | Homepage: https://github.com/perusio/lua-resty-tarantool 12 | 13 | Package: lua-resty-tarantool 14 | Architecture: all 15 | Depends: ${shlibs:Depends}, 16 | ${misc:Depends}, 17 | nginx (>= 1.9.0-perusio.1.0) | nginx-extras, 18 | lua-bitop | liblua5.2-0 19 | Description: Library to work with the tarantool NoSQL database 20 | based on the cosocket driver for the nginx embedded Lua language 21 | module. 22 | . 23 | Because this module is based on the ngx_lua's cosocket API, it inherits 24 | the advantage of a real nonblocking behaviour running effectively on the 25 | underlying nginx server ultra-fast technology design, allowing Web 26 | developers making use of the Lua programming language to script and 27 | construct extremely high-performance web applications capable to handle 28 | 10K+ connections. 29 | . 30 | Note that this module works for both nginx with the embedded Lua 31 | module and the OpenResty bundle. 32 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: lua-resty-tarantool 3 | Upstream-Contact: António P. P. Almeida 4 | Source: https://github.com/perusio/lua-resty-tarantool 5 | 6 | Files: * 7 | Copyright: 2015 António P. P. Almeida 8 | License: MIT 9 | 10 | License: MIT 11 | The MIT License 12 | . 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated 15 | documentation files (the "Software"), to deal in the Software 16 | without restriction, including without limitation the rights to 17 | use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to 19 | whom the Software is furnished to do so, subject to the 20 | following conditions: 21 | . 22 | The above copyright notice and this permission notice shall 23 | be included in all copies or substantial portions of the 24 | Software. 25 | . 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT 27 | WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 28 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR 30 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT 31 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 32 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 34 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 35 | CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | OTHER DEALINGS IN THE SOFTWARE. 37 | -------------------------------------------------------------------------------- /html/ldoc.css: -------------------------------------------------------------------------------- 1 | /* BEGIN RESET 2 | 3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 4 | Code licensed under the BSD License: 5 | http://developer.yahoo.com/yui/license.html 6 | version: 2.8.2r1 7 | */ 8 | html { 9 | color: #000; 10 | background: #FFF; 11 | } 12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | table { 17 | border-collapse: collapse; 18 | border-spacing: 0; 19 | } 20 | fieldset,img { 21 | border: 0; 22 | } 23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 24 | font-style: inherit; 25 | font-weight: inherit; 26 | } 27 | del,ins { 28 | text-decoration: none; 29 | } 30 | li { 31 | list-style: disc; 32 | margin-left: 20px; 33 | } 34 | caption,th { 35 | text-align: left; 36 | } 37 | h1,h2,h3,h4,h5,h6 { 38 | font-size: 100%; 39 | font-weight: bold; 40 | } 41 | q:before,q:after { 42 | content: ''; 43 | } 44 | abbr,acronym { 45 | border: 0; 46 | font-variant: normal; 47 | } 48 | sup { 49 | vertical-align: baseline; 50 | } 51 | sub { 52 | vertical-align: baseline; 53 | } 54 | legend { 55 | color: #000; 56 | } 57 | input,button,textarea,select,optgroup,option { 58 | font-family: inherit; 59 | font-size: inherit; 60 | font-style: inherit; 61 | font-weight: inherit; 62 | } 63 | input,button,textarea,select {*font-size:100%; 64 | } 65 | /* END RESET */ 66 | 67 | body { 68 | margin-left: 1em; 69 | margin-right: 1em; 70 | font-family: arial, helvetica, geneva, sans-serif; 71 | background-color: #ffffff; margin: 0px; 72 | } 73 | 74 | code, tt { font-family: monospace; } 75 | span.parameter { font-family:monospace; } 76 | span.parameter:after { content:":"; } 77 | span.types:before { content:"("; } 78 | span.types:after { content:")"; } 79 | .type { font-weight: bold; font-style:italic } 80 | 81 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 82 | 83 | p, ul { margin: 10px 0 0 0px;} 84 | 85 | strong { font-weight: bold;} 86 | 87 | em { font-style: italic;} 88 | 89 | h1 { 90 | font-size: 1.5em; 91 | margin: 0 0 20px 0; 92 | } 93 | h2, h3, h4 { margin: 15px 0 10px 0; } 94 | h2 { font-size: 1.25em; } 95 | h3 { font-size: 1.15em; } 96 | h4 { font-size: 1.06em; } 97 | 98 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 99 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 100 | a:link:hover { text-decoration: underline; } 101 | 102 | hr { 103 | color:#cccccc; 104 | background: #00007f; 105 | height: 1px; 106 | } 107 | 108 | blockquote { margin-left: 3em; } 109 | 110 | ul { list-style-type: disc; } 111 | 112 | p.name { 113 | font-family: "Andale Mono", monospace; 114 | padding-top: 1em; 115 | } 116 | 117 | pre.example { 118 | background-color: rgb(245, 245, 245); 119 | border: 1px solid silver; 120 | padding: 10px; 121 | margin: 10px 0 10px 0; 122 | font-family: "Andale Mono", monospace; 123 | font-size: .85em; 124 | } 125 | 126 | pre { 127 | background-color: rgb(245, 245, 245); 128 | border: 1px solid silver; 129 | padding: 10px; 130 | margin: 10px 0 10px 0; 131 | overflow: auto; 132 | font-family: "Andale Mono", monospace; 133 | } 134 | 135 | 136 | table.index { border: 1px #00007f; } 137 | table.index td { text-align: left; vertical-align: top; } 138 | 139 | #container { 140 | margin-left: 1em; 141 | margin-right: 1em; 142 | background-color: #f0f0f0; 143 | } 144 | 145 | #product { 146 | text-align: center; 147 | border-bottom: 1px solid #cccccc; 148 | background-color: #ffffff; 149 | } 150 | 151 | #product big { 152 | font-size: 2em; 153 | } 154 | 155 | #main { 156 | background-color: #f0f0f0; 157 | border-left: 2px solid #cccccc; 158 | } 159 | 160 | #navigation { 161 | float: left; 162 | width: 18em; 163 | vertical-align: top; 164 | background-color: #f0f0f0; 165 | overflow: visible; 166 | } 167 | 168 | #navigation h2 { 169 | background-color:#e7e7e7; 170 | font-size:1.1em; 171 | color:#000000; 172 | text-align: left; 173 | padding:0.2em; 174 | border-top:1px solid #dddddd; 175 | border-bottom:1px solid #dddddd; 176 | } 177 | 178 | #navigation ul 179 | { 180 | font-size:1em; 181 | list-style-type: none; 182 | margin: 1px 1px 10px 1px; 183 | } 184 | 185 | #navigation li { 186 | text-indent: -1em; 187 | display: block; 188 | margin: 3px 0px 0px 22px; 189 | } 190 | 191 | #navigation li li a { 192 | margin: 0px 3px 0px -1em; 193 | } 194 | 195 | #content { 196 | margin-left: 18em; 197 | padding: 1em; 198 | width: 700px; 199 | border-left: 2px solid #cccccc; 200 | border-right: 2px solid #cccccc; 201 | background-color: #ffffff; 202 | } 203 | 204 | #about { 205 | clear: both; 206 | padding: 5px; 207 | border-top: 2px solid #cccccc; 208 | background-color: #ffffff; 209 | } 210 | 211 | @media print { 212 | body { 213 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 214 | } 215 | a { font-weight: bold; color: #004080; text-decoration: underline; } 216 | 217 | #main { 218 | background-color: #ffffff; 219 | border-left: 0px; 220 | } 221 | 222 | #container { 223 | margin-left: 2%; 224 | margin-right: 2%; 225 | background-color: #ffffff; 226 | } 227 | 228 | #content { 229 | padding: 1em; 230 | background-color: #ffffff; 231 | } 232 | 233 | #navigation { 234 | display: none; 235 | } 236 | pre.example { 237 | font-family: "Andale Mono", monospace; 238 | font-size: 10pt; 239 | page-break-inside: avoid; 240 | } 241 | } 242 | 243 | table.module_list { 244 | border-width: 1px; 245 | border-style: solid; 246 | border-color: #cccccc; 247 | border-collapse: collapse; 248 | } 249 | table.module_list td { 250 | border-width: 1px; 251 | padding: 3px; 252 | border-style: solid; 253 | border-color: #cccccc; 254 | } 255 | table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } 256 | table.module_list td.summary { width: 100%; } 257 | 258 | 259 | table.function_list { 260 | border-width: 1px; 261 | border-style: solid; 262 | border-color: #cccccc; 263 | border-collapse: collapse; 264 | } 265 | table.function_list td { 266 | border-width: 1px; 267 | padding: 3px; 268 | border-style: solid; 269 | border-color: #cccccc; 270 | } 271 | table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } 272 | table.function_list td.summary { width: 100%; } 273 | 274 | ul.nowrap { 275 | overflow:auto; 276 | white-space:nowrap; 277 | } 278 | 279 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 280 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 281 | dl.table h3, dl.function h3 {font-size: .95em;} 282 | 283 | /* stop sublists from having initial vertical space */ 284 | ul ul { margin-top: 0px; } 285 | ol ul { margin-top: 0px; } 286 | ol ol { margin-top: 0px; } 287 | ul ol { margin-top: 0px; } 288 | 289 | /* styles for prettification of source */ 290 | pre .comment { color: #558817; } 291 | pre .constant { color: #a8660d; } 292 | pre .escape { color: #844631; } 293 | pre .keyword { color: #2239a8; font-weight: bold; } 294 | pre .library { color: #0e7c6b; } 295 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } 296 | pre .string { color: #a8660d; } 297 | pre .number { color: #f8660d; } 298 | pre .operator { color: #2239a8; font-weight: bold; } 299 | pre .preprocessor, pre .prepro { color: #a33243; } 300 | pre .global { color: #800080; } 301 | pre .prompt { color: #558817; } 302 | pre .url { color: #272fc2; text-decoration: underline; } 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Openresty library for querying the tarantool NoSQL database 2 | 3 | ## Introduction 4 | 5 | This is a library to connect to the [tarantool](http://tarantool.org) 6 | NoSQL database. This database has very interesting features that make 7 | it be sort of a bridge between a traditional SQL based database and 8 | document oriented storages like [CouchDB](http://couchdb.org). 9 | 10 | It's a fork of another 11 | [project](https://github.com/ziontab/lua-nginx-tarantool) that I was 12 | unhappy with. It's abundantly documented and is update regarding the 13 | tarantool API. Notably wtih support for the 14 | [upsert](https://github.com/tarantool/tarantool/issues/905) command. 15 | 16 | Another thing to bear in mind is that the library tries to be 17 | consistent between the way the `update` and `upsert` commands are 18 | issued in the console using Lua and the way the API works. Notably the 19 | field numbers. In the console a field number takes into account the 20 | existence of a primary index as the first field. Hence any field that 21 | come afterward will have an position that accounts for 22 | it. Specifically when specifying the operators to use for the `update` 23 | or `upsert` operations. 24 | 25 | ## Installation 26 | 27 | ### OpenResty 28 | 29 | If you're using [OpenResty](http://openresty.org) the library should 30 | be installed under: `/usr/local/openresty/lualib/resty`. 31 | 32 | ### Debian package 33 | 34 | I package the library for debian 35 | [here](https://debian.perusio.net). Just follow the instructions there 36 | and install it. 37 | 38 | ### adhoc installation 39 | 40 | Put the library in a place in your filesystem that you deem 41 | appropriate. Don't forget to adjust the Lua package path, either by 42 | setting `package.path` in Lua code or using the 43 | [`lua_package_path`](https://www.nginx.com/resources/wiki/modules/lua/#lua-package-path) 44 | directive. 45 | 46 | ## Requirements 47 | 48 | Since tarantool uses [MessagePack](http://msgpack.org) for 49 | serialization the 50 | [lua-MessagePack](https://github.com/fperrad/lua-MessagePack) package 51 | is required. 52 | 53 | It relies on the [BitOp](http://bitop.luajit.org/api.html) from 54 | LuaJIT. Therefore you need a nginx Lua module that is linked against **LuaJIT** and 55 | not Lua 5.1. 56 | 57 | ## Usage 58 | 59 | ### Creating a connection 60 | 61 | ```lua 62 | local tnt = require 'resty.tarantool' 63 | 64 | local tar, err = tnt:new({ 65 | host = '127.0.0.1', 66 | port = 3301, 67 | user = 'luser', 68 | password = 'some_password', 69 | socket_timeout = 2000, 70 | call_semantics = 'new' -- can be 'new' or 'old'* 71 | }) 72 | ``` 73 | 74 | The above creates a connection object that connects to a tarantool 75 | server instance running on the loopback in port 3301, for user `luser` 76 | with password `some password`. See the 77 | [Tarantool manual in authentication](http://tarantool.org/doc/book/box/authentication.html) 78 | for details on how to setup users and assigning privileges to them. 79 | 80 | The socket timeout (receive and send) is 2 seconds (2000 ms). 81 | 82 | \* The option `call_semantics` controls whether the code for the new call 83 | method (0x0a) or the old one (0x06) is used. 84 | The old method wraps every result in a table as described [here][binary-protocol]. 85 | **The current default is 'old', but this might change in the future**. 86 | It is therefore suggested to set it manually to 'old' in projects that rely on 87 | the old behavior. 88 | 89 | [binary-protocol]: https://www.tarantool.io/en/doc/2.2/dev_guide/internals/box_protocol/ 'Tarantool Binary Protocol' 90 | 91 | ### set_timeout 92 | 93 | settimeout(, ) 94 | 95 | Sets both the send and receive timeouts in miliseconds for a given 96 | socket. 97 | 98 | ```lua 99 | tnt:set_timeout(5000) -- 5s timeout for send/receive operations 100 | ``` 101 | 102 | The function returns true if the setting succeeds, `nil` if not. Note 103 | that for the timeout to take effect this function needs to be invoked 104 | **before** the connection is established, i.e., before invoking the 105 | `connect` function. Alternatively the timeout can be specified when 106 | creating the connection object (cosocket). 107 | 108 | ### connect 109 | 110 | connect() 111 | 112 | Connects the socket created above to the port and address specified 113 | when creating the connection object. 114 | 115 | ```lua 116 | tar:connect() 117 | ``` 118 | The function returns true if the connection succeeds, `nil` if not. 119 | 120 | ### set_keepalive 121 | 122 | set_keepalive() 123 | 124 | Makes the connection created get pushed to a connection pool so that 125 | the connection is kept alive across multiple requests. 126 | 127 | ```lua 128 | tar:set_keepalive() 129 | ``` 130 | 131 | The function returns true if the socket is successfully pushed to 132 | connection pool (set keepalive). `nil` if not. 133 | 134 | ### disconnect 135 | 136 | disconnect() 137 | 138 | Closes a connection to a given tarantool server running on a given 139 | address and port. 140 | 141 | ```lua 142 | tar:disconnect() 143 | ``` 144 | 145 | The function returns true if the connection is successfully closed. `nil` if not. 146 | 147 | 148 | ### ping 149 | 150 | The ping command is useful for monitoring the tarantool server to see 151 | if it's available. If it's available for queries it returns the string 152 | `PONG`. 153 | 154 | ```lua 155 | tar:ping() 156 | -- returns PONG 157 | ``` 158 | 159 | ### select 160 | 161 | The select operation queries a given database (space) for retrieving 162 | records. 163 | 164 | select(, , , , ) 165 | 166 | where `` is an optional argument that can consists of a table 167 | that can have the following keys: 168 | 169 | * `offset`: number of records to skip when doing the query. 170 | * `limit`: the maximum number of records to return. 171 | * `iterator`: a number specifiyng the iterator to use. Specified by 172 | the table: 173 | 174 | ```lua 175 | local iterator_keys = { 176 | EQ = 0, -- equality 177 | REQ = 1, -- reverse equality 178 | ALL = 2, -- all tuples in an index 179 | LT = 3, -- less than 180 | LE = 4, -- less than or equal 181 | GE = 5, -- greater than or equal 182 | GT = 6, -- greater than 183 | BITSET_ALL_SET = 7, -- bits in the bitmask all set 184 | BITSET_ANY_SET = 8, -- any of the bist in the bitmask are set 185 | BITSET_ALL_NOT_SET = 9, -- none on the bits on the bitmask are set 186 | } 187 | ``` 188 | More details about iterators on the [tarantool manual](http://tarantool.org/doc/book/box/box_index.html). 189 | 190 | #### select examples 191 | 192 | ##### Query the `_space` space (DB) to get the space id of the `_index` space. 193 | 194 | ```lua 195 | local res, err = tar:select('_space', 'name', '_index') 196 | 197 | if err then return ngx.say(err) end 198 | 199 | -- response: 200 | [2881,"_index","memtx",0,"", 201 | [{"name":"id","type":"num"}, 202 | {"name":"iid","type":"num"}, 203 | {"name":"name","type":"str"}, 204 | {"name":"type","type":"str"}, 205 | {"name":"opts","type":"array"}, 206 | {"name":"parts","type":"array"}]]] 207 | ``` 208 | The above request is equivalent to the console request: 209 | 210 | ```lua 211 | box.space._space.index.name:select{ '_index' } 212 | ``` 213 | 214 | #### Query the space 'activities' for the activities with a `price` less than 300 215 | 216 | ```lua 217 | -- N.B. price is an index of the activities space. 218 | local res, err = tar:select('activities', 'price', 300, { iterator = 'LT' }) 219 | ``` 220 | The above request is equivalent to the console request: 221 | 222 | ```lua 223 | box.space.activities.index.price:select({ 300 }, { iterator = 'LT' }) 224 | ``` 225 | ### insert 226 | 227 | insert(, , ) 228 | 229 | where `` is the tuple to insert into `` while setting 230 | the primary index, which is unique, to the value specified in the 231 | tuple. 232 | 233 | The function returns the **inserted** record if the operation succeeds. 234 | 235 | #### insert examples 236 | 237 | ```lua 238 | local res, err = tar:insert('activities', { 16, 120, { activity = 'surf', price = 121 } }) 239 | 240 | -- response: 241 | [[16,120,{"activity":"surf","price":121}]] 242 | ``` 243 | The above request is equivalent to the console request: 244 | 245 | ```lua 246 | box.space.activities:insert({16, 120, { activity = 'surf', price = 121 }}) 247 | ``` 248 | 16 is the value of the primary index here. This means that for an 249 | integer type index this will be the record with primary index 16. 250 | 251 | ### replace 252 | 253 | replace(, , ) 254 | 255 | The replace command is similar in the invocation and signature to the 256 | insert command. But now we're looking for **replacing** a record that 257 | exists already instead of inserting a new one. We need again the value 258 | of a primary unique index. But now the value must exist for the 259 | operation to succeed. If the operations succeeds the record with the 260 | **replaced** values is returned. 261 | 262 | #### replace examples 263 | 264 | ```lua 265 | local res, err = tar:replace('activities', { 16, 120, { activity = 'surf', price = 120 } }) 266 | -- response: 267 | [[16,120,{"activity":"surf","price":120}]] 268 | ``` 269 | Here we replace the former 121 price by 120. The value of the primary 270 | index, 16 matches the record we inserted above. 271 | 272 | The above request is equivalent to the console request: 273 | 274 | ```lua 275 | box.space.activities:update({ 16, 120, { activity = 'surf', price = 120 }}) 276 | ``` 277 | ### update 278 | 279 | update(, , , , ) 280 | 281 | where `` is the list of operators as specified n 282 | [tarantool manual](http://tarantool.org/doc/book/box/box_space.html?highlight=operator#lua-function.space_object.update). 283 | The pair (, ) uniquely identifies a record, i.e., the 284 | `` is a value of the primary (unique) ``. 285 | 286 | `` is a table of the form: 287 | 288 | ```lua 289 | { , , } 290 | ``` 291 | the operators are: 292 | 293 | * `+` for adding to a numeric field. 294 | * `-` for subtracting to a numeric field. 295 | * `&` for bitwise AND operation between two unsigned integers. 296 | * `|` for bitwise OR operation between two unsigned integers. 297 | * `^` for bitwise XOR operation between two unsigned integers. 298 | * `:` for string splicing. 299 | * `!` for field insertion. 300 | * `#` for field deletion. 301 | * `=` for assigning a given value to a field. 302 | 303 | it returns the **updated** record if the operation is successful. 304 | 305 | #### update examples 306 | 307 | ```lua 308 | local res, err = tar:update('activities', 'primary', 16, { { '=', 2, 341 }, { '=', 3, { activity = 'kitesurfing', price = 341 }}} ) 309 | -- response: 310 | [16,341,{"activity":"kitesurfing","price":341}]] 311 | ``` 312 | The record with `primary` index 16 that we inserted above was updated. 313 | 314 | The above request is equivalent to the console request: 315 | 316 | ```lua 317 | box.space.activities.index.primary({ 16 }, { { '=', 2, 341 }, { '=', 3, { activity = 'kitesurfing', price = 341 }}}) 318 | ``` 319 | ### upsert 320 | 321 | upsert(, , , , ) 322 | 323 | apart from the `` argument the function signature is 324 | similar to update. In fact upsert is two commands in one. update if 325 | the record specified by the pair (, ) exists and insert if 326 | not. The key is a value from a primary index, i.e., is unique. The 327 | `` is the tuple to be inserted if the `` value doesn't 328 | exist in the ``. It returns an empty table `{}` if the 329 | operation is successful. If the operation is unsuccessful it returns `nil`. 330 | 331 | #### upsert examples 332 | 333 | An **insert**. 334 | 335 | ```lua 336 | local res, err = tar:upsert('activities', 17, { { '=', 2, 450 }, { '=', 3, { activity = 'submarine tour 8', price = 450 }}}, { 17, 450, { activity = 'waterski', price = 365 }}) 337 | -- response: 338 | {} 339 | ``` 340 | We **inserted** a new record with key 17 for the primary index from 341 | the tuple: 342 | 343 | ```lua 344 | { 18, 450, { activity = 'waterski', price = 365 }} 345 | ``` 346 | The above request is equivalent to the console request: 347 | 348 | ```lua 349 | box.space.activities:upsert({ 17 }, { { '=', 2, 450 }, { '=', 3, { activity = 'submarine tour 8', price = 450 }}}, { 17, 450, { activity = 'waterski', price = 365 }}) 350 | ``` 351 | An **update**. 352 | 353 | ```lua 354 | local res, err = tar:upsert('activities', 17, { { '=', 2, 450 }, { '=', 3, { activity = 'submarine tour 8', price = 450 }}}, { 18, 285, { activity = 'kitesurfing', price = 285 }}) 355 | -- response: 356 | {} 357 | ``` 358 | Now we perform an update of the record identified by the key 17 in de 359 | `primary` index (unique). 360 | 361 | ### delete 362 | 363 | delete(, , ) 364 | 365 | deletes the record uniquely specified by `` from ``. Note 366 | that `` must belong to a primary (unique) index. It returns the 367 | **deleted** record if the operation is successful. 368 | 369 | #### delete examples 370 | 371 | ```lua 372 | local response, err = tar:delete('activities', 17) 373 | -- response: 374 | [17,450,{"activity":"waterski","price":365}]] 375 | ``` 376 | We deleted the record uniquely identified by the key 17 in the primary 377 | index from the activites space. 378 | 379 | The above request is equivalent to the console request: 380 | 381 | ```lua 382 | box.space.activities:delete({ 17 }) 383 | 384 | ``` 385 | 386 | ### call 387 | 388 | call(, , ) 389 | 390 | Invokes a 391 | [stored procedure](http://tarantool.org/doc/book/app_c_lua_tutorial.html) 392 | (Lua function) in the tarantool server we're connected to. It returns 393 | the **results** of the invocation. 394 | 395 | #### call examples 396 | 397 | Since the tarantool console is a Lua REPL any function can be invoked 398 | as long as it is available in the environment. 399 | 400 | ```lua 401 | local res, err = tar:call('table.concat', {{ 'hello', ' ', 'world' }}) 402 | -- response: 403 | [["hello world"]] 404 | ``` 405 | We called the `table.concat` function from the table library to 406 | concatenate the table: 407 | 408 | ```lua 409 | {'hello', ' ', 'world' } 410 | ``` 411 | The above request is equivalent to the console request: 412 | 413 | ```lua 414 | table.concat({ 'hello', ' ', 'world' }) 415 | ``` 416 | 417 | For many examples of tarantool stored procedures see the repository; 418 | https://github.com/mailru/tarlua 419 | 420 | ### eval 421 | 422 | eval(, , ) 423 | 424 | Invokes the tarantool embedded Lua interpreter to evaluate the given 425 | `` and returns the result in the ``, which 426 | is usually just an empty table `{ }`. 427 | 428 | ### eval examples 429 | 430 | ```lua 431 | local res, err = tar:eval('return 23 * 20', { }) 432 | -- response: 433 | [460] 434 | ``` 435 | we invoked the interpreter to evaluate the Lua expression: 436 | 437 | ```lua 438 | return 23 * 20 439 | ``` 440 | 441 | which is also the equivalent tarantool console request. 442 | 443 | ### hide\_version\_header 444 | 445 | hide_version_header() 446 | 447 | By default each response sends a custom HTTP header 448 | `X-Tarantool-Version` with the version of the tarantool server. 449 | 450 | X-Tarantool-Version: 1.6.6-191-g82d1bc3 451 | 452 | Invoking `hide_version_header` removes the header. 453 | 454 | ```lua 455 | tar:hide_version_header() 456 | ``` 457 | 458 | It returns no values. 459 | 460 | ## TODO 461 | 462 | * Test setup. 463 | -------------------------------------------------------------------------------- /html/resty-tarantool.html.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 42 | 43 |
44 | 45 |

Module tarantool.lua

46 |

Module for working with tarantool in OpenResty or nginx with 47 | embedded Lua

48 |

49 |

Info:

50 |
    51 |
  • License: MIT
  • 52 |
  • Author: António P. P. Almeida
  • 53 |
54 | 55 | 56 |

Functions

57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
new (self, params)Create a connection object.
disconnect (self)Closes a socket.
set_keepalive (self)Pushes a socket into a connection pool to keep it alive.
set_timeout (self, timeout)Set the timeout for the operations on a socket.
connect (self, host, port)Connect to the server using the created cosocket.
ping (self)Performs a PING type request to the server.
hide_version_header (self)Sets the flag that signals to not send the 85 | Tarantool version as a custom header.
select (self, space, index, key, opts)Performs a select operation on given space with the given index.
insert (self, space, tuple)Inserts a given set of values specified by a tuple.
replace (self, space, tuple)Replaces a given set of values specified by a tuple.
delete (self, space, key)Deletes a given tuple (record) from a space (DB).
update (self, space, index, key, oplist)Updates a given tuple (record) in a space (DB).
upsert (self, space, key, oplist, new_tuple)Upserts a given tuple (record) in a space (DB).
call (self, proc, args)Executes a stored procedure (Lua function) in a tarantool server.
eval (self, exp, result)Performs an eval operation in the tarantool server.
120 | 121 |
122 |
123 | 124 | 125 |

Functions

126 |
127 |
128 | 129 | new (self, params) 130 |
131 |
132 | Create a connection object. 133 | 134 | 135 |

Parameters:

136 |
    137 |
  • self 138 | table connection object. 139 |
  • 140 |
  • params 141 | table connection parameters. 142 |
  • 143 |
144 | 145 |

Returns:

146 |
    147 | 148 | table 149 | The connection object. 150 |
151 | 152 | 153 | 154 | 155 |
156 |
157 | 158 | disconnect (self) 159 |
160 |
161 | Closes a socket. 162 | 163 | 164 |

Parameters:

165 |
    166 |
  • self 167 | table connection object. 168 |
  • 169 |
170 | 171 |

Returns:

172 |
    173 | 174 | boolean 175 | true if the closing is successful. 176 |
177 | 178 | 179 | 180 | 181 |
182 |
183 | 184 | set_keepalive (self) 185 |
186 |
187 | Pushes a socket into a connection pool to keep it alive. 188 | 189 | 190 |

Parameters:

191 |
    192 |
  • self 193 | table connection object. 194 |
  • 195 |
196 | 197 |

Returns:

198 |
    199 | 200 | boolean 201 | true if successful. 202 |
203 | 204 | 205 | 206 | 207 |
208 |
209 | 210 | set_timeout (self, timeout) 211 |
212 |
213 | Set the timeout for the operations on a socket. 214 | 215 | 216 |

Parameters:

217 |
    218 |
  • self 219 | table connection object. 220 |
  • 221 |
  • timeout 222 | integer in ms. 223 |
  • 224 |
225 | 226 |

Returns:

227 |
    228 | 229 | boolean 230 | true if successful, false if not. 231 |
232 | 233 | 234 | 235 | 236 |
237 |
238 | 239 | connect (self, host, port) 240 |
241 |
242 | Connect to the server using the created cosocket. 243 | 244 | 245 |

Parameters:

246 |
    247 |
  • self 248 | table connection object. 249 |
  • 250 |
  • host 251 | string connecting to. 252 |
  • 253 |
  • port 254 | integer on this port. 255 |
  • 256 |
257 | 258 |

Returns:

259 |
    260 | 261 | boolean or nil 262 | true if the connection suceeds, nil if not. 263 |
264 | 265 | 266 | 267 | 268 |
269 |
270 | 271 | ping (self) 272 |
273 |
274 | Performs a PING type request to the server. 275 | 276 | 277 |

Parameters:

278 |
    279 |
  • self 280 | table connection object. 281 |
  • 282 |
283 | 284 |

Returns:

285 |
    286 | 287 | string 288 | 'PONG' if request is successful. 289 |
290 | 291 | 292 | 293 | 294 |
295 |
296 | 297 | hide_version_header (self) 298 |
299 |
300 | Sets the flag that signals to not send the 301 | Tarantool version as a custom header. 302 | 303 | 304 |

Parameters:

305 |
    306 |
  • self 307 | table connection object. 308 |
  • 309 |
310 | 311 |

Returns:

312 |
    313 | 314 | nothing 315 | Side effects only. 316 |
317 | 318 | 319 | 320 | 321 |
322 |
323 | 324 | select (self, space, index, key, opts) 325 |
326 |
327 | Performs a select operation on given space with the given index. 328 | 329 | 330 |

Parameters:

331 |
    332 |
  • self 333 | table connection object 334 |
  • 335 |
  • space 336 | string space name. 337 |
  • 338 |
  • index 339 | string index name. 340 |
  • 341 |
  • key 342 | mixed query key. 343 |
  • 344 |
  • opts 345 | table update operations list. 346 |
  • 347 |
348 | 349 |

Returns:

350 |
    351 | 352 | table 353 | Select query result if successful. 354 |
355 | 356 | 357 | 358 | 359 |
360 |
361 | 362 | insert (self, space, tuple) 363 |
364 |
365 | Inserts a given set of values specified by a tuple. 366 | 367 | 368 |

Parameters:

369 |
    370 |
  • self 371 | table connection object. 372 |
  • 373 |
  • space 374 | string space name. 375 |
  • 376 |
  • tuple 377 | table (indexes, field_values). 378 |
  • 379 |
380 | 381 |

Returns:

382 |
    383 | 384 | table 385 | Inserted record. 386 |
387 | 388 | 389 | 390 | 391 |
392 |
393 | 394 | replace (self, space, tuple) 395 |
396 |
397 | Replaces a given set of values specified by a tuple. 398 | 399 | 400 |

Parameters:

401 |
    402 |
  • self 403 | table connection object. 404 |
  • 405 |
  • space 406 | string space name. 407 |
  • 408 |
  • tuple 409 | table (indexes, field_values). The indexes have to 410 | match an existing record (tuple). 411 | 412 |
  • 413 |
414 | 415 |

Returns:

416 |
    417 | 418 | table 419 | Record with the replaced values. 420 |
421 | 422 | 423 | 424 | 425 |
426 |
427 | 428 | delete (self, space, key) 429 |
430 |
431 | Deletes a given tuple (record) from a space (DB). Note that the key 432 | specified must belong to a primary index or any other unique index. 433 | 434 | 435 |

Parameters:

436 |
    437 |
  • self 438 | table connection object. 439 |
  • 440 |
  • space 441 | string space name. 442 |
  • 443 |
  • key 444 | mixed query key. 445 |
  • 446 |
447 | 448 |

Returns:

449 |
    450 | 451 | table. 452 | The deleted record. 453 |
454 | 455 | 456 | 457 | 458 |
459 |
460 | 461 | update (self, space, index, key, oplist) 462 |
463 |
464 | Updates a given tuple (record) in a space (DB). Note that the key 465 | specified must belong to a primary index or any other unique index. 466 | 467 | 468 | 469 |

Parameters:

470 |
    471 |
  • self 472 | table connection object. 473 |
  • 474 |
  • space 475 | string space name. 476 |
  • 477 |
  • index 478 | number or string index identifier. 479 |
  • 480 |
  • key 481 | number or string query key. 482 |
  • 483 |
  • oplist 484 | table update operator list. 485 |
  • 486 |
487 | 488 |

Returns:

489 |
    490 | 491 | table. 492 | Updated or upserted record. 493 |
494 | 495 | 496 | 497 | 498 |
499 |
500 | 501 | upsert (self, space, key, oplist, new_tuple) 502 |
503 |
504 | Upserts a given tuple (record) in a space (DB). Note that the key 505 | specified must belong to a primary index or any other unique 506 | index. Upsert means update if it exists, insert if not. 507 | 508 | 509 | 510 |

Parameters:

511 |
    512 |
  • self 513 | table connection object. 514 |
  • 515 |
  • space 516 | string space name. 517 |
  • 518 |
  • key 519 | mixed query key. 520 |
  • 521 |
  • oplist 522 | table upsert/update operator list. These values are 523 | used for the update. 524 |
  • 525 |
  • new_tuple 526 | table tuple to be used as the record value when 527 | inserting. 528 | 529 |
  • 530 |
531 | 532 |

Returns:

533 |
    534 | 535 | table. 536 | Currently returns an empty table if successful. 537 |
538 | 539 | 540 | 541 | 542 |
543 |
544 | 545 | call (self, proc, args) 546 |
547 |
548 | Executes a stored procedure (Lua function) in a tarantool server. 549 | 550 | 551 |

Parameters:

552 |
    553 |
  • self 554 | table connection object. 555 |
  • 556 |
  • proc 557 | string function name. 558 |
  • 559 |
  • args 560 | table function arguments. 561 |
  • 562 |
563 | 564 |

Returns:

565 |
    566 | 567 | table 568 | Result of the stored procedure invocation. 569 |
570 | 571 | 572 | 573 | 574 |
575 |
576 | 577 | eval (self, exp, result) 578 |
579 |
580 | Performs an eval operation in the tarantool server. I.e., it 581 | evaluates the given Lua code and returns the result in a tuple. 582 | 583 | 584 | 585 |

Parameters:

586 |
    587 |
  • self 588 | table connection object 589 |
  • 590 |
  • exp 591 | string containing the Lua expression to be evaluated. 592 |
  • 593 |
  • result 594 | table the tuple where the result will be returned. 595 |
  • 596 |
597 | 598 |

Returns:

599 |
    600 | 601 | table 602 | If the Lua code evaluation was successful. 603 |
604 | 605 | 606 | 607 | 608 |
609 |
610 | 611 | 612 |
613 |
614 |
615 | generated by LDoc 1.4.2 616 |
617 |
618 | 619 | 620 | -------------------------------------------------------------------------------- /lib/resty/tarantool.lua: -------------------------------------------------------------------------------- 1 | --- Module for working with tarantool in OpenResty or nginx with 2 | -- embedded Lua 3 | -- @module tarantool.lua 4 | -- @author António P. P. Almeida 5 | -- @license MIT 6 | -- @alias M 7 | 8 | -- Bit operations. Try to use the LuaJIT bit operations package. 9 | local ok_bit, bit = pcall(require, 'bit') 10 | -- If in Lua 5.2 use the bit32 package. 11 | if not ok_bit and _VERSION == 'Lua 5.2' then 12 | bit = require 'bit32' 13 | elseif not ok_bit then 14 | return nil, 'Bitwise operator support missing.' 15 | end 16 | 17 | -- MessagePack handling. 18 | local mp = require 'MessagePack' 19 | 20 | --- Some local definitions. 21 | -- String functions. 22 | local sub = string.sub 23 | local gsub = string.gsub 24 | local sbyte = string.byte 25 | local schar = string.char 26 | local slen = string.len 27 | local format = string.format 28 | local match = string.match 29 | -- Table functions. 30 | local concat = table.concat 31 | -- nginx Lua functions. 32 | local ngx = ngx 33 | local tcp = ngx.socket.tcp 34 | local sha1b = ngx.sha1_bin 35 | -- Debugging related functions. 36 | local log = ngx.log 37 | local ERR = ngx.ERR 38 | -- Generic Lua functions. 39 | local type = type 40 | local ipairs = ipairs 41 | local pairs = pairs 42 | local error = error 43 | local tonumber = tonumber 44 | local setmetatable = setmetatable 45 | 46 | -- Depending on the LuaJIT version a table.new function may be 47 | -- available. That speeds up operations since it bypasses most of the 48 | -- memory management associated with table handling: it preallocates 49 | -- the space. This function has the same signature as the C Lua API 50 | -- lua_createtable. narr = number of array elements, 51 | -- nrec = number of non array elements (records). 52 | local ok, new_tab = pcall(require, 'table.new') 53 | if not ok then 54 | new_tab = function (narr, nrec) return {} end 55 | end 56 | 57 | -- Avoid polluting the global environment. 58 | -- If we are in Lua 5.1 this function exists. 59 | if _G.setfenv then 60 | setfenv(1, {}) 61 | else -- Lua 5.2. 62 | _ENV = nil 63 | end 64 | 65 | -- IProto protocol basic settings. 66 | local iproto = { 67 | -- Greeting message components size. 68 | greeting_size = 128, 69 | greeting_salt_offset = 64, 70 | greeting_salt_size = 44, 71 | -- Size of the length for header and body. 72 | head_body_len_size = 5, 73 | -- Maximum number of requests in a single connection. 74 | request_per_connection = 100000, 75 | max_limit = 0xffffffff, 76 | } 77 | 78 | -- IProto packet keys. 79 | local packet_keys = { 80 | type = 0x00, 81 | sync = 0x01, 82 | space_id = 0x10, 83 | index_id = 0x11, 84 | limit = 0x12, 85 | offset = 0x13, 86 | iterator = 0x14, 87 | key = 0x20, 88 | tuple = 0x21, 89 | function_name = 0x22, 90 | username = 0x23, 91 | expression = 0x27, 92 | def_tuple = 0x28, 93 | data = 0x30, 94 | error = 0x31 95 | } 96 | 97 | -- IProto command keys. 98 | local command_keys = { 99 | select = 0x01, 100 | insert = 0x02, 101 | replace = 0x03, 102 | update = 0x04, 103 | delete = 0x05, 104 | call = { 105 | -- Old call, wraps each return value in a table 106 | old = 0x06, 107 | -- Newer call that doesn't wrap values 108 | new = 0x0a 109 | }, 110 | auth = 0x07, 111 | eval = 0x08, 112 | upsert = 0x09, 113 | -- Admin commands. 114 | ping = 0x40, 115 | } 116 | 117 | -- IProto response keys. 118 | local response_keys = { 119 | ok = 0x00, 120 | -- Minimum value. 121 | error = 0x8000, 122 | } 123 | 124 | -- IDs for the various schemas that define tarantool metadata. 125 | local _space_id = { 126 | schema = 272, -- _space 127 | space = 280, -- _schema 128 | index = 288, -- _index 129 | func = 296, -- _func 130 | user = 304, -- _user 131 | priv = 312, -- _priv 132 | cluster = 320, -- _cluster 133 | } 134 | 135 | -- IDs for indexes of _space and _index. 136 | local indexes_id = { 137 | -- _space. 138 | space = { 139 | primary = 0, 140 | name = 2 141 | }, 142 | -- _index. 143 | index = { 144 | primary = 0, 145 | name = 2 146 | } 147 | } 148 | 149 | -- Iterator keys, i.e., codes that represent it in terms of the IProto 150 | -- protocol. 151 | local iterator_keys = { 152 | EQ = 0, -- equality 153 | REQ = 1, -- reverse equality 154 | ALL = 2, -- all tuples in an index 155 | LT = 3, -- less than 156 | LE = 4, -- less than or equal 157 | GE = 5, -- greater than or equal 158 | GT = 6, -- greater than 159 | BITSET_ALL_SET = 7, -- bits in the bitmask all set 160 | BITSET_ANY_SET = 8, -- any of the bist in the bitmask are set 161 | BITSET_ALL_NOT_SET = 9, -- none of the bits on the bitmask are set 162 | } 163 | 164 | -- Default options. 165 | local defaults = { 166 | host = '127.0.0.1', 167 | port = 3301, 168 | password = '', 169 | socket_timeout = 5000, -- ms 170 | call_semantics = 'old', 171 | } 172 | 173 | -- Lua pattern used to extract the version number from the server 174 | -- response header. 175 | local version_number_patt = '[%d.-]+[%l%x]*' 176 | -- For versions less than 0.3.3 this setting makes sense. 177 | -- Otherwise is deprecated. 178 | local mp_version, reps = gsub(mp._VERSION, '%.', '') 179 | if reps > 0 and tonumber(mp_version) < 33 then 180 | mp.set_integer('unsigned') 181 | end 182 | 183 | --- Module table. 184 | local M = { _VERSION = '0.3.1', _NAME = 'tarantool', _DESCRIPTION = 'resty Lua library for tarantool' } 185 | local mt = { __index = M } 186 | 187 | --- Create a connection object. 188 | -- 189 | -- @param self table connection object. 190 | -- @param params table connection parameters. 191 | -- 192 | -- @return table 193 | -- The connection object. 194 | function M.new(self, params) 195 | -- Create an object using the defaults. 196 | -- tarc = tarantool connection. 197 | local tarc = { 198 | -- If true it sends a custom header with the tarantool version. 199 | show_version_header = true, 200 | } 201 | for key, value in pairs(defaults) do 202 | tarc[key] = value 203 | end 204 | -- Loop over the given parameters and assign the values accordingly. 205 | if params and type(params) == 'table' then 206 | for key, value in pairs(params) do 207 | if params[key] ~= nil then 208 | tarc[key] = params[key] 209 | end 210 | end 211 | end 212 | 213 | -- Create a TCP cosocket. 214 | local tcpsock = tcp() 215 | 216 | -- Check if the socket was successfully created. 217 | if not tcpsock then 218 | return nil, err 219 | end 220 | -- Set the socket timeout. 221 | if tarc.socket_timeout then 222 | tcpsock:settimeout(tarc.socket_timeout) 223 | end 224 | 225 | tarc.sock = tcpsock 226 | tarc._spaces = {} 227 | tarc._indexes = {} 228 | return setmetatable(tarc, mt) 229 | end 230 | 231 | --- Closes a socket. 232 | -- 233 | -- @param self table connection object. 234 | -- 235 | -- @return boolean 236 | -- true if the closing is successful. 237 | function M.disconnect(self) 238 | if not self.sock then 239 | return nil, 'No socket created.' 240 | end 241 | return self.sock:close() 242 | end 243 | 244 | --- Pushes a socket into a connection pool to keep it alive. 245 | -- 246 | -- @param self table connection object. 247 | -- 248 | -- @return boolean 249 | -- true if successful. 250 | function M.set_keepalive(self) 251 | if not self.sock then 252 | return nil, 'No socket created.' 253 | end 254 | local ok, err = self.sock:setkeepalive() 255 | -- If we didn't manage to set it in keepalive, close it. 256 | if not ok then 257 | self:disconnect() 258 | return nil, err 259 | end 260 | return ok 261 | end 262 | 263 | --- Set the timeout for the operations on a socket. 264 | -- 265 | -- @param self table connection object. 266 | -- @param timeout integer in ms. 267 | -- 268 | -- @return boolean 269 | -- true if successful, false if not. 270 | function M.set_timeout(self, timeout) 271 | local sock = self.sock 272 | if not sock then 273 | return nil, 'Not initialized.' 274 | end 275 | -- Set the timeout for all socket operations. 276 | return sock:settimeout(timeout) 277 | end 278 | 279 | --- XORs two given strings 280 | -- 281 | -- @param str1 string string 1 to XOR. 282 | -- @param str2 string string 2 to XOR. 283 | -- 284 | -- @return string 285 | -- XORed strings. 286 | local function xorstr(str1, str2) 287 | local len = slen(str1) 288 | local result = new_tab(len, 0) 289 | -- Test if the strings are the same length. 290 | if len ~= slen(str2) then 291 | return nil 292 | end 293 | -- Loop over all the string. 294 | for i = 1, len do 295 | result[i] = schar(bit.bxor(sbyte(str1, i), sbyte(str2, i))) 296 | end 297 | -- Return the concatenated (XORed) string. 298 | return concat(result) 299 | end 300 | 301 | --- Serialize the request message using MsgPack. 302 | -- 303 | -- @param header table request header. 304 | -- @param body table request body. 305 | -- 306 | -- @return string 307 | -- Serialized request messge using MsgPack. 308 | local function encode_request(header, body) 309 | local msg = new_tab(3, 0) 310 | msg[2] = mp.pack(header) 311 | msg[3] = mp.pack(body) 312 | msg[1] = mp.pack(slen(msg[2]) + slen(msg[3])) 313 | return concat(msg) 314 | end 315 | 316 | --- Serialize the request message using MsgPack. 317 | -- for messages with no body (ping). 318 | -- 319 | -- @param header table request header. 320 | -- 321 | -- @return string 322 | -- Serialized request messge using MsgPack. 323 | local function encode_request_no_body(header) 324 | local msg = mp.pack(header) 325 | return mp.pack(slen(msg)) .. msg 326 | end 327 | 328 | --- Issue a request to tarantool. 329 | -- 330 | -- @local 331 | -- 332 | -- @param self table connection object. 333 | -- @param header table request header. 334 | -- @param body table request body (payload). 335 | -- 336 | -- @return table or nil, string. 337 | -- A table with the response or otherwise if there's an error. 338 | local function request(self, header, body) 339 | local sock = self.sock 340 | local htype = type(header) 341 | -- The header must be a table. Bail out if not. 342 | if htype ~= 'table' then 343 | return nil, format('Invalid request header: type %s.', htype) 344 | end 345 | -- Get an ID for the response so that a request can be matched with 346 | -- a response. This is needed for asynch I/O on the server side. A 347 | -- coroutine (cosocket) may yield but we need to keep a count of how 348 | -- many streams are open. So that when it resumes we can match the 349 | -- proper response with the corresponding request. 350 | self.sync_id = ((self.sync_id or 0) + 1) % iproto.request_per_connection 351 | if not header[packet_keys.sync] then 352 | header[packet_keys.sync] = self.sync_id 353 | else 354 | self.sync_id = header[packet_keys.sync] 355 | end 356 | -- Serialize the request message. 357 | local request 358 | -- For all messages except PING the message has always a body. 359 | if body then 360 | request = encode_request(header, body) 361 | else 362 | -- No body: PING message. 363 | request = encode_request_no_body(header) 364 | end 365 | -- Send the request. 366 | local bytes, err = sock:send(request) 367 | -- Check if the request failed. 368 | if bytes == nil then 369 | sock:close() 370 | return nil, format('Failed to send request: %s.', err) 371 | end 372 | -- Receive the response, only the size. 373 | local size, err = sock:receive(iproto.head_body_len_size) 374 | if not size then 375 | sock:close() 376 | return nil, format('Failed to get response size: %s.', err) 377 | end 378 | -- Get the size (deserialize it). 379 | size = mp.unpack(size) 380 | -- If if fails then bail out. 381 | if not size then 382 | -- sock:close() 383 | return nil, 'Client response has invalid size.' 384 | end 385 | -- Extract the message (header and body). 386 | local header_and_body, err, partial = sock:receive(size) 387 | if not header_and_body then 388 | sock:close() 389 | return nil, format('Failed to get response header and body: %s.', err) 390 | end 391 | -- Deserialize the response. Returns an iterator. 392 | local iterator = mp.unpacker(header_and_body) 393 | -- The first element is the header. 394 | local v, response_header = iterator() 395 | -- It should be a table. 396 | htype = type(response_header) 397 | if htype ~= 'table' then 398 | return nil, format('Invalid header: %s (table expected)', htype) 399 | end 400 | -- Check if the response is the one corresponding to the current 401 | -- stream ID. 402 | if response_header[packet_keys.sync] ~= self.sync_id then 403 | return nil, 404 | format('Mismatch of response and request ids. req: %d res: %d.', 405 | self.sync_id, 406 | response_header[packet_keys.sync]) 407 | end 408 | -- Handle the response body. 409 | local response_body 410 | v, response_body = iterator() 411 | -- If not a table then is empty. 412 | if type(response_body) ~= 'table' then 413 | response_body = {} 414 | end 415 | -- Return the response as a table with the data and the metadata. 416 | return { code = response_header[packet_keys.type], 417 | data = response_body[packet_keys.data], 418 | error = response_body[packet_keys.error], 419 | } 420 | end 421 | 422 | --- Perform the authentication with the tarantool server. 423 | -- 424 | -- @param self table object representing the current connection with 425 | -- all the parameters. 426 | -- 427 | -- @local 428 | -- 429 | -- @return boolean or nil, string 430 | -- If the authentication succeeds return true, if not nil and the 431 | -- error message. 432 | local function authenticate(self) 433 | -- If there's no username no authentication is performed. 434 | if not self.user then 435 | return true 436 | end 437 | -- The authentication occurs in three steps. 438 | local first = sha1b(self.password) 439 | local last = sha1b(self._salt .. sha1b(first)) 440 | -- Issue the authentication request. 441 | local response, err = 442 | request(self, 443 | { [packet_keys.type] = command_keys.auth }, 444 | { [packet_keys.username] = self.user, 445 | [packet_keys.tuple] = { 'chap-sha1', xorstr(first, last) } }) 446 | -- If there's an error signal the error and return nil. 447 | if err then 448 | return nil, err 449 | -- Check the server response code. 450 | elseif response and response.code ~= response_keys.ok then 451 | return nil, response and response.error or 'Internal error' 452 | else 453 | -- If the response code is correct return true. 454 | return true 455 | end 456 | end 457 | 458 | --- Performs the handshake of the IProto protocol. 459 | -- 460 | -- @local 461 | -- 462 | -- @param self table connection object. 463 | -- 464 | -- @return boolean or nil, string 465 | -- True if the handshake works. Signal an error if doesn't. 466 | -- 467 | local function handshake(self) 468 | -- First we check if the connection is already in place. If so, if 469 | -- it is ok. 470 | local count, err = self.sock:getreusedtimes() 471 | -- Implementing the handshake of the IProto protocol. 472 | -- @see http://tarantool.org/doc/dev_guide/box-protocol.html. 473 | -- 474 | local greeting, greeting_err 475 | -- If we're creating a new connection instead of reusing an old one 476 | -- comming from the connection pool, i.e., keepalive. 477 | if count == 0 then 478 | -- 1. Getting a greeting packet from the server. 479 | greeting, greeting_err = self.sock:receive(iproto.greeting_size) 480 | if not greeting or greeting_err then 481 | self.sock:close() 482 | return nil, greeting_err 483 | end 484 | -- Get the server version. 485 | self._version = match(sub(greeting, 1, iproto.greeting_salt_offset), 486 | version_number_patt) 487 | -- Set a HTTP header with the server version. 488 | if self.show_version_header then 489 | ngx.header['X-Tarantool-Version'] = self._version 490 | end 491 | -- Get the salt embedded in greeting message. 492 | self._salt = sub(greeting, iproto.greeting_salt_offset + 1) 493 | -- Decode the base64 encoded salt. Note that according to the 494 | -- protocol specification currently only the first 20 bytes are 495 | -- used for the salt. 496 | -- @see http://tarantool.org/doc/dev_guide/box-protocol.html#authentication. 497 | self._salt = sub(ngx.decode_base64(self._salt), 1, 20) 498 | -- Proceed to authenticate now. 499 | return authenticate(self) 500 | end 501 | -- Return true if we're reusing a socket (keepalive). 502 | return true 503 | end 504 | 505 | --- Connect to the server using the created cosocket. 506 | -- 507 | -- @param self table connection object. 508 | -- @param host string connecting to. 509 | -- @param port integer on this port. 510 | -- 511 | -- @return boolean or nil 512 | -- true if the connection suceeds, nil if not. 513 | function M.connect(self, host, port) 514 | -- Check if we have a socket available. 515 | if not self.sock then 516 | return nil, 'No socket created.' 517 | end 518 | -- Connect to the server, 519 | local ok, err = self.sock:connect(host or self.host, port or self.port) 520 | if not ok then 521 | return ok, err 522 | end 523 | -- Perform the handshake. 524 | return handshake(self) 525 | end 526 | 527 | --- Performs a PING type request to the server. 528 | -- 529 | -- @param self table connection object. 530 | -- 531 | -- @return string 532 | -- 'PONG' if request is successful. 533 | function M.ping(self) 534 | -- Issue the request. PING has no message body. 535 | local response, err = request(self, { [packet_keys.type] = command_keys.ping }) 536 | if err then 537 | return nil, err 538 | elseif response and response.code ~= response_keys.ok then 539 | return nil, response and response.error or 'Internal error.' 540 | else 541 | return 'PONG' 542 | end 543 | end 544 | 545 | --- Sets the flag that signals to not send the 546 | -- Tarantool version as a custom header. 547 | -- 548 | -- @param self table connection object. 549 | -- 550 | -- @return nothing 551 | -- Side effects only. 552 | function M.hide_version_header(self) 553 | self.show_version_header = false 554 | end 555 | 556 | --- Finds a space numeric id to be used in the IProto packets. 557 | -- 558 | -- @local 559 | -- 560 | -- @param self table connection object. 561 | -- @param space string space id (name). 562 | -- 563 | -- @return integer 564 | -- Space numeric id. 565 | local function get_space_id(self, space) 566 | -- Since the IProto protocol uses an integer space_id to associate a 567 | -- a request with a space we need to find first the id of the given 568 | -- space. This is done by querying the server and obtaining the id. 569 | -- The space we need to query where all the spaces are registered is 570 | -- _space. This space has the following metadata. 571 | -- 572 | -- space_id = 280 573 | -- indexes (id, name, spec) 574 | -- 0: primary - 1 part, NUM 575 | -- 1: owner - 1 part, STR 576 | -- 2: name - 1 part, STR 577 | -- We're interested in the index 2, which allow us to obtain the 578 | -- space_id, given the name. So the query is in the tarantool 579 | -- console: 580 | -- box.space._space.index.name:select{}. 581 | -- This returns a table. The first element is the space_id. 582 | -- Otherwise if the space is already a number than we can skip 583 | -- this and return the value immediately. 584 | if type(space) == 'number' then 585 | return space 586 | end 587 | -- If it's a string check if we already have it. 588 | if type(space) == 'string' and type(self._spaces) == 'table' and type(self._spaces[space]) == 'number' then 589 | return self._spaces[space] 590 | end 591 | -- Query the _space space to retrieve space_id. 592 | local data, err = self:select(_space_id.space, indexes_id.space.name, space) 593 | -- If there was an error return it. 594 | if err then return nil, err end 595 | -- Check the reults. It's something of the form: 596 | -- [ space_id, owner_id, name, engine, options ]. 597 | if type(data) == 'table' and type(data[1]) == 'table' and type(data[1][1]) == 'number' then 598 | -- We found the space_id set the attribute in the connection object 599 | -- and return it. 600 | self._spaces[space] = tonumber(data[1][1]) 601 | return self._spaces[space] 602 | end 603 | -- Last resort: we didn't found anything. 604 | return nil, format('Cannot find space: %s.', space) 605 | end 606 | 607 | --- Finds a space numeric id to be used in the IProto packets. 608 | -- 609 | -- @local 610 | -- 611 | -- @param self table connection object. 612 | -- @param space string space id (name). 613 | -- @param index index index id (name). 614 | -- 615 | -- @return integer 616 | -- Index numeric id. 617 | local function get_index_id(self, space, index) 618 | -- Since the IProto protocol uses an integer indexid to associate a 619 | -- a request with a index we need to find first the id of the given 620 | -- index. This is done by querying the server and obtaining the id. 621 | -- The index we need to query where all the indexs are registered is 622 | -- _index. This index has the following metadata. 623 | -- 624 | -- space_id = 288 625 | -- indexes (id, name, spec) 626 | -- 0: primary - 2 parts, (NUM, NUM) 627 | -- 2: name - 2 parts, (NUM, STR) 628 | -- We're interested in the index 2, which allow us to obtain the 629 | -- index_id, given the space_id index name. So the query is in the 630 | -- tarantool console: 631 | -- box.index._index.index.name:select{, }. 632 | -- This returns a table. The second element is the index id. 633 | -- Otherwise if the index is already a number than we can skip 634 | -- this and return the value immediately. 635 | if type(index) == 'number' then 636 | return index 637 | end 638 | -- If it's a string check if we already have it. 639 | if type(index) == 'string' and type(self._indexes) == 'table' and type(self._indexes[index]) == 'number' then 640 | return self._indexes[index] 641 | end 642 | -- First get the space_id. 643 | local spaceid, err = get_space_id(self, space) 644 | -- If there was an error return it. 645 | if not spaceid then return nil, err end 646 | -- Query _index to retrieve the index_id. 647 | local data, err = self:select(_space_id.index, indexes_id.index.name, { spaceid, index }) 648 | -- If there was an error return it. 649 | if err then return nil, err end 650 | -- Check the reults. It's something of the form: 651 | -- [ spaceid, indexid, name, type, {"unique": } [opts]]. 652 | if type(data) == 'table' and type(data[1]) == 'table' and type(data[1][2]) == 'number' then 653 | -- We found the index_id set the attribute in the connection object 654 | -- and return it. 655 | self._indexes[index] = data[1][2] 656 | return self._indexes[index] 657 | end 658 | -- Last resort: we didn't found anything. 659 | return nil, err or format('Cannot find index: %s.', index) 660 | end 661 | 662 | --- Transforms the given argument into a table if it's not already 663 | -- one. IProto key field, an array. 664 | -- 665 | -- @local 666 | -- 667 | -- @param value mixed the value to be used as query 668 | -- key. 669 | -- @return table 670 | -- Given value in a table. 671 | local function prepare_key(value) 672 | if type(value) == 'table' then 673 | return value 674 | elseif value == nil then 675 | return { } 676 | else 677 | return { value } 678 | end 679 | end 680 | 681 | --- Performs a select operation on given space with the given index. 682 | -- 683 | -- @param self table connection object 684 | -- @param space string space name. 685 | -- @param index string index name. 686 | -- @param key mixed query key. 687 | -- @param opts table update operations list. 688 | -- 689 | -- @return table 690 | -- Select query result if successful. 691 | function M.select(self, space, index, key, opts) 692 | -- If not options are given set it to {}. 693 | if opts == nil then opts = {} end 694 | -- First get the space numeric id, i.e., what's the value of the 695 | -- space_id field in the space. 696 | local spaceid, err = get_space_id(self, space) 697 | if not spaceid then 698 | return nil, err 699 | end 700 | -- Second get the index numeric id. Which position is the index 701 | -- we're using to select the record. 702 | local indexid, err = get_index_id(self, spaceid, index) 703 | if not indexid then 704 | return nil, err 705 | end 706 | -- Create the request body (packet payload). 707 | local body = new_tab(0, 6) 708 | body[packet_keys.space_id] = spaceid 709 | body[packet_keys.index_id] = indexid 710 | body[packet_keys.key] = prepare_key(key) 711 | -- Add the limit. 712 | if opts.limit ~= nil then 713 | body[packet_keys.limit] = tonumber(opts.limit) 714 | else 715 | body[packet_keys.limit] = iproto.max_limit 716 | end 717 | -- Add the offset. 718 | if opts.offset and type(opts.offset) == 'number' then 719 | body[packet_keys.offset] = opts.offset 720 | else 721 | body[packet_keys.offset] = 0 722 | end 723 | -- Add the iterator if specified. 724 | if opts.iterator and iterator_keys[opts.iterator] ~= nil then 725 | body[packet_keys.iterator] = iterator_keys[opts.iterator] 726 | else 727 | -- If no valid iterator is specified then set it to 'EQ'. 728 | body[packet_keys.iterator] = iterator_keys.EQ 729 | end 730 | -- Make the select request. 731 | local response, err = request(self, 732 | { [packet_keys.type] = command_keys.select }, 733 | body) 734 | -- Handle the error if it occurs. 735 | if err then 736 | return nil, err 737 | elseif response and response.code ~= response_keys.ok then 738 | return nil, response and response.error or 'Internal error.' 739 | else 740 | -- Return the response data. 741 | return response.data 742 | end 743 | end 744 | 745 | --- Issue a insert or replace command to the tarantool DB. 746 | -- 747 | -- @local 748 | -- 749 | -- @param self table connection object. 750 | -- @param space string space name. 751 | -- @param tuple table values to be inserted/replaced 752 | -- (indexes, field_values). 753 | -- @param action string insert or replace. 754 | -- 755 | -- @return table 756 | -- Request data response. 757 | local function insert_replace(self, space, tuple, action) 758 | -- First get the space numeric id, i.e., what's the value of the 759 | -- space_id field in the space. 760 | local spaceid, err = get_space_id(self, space) 761 | if not spaceid then 762 | return nil, err 763 | end 764 | -- Issue the request. It can be an insert or replace operations. 765 | local response, err = request(self, 766 | { [packet_keys.type] = command_keys[action] }, 767 | { [packet_keys.space_id] = spaceid, 768 | [packet_keys.tuple] = tuple }) 769 | -- Handle the error if it occurs. 770 | if err then 771 | return nil, err 772 | elseif response and response.code ~= response_keys.ok then 773 | return nil, response and response.error or 'Internal error.' 774 | else 775 | -- Return the response data. 776 | return response.data 777 | end 778 | end 779 | 780 | --- Inserts a given set of values specified by a tuple. 781 | -- 782 | -- @param self table connection object. 783 | -- @param space string space name. 784 | -- @param tuple table (indexes, field_values). 785 | -- 786 | -- @return table 787 | -- Inserted record. 788 | function M.insert(self, space, tuple) 789 | return insert_replace(self, space, tuple, 'insert') 790 | end 791 | 792 | --- Replaces a given set of values specified by a tuple. 793 | -- 794 | -- @param self table connection object. 795 | -- @param space string space name. 796 | -- @param tuple table (indexes, field_values). The indexes have to 797 | -- match an existing record (tuple). 798 | -- 799 | -- @return table 800 | -- Record with the replaced values. 801 | function M.replace(self, space, tuple) 802 | return insert_replace(self, space, tuple, 'replace') 803 | end 804 | 805 | --- Deletes a given tuple (record) from a space (DB). Note that the key 806 | -- specified must belong to a primary index or any other unique index. 807 | -- @param self table connection object. 808 | -- @param space string space name. 809 | -- @param key mixed query key. 810 | -- 811 | -- @return table. 812 | -- The deleted record. 813 | function M.delete(self, space, key) 814 | -- First get the space numeric id, i.e., what's the value of the 815 | -- space_id field in the space. 816 | local spaceid, err = get_space_id(self, space) 817 | if not spaceid then 818 | return nil, err 819 | end 820 | -- Issue the request. 821 | local response, err = request(self, 822 | { [packet_keys.type] = command_keys.delete }, 823 | { [packet_keys.space_id] = spaceid, 824 | [packet_keys.key] = prepare_key(key) }) 825 | -- Handle the error if it occurs. 826 | if err then 827 | return nil, err 828 | elseif response and response.code ~= response_keys.ok then 829 | return nil, response and response.error or 'Internal error.' 830 | else 831 | -- Return the response data. 832 | return response.data 833 | end 834 | end 835 | 836 | --- Massages the operator list for the update operation 837 | -- so that the field numbers are the same as in the console. 838 | -- 839 | -- @local 840 | -- 841 | -- @param oplist table operator list. 842 | -- 843 | -- @return table 844 | -- Massaged operator list. 845 | local function prepare_op(oplist) 846 | local new_oplist, len = oplist, #oplist 847 | -- Since in the operator list the field positions differ from 848 | -- the ones given on the console. Here we assume that the the 849 | -- primary index doesn't count. So what would in the console is 850 | -- field 2, here is field 1, and so on. 851 | -- Loop over all operators. 852 | for i = 1, len do 853 | -- In each operator list component the field number is always 854 | -- the second element. Subtract one to field number to get the 855 | -- field number we need to send in the request: 2 -> 1, 3 -> 2, 856 | -- and so on. 857 | new_oplist[i][2] = oplist[i][2] - 1 858 | end 859 | -- Returned the massaged operator list. 860 | return new_oplist 861 | end 862 | 863 | --- Updates a given tuple (record) in a space (DB). Note that the key 864 | -- specified must belong to a primary index or any other unique index. 865 | -- 866 | -- @param self table connection object. 867 | -- @param space string space name. 868 | -- @param index number or string index identifier. 869 | -- @param key number or string query key. 870 | -- @param oplist table update operator list. 871 | -- 872 | -- @return table. 873 | -- Updated or upserted record. 874 | function M.update(self, space, index, key, oplist) 875 | -- First get the space numeric id, i.e., what's the value of the 876 | -- space_id field in the space. 877 | local spaceid, err = get_space_id(self, space) 878 | if not spaceid then 879 | return nil, err 880 | end 881 | -- Second get the index numeric id. Which position is the index 882 | -- we're using to select the record. 883 | local indexid, err = get_index_id(self, spaceid, index) 884 | if not indexid then 885 | return nil, err 886 | end 887 | -- Issue the request. 888 | local response, err = request(self, 889 | { [packet_keys.type] = command_keys.update }, { 890 | [packet_keys.space_id] = spaceid, 891 | [packet_keys.index_id] = indexid, 892 | [packet_keys.key] = prepare_key(key), 893 | [packet_keys.tuple] = prepare_op(oplist) }) 894 | -- Handle the error if it occurs. 895 | if err then 896 | return nil, err 897 | elseif response and response.code ~= response_keys.ok then 898 | return nil, response and response.error or 'Internal error.' 899 | else 900 | -- Return the response data. 901 | return response.data 902 | end 903 | end 904 | 905 | --- Upserts a given tuple (record) in a space (DB). Note that the key 906 | -- specified must belong to a primary index or any other unique 907 | -- index. Upsert means update if it exists, insert if not. 908 | -- 909 | -- @param self table connection object. 910 | -- @param space string space name. 911 | -- @param key mixed query key. 912 | -- @param oplist table upsert/update operator list. These values are 913 | -- used for the update. 914 | -- @param new_tuple table tuple to be used as the record value when 915 | -- inserting. 916 | -- 917 | -- @return table. 918 | -- Currently returns an empty table if successful. 919 | function M.upsert(self, space, key, oplist, new_tuple) 920 | -- First get the space numeric id, i.e., what's the value of the 921 | -- space_id field in the space. 922 | local spaceid, err = get_space_id(self, space) 923 | if not spaceid then 924 | return nil, err 925 | end 926 | -- Issue the request. 927 | local response, err = request(self, 928 | { [packet_keys.type] = command_keys.upsert }, 929 | { [packet_keys.space_id] = spaceid, 930 | [packet_keys.key] = prepare_key(key), 931 | [packet_keys.tuple] = new_tuple, 932 | [packet_keys.def_tuple] = prepare_op(oplist) }) 933 | -- Handle the error if it occurs. 934 | if err then 935 | return nil, err 936 | elseif response and response.code ~= response_keys.ok then 937 | return nil, response and response.error or 'Internal error.' 938 | else 939 | -- Return the response data. 940 | return response.data 941 | end 942 | end 943 | 944 | --- Executes a stored procedure (Lua function) in a tarantool server. 945 | -- Uses the new (0x0a) command, which does not wrap each return value in a table. 946 | -- 947 | -- @param self table connection object. 948 | -- @param proc string function name. 949 | -- @param args table function arguments. 950 | -- 951 | -- @return table 952 | -- Result of the stored procedure invocation. 953 | function M.call(self, proc, args) 954 | -- Issue the request. 955 | local response, err = request(self, 956 | { [packet_keys.type] = command_keys.call[self.call_semantics] or error('Incorrect value for "call_semantics" option') }, 957 | { [packet_keys.function_name] = proc, 958 | [packet_keys.tuple] = args } ) 959 | -- Handle the error if it occurs. 960 | if err then 961 | return nil, err 962 | elseif response and response.code ~= response_keys.ok then 963 | return nil, response and response.error or 'Internal error.' 964 | else 965 | -- Return the response data. 966 | return response.data 967 | end 968 | end 969 | 970 | --- Performs an eval operation in the tarantool server. I.e., it 971 | -- evaluates the given Lua code and returns the result in a tuple. 972 | -- 973 | -- @param self table connection object 974 | -- @param exp string containing the Lua expression to be evaluated. 975 | -- @param result table the tuple where the result will be returned. 976 | -- 977 | -- @return table 978 | -- If the Lua code evaluation was successful. 979 | function M.eval(self, exp, result) 980 | -- Issue the request. 981 | local response, err = request(self, 982 | { [packet_keys.type] = command_keys.eval }, 983 | { [packet_keys.expression] = exp, 984 | [packet_keys.tuple] = result } ) 985 | -- Handle the error if it occurs. 986 | if err then 987 | return nil, err 988 | elseif response and response.code ~= response_keys.ok then 989 | return nil, response and response.error or 'Internal error.' 990 | else 991 | -- Return the response data. 992 | return response.data 993 | end 994 | end 995 | 996 | -- Return the module table. 997 | return M 998 | --------------------------------------------------------------------------------