├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── couchdb.lua ├── dist.ini ├── lua-resty-couchdb-4.2-2.rockspec └── spec └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.rock 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openresty/openresty:buster 2 | 3 | RUN mkdir /home/rogon 4 | RUN apt update 5 | RUN apt install -y procps vim curl luarocks 6 | RUN luarocks install busted 7 | 8 | CMD ["openresty"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jeffry L 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | 3 | docker pull couchdb:3.0 4 | docker run -d --rm --name couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password couchdb:3.0 5 | docker build -t openresty . 6 | docker run -it --name openresty --link couchdb:openresty -v $(CURDIR):"/home/rogon/lua-resty-couchdb" openresty:latest /bin/bash 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-couchdb 2 | 3 | Lua resty minimal couchdb client 4 | 5 | # Installation 6 | 7 | ```bash 8 | #luarocks install lua-resty-couchdb 9 | ``` 10 | 11 | # Usage 12 | ```lua 13 | local couch = require 'resty.couchdb' 14 | local config = { 15 | host = 'https://localhost:5984', 16 | user = 'couchdb-user', 17 | password = 'couchdb-pass' 18 | } 19 | local couch = couch.new(config) 20 | local user = couch:db('_users') 21 | 22 | -- create db 23 | local res, err = user:create() 24 | 25 | -- add rows 26 | local res, err = user:post(data) 27 | 28 | -- view 29 | local res, err = user:view('room', 'booked', { 30 | inclusive_end = tostring(true), -- boolean not supported, must be string 31 | start_key = '"hello"', -- double quote required by couchdb 32 | end_key = '"world' 33 | }) 34 | 35 | -- all docs 36 | local res, err = user:all_docs({ 37 | inclusive_end = tostring(true), -- boolean not supported, must be string 38 | start_key = '"hello"', -- double quote required by couchdb 39 | end_key = '"world' 40 | }) 41 | 42 | -- delete db 43 | local res, err = user:destory() 44 | 45 | 46 | ``` 47 | 48 | ### API 49 | Please refer to the CouchDB API documentation at [docs.couchdb.org](http://docs.couchdb.org/en/stable/http-api.html) for available 50 | REST API. 51 | 52 | #### configuration 53 | This api should be called first to set the correct database parameter 54 | before calling any database action method. 55 | 56 | - database name eg: booking 57 | 58 | #### get(id) 59 | Get database value 60 | - id document id 61 | - return lua table 62 | 63 | #### put(data) 64 | Insert data to database 65 | - id document id 66 | - data *(table)* data to save 67 | 68 | #### post(data) 69 | Insert data to database 70 | - id document id 71 | - data *(table)* data to save 72 | 73 | 74 | #### delete(id) 75 | Delete data from database 76 | - id document id 77 | 78 | #### save(data) 79 | Update existing data. This api will automatically get the latest rev to use for updating the data. 80 | - id document id 81 | - data *(table)* to save 82 | 83 | 84 | #### view(design_name, view_name, opts) 85 | Query rows of data using views 86 | - design_name *(string)* couchdb design name 87 | - view_name *(string)* couchdb view name 88 | - opts *(table)* options parameter as [documented here](http://docs.couchdb.org/en/stable/api/ddoc/views.html). 89 | Important note: start\_key and end\_key must always surrounded by double quote and boolean value not supported. 90 | For boolean value, it should be converted to string using lua **tostring** 91 | 92 | #### all_docs(opts) 93 | Query rows of data using bulk api 94 | - opts *(table)* options parameter as [documented here](http://docs.couchdb.org/en/stable/api/database/bulk-api.html). 95 | Important note: start\_key and end\_key must always surrounded by double quote and boolean value not supported. 96 | For boolean value, it should be converted to string using lua **tostring** 97 | 98 | #### bulk_docs(opts) 99 | Update data using bulk api 100 | - opts *(table)* options parameter as [documented here](http://docs.couchdb.org/en/stable/api/database/bulk-api.html). 101 | 102 | 103 | #### create() 104 | Create new database name 105 | 106 | #### destroy() 107 | Delete database 108 | 109 | 110 | ## Reference 111 | - [CouchDB API](http://docs.couchdb.org/en/stable/http-api.html) 112 | - [CouchDB View Options](http://docs.couchdb.org/en/stable/api/ddoc/views.html) 113 | - [Request documentation](https://github.com/request/request) 114 | -------------------------------------------------------------------------------- /couchdb.lua: -------------------------------------------------------------------------------- 1 | -- Minimalist couchdb client for lua resty 2 | -- Author: Jeffry L. 3 | -- Website: github.com/paragasu/lua-resty-couchdb 4 | -- Licence: MIT 5 | 6 | local http = require 'resty.http' 7 | local json = json or require 'cjson' 8 | local _M = { __VERSION = '4.2-2' } 9 | local mt = { __index = _M } 10 | local i = require 'inspect' 11 | local database = nil 12 | 13 | -- @param config table 14 | -- config.host couchdb db host and port 15 | -- config.username couchdb username 16 | -- config.password couchdb password 17 | function _M.new(config) 18 | if not config then error("Missing couchdb config") end 19 | if not config.user then error("Missing couchdb user") end 20 | if not config.host then error("Missing couchdb server host") end 21 | if not config.password then error("Missing couchdb password config") end 22 | _M.host = config.host 23 | _M.auth_basic_hash = ngx.encode_base64(config.user .. ':' .. config.password) 24 | return setmetatable(_M, mt) 25 | end 26 | 27 | local function is_table(t) return type(t) == 'table' end 28 | 29 | -- modified from https://www.reddit.com/r/lua/comments/417v44/efficient_table_comparison 30 | local function is_table_equal(a,b) 31 | local t1,t2 = {}, {} 32 | if not is_table(a) then return false end 33 | if not is_table(b) then return false end 34 | if #a ~= #b then return false end 35 | for k,v in pairs(a) do t1[k] = (t1[k] or 0) + 1 end 36 | for k,v in pairs(b) do t2[k] = (t2[k] or 0) + 1 end 37 | for k,v in pairs(t1) do if v ~= t2[k] then return false end end 38 | for k,v in pairs(t2) do if v ~= t1[k] then return false end end 39 | return true 40 | end 41 | 42 | local function create_url(path, method, params) 43 | if not database then error("Database not exists") end 44 | if not path then return _M.host .. '/' .. database end 45 | local url = _M.host .. '/' .. database .. '/' .. path 46 | if params ~= nil and (method == 'GET' or method == 'DELETE') then 47 | return url .. '?' .. ngx.encode_args(params) 48 | end 49 | return url 50 | end 51 | 52 | -- build valid view options 53 | -- as in http://docs.couchdb.org/en/1.6.1/api/ddoc/views.html 54 | -- key, startkey, endkey, start_key and end_key is json 55 | -- startkey or end_key must be surrounded by double quote 56 | local function build_query_params(opts_or_key) 57 | if is_table(opts_or_key) then 58 | return ngx.encode_args(opts_or_key) 59 | else 60 | return string.format('key="%s"', opts_or_key) 61 | end 62 | end 63 | 64 | function _M.db(self, database_name) 65 | local db = {} 66 | database = database_name 67 | 68 | local function request(method, path, params) 69 | local httpc = http.new() 70 | local args = { 71 | method = method, 72 | body = json.encode(params), 73 | ssl_verify = false, 74 | headers = { 75 | ['Content-Type'] = 'application/json', 76 | ['Authorization'] = 'Basic ' .. _M.auth_basic_hash 77 | } 78 | } 79 | local url = create_url(path, method, params) 80 | local res, err = httpc:request_uri(url, args) 81 | if not res then return nil, err end 82 | if res.status == 200 or res.status == 201 then 83 | return json.decode(res.body) 84 | else 85 | return nil, json.decode(res.body) 86 | end 87 | end 88 | 89 | -- save document 90 | -- automatically find out the latest rev 91 | function db.save(self, doc) 92 | local old, err = db:get(doc._id) 93 | local params = old or {} 94 | -- only update if data has changes 95 | if is_table_equal(params, doc) then return doc end 96 | for k,v in pairs(doc) do params[k] = v end 97 | local res = db:put(params) 98 | return db:get(res.id) 99 | end 100 | 101 | -- query couchdb design doc 102 | -- opts_or_key assume option or key if string provided 103 | -- construct url query format /_design/design_name/_view/view_name?opts 104 | -- Note: the key params must be enclosed in double quotes 105 | function db.view(self, design_name, view_name, opts_or_key) 106 | local req = { '_design', design_name, '_view', view_name, '?' .. build_query_params(opts_or_key) } 107 | local url = table.concat(req, '/') 108 | return db:get(url) 109 | end 110 | 111 | function db.all_docs(self, args) 112 | return db:get('_all_docs?' .. build_query_params(args)) 113 | end 114 | 115 | function db.bulk_docs(self, params) 116 | return db:post('_bulk_docs', params) 117 | end 118 | 119 | function db.delete(self, doc) 120 | if not is_table(doc) then return nil, 'Delete param is not a valid doc table' end 121 | return request('DELETE', doc._id, { rev = doc._rev }) 122 | end 123 | 124 | function db.get(self, id) return request('GET', id) end 125 | function db.put(self, doc) return request('PUT', doc._id, doc) end 126 | function db.post(self, path, data) return request('POST', path, data) end 127 | function db.find(self, options) return db.post('_find', options) end 128 | function db.create() return request('PUT') end 129 | function db.destroy() return request('DELETE') end 130 | 131 | return db 132 | end 133 | 134 | return _M 135 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-couchdb 2 | version = 4.2.2 3 | abstract = Lua API wrapper for couchdb 4 | author = Jeffry L "paragasu" 5 | is_original = yes 6 | license = mit 7 | repo_link = https://github.com/paragasu/lua-resty-couchdb 8 | requires = lua = 5.1, lua-cjson, lua-resty-http 9 | 10 | -------------------------------------------------------------------------------- /lua-resty-couchdb-4.2-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-couchdb" 2 | version = "4.2-2" 3 | source = { 4 | url = "git://github.com/paragasu/lua-resty-couchdb", 5 | tag = "v4.2-2" 6 | } 7 | description = { 8 | summary = "Minimalist couchdb client for lua resty", 9 | homepage = "https://github.com/paragasu/lua-resty-couchdb", 10 | license = "MIT", 11 | maintainer = "Jeffry L. " 12 | } 13 | dependencies = { 14 | "lua >= 5.1", 15 | "lua-cjson", 16 | "lua-resty-http" 17 | } 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | ["resty.couchdb"] = "couchdb.lua", 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";../?.lua" 2 | 3 | local i = require 'inspect' 4 | local json = require 'cjson' 5 | local couchdb = require 'couchdb' 6 | local couch = couchdb.new({ 7 | host = 'http://127.0.0.1:5984', 8 | user = 'admin', 9 | password = 'admin' 10 | }) 11 | 12 | local db = couch:db('test') 13 | 14 | require 'busted.runner'() 15 | 16 | describe('Database', function() 17 | 18 | setup(function() 19 | local res, err = db:destroy() 20 | end) 21 | 22 | it('Create test database', function() 23 | local res, err = db:create() 24 | assert.are.equal(res.ok, true) 25 | end) 26 | 27 | it('Test build view query with string', function() 28 | local url = db.build_query_params('hello') 29 | assert.are.equal(url, 'key="hello"') 30 | end) 31 | 32 | it('Test build view query with table', function() 33 | local url = db.build_query_params({ inclusive_key=tostring(true), start_key='"hello"', end_key='"world"' }) 34 | assert.are.equal(url, 'start_key=%22hello%22&end_key=%22world%22&inclusive_key=true') 35 | end) 36 | 37 | it('Test all docs', function() 38 | local res, err = db:all_docs({ inclusive_key=tostring(true), start_key='"hello"', end_key='"world"' }) 39 | assert.are.equal(res.offset, 0) 40 | end) 41 | 42 | it('Test compare table equal', function() 43 | local res = db.is_table_equal({ hello = 'world' }, nil) 44 | assert.are.equal(res, false) 45 | end) 46 | 47 | it('Test compare table equal', function() 48 | local res = db.is_table_equal({ hello = 'world' }, { hello = 'world' }) 49 | assert.are.equal(res, true) 50 | end) 51 | 52 | it('Test compare table equal', function() 53 | local res = db.is_table_equal({ hello = 'world' }, { hello = 'world', sumandak = 'tamparuli' }) 54 | assert.are.equal(res, false) 55 | end) 56 | 57 | it('Test add doc', function() 58 | local res, err = db:put({ _id = 'hello', hello = 'world' }) 59 | assert.are.equal(res.ok, true) 60 | end) 61 | 62 | it('Test save doc', function() 63 | local res, err = db:save({ _id = 'hello', hello = 'world', sumandak = 'tamparuli' }) 64 | assert.are.equal(res.sumandak, 'tamparuli') 65 | end) 66 | 67 | it('Test delete doc', function() 68 | local doc, err = db:get('hello') 69 | local res, err = db:delete(doc) 70 | local data, err = db:get('hello') 71 | assert.are.equal(err.error, 'not_found') 72 | end) 73 | end) 74 | --------------------------------------------------------------------------------