├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── LICENSE
├── NEWS.md
├── NOTICE
├── README.md
├── doc
├── README.md
├── couchbeam.md
├── couchbeam_app.md
├── couchbeam_attachments.md
├── couchbeam_changes.md
├── couchbeam_changes_stream.md
├── couchbeam_changes_sup.md
├── couchbeam_deps.md
├── couchbeam_doc.md
├── couchbeam_ejson.md
├── couchbeam_httpc.md
├── couchbeam_sup.md
├── couchbeam_util.md
├── couchbeam_uuids.md
├── couchbeam_view.md
├── couchbeam_view_stream.md
├── couchbeam_view_sup.md
├── gen_changes.md
└── overview.edoc
├── examples
├── changes.ebin
├── changes_once.ebin
└── test_gen_changes.erl
├── include
└── couchbeam.hrl
├── rebar.config
├── rebar.lock
├── src
├── couchbeam.app.src
├── couchbeam.erl
├── couchbeam_app.erl
├── couchbeam_attachments.erl
├── couchbeam_changes.erl
├── couchbeam_changes_stream.erl
├── couchbeam_changes_sup.erl
├── couchbeam_doc.erl
├── couchbeam_ejson.erl
├── couchbeam_httpc.erl
├── couchbeam_sup.erl
├── couchbeam_util.erl
├── couchbeam_uuids.erl
├── couchbeam_view.erl
├── couchbeam_view_stream.erl
├── couchbeam_view_sup.erl
└── gen_changes.erl
└── support
└── 1M
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches: [ master, main ]
6 | pull_request:
7 | branches: [ master, main ]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: Setup CouchDB with single node configuration
17 | run: |
18 | # Create local.ini with single node configuration
19 | sudo mkdir -p /opt/couchdb/etc/local.d
20 | sudo tee /opt/couchdb/etc/local.d/10-single-node.ini > /dev/null <
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | couchbeam NEWS
2 | --------------
3 |
4 | version 1.7.0 / 2025-05-28
5 | --------------------------
6 |
7 | - fix resource leaks and race conditions in stream modules
8 | - fix unclosed hackney connections on error paths
9 | - add proper cleanup for monitor references using try-finally
10 | - fix ETS table race conditions by checking process liveness
11 | - improve stream initialization order to prevent races
12 | - add error handling for hackney operations to prevent leaks
13 | - fix changes stream registration race condition by registering before parent notification
14 |
15 | version 1.6.0 / 2025-01-26
16 | --------------------------
17 |
18 | - add support for CouchDB _find endpoint
19 | - add ability to query _show functions
20 | - add option for disabling view_stream usage (enabled by default)
21 | - fix error handling in gen_changes callback handling
22 | - fix resource cleanup in stream modules to prevent connection leaks
23 | - improve replication test reliability and timeout handling
24 | - update hackney dependency to 1.23
25 | - update jsx dependency
26 | - add GitHub Actions for CouchDB testing
27 | - fix dialyzer complaints and pattern matching issues
28 | - OTP 27 compatibility improvements
29 |
30 | version 1.5.4 / 2025-02-20
31 | --------------------------
32 |
33 | - bump hackney 1.22.0
34 |
35 | version 1.5.3 / 2024-08-20
36 | --------------------------
37 |
38 | - fix packaging
39 |
40 | version 1.5.1 / 2024-11-07
41 | --------------------------
42 |
43 | - fix pattern matching
44 |
45 | version 1.5.1 / 2024-11-07
46 | --------------------------
47 |
48 | - fix: handle condition that may cause persistent CLOSE-WAIT sockets
49 |
50 | version 1.5.0 / 2023-10-11
51 | --------------------------
52 |
53 | - use hackney 1.20.0
54 | - fix compatibility with couchdb 3
55 | - fix compatibility with Erlang >= 23
56 |
57 | version 1.4.1 / 2016-09-26
58 | --------------------------
59 |
60 | - maintainance update
61 |
62 | version 1.4.0 / 2016-09-22
63 | --------------------------
64 |
65 | - maintainance update.
66 |
67 | version 1.3.1 / 2016-07-01
68 | --------------------------
69 |
70 | - fix: accept 202 status in `couchbeam:save_doc/4` function (#144)
71 | - fix: spec syntax to build with Erlang 19 (#145)
72 |
73 | version 1.3.0 / 2016-03-22
74 | --------------------------
75 |
76 | - add `couchbeam:all_dbs/2`
77 | - add `couchbeam:view_cleanup/1`
78 | - add `couchbeam:design_info/2`
79 | - add `post_decode` function to view stream
80 | - add Elixir mix support
81 | - fix: handle http errors in view stream (#140)
82 | - fix: build with latest rebar3
83 |
84 | version 1.2.1 / 2015/11/04
85 | --------------------------
86 |
87 | - also support hackney 1.4.4 for rebar2.
88 | - fix hex.pm release to really use 1.4.4
89 |
90 | version 1.2.0 / 2015/11/04
91 | --------------------------
92 |
93 | - move to eunit for tests.
94 | - hex.pm support
95 | - mix & rebar3 build tools support
96 | - bump hackney to 1.4.4
97 | - bump jsx to 2.2.8
98 |
99 | ### Breaking change
100 |
101 | erlang-oauth is now optionnal and won't be installed by default.
102 |
103 | version 1.1.8 / 2015-08-27
104 | --------------------------
105 |
106 | - use latest stable branch of [hackney](https://github.com/benoitc/hackney)
107 |
108 | version 1.1.7 / 2015-03-11
109 | --------------------------
110 |
111 | - bump [hackney](https://github.com/benoitc/hackney) to 1.1.0
112 | - fix `Conten-Type` header ehen posting doc IDS in changes #126
113 | - fix documentation
114 |
115 | version 1.1.6 / 2015-01-02
116 | --------------------------
117 |
118 | - fix `included_applications` (#122)
119 |
120 | version 1.1.5 / 2014-12-09
121 | --------------------------
122 |
123 | - improvement: do not force connections options to nodelay
124 | - update to [Hackney](https://github.com/benoitc/hackney) 1.0.4 fix #120
125 | - fix: retry fecthing UUIDS on error (#121)
126 |
127 | version 1.1.4 / 2014-12-01
128 | --------------------------
129 |
130 | - update to [Hackney](https://github.com/benoitc/hackney) 1.0.1: more SSL
131 | certificate authority handling.
132 | - fix: changes stream
133 |
134 | version 1.1.3 / 2014-11-30
135 | --------------------------
136 |
137 | - update to [Hackney](https://github.com/benoitc/hackney) 1.0.0
138 |
139 | version 1.1.2 / 2014-11-15
140 | --------------------------
141 |
142 | - remove spurious prints
143 |
144 | version 1.1.1 / 2014-11-11
145 | --------------------------
146 |
147 | - update to [hackney 0.15.0](https://github.com/benoitc/hackney/releases ),
148 | improving performances and concurrency
149 | - fix `couchbeam:doc_exists/2`(#116)
150 | - fix `couchbeam:reply_att/1 (#114)
151 |
152 |
153 | version 1.1.0 / 2014-10-28
154 | --------------------------
155 |
156 | - update to [hackney 0.14.3](https://github.com/benoitc/hackney/releases)
157 | - fix memory leaks
158 | - correctly close sockets
159 | - fix streaming issue: don't wait the stream timeout to report the initial
160 | error.
161 | - update JSX dependency to version 2.1.1
162 |
163 | version 1.0.7 / 2014-07-08
164 | --------------------------
165 |
166 | - bump to [hackney 0.13.0](https://github.com/benoitc/hackney/releases/tag/0.13.0)
167 |
168 | version 1.0.6 / 2014-04-18
169 | --------------------------
170 |
171 | - bump to [hackney 0.12.1](https://github.com/benoitc/hackney/releases/tag/0.12.2)
172 |
173 |
174 | version 1.0.5 / 2014-04-18
175 | --------------------------
176 |
177 | - improve connections with HTTP proxies
178 | - improve content-types detection of attachments
179 | - improve URL encoding normalzation, useful when connecting to an
180 | international domain/URI
181 | - URL resolving is faster
182 | - bump to [hackney 0.12.0](https://github.com/benoitc/hackney/releases/tag/0.12.0)
183 |
184 | version 1.0.4 / 2014-04-15
185 | --------------------------
186 |
187 | - remove spurious print
188 |
189 | version 1.0.3 / 2014-04-15
190 | --------------------------
191 |
192 | - add support for the `new_edits` option in bulk doc API.
193 | - improvement: send a doc as multipart that already contains attachments
194 | - bump [hackney](http://github.com/benoitc/hackney) to 0.11.2
195 | - fix path encoding
196 |
197 | version 1.0.2 / 2014-01-03
198 | --------------------------
199 |
200 | - fix: send a doc as multipart that already contains attachments
201 |
202 |
203 | version 1.0.1 / 2013-12-30
204 | --------------------------
205 |
206 | - fix connection reusing in changes and view streams
207 | - bump hackney version to 0.10.1
208 |
209 |
210 | version 1.0.0 / 2013-12-21
211 | --------------------------
212 |
213 | **First stable release**. This is a supported release.
214 |
215 | - send a doc and its attachments efficiently using the [multipart
216 | API](http://docs.couchdb.org/en/latest/api/document/common.html#creating-multiple-attachments).
217 | - add `couchbeam:get_config/{1,2,3}`, `couchbeam:set_config/{4,5}` and
218 | `couchbeam:delete_config/{3,4}` to use the [config
219 | API](http://docs.couchdb.org/en/latest/api/server/configuration.html).
220 | - add `couchbeam_uuids:random/0` and `couchbeam_uuids:utc_random/0` to
221 | generate UUIDS in your app instead of reusing the UUID generated on
222 | the node. By default couchbeam is fetching from the node, which is
223 | - add `{error, forbidden}` and `{error, unauthenticated}` as possible
224 | results of a reply.
225 | better if you want to use UUID based on the node time.
226 | - fix accept header handling
227 |
228 |
229 | version 0.10.0 / 2013-12-21
230 | ---------------------------
231 |
232 | - add `couchbeam:copy_doc/{2,3}` to support the COPY API
233 | - add `couchbeam:get_missing_revs/2` to get the list of missing
234 | revisions
235 | - add support of the [multipart
236 | API](http://docs.couchdb.org/en/latest/api/document/common.html#efficient-multiple-attachments-retrieving) when fetching a doc: This change make
237 | `couchbeam:open_doc/3` return a multipart response `{ok, {multipart,
238 | Stream}}` when using the setting `attachments=true` option. A new option
239 | {`accept. <<"multipart/mixed">>}" can also be used with the options
240 | `open_revs` or `revs` to fetch the response as a multipart.
241 | - bump the [hackney](http://github.com/benoitc/hackney) version to
242 | **0.9.1** .
243 |
244 |
245 | With this change you can now efficiently retrieve a doc with all of its
246 | attachments or a doc wit all its revisions.
247 | master
248 |
249 | version 0.9.3 / 2013-12-07
250 | --------------------------
251 |
252 | - fix: `couchbeam:open_or_create_db/2'
253 |
254 | version 0.9.2 / 2013-12-07
255 | --------------------------
256 |
257 | - bump hackney version to 0.8.3
258 |
259 | version 0.9.1 / 2013-12-05
260 | --------------------------
261 |
262 | - fix design docid encoding
263 |
264 | version 0.9.0 / 2013-12-05
265 | --------------------------
266 |
267 | This is a major release pre-1.0. API is now frozen and won't change much
268 | until the version 1.0.
269 |
270 | - replaced the use of `ibrowse` by `hackney` to handle HTTP connections
271 | - new [streaming
272 | API](https://github.com/benoitc/couchbeam#stream-view-results) in view
273 | - breaking change: remobe
274 | - breaking change: remove deprecated view API. Everything is now managed in the
275 | [couch_view](https://github.com/benoitc/couchbeam/blob/master/doc/couchbeam_view.md) module.
276 | - replace `couchbeam_changes:stream` and `couchbeam_changes:fetch`
277 | functions by `couchbeam_changes/follow` and `couchbeam_changes:follow_once`.
278 | - breaking change: new attachment API
279 | - new: JSX a pure erlang JSON encoder/decoder is now the default. Jiffy
280 | can be set at the compilation by defining `WITH_JIFFY` in the Erlang
281 | options.
282 | - removed mochiweb dependency.
283 |
284 |
285 | version 0.7.0 / 2011-07-05
286 | --------------------------
287 |
288 | This release contains backwards incompatible changes.
289 |
290 | - New and more efficient couchbeam_changes API, we now parse json stream
291 | instead of the try catch steps we used before.
292 | - New and more efficient couchbeam_view API. we now parse json stream
293 | instead of getting all results. New couchbeam_view:stream and
294 | couchbeam_view fetch functions have been added. We also don't use any
295 | more a view record in other functions
296 | - HTTP functions have been moved to couchbeam_httpc modules
297 | - gen_changes behaviour has been updated to use the couchbeam_changes
298 | API. It's also abble to restart a lost connection for longpoll and
299 | continuous feeds.
300 |
301 | Breaking Changes:
302 |
303 | - couchbeam:view and couchbeam:all_docs have been deprecated. Old views
304 | functions using the #view{} record from these functions have been
305 | moved in couchbeam_oldview module.
306 | - couchbeam:wait_changes, couchbeam:wait_changes_once, couchbeam:changes
307 | functions have been deprecated and are now replaced by
308 | couchbeam_changes:stream and couchbeam_changes:fetch functions.
309 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Couchdbeam
2 | ----------
3 |
4 | 2009-2016 (c) Benoitît Chesneau
5 |
6 | couchbeam is released under the MIT license. See the LICENSE file for the
7 | complete license.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Couchbeam - simple Apache CouchDB client library for Erlang applications #
4 |
5 | Copyright (c) 2009-2025 Benoît Chesneau.
6 |
7 | __Version:__ 1.7.0
8 |
9 | # couchbeam
10 |
11 | Couchbeam is a simple erlang library for [Barrel](https://barrel-db.org) or [Apache CouchDB](http://couchdb.apache.org). Couchbeam provides you a full featured and easy client to access and manage multiple nodes.
12 |
13 | #### Main features:
14 |
15 | - Complete support of the BarrelDB and Apache CouchDB API
16 | - Stream view results to your app
17 | - Stream changes feeds
18 | - reduced memory usage
19 | - fetch and send attachments in a streaming fashion
20 | - by default use the JSX module to encode/decode JSON
21 | - support [Jiffy](http://github.com/davisp/jiffy) a JSON encoder/decoder
22 | in C.
23 |
24 | #### Useful modules are:
25 |
26 | - [`couchbeam`](http://github.com/benoitc/couchbeam/blob/master/doc/couchbeam.md): The `couchbeam` module is the main interface for interaction with this application. It includes functions for managing connections to Apache CouchDB or RCOUCH servers and databases and for performing document creations, updates, deletes, views...
27 | - [`couchbeam_doc`](http://github.com/benoitc/couchbeam/blob/master/doc/couchbeam_doc.md) Module to manipulate Documents structures. You can set values,
28 | updates keys, ...
29 | - [`couchbeam_attachments`](http://github.com/benoitc/couchbeam/blob/master/doc/couchbeam_attachments.md): Module to manipulate attachments. You can add, remove
30 | attachments in a Document structure (inline attachments).
31 | - [`couchbeam_view`](http://github.com/benoitc/couchbeam/blob/master/doc/couchbeam_view.md): Module to manage view results.
32 | - [`couchbeam_changes`](http://github.com/benoitc/couchbeam/blob/master/doc/couchbeam_changes.md): Module to manage changes feeds. Follow continuously
33 | the changes in a db or get all changes at once.
34 |
35 | The goal of Couchbeam is to ease the access to the Apache CouchDB and RCOUCH HTTP API in erlang.
36 |
37 | Read the [NEWS](https://raw.github.com/benoitc/couchbeam/master/NEWS) file
38 | to get last changelog.
39 |
40 | ## Installation
41 |
42 | Download the sources from our [Github repository](http://github.com/benoitc/couchbeam)
43 |
44 | To build the application simply run 'make'. This should build .beam, .app
45 | files and documentation.
46 |
47 | To run tests run 'make test'.
48 | To generate doc, run 'make doc'.
49 |
50 | Or add it to your rebar config
51 |
52 | ```
53 | erlang
54 | {deps, [
55 | ....
56 | {couchbeam, ".*", {git, "git://github.com/benoitc/couchbeam.git", {branch, "master"}}}
57 | ]}.
58 | ```
59 |
60 | Note to compile with jiffy you need to define in the erlang options the
61 | variable `WITH_JIFFY`.
62 |
63 | if you use rebar, add to your `rebar.config`:
64 |
65 | ```
66 | erlang
67 | {erl_opts, [{d, 'WITH_JIFFY'}]}.
68 | ```
69 |
70 | or use the `rebar` command with the `-D` options:
71 |
72 | ```
73 | sh
74 | rebar compile -DWITH_JIFFY
75 | ```
76 |
77 | ## Basic Usage
78 |
79 | ### Start couchbeam
80 |
81 | Couchbeam is an [OTP](http://www.erlang.org/doc/design_principles/users_guide.html)
82 | application. You have to start it first before using any of the
83 | functions. The couchbeam application will start the default socket pool
84 | for you.
85 |
86 | To start in the console run:
87 |
88 | ```
89 | sh
90 | $ erl -pa ebin
91 | 1> couchbeam:start().
92 | ok
93 | ```
94 |
95 | It will start hackney and all of the application it depends on:
96 |
97 | ```
98 | erlang
99 | application:start(crypto),
100 | application:start(asn1),
101 | application:start(public_key),
102 | application:start(ssl),
103 | application:start(hackney),
104 | application:start(couchbeam).
105 | ```
106 |
107 | Or add couchbeam to the applications property of your .app in a release
108 |
109 | ### Create a connection to the server
110 |
111 | To create a connection to a server machine:
112 |
113 | ```
114 | erlang
115 | Url = "http://localhost:5984",
116 | Options = [],
117 | S = couchbeam:server_connection(Url, Options).
118 | ```
119 |
120 | Test the connection with `couchbeam:server_info/1` :
121 |
122 | ```
123 | erlang
124 | {ok, _Version} = couchbeam:server_info(S).
125 | ```
126 |
127 | ### Open or Create a database
128 |
129 | All document operations are done in databases. To open a database simply do:
130 |
131 | ```
132 | erlang
133 | Options = [],
134 | {ok, Db} = couchbeam:open_db(Server, "testdb", Options).
135 | ```
136 |
137 | To create a new one:
138 |
139 | ```
140 | erlang
141 | Options = [],
142 | {ok, Db} = couchbeam:create_db(Server, "testdb", Options).
143 | ```
144 |
145 | You can also use the shorcut `couchbeam:open_or_create_db/3`. that
146 | will create a database if it does not exist.
147 |
148 | ### Make a new document
149 |
150 | Make a new document:
151 |
152 | ```
153 | erlang
154 | Doc = {[
155 | {<<"_id">>, <<"test">>},
156 | {<<"content">>, <<"some text">>}
157 | ]}.
158 | ```
159 |
160 | And save it to the database:
161 |
162 | ```
163 | erlang
164 | {ok, Doc1} = couchbeam:save_doc(Db, Doc).
165 | ```
166 |
167 | The `couchbeam:save_doc/2` return a new document with updated
168 | revision and if you do not specify the _id, a unique document id.
169 |
170 | To change an document property use functions from `couchbeam_doc`.
171 |
172 | ### Retrieve a document
173 |
174 | To retrieve a document do:
175 |
176 | ```
177 | erlang
178 | {ok, Doc2} = couchbeam:open_doc(Db, "test").
179 | ```
180 |
181 | If you want a specific revision:
182 |
183 | ```
184 | erlang
185 | Rev = couchbeam_doc:get_rev(Doc1),
186 | Options = [{rev, Rev}],
187 | {ok, Doc3} = couchbeam:open_doc(Db, "test", Options).
188 | ```
189 |
190 | Here we get the revision from the document we previously stored. Any
191 | options from the Apache CouchDB and RCOUCH API can be used.
192 |
193 | ### Get all documents
194 |
195 | To get all documents you have first to create an object
196 | that will keep all informations.
197 |
198 | ```
199 | erlang
200 | Options = [include_docs],
201 | {ok, AllDocs} = couchbeam_view:all(Db, Options).
202 | ```
203 |
204 | Ex of results:
205 |
206 | ```
207 | erlang
208 | {ok,[{[{<<"id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
209 | {<<"key">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
210 | {<<"value">>,
211 | {[{<<"rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18bdb">>}]}},
212 | {<<"doc">>,
213 | {[{<<"_id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
214 | {<<"_rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18"...>>}]}}]},
215 | ]}.
216 | ```
217 |
218 | All functions to manipulate these results are in the `couchbeam_view` module.
219 |
220 | ### Couch DB views
221 |
222 | Views are workin like all_docs. You have to create a View object before
223 | doing anything.
224 |
225 | ```
226 | erlang
227 | Options = [],
228 | DesignName = "designname",
229 | ViewName = "viewname",
230 | {ok, ViewResults} = couchbeam_view:fetch(Db, {DesignName, ViewName}, Options).
231 | ```
232 |
233 | Like the `all_docs` function, use the functions
234 | from `couchbeam_view` module to manipulate results. You can pass
235 | any querying options from the [view API](http://docs.rcouch.org/en/latest/api/ddoc/views.html).
236 |
237 | Design doc are created like any documents:
238 |
239 | ```
240 | erlang
241 | DesignDoc = {[
242 | {<<"_id">>, <<"_design/couchbeam">>},
243 | {<<"language">>,<<"javascript">>},
244 | {<<"views">>,
245 | {[{<<"test">>,
246 | {[{<<"map">>,
247 | <<"function (doc) {\n if (doc.type == \"test\") {\n emit(doc._id, doc);\n}\n}">>
248 | }]}
249 | },{<<"test2">>,
250 | {[{<<"map">>,
251 | <<"function (doc) {\n if (doc.type == \"test2\") {\n emit(doc._id, null);\n}\n}">>
252 | }]}
253 | }]}
254 | }
255 | ]},
256 | {ok, DesignDoc1} = couchbeam:save_doc(Db, DesignDoc).
257 | ```
258 |
259 | You can also use [couchapp](http://github.com/couchapp/couchapp) to manage them
260 | more easily.
261 |
262 | ### Stream View results
263 |
264 | While you can get results using `couchbeam_views:fetch/2`, you can also retrieve
265 | all rows in a streaming fashion:
266 |
267 | ```
268 | erlang
269 | ViewFun = fun(Ref, F) ->
270 | receive
271 | {Ref, done} ->
272 | io:format("done", []),
273 | done;
274 | {Ref, {row, Row}} ->
275 | io:format("got ~p~n", [Row]),
276 | F(Ref, F);
277 | {error, Ref, Error} ->
278 | io:format("error: ~p~n", [Error])
279 | end
280 | end,
281 |
282 | {ok, StreamRef} = couchbeam_view:stream(Db, 'all_docs'),
283 | ViewFun(StreamRef, ViewFun),
284 | {ok, StreamRef2} = couchbeam_view:stream(Db, 'all_docs', [include_docs]),
285 | ViewFun(StreamRef2, ViewFun).
286 | ```
287 |
288 | You can of course do the same with a view:
289 |
290 | ```
291 | erlang
292 | DesignNam = "designname",
293 | ViewName = "viewname",
294 | {ok, StreamRef3} = couchbeam_view:stream(Db, {DesignNam, ViewName}, [include_docs]),
295 | ViewFun(StreamRef3, ViewFun).
296 | ```
297 |
298 | ### Put, Fetch and Delete documents attachments
299 |
300 | You can add attachments to any documents. Attachments could be anything.
301 |
302 | To send an attachment:
303 |
304 | ```
305 | erlang
306 | DocID = "test",
307 | AttName = "test.txt",
308 | Att = "some content I want to attach",
309 | Options = []
310 | {ok, _Result} = couchbeam:put_attachment(Db, DocId, AttName, Att, Options).
311 | ```
312 |
313 | All attachments are streamed to servers. `Att` could be also be an iolist
314 | or functions, see `couchbeam:put_attachment/5` for more information.
315 |
316 | To fetch an attachment:
317 |
318 | ```
319 | erlang
320 | {ok Att1} = couchbeam:fetch_attachment(Db, DocId, AttName).
321 | ```
322 |
323 | You can use `couchbeam:stream_fetch_attachment/6` for the stream
324 | fetch.
325 |
326 | To delete an attachment:
327 |
328 | ```
329 | erlang
330 | {ok, Doc4} = couchbeam:open_doc(Db, DocID),
331 | ok = couchbeam:delete_attachment(Db, Doc4, AttName).
332 | ```
333 |
334 | ### Changes
335 |
336 | Apache CouchDB and RCOUCH provide a means to get a list of changes made to documents in
337 | the database. With couchbeam you can get changes using `couchbeam_changes:follow_once/2`.
338 | This function returns all changes immediately. But you can also retrieve
339 | all changes rows using longpolling :
340 |
341 | ```
342 | erlang
343 | Options = [],
344 | {ok, LastSeq, Rows} = couchbeam_changes:follow_once(Db, Options).
345 | ```
346 |
347 | Options can be any Changes query parameters. See the [change API](http://docs.rcouch.org/en/latest/api/database/changes.html) for more informations.
348 |
349 | You can also get [continuous](http://docs.rcouch.org/en/latest/api/database/changes.html#continuous):
350 |
351 | ```
352 | erlang
353 | ChangesFun = fun(StreamRef, F) ->
354 | receive
355 | {StreamRef, {done, LastSeq}} ->
356 | io:format("stopped, last seq is ~p~n", [LastSeq]),
357 | ok;
358 | {StreamRef, {change, Change}} ->
359 | io:format("change row ~p ~n", [Change]),
360 | F(StreamRef, F);
361 | {StreamRef, Error}->
362 | io:format("error ? ~p ~n,", [Error])
363 | end
364 | end,
365 | Options = [continuous, heartbeat],
366 | {ok, StreamRef} = couchbeam_changes:follow(Db, Options),
367 | ChangesFun(StreamRef, ChangesFun).
368 | ```
369 |
370 | > **Note**: a `gen_changes` behaviour exists in couchbeam that you can
371 | use to create your own specific gen_server receiving changes. Have a
372 | look in the
373 | [example](https://github.com/benoitc/couchbeam/blob/master/examples/test_gen_changes.erl)
374 | for more info.
375 |
376 | ### Authentication/ Connections options
377 |
378 | You can authenticate to the database or Apache CouchDB or RCOUCH server by filling
379 | options to the Option list in `couchbeam:server_connection/4` for the
380 | server or in `couchbeam:create_db/3`, `couchbeam:open_db/3`,
381 | `couchbeam:wopen_or_create_db/3` functions.
382 |
383 | To set basic_auth on a server:
384 |
385 | ```
386 | erlang
387 | UserName = "guest",
388 | Password = "test",
389 | Url = "http://localhost:5984",
390 | Options = [{basic_auth, {UserName, Password}}],
391 | S1 = couchbeam:server_connection(Url, Options).
392 | ```
393 |
394 | Couchbeam support SSL, OAuth, Basic Authentication, and Proxy. You can
395 | also set a cookie. For more informations about the options have a look
396 | in the `couchbeam:server_connection/2` documentation.
397 |
398 | ## Contribute
399 |
400 | For issues, comments or feedback please [create an
401 | issue](http://github.com/benoitc/couchbeam/issues).
402 |
403 |
404 | ## Modules ##
405 |
406 |
407 |
424 |
425 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Couchbeam - simple Apache CouchDB client library for Erlang applications #
4 |
5 | Copyright (c) 2009-2025 Benoît Chesneau.
6 |
7 | __Version:__ 1.5.0
8 |
9 | # couchbeam
10 |
11 | Couchbeam is a simple erlang library for [Barrel](https://barrel-db.org) or [Apache CouchDB](http://couchdb.apache.org). Couchbeam provides you a full featured and easy client to access and manage multiple nodes.
12 |
13 | #### Main features:
14 |
15 | - Complete support of the BarrelDB and Apache CouchDB API
16 | - Stream view results to your app
17 | - Stream changes feeds
18 | - reduced memory usage
19 | - fetch and send attachments in a streaming fashion
20 | - by default use the JSX module to encode/decode JSON
21 | - support [Jiffy](http://github.com/davisp/jiffy) a JSON encoder/decoder
22 | in C.
23 |
24 | #### Useful modules are:
25 |
26 | - [`couchbeam`](couchbeam.md): The `couchbeam` module is the main interface for interaction with this application. It includes functions for managing connections to Apache CouchDB or RCOUCH servers and databases and for performing document creations, updates, deletes, views...
27 | - [`couchbeam_doc`](couchbeam_doc.md) Module to manipulate Documents structures. You can set values,
28 | updates keys, ...
29 | - [`couchbeam_attachments`](couchbeam_attachments.md): Module to manipulate attachments. You can add, remove
30 | attachments in a Document structure (inline attachments).
31 | - [`couchbeam_view`](couchbeam_view.md): Module to manage view results.
32 | - [`couchbeam_changes`](couchbeam_changes.md): Module to manage changes feeds. Follow continuously
33 | the changes in a db or get all changes at once.
34 |
35 | The goal of Couchbeam is to ease the access to the Apache CouchDB and RCOUCH HTTP API in erlang.
36 |
37 | Read the [NEWS](https://raw.github.com/benoitc/couchbeam/master/NEWS) file
38 | to get last changelog.
39 |
40 | ## Installation
41 |
42 | Download the sources from our [Github repository](http://github.com/benoitc/couchbeam)
43 |
44 | To build the application simply run 'make'. This should build .beam, .app
45 | files and documentation.
46 |
47 | To run tests run 'make test'.
48 | To generate doc, run 'make doc'.
49 |
50 | Or add it to your rebar config
51 |
52 | ```
53 | erlang
54 | {deps, [
55 | ....
56 | {couchbeam, ".*", {git, "git://github.com/benoitc/couchbeam.git", {branch, "master"}}}
57 | ]}.
58 | ```
59 |
60 | Note to compile with jiffy you need to define in the erlang options the
61 | variable `WITH_JIFFY`.
62 |
63 | if you use rebar, add to your `rebar.config`:
64 |
65 | ```
66 | erlang
67 | {erl_opts, [{d, 'WITH_JIFFY'}]}.
68 | ```
69 |
70 | or use the `rebar` command with the `-D` options:
71 |
72 | ```
73 | sh
74 | rebar compile -DWITH_JIFFY
75 | ```
76 |
77 | ## Basic Usage
78 |
79 | ### Start couchbeam
80 |
81 | Couchbeam is an [OTP](http://www.erlang.org/doc/design_principles/users_guide.html)
82 | application. You have to start it first before using any of the
83 | functions. The couchbeam application will start the default socket pool
84 | for you.
85 |
86 | To start in the console run:
87 |
88 | ```
89 | sh
90 | $ erl -pa ebin
91 | 1> couchbeam:start().
92 | ok
93 | ```
94 |
95 | It will start hackney and all of the application it depends on:
96 |
97 | ```
98 | erlang
99 | application:start(crypto),
100 | application:start(asn1),
101 | application:start(public_key),
102 | application:start(ssl),
103 | application:start(hackney),
104 | application:start(couchbeam).
105 | ```
106 |
107 | Or add couchbeam to the applications property of your .app in a release
108 |
109 | ### Create a connection to the server
110 |
111 | To create a connection to a server machine:
112 |
113 | ```
114 | erlang
115 | Url = "http://localhost:5984",
116 | Options = [],
117 | S = couchbeam:server_connection(Url, Options).
118 | ```
119 |
120 | Test the connection with `couchbeam:server_info/1` :
121 |
122 | ```
123 | erlang
124 | {ok, _Version} = couchbeam:server_info(S).
125 | ```
126 |
127 | ### Open or Create a database
128 |
129 | All document operations are done in databases. To open a database simply do:
130 |
131 | ```
132 | erlang
133 | Options = [],
134 | {ok, Db} = couchbeam:open_db(Server, "testdb", Options).
135 | ```
136 |
137 | To create a new one:
138 |
139 | ```
140 | erlang
141 | Options = [],
142 | {ok, Db} = couchbeam:create_db(Server, "testdb", Options).
143 | ```
144 |
145 | You can also use the shorcut `couchbeam:open_or_create_db/3`. that
146 | will create a database if it does not exist.
147 |
148 | ### Make a new document
149 |
150 | Make a new document:
151 |
152 | ```
153 | erlang
154 | Doc = {[
155 | {<<"_id">>, <<"test">>},
156 | {<<"content">>, <<"some text">>}
157 | ]}.
158 | ```
159 |
160 | And save it to the database:
161 |
162 | ```
163 | erlang
164 | {ok, Doc1} = couchbeam:save_doc(Db, Doc).
165 | ```
166 |
167 | The `couchbeam:save_doc/2` return a new document with updated
168 | revision and if you do not specify the _id, a unique document id.
169 |
170 | To change an document property use functions from `couchbeam_doc`.
171 |
172 | ### Retrieve a document
173 |
174 | To retrieve a document do:
175 |
176 | ```
177 | erlang
178 | {ok, Doc2} = couchbeam:open_doc(Db, "test").
179 | ```
180 |
181 | If you want a specific revision:
182 |
183 | ```
184 | erlang
185 | Rev = couchbeam_doc:get_rev(Doc1),
186 | Options = [{rev, Rev}],
187 | {ok, Doc3} = couchbeam:open_doc(Db, "test", Options).
188 | ```
189 |
190 | Here we get the revision from the document we previously stored. Any
191 | options from the Apache CouchDB and RCOUCH API can be used.
192 |
193 | ### Get all documents
194 |
195 | To get all documents you have first to create an object
196 | that will keep all informations.
197 |
198 | ```
199 | erlang
200 | Options = [include_docs],
201 | {ok, AllDocs} = couchbeam_view:all(Db, Options).
202 | ```
203 |
204 | Ex of results:
205 |
206 | ```
207 | erlang
208 | {ok,[{[{<<"id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
209 | {<<"key">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
210 | {<<"value">>,
211 | {[{<<"rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18bdb">>}]}},
212 | {<<"doc">>,
213 | {[{<<"_id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
214 | {<<"_rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18"...>>}]}}]},
215 | ]}.
216 | ```
217 |
218 | All functions to manipulate these results are in the `couchbeam_view` module.
219 |
220 | ### Couch DB views
221 |
222 | Views are workin like all_docs. You have to create a View object before
223 | doing anything.
224 |
225 | ```
226 | erlang
227 | Options = [],
228 | DesignName = "designname",
229 | ViewName = "viewname",
230 | {ok, ViewResults} = couchbeam_view:fetch(Db, {DesignName, ViewName}, Options).
231 | ```
232 |
233 | Like the `all_docs` function, use the functions
234 | from `couchbeam_view` module to manipulate results. You can pass
235 | any querying options from the [view API](http://docs.rcouch.org/en/latest/api/ddoc/views.html).
236 |
237 | Design doc are created like any documents:
238 |
239 | ```
240 | erlang
241 | DesignDoc = {[
242 | {<<"_id">>, <<"_design/couchbeam">>},
243 | {<<"language">>,<<"javascript">>},
244 | {<<"views">>,
245 | {[{<<"test">>,
246 | {[{<<"map">>,
247 | <<"function (doc) {\n if (doc.type == \"test\") {\n emit(doc._id, doc);\n}\n}">>
248 | }]}
249 | },{<<"test2">>,
250 | {[{<<"map">>,
251 | <<"function (doc) {\n if (doc.type == \"test2\") {\n emit(doc._id, null);\n}\n}">>
252 | }]}
253 | }]}
254 | }
255 | ]},
256 | {ok, DesignDoc1} = couchbeam:save_doc(Db, DesignDoc).
257 | ```
258 |
259 | You can also use [couchapp](http://github.com/couchapp/couchapp) to manage them
260 | more easily.
261 |
262 | ### Stream View results
263 |
264 | While you can get results using `couchbeam_views:fetch/2`, you can also retrieve
265 | all rows in a streaming fashion:
266 |
267 | ```
268 | erlang
269 | ViewFun = fun(Ref, F) ->
270 | receive
271 | {Ref, done} ->
272 | io:format("done", []),
273 | done;
274 | {Ref, {row, Row}} ->
275 | io:format("got ~p~n", [Row]),
276 | F(Ref, F);
277 | {error, Ref, Error} ->
278 | io:format("error: ~p~n", [Error])
279 | end
280 | end,
281 |
282 | {ok, StreamRef} = couchbeam_view:stream(Db, 'all_docs'),
283 | ViewFun(StreamRef, ViewFun),
284 | {ok, StreamRef2} = couchbeam_view:stream(Db, 'all_docs', [include_docs]),
285 | ViewFun(StreamRef2, ViewFun).
286 | ```
287 |
288 | You can of course do the same with a view:
289 |
290 | ```
291 | erlang
292 | DesignNam = "designname",
293 | ViewName = "viewname",
294 | {ok, StreamRef3} = couchbeam_view:stream(Db, {DesignNam, ViewName}, [include_docs]),
295 | ViewFun(StreamRef3, ViewFun).
296 | ```
297 |
298 | ### Put, Fetch and Delete documents attachments
299 |
300 | You can add attachments to any documents. Attachments could be anything.
301 |
302 | To send an attachment:
303 |
304 | ```
305 | erlang
306 | DocID = "test",
307 | AttName = "test.txt",
308 | Att = "some content I want to attach",
309 | Options = []
310 | {ok, _Result} = couchbeam:put_attachment(Db, DocId, AttName, Att, Options).
311 | ```
312 |
313 | All attachments are streamed to servers. `Att` could be also be an iolist
314 | or functions, see `couchbeam:put_attachment/5` for more information.
315 |
316 | To fetch an attachment:
317 |
318 | ```
319 | erlang
320 | {ok Att1} = couchbeam:fetch_attachment(Db, DocId, AttName).
321 | ```
322 |
323 | You can use `couchbeam:stream_fetch_attachment/6` for the stream
324 | fetch.
325 |
326 | To delete an attachment:
327 |
328 | ```
329 | erlang
330 | {ok, Doc4} = couchbeam:open_doc(Db, DocID),
331 | ok = couchbeam:delete_attachment(Db, Doc4, AttName).
332 | ```
333 |
334 | ### Changes
335 |
336 | Apache CouchDB and RCOUCH provide a means to get a list of changes made to documents in
337 | the database. With couchbeam you can get changes using `couchbeam_changes:follow_once/2`.
338 | This function returns all changes immediately. But you can also retrieve
339 | all changes rows using longpolling :
340 |
341 | ```
342 | erlang
343 | Options = [],
344 | {ok, LastSeq, Rows} = couchbeam_changes:follow_once(Db, Options).
345 | ```
346 |
347 | Options can be any Changes query parameters. See the [change API](http://docs.rcouch.org/en/latest/api/database/changes.html) for more informations.
348 |
349 | You can also get [continuous](http://docs.rcouch.org/en/latest/api/database/changes.html#continuous):
350 |
351 | ```
352 | erlang
353 | ChangesFun = fun(StreamRef, F) ->
354 | receive
355 | {StreamRef, {done, LastSeq}} ->
356 | io:format("stopped, last seq is ~p~n", [LastSeq]),
357 | ok;
358 | {StreamRef, {change, Change}} ->
359 | io:format("change row ~p ~n", [Change]),
360 | F(StreamRef, F);
361 | {StreamRef, Error}->
362 | io:format("error ? ~p ~n,", [Error])
363 | end
364 | end,
365 | Options = [continuous, heartbeat],
366 | {ok, StreamRef} = couchbeam_changes:follow(Db, Options),
367 | ChangesFun(StreamRef, ChangesFun).
368 | ```
369 |
370 | > **Note**: a `gen_changes` behaviour exists in couchbeam that you can
371 | use to create your own specific gen_server receiving changes. Have a
372 | look in the
373 | [example](https://github.com/benoitc/couchbeam/blob/master/examples/test_gen_changes.erl)
374 | for more info.
375 |
376 | ### Authentication/ Connections options
377 |
378 | You can authenticate to the database or Apache CouchDB or RCOUCH server by filling
379 | options to the Option list in `couchbeam:server_connection/4` for the
380 | server or in `couchbeam:create_db/3`, `couchbeam:open_db/3`,
381 | `couchbeam:wopen_or_create_db/3` functions.
382 |
383 | To set basic_auth on a server:
384 |
385 | ```
386 | erlang
387 | UserName = "guest",
388 | Password = "test",
389 | Url = "http://localhost:5984",
390 | Options = [{basic_auth, {UserName, Password}}],
391 | S1 = couchbeam:server_connection(Url, Options).
392 | ```
393 |
394 | Couchbeam support SSL, OAuth, Basic Authentication, and Proxy. You can
395 | also set a cookie. For more informations about the options have a look
396 | in the `couchbeam:server_connection/2` documentation.
397 |
398 | ## Contribute
399 |
400 | For issues, comments or feedback please [create an
401 | issue](http://github.com/benoitc/couchbeam/issues).
402 |
403 |
404 | ## Modules ##
405 |
406 |
407 |
424 |
425 |
--------------------------------------------------------------------------------
/doc/couchbeam_app.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_app #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`application`](application.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### start/2 ###
24 |
25 | `start(Type, StartArgs) -> any()`
26 |
27 |
28 |
29 | ### stop/1 ###
30 |
31 | `stop(State) -> any()`
32 |
33 |
--------------------------------------------------------------------------------
/doc/couchbeam_attachments.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_attachments #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | This module contains utilities to manage attachments.
9 |
10 |
11 |
12 | ## Function Index ##
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## Function Details ##
21 |
22 |
23 |
24 | ### add_inline/3 ###
25 |
26 |
27 | add_inline(Doc::json_obj(), Content::attachment_content(), AName::string()) -> json_obj()
28 |
29 |
30 |
31 | add attachment to a doc and encode it. Give possibility to send attachments inline.
32 |
33 |
34 |
35 | ### add_inline/4 ###
36 |
37 |
38 | add_inline(Doc::json_obj(), Content::attachment_content(), AName::string(), ContentType::string()) -> json_obj()
39 |
40 |
41 |
42 | add attachment to a doc and encode it with ContentType fixed.
43 |
44 |
45 |
46 | ### add_stub/3 ###
47 |
48 | `add_stub(Doc, Name, ContentType) -> any()`
49 |
50 |
51 |
52 | ### delete_inline/2 ###
53 |
54 |
55 | delete_inline(Doc::json_obj(), AName::string()) -> json_obj()
56 |
57 |
58 |
59 | delete an attachment record in doc. This is different from delete_attachment
60 | change is only applied in Doc object. Save_doc should be save to save changes.
61 |
62 |
--------------------------------------------------------------------------------
/doc/couchbeam_changes.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_changes #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 | ## Function Details ##
19 |
20 |
21 |
22 | ### cancel_stream/1 ###
23 |
24 | `cancel_stream(Ref) -> any()`
25 |
26 |
27 |
28 | ### follow/1 ###
29 |
30 |
31 | follow(Db::db()) -> {ok, StreamRef::atom()} | {error, term()}
32 |
33 |
34 |
35 |
36 |
37 | ### follow/2 ###
38 |
39 |
40 | follow(Db::db(), Options::changes_options()) -> {ok, StreamRef::atom()} | {error, term()}
41 |
42 |
43 |
44 | Stream changes to a pid
45 |
46 | Db : a db record
47 |
48 | Client : pid or callback where to send changes events where events are
49 | The pid receive these events:
50 |
51 |
52 |
53 | {change, StartRef, {done, Lastseq::integer()}
54 |
55 |
56 |
57 |
58 | Connection terminated or you got all changes
59 |
60 |
61 |
62 |
63 | {change, StartRef, Row :: ejson_object()}
64 |
65 |
66 |
67 |
68 | Line of change
69 |
70 |
71 |
72 |
73 | {error, LastSeq::integer(), Msg::term()}
74 |
75 |
76 |
77 |
78 | Got an error, connection is closed when an error
79 | happend.
80 |
81 |
82 |
83 | LastSeq is the last sequence of changes.
84 |
85 | While the callbac could be like:
86 |
87 | ```
88 |
89 | fun({done, LastSeq}) ->
90 | ok;
91 | fun({done, LastSeq}) ->
92 | ok;
93 | fun({done, LastSeq}) ->
94 | ok.
95 | ```
96 |
97 |
98 | ```
99 | >Options :: changes_stream_options() [continuous
100 | | longpoll
101 | | normal
102 | | include_docs
103 | | {since, integer() | now}
104 | | {timeout, integer()}
105 | | heartbeat | {heartbeat, integer()}
106 | | {filter, string()} | {filter, string(), list({string(), string() | integer()})}
107 | | {view, string()},
108 | | {docids, list))},
109 | | {stream_to, pid()},
110 | | {async, once | normal}]
111 | ```
112 |
113 | * `continuous | longpoll | normal`: set the type of changes
114 | feed to get
115 |
116 | * `include_doc`: if you want to include the doc in the line of
117 | change
118 |
119 | * `{timeout, Timeout::integer()}`: timeout
120 |
121 | * `heartbeat | {heartbeat, Heartbeat::integer()}`: set couchdb
122 | to send a heartbeat to maintain connection open
123 |
124 | * `{filter, FilterName} | {filter, FilterName, Args::list({key,
125 | value})}`: set the filter to use with optional arguments
126 |
127 | * `{view, ViewName}`: use a view function as filter. Note
128 | that it requires to set filter special value `"_view"`
129 | to enable this feature.
130 |
131 | * >`{stream_to, Pid}`: the pid where the changes will be sent,
132 | by default the current pid. Used for continuous and longpoll
133 | connections
134 |
135 |
136 | Return {ok, StartRef, ChangesPid} or {error, Error}. Ref can be
137 | used to disctint all changes from this pid. ChangesPid is the pid of
138 | the changes loop process. Can be used to monitor it or kill it
139 | when needed.
140 |
141 |
142 |
143 | ### follow_once/1 ###
144 |
145 |
146 | follow_once(Db::db()) -> {ok, LastSeq::integer(), Changes::list()} | {error, term()}
147 |
148 |
149 |
150 |
151 |
152 | ### follow_once/2 ###
153 |
154 |
155 | follow_once(Db::db(), Options::changes_options()) -> {ok, LastSeq::integer(), Changes::list()} | {error, term()}
156 |
157 |
158 |
159 | fetch all changes at once using a normal or longpoll
160 | connections.
161 |
162 | Db : a db record
163 |
164 | ```
165 | Options :: changes_options() [
166 | | longpoll
167 | | normal
168 | | include_docs
169 | | {since, integer() | now}
170 | | {timeout, integer()}
171 | | heartbeat | {heartbeat, integer()}
172 | | {filter, string()}
173 | | {filter, string(), list({string(), string() | integer()})}
174 | | {docids, list()))},
175 | | {stream_to, pid()}
176 | ]
177 | ```
178 |
179 | * `longpoll | normal`: set the type of changes
180 | feed to get
181 |
182 | * `include_docs`: if you want to include the doc in the line of
183 | change
184 |
185 | * `{timeout, Timeout::integer()}`: timeout
186 |
187 | * `heartbeat | {heartbeat, Heartbeat::integer()}`: set couchdb
188 | to send a heartbeat to maintain connection open
189 |
190 | * `{filter, FilterName} | {filter, FilterName, Args::list({key,
191 | value})`: set the filter to use with optional arguments
192 |
193 | * `{view, ViewName}`: use a view function as filter. Note
194 | that it requires to set filter special value `"_view"`
195 | to enable this feature.
196 |
197 |
198 | Result: `{ok, LastSeq::integer(), Rows::list()}` or
199 | `{error, LastSeq, Error}`. LastSeq is the last sequence of changes.
200 |
201 |
202 |
203 | ### stream_next/1 ###
204 |
205 | `stream_next(Ref) -> any()`
206 |
207 |
--------------------------------------------------------------------------------
/doc/couchbeam_changes_stream.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_changes_stream #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### collect_object/2 ###
22 |
23 | `collect_object(X1, X2) -> any()`
24 |
25 |
26 |
27 | ### handle_event/2 ###
28 |
29 | `handle_event(Event, St) -> any()`
30 |
31 |
32 |
33 | ### init/1 ###
34 |
35 | `init(X1) -> any()`
36 |
37 |
38 |
39 | ### init_stream/5 ###
40 |
41 | `init_stream(Parent, Owner, StreamRef, Db, Options) -> any()`
42 |
43 |
44 |
45 | ### maybe_continue/1 ###
46 |
47 | `maybe_continue(State) -> any()`
48 |
49 |
50 |
51 | ### maybe_continue_decoding/1 ###
52 |
53 | `maybe_continue_decoding(State) -> any()`
54 |
55 |
56 |
57 | ### start_link/4 ###
58 |
59 | `start_link(Owner, StreamRef, Db, Options) -> any()`
60 |
61 |
62 |
63 | ### system_code_change/4 ###
64 |
65 | `system_code_change(Misc, X2, X3, X4) -> any()`
66 |
67 |
68 |
69 | ### system_continue/3 ###
70 |
71 | `system_continue(X1, X2, X3) -> any()`
72 |
73 |
74 |
75 | ### system_terminate/4 ###
76 |
77 |
78 | system_terminate(Reason::any(), X2::term(), X3::term(), State::term()) -> no_return()
79 |
80 |
81 |
82 |
83 |
84 | ### wait_reconnect/1 ###
85 |
86 | `wait_reconnect(State) -> any()`
87 |
88 |
89 |
90 | ### wait_results/2 ###
91 |
92 | `wait_results(X1, St) -> any()`
93 |
94 |
95 |
96 | ### wait_results1/2 ###
97 |
98 | `wait_results1(X1, X2) -> any()`
99 |
100 |
--------------------------------------------------------------------------------
/doc/couchbeam_changes_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_changes_sup #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`supervisor`](supervisor.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### init/1 ###
24 |
25 | `init(X1) -> any()`
26 |
27 |
28 |
29 | ### start_link/0 ###
30 |
31 |
32 | start_link() -> {ok, pid()}
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/doc/couchbeam_deps.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_deps #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 |
10 | ## Function Index ##
11 |
12 |
13 | deps_on_path/0 | List of project dependencies on the path. |
ensure/0 | Ensure that the ebin and include paths for dependencies of
14 | this application are on the code path. |
ensure/1 | Ensure that all ebin and include paths for dependencies
15 | of the application for Module are on the code path. |
get_base_dir/0 | Return the application directory for this application. |
get_base_dir/1 | Return the application directory for Module. |
local_path/1 | Return an application-relative directory for this application. |
local_path/2 | Return an application-relative directory from Module's application. |
new_siblings/1 | Find new siblings paths relative to Module that aren't already on the
16 | code path. |
17 |
18 |
19 |
20 |
21 | ## Function Details ##
22 |
23 |
24 |
25 | ### deps_on_path/0 ###
26 |
27 |
28 |
29 | deps_on_path() -> [ProjNameAndVers]
30 |
31 |
32 |
33 | List of project dependencies on the path.
34 |
35 |
36 | ### ensure/0 ###
37 |
38 |
39 |
40 | ensure() -> ok
41 |
42 |
43 |
44 | Ensure that the ebin and include paths for dependencies of
45 | this application are on the code path. Equivalent to
46 | ensure(?Module).
47 |
48 |
49 | ### ensure/1 ###
50 |
51 |
52 |
53 | ensure(Module) -> ok
54 |
55 |
56 |
57 | Ensure that all ebin and include paths for dependencies
58 | of the application for Module are on the code path.
59 |
60 |
61 | ### get_base_dir/0 ###
62 |
63 |
64 |
65 | get_base_dir() -> string()
66 |
67 |
68 |
69 | Return the application directory for this application. Equivalent to
70 | get_base_dir(?MODULE).
71 |
72 |
73 | ### get_base_dir/1 ###
74 |
75 |
76 |
77 | get_base_dir(Module) -> string()
78 |
79 |
80 |
81 | Return the application directory for Module. It assumes Module is in
82 | a standard OTP layout application in the ebin or src directory.
83 |
84 |
85 | ### local_path/1 ###
86 |
87 |
88 |
89 | local_path(Components) -> string()
90 |
91 |
92 |
93 | Return an application-relative directory for this application.
94 | Equivalent to local_path(Components, ?MODULE).
95 |
96 |
97 | ### local_path/2 ###
98 |
99 |
100 |
101 | local_path(Components::[string()], Module) -> string()
102 |
103 |
104 |
105 | Return an application-relative directory from Module's application.
106 |
107 |
108 | ### new_siblings/1 ###
109 |
110 |
111 |
112 | new_siblings(Module) -> [Dir]
113 |
114 |
115 |
116 | Find new siblings paths relative to Module that aren't already on the
117 | code path.
118 |
--------------------------------------------------------------------------------
/doc/couchbeam_doc.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_doc #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### key_val() ###
16 |
17 |
18 |
19 | key_val() = lis() | binary()
20 |
21 |
22 |
23 |
24 |
25 | ### property() ###
26 |
27 |
28 |
29 | property() = json_obj() | tuple()
30 |
31 |
32 |
33 |
34 | ## Function Index ##
35 |
36 |
37 | delete_value/2 | Deletes all entries associated with Key in json object. |
extend/2 | extend a jsonobject by a property, list of property or another jsonobject. |
extend/3 | extend a jsonobject by key, value. |
get_id/1 | get document id. |
get_idrev/1 | get a tuple containing docucment id and revision. |
get_rev/1 | get document revision. |
get_value/2 | Returns the value of a simple key/value property in json object
38 | Equivalent to get_value(Key, JsonObj, undefined). |
get_value/3 | Returns the value of a simple key/value property in json object
39 | function from erlang_couchdb. |
is_saved/1 | If document have been saved (revision is defined) return true,
40 | else, return false. |
set_value/3 | set a value for a key in jsonobj. |
take_value/2 | Returns the value of a simple key/value property in json object and deletes
41 | it form json object
42 | Equivalent to take_value(Key, JsonObj, undefined). |
take_value/3 | Returns the value of a simple key/value property in json object and deletes
43 | it from json object. |
44 |
45 |
46 |
47 |
48 | ## Function Details ##
49 |
50 |
51 |
52 | ### delete_value/2 ###
53 |
54 |
55 | delete_value(Key::key_val(), JsonObj::json_obj()) -> json_obj()
56 |
57 |
58 |
59 | Deletes all entries associated with Key in json object.
60 |
61 |
62 |
63 | ### extend/2 ###
64 |
65 |
66 | extend(Prop::property(), JsonObj::json_obj()) -> json_obj()
67 |
68 |
69 |
70 | extend a jsonobject by a property, list of property or another jsonobject
71 |
72 |
73 |
74 | ### extend/3 ###
75 |
76 |
77 | extend(Key::binary(), Value::json_term(), JsonObj::json_obj()) -> json_obj()
78 |
79 |
80 |
81 | extend a jsonobject by key, value
82 |
83 |
84 |
85 | ### get_id/1 ###
86 |
87 |
88 | get_id(Doc::json_obj()) -> binary()
89 |
90 |
91 |
92 | get document id.
93 |
94 |
95 |
96 | ### get_idrev/1 ###
97 |
98 |
99 | get_idrev(Doc::json_obj()) -> {DocId, DocRev}
100 |
101 |
102 |
103 | get a tuple containing docucment id and revision.
104 |
105 |
106 |
107 | ### get_rev/1 ###
108 |
109 |
110 | get_rev(Doc::json_obj()) -> binary()
111 |
112 |
113 |
114 | get document revision.
115 |
116 |
117 |
118 | ### get_value/2 ###
119 |
120 |
121 | get_value(Key::key_val(), JsonObj::json_obj()) -> term()
122 |
123 |
124 |
125 | Returns the value of a simple key/value property in json object
126 | Equivalent to get_value(Key, JsonObj, undefined).
127 |
128 |
129 |
130 | ### get_value/3 ###
131 |
132 |
133 | get_value(Key::lis() | binary(), JsonObj::json_obj(), Default::term()) -> term()
134 |
135 |
136 |
137 | Returns the value of a simple key/value property in json object
138 | function from erlang_couchdb
139 |
140 |
141 |
142 | ### is_saved/1 ###
143 |
144 |
145 | is_saved(Doc::json_obj()) -> boolean()
146 |
147 |
148 |
149 | If document have been saved (revision is defined) return true,
150 | else, return false.
151 |
152 |
153 |
154 | ### set_value/3 ###
155 |
156 |
157 | set_value(Key::key_val(), Value::term(), JsonObj::json_obj()) -> term()
158 |
159 |
160 |
161 | set a value for a key in jsonobj. If key exists it will be updated.
162 |
163 |
164 |
165 | ### take_value/2 ###
166 |
167 |
168 | take_value(Key::key_val(), JsonObj::json_obj()) -> {term(), json_obj()}
169 |
170 |
171 |
172 | Returns the value of a simple key/value property in json object and deletes
173 | it form json object
174 | Equivalent to take_value(Key, JsonObj, undefined).
175 |
176 |
177 |
178 | ### take_value/3 ###
179 |
180 |
181 | take_value(Key::key_val() | binary(), JsonObj::json_obj(), Default::term()) -> {term(), json_obj()}
182 |
183 |
184 |
185 | Returns the value of a simple key/value property in json object and deletes
186 | it from json object
187 |
188 |
--------------------------------------------------------------------------------
/doc/couchbeam_ejson.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_ejson #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### decode/1 ###
22 |
23 |
24 | decode(D::binary()) -> ejson()
25 |
26 |
27 |
28 | decode a binary to an EJSON term. Throw an exception if there is
29 | any error.
30 |
31 |
32 |
33 | ### encode/1 ###
34 |
35 |
36 | encode(D::ejson()) -> binary()
37 |
38 |
39 |
40 | encode an erlang term to JSON. Throw an exception if there is
41 | any error.
42 |
43 |
44 |
45 | ### post_decode/1 ###
46 |
47 | `post_decode(Rest) -> any()`
48 |
49 |
--------------------------------------------------------------------------------
/doc/couchbeam_httpc.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_httpc #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### db_request/5 ###
22 |
23 | `db_request(Method, Url, Headers, Body, Options) -> any()`
24 |
25 |
26 |
27 | ### db_request/6 ###
28 |
29 | `db_request(Method, Url, Headers, Body, Options, Expect) -> any()`
30 |
31 |
32 |
33 | ### db_resp/2 ###
34 |
35 | `db_resp(Resp, Expect) -> any()`
36 |
37 |
38 |
39 | ### db_url/1 ###
40 |
41 | `db_url(Db) -> any()`
42 |
43 |
44 |
45 | ### doc_url/2 ###
46 |
47 | `doc_url(Db, DocId) -> any()`
48 |
49 |
50 |
51 | ### json_body/1 ###
52 |
53 | `json_body(Ref) -> any()`
54 |
55 |
56 |
57 | ### make_headers/4 ###
58 |
59 | `make_headers(Method, Url, Headers, Options) -> any()`
60 |
61 |
62 |
63 | ### maybe_oauth_header/4 ###
64 |
65 | `maybe_oauth_header(Method, Url, Headers, Options) -> any()`
66 |
67 |
68 |
69 | ### request/5 ###
70 |
71 | `request(Method, Url, Headers, Body, Options) -> any()`
72 |
73 |
74 |
75 | ### server_url/1 ###
76 |
77 |
78 | server_url(Server::{Host, Port}) -> iolist()
79 |
80 |
81 |
82 | Asemble the server URL for the given client
83 |
84 |
--------------------------------------------------------------------------------
/doc/couchbeam_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_sup #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`supervisor`](supervisor.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### init/1 ###
24 |
25 | `init(X1) -> any()`
26 |
27 |
28 |
29 | ### start_link/0 ###
30 |
31 | `start_link() -> any()`
32 |
33 |
--------------------------------------------------------------------------------
/doc/couchbeam_util.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_util #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### binary_env/2 ###
22 |
23 | `binary_env(Key, Default) -> any()`
24 |
25 |
26 |
27 | ### dbname/1 ###
28 |
29 | `dbname(DbName) -> any()`
30 |
31 |
32 |
33 | ### deprecated/3 ###
34 |
35 | `deprecated(Old, New, When) -> any()`
36 |
37 |
38 |
39 | ### encode_att_name/1 ###
40 |
41 | `encode_att_name(Name) -> any()`
42 |
43 |
44 |
45 | ### encode_docid/1 ###
46 |
47 | `encode_docid(DocId) -> any()`
48 |
49 |
50 |
51 | ### encode_docid1/1 ###
52 |
53 | `encode_docid1(DocId) -> any()`
54 |
55 |
56 |
57 | ### encode_docid_noop/1 ###
58 |
59 | `encode_docid_noop(DocId) -> any()`
60 |
61 |
62 |
63 | ### encode_query/1 ###
64 |
65 | `encode_query(QSL) -> any()`
66 |
67 | Encode needed value of Query proplists in json
68 |
69 |
70 |
71 | ### encode_query_value/2 ###
72 |
73 | `encode_query_value(K, V) -> any()`
74 |
75 | Encode value in JSON if needed depending on the key
76 |
77 |
78 |
79 | ### force_param/3 ###
80 |
81 | `force_param(Key, Value, Options) -> any()`
82 |
83 | replace a value in a proplist
84 |
85 |
86 |
87 | ### get_app_env/2 ###
88 |
89 | `get_app_env(Env, Default) -> any()`
90 |
91 |
92 |
93 | ### get_value/2 ###
94 |
95 |
96 | get_value(Key::term(), Prop::[term()]) -> term()
97 |
98 |
99 |
100 | emulate proplists:get_value/2,3 but use faster lists:keyfind/3
101 |
102 |
103 |
104 | ### get_value/3 ###
105 |
106 |
107 | get_value(Key::term(), Prop::[term()], Default::term()) -> term()
108 |
109 |
110 |
111 |
112 |
113 | ### oauth_header/3 ###
114 |
115 | `oauth_header(Url, Action, OauthProps) -> any()`
116 |
117 |
118 |
119 | ### parse_options/1 ###
120 |
121 | `parse_options(Options) -> any()`
122 |
123 | make view options a list
124 |
125 |
126 |
127 | ### parse_options/2 ###
128 |
129 | `parse_options(Rest, Acc) -> any()`
130 |
131 |
132 |
133 | ### propmerge/3 ###
134 |
135 | `propmerge(F, L1, L2) -> any()`
136 |
137 | merge 2 proplists. All the Key - Value pairs from both proplists
138 | are included in the new proplists. If a key occurs in both dictionaries
139 | then Fun is called with the key and both values to return a new
140 | value. This a wreapper around dict:merge
141 |
142 |
143 |
144 | ### propmerge1/2 ###
145 |
146 | `propmerge1(L1, L2) -> any()`
147 |
148 | Update a proplist with values of the second. In case the same
149 | key is in 2 proplists, the value from the first are kept.
150 |
151 |
152 |
153 | ### proxy_header/3 ###
154 |
155 | `proxy_header(UserName, Roles, Secret) -> any()`
156 |
157 |
158 |
159 | ### proxy_token/2 ###
160 |
161 | `proxy_token(Secret, UserName) -> any()`
162 |
163 |
164 |
165 | ### shutdown_sync/1 ###
166 |
167 | `shutdown_sync(Pid) -> any()`
168 |
169 |
170 |
171 | ### start_app_deps/1 ###
172 |
173 |
174 | start_app_deps(App::atom()) -> ok
175 |
176 |
177 |
178 | Start depedent applications of App.
179 |
180 |
181 |
182 | ### to_atom/1 ###
183 |
184 | `to_atom(V) -> any()`
185 |
186 |
187 |
188 | ### to_binary/1 ###
189 |
190 | `to_binary(V) -> any()`
191 |
192 |
193 |
194 | ### to_integer/1 ###
195 |
196 | `to_integer(V) -> any()`
197 |
198 |
199 |
200 | ### to_list/1 ###
201 |
202 | `to_list(V) -> any()`
203 |
204 |
--------------------------------------------------------------------------------
/doc/couchbeam_uuids.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_uuids #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`gen_server`](gen_server.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### code_change/3 ###
24 |
25 | `code_change(OldVsn, State, Extra) -> any()`
26 |
27 |
28 |
29 | ### get_uuids/2 ###
30 |
31 |
32 | get_uuids(Server::server(), Count::integer()) -> lists()
33 |
34 |
35 |
36 | Get a list of uuids from the server
37 |
38 |
39 |
40 | ### handle_call/3 ###
41 |
42 | `handle_call(X1, From, State) -> any()`
43 |
44 |
45 |
46 | ### handle_cast/2 ###
47 |
48 | `handle_cast(Msg, State) -> any()`
49 |
50 |
51 |
52 | ### handle_info/2 ###
53 |
54 | `handle_info(Info, State) -> any()`
55 |
56 |
57 |
58 | ### random/0 ###
59 |
60 |
61 | random() -> binary()
62 |
63 |
64 |
65 | return a random uuid
66 |
67 |
68 |
69 | ### start_link/0 ###
70 |
71 |
72 | start_link() -> {ok, pid()}
73 |
74 |
75 |
76 | Starts the couchbeam process linked to the calling process. Usually
77 | invoked by the supervisor couchbeam_sup
78 |
79 |
80 |
81 | ### terminate/2 ###
82 |
83 | `terminate(Reason, State) -> any()`
84 |
85 |
86 |
87 | ### utc_random/0 ###
88 |
89 |
90 | utc_random() -> binary()
91 |
92 |
93 |
94 | return a random uuid based on time
95 |
96 |
--------------------------------------------------------------------------------
/doc/couchbeam_view.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_view #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 | all/1 | fetch all docs. |
all/2 | fetch all docs. |
cancel_stream/1 | |
count/1 | Equivalent to count(Db, all_docs, []). |
count/2 | Equivalent to count(Db, ViewName, []). |
count/3 | count number of doc in a view (or all docs). |
fetch/1 | Equivalent to fetch(Db, all_docs, []). |
fetch/2 | Equivalent to fetch(Db, ViewName, []). |
fetch/3 | Collect view results. |
first/1 | Equivalent to first(Db, all_docs, []). |
first/2 | Equivalent to first(Db, ViewName, []). |
first/3 | get first result of a view. |
fold/4 | Equivalent to fold(Function, Acc, Db, ViewName, []). |
fold/5 | call Function(Row, AccIn) on succesive row, starting with
13 | AccIn == Acc. |
foreach/3 | Equivalent to foreach(Function, Db, ViewName, []). |
foreach/4 | call Function(Row) on succesive row. |
parse_view_options/1 | parse view options. |
stream/2 | Equivalent to stream(Db, ViewName, Client, []). |
stream/3 | stream view results to a pid. |
stream_next/1 | |
14 |
15 |
16 |
17 |
18 | ## Function Details ##
19 |
20 |
21 |
22 | ### all/1 ###
23 |
24 |
25 | all(Db::db()) -> {ok, Rows::[ejson_object()]} | {error, term()}
26 |
27 |
28 |
29 | Equivalent to [`fetch(Db, all_docs, [])`](#fetch-3).
30 |
31 | fetch all docs
32 |
33 |
34 |
35 | ### all/2 ###
36 |
37 |
38 | all(Db::db(), Options::view_options()) -> {ok, Rows::[ejson_object()]} | {error, term()}
39 |
40 |
41 |
42 | Equivalent to [`fetch(Db, all_docs, Options)`](#fetch-3).
43 |
44 | fetch all docs
45 |
46 |
47 |
48 | ### cancel_stream/1 ###
49 |
50 | `cancel_stream(Ref) -> any()`
51 |
52 |
53 |
54 | ### count/1 ###
55 |
56 |
57 | count(Db::db()) -> integer() | {error, term()}
58 |
59 |
60 |
61 | Equivalent to [`count(Db, all_docs, [])`](#count-3).
62 |
63 |
64 |
65 | ### count/2 ###
66 |
67 |
68 | count(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> integer() | {error, term()}
69 |
70 |
71 |
72 | Equivalent to [`count(Db, ViewName, [])`](#count-3).
73 |
74 |
75 |
76 | ### count/3 ###
77 |
78 |
79 | count(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> integer() | {error, term()}
80 |
81 |
82 |
83 | count number of doc in a view (or all docs)
84 |
85 |
86 |
87 | ### fetch/1 ###
88 |
89 |
90 | fetch(Db::db()) -> {ok, Rows::[ejson_object()]} | {error, term()}
91 |
92 |
93 |
94 | Equivalent to [`fetch(Db, all_docs, [])`](#fetch-3).
95 |
96 |
97 |
98 | ### fetch/2 ###
99 |
100 |
101 | fetch(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> {ok, Rows::[ejson_object()]} | {error, term()}
102 |
103 |
104 |
105 | Equivalent to [`fetch(Db, ViewName, [])`](#fetch-3).
106 |
107 |
108 |
109 | ### fetch/3 ###
110 |
111 |
112 | fetch(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> {ok, Rows::[ejson_object()]} | {error, term()}
113 |
114 |
115 |
116 | Collect view results
117 |
118 | Db: a db record
119 |
120 | ViewName: `'all_docs'` to get all docs or `{DesignName,
121 | ViewName}`
122 |
123 |
124 | ```
125 | Options :: view_options() [{key, binary()}
126 | | {start_docid, binary()} | {startkey_docid, binary()}
127 | | {end_docid, binary()} | {endkey_docid, binary()}
128 | | {start_key, binary()} | {end_key, binary()}
129 | | {limit, integer()}
130 | | {stale, stale()}
131 | | descending
132 | | {skip, integer()}
133 | | group | {group_level, integer()}
134 | | {inclusive_end, boolean()} | {reduce, boolean()} | reduce | include_docs | conflicts
135 | | {keys, list(binary())}
136 | ```
137 |
138 |
139 | See [`couchbeam_view:stream/4`](couchbeam_view.md#stream-4) for more information about
140 | options.
141 |
142 | Return: {ok, Rows} or {error, Error}
143 |
144 |
145 |
146 | ### first/1 ###
147 |
148 |
149 | first(Db::db()) -> {ok, Row::ejson_object()} | {error, term()}
150 |
151 |
152 |
153 | Equivalent to [`first(Db, all_docs, [])`](#first-3).
154 |
155 |
156 |
157 | ### first/2 ###
158 |
159 |
160 | first(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> {ok, Row::ejson_object()} | {error, term()}
161 |
162 |
163 |
164 | Equivalent to [`first(Db, ViewName, [])`](#first-3).
165 |
166 |
167 |
168 | ### first/3 ###
169 |
170 |
171 | first(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> {ok, Rows::ejson_object()} | {error, term()}
172 |
173 |
174 |
175 | get first result of a view
176 |
177 | Db: a db record
178 |
179 | ViewName: 'all_docs' to get all docs or {DesignName,
180 | ViewName}
181 |
182 |
183 | ```
184 | Options :: view_options() [{key, binary()}
185 | | {start_docid, binary()} | {startkey_docid, binary()}
186 | | {end_docid, binary()} | {endkey_docid, binary()}
187 | | {start_key, binary()} | {end_key, binary()}
188 | | {limit, integer()}
189 | | {stale, stale()}
190 | | descending
191 | | {skip, integer()}
192 | | group | {group_level, integer()}
193 | | {inclusive_end, boolean()} | {reduce, boolean()} | reduce | include_docs | conflicts
194 | | {keys, list(binary())}
195 | ```
196 |
197 |
198 | See [`couchbeam_view:stream/4`](couchbeam_view.md#stream-4) for more information about
199 | options.
200 |
201 | Return: {ok, Row} or {error, Error}
202 |
203 |
204 |
205 | ### fold/4 ###
206 |
207 |
208 | fold(Function::function(), Acc::any(), Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> [term()] | {error, term()}
209 |
210 |
211 |
212 | Equivalent to [`fold(Function, Acc, Db, ViewName, [])`](#fold-5).
213 |
214 |
215 |
216 | ### fold/5 ###
217 |
218 |
219 | fold(Function::function(), Acc::any(), Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> [term()] | {error, term()}
220 |
221 |
222 |
223 | call Function(Row, AccIn) on succesive row, starting with
224 | AccIn == Acc. Function/2 must return a new list accumultator or the
225 | atom _done_ to stop fetching results. Acc0 is returned if the
226 | list is empty. For example:
227 |
228 | ```
229 | couchbeam_view:fold(fun(Row, Acc) -> [Row|Acc] end, [], Db, 'all_docs').
230 | ```
231 |
232 |
233 |
234 | ### foreach/3 ###
235 |
236 |
237 | foreach(Function::function(), Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> [term()] | {error, term()}
238 |
239 |
240 |
241 | Equivalent to [`foreach(Function, Db, ViewName, [])`](#foreach-4).
242 |
243 |
244 |
245 | ### foreach/4 ###
246 |
247 |
248 | foreach(Function::function(), Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> [term()] | {error, term()}
249 |
250 |
251 |
252 | call Function(Row) on succesive row. Example:
253 |
254 | ```
255 | couchbeam_view:foreach(fun(Row) -> io:format("got row ~p~n", [Row]) end, Db, 'all_docs').
256 | ```
257 |
258 |
259 |
260 | ### parse_view_options/1 ###
261 |
262 |
263 | parse_view_options(Options::list()) -> view_query_args()
264 |
265 |
266 |
267 | parse view options
268 |
269 |
270 |
271 | ### stream/2 ###
272 |
273 |
274 | stream(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}) -> {ok, StartRef::term(), ViewPid::pid()} | {error, term()}
275 |
276 |
277 |
278 | Equivalent to [`stream(Db, ViewName, Client, [])`](#stream-4).
279 |
280 |
281 |
282 | ### stream/3 ###
283 |
284 |
285 | stream(Db::db(), ViewName::all_docs | {DesignName::design_name(), ViewName::view_name()}, Options::view_options()) -> {ok, StartRef::term()} | {error, term()}
286 |
287 |
288 |
289 | stream view results to a pid
290 |
291 | Db: a db record
292 |
293 | ViewName: 'all_docs' to get all docs or {DesignName,
294 | ViewName}
295 |
296 | Client: pid where to send view events where events are:
297 |
298 |
299 |
300 | {row, StartRef, done}
301 |
302 |
303 |
304 |
305 | All view results have been fetched
306 |
307 |
308 |
309 |
310 | {row, StartRef, Row :: ejson_object()}
311 |
312 |
313 |
314 |
315 | A row in the view
316 |
317 |
318 |
319 |
320 | {error, StartRef, Error}
321 |
322 |
323 |
324 |
325 | Got an error, connection is closed when an error
326 | happend.
327 |
328 |
329 |
330 | ```
331 | Options :: view_options() [{key, binary()}
332 | | {start_docid, binary()} | {startkey_docid, binary()}
333 | | {end_docid, binary()} | {endkey_docid, binary()}
334 | | {start_key, binary()} | {end_key, binary()}
335 | | {limit, integer()}
336 | | {stale, stale()}
337 | | descending
338 | | {skip, integer()}
339 | | group | {group_level, integer()}
340 | | {inclusive_end, boolean()} | {reduce, boolean()} | reduce | include_docs | conflicts
341 | | {keys, list(binary())}
342 | | {stream_to, Pid}
: the pid where the changes will be sent,
343 | by default the current pid. Used for continuous and longpoll
344 | connections
345 | ```
346 |
347 | * `{key, Key}`: key value
348 |
349 | * `{start_docid, DocId}` | `{startkey_docid, DocId}`: document id to start with (to allow pagination
350 | for duplicate start keys
351 |
352 | * `{end_docid, DocId}` | `{endkey_docid, DocId}`: last document id to include in the result (to
353 | allow pagination for duplicate endkeys)
354 |
355 | * `{start_key, Key}`: start result from key value
356 |
357 | * `{end_key, Key}`: end result from key value
358 |
359 | * `{limit, Limit}`: Limit the number of documents in the result
360 |
361 | * `{stale, Stale}`: If stale=ok is set, CouchDB will not refresh the view
362 | even if it is stale, the benefit is a an improved query latency. If
363 | stale=update_after is set, CouchDB will update the view after the stale
364 | result is returned. If stale=false is set, CouchDB will update the view before
365 | the query. The default value of this parameter is update_after.
366 |
367 | * `descending`: reverse the result
368 |
369 | * `{skip, N}`: skip n number of documents
370 |
371 | * `group`: the reduce function reduces to a single result
372 | row.
373 |
374 | * `{group_level, Level}`: the reduce function reduces to a set
375 | of distinct keys.
376 |
377 | * `{reduce, boolean()}`: whether to use the reduce function of the view. It defaults to
378 | true, if a reduce function is defined and to false otherwise.
379 |
380 | * `include_docs`: automatically fetch and include the document
381 | which emitted each view entry
382 |
383 | * `{inclusive_end, boolean()}`: Controls whether the endkey is included in
384 | the result. It defaults to true.
385 |
386 | * `conflicts`: include conflicts
387 |
388 | * `{keys, [Keys]}`: to pass multiple keys to the view query
389 |
390 |
391 | Return `{ok, StartRef, ViewPid}` or `{error,
392 | Error}`. Ref can be
393 | used to disctint all changes from this pid. ViewPid is the pid of
394 | the view loop process. Can be used to monitor it or kill it
395 | when needed.
396 |
397 |
398 |
399 | ### stream_next/1 ###
400 |
401 | `stream_next(Ref) -> any()`
402 |
403 |
--------------------------------------------------------------------------------
/doc/couchbeam_view_stream.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_view_stream #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 | ## Function Index ##
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Function Details ##
18 |
19 |
20 |
21 | ### collect_object/2 ###
22 |
23 | `collect_object(X1, X2) -> any()`
24 |
25 |
26 |
27 | ### handle_event/2 ###
28 |
29 | `handle_event(Event, St) -> any()`
30 |
31 |
32 |
33 | ### init/1 ###
34 |
35 | `init(X1) -> any()`
36 |
37 |
38 |
39 | ### init_stream/5 ###
40 |
41 | `init_stream(Parent, Owner, StreamRef, Req, StreamOptions) -> any()`
42 |
43 |
44 |
45 | ### maybe_continue/1 ###
46 |
47 | `maybe_continue(State) -> any()`
48 |
49 |
50 |
51 | ### maybe_continue_decoding/1 ###
52 |
53 | `maybe_continue_decoding(Viewst) -> any()`
54 |
55 |
56 |
57 | ### start_link/4 ###
58 |
59 | `start_link(Owner, StreamRef, X3, StreamOptions) -> any()`
60 |
61 |
62 |
63 | ### system_code_change/4 ###
64 |
65 | `system_code_change(Misc, X2, X3, X4) -> any()`
66 |
67 |
68 |
69 | ### system_continue/3 ###
70 |
71 | `system_continue(X1, X2, X3) -> any()`
72 |
73 |
74 |
75 | ### system_terminate/4 ###
76 |
77 |
78 | system_terminate(Reason::any(), X2::term(), X3::term(), State::term()) -> no_return()
79 |
80 |
81 |
82 |
83 |
84 | ### wait_rows/2 ###
85 |
86 | `wait_rows(X1, St) -> any()`
87 |
88 |
89 |
90 | ### wait_rows1/2 ###
91 |
92 | `wait_rows1(X1, X2) -> any()`
93 |
94 |
95 |
96 | ### wait_val/2 ###
97 |
98 | `wait_val(X1, X2) -> any()`
99 |
100 |
--------------------------------------------------------------------------------
/doc/couchbeam_view_sup.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module couchbeam_view_sup #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 | __Behaviours:__ [`supervisor`](supervisor.md).
8 |
9 |
10 |
11 | ## Function Index ##
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Function Details ##
20 |
21 |
22 |
23 | ### init/1 ###
24 |
25 | `init(X1) -> any()`
26 |
27 |
28 |
29 | ### start_link/0 ###
30 |
31 |
32 | start_link() -> {ok, pid()}
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/doc/gen_changes.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module gen_changes #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 | gen_changes CouchDB continuous changes consumer behavior
9 | This behaviour allows you to create easily a server that consume
10 | Couchdb continuous changes.
11 |
12 | __This module defines the `gen_changes` behaviour.__
Required callback functions: `init/1`, `handle_change/2`, `handle_call/3`, `handle_cast/2`, `handle_info/2`, `terminate/2`.
13 |
14 |
15 |
16 | ## Function Index ##
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## Function Details ##
25 |
26 |
27 |
28 | ### behaviour_info/1 ###
29 |
30 | `behaviour_info(X1) -> any()`
31 |
32 |
33 |
34 | ### call/2 ###
35 |
36 | `call(Name, Request) -> any()`
37 |
38 |
39 |
40 | ### call/3 ###
41 |
42 | `call(Name, Request, Timeout) -> any()`
43 |
44 |
45 |
46 | ### cast/2 ###
47 |
48 | `cast(Dest, Request) -> any()`
49 |
50 |
51 |
52 | ### code_change/3 ###
53 |
54 | `code_change(OldVersion, State, Extra) -> any()`
55 |
56 |
57 |
58 | ### get_seq/1 ###
59 |
60 | `get_seq(Pid) -> any()`
61 |
62 |
63 |
64 | ### handle_call/3 ###
65 |
66 | `handle_call(Request, From, State) -> any()`
67 |
68 |
69 |
70 | ### handle_cast/2 ###
71 |
72 | `handle_cast(Msg, State) -> any()`
73 |
74 |
75 |
76 | ### handle_info/2 ###
77 |
78 | `handle_info(Info, State) -> any()`
79 |
80 |
81 |
82 | ### init/1 ###
83 |
84 | `init(X1) -> any()`
85 |
86 |
87 |
88 | ### start_link/4 ###
89 |
90 |
91 | start_link(Module, Db::db(), Options::changesoptions(), InitArgs::list()) -> term()
92 |
93 |
94 |
95 |
96 | create a gen_changes process as part of a supervision tree.
97 | The function should be called, directly or indirectly, by the supervisor.
98 |
99 |
100 |
101 | ### stop/1 ###
102 |
103 | `stop(Pid) -> any()`
104 |
105 |
106 |
107 | ### terminate/2 ###
108 |
109 | `terminate(Reason, Gen_changes_state) -> any()`
110 |
111 |
--------------------------------------------------------------------------------
/doc/overview.edoc:
--------------------------------------------------------------------------------
1 | %% -*- erlang -*-
2 | %%
3 | %% This file is part of couchbeam released under the MIT license.
4 | %% See the NOTICE for more information.
5 |
6 |
7 |
8 | @copyright 2009-2025 Benoît Chesneau.
9 | @version 1.5.4
10 | @title Couchbeam - simple Apache CouchDB client library for Erlang applications
11 |
12 | @doc
13 |
14 | # couchbeam
15 |
16 | Couchbeam is a simple erlang library for [Barrel](https://barrel-db.org) or [Apache CouchDB](http://couchdb.apache.org). Couchbeam provides you a full featured and easy client to access and manage multiple nodes.
17 |
18 | #### Main features:
19 |
20 | - Complete support of the BarrelDB and Apache CouchDB API
21 | - Stream view results to your app
22 | - Stream changes feeds
23 | - reduced memory usage
24 | - fetch and send attachments in a streaming fashion
25 | - by default use the JSX module to encode/decode JSON
26 | - support [Jiffy](http://github.com/davisp/jiffy) a JSON encoder/decoder
27 | in C.
28 |
29 |
30 | #### Useful modules are:
31 |
32 | - {@link couchbeam}: The `couchbeam' module is the main interface for interaction with this application. It includes functions for managing connections to Apache CouchDB or RCOUCH servers and databases and for performing document creations, updates, deletes, views...
33 | - {@link couchbeam_doc} Module to manipulate Documents structures. You can set values,
34 | updates keys, ...
35 | - {@link couchbeam_attachments}: Module to manipulate attachments. You can add, remove
36 | attachments in a Document structure (inline attachments).
37 | - {@link couchbeam_view}: Module to manage view results.
38 | - {@link couchbeam_changes}: Module to manage changes feeds. Follow continuously
39 | the changes in a db or get all changes at once.
40 |
41 |
42 | The goal of Couchbeam is to ease the access to the Apache CouchDB and RCOUCH HTTP API in erlang.
43 |
44 | Read the [NEWS](https://raw.github.com/benoitc/couchbeam/master/NEWS) file
45 | to get last changelog.
46 |
47 | ## Installation
48 |
49 | Download the sources from our [Github repository](http://github.com/benoitc/couchbeam)
50 |
51 | To build the application simply run 'make'. This should build .beam, .app
52 | files and documentation.
53 |
54 | To run tests run 'make test'.
55 | To generate doc, run 'make doc'.
56 |
57 |
58 | Or add it to your rebar config
59 |
60 | ```erlang
61 | {deps, [
62 | ....
63 | {couchbeam, ".*", {git, "git://github.com/benoitc/couchbeam.git", {branch, "master"}}}
64 | ]}.
65 | '''
66 |
67 | Note to compile with jiffy you need to define in the erlang options the
68 | variable `WITH_JIFFY'.
69 |
70 | if you use rebar, add to your `rebar.config':
71 |
72 | ```erlang
73 | {erl_opts, [{d, 'WITH_JIFFY'}]}.
74 | '''
75 |
76 | or use the `rebar' command with the `-D' options:
77 |
78 | ```sh
79 | rebar compile -DWITH_JIFFY
80 | '''
81 |
82 | ## Basic Usage
83 |
84 | ### Start couchbeam
85 |
86 | Couchbeam is an [OTP](http://www.erlang.org/doc/design_principles/users_guide.html)
87 | application. You have to start it first before using any of the
88 | functions. The couchbeam application will start the default socket pool
89 | for you.
90 |
91 | To start in the console run:
92 |
93 |
94 | ```sh
95 | $ erl -pa ebin
96 | 1> couchbeam:start().
97 | ok
98 | '''
99 |
100 |
101 | It will start hackney and all of the application it depends on:
102 |
103 | ```erlang
104 | application:start(crypto),
105 | application:start(asn1),
106 | application:start(public_key),
107 | application:start(ssl),
108 | application:start(hackney),
109 | application:start(couchbeam).
110 | '''
111 |
112 | Or add couchbeam to the applications property of your .app in a release
113 |
114 | ### Create a connection to the server
115 |
116 | To create a connection to a server machine:
117 |
118 | ```erlang
119 | Url = "http://localhost:5984",
120 | Options = [],
121 | S = couchbeam:server_connection(Url, Options).
122 | '''
123 |
124 | Test the connection with `couchbeam:server_info/1' :
125 |
126 | ```erlang
127 | {ok, _Version} = couchbeam:server_info(S).
128 | '''
129 |
130 | ### Open or Create a database
131 |
132 | All document operations are done in databases. To open a database simply do:
133 |
134 | ```erlang
135 | Options = [],
136 | {ok, Db} = couchbeam:open_db(Server, "testdb", Options).
137 | '''
138 |
139 | To create a new one:
140 |
141 | ```erlang
142 | Options = [],
143 | {ok, Db} = couchbeam:create_db(Server, "testdb", Options).
144 | '''
145 |
146 | You can also use the shorcut `couchbeam:open_or_create_db/3'. that
147 | will create a database if it does not exist.
148 |
149 | ### Make a new document
150 |
151 | Make a new document:
152 |
153 | ```erlang
154 | Doc = {[
155 | {<<"_id">>, <<"test">>},
156 | {<<"content">>, <<"some text">>}
157 | ]}.
158 | '''
159 |
160 | And save it to the database:
161 |
162 | ```erlang
163 | {ok, Doc1} = couchbeam:save_doc(Db, Doc).
164 | '''
165 |
166 | The `couchbeam:save_doc/2' return a new document with updated
167 | revision and if you do not specify the _id, a unique document id.
168 |
169 | To change an document property use functions from `couchbeam_doc'.
170 |
171 | ### Retrieve a document
172 |
173 | To retrieve a document do:
174 |
175 | ```erlang
176 | {ok, Doc2} = couchbeam:open_doc(Db, "test").
177 | '''
178 |
179 | If you want a specific revision:
180 |
181 | ```erlang
182 | Rev = couchbeam_doc:get_rev(Doc1),
183 | Options = [{rev, Rev}],
184 | {ok, Doc3} = couchbeam:open_doc(Db, "test", Options).
185 | '''
186 |
187 | Here we get the revision from the document we previously stored. Any
188 | options from the Apache CouchDB and RCOUCH API can be used.
189 |
190 | ### Get all documents
191 |
192 | To get all documents you have first to create an object
193 | that will keep all informations.
194 |
195 | ```erlang
196 | Options = [include_docs],
197 | {ok, AllDocs} = couchbeam_view:all(Db, Options).
198 | '''
199 |
200 |
201 | Ex of results:
202 |
203 | ```erlang
204 | {ok,[{[{<<"id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
205 | {<<"key">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
206 | {<<"value">>,
207 | {[{<<"rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18bdb">>}]}},
208 | {<<"doc">>,
209 | {[{<<"_id">>,<<"7a0ce91d0d0c5e5b51e904d1ee3266a3">>},
210 | {<<"_rev">>,<<"15-15c0b3c4efa74f9a80d28ac040f18"...>>}]}}]},
211 | ]}.
212 | '''
213 |
214 | All functions to manipulate these results are in the `couchbeam_view' module.
215 |
216 | ### Couch DB views
217 |
218 | Views are workin like all_docs. You have to create a View object before
219 | doing anything.
220 |
221 | ```erlang
222 | Options = [],
223 | DesignName = "designname",
224 | ViewName = "viewname",
225 | {ok, ViewResults} = couchbeam_view:fetch(Db, {DesignName, ViewName}, Options).
226 | '''
227 |
228 | Like the `all_docs' function, use the functions
229 | from `couchbeam_view' module to manipulate results. You can pass
230 | any querying options from the [view API](http://docs.rcouch.org/en/latest/api/ddoc/views.html).
231 |
232 | Design doc are created like any documents:
233 |
234 | ```erlang
235 | DesignDoc = {[
236 | {<<"_id">>, <<"_design/couchbeam">>},
237 | {<<"language">>,<<"javascript">>},
238 | {<<"views">>,
239 | {[{<<"test">>,
240 | {[{<<"map">>,
241 | <<"function (doc) {\n if (doc.type == \"test\") {\n emit(doc._id, doc);\n}\n}">>
242 | }]}
243 | },{<<"test2">>,
244 | {[{<<"map">>,
245 | <<"function (doc) {\n if (doc.type == \"test2\") {\n emit(doc._id, null);\n}\n}">>
246 | }]}
247 | }]}
248 | }
249 | ]},
250 | {ok, DesignDoc1} = couchbeam:save_doc(Db, DesignDoc).
251 | '''
252 |
253 | You can also use couchapp to manage them
254 | more easily.
255 |
256 | ### Stream View results
257 |
258 | While you can get results using `couchbeam_views:fetch/2', you can also retrieve
259 | all rows in a streaming fashion:
260 |
261 | ```erlang
262 | ViewFun = fun(Ref, F) ->
263 | receive
264 | {Ref, done} ->
265 | io:format("done", []),
266 | done;
267 | {Ref, {row, Row}} ->
268 | io:format("got ~p~n", [Row]),
269 | F(Ref, F);
270 | {error, Ref, Error} ->
271 | io:format("error: ~p~n", [Error])
272 | end
273 | end,
274 |
275 | {ok, StreamRef} = couchbeam_view:stream(Db, 'all_docs'),
276 | ViewFun(StreamRef, ViewFun),
277 | {ok, StreamRef2} = couchbeam_view:stream(Db, 'all_docs', [include_docs]),
278 | ViewFun(StreamRef2, ViewFun).
279 | '''
280 |
281 | You can of course do the same with a view:
282 |
283 | ```erlang
284 | DesignNam = "designname",
285 | ViewName = "viewname",
286 | {ok, StreamRef3} = couchbeam_view:stream(Db, {DesignNam, ViewName}, [include_docs]),
287 | ViewFun(StreamRef3, ViewFun).
288 | '''
289 |
290 | ### Put, Fetch and Delete documents attachments
291 |
292 | You can add attachments to any documents. Attachments could be anything.
293 |
294 | To send an attachment:
295 |
296 | ```erlang
297 | DocID = "test",
298 | AttName = "test.txt",
299 | Att = "some content I want to attach",
300 | Options = []
301 | {ok, _Result} = couchbeam:put_attachment(Db, DocId, AttName, Att, Options).
302 | '''
303 |
304 | All attachments are streamed to servers. `Att' could be also be an iolist
305 | or functions, see `couchbeam:put_attachment/5' for more information.
306 |
307 | To fetch an attachment:
308 |
309 | ```erlang
310 | {ok Att1} = couchbeam:fetch_attachment(Db, DocId, AttName).
311 | '''
312 |
313 | You can use `couchbeam:stream_fetch_attachment/6' for the stream
314 | fetch.
315 |
316 | To delete an attachment:
317 |
318 | ```erlang
319 | {ok, Doc4} = couchbeam:open_doc(Db, DocID),
320 | ok = couchbeam:delete_attachment(Db, Doc4, AttName).
321 | '''
322 |
323 | ### Changes
324 |
325 | Apache CouchDB and RCOUCH provide a means to get a list of changes made to documents in
326 | the database. With couchbeam you can get changes using `couchbeam_changes:follow_once/2'.
327 | This function returns all changes immediately. But you can also retrieve
328 | all changes rows using longpolling :
329 |
330 | ```erlang
331 | Options = [],
332 | {ok, LastSeq, Rows} = couchbeam_changes:follow_once(Db, Options).
333 | '''
334 |
335 | Options can be any Changes query parameters. See the [change API](http://docs.rcouch.org/en/latest/api/database/changes.html) for more informations.
336 |
337 | You can also get [continuous](http://docs.rcouch.org/en/latest/api/database/changes.html#continuous):
338 |
339 | ```erlang
340 | ChangesFun = fun(StreamRef, F) ->
341 | receive
342 | {StreamRef, {done, LastSeq}} ->
343 | io:format("stopped, last seq is ~p~n", [LastSeq]),
344 | ok;
345 | {StreamRef, {change, Change}} ->
346 | io:format("change row ~p ~n", [Change]),
347 | F(StreamRef, F);
348 | {StreamRef, Error}->
349 | io:format("error ? ~p ~n,", [Error])
350 | end
351 | end,
352 | Options = [continuous, heartbeat],
353 | {ok, StreamRef} = couchbeam_changes:follow(Db, Options),
354 | ChangesFun(StreamRef, ChangesFun).
355 | '''
356 |
357 | > **Note**: a `gen_changes' behaviour exists in couchbeam that you can
358 | use to create your own specific gen_server receiving changes. Have a
359 | look in the
360 | [example](https://github.com/benoitc/couchbeam/blob/master/examples/test_gen_changes.erl)
361 | for more info.
362 |
363 | ### Authentication/ Connections options
364 |
365 | You can authenticate to the database or Apache CouchDB or RCOUCH server by filling
366 | options to the Option list in `couchbeam:server_connection/4' for the
367 | server or in `couchbeam:create_db/3', `couchbeam:open_db/3',
368 | `couchbeam:wopen_or_create_db/3' functions.
369 |
370 | To set basic_auth on a server:
371 |
372 | ```erlang
373 | UserName = "guest",
374 | Password = "test",
375 | Url = "http://localhost:5984",
376 | Options = [{basic_auth, {UserName, Password}}],
377 | S1 = couchbeam:server_connection(Url, Options).
378 | '''
379 |
380 | Couchbeam support SSL, OAuth, Basic Authentication, and Proxy. You can
381 | also set a cookie. For more informations about the options have a look
382 | in the `couchbeam:server_connection/2' documentation.
383 |
384 |
385 | ## Contribute
386 |
387 | For issues, comments or feedback please [create an
388 | issue](http://github.com/benoitc/couchbeam/issues).
389 |
390 | @end
391 |
--------------------------------------------------------------------------------
/examples/changes.ebin:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env escript
2 | %% -*- erlang -*-
3 | %%! -pa ./ebin
4 |
5 | -module(changes).
6 |
7 | main(_) ->
8 | couchbeam:start(),
9 | Server = couchbeam:server_connection(),
10 | {ok, Db} = couchbeam:open_or_create_db(Server, "testdb"),
11 | {ok, Ref} = couchbeam_changes:follow(Db, [continuous, heartbeat]),
12 | io:format("stream ref ~p~n", [Ref]),
13 | io:format("i am ~p~n", [self()]),
14 | get_changes(Ref).
15 |
16 |
17 | get_changes(Ref) ->
18 | receive
19 | {Ref, {done, LastSeq}} ->
20 | io:format("stopped, last seq is ~p~n", [LastSeq]),
21 | ok;
22 | {Ref, {change, Change}} ->
23 | io:format("got ~p ~n", [Change]),
24 | get_changes(Ref);
25 | {Ref, Else}->
26 | io:format("else ? ~p ~n", [Else])
27 | end.
28 |
--------------------------------------------------------------------------------
/examples/changes_once.ebin:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env escript
2 | %% -*- erlang -*-
3 | %%! -pa ./ebin
4 |
5 | -module(changes).
6 |
7 | main(_) ->
8 | couchbeam:start(),
9 | Server = couchbeam:server_connection(),
10 | {ok, Db} = couchbeam:open_or_create_db(Server, "testdb"),
11 | {ok, LastSeq, Changes} = couchbeam_changes:follow_once(Db, [longpoll]),
12 | io:format("longpoll changes ~p~n", [Changes]),
13 | io:format("now wait until the next change...~n", []),
14 | {ok, _, Changes2} = couchbeam_changes:follow_once(Db,
15 | [longpoll,
16 | {since, LastSeq}]),
17 | io:format("longpoll changes 2 ~p~n", [Changes2]),
18 |
19 | {ok, _, Changes3} = couchbeam_changes:follow_once(Db),
20 | io:format("normal changes ~p~n", [Changes3]).
21 |
--------------------------------------------------------------------------------
/examples/test_gen_changes.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(test_gen_changes).
7 |
8 | -behaviour(gen_changes).
9 | -export([start_link/2,
10 | init/1,
11 | handle_change/2,
12 | handle_call/3,
13 | handle_cast/2,
14 | handle_info/2,
15 | terminate/2]).
16 |
17 | -export([get_changes/1]).
18 |
19 |
20 | -record(state, {changes=[]}).
21 |
22 | start_link(Db, Opts) ->
23 | gen_changes:start_link(?MODULE, Db, Opts, []).
24 |
25 |
26 | init([]) ->
27 | {ok, #state{}}.
28 |
29 | get_changes(Pid) ->
30 | gen_changes:call(Pid, get_changes).
31 |
32 | handle_change({done, _LastSeq}, State) ->
33 | {noreply, State};
34 |
35 |
36 | handle_change(Change, State=#state{changes=Changes}) ->
37 | NewChanges = [Change|Changes],
38 | {noreply, State#state{changes=NewChanges}}.
39 |
40 | handle_call(get_changes, _From, State=#state{changes=Changes}) ->
41 | {reply, lists:reverse(Changes), State}.
42 |
43 | handle_cast(_Msg, State) -> {noreply, State}.
44 |
45 | handle_info(Info, State) ->
46 | io:format("Unknown message: ~p~n", [Info]),
47 | {noreply, State}.
48 |
49 | terminate(Reason, _State) ->
50 | io:format("~p terminating with reason ~p~n", [?MODULE, Reason]),
51 | ok.
52 |
53 |
--------------------------------------------------------------------------------
/include/couchbeam.hrl:
--------------------------------------------------------------------------------
1 | %% @author Benoît Chesneau
2 | %% @copyright 2009 Benoît Chesneau.
3 | %%
4 | %%
5 | %% Licensed under the Apache License, Version 2.0 (the "License");
6 | %% you may not use this file except in compliance with the License.
7 | %% You may obtain a copy of the License at
8 | %%
9 | %% http://www.apache.org/licenses/LICENSE-2.0
10 | %%
11 | %% Unless required by applicable law or agreed to in writing, software
12 | %% distributed under the License is distributed on an "AS IS" BASIS,
13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | %% See the License for the specific language governing permissions and
15 | %% limitations under the License.
16 |
17 |
18 | -define(DEFAULT_TIMEOUT, 60000).
19 |
20 | -type header() :: {string() | atom(), string()}.
21 | -type headers() :: [header()].
22 | %% In R13B bool() is now called boolean()
23 | %% Uncomment if it's not compiling.
24 | %% -type boolean() :: bool()
25 |
26 |
27 |
28 | -type db_name() :: binary() | string().
29 | -type docid() :: binary() | string().
30 |
31 | -type design_name() :: binary() | string().
32 | -type view_name() :: binary() | string().
33 |
34 | -type ejson() :: ejson_object() | ejson_array().
35 |
36 | -type ejson_array() :: [ejson_term()].
37 | -type ejson_object() :: {[{ejson_key(), ejson_term()}]}.
38 |
39 | -type ejson_key() :: binary() | atom().
40 |
41 | -type ejson_term() :: ejson_array()
42 | | ejson_object()
43 | | ejson_string()
44 | | ejson_number()
45 | | true | false | null.
46 |
47 | -type ejson_string() :: binary().
48 |
49 | -type ejson_number() :: float() | integer().
50 |
51 | -type doc() :: ejson_object().
52 |
53 | -type changes_option() :: continuous | longpoll | normal
54 | | include_docs | {since, integer()}
55 | | {timeout, integer()}
56 | | heartbeat | {heartbeat, integer()}
57 | | {filter, string()} | {filter, string(), list({string(), string() | integer()}
58 | )}
59 | | conflicts | {style, string()} | descending.
60 | -type changes_options() :: list(changes_option()).
61 |
62 | -type changes_option1() :: longpoll | normal
63 | | include_docs | {since, integer()}
64 | | {timeout, integer()}
65 | | heartbeat | {heartbeat, integer()}
66 | | {filter, string()} | {filter, string(), list({string(), string() | integer()}
67 | )}
68 | | conflicts | {style, string()} | descending.
69 | -type changes_options1() :: list(changes_option1()).
70 |
71 | -type changes_stream_option() :: continuous | longpoll | normal
72 | | include_docs
73 | | {since, integer()}
74 | | {timeout, integer()}
75 | | heartbeat | {heartbeat, integer()}
76 | | {filter, string() | binary()}
77 | | {filter, string() | binary(), list({string(), string() | integer()})}
78 | | conflicts
79 | | {style, string() | binary()}
80 | | descending
81 | | {view, string() | binary()}
82 | | {doc_ids, list()}.
83 | -type changes_stream_options() :: list(changes_stream_option()).
84 |
85 | -type stale() :: ok | update_after | false.
86 |
87 | -type view_option() :: {key, binary()} | {startkey_docid, binary()}
88 | | {start_docid, binary()} | {startkey_docid, binary()}
89 | | {end_docid, binary()} | {endkey_docid, binary()}
90 | | {start_key, binary()} | {end_key, binary()}
91 | | {limit, integer()}
92 | | {stale, stale()}
93 | | descending
94 | | {skip, integer()}
95 | | group | {group_level, exact | integer()}
96 | | reduce | {reduce, boolean()}
97 | | inclusive_end | include_docs | conflicts
98 | | {list, binary()}
99 | | {keys, list(binary())}
100 | | async_query.
101 |
102 | -type view_options() :: list(view_option()).
103 |
104 | -record(view_query_args, {
105 | method = get :: atom(),
106 | options = [] :: view_options(),
107 | keys = [] :: list(binary())}).
108 |
109 | -type view_query_args() :: #view_query_args{}.
110 |
111 | -record(server, {
112 | url,
113 | options = [] :: list()
114 | }).
115 |
116 | -type server() :: #server{}.
117 |
118 | % record to keep database information
119 | -record(db, {
120 | server :: server(),
121 | name :: binary(),
122 | options = [] :: list()
123 | }).
124 |
125 | -type db() :: #db{}.
126 |
127 | -record(server_uuids, {
128 | server_url,
129 | uuids
130 | }).
131 |
132 | -record(view, {
133 | db :: db(),
134 | name :: string(),
135 | options :: iolist(),
136 | method :: atom(),
137 | body :: iolist(),
138 | headers :: list(),
139 | url_parts :: list(),
140 | url :: string()
141 | }).
142 |
143 | -type view() :: #view{}.
144 |
145 | -record(changes_args, {
146 | type = normal,
147 | since = 0,
148 | http_options = []}).
149 | -type changes_args() :: #changes_args{}.
150 |
151 | -record(gen_changes_state, {
152 | stream_ref,
153 | last_seq=0,
154 | mod,
155 | modstate,
156 | db,
157 | options}).
158 |
159 | -define(USER_AGENT, "couchbeam/0.9.0").
160 |
161 | -define(DEPRECATED(Old, New, When),
162 | couchbeam_util:deprecated(Old, New, When)).
163 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 |
3 |
4 | {erl_opts, [debug_info,
5 | {platform_define, "^(2[3-9])", 'USE_CRYPTO_MAC'}]}.
6 |
7 | {deps, [
8 | {jsx, "3.1.0"},
9 | {hackney, "1.24.1"}
10 | ]}.
11 |
12 |
13 | {profiles, [{docs, [{deps, [{edown,"0.9.1"}]},
14 | {edoc_opts, [{doclet, edown_doclet},
15 | {packages, false},
16 | {subpackages, true},
17 | {top_level_readme,
18 | {"./README.md", "http://github.com/benoitc/couchbeam"}}
19 | ]}]},
20 | {test, [
21 | {cover_enabled, true},
22 | {eunit_opts, [verbose]},
23 | {deps, [{oauth, "2.1.0"}]}
24 | ]}
25 | ]}.
26 |
--------------------------------------------------------------------------------
/rebar.lock:
--------------------------------------------------------------------------------
1 | {"1.2.0",
2 | [{<<"certifi">>,{pkg,<<"certifi">>,<<"2.15.0">>},1},
3 | {<<"hackney">>,{pkg,<<"hackney">>,<<"1.24.1">>},0},
4 | {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1},
5 | {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
6 | {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
7 | {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},1},
8 | {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.4.1">>},1},
9 | {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},1},
10 | {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}]}.
11 | [
12 | {pkg_hash,[
13 | {<<"certifi">>, <<"0E6E882FCDAAA0A5A9F2B3DB55B1394DBA07E8D6D9BCAD08318FB604C6839712">>},
14 | {<<"hackney">>, <<"F5205A125BBA6ED4587F9DB3CC7C729D11316FA8F215D3E57ED1C067A9703FA9">>},
15 | {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
16 | {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
17 | {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
18 | {<<"mimerl">>, <<"3882A5CA67FBBE7117BA8947F27643557ADEC38FA2307490C4C4207624CB213B">>},
19 | {<<"parse_trans">>, <<"6E6AA8167CB44CC8F39441D05193BE6E6F4E7C2946CB2759F015F8C56B76E5FF">>},
20 | {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>},
21 | {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]},
22 | {pkg_hash_ext,[
23 | {<<"certifi">>, <<"B147ED22CE71D72EAFDAD94F055165C1C182F61A2FF49DF28BCC71D1D5B94A60">>},
24 | {<<"hackney">>, <<"F4A7392A0B53D8BBC3EB855BDCC919CD677358E65B2AFD3840B5B3690C4C8A39">>},
25 | {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
26 | {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
27 | {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>},
28 | {<<"mimerl">>, <<"13AF15F9F68C65884ECCA3A3891D50A7B57D82152792F3E19D88650AA126B144">>},
29 | {<<"parse_trans">>, <<"620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A">>},
30 | {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>},
31 | {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]}
32 | ].
33 |
--------------------------------------------------------------------------------
/src/couchbeam.app.src:
--------------------------------------------------------------------------------
1 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
2 | %% ex: ft=erlang ts=4 sw=4 et
3 | %%%
4 | %%% This file is part of couchbeam released under the MIT license.
5 | %%% See the NOTICE for more information.
6 |
7 | {application, couchbeam,
8 | [{description, "Erlang CouchDB client"},
9 | {vsn, "1.7.0"},
10 | {modules, []},
11 | {registered, [
12 | couchbeam_sup
13 | ]},
14 | {applications, [kernel,
15 | stdlib,
16 | asn1,
17 | crypto,
18 | public_key,
19 | ssl,
20 | idna,
21 | hackney]},
22 | {included_applications, []},
23 | {env, []},
24 | {mod, { couchbeam_app, []}},
25 |
26 | {maintainers, ["Benoit Chesneau"]},
27 | {licenses, ["MIT"]},
28 | {links, [{"Github", "https://github.com/benoitc/couchbeam"}]},
29 | {files, [
30 | "src",
31 | "include",
32 | "rebar.config",
33 | "rebar.lock",
34 | "README.md",
35 | "NEWS.md",
36 | "LICENSE",
37 | "NOTICE"]}
38 | ]
39 | }.
40 |
--------------------------------------------------------------------------------
/src/couchbeam_app.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_app).
7 |
8 | -behaviour(application).
9 |
10 | -export([start/2, stop/1]).
11 |
12 | %%--------------------------------------------------------------------
13 | %% Func: start/2
14 | %% Returns: {ok, Pid} |
15 | %% {ok, Pid, State} |
16 | %% {error, Reason}
17 | %%--------------------------------------------------------------------
18 | start(_Type, _StartArgs) ->
19 | couchbeam_util:start_app_deps(couchbeam),
20 | couchbeam_sup:start_link().
21 |
22 | %%--------------------------------------------------------------------
23 | %% Func: stop/1
24 | %% Returns: any
25 | %%--------------------------------------------------------------------
26 | stop(_State) ->
27 | ok.
28 |
29 |
--------------------------------------------------------------------------------
/src/couchbeam_attachments.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 |
7 | %% @doc This module contains utilities to manage attachments
8 |
9 | -module(couchbeam_attachments).
10 |
11 | -include("couchbeam.hrl").
12 |
13 | -export([add_inline/3, add_inline/4,
14 | add_stub/3,
15 | delete_inline/2]).
16 |
17 | %% @spec add_inline(Doc::json_obj(),Content::attachment_content(),
18 | %% AName::string()) -> json_obj()
19 | %% @doc add attachment to a doc and encode it. Give possibility to send attachments inline.
20 | add_inline(Doc, Content, AName) ->
21 | AName1 = hackney_bstr:to_binary(AName),
22 | ContentType = mimerl:filename(AName1),
23 | add_inline(Doc, Content, AName1, ContentType).
24 |
25 | %% @spec add_inline(Doc::json_obj(), Content::attachment_content(),
26 | %% AName::string(), ContentType::string()) -> json_obj()
27 | %% @doc add attachment to a doc and encode it with ContentType fixed.
28 | add_inline(Doc, Content, AName, ContentType) ->
29 | {Props} = Doc,
30 | Data = base64:encode(Content),
31 | Attachment = {AName, {[{<<"content_type">>, ContentType},
32 | {<<"data">>, Data}]}},
33 |
34 | Attachments1 = case proplists:get_value(<<"_attachments">>, Props) of
35 | undefined ->
36 | [Attachment];
37 | {Attachments} ->
38 | case set_attachment(Attachments, [], Attachment) of
39 | notfound ->
40 | [Attachment|Attachments];
41 | A ->
42 | A
43 | end
44 | end,
45 | couchbeam_doc:set_value(<<"_attachments">>, {Attachments1}, Doc).
46 |
47 |
48 | add_stub({Props} = Doc, Name, ContentType) ->
49 | Att = {couchbeam_util:to_binary(Name), {[
50 | {<<"content_type">>, couchbeam_util:to_binary(ContentType)}
51 | ]}},
52 |
53 | Attachments1 = case proplists:get_value(<<"_attachments">>, Props) of
54 | undefined ->
55 | [Att];
56 | {Attachments} ->
57 | case set_attachment(Attachments, [], Att) of
58 | notfound ->
59 | [Att|Attachments];
60 | A ->
61 | A
62 | end
63 | end,
64 | couchbeam_doc:set_value(<<"_attachments">>, {Attachments1}, Doc).
65 |
66 |
67 | %% @spec delete_inline(Doc::json_obj(), AName::string()) -> json_obj()
68 | %% @doc delete an attachment record in doc. This is different from delete_attachment
69 | %% change is only applied in Doc object. Save_doc should be save to save changes.
70 | delete_inline(Doc, AName) when is_list(AName) ->
71 | delete_inline(Doc, list_to_binary(AName));
72 | delete_inline(Doc, AName) when is_binary(AName) ->
73 | {Props} = Doc,
74 | case proplists:get_value(<<"_attachments">>, Props) of
75 | undefined ->
76 | Doc;
77 | {Attachments} ->
78 | case proplists:get_value(AName, Attachments) of
79 | undefined ->
80 | Doc;
81 | _ ->
82 | Attachments1 = proplists:delete(AName, Attachments),
83 | couchbeam_doc:set_value(<<"_attachments">>, {Attachments1}, Doc)
84 | end
85 | end.
86 |
87 | % @private
88 | set_attachment(Attachments, NewAttachments, Attachment) ->
89 | set_attachment(Attachments, NewAttachments, Attachment, false).
90 | set_attachment([], Attachments, _Attachment, Found) ->
91 | case Found of
92 | true ->
93 | Attachments;
94 | false ->
95 | notfound
96 | end;
97 | set_attachment([{Name, V}|T], Attachments, Attachment, Found) ->
98 | {AName, _} = Attachment,
99 | {Attachment1, Found1} = if
100 | Name =:= AName, Found =:= false ->
101 | {Attachment, true};
102 | true ->
103 | {{Name, V}, Found}
104 | end,
105 | set_attachment(T, [Attachment1|Attachments], Attachment, Found1).
106 |
--------------------------------------------------------------------------------
/src/couchbeam_changes.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_changes).
7 |
8 | -include("couchbeam.hrl").
9 |
10 | -export([follow/1, follow/2,
11 | cancel_stream/1,
12 | stream_next/1,
13 | follow_once/1, follow_once/2]).
14 |
15 | -spec follow(Db::db()) -> {ok, StreamRef::atom()} | {error, term()}.
16 | follow(Db) ->
17 | follow(Db, []).
18 |
19 | %% @doc Stream changes to a pid
20 | %% Db : a db record
21 | %% Client : pid or callback where to send changes events where events are
22 | %% The pid receive these events:
23 | %%
24 | %% - {change, StartRef, {done, Lastseq::integer()}
25 | %% - Connection terminated or you got all changes
26 | %% - {change, StartRef, Row :: ejson_object()}
27 | %% - Line of change
28 | %% - {error, LastSeq::integer(), Msg::term()}
29 | %% - Got an error, connection is closed when an error
30 | %% happend.
31 | %%
32 | %% LastSeq is the last sequence of changes.
33 | %% While the callbac could be like:
34 | %%
35 | %% fun({done, LastSeq}) ->
36 | %% ok;
37 | %% fun({done, LastSeq}) ->
38 | %% ok;
39 | %% fun({done, LastSeq}) ->
40 | %% ok.
41 | %% >Options :: changes_stream_options() [continuous
42 | %% | longpoll
43 | %% | normal
44 | %% | include_docs
45 | %% | {since, integer() | now}
46 | %% | {timeout, integer()}
47 | %% | heartbeat | {heartbeat, integer()}
48 | %% | {filter, string()} | {filter, string(), list({string(), string() | integer()})}
49 | %% | {view, string()},
50 | %% | {docids, list))},
51 | %% | {stream_to, pid()},
52 | %% | {async, once | normal}]
53 | %%
54 | %%
55 | %% continuous | longpoll | normal
: set the type of changes
56 | %% feed to get
57 | %% include_doc
: if you want to include the doc in the line of
58 | %% change
59 | %% {timeout, Timeout::integer()}
: timeout
60 | %% heartbeat | {heartbeat, Heartbeat::integer()}
: set couchdb
61 | %% to send a heartbeat to maintain connection open
62 | %% {filter, FilterName} | {filter, FilterName, Args::list({key,
63 | %% value})}
: set the filter to use with optional arguments
64 | %% {view, ViewName}
: use a view function as filter. Note
65 | %% that it requires to set filter special value "_view"
66 | %% to enable this feature.
67 | %% - >`{stream_to, Pid}': the pid where the changes will be sent,
68 | %% by default the current pid. Used for continuous and longpoll
69 | %% connections
70 | %%
71 | %%
72 | %% Return {ok, StartRef, ChangesPid} or {error, Error}. Ref can be
73 | %% used to disctint all changes from this pid. ChangesPid is the pid of
74 | %% the changes loop process. Can be used to monitor it or kill it
75 | %% when needed.
76 | -spec follow(Db::db(), Options::changes_options()) ->
77 | {ok, StreamRef::atom()} | {error, term()}.
78 | follow(Db, Options) ->
79 | {To, Options1} = case proplists:get_value(stream_to, Options) of
80 | undefined ->
81 | {self(), Options};
82 | Pid ->
83 | {Pid, proplists:delete(stream_to, Options)}
84 | end,
85 |
86 | Ref = make_ref(),
87 | case supervisor:start_child(couchbeam_changes_sup, [To, Ref, Db,
88 | Options1]) of
89 | {ok, _Pid} ->
90 | {ok, Ref};
91 | {error, _} = Error ->
92 | Error
93 | end.
94 |
95 | -spec follow_once(Db::db())
96 | -> {ok, LastSeq::integer(), Changes::list()} | {error,term()}.
97 | follow_once(Db) ->
98 | follow_once(Db, []).
99 |
100 |
101 | %% @doc fetch all changes at once using a normal or longpoll
102 | %% connections.
103 | %%
104 | %% Db : a db record
105 | %% Options :: changes_options() [
106 | %% | longpoll
107 | %% | normal
108 | %% | include_docs
109 | %% | {since, integer() | now}
110 | %% | {timeout, integer()}
111 | %% | heartbeat | {heartbeat, integer()}
112 | %% | {filter, string()}
113 | %% | {filter, string(), list({string(), string() | integer()})}
114 | %% | {docids, list()))},
115 | %% | {stream_to, pid()}
116 | %% ]
117 | %%
118 | %%
119 | %% longpoll | normal
: set the type of changes
120 | %% feed to get
121 | %% include_docs
: if you want to include the doc in the line of
122 | %% change
123 | %% {timeout, Timeout::integer()}
: timeout
124 | %% heartbeat | {heartbeat, Heartbeat::integer()}
: set couchdb
125 | %% to send a heartbeat to maintain connection open
126 | %% {filter, FilterName} | {filter, FilterName, Args::list({key,
127 | %% value})
: set the filter to use with optional arguments
128 | %% {view, ViewName}
: use a view function as filter. Note
129 | %% that it requires to set filter special value "_view"
130 | %% to enable this feature.
131 | %%
132 | %%
133 | %% Result: {ok, LastSeq::integer(), Rows::list()}
or
134 | %% {error, LastSeq, Error}
. LastSeq is the last sequence of changes.
135 | -spec follow_once(Db::db(), Options::changes_options())
136 | -> {ok, LastSeq::integer(), Changes::list()} | {error,term()}.
137 | follow_once(Db, Options) ->
138 | case parse_options_once(Options, []) of
139 | {error, _}=Error ->
140 | Error;
141 | Options1 ->
142 | FinalOptions = couchbeam_util:force_param(reconnect_after,
143 | false, Options1),
144 | case proplists:get_value(feed, FinalOptions) of
145 | longpoll ->
146 | case follow(Db, FinalOptions) of
147 | {ok, Ref} ->
148 | collect_changes(Ref);
149 | {error, _} = Error ->
150 | Error
151 | end;
152 | _ ->
153 | changes_request(Db, FinalOptions)
154 | end
155 | end.
156 |
157 |
158 |
159 | cancel_stream(Ref) ->
160 | with_changes_stream(Ref, fun(Pid) ->
161 | case supervisor:terminate_child(couchbeam_view_sup, Pid) of
162 | ok ->
163 | case supervisor:delete_child(couchbeam_view_sup, Pid) of
164 | ok ->ok;
165 | {error, not_found} -> ok;
166 | Error -> Error
167 | end;
168 | Error ->
169 | Error
170 | end
171 | end).
172 |
173 | stream_next(Ref) ->
174 | with_changes_stream(Ref, fun(Pid) ->
175 | Pid ! {Ref, stream_next}
176 | end).
177 |
178 | %% @private
179 | collect_changes(Ref) ->
180 | collect_changes(Ref, []).
181 |
182 | collect_changes(Ref, Acc) ->
183 | receive
184 | {Ref, {done, LastSeq}} ->
185 | Changes = lists:reverse(Acc),
186 | {ok, LastSeq, Changes};
187 | {Ref, {change, Change}} ->
188 | collect_changes(Ref, [Change|Acc]);
189 | {Ref, Error} ->
190 | Error;
191 | Error ->
192 | Error
193 | end.
194 |
195 |
196 | with_changes_stream(Ref, Fun) ->
197 | case ets:lookup(couchbeam_changes_streams, Ref) of
198 | [] ->
199 | {error, stream_undefined};
200 | [{Ref, Pid}] ->
201 | %% Check if the process is still alive to avoid race conditions
202 | case is_process_alive(Pid) of
203 | true ->
204 | Fun(Pid);
205 | false ->
206 | %% Clean up the stale entry
207 | ets:delete(couchbeam_changes_streams, Ref),
208 | {error, stream_undefined}
209 | end
210 | end.
211 |
212 |
213 | changes_request(#db{server=Server, options=ConnOptions}=Db, Options) ->
214 | %% if we are filtering the changes using docids, send a POST request
215 | %% instead of a GET to make sure it will be accepted whatever the
216 | %% number of doc ids given.
217 | {DocIds, Options1} = case proplists:get_value(doc_ids, Options) of
218 | undefined ->
219 | {[], Options};
220 | [] ->
221 | {[], Options};
222 | Ids ->
223 | {Ids, proplists:delete(doc_ids, Options)}
224 | end,
225 |
226 | %% make url
227 | Url = hackney_url:make_url(couchbeam_httpc:server_url(Server),
228 | [couchbeam_httpc:db_url(Db), <<"_changes">>],
229 | Options1),
230 |
231 | %% do the request
232 | Resp = case DocIds of
233 | [] ->
234 | couchbeam_httpc:db_request(get, Url, [], <<>>, ConnOptions,
235 | [200, 202]);
236 | _ ->
237 | Body = couchbeam_ejson:encode({[{<<"doc_ids">>, DocIds}]}),
238 | Headers = [{<<"Content-Type">>, <<"application/json">>}],
239 | couchbeam_httpc:db_request(post, Url, Headers, Body, ConnOptions,
240 | [200, 202])
241 | end,
242 |
243 | case Resp of
244 | {ok, _, _, Ref} ->
245 | {Props} = couchbeam_httpc:json_body(Ref),
246 | LastSeq = couchbeam_util:get_value(<<"last_seq">>, Props),
247 | Changes = couchbeam_util:get_value(<<"results">>, Props),
248 | {ok, LastSeq, Changes};
249 | Error ->
250 | Error
251 | end.
252 |
253 | parse_options_once([], Acc) ->
254 | lists:reverse(Acc);
255 | parse_options_once([normal | Rest], Acc) ->
256 | parse_options_once(Rest, couchbeam_util:force_param(feed, normal,
257 | Acc));
258 | parse_options_once([continuous | _Rest], _Acc) ->
259 | {error, {badarg, continuous}};
260 | parse_options_once([longpoll | Rest], Acc) ->
261 | parse_options_once(Rest, couchbeam_util:force_param(feed, longpoll,
262 | Acc));
263 | parse_options_once([heartbeat | Rest], Acc) ->
264 | parse_options_once(Rest, couchbeam_util:force_param(heartbeat, true,
265 | Acc));
266 | parse_options_once([descending | Rest], Acc) ->
267 | parse_options_once(Rest, couchbeam_util:force_param(descending, true,
268 | Acc));
269 | parse_options_once([conflicts | Rest], Acc) ->
270 | parse_options_once(Rest, couchbeam_util:force_param(conflicts, true,
271 | Acc));
272 | parse_options_once([include_docs | Rest], Acc) ->
273 | parse_options_once(Rest, couchbeam_util:force_param(include_docs, true,
274 | Acc));
275 | parse_options_once([{K, V} | Rest], Acc) ->
276 | parse_options_once(Rest, [{K, V} | Acc]).
277 |
--------------------------------------------------------------------------------
/src/couchbeam_changes_sup.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_changes_sup).
7 |
8 | -behaviour(supervisor).
9 |
10 | %% API.
11 | -export([start_link/0]).
12 |
13 | %% supervisor.
14 | -export([init/1]).
15 |
16 | -define(SUPERVISOR, ?MODULE).
17 |
18 | %% API.
19 |
20 | -spec start_link() -> {ok, pid()}.
21 | start_link() ->
22 | supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
23 |
24 | %% supervisor.
25 |
26 | init([]) ->
27 |
28 | %% start table to keep async streams ref
29 | ets:new(couchbeam_changes_streams, [set, public, named_table]),
30 |
31 | %% define a stream spec
32 | Stream = {couchbeam_changes_stream,
33 | {couchbeam_changes_stream, start_link, []},
34 | temporary, infinity, worker, [couchbeam_changes_stream]},
35 |
36 | {ok, {{simple_one_for_one, 10, 3600}, [Stream]}}.
37 |
--------------------------------------------------------------------------------
/src/couchbeam_doc.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_doc).
7 | -author('Benoît Chesneau ').
8 | -include("couchbeam.hrl").
9 |
10 | -export([set_value/3, get_value/2, get_value/3,
11 | take_value/2, take_value/3,
12 | delete_value/2, extend/2, extend/3]).
13 | -export([get_id/1, get_rev/1, get_idrev/1, is_saved/1]).
14 |
15 | %% @spec get_id(Doc::json_obj()) -> binary()
16 | %% @doc get document id.
17 | get_id(Doc) ->
18 | get_value(<<"_id">>, Doc).
19 |
20 | %% @spec get_rev(Doc::json_obj()) -> binary()
21 | %% @doc get document revision.
22 | get_rev(Doc) ->
23 | get_value(<<"_rev">>, Doc).
24 |
25 | %% @spec get_idrev(Doc::json_obj()) -> {DocId, DocRev}
26 | %% @doc get a tuple containing docucment id and revision.
27 | get_idrev(Doc) ->
28 | DocId = get_value(<<"_id">>, Doc),
29 | DocRev = get_value(<<"_rev">>, Doc),
30 | {DocId, DocRev}.
31 |
32 | %% @spec is_saved(Doc::json_obj()) -> boolean()
33 | %% @doc If document have been saved (revision is defined) return true,
34 | %% else, return false.
35 | is_saved(Doc) ->
36 | case get_value(<<"_rev">>, Doc) of
37 | undefined -> false;
38 | _ -> true
39 | end.
40 |
41 | %% @spec set_value(Key::key_val(), Value::term(), JsonObj::json_obj()) -> term()
42 | %% @doc set a value for a key in jsonobj. If key exists it will be updated.
43 | set_value(Key, Value, JsonObj) when is_list(Key)->
44 | set_value(list_to_binary(Key), Value, JsonObj);
45 | set_value(Key, Value, JsonObj) when is_binary(Key) ->
46 | {Props} = JsonObj,
47 | case proplists:is_defined(Key, Props) of
48 | true -> set_value1(Props, Key, Value, []);
49 | false-> {lists:reverse([{Key, Value}|lists:reverse(Props)])}
50 | end.
51 |
52 | %% @spec get_value(Key::key_val(), JsonObj::json_obj()) -> term()
53 | %% @type key_val() = lis() | binary()
54 | %% @doc Returns the value of a simple key/value property in json object
55 | %% Equivalent to get_value(Key, JsonObj, undefined).
56 | get_value(Key, JsonObj) ->
57 | get_value(Key, JsonObj, undefined).
58 |
59 |
60 | %% @spec get_value(Key::lis() | binary(), JsonObj::json_obj(), Default::term()) -> term()
61 | %% @doc Returns the value of a simple key/value property in json object
62 | %% function from erlang_couchdb
63 | get_value(Key, JsonObj, Default) when is_list(Key) ->
64 | get_value(list_to_binary(Key), JsonObj, Default);
65 | get_value(Key, JsonObj, Default) when is_binary(Key) ->
66 | {Props} = JsonObj,
67 | couchbeam_util:get_value(Key, Props, Default).
68 |
69 |
70 | %% @spec take_value(Key::key_val(), JsonObj::json_obj()) -> {term(), json_obj()}
71 | %% @doc Returns the value of a simple key/value property in json object and deletes
72 | %% it form json object
73 | %% Equivalent to take_value(Key, JsonObj, undefined).
74 | take_value(Key, JsonObj) ->
75 | take_value(Key, JsonObj, undefined).
76 |
77 |
78 | %% @spec take_value(Key::key_val() | binary(), JsonObj::json_obj(),
79 | %% Default::term()) -> {term(), json_obj()}
80 | %% @doc Returns the value of a simple key/value property in json object and deletes
81 | %% it from json object
82 | take_value(Key, JsonObj, Default) when is_list(Key) ->
83 | get_value(list_to_binary(Key), JsonObj, Default);
84 | take_value(Key, JsonObj, Default) when is_binary(Key) ->
85 | {Props} = JsonObj,
86 | case lists:keytake(Key, 1, Props) of
87 | {value, {Key, Value}, Rest} ->
88 | {Value, {Rest}};
89 | false ->
90 | {Default, JsonObj}
91 | end.
92 |
93 | %% @spec delete_value(Key::key_val(), JsonObj::json_obj()) -> json_obj()
94 | %% @doc Deletes all entries associated with Key in json object.
95 | delete_value(Key, JsonObj) when is_list(Key) ->
96 | delete_value(list_to_binary(Key), JsonObj);
97 | delete_value(Key, JsonObj) when is_binary(Key) ->
98 | {Props} = JsonObj,
99 | Props1 = proplists:delete(Key, Props),
100 | {Props1}.
101 |
102 | %% @spec extend(Key::binary(), Value::json_term(), JsonObj::json_obj()) -> json_obj()
103 | %% @doc extend a jsonobject by key, value
104 | extend(Key, Value, JsonObj) ->
105 | extend({Key, Value}, JsonObj).
106 |
107 | %% @spec extend(Prop::property(), JsonObj::json_obj()) -> json_obj()
108 | %% @type property() = json_obj() | tuple()
109 | %% @doc extend a jsonobject by a property, list of property or another jsonobject
110 | extend([], JsonObj) ->
111 | JsonObj;
112 | extend({List}, JsonObj) when is_list(List) ->
113 | extend(List, JsonObj);
114 | extend([Prop|R], JsonObj)->
115 | NewObj = extend(Prop, JsonObj),
116 | extend(R, NewObj);
117 | extend({Key, Value}, JsonObj) ->
118 | set_value(Key, Value, JsonObj).
119 |
120 | %% @private
121 | set_value1([], _Key, _Value, Acc) ->
122 | {lists:reverse(Acc)};
123 | set_value1([{K, V}|T], Key, Value, Acc) ->
124 | Acc1 = if
125 | K =:= Key ->
126 | [{Key, Value}|Acc];
127 | true ->
128 | [{K, V}|Acc]
129 | end,
130 | set_value1(T, Key, Value, Acc1).
131 |
132 |
133 | -ifdef(TEST).
134 |
135 | -include_lib("eunit/include/eunit.hrl").
136 |
137 | get_value_test() ->
138 | Doc = {[{<<"a">>, 1}]},
139 | ?assertEqual(1, couchbeam_doc:get_value(<<"a">>, Doc)),
140 | ?assertEqual(1, couchbeam_doc:get_value("a", Doc)),
141 | ?assertEqual(undefined, couchbeam_doc:get_value("b", Doc)),
142 | ?assertEqual(nil, couchbeam_doc:get_value("b", Doc, nil)),
143 | ok.
144 |
145 | set_value_test() ->
146 | Doc = {[{<<"a">>, 1}]},
147 | ?assertEqual(undefined, couchbeam_doc:get_value("b", Doc)),
148 | Doc1 = couchbeam_doc:set_value("b", 1, Doc),
149 | ?assertEqual(1, couchbeam_doc:get_value("b", Doc1)),
150 | Doc2 = couchbeam_doc:set_value("b", 0, Doc1),
151 | ?assertEqual(0, couchbeam_doc:get_value("b", Doc2)),
152 | ok.
153 |
154 | delete_value_test() ->
155 | Doc = {[{<<"a">>, 1}, {<<"b">>, 1}]},
156 | Doc1 = couchbeam_doc:delete_value("b", Doc),
157 | ?assertEqual(undefined, couchbeam_doc:get_value("b", Doc1)),
158 | ok.
159 |
160 | extend_test() ->
161 | Doc = {[{<<"a">>, 1}]},
162 | ?assertEqual(1, couchbeam_doc:get_value("a", Doc)),
163 | ?assertEqual(undefined, couchbeam_doc:get_value("b", Doc)),
164 | ?assertEqual(undefined, couchbeam_doc:get_value("c", Doc)),
165 | Doc1 = couchbeam_doc:extend([{<<"b">>, 1}, {<<"c">>, 1}], Doc),
166 | ?assertEqual(1, couchbeam_doc:get_value("b", Doc1)),
167 | ?assertEqual(1, couchbeam_doc:get_value("c", Doc1)),
168 | Doc2 = couchbeam_doc:extend([{<<"b">>, 3}, {<<"d">>, 1}], Doc1),
169 | ?assertEqual(3, couchbeam_doc:get_value("b", Doc2)),
170 | ?assertEqual(1, couchbeam_doc:get_value("d", Doc2)),
171 | ok.
172 |
173 | id_rev_test() ->
174 | Doc = {[{<<"a">>, 1}]},
175 | ?assertEqual(undefined, couchbeam_doc:get_id(Doc)),
176 | ?assertEqual(undefined, couchbeam_doc:get_rev(Doc)),
177 | ?assertEqual({undefined, undefined}, couchbeam_doc:get_idrev(Doc)),
178 | Doc1 = couchbeam_doc:extend([{<<"_id">>, 1}, {<<"_rev">>, 1}], Doc),
179 | ?assertEqual(1, couchbeam_doc:get_id(Doc1)),
180 | ?assertEqual(1, couchbeam_doc:get_rev(Doc1)),
181 | ?assertEqual({1, 1}, couchbeam_doc:get_idrev(Doc1)),
182 | ok.
183 |
184 | is_saved_test() ->
185 | Doc = {[{<<"a">>, 1}]},
186 | ?assertEqual(false, couchbeam_doc:is_saved(Doc)),
187 | Doc1 = couchbeam_doc:set_value(<<"_rev">>, <<"x">>, Doc),
188 | ?assertEqual(true, couchbeam_doc:is_saved(Doc1)),
189 | ok.
190 |
191 |
192 | take_value_test() ->
193 | Doc = {[{<<"a">>, 1}, {<<"b">>, 2}]},
194 | ?assertEqual({undefined, Doc}, couchbeam_doc:take_value(<<"c">>, Doc)),
195 | ?assertEqual({1, {[{<<"b">>, 2}]}}, couchbeam_doc:take_value(<<"a">>, Doc)),
196 | ok.
197 |
198 | -endif.
199 |
200 |
201 |
--------------------------------------------------------------------------------
/src/couchbeam_ejson.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 | %%%
6 |
7 | -module(couchbeam_ejson).
8 |
9 | -export([encode/1, decode/1]).
10 | -export([post_decode/1]).
11 |
12 | -include("couchbeam.hrl").
13 |
14 |
15 | -ifndef('WITH_JIFFY').
16 | -define(JSON_ENCODE(D), jsx:encode(pre_encode(D))).
17 | -define(JSON_DECODE(D), post_decode(jsx:decode(D, [{return_maps, false}]))).
18 |
19 | -else.
20 | -define(JSON_ENCODE(D), jiffy:encode(D, [uescape])).
21 | -define(JSON_DECODE(D), jiffy:decode(D)).
22 | -endif.
23 |
24 |
25 | -spec encode(ejson()) -> binary().
26 |
27 | %% @doc encode an erlang term to JSON. Throw an exception if there is
28 | %% any error.
29 | encode(D) ->
30 | ?JSON_ENCODE(D).
31 |
32 | -spec decode(binary()) -> ejson().
33 | %% @doc decode a binary to an EJSON term. Throw an exception if there is
34 | %% any error.
35 | decode(D) ->
36 | try
37 | ?JSON_DECODE(D)
38 | catch
39 | throw:Error ->
40 | throw({invalid_json, Error});
41 | error:badarg ->
42 | throw({invalid_json, badarg})
43 | end.
44 |
45 | pre_encode({[]}) ->
46 | [{}];
47 | pre_encode({PropList}) ->
48 | pre_encode(PropList);
49 | pre_encode([{_, _}|_] = PropList) ->
50 | [ {Key, pre_encode(Value)} || {Key, Value} <- PropList ];
51 | pre_encode(List) when is_list(List) ->
52 | [ pre_encode(Term) || Term <- List ];
53 | pre_encode(true) ->
54 | true;
55 | pre_encode(false) ->
56 | false;
57 | pre_encode(null) ->
58 | null;
59 | pre_encode(Atom) when is_atom(Atom) ->
60 | erlang:atom_to_binary(Atom, utf8);
61 | pre_encode(Term) when is_integer(Term); is_float(Term); is_binary(Term) ->
62 | Term.
63 |
64 | post_decode({[{}]}) ->
65 | {[]};
66 | post_decode([{}]) ->
67 | {[]};
68 | post_decode([{_Key, _Value} | _Rest] = PropList) ->
69 | {[ {Key, post_decode(Value)} || {Key, Value} <- PropList ]};
70 | post_decode(List) when is_list(List) ->
71 | [ post_decode(Term) || Term <- List];
72 | post_decode({Term}) ->
73 | post_decode(Term);
74 | post_decode(Term) ->
75 | Term.
76 |
--------------------------------------------------------------------------------
/src/couchbeam_httpc.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_httpc).
7 |
8 | -export([request/5,
9 | db_request/5, db_request/6,
10 | json_body/1,
11 | db_resp/2,
12 | make_headers/4,
13 | maybe_oauth_header/4]).
14 | %% urls utils
15 | -export([server_url/1, db_url/1, doc_url/2]).
16 | %% atts utols
17 | -export([reply_att/1, wait_mp_doc/2, len_doc_to_mp_stream/3, send_mp_doc/5]).
18 |
19 | -include("couchbeam.hrl").
20 |
21 | request(Method, Url, Headers, Body, Options) ->
22 | {FinalHeaders, FinalOpts} = make_headers(Method, Url, Headers,
23 | Options),
24 |
25 | hackney:request(Method, Url , FinalHeaders, Body, FinalOpts).
26 |
27 | db_request(Method, Url, Headers, Body, Options) ->
28 | db_request(Method, Url, Headers, Body, Options, []).
29 |
30 | db_request(Method, Url, Headers, Body, Options, Expect) ->
31 | Resp = request(Method, Url, Headers, Body, Options),
32 | db_resp(Resp, Expect).
33 |
34 | json_body(Ref) ->
35 | case hackney:body(Ref) of
36 | {ok, Body} ->
37 | couchbeam_ejson:decode(Body);
38 | {error, _} = Error ->
39 | Error
40 | end.
41 |
42 | make_headers(Method, Url, Headers, Options) ->
43 | Headers1 = case couchbeam_util:get_value(<<"Accept">>, Headers) of
44 | undefined ->
45 | [{<<"Accept">>, <<"application/json, */*;q=0.9">>} | Headers];
46 | _ ->
47 | Headers
48 | end,
49 | {Headers2, Options1} = maybe_oauth_header(Method, Url, Headers1, Options),
50 | maybe_proxyauth_header(Headers2, Options1).
51 |
52 |
53 | maybe_oauth_header(Method, Url, Headers, Options) ->
54 | case couchbeam_util:get_value(oauth, Options) of
55 | undefined ->
56 | {Headers, Options};
57 | OauthProps ->
58 | Hdr = couchbeam_util:oauth_header(Url, Method, OauthProps),
59 | {[Hdr|Headers], proplists:delete(oauth, Options)}
60 | end.
61 |
62 | maybe_proxyauth_header(Headers, Options) ->
63 | case couchbeam_util:get_value(proxyauth, Options) of
64 | undefined ->
65 | {Headers, Options};
66 | ProxyauthProps ->
67 | {lists:append([ProxyauthProps,Headers]), proplists:delete(proxyauth, Options)}
68 | end.
69 |
70 | db_resp({ok, Ref}=Resp, _Expect) when is_reference(Ref) ->
71 | Resp;
72 | db_resp({ok, 401, _}, _Expect) ->
73 | {error, unauthenticated};
74 | db_resp({ok, 403, _}, _Expect) ->
75 | {error, forbidden};
76 | db_resp({ok, 404, _}, _Expect) ->
77 | {error, not_found};
78 | db_resp({ok, 409, _}, _Expect) ->
79 | {error, conflict};
80 | db_resp({ok, 412, _}, _Expect) ->
81 | {error, precondition_failed};
82 | db_resp({ok, _, _}=Resp, []) ->
83 | Resp;
84 | db_resp({ok, Status, Headers}=Resp, Expect) ->
85 | case lists:member(Status, Expect) of
86 | true -> Resp;
87 | false ->
88 | {error, {bad_response, {Status, Headers, <<>>}}}
89 | end;
90 | db_resp({ok, 401, _, Ref}, _Expect) ->
91 | hackney:skip_body(Ref),
92 | {error, unauthenticated};
93 | db_resp({ok, 403, _, Ref}, _Expect) ->
94 | hackney:skip_body(Ref),
95 | {error, forbidden};
96 | db_resp({ok, 404, _, Ref}, _Expect) ->
97 | hackney:skip_body(Ref),
98 | {error, not_found};
99 | db_resp({ok, 409, _, Ref}, _Expect) ->
100 | hackney:skip_body(Ref),
101 | {error, conflict};
102 | db_resp({ok, 412, _, Ref}, _Expect) ->
103 | hackney:skip_body(Ref),
104 | {error, precondition_failed};
105 | db_resp({ok, _, _, _}=Resp, []) ->
106 | Resp;
107 | db_resp({ok, Status, Headers, Ref}=Resp, Expect) ->
108 | case lists:member(Status, Expect) of
109 | true -> Resp;
110 | false -> {error, {bad_response, {Status, Headers, db_resp_body(Ref)}}}
111 | end;
112 | db_resp(Error, _Expect) ->
113 | Error.
114 |
115 | db_resp_body(Ref) ->
116 | case hackney:body(Ref) of
117 | {ok, Body} -> Body;
118 | {error, _} ->
119 | %% Try to close the connection to prevent leaks
120 | catch hackney:close(Ref),
121 | <<>>
122 | end.
123 |
124 | %% @doc Asemble the server URL for the given client
125 | %% @spec server_url({Host, Port}) -> iolist()
126 | server_url(#server{url=Url}) ->
127 | Url.
128 |
129 | db_url(#db{name=DbName}) ->
130 | DbName.
131 |
132 | doc_url(Db, DocId) ->
133 | iolist_to_binary([db_url(Db), <<"/">>, DocId]).
134 |
135 |
136 | %% attachments handling
137 |
138 | %% @hidden
139 | reply_att(ok) ->
140 | ok;
141 | reply_att(done) ->
142 | done;
143 | reply_att({ok, 404, _, Ref}) ->
144 | hackney:skip_body(Ref),
145 | {error, not_found};
146 | reply_att({ok, 409, _, Ref}) ->
147 | hackney:skip_body(Ref),
148 | {error, conflict};
149 | reply_att({ok, Status, _, Ref}) when Status =:= 200 orelse Status =:= 201 ->
150 | case couchbeam_httpc:json_body(Ref) of
151 | {[{<<"ok">>, true}|R]} ->
152 | {ok, {R}};
153 | {error, _} = Error ->
154 | Error
155 | end;
156 | reply_att({ok, Status, Headers, Ref}) ->
157 | {ok, Body} = hackney:body(Ref),
158 | {error, {bad_response, {Status, Headers, Body}}};
159 | reply_att(Error) ->
160 | Error.
161 |
162 | %% @hidden
163 | wait_mp_doc(Ref, Buffer) ->
164 | %% we are always waiting for the full doc
165 | case hackney:stream_multipart(Ref) of
166 | {headers, _} ->
167 | wait_mp_doc(Ref, Buffer);
168 | {body, Data} ->
169 | NBuffer = << Buffer/binary, Data/binary >>,
170 | wait_mp_doc(Ref, NBuffer);
171 | end_of_part when Buffer =:= <<>> ->
172 | %% end of part in multipart/mixed
173 | wait_mp_doc(Ref, Buffer);
174 | end_of_part ->
175 | %% decode the doc
176 | {Props} = Doc = couchbeam_ejson:decode(Buffer),
177 | case couchbeam_util:get_value(<<"_attachments">>, Props, {[]}) of
178 | {[]} ->
179 | %% not attachments wait for the eof or the next doc
180 | NState = {Ref, fun() -> wait_mp_doc(Ref, <<>>) end},
181 | {doc, Doc, NState};
182 | {Atts} ->
183 | %% start to receive the attachments
184 | %% we get the list of attnames for the versions of
185 | %% couchdb that don't provide the att name in the
186 | %% header.
187 | AttNames = [AttName || {AttName, _} <- Atts],
188 | NState = {Ref, fun() ->
189 | wait_mp_att(Ref, {nil, AttNames})
190 | end},
191 | {doc, Doc, NState}
192 | end;
193 | mp_mixed ->
194 | %% we are starting a multipart/mixed (probably on revs)
195 | %% continue
196 | wait_mp_doc(Ref, Buffer);
197 | mp_mixed_eof ->
198 | %% end of multipar/mixed wait for the next doc
199 | wait_mp_doc(Ref, Buffer);
200 | eof ->
201 | eof
202 | end.
203 |
204 | %% @hidden
205 | wait_mp_att(Ref, {AttName, AttNames}) ->
206 | case hackney:stream_multipart(Ref) of
207 | {headers, Headers} ->
208 | %% old couchdb api doesn't give the content-disposition
209 | %% header so we have to use the list of att names given in
210 | %% the doc. Hopefully the erlang parser keeps it in order.
211 | case hackney_headers:get_value(<<"content-disposition">>,
212 | hackney_headers:new(Headers)) of
213 | undefined ->
214 | [Name | Rest] = AttNames,
215 | NState = {Ref, fun() ->
216 | wait_mp_att(Ref, {Name, Rest})
217 | end},
218 | {att, Name, NState};
219 | CDisp ->
220 | {_, Props} = content_disposition(CDisp),
221 | Name = proplists:get_value(<<"filename">>, Props),
222 | [_ | Rest] = AttNames,
223 | NState = {Ref, fun() ->
224 | wait_mp_att(Ref, {Name, Rest})
225 | end},
226 | {att, Name, NState}
227 | end;
228 | {body, Data} ->
229 | %% return the attachment par
230 | NState = {Ref, fun() -> wait_mp_att(Ref, {AttName, AttNames}) end},
231 | {att_body, AttName, Data, NState};
232 | end_of_part ->
233 | %% wait for the next attachment
234 | NState = {Ref, fun() -> wait_mp_att(Ref, {nil, AttNames}) end},
235 | {att_eof, AttName, NState};
236 | mp_mixed_eof ->
237 | %% wait for the next doc
238 | wait_mp_doc(Ref, <<>>);
239 | eof ->
240 | %% we are done with the multipart request
241 | eof
242 | end.
243 |
244 | %% @hidden
245 | content_disposition(Data) ->
246 | hackney_bstr:token_ci(Data, fun
247 | (_Rest, <<>>) ->
248 | {error, badarg};
249 | (Rest, Disposition) ->
250 | hackney_bstr:params(Rest, fun
251 | (<<>>, Params) -> {Disposition, Params};
252 | (_Rest2, _) -> {error, badarg}
253 | end)
254 | end).
255 |
256 | %% @hidden
257 | len_doc_to_mp_stream(Atts, Boundary, {Props}) ->
258 | {AttsSize, Stubs} = lists:foldl(fun(Att, {AccSize, AccAtts}) ->
259 | {AttLen, Name, Type, Encoding, _Msg} = att_info(Att),
260 | AccSize1 = AccSize +
261 | 4 + %% \r\n\r\n
262 | AttLen +
263 | byte_size(hackney_bstr:to_binary(AttLen)) +
264 | 4 + %% "\r\n--"
265 | byte_size(Boundary) +
266 | byte_size(Name) +
267 | byte_size(Type) +
268 | byte_size(<<"\r\nContent-Disposition: attachment; filename=\"\"">> ) +
269 | byte_size(<<"\r\nContent-Type: ">>) +
270 | byte_size(<<"\r\nContent-Length: ">>) +
271 | case Encoding of
272 | <<"identity">> ->
273 | 0;
274 | _ ->
275 | byte_size(Encoding) +
276 | byte_size(<<"\r\nContent-Encoding: ">>)
277 | end,
278 | AccAtts1 = [{Name, {[{<<"content_type">>, Type},
279 | {<<"length">>, AttLen},
280 | {<<"follows">>, true},
281 | {<<"encoding">>, Encoding}]}}
282 | | AccAtts],
283 | {AccSize1, AccAtts1}
284 | end, {0, []}, Atts),
285 |
286 | Doc1 = case couchbeam_util:get_value(<<"_attachments">>, Props) of
287 | undefined ->
288 | {Props ++ [{<<"_attachments">>, {Stubs}}]};
289 | {OldAtts} ->
290 | %% remove updated attachments from the old list of
291 | %% attachments
292 | OldAtts1 = lists:foldl(fun({Name, AttProps}, Acc) ->
293 | case couchbeam_util:get_value(Name, Stubs) of
294 | undefined ->
295 | [{Name, AttProps} | Acc];
296 | _ ->
297 | Acc
298 | end
299 | end, [], OldAtts),
300 | %% update the list of the attachnebts with the attachments
301 | %% that will be sent in the multipart
302 | FinalAtts = lists:reverse(OldAtts1) ++ Stubs,
303 | {lists:keyreplace(<<"_attachments">>, 1, Props,
304 | {<<"_attachments">>, {FinalAtts}})}
305 | end,
306 |
307 | %% eencode the doc
308 | JsonDoc = couchbeam_ejson:encode(Doc1),
309 |
310 | %% calculate the final size with the doc part
311 | FinalSize = 2 + % "--"
312 | byte_size(Boundary) +
313 | 36 + % "\r\ncontent-type: application/json\r\n\r\n"
314 | byte_size(JsonDoc) +
315 | 4 + % "\r\n--"
316 | byte_size(Boundary) +
317 | + AttsSize +
318 | 2, % "--"
319 |
320 | {FinalSize, JsonDoc, Doc1}.
321 |
322 | %% @hidden
323 | send_mp_doc(Atts, Ref, Boundary, JsonDoc, Doc) ->
324 | %% send the doc
325 | DocParts = [<<"--", Boundary/binary >>,
326 | <<"Content-Type: application/json">>,
327 | <<>>, JsonDoc, <<>>],
328 | DocBin = hackney_bstr:join(DocParts, <<"\r\n">>),
329 | case hackney:send_body(Ref, DocBin) of
330 | ok ->
331 | send_mp_doc_atts(Atts, Ref, Doc, Boundary);
332 | Error ->
333 | Error
334 | end.
335 |
336 | %% @hidden
337 | send_mp_doc_atts([], Ref, Doc, Boundary) ->
338 | %% no more attachments, send the final boundary (eof)
339 | case hackney:send_body(Ref, <<"\r\n--", Boundary/binary, "--" >>) of
340 | ok ->
341 | %% collect the response.
342 | mp_doc_reply(Ref, Doc);
343 | Error ->
344 | Error
345 | end;
346 |
347 | send_mp_doc_atts([Att | Rest], Ref, Doc, Boundary) ->
348 | {AttLen, Name, Type, Encoding, Msg} = att_info(Att),
349 | BinAttLen = hackney_bstr:to_binary(AttLen),
350 | AttHeadersParts = [<<"--", Boundary/binary >>,
351 | <<"Content-Disposition: attachment; filename=\"", Name/binary, "\"" >>,
352 | <<"Content-Type: ", Type/binary >>,
353 | <<"Content-Length: ", BinAttLen/binary >>],
354 |
355 | AttHeadersParts1 = AttHeadersParts ++
356 | case Encoding of
357 | <<"identity">> ->
358 | [<<>>, <<>>];
359 | _ ->
360 | [<<"Content-Encoding: ", Encoding/binary >>, <<>>,
361 | <<>>]
362 | end,
363 | AttHeadersBin = hackney_bstr:join(AttHeadersParts1, <<"\r\n">>),
364 |
365 | %% first send the att headers
366 | case hackney:send_body(Ref, AttHeadersBin) of
367 | ok ->
368 | %% send the attachment by itself
369 | case hackney:send_body(Ref, Msg) of
370 | ok ->
371 | %% everything is OK continue to the next attachment
372 | send_mp_doc_atts(Rest, Ref, Doc, Boundary);
373 | Error ->
374 | Error
375 | end;
376 | Error ->
377 | Error
378 | end.
379 |
380 | %% @hidden
381 | mp_doc_reply(Ref, Doc) ->
382 | Resp = hackney:start_response(Ref),
383 | case couchbeam_httpc:db_resp(Resp, [200, 201]) of
384 | {ok, _, _, Ref} ->
385 | {JsonProp} = couchbeam_httpc:json_body(Ref),
386 | NewRev = couchbeam_util:get_value(<<"rev">>, JsonProp),
387 | NewDocId = couchbeam_util:get_value(<<"id">>, JsonProp),
388 | %% set the new doc ID
389 | Doc1 = couchbeam_doc:set_value(<<"_id">>, NewDocId, Doc),
390 | %% set the new rev
391 | FinalDoc = couchbeam_doc:set_value(<<"_rev">>, NewRev, Doc1),
392 | %% return the updated document
393 | {ok, FinalDoc};
394 | Error ->
395 | Error
396 | end.
397 |
398 | %% @hidden
399 | att_info({Name, {file, Path}=Msg}) ->
400 | CType = mimerl:filename(hackney_bstr:to_binary(Name)),
401 | Len = filelib:file_size(Path),
402 | {Len, Name, CType, <<"identity">>, Msg};
403 | att_info({Name, Bin}) when is_list(Bin) ->
404 | att_info({Name, iolist_to_binary(Bin)});
405 | att_info({Name, Bin}) when is_binary(Bin) ->
406 | CType = mimerl:filename(hackney_bstr:to_binary(Name)),
407 | {byte_size(Bin), Name, CType, <<"identity">>, Bin};
408 | att_info({Name, {file, Path}=Msg, Encoding}) ->
409 | CType = mimerl:filename(hackney_bstr:to_binary(Name)),
410 | Len = filelib:file_size(Path),
411 | {Len, Name, CType, Encoding, Msg};
412 | att_info({Name, {Fun, _Acc0}=Msg, Len}) when is_function(Fun) ->
413 | {Len, Name, <<"application/octet-stream">>, <<"identity">>, Msg};
414 | att_info({Name, Fun, Len}) when is_function(Fun) ->
415 | {Len, Name, <<"application/octet-stream">>, <<"identity">>, Fun};
416 | att_info({Name, Bin, Encoding}) when is_binary(Bin) ->
417 | CType = mimerl:filename(hackney_bstr:to_binary(Name)),
418 | {byte_size(Bin), Name, CType, Encoding, Bin};
419 | att_info({Name, {Fun, _Acc0}=Msg, Len, Encoding}) when is_function(Fun) ->
420 | {Len, Name, <<"application/octet-stream">>, Encoding, Msg};
421 | att_info({Name, Fun, Len, Encoding}) when is_function(Fun) ->
422 | {Len, Name, <<"application/octet-stream">>, Encoding, Fun};
423 | att_info({Name, Bin, CType, Encoding}) when is_binary(Bin) ->
424 | {byte_size(Bin), Name, CType, Encoding, Bin};
425 | att_info({Name, {Fun, _Acc0}=Msg, Len, CType, Encoding})
426 | when is_function(Fun) ->
427 | {Len, Name, CType, Encoding, Msg};
428 | att_info({Name, Fun, Len, CType, Encoding}) when is_function(Fun) ->
429 | {Len, Name, CType, Encoding, Fun}.
430 |
--------------------------------------------------------------------------------
/src/couchbeam_sup.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_sup).
7 | -author('Benoît Chesneau ').
8 | -behaviour(supervisor).
9 |
10 | -export([start_link/0, init/1]).
11 |
12 | -define(SERVER, ?MODULE).
13 |
14 |
15 | start_link() ->
16 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
17 |
18 | init([]) ->
19 | %% gen_server to cache UUIDs generated by the couchdb node.
20 | Uuids = {couchbeam_uuids,
21 | {couchbeam_uuids, start_link, []},
22 | permanent,2000,worker, [couchbeam_uuids]},
23 |
24 | %% view stream supervisor
25 | ViewSup = {couchbeam_view_sup,
26 | {couchbeam_view_sup, start_link, []},
27 | permanent, 2000, supervisor, [couchbeam_view_sup]},
28 |
29 | %% changes stream supervisor
30 | ChangesSup = {couchbeam_changes_sup,
31 | {couchbeam_changes_sup, start_link, []},
32 | permanent, 2000, supervisor, [couchbeam_changes_sup]},
33 |
34 | {ok, {{one_for_one, 10, 3600}, [Uuids, ViewSup, ChangesSup]}}.
35 |
--------------------------------------------------------------------------------
/src/couchbeam_util.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_util).
7 |
8 | -include_lib("hackney/include/hackney.hrl").
9 | -include_lib("hackney/include/hackney_lib.hrl").
10 |
11 | -export([dbname/1]).
12 | -export([encode_docid/1, encode_att_name/1]).
13 | -export([parse_options/1, parse_options/2]).
14 | -export([to_list/1, to_binary/1, to_integer/1, to_atom/1]).
15 | -export([binary_env/2]).
16 | -export([encode_query/1, encode_query_value/2]).
17 | -export([oauth_header/3]).
18 | -export([propmerge/3, propmerge1/2]).
19 | -export([get_value/2, get_value/3]).
20 | -export([deprecated/3, shutdown_sync/1]).
21 | -export([start_app_deps/1, get_app_env/2]).
22 | -export([encode_docid1/1, encode_docid_noop/1]).
23 | -export([force_param/3]).
24 | -export([proxy_token/2, proxy_header/3]).
25 |
26 | -define(PROXY_AUTH_HEADERS,[
27 | {username,<<"X-Auth-CouchDB-UserName">>},
28 | {roles,<<"X-Auth-CouchDB-Roles">>},
29 | {token,<<"X-Auth-CouchDB-Token">>}]).
30 |
31 | -define(ENCODE_DOCID_FUNC, encode_docid1).
32 |
33 | dbname(DbName) when is_list(DbName) ->
34 | list_to_binary(DbName);
35 | dbname(DbName) when is_binary(DbName) ->
36 | DbName;
37 | dbname(DbName) ->
38 | erlang:error({illegal_database_name, DbName}).
39 |
40 | encode_att_name(Name) when is_binary(Name) ->
41 | encode_att_name(xmerl_ucs:from_utf8(Name));
42 | encode_att_name(Name) ->
43 | Parts = lists:foldl(fun(P, Att) ->
44 | [xmerl_ucs:to_utf8(P)|Att]
45 | end, [], string:tokens(Name, "/")),
46 | ?MODULE:to_binary(lists:flatten(Parts)).
47 |
48 | encode_docid(DocId) when is_list(DocId) ->
49 | encode_docid(list_to_binary(DocId));
50 | encode_docid(DocId)->
51 | ?ENCODE_DOCID_FUNC(DocId).
52 |
53 | encode_docid1(DocId) ->
54 | case DocId of
55 | << "_design/", Rest/binary >> ->
56 | Rest1 = hackney_url:urlencode(Rest, [noplus]),
57 | <<"_design/", Rest1/binary >>;
58 | _ ->
59 | hackney_url:urlencode(DocId, [noplus])
60 | end.
61 |
62 | encode_docid_noop(DocId) ->
63 | DocId.
64 |
65 | %% @doc Encode needed value of Query proplists in json
66 | encode_query([]) ->
67 | [];
68 | encode_query(QSL) when is_list(QSL) ->
69 | lists:foldl(fun({K, V}, Acc) ->
70 | V1 = encode_query_value(K, V),
71 | [{K, V1}|Acc]
72 | end, [], QSL);
73 | encode_query(QSL) ->
74 | QSL.
75 |
76 | %% @doc Encode value in JSON if needed depending on the key
77 | encode_query_value(K, V) when is_atom(K) ->
78 | encode_query_value(atom_to_list(K), V);
79 | encode_query_value(K, V) when is_binary(K) ->
80 | encode_query_value(binary_to_list(K), V);
81 | encode_query_value(_K, V) -> V.
82 |
83 | % build oauth header
84 | oauth_header(Url, Action, OauthProps) when is_binary(Url) ->
85 | oauth_header(binary_to_list(Url),Action, OauthProps);
86 | oauth_header(Url, Action, OauthProps) ->
87 | #hackney_url{qs=QS} = hackney_url:parse_url(Url),
88 | QSL = [{binary_to_list(K), binary_to_list(V)} || {K,V} <-
89 | hackney_url:parse_qs(QS)],
90 |
91 | % get oauth paramerers
92 | ConsumerKey = to_list(get_value(consumer_key, OauthProps)),
93 | Token = to_list(get_value(token, OauthProps)),
94 | TokenSecret = to_list(get_value(token_secret, OauthProps)),
95 | ConsumerSecret = to_list(get_value(consumer_secret, OauthProps)),
96 | SignatureMethodStr = to_list(get_value(signature_method,
97 | OauthProps, "HMAC-SHA1")),
98 |
99 | SignatureMethodAtom = case SignatureMethodStr of
100 | "PLAINTEXT" ->
101 | plaintext;
102 | "HMAC-SHA1" ->
103 | hmac_sha1;
104 | "RSA-SHA1" ->
105 | rsa_sha1
106 | end,
107 | Consumer = {ConsumerKey, ConsumerSecret, SignatureMethodAtom},
108 | Method = case Action of
109 | delete -> "DELETE";
110 | get -> "GET";
111 | post -> "POST";
112 | put -> "PUT";
113 | head -> "HEAD"
114 | end,
115 | Params = oauth:sign(Method, Url, QSL, Consumer, Token, TokenSecret) -- QSL,
116 |
117 | Realm = "OAuth " ++ oauth:header_params_encode(Params),
118 | {<<"Authorization">>, list_to_binary(Realm)}.
119 |
120 |
121 | %% @doc merge 2 proplists. All the Key - Value pairs from both proplists
122 | %% are included in the new proplists. If a key occurs in both dictionaries
123 | %% then Fun is called with the key and both values to return a new
124 | %% value. This a wreapper around dict:merge
125 | propmerge(F, L1, L2) ->
126 | dict:to_list(dict:merge(F, dict:from_list(L1), dict:from_list(L2))).
127 |
128 | %% @doc Update a proplist with values of the second. In case the same
129 | %% key is in 2 proplists, the value from the first are kept.
130 | propmerge1(L1, L2) ->
131 | propmerge(fun(_, V1, _) -> V1 end, L1, L2).
132 |
133 | %% @doc replace a value in a proplist
134 | force_param(Key, Value, Options) ->
135 | case couchbeam_util:get_value(Key, Options) of
136 | undefined ->
137 | [{Key, Value} | Options];
138 | _ ->
139 | lists:keystore(Key, 1, Options, {Key, Value})
140 | end.
141 |
142 | %% @doc emulate proplists:get_value/2,3 but use faster lists:keyfind/3
143 | -spec get_value(Key :: term(), Prop :: [term()]) -> term().
144 | get_value(Key, Prop) ->
145 | get_value(Key, Prop, undefined).
146 |
147 | -spec get_value(Key :: term(), Prop :: [term()], Default :: term()) -> term().
148 | get_value(Key, Prop, Default) ->
149 | case lists:keyfind(Key, 1, Prop) of
150 | false ->
151 | case lists:member(Key, Prop) of
152 | true -> true;
153 | false -> Default
154 | end;
155 | {Key, V} -> % only return V if a two-tuple is found
156 | V;
157 | Other when is_tuple(Other) -> % otherwise return the default
158 | Default
159 | end.
160 |
161 | %% @doc make view options a list
162 | parse_options(Options) ->
163 | parse_options(Options, []).
164 |
165 | parse_options([], Acc) ->
166 | Acc;
167 | parse_options([V|Rest], Acc) when is_atom(V) ->
168 | parse_options(Rest, [{atom_to_list(V), true}|Acc]);
169 | parse_options([{K,V}|Rest], Acc) when is_list(K) ->
170 | parse_options(Rest, [{K,V}|Acc]);
171 | parse_options([{K,V}|Rest], Acc) when is_binary(K) ->
172 | parse_options(Rest, [{binary_to_list(K),V}|Acc]);
173 | parse_options([{K,V}|Rest], Acc) when is_atom(K) ->
174 | parse_options(Rest, [{atom_to_list(K),V}|Acc]);
175 | parse_options(_,_) ->
176 | fail.
177 |
178 | to_binary(V) when is_binary(V) ->
179 | V;
180 | to_binary(V) when is_list(V) ->
181 | try
182 | list_to_binary(V)
183 | catch
184 | _ ->
185 | list_to_binary(io_lib:format("~p", [V]))
186 | end;
187 | to_binary(V) when is_atom(V) ->
188 | list_to_binary(atom_to_list(V));
189 | to_binary(V) ->
190 | V.
191 |
192 | to_integer(V) when is_integer(V) ->
193 | V;
194 | to_integer(V) when is_list(V) ->
195 | erlang:list_to_integer(V);
196 | to_integer(V) when is_binary(V) ->
197 | erlang:list_to_integer(binary_to_list(V)).
198 |
199 | to_list(V) when is_list(V) ->
200 | V;
201 | to_list(V) when is_binary(V) ->
202 | binary_to_list(V);
203 | to_list(V) when is_atom(V) ->
204 | atom_to_list(V);
205 | to_list(V) ->
206 | V.
207 |
208 | to_atom(V) when is_atom(V) ->
209 | V;
210 | to_atom(V) when is_list(V) ->
211 | list_to_atom(V);
212 | to_atom(V) when is_binary(V) ->
213 | list_to_atom(binary_to_list(V));
214 | to_atom(V) ->
215 | list_to_atom(lists:flatten(io_lib:format("~p", [V]))).
216 |
217 |
218 | binary_env(Key, Default) ->
219 | Value = os:getenv(Key, Default),
220 | to_binary(Value).
221 |
222 | deprecated(Old, New, When) ->
223 | io:format(
224 | <<
225 | "WARNING: function deprecated~n"
226 | "Function '~p' has been deprecated~n"
227 | "in favor of '~p'.~n"
228 | "'~p' will be removed ~s.~n~n"
229 | >>, [Old, New, Old, When]).
230 |
231 | shutdown_sync(Pid) when not is_pid(Pid)->
232 | ok;
233 | shutdown_sync(Pid) ->
234 | MRef = erlang:monitor(process, Pid),
235 | try
236 | catch unlink(Pid),
237 | catch exit(Pid, shutdown),
238 | receive
239 | {'DOWN', MRef, _, _, _} ->
240 | ok
241 | end
242 | after
243 | erlang:demonitor(MRef, [flush])
244 | end.
245 |
246 | %% @spec start_app_deps(App :: atom()) -> ok
247 | %% @doc Start depedent applications of App.
248 | start_app_deps(App) ->
249 | {ok, DepApps} = application:get_key(App, applications),
250 | [ensure_started(A) || A <- DepApps],
251 | ok.
252 |
253 | %% @spec ensure_started(Application :: atom()) -> ok
254 | %% @doc Start the named application if not already started.
255 | ensure_started(App) ->
256 | case application:start(App) of
257 | ok ->
258 | ok;
259 | {error, {already_started, App}} ->
260 | ok
261 | end.
262 |
263 | get_app_env(Env, Default) ->
264 | case application:get_env(couchbeam, Env) of
265 | {ok, Val} -> Val;
266 | undefined -> Default
267 | end.
268 |
269 | proxy_header(UserName,Roles,Secret) ->
270 | proxy_header(UserName,Roles,Secret,?PROXY_AUTH_HEADERS).
271 |
272 | proxy_header(UserName,Roles,Secret,HeaderNames) ->
273 | proxy_header_token(UserName,Roles,proxy_token(Secret,UserName),HeaderNames).
274 |
275 | proxy_header_token(UserName,Roles,Token,L) ->
276 | [
277 | {hgv(username,L), UserName},
278 | {hgv(roles,L), Roles},
279 | {hgv(token,L), Token}
280 | ].
281 |
282 | hgv(N,L) ->
283 | get_value(N,L,get_value(N,?PROXY_AUTH_HEADERS)).
284 |
285 | proxy_token(Secret,UserName) ->
286 | hackney_bstr:to_hex(hmac(sha, Secret, UserName)).
287 |
288 | -ifdef(USE_CRYPTO_MAC).
289 | hmac(Alg, Key, Data) ->
290 | crypto:mac(hmac, Alg, Key, Data).
291 | -else.
292 | hmac(Alg, Key, Data) ->
293 | crypto:hmac(Alg, Key, Data).
294 | -endif.
295 |
--------------------------------------------------------------------------------
/src/couchbeam_uuids.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_uuids).
7 | -behaviour(gen_server).
8 |
9 | -export([random/0,
10 | utc_random/0]).
11 |
12 | -export([start_link/0]).
13 | -export([get_uuids/2]).
14 |
15 | %% gen_server callbacks
16 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
17 | terminate/2, code_change/3]).
18 |
19 |
20 | -include("couchbeam.hrl").
21 |
22 | -define(START_RETRY, 200).
23 | -define(MAX_RETRY, 10000).
24 |
25 | -record(state, {}).
26 |
27 | %% @doc return a random uuid
28 | -spec random() -> binary().
29 | random() ->
30 | list_to_binary(hackney_bstr:to_hex(crypto:strong_rand_bytes(16))).
31 |
32 | %% @doc return a random uuid based on time
33 | -spec utc_random() -> binary().
34 | utc_random() ->
35 | utc_suffix(hackney_bstr:to_hex(crypto:strong_rand_bytes(9))).
36 |
37 | %% @doc Get a list of uuids from the server
38 | %% @spec get_uuids(server(), integer()) -> lists()
39 | get_uuids(Server, Count) ->
40 | gen_server:call(?MODULE, {get_uuids, Server, Count}, infinity).
41 |
42 | %%--------------------------------------------------------------------
43 | %% Function: start_link/0
44 | %% Description: Starts the server
45 | %%--------------------------------------------------------------------
46 | %% @doc Starts the couchbeam process linked to the calling process. Usually
47 | %% invoked by the supervisor couchbeam_sup
48 | %% @spec start_link() -> {ok, pid()}
49 | start_link() ->
50 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
51 |
52 | %%---------------------------------------------------------------------------
53 | %% gen_server callbacks
54 | %%---------------------------------------------------------------------------
55 | %% @private
56 |
57 | init(_) ->
58 | process_flag(trap_exit, true),
59 | ets:new(couchbeam_uuids, [named_table, public, {keypos, 2}]),
60 | {ok, #state{}}.
61 |
62 | handle_call({get_uuids, #server{url=Url}=Server, Count},
63 | _From, State) ->
64 | {ok, Uuids} = do_get_uuids(Server, Count, [],
65 | ets:lookup(couchbeam_uuids, Url)),
66 | {reply, Uuids, State}.
67 |
68 |
69 | handle_cast(_Msg, State) ->
70 | {noreply, State}.
71 |
72 |
73 | handle_info(_Info, State) ->
74 | {noreply, State}.
75 |
76 | terminate(_Reason, _State) ->
77 | ok.
78 |
79 | code_change(_OldVsn, State, _Extra) ->
80 | {ok, State}.
81 |
82 | %%--------------------------------------------------------------------
83 | %%% Internal functions
84 | %%--------------------------------------------------------------------
85 | do_get_uuids(_Server, Count, Acc, _) when length(Acc) >= Count ->
86 | {ok, Acc};
87 | do_get_uuids(Server, Count, Acc, []) ->
88 | {ok, ServerUuids} = get_new_uuids(Server),
89 | do_get_uuids(Server, Count, Acc, [ServerUuids]);
90 | do_get_uuids(Server, Count, Acc, [#server_uuids{uuids=Uuids}]) ->
91 | case Uuids of
92 | [] ->
93 | {ok, ServerUuids} = get_new_uuids(Server),
94 | do_get_uuids(Server, Count, Acc, [ServerUuids]);
95 | _ when length(Uuids) < Count ->
96 | {ok, ServerUuids} = get_new_uuids(Server, Uuids),
97 | do_get_uuids(Server, Count, Acc, [ServerUuids]);
98 | _ ->
99 | {Acc1, Uuids1} = do_get_uuids1(Acc, Uuids, Count),
100 | #server{url=Url} = Server,
101 | ServerUuids = #server_uuids{server_url=Url, uuids=Uuids1},
102 | ets:insert(couchbeam_uuids, ServerUuids),
103 | do_get_uuids(Server, Count, Acc1, [ServerUuids])
104 | end.
105 |
106 |
107 |
108 | do_get_uuids1(Acc, Uuids, 0) ->
109 | {Acc, Uuids};
110 | do_get_uuids1(Acc, [Uuid|Rest], Count) ->
111 | do_get_uuids1([Uuid|Acc], Rest, Count-1).
112 |
113 |
114 | get_new_uuids(Server) ->
115 | get_new_uuids(Server, []).
116 |
117 | get_new_uuids(Server, Acc) ->
118 | get_new_uuids(Server, ?START_RETRY, Acc).
119 |
120 | get_new_uuids(#server{url=ServerUrl, options=Opts}=Server, Backoff, Acc) ->
121 | Count = list_to_binary(integer_to_list(1000 - length(Acc))),
122 | Url = hackney_url:make_url(ServerUrl, <<"/_uuids">>, [{<<"count">>, Count}]),
123 | case couchbeam_httpc:request(get, Url, [], <<>>, Opts) of
124 | {ok, 200, _, Ref} ->
125 | {ok, Body} = hackney:body(Ref),
126 | {[{<<"uuids">>, Uuids}]} = couchbeam_ejson:decode(Body),
127 | ServerUuids = #server_uuids{server_url=ServerUrl,
128 | uuids=(Acc ++ Uuids)},
129 | ets:insert(couchbeam_uuids, ServerUuids),
130 | {ok, ServerUuids};
131 | {ok, Status, Headers, Ref} ->
132 | {ok, Body} = hackney:body(Ref),
133 | {error, {bad_response, {Status, Headers, Body}}};
134 | {error, closed} ->
135 | wait_for_retry(Server, Backoff, Acc);
136 | {error, connect_timeout} ->
137 | wait_for_retry(Server, Backoff, Acc);
138 | Error ->
139 | Error
140 | end.
141 |
142 | wait_for_retry(_S, ?MAX_RETRY, _A) ->
143 | {error, max_retry};
144 | wait_for_retry(S, B, A) ->
145 | %% increment backoff
146 | B2 = min(B bsl 1, ?MAX_RETRY),
147 | timer:sleep(B2),
148 | get_new_uuids(S, B2, A).
149 |
150 | utc_suffix(Suffix) ->
151 | Now = {_, _, Micro} = erlang:timestamp(),
152 | Nowish = calendar:now_to_universal_time(Now),
153 | Nowsecs = calendar:datetime_to_gregorian_seconds(Nowish),
154 | Then = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
155 | Prefix = io_lib:format("~14.16.0b", [(Nowsecs - Then) * 1000000 + Micro]),
156 | list_to_binary(Prefix ++ Suffix).
157 |
--------------------------------------------------------------------------------
/src/couchbeam_view_stream.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_view_stream).
7 |
8 | -export([start_link/4]).
9 |
10 | -export([init_stream/5,
11 | maybe_continue/1,
12 | system_continue/3,
13 | system_terminate/4,
14 | system_code_change/4]).
15 |
16 |
17 | -export([init/1,
18 | handle_event/2,
19 | wait_rows/2,
20 | wait_rows1/2,
21 | wait_val/2,
22 | collect_object/2,
23 | maybe_continue_decoding/1]).
24 |
25 |
26 | -include("couchbeam.hrl").
27 |
28 | -record(state, {parent,
29 | owner,
30 | req,
31 | ref,
32 | mref,
33 | client_ref=nil,
34 | decoder,
35 | async=normal}).
36 |
37 | -record(viewst, {parent,
38 | owner,
39 | ref,
40 | mref,
41 | client_ref,
42 | async=false}).
43 |
44 |
45 | -define(TIMEOUT, 10000).
46 | -define(DEFAULT_CHANGES_POLL, 5000). % we check every 5secs
47 |
48 |
49 |
50 | start_link(Owner, StreamRef, {Db, Url, Args}, StreamOptions) ->
51 | proc_lib:start_link(?MODULE, init_stream, [self(), Owner, StreamRef,
52 | {Db, Url, Args},
53 | StreamOptions]).
54 |
55 | init_stream(Parent, Owner, StreamRef, {_Db, _Url, _Args}=Req,
56 | StreamOptions) ->
57 |
58 |
59 | Async = proplists:get_value(async, StreamOptions, normal),
60 |
61 | %% monitor the process receiving the messages
62 | MRef = erlang:monitor(process, Owner),
63 |
64 | %% tell to the parent that we are ok
65 | proc_lib:init_ack(Parent, {ok, self()}),
66 |
67 | InitState = #state{parent=Parent,
68 | owner=Owner,
69 | req=Req,
70 | ref=StreamRef,
71 | mref=MRef,
72 | async=Async},
73 |
74 | %% connect to the view
75 | try
76 | case do_init_stream(Req, InitState) of
77 | {ok, State} ->
78 | %% register the stream
79 | ets:insert(couchbeam_view_streams, [{StreamRef, self()}]),
80 | %% start the loop
81 | loop(State);
82 | Error ->
83 | report_error(Error, StreamRef, Owner)
84 | end
85 | after
86 | %% Always clean up the monitor reference
87 | erlang:demonitor(MRef, [flush])
88 | end,
89 | ok.
90 |
91 | do_init_stream({#db{options=Opts}, Url, Args}, #state{mref=MRef}=State) ->
92 | %% we are doing the request asynchronously
93 | FinalOpts = [{async, once} | Opts],
94 | Reply = case Args#view_query_args.method of
95 | get ->
96 | couchbeam_httpc:request(get, Url, [], <<>>, FinalOpts);
97 | post ->
98 | Body = couchbeam_ejson:encode({[{<<"keys">>,
99 | Args#view_query_args.keys}]}),
100 | Headers = [{<<"Content-Type">>, <<"application/json">>}],
101 | couchbeam_httpc:request(post, Url, Headers, Body, FinalOpts)
102 | end,
103 |
104 | case Reply of
105 | {ok, Ref} ->
106 | Req = hackney:request_info(Ref),
107 | Mod = proplists:get_value(transport, Req),
108 | S = proplists:get_value(socket, Req),
109 | _ = Mod:controlling_process(S, self()),
110 | receive
111 | {'DOWN', MRef, _, _, _} ->
112 | %% parent exited there is no need to continue
113 | exit(normal);
114 | {hackney_response, Ref, {status, 200, _}} ->
115 | #state{parent=Parent,
116 | owner=Owner,
117 | ref=StreamRef,
118 | async=Async} = State,
119 |
120 | DecoderFun = jsx:decoder(?MODULE, [Parent, Owner,
121 | StreamRef, MRef, Ref,
122 | Async], [stream]),
123 | {ok, State#state{client_ref=Ref,
124 | decoder=DecoderFun}};
125 |
126 | {hackney_response, Ref, {status, 404, _}} ->
127 | {error, not_found};
128 | {hackney_response, Ref, {status, Status, Reason}} ->
129 | {error, {http_error, Status, Reason}};
130 | {hackney_response, Ref, {error, Reason}} ->
131 | {error, Reason}
132 | after ?TIMEOUT ->
133 | {error, timeout}
134 | end;
135 | Error ->
136 | {error, Error}
137 | end.
138 |
139 |
140 |
141 | loop(#state{owner=Owner,
142 | ref=StreamRef,
143 | mref=MRef,
144 | client_ref=ClientRef}=State) ->
145 |
146 | hackney:stream_next(ClientRef),
147 | receive
148 | {'DOWN', MRef, _, _, _} ->
149 | %% parent exited there is no need to continue
150 | exit(normal);
151 | {hackney_response, ClientRef, {headers, _Headers}} ->
152 | loop(State);
153 | {hackney_response, ClientRef, done} ->
154 | %% unregister the stream
155 | ets:delete(couchbeam_view_streams, StreamRef),
156 | %% tell to the owner that we are done and exit,
157 | Owner ! {StreamRef, done};
158 | {hackney_response, ClientRef, Data} when is_binary(Data) ->
159 | decode_data(Data, State);
160 | {hackney_response, ClientRef, Error} ->
161 | ets:delete(couchbeam_view_streams, StreamRef),
162 | %% report the error
163 | report_error(Error, StreamRef, Owner),
164 | exit(Error)
165 | end.
166 |
167 | decode_data(Data, #state{owner=Owner,
168 | ref=StreamRef,
169 | client_ref=ClientRef,
170 | decoder=DecodeFun}=State) ->
171 | try
172 | {incomplete, DecodeFun2} = DecodeFun(Data),
173 | try DecodeFun2(end_stream) of done ->
174 | %% stop the request
175 | {ok, _} = hackney:stop_async(ClientRef),
176 | %% skip the rest of the body so the socket is
177 | %% replaced in the pool
178 | catch hackney:skip_body(ClientRef),
179 | %% unregister the stream
180 | ets:delete(couchbeam_view_streams, StreamRef),
181 | %% tell to the owner that we are done and exit,
182 | Owner ! {StreamRef, done}
183 | catch error:badarg ->
184 | maybe_continue(State#state{decoder=DecodeFun2})
185 | end
186 | catch error:badarg ->
187 | maybe_close(State),
188 | exit(badarg)
189 | end.
190 |
191 | maybe_continue(#state{parent=Parent, owner=Owner, ref=Ref, mref=MRef,
192 | async=once}=State) ->
193 |
194 | receive
195 | {'DOWN', MRef, _, _, _} ->
196 | %% parent exited there is no need to continue
197 | maybe_close(State),
198 | exit(normal);
199 | {Ref, stream_next} ->
200 | loop(State);
201 | {Ref, cancel} ->
202 | maybe_close(State),
203 | %% unregister the stream
204 | ets:delete(couchbeam_view_streams, Ref),
205 | %% tell the parent we exited
206 | Owner ! {Ref, ok};
207 | {system, From, Request} ->
208 | sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
209 | {loop, State});
210 | Else ->
211 | error_logger:error_msg("Unexpected message: ~w~n", [Else]),
212 | %% unregister the stream
213 | ets:delete(couchbeam_view_streams, Ref),
214 | %% report the error
215 | report_error(Else, Ref, Owner),
216 | exit(Else)
217 | after 0 ->
218 | loop(State)
219 | end;
220 | maybe_continue(#state{parent=Parent,
221 | owner=Owner,
222 | ref=Ref,
223 | mref=MRef}=State) ->
224 | receive
225 | {'DOWN', MRef, _, _, _} ->
226 | %% parent exited there is no need to continue
227 | maybe_close(State),
228 | exit(normal);
229 | {Ref, cancel} ->
230 | maybe_close(State),
231 | %% unregister the stream
232 | ets:delete(couchbeam_view_streams, Ref),
233 | %% tell the parent we exited
234 | Owner ! {Ref, ok};
235 | {Ref, pause} ->
236 | erlang:hibernate(?MODULE, maybe_continue, [State]);
237 | {Ref, resume} ->
238 | loop(State);
239 | {system, From, Request} ->
240 | sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
241 | {loop, State});
242 | Else ->
243 | error_logger:error_msg("Unexpected message: ~w~n", [Else]),
244 | %% unregister the stream
245 | ets:delete(couchbeam_view_streams, Ref),
246 | %% report the error
247 | report_error(Else, Ref, Owner),
248 | exit(Else)
249 | after 0 ->
250 | loop(State)
251 | end.
252 |
253 | maybe_close(#state{client_ref=nil}) ->
254 | ok;
255 | maybe_close(#state{client_ref=Ref}) ->
256 | hackney:close(Ref).
257 |
258 |
259 | system_continue(_, _, {maybe_continue, State}) ->
260 | maybe_continue(State);
261 | system_continue(_, _, {loop, State}) ->
262 | loop(State).
263 |
264 | -spec system_terminate(any(), _, _, _) -> no_return().
265 | system_terminate(Reason, _, _, #state{ref=StreamRef,
266 | client_ref=ClientRef}) ->
267 | hackney:close(ClientRef),
268 | %% unregister the stream
269 | catch ets:delete(couchbeam_view_streams, StreamRef),
270 | exit(Reason).
271 |
272 | system_code_change(Misc, _, _, _) ->
273 | {ok, Misc}.
274 |
275 |
276 | %%% json decoder %%%
277 |
278 | init([Parent, Owner, StreamRef, MRef, ClientRef, Async]) ->
279 | InitialState = #viewst{parent=Parent,
280 | owner=Owner,
281 | ref=StreamRef,
282 | mref=MRef,
283 | client_ref=ClientRef,
284 | async=Async},
285 | {wait_rows, 0, [[]], InitialState}.
286 |
287 | handle_event(end_json, _) ->
288 | done;
289 | handle_event(Event, {Fun, _, _, _}=St) ->
290 | ?MODULE:Fun(Event, St).
291 |
292 |
293 |
294 | wait_rows(start_object, St) ->
295 | St;
296 | wait_rows(end_object, St) ->
297 | St;
298 | wait_rows({key, <<"rows">>}, {_, _, _, ViewSt}) ->
299 | {wait_rows1, 0, [[]], ViewSt};
300 | wait_rows({key, <<"total_rows">>}, {_, _, _, ViewSt}) ->
301 | {wait_val, 0, [[]], ViewSt};
302 | wait_rows({key, <<"offset">>}, {_, _, _, ViewSt}) ->
303 | {wait_val, 0, [[]], ViewSt}.
304 |
305 | wait_val({_, _}, {_, _, _, ViewSt}) ->
306 | {wait_rows, 0, [[]], ViewSt}.
307 |
308 | wait_rows1(start_array, {_, _, _, ViewSt}) ->
309 | {wait_rows1, 0, [[]], ViewSt};
310 | wait_rows1(start_object, {_, _, Terms, ViewSt}) ->
311 | {collect_object, 0, [[]|Terms], ViewSt};
312 | wait_rows1(end_array, {_, _, _, ViewSt}) ->
313 | {wait_rows, 0, [[]], ViewSt}.
314 |
315 |
316 | collect_object(start_object, {_, NestCount, Terms, ViewSt}) ->
317 | {collect_object, NestCount + 1, [[]|Terms], ViewSt};
318 |
319 | collect_object(end_object, {_, NestCount, [[], {key, Key}, Last|Terms],
320 | ViewSt}) ->
321 | {collect_object, NestCount - 1, [[{Key, {[{}]}}] ++ Last] ++ Terms,
322 | ViewSt};
323 |
324 | collect_object(end_object, {_, NestCount, [Object, {key, Key},
325 | Last|Terms], ViewSt}) ->
326 | {collect_object, NestCount - 1,
327 | [[{Key, {lists:reverse(Object)}}] ++ Last] ++ Terms, ViewSt};
328 |
329 | collect_object(end_object, {_, 0, [[], Last|Terms], ViewSt}) ->
330 | [[Row]] = [[{[{}]}] ++ Last] ++ Terms,
331 | send_row(Row, ViewSt);
332 |
333 | collect_object(end_object, {_, NestCount, [[], Last|Terms], ViewSt}) ->
334 | {collect_object, NestCount - 1, [[{[{}]}] ++ Last] ++ Terms, ViewSt};
335 |
336 | collect_object(end_object, {_, 0, [Object, Last|Terms], ViewSt}) ->
337 | [[Row]] = [[{lists:reverse(Object)}] ++ Last] ++ Terms,
338 | send_row(Row, ViewSt);
339 |
340 |
341 | collect_object(end_object, {_, NestCount, [Object, Last|Terms], ViewSt}) ->
342 | Acc = [[{lists:reverse(Object)}] ++ Last] ++ Terms,
343 | {collect_object, NestCount - 1, Acc, ViewSt};
344 |
345 |
346 | collect_object(start_array, {_, NestCount, Terms, ViewSt}) ->
347 | {collect_object, NestCount, [[]|Terms], ViewSt};
348 | collect_object(end_array, {_, NestCount, [List, {key, Key}, Last|Terms],
349 | ViewSt}) ->
350 | {collect_object, NestCount,
351 | [[{Key, lists:reverse(List)}] ++ Last] ++ Terms, ViewSt};
352 | collect_object(end_array, {_, NestCount, [List, Last|Terms], ViewSt}) ->
353 | {collect_object, NestCount, [[lists:reverse(List)] ++ Last] ++ Terms,
354 | ViewSt};
355 |
356 | collect_object({key, Key}, {_, NestCount, Terms, ViewSt}) ->
357 | {collect_object, NestCount, [{key, Key}] ++ Terms,
358 | ViewSt};
359 |
360 | collect_object({_, Event}, {_, NestCount, [{key, Key}, Last|Terms], ViewSt}) ->
361 | {collect_object, NestCount, [[{Key, Event}] ++ Last] ++ Terms, ViewSt};
362 | collect_object({_, Event}, {_, NestCount, [Last|Terms], ViewSt}) ->
363 | {collect_object, NestCount, [[Event] ++ Last] ++ Terms, ViewSt}.
364 |
365 | send_row(Row, #viewst{owner=Owner, ref=Ref}=ViewSt) ->
366 | Owner ! {Ref, {row, couchbeam_ejson:post_decode(Row)}},
367 | maybe_continue_decoding(ViewSt).
368 |
369 | %% eventually wait for the next call from the parent
370 | maybe_continue_decoding(#viewst{parent=Parent,
371 | owner=Owner,
372 | ref=Ref,
373 | mref=MRef,
374 | client_ref=ClientRef,
375 | async=once}=ViewSt) ->
376 | receive
377 | {'DOWN', MRef, _, _, _} ->
378 | %% parent exited there is no need to continue
379 | exit(normal);
380 | {Ref, stream_next} ->
381 | {wait_rows1, 0, [[]], ViewSt};
382 | {Ref, cancel} ->
383 | hackney:close(ClientRef),
384 | %% unregister the stream
385 | ets:delete(couchbeam_view_streams, Ref),
386 | %% tell the parent we exited
387 | Owner ! {Ref, ok},
388 | %% and exit
389 | exit(normal);
390 | {system, From, Request} ->
391 | sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
392 | {maybe_continue_decoding, ViewSt});
393 | Else ->
394 | error_logger:error_msg("Unexpected message: ~w~n", [Else]),
395 | %% unregister the stream
396 | ets:delete(couchbeam_view_streams, Ref),
397 | %% report the error
398 | report_error(Else, Ref, Owner),
399 | exit(Else)
400 | after 5000 ->
401 | erlang:hibernate(?MODULE, maybe_continue_decoding, [ViewSt])
402 | end;
403 |
404 | maybe_continue_decoding(#viewst{parent=Parent,
405 | owner=Owner,
406 | ref=Ref,
407 | mref=MRef,
408 | client_ref=ClientRef}=ViewSt) ->
409 | receive
410 | {'DOWN', MRef, _, _, _} ->
411 | %% parent exited there is no need to continue
412 | exit(normal);
413 | {Ref, cancel} ->
414 | hackney:close(ClientRef),
415 | Owner ! {Ref, ok},
416 | exit(normal);
417 | {Ref, pause} ->
418 | erlang:hibernate(?MODULE, maybe_continue_decoding, [ViewSt]);
419 | {Ref, resume} ->
420 | {wait_rows1, 0, [[]], ViewSt};
421 | {system, From, Request} ->
422 | sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
423 | {maybe_continue_decoding, ViewSt});
424 | Else ->
425 | error_logger:error_msg("Unexpected message: ~w~n", [Else]),
426 | report_error(Else, Ref, Owner),
427 | exit(Else)
428 | after 0 ->
429 | {wait_rows1, 0, [[]], ViewSt}
430 | end.
431 |
432 | report_error({error, _What}=Error, Ref, Pid) ->
433 | Pid ! {Ref, Error};
434 | report_error(What, Ref, Pid) ->
435 | Pid ! {Ref, {error, What}}.
436 |
--------------------------------------------------------------------------------
/src/couchbeam_view_sup.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | -module(couchbeam_view_sup).
7 |
8 | -behaviour(supervisor).
9 |
10 | %% API.
11 | -export([start_link/0]).
12 |
13 | %% supervisor.
14 | -export([init/1]).
15 |
16 | -define(SUPERVISOR, ?MODULE).
17 |
18 | %% API.
19 |
20 | -spec start_link() -> {ok, pid()}.
21 | start_link() ->
22 | supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
23 |
24 | %% supervisor.
25 |
26 | init([]) ->
27 |
28 | %% start table to keep async streams ref
29 | ets:new(couchbeam_view_streams, [set, public, named_table]),
30 |
31 | %% define a stream spec
32 | Stream = {couchbeam_view_stream, {couchbeam_view_stream, start_link, []},
33 | temporary, infinity, worker, [couchbeam_view_stream]},
34 |
35 | {ok, {{simple_one_for_one, 10, 3600}, [Stream]}}.
36 |
--------------------------------------------------------------------------------
/src/gen_changes.erl:
--------------------------------------------------------------------------------
1 | %%% -*- erlang -*-
2 | %%%
3 | %%% This file is part of couchbeam released under the MIT license.
4 | %%% See the NOTICE for more information.
5 |
6 | %% @doc gen_changes CouchDB continuous changes consumer behavior
7 | %% This behaviour allows you to create easily a server that consume
8 | %% Couchdb continuous changes
9 |
10 | -module(gen_changes).
11 |
12 | -include("couchbeam.hrl").
13 |
14 | -behavior(gen_server).
15 |
16 | -export([start_link/4]).
17 | -export([init/1,
18 | handle_call/3,
19 | handle_cast/2,
20 | handle_info/2,
21 | terminate/2,
22 | code_change/3]).
23 | -export([behaviour_info/1]).
24 |
25 | -export([call/2,
26 | call/3,
27 | cast/2]).
28 |
29 | -export([stop/1, get_seq/1]).
30 |
31 |
32 | behaviour_info(callbacks) ->
33 | [{init, 1},
34 | {handle_change, 2},
35 | {handle_call, 3},
36 | {handle_cast, 2},
37 | {handle_info, 2},
38 | {terminate, 2}];
39 | behaviour_info(_) ->
40 | undefined.
41 |
42 | call(Name, Request) ->
43 | gen_server:call(Name, Request).
44 |
45 | call(Name, Request, Timeout) ->
46 | gen_server:call(Name, Request, Timeout).
47 |
48 | cast(Dest, Request) ->
49 | gen_server:cast(Dest, Request).
50 |
51 | %% @doc create a gen_changes process as part of a supervision tree.
52 | %% The function should be called, directly or indirectly, by the supervisor.
53 | %% @spec start_link(Module, Db::db(), Options::changesoptions(),
54 | %% InitArgs::list()) -> term()
55 | %% changesoptions() = [changeoption()]
56 | %% changeoption() = {include_docs, string()} |
57 | %% {filter, string()} |
58 | %% {since, integer()|string()} |
59 | %% {heartbeat, string()|boolean()}
60 | start_link(Module, Db, Options, InitArgs) ->
61 | gen_server:start_link(?MODULE, [Module, Db, Options, InitArgs], []).
62 |
63 | init([Module, Db, Options, InitArgs]) ->
64 | case Module:init(InitArgs) of
65 | {ok, ModState} ->
66 | case couchbeam_changes:follow(Db, Options) of
67 | {ok, StreamRef} ->
68 | LastSeq = proplists:get_value(since, Options, 0),
69 | {ok, #gen_changes_state{stream_ref=StreamRef,
70 | mod=Module,
71 | modstate=ModState,
72 | db=Db,
73 | options=Options,
74 | last_seq=LastSeq}};
75 | {error, Error} ->
76 | Module:terminate(Error, ModState),
77 | {stop, Error}
78 | end;
79 | Error ->
80 | Error
81 | end.
82 |
83 | stop(Pid) when is_pid(Pid) ->
84 | gen_server:cast(Pid, stop).
85 |
86 | get_seq(Pid) when is_pid(Pid) ->
87 | gen_server:call(Pid, get_seq).
88 |
89 | handle_call(get_seq, _From, State=#gen_changes_state{last_seq=Seq}) ->
90 | {reply, Seq, State};
91 | handle_call(Request, From,
92 | State=#gen_changes_state{mod=Module, modstate=ModState}) ->
93 | case Module:handle_call(Request, From, ModState) of
94 | {reply, Reply, NewModState} ->
95 | {reply, Reply, State#gen_changes_state{modstate=NewModState}};
96 | {reply, Reply, NewModState, A}
97 | when A =:= hibernate orelse is_number(A) ->
98 | {reply, Reply, State#gen_changes_state{modstate=NewModState}, A};
99 | {noreply, NewModState} ->
100 | {noreply, State#gen_changes_state{modstate=NewModState}};
101 | {noreply, NewModState, A} when A =:= hibernate orelse is_number(A) ->
102 | {noreply, State#gen_changes_state{modstate=NewModState}, A};
103 | {stop, Reason, NewModState} ->
104 | {stop, Reason, State#gen_changes_state{modstate=NewModState}};
105 | {stop, Reason, Reply, NewModState} ->
106 | {stop, Reason, Reply, State#gen_changes_state{modstate=NewModState}}
107 | end.
108 |
109 | handle_cast(stop, State) ->
110 | {stop, normal, State};
111 | handle_cast(Msg, State=#gen_changes_state{mod=Module, modstate=ModState}) ->
112 | case Module:handle_cast(Msg, ModState) of
113 | {noreply, NewModState} ->
114 | {noreply, State#gen_changes_state{modstate=NewModState}};
115 | {noreply, NewModState, A} when A =:= hibernate orelse is_number(A) ->
116 | {noreply, State#gen_changes_state{modstate=NewModState}, A};
117 | {stop, Reason, NewModState} ->
118 | {stop, Reason, State#gen_changes_state{modstate=NewModState}}
119 | end.
120 |
121 |
122 | handle_info({Ref, Msg},
123 | State=#gen_changes_state{mod=Module, modstate=ModState,
124 | stream_ref=Ref}) ->
125 |
126 | State2 = case Msg of
127 | {done, LastSeq} ->
128 | State#gen_changes_state{last_seq=LastSeq};
129 | {change, Change} ->
130 | Seq = couchbeam_doc:get_value(<<"seq">>, Change),
131 | State#gen_changes_state{last_seq=Seq}
132 | end,
133 |
134 | try Module:handle_change(Msg, ModState) of
135 | {noreply, NewModState} ->
136 | {noreply, State2#gen_changes_state{modstate=NewModState}};
137 | {noreply, NewModState, A} when A =:= hibernate orelse is_number(A) ->
138 | {noreply, State2#gen_changes_state{modstate=NewModState}, A};
139 | {stop, Reason, NewModState} ->
140 | {stop, Reason, State2#gen_changes_state{modstate=NewModState}};
141 | Other ->
142 | error_logger:error_msg("~p:handle_change/2 returned unexpected value: ~p~n", [Module, Other]),
143 | {stop, {bad_return_value, Other}, State2}
144 | catch
145 | Class:Reason:Stacktrace ->
146 | error_logger:error_msg("~p:handle_change/2 crashed: ~p:~p~n~p~n", [Module, Class, Reason, Stacktrace]),
147 | {stop, {handle_change_crashed, {Class, Reason}}, State2}
148 | end;
149 |
150 |
151 | handle_info({Ref, {error, Error}},
152 | State=#gen_changes_state{stream_ref=Ref, last_seq=LastSeq}) ->
153 | handle_info({error, [Error, {last_seq, LastSeq}]}, State);
154 |
155 | handle_info(Info, State=#gen_changes_state{mod=Module, modstate=ModState}) ->
156 | case Module:handle_info(Info, ModState) of
157 | {noreply, NewModState} ->
158 | {noreply, State#gen_changes_state{modstate=NewModState}};
159 | {noreply, NewModState, A} when A =:= hibernate orelse is_number(A) ->
160 | {noreply, State#gen_changes_state{modstate=NewModState}, A};
161 | {stop, Reason, NewModState} ->
162 | {stop, Reason, State#gen_changes_state{modstate=NewModState}}
163 | end.
164 |
165 | code_change(_OldVersion, State, _Extra) ->
166 | %% TODO: support code changes?
167 | {ok, State}.
168 |
169 | terminate(Reason, #gen_changes_state{stream_ref=Ref,
170 | mod=Module, modstate=ModState}) ->
171 | Module:terminate(Reason, ModState),
172 | couchbeam_changes:cancel_stream(Ref),
173 | ok.
174 |
--------------------------------------------------------------------------------
/support/1M:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benoitc/couchbeam/1942999129a878ed8ea908aada32ae54c4bdcc9f/support/1M
--------------------------------------------------------------------------------