├── .gitignore ├── LICENSE ├── Makefile ├── README ├── README.md ├── doc ├── edoc-info ├── erlang.png ├── kvdb.md ├── kvdb_conf.md ├── kvdb_cron.md ├── kvdb_cron_parse.md ├── kvdb_cron_scan.md ├── kvdb_ets_dumper.md ├── kvdb_export.md ├── kvdb_paired.md ├── kvdb_proxy_sup.md ├── kvdb_queue.md ├── kvdb_riak.md ├── kvdb_riak_mapred.md ├── kvdb_riak_proxy.md ├── kvdb_schema.md ├── kvdb_schema_events.md ├── overview.edoc └── stylesheet.css ├── ebin └── .gitignore ├── include ├── kvdb.hrl ├── kvdb_conf.hrl └── riak_kv_wm_raw.hrl ├── priv ├── all_apps.config ├── backends.script └── excluded_apps.script ├── rebar.config ├── rebar.config.script ├── src ├── kvdb.app.src ├── kvdb.erl ├── kvdb_app.erl ├── kvdb_conf.erl ├── kvdb_cron.erl ├── kvdb_cron_parse.yrl ├── kvdb_cron_scan.xrl ├── kvdb_cron_sup.erl ├── kvdb_db_sup.erl ├── kvdb_diff.erl ├── kvdb_direct.erl ├── kvdb_ets.erl ├── kvdb_ets_dumper.erl ├── kvdb_export.erl ├── kvdb_leveldb.erl ├── kvdb_lib.erl ├── kvdb_log.erl ├── kvdb_meta.erl ├── kvdb_paired.erl ├── kvdb_proxy_sup.erl ├── kvdb_queue.erl ├── kvdb_riak.erl ├── kvdb_riak_mapred.erl ├── kvdb_riak_proxy.erl ├── kvdb_schema.erl ├── kvdb_schema_events.erl ├── kvdb_server.erl ├── kvdb_server_sup.erl ├── kvdb_sqlite3.erl ├── kvdb_sup.erl ├── kvdb_trans.erl └── log.hrl ├── test.config ├── test ├── feuerlabs_eunit.hrl ├── kvdb_conf_tests.erl ├── kvdb_lib_proper.erl ├── kvdb_tests.erl ├── kvdb_trans_tests.erl └── kvdb_usb_ets.erl └── tetrapak └── config.ini /.gitignore: -------------------------------------------------------------------------------- 1 | ebin 2 | src/kvdb_cron_parse.erl 3 | src/kvdb_cron_scan.erl 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test doc compile trans_test basic_test 2 | 3 | all: compile 4 | 5 | compile: 6 | rebar compile 7 | 8 | doc: 9 | rebar doc 10 | 11 | test: compile 12 | rebar eunit skip_deps=true 13 | 14 | trans_test: compile 15 | rebar eunit skip_deps=true suite=kvdb_trans 16 | 17 | conf_test: compile 18 | rebar eunit skip_deps=true suite=kvdb_conf 19 | 20 | basic_test: compile 21 | rebar eunit skip_deps=true suite=kvdb -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Feuerlabs/kvdb/c8c71bf5422a8fa2e58c608c65629be7a58e27f3/README -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # The kvdb application # 4 | 5 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)), Tony Rogvall ([`tony@rogvall.se`](mailto:tony@rogvall.se)). 6 | 7 | KVDB - Database Management System for Connected Device Management 8 | 9 | KVDB was initially designed to support the requirements of the Exosense 10 | system for managing Connected Devices, but is essentially a general-purpose 11 | DBMS. A requirement was that it should be useable both in an embedded device 12 | and on the device management server. To this end, KVDB supports a number of 13 | different storage backends, each with different characteristics. 14 | 15 | Features: 16 | 17 | * Ordered-set semantics 18 | 19 | * Transaction semantics 20 | 21 | * Persistent queues 22 | 23 | * CRON-like persistent timers 24 | 25 | * Extensible indexing 26 | 27 | * Storage backend plugins 28 | 29 | * Schema callback behavior 30 | 31 | See also the [Wiki](http://github.com/Feuerlabs/kvdb/wiki) for further description and examples. 32 | 33 | 34 | ## Modules ## 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
kvdb
kvdb_conf
kvdb_cron
kvdb_cron_parse
kvdb_cron_scan
kvdb_ets_dumper
kvdb_export
kvdb_paired
kvdb_proxy_sup
kvdb_queue
kvdb_riak
kvdb_riak_mapred
kvdb_riak_proxy
kvdb_schema
kvdb_schema_events
53 | 54 | -------------------------------------------------------------------------------- /doc/edoc-info: -------------------------------------------------------------------------------- 1 | {application,kvdb}. 2 | {packages,[]}. 3 | {modules,[kvdb,kvdb_conf,kvdb_cron,kvdb_cron_parse,kvdb_cron_scan, 4 | kvdb_ets_dumper,kvdb_export,kvdb_paired,kvdb_proxy_sup,kvdb_queue, 5 | kvdb_riak,kvdb_riak_mapred,kvdb_riak_proxy,kvdb_schema, 6 | kvdb_schema_events]}. 7 | -------------------------------------------------------------------------------- /doc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Feuerlabs/kvdb/c8c71bf5422a8fa2e58c608c65629be7a58e27f3/doc/erlang.png -------------------------------------------------------------------------------- /doc/kvdb_cron.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_cron # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | Persistent timers for KVDB. 10 | __Behaviours:__ [`gen_server`](gen_server.md). 11 | 12 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 13 | 14 | 15 | ## Description ## 16 | 17 | 18 | 19 | Grammar for timer expressions: 20 | 21 | 22 | 23 | 24 | ``` 25 | ::= "{" 62 | 63 | 64 | ## Function Index ## 65 | 66 | 67 |
add/7
add/8
code_change/3
create_crontab/2
delete/3
delete/4
delete_abs/3
handle_call/3
handle_cast/2
handle_info/2
init/1
init_meta/1
set_timers/1
start_link/2
terminate/2
testf/0
68 | 69 | 70 | 71 | 72 | ## Function Details ## 73 | 74 | 75 | 76 | ### add/7 ### 77 | 78 | 79 |

 80 | add(Db, Tab, When::timespec(), Key::any(), M::atom(), F::atom(), As::[any()]) -> ok | error()
 81 | 
82 | 83 |

84 | 85 | 86 | 87 | 88 | 89 | ### add/8 ### 90 | 91 | `add(Db, Tab, Q, When, Options, M, F, As) -> any()` 92 | 93 | 94 | 95 | 96 | ### code_change/3 ### 97 | 98 | `code_change(FromVsn, St, Extra) -> any()` 99 | 100 | 101 | 102 | 103 | ### create_crontab/2 ### 104 | 105 | `create_crontab(Db, Tab) -> any()` 106 | 107 | 108 | 109 | 110 | ### delete/3 ### 111 | 112 | `delete(Db, Tab, Key) -> any()` 113 | 114 | 115 | 116 | 117 | ### delete/4 ### 118 | 119 | `delete(Db, Tab, Q, Key) -> any()` 120 | 121 | 122 | 123 | 124 | ### delete_abs/3 ### 125 | 126 | `delete_abs(Db, Tab, QK) -> any()` 127 | 128 | 129 | 130 | 131 | ### handle_call/3 ### 132 | 133 | `handle_call(Req, From, St) -> any()` 134 | 135 | 136 | 137 | 138 | ### handle_cast/2 ### 139 | 140 | `handle_cast(Msg, St) -> any()` 141 | 142 | 143 | 144 | 145 | ### handle_info/2 ### 146 | 147 | `handle_info(Msg, St) -> any()` 148 | 149 | 150 | 151 | 152 | ### init/1 ### 153 | 154 | `init(X1) -> any()` 155 | 156 | 157 | 158 | 159 | ### init_meta/1 ### 160 | 161 | `init_meta(Db) -> any()` 162 | 163 | 164 | 165 | 166 | ### set_timers/1 ### 167 | 168 | `set_timers(Db) -> any()` 169 | 170 | 171 | 172 | 173 | ### start_link/2 ### 174 | 175 | `start_link(Db, Options) -> any()` 176 | 177 | 178 | 179 | 180 | ### terminate/2 ### 181 | 182 | `terminate(Reason, St) -> any()` 183 | 184 | 185 | 186 | 187 | ### testf/0 ### 188 | 189 | `testf() -> any()` 190 | 191 | 192 | -------------------------------------------------------------------------------- /doc/kvdb_cron_parse.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_cron_parse # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### yecc_ret() ### 18 | 19 | 20 | 21 |

22 | yecc_ret() = {error, term()} | {ok, term()}
23 | 
24 | 25 | 26 | 27 | 28 | ## Function Index ## 29 | 30 | 31 |
format_error/1
parse/1
parse_and_scan/1
32 | 33 | 34 | 35 | 36 | ## Function Details ## 37 | 38 | 39 | 40 | ### format_error/1 ### 41 | 42 | 43 |

44 | format_error(Message::any()) -> [char() | list()]
45 | 
46 | 47 |

48 | 49 | 50 | 51 | 52 | 53 | ### parse/1 ### 54 | 55 | 56 |

57 | parse(Tokens::list()) -> yecc_ret()
58 | 
59 | 60 |

61 | 62 | 63 | 64 | 65 | 66 | ### parse_and_scan/1 ### 67 | 68 | 69 |

70 | parse_and_scan(X1::{function() | {atom(), atom()}, [term()]} | {atom(), atom(), [term()]}) -> yecc_ret()
71 | 
72 | 73 |

74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/kvdb_cron_scan.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_cron_scan # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
string/1
string/2
token/2
token/3
tokens/2
tokens/3
14 | 15 | 16 | 17 | 18 | ## Function Details ## 19 | 20 | 21 | 22 | ### string/1 ### 23 | 24 | `string(String) -> any()` 25 | 26 | 27 | 28 | 29 | ### string/2 ### 30 | 31 | `string(String, Line) -> any()` 32 | 33 | 34 | 35 | 36 | ### token/2 ### 37 | 38 | `token(Cont, Chars) -> any()` 39 | 40 | 41 | 42 | 43 | ### token/3 ### 44 | 45 | `token(X1, Chars, Line) -> any()` 46 | 47 | 48 | 49 | 50 | ### tokens/2 ### 51 | 52 | `tokens(Cont, Chars) -> any()` 53 | 54 | 55 | 56 | 57 | ### tokens/3 ### 58 | 59 | `tokens(X1, Chars, Line) -> any()` 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/kvdb_ets_dumper.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_ets_dumper # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### tab() ### 18 | 19 | 20 | 21 |

22 | tab() = atom() | tid()
23 | 
24 | 25 | 26 | 27 | a similar definition is also in erl_types 28 | 29 | 30 | 31 | ### tid() ### 32 | 33 | 34 | __abstract datatype__: `tid()` 35 | 36 | 37 | 38 | ## Function Index ## 39 | 40 | 41 |
file2tab/1
file2tab/2
tab2file/2
tab2file/3
42 | 43 | 44 | 45 | 46 | ## Function Details ## 47 | 48 | 49 | 50 | ### file2tab/1 ### 51 | 52 | 53 |

54 | file2tab(Filename) -> {ok, Tab} | {error, Reason}
55 | 
56 | 57 | 58 | 59 | 60 | 61 | 62 | ### file2tab/2 ### 63 | 64 | 65 |

66 | file2tab(Filename, Options) -> {ok, Tab} | {error, Reason}
67 | 
68 | 69 | 70 | 71 | 72 | 73 | 74 | ### tab2file/2 ### 75 | 76 | 77 |

78 | tab2file(Tab, Filename) -> ok | {error, Reason}
79 | 
80 | 81 | 82 | 83 | 84 | 85 | 86 | ### tab2file/3 ### 87 | 88 | 89 |

90 | tab2file(Tab, Filename, Options) -> ok | {error, Reason}
91 | 
92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /doc/kvdb_export.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_export # 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/kvdb_paired.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_paired # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | Paired backend for kvdb. 11 | __Behaviours:__ [`kvdb`](kvdb.md). 12 | 13 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | 16 | ## Description ## 17 | 18 | 19 | 20 | NOTE: This is work in progress. Several things still do not work 21 | 22 | 23 | 24 | The idea with this backend is to combine two backends as a write-through 25 | pair. That is: all reads are done on 'backend 1', but writes are served 26 | in both 'backend 1' and 'backend 2'. An example of how to use this would 27 | be e.g. a kvdb_ets backend in front of a kvdb_riak backend: 28 | 29 | 30 | 31 | ```erlang 32 | 33 | kvdb:open(p, [{backend, kvdb_paired}, 34 | {module1, kvdb_ets}, 35 | {module2, kvdb_riak}, 36 | {options2, [{update_index, false}]}]). 37 | ``` 38 | 39 | 40 | The 'options2' list applies only to the 'backend 2' (riak in this case). 41 | With `{update_index, false}` on 'backend 2', we will not maintain indexes 42 | on the riak side, but rebuild them when the ets backend is populated. 43 | 44 | ## Function Index ## 45 | 46 | 47 |
add_table/3
close/1
delete/3
delete_table/2
dump_tables/1
extract/3
first/2
first_queue/2
get/3
get_attrs/4
get_schema_mod/2
index_get/4
index_keys/4
info/2
is_queue_empty/3
is_table/2
last/2
list_queue/3
list_queue/6
list_queue/7
list_tables/1
mark_queue_object/4
next/3
next_queue/3
open/2
pop/3
prefix_match/3
prefix_match/4
prefix_match_rel/5
prel_pop/3
prev/3
proxy_childspecs/2
push/4
put/3
queue_delete/3
queue_head_delete/3
queue_head_read/3
queue_head_write/4
queue_insert/5
queue_read/3
schema_delete/3
schema_fold/3
schema_read/3
schema_write/4
update_counter/4
48 | 49 | 50 | 51 | 52 | ## Function Details ## 53 | 54 | 55 | 56 | ### add_table/3 ### 57 | 58 | `add_table(Db, Table, Opts) -> any()` 59 | 60 | 61 | 62 | 63 | ### close/1 ### 64 | 65 | `close(Db) -> any()` 66 | 67 | 68 | 69 | 70 | ### delete/3 ### 71 | 72 | `delete(Db, T, K) -> any()` 73 | 74 | 75 | 76 | 77 | ### delete_table/2 ### 78 | 79 | `delete_table(Db, Table) -> any()` 80 | 81 | 82 | 83 | 84 | ### dump_tables/1 ### 85 | 86 | `dump_tables(Db) -> any()` 87 | 88 | 89 | 90 | 91 | ### extract/3 ### 92 | 93 | `extract(Db, T, K) -> any()` 94 | 95 | 96 | 97 | 98 | ### first/2 ### 99 | 100 | `first(Db, Tab) -> any()` 101 | 102 | 103 | 104 | 105 | ### first_queue/2 ### 106 | 107 | `first_queue(Db, Tab) -> any()` 108 | 109 | 110 | 111 | 112 | ### get/3 ### 113 | 114 | `get(Db, Tab, K) -> any()` 115 | 116 | 117 | 118 | 119 | ### get_attrs/4 ### 120 | 121 | `get_attrs(Db, Tab, K, As) -> any()` 122 | 123 | 124 | 125 | 126 | ### get_schema_mod/2 ### 127 | 128 | `get_schema_mod(Db, Default) -> any()` 129 | 130 | 131 | 132 | 133 | ### index_get/4 ### 134 | 135 | `index_get(Db, Tab, K, V) -> any()` 136 | 137 | 138 | 139 | 140 | ### index_keys/4 ### 141 | 142 | `index_keys(Db, T, K, V) -> any()` 143 | 144 | 145 | 146 | 147 | ### info/2 ### 148 | 149 | `info(Db, What) -> any()` 150 | 151 | 152 | 153 | 154 | ### is_queue_empty/3 ### 155 | 156 | `is_queue_empty(Db, T, Q) -> any()` 157 | 158 | 159 | 160 | 161 | ### is_table/2 ### 162 | 163 | `is_table(Db, Tab) -> any()` 164 | 165 | 166 | 167 | 168 | ### last/2 ### 169 | 170 | `last(Db, Tab) -> any()` 171 | 172 | 173 | 174 | 175 | ### list_queue/3 ### 176 | 177 | `list_queue(Db, T, Q) -> any()` 178 | 179 | 180 | 181 | 182 | ### list_queue/6 ### 183 | 184 | `list_queue(Db, T, Q, Fltr, HeedBlock, Limit) -> any()` 185 | 186 | 187 | 188 | 189 | ### list_queue/7 ### 190 | 191 | `list_queue(Db, T, Q, Fltr, HeedBlock, Limit, Reverse) -> any()` 192 | 193 | 194 | 195 | 196 | ### list_tables/1 ### 197 | 198 | `list_tables(Db) -> any()` 199 | 200 | 201 | 202 | 203 | ### mark_queue_object/4 ### 204 | 205 | `mark_queue_object(Db, Tab, K, St) -> any()` 206 | 207 | 208 | 209 | 210 | ### next/3 ### 211 | 212 | `next(Db, Tab, K) -> any()` 213 | 214 | 215 | 216 | 217 | ### next_queue/3 ### 218 | 219 | `next_queue(Db, Tab, Q) -> any()` 220 | 221 | 222 | 223 | 224 | ### open/2 ### 225 | 226 | `open(DbName, Options) -> any()` 227 | 228 | 229 | 230 | 231 | ### pop/3 ### 232 | 233 | `pop(Db, T, Q) -> any()` 234 | 235 | 236 | 237 | 238 | ### prefix_match/3 ### 239 | 240 | `prefix_match(Db, Tab, Pfx) -> any()` 241 | 242 | 243 | 244 | 245 | ### prefix_match/4 ### 246 | 247 | `prefix_match(Db, Tab, Pfx, Limit) -> any()` 248 | 249 | 250 | 251 | 252 | ### prefix_match_rel/5 ### 253 | 254 | `prefix_match_rel(Db, Tab, Prefix, Start, Limit) -> any()` 255 | 256 | 257 | 258 | 259 | ### prel_pop/3 ### 260 | 261 | `prel_pop(Db, T, Q) -> any()` 262 | 263 | 264 | 265 | 266 | ### prev/3 ### 267 | 268 | `prev(Db, Tab, K) -> any()` 269 | 270 | 271 | 272 | 273 | ### proxy_childspecs/2 ### 274 | 275 | `proxy_childspecs(Name, Options) -> any()` 276 | 277 | 278 | 279 | 280 | ### push/4 ### 281 | 282 | `push(Db, Table, Q, Obj) -> any()` 283 | 284 | 285 | 286 | 287 | ### put/3 ### 288 | 289 | `put(Db, Table, Obj) -> any()` 290 | 291 | 292 | 293 | 294 | ### queue_delete/3 ### 295 | 296 | `queue_delete(Db, T, K) -> any()` 297 | 298 | 299 | 300 | 301 | ### queue_head_delete/3 ### 302 | 303 | `queue_head_delete(Db, Tab, Q) -> any()` 304 | 305 | 306 | 307 | 308 | ### queue_head_read/3 ### 309 | 310 | `queue_head_read(Db, Tab, Q) -> any()` 311 | 312 | 313 | 314 | 315 | ### queue_head_write/4 ### 316 | 317 | `queue_head_write(Db, Tab, Q, Obj) -> any()` 318 | 319 | 320 | 321 | 322 | ### queue_insert/5 ### 323 | 324 | `queue_insert(Db, T, K, St, Obj) -> any()` 325 | 326 | 327 | 328 | 329 | ### queue_read/3 ### 330 | 331 | `queue_read(Db, T, K) -> any()` 332 | 333 | 334 | 335 | 336 | ### schema_delete/3 ### 337 | 338 | `schema_delete(Db, Cat, K) -> any()` 339 | 340 | 341 | 342 | 343 | ### schema_fold/3 ### 344 | 345 | `schema_fold(Db, F, A) -> any()` 346 | 347 | 348 | 349 | 350 | ### schema_read/3 ### 351 | 352 | `schema_read(Db, Cat, K) -> any()` 353 | 354 | 355 | 356 | 357 | ### schema_write/4 ### 358 | 359 | `schema_write(Db, Cat, K, V) -> any()` 360 | 361 | 362 | 363 | 364 | ### update_counter/4 ### 365 | 366 | `update_counter(Db, Table, Key, Incr) -> any()` 367 | 368 | 369 | -------------------------------------------------------------------------------- /doc/kvdb_proxy_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_proxy_sup # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | Database proxy supervisor. 11 | __Behaviours:__ [`supervisor`](supervisor.md). 12 | 13 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | 16 | ## Description ## 17 | 18 | 19 | NOTE: This is work in progress, highly experimental. 20 | The general idea is that a backend can specify 'proxy processes' 21 | as a list of childspecs. An example of such a proxy is kvdb_riak_proxy. 22 | 23 | 24 | ## Function Index ## 25 | 26 | 27 |
init/1
start_link/2
28 | 29 | 30 | 31 | 32 | ## Function Details ## 33 | 34 | 35 | 36 | ### init/1 ### 37 | 38 | `init(X1) -> any()` 39 | 40 | 41 | 42 | 43 | ### start_link/2 ### 44 | 45 | `start_link(Name, Options) -> any()` 46 | 47 | 48 | -------------------------------------------------------------------------------- /doc/kvdb_queue.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_queue # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | Useful queue inspection and management functions. 11 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
clear_queue/3
clear_queues/2
delete/3
extract/3
first/2
info/4
is_empty/3
list/3
list/6
list_full/3
list_queues/2
list_queues/3
next/3
pop/2
pop/3
prel_pop/2
prel_pop/3
push/3
push/4
size/3
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### clear_queue/3 ### 27 | 28 | `clear_queue(Db, Table, Q) -> any()` 29 | 30 | 31 | 32 | 33 | ### clear_queues/2 ### 34 | 35 | `clear_queues(Db, Table) -> any()` 36 | 37 | 38 | 39 | 40 | ### delete/3 ### 41 | 42 | `delete(Db, Table, Key) -> any()` 43 | 44 | 45 | 46 | 47 | ### extract/3 ### 48 | 49 | `extract(Db, Table, Key) -> any()` 50 | 51 | 52 | 53 | 54 | ### first/2 ### 55 | 56 | `first(Db, Table) -> any()` 57 | 58 | 59 | 60 | 61 | ### info/4 ### 62 | 63 | `info(Db, Table, Q, X4) -> any()` 64 | 65 | 66 | 67 | 68 | ### is_empty/3 ### 69 | 70 | `is_empty(Db, Table, Q) -> any()` 71 | 72 | 73 | 74 | 75 | ### list/3 ### 76 | 77 | `list(Db, Table, Q) -> any()` 78 | 79 | 80 | 81 | 82 | ### list/6 ### 83 | 84 | `list(Db, Tab, Q, Fltr, HeedBlock, Limit) -> any()` 85 | 86 | 87 | 88 | 89 | ### list_full/3 ### 90 | 91 | `list_full(Db, Tab, Q) -> any()` 92 | 93 | 94 | 95 | 96 | ### list_queues/2 ### 97 | 98 | `list_queues(Db, Table) -> any()` 99 | 100 | 101 | 102 | 103 | ### list_queues/3 ### 104 | 105 | `list_queues(Db, Table, Limit) -> any()` 106 | 107 | 108 | 109 | 110 | ### next/3 ### 111 | 112 | `next(Db, Table, PrevQ) -> any()` 113 | 114 | 115 | 116 | 117 | ### pop/2 ### 118 | 119 | `pop(Db, Table) -> any()` 120 | 121 | 122 | 123 | 124 | ### pop/3 ### 125 | 126 | `pop(Db, Table, Q) -> any()` 127 | 128 | 129 | 130 | 131 | ### prel_pop/2 ### 132 | 133 | `prel_pop(Db, Table) -> any()` 134 | 135 | 136 | 137 | 138 | ### prel_pop/3 ### 139 | 140 | `prel_pop(Db, Table, Q) -> any()` 141 | 142 | 143 | 144 | 145 | ### push/3 ### 146 | 147 | `push(Db, Table, Obj) -> any()` 148 | 149 | 150 | 151 | 152 | ### push/4 ### 153 | 154 | `push(Db, Table, Q, Obj) -> any()` 155 | 156 | 157 | 158 | 159 | ### size/3 ### 160 | 161 | `size(Db, Table, Q) -> any()` 162 | 163 | 164 | -------------------------------------------------------------------------------- /doc/kvdb_riak.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_riak # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | Highly experimental riak backend for kvdb. 11 | __Behaviours:__ [`kvdb`](kvdb.md). 12 | 13 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | 16 | ## Description ## 17 | 18 | 19 | 20 | NOTE: This is work in progress. Several things still do not work 21 | 22 | 23 | The best way to use this for now is via the kvdb_paired backend. 24 | 25 | ## Function Index ## 26 | 27 | 28 |
add_table/3
close/1
delete/3
delete_table/2
dump_tables/1
extract/3
first/2
first_queue/2
get/3
get_attrs/4
get_schema_mod/2
index_get/4
index_keys/4
info/2
is_queue_empty/3
is_table/2
last/2
list_queue/3
list_queue/6
list_tables/1
mark_queue_object/4
next/3
next_queue/3
open/2
pop/3
prefix_match/3
prefix_match/4
prefix_match_rel/5
prel_pop/3
prev/3
proxy_childspecs/2
push/4
put/3
queue_delete/3
queue_head_delete/3
queue_head_read/3
queue_head_write/4
queue_insert/5
queue_read/3
update_counter/4
29 | 30 | 31 | 32 | 33 | ## Function Details ## 34 | 35 | 36 | 37 | ### add_table/3 ### 38 | 39 | `add_table(Db, Table, Opts) -> any()` 40 | 41 | 42 | 43 | 44 | ### close/1 ### 45 | 46 | `close(Db) -> any()` 47 | 48 | 49 | 50 | 51 | ### delete/3 ### 52 | 53 | `delete(Db, Tab, K) -> any()` 54 | 55 | 56 | 57 | 58 | ### delete_table/2 ### 59 | 60 | `delete_table(Db, Table) -> any()` 61 | 62 | 63 | 64 | 65 | ### dump_tables/1 ### 66 | 67 | `dump_tables(X1) -> any()` 68 | 69 | 70 | 71 | 72 | ### extract/3 ### 73 | 74 | `extract(X1, X2, X3) -> any()` 75 | 76 | 77 | 78 | 79 | ### first/2 ### 80 | 81 | `first(X1, X2) -> any()` 82 | 83 | 84 | 85 | 86 | ### first_queue/2 ### 87 | 88 | `first_queue(X1, X2) -> any()` 89 | 90 | 91 | 92 | 93 | ### get/3 ### 94 | 95 | `get(Db, Tab, K) -> any()` 96 | 97 | 98 | 99 | 100 | ### get_attrs/4 ### 101 | 102 | `get_attrs(X1, X2, X3, X4) -> any()` 103 | 104 | 105 | 106 | 107 | ### get_schema_mod/2 ### 108 | 109 | `get_schema_mod(Db, Default) -> any()` 110 | 111 | 112 | 113 | 114 | ### index_get/4 ### 115 | 116 | `index_get(X1, X2, X3, X4) -> any()` 117 | 118 | 119 | 120 | 121 | ### index_keys/4 ### 122 | 123 | `index_keys(X1, X2, X3, X4) -> any()` 124 | 125 | 126 | 127 | 128 | ### info/2 ### 129 | 130 | `info(Db, What) -> any()` 131 | 132 | 133 | 134 | 135 | ### is_queue_empty/3 ### 136 | 137 | `is_queue_empty(X1, X2, X3) -> any()` 138 | 139 | 140 | 141 | 142 | ### is_table/2 ### 143 | 144 | `is_table(Db, Tab) -> any()` 145 | 146 | 147 | 148 | 149 | ### last/2 ### 150 | 151 | `last(X1, X2) -> any()` 152 | 153 | 154 | 155 | 156 | ### list_queue/3 ### 157 | 158 | `list_queue(X1, X2, X3) -> any()` 159 | 160 | 161 | 162 | 163 | ### list_queue/6 ### 164 | 165 | `list_queue(X1, X2, X3, X4, X5, X6) -> any()` 166 | 167 | 168 | 169 | 170 | ### list_tables/1 ### 171 | 172 | `list_tables(Db) -> any()` 173 | 174 | 175 | 176 | 177 | ### mark_queue_object/4 ### 178 | 179 | `mark_queue_object(X1, X2, X3, X4) -> any()` 180 | 181 | 182 | 183 | 184 | ### next/3 ### 185 | 186 | `next(X1, X2, X3) -> any()` 187 | 188 | 189 | 190 | 191 | ### next_queue/3 ### 192 | 193 | `next_queue(X1, X2, X3) -> any()` 194 | 195 | 196 | 197 | 198 | ### open/2 ### 199 | 200 | `open(DbName, Options) -> any()` 201 | 202 | 203 | 204 | 205 | ### pop/3 ### 206 | 207 | `pop(X1, X2, X3) -> any()` 208 | 209 | 210 | 211 | 212 | ### prefix_match/3 ### 213 | 214 | `prefix_match(Db, Table, Pfx) -> any()` 215 | 216 | 217 | 218 | 219 | ### prefix_match/4 ### 220 | 221 | `prefix_match(Db, Table, Pfx, Limit) -> any()` 222 | 223 | 224 | 225 | 226 | ### prefix_match_rel/5 ### 227 | 228 | `prefix_match_rel(X1, X2, X3, X4, X5) -> any()` 229 | 230 | 231 | 232 | 233 | ### prel_pop/3 ### 234 | 235 | `prel_pop(X1, X2, X3) -> any()` 236 | 237 | 238 | 239 | 240 | ### prev/3 ### 241 | 242 | `prev(X1, X2, X3) -> any()` 243 | 244 | 245 | 246 | 247 | ### proxy_childspecs/2 ### 248 | 249 | `proxy_childspecs(Name, Options) -> any()` 250 | 251 | 252 | 253 | 254 | ### push/4 ### 255 | 256 | `push(X1, X2, X3, X4) -> any()` 257 | 258 | 259 | 260 | 261 | ### put/3 ### 262 | 263 | `put(Db, Table, Obj) -> any()` 264 | 265 | 266 | 267 | 268 | ### queue_delete/3 ### 269 | 270 | `queue_delete(X1, X2, X3) -> any()` 271 | 272 | 273 | 274 | 275 | ### queue_head_delete/3 ### 276 | 277 | `queue_head_delete(X1, X2, X3) -> any()` 278 | 279 | 280 | 281 | 282 | ### queue_head_read/3 ### 283 | 284 | `queue_head_read(X1, X2, X3) -> any()` 285 | 286 | 287 | 288 | 289 | ### queue_head_write/4 ### 290 | 291 | `queue_head_write(X1, X2, X3, X4) -> any()` 292 | 293 | 294 | 295 | 296 | ### queue_insert/5 ### 297 | 298 | `queue_insert(X1, X2, X3, X4, X5) -> any()` 299 | 300 | 301 | 302 | 303 | ### queue_read/3 ### 304 | 305 | `queue_read(X1, X2, X3) -> any()` 306 | 307 | 308 | 309 | 310 | ### update_counter/4 ### 311 | 312 | `update_counter(Db, Table, Key, Incr) -> any()` 313 | 314 | 315 | -------------------------------------------------------------------------------- /doc/kvdb_riak_mapred.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_riak_mapred # 4 | * [Description](#description) 5 | 6 | 7 | 8 | Riak map-reduce hooks for kvdb_riak backend. 9 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 10 | 11 | 12 | ## Description ## 13 | 14 | 15 | NOTE: This is work in progress, highly experimental. 16 | -------------------------------------------------------------------------------- /doc/kvdb_riak_proxy.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_riak_proxy # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | Riak connectivity manager (proxy) for kvdb_riak backend. 11 | __Behaviours:__ [`gen_server`](gen_server.md). 12 | 13 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | 16 | ## Description ## 17 | 18 | 19 | NOTE: This is work in progress, highly experimental. 20 | 21 | 22 | ## Function Index ## 23 | 24 | 25 |
code_change/3
get_session/1
handle_call/3
handle_cast/2
handle_info/2
init/1
start_link/2
terminate/2
26 | 27 | 28 | 29 | 30 | ## Function Details ## 31 | 32 | 33 | 34 | ### code_change/3 ### 35 | 36 | `code_change(X1, S, X3) -> any()` 37 | 38 | 39 | 40 | 41 | ### get_session/1 ### 42 | 43 | `get_session(Name) -> any()` 44 | 45 | 46 | 47 | 48 | ### handle_call/3 ### 49 | 50 | `handle_call(X1, X2, St) -> any()` 51 | 52 | 53 | 54 | 55 | ### handle_cast/2 ### 56 | 57 | `handle_cast(X1, S) -> any()` 58 | 59 | 60 | 61 | 62 | ### handle_info/2 ### 63 | 64 | `handle_info(X1, S) -> any()` 65 | 66 | 67 | 68 | 69 | ### init/1 ### 70 | 71 | `init(X1) -> any()` 72 | 73 | 74 | 75 | 76 | ### start_link/2 ### 77 | 78 | `start_link(Name, Options) -> any()` 79 | 80 | 81 | 82 | 83 | ### terminate/2 ### 84 | 85 | `terminate(X1, X2) -> any()` 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/kvdb_schema.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_schema # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | KVDB schema callback module behavior. 11 | __This module defines the `kvdb_schema` behaviour.__ 12 |

13 | Required callback functions: `validate/3`, `on_update/4`, `pre_commit/2`, `post_commit/2`. 14 | 15 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 16 | 17 | 18 | ## Function Index ## 19 | 20 | 21 |
all_ok/3
behaviour_info/1
fold_schema/3
on_update/4
post_commit/2
pre_commit/2
read/1
read/2
validate/3
write/2
22 | 23 | 24 | 25 | 26 | ## Function Details ## 27 | 28 | 29 | 30 | ### all_ok/3 ### 31 | 32 | `all_ok(Schema, F, Obj) -> any()` 33 | 34 | 35 | 36 | 37 | ### behaviour_info/1 ### 38 | 39 | `behaviour_info(X1) -> any()` 40 | 41 | 42 | 43 | 44 | ### fold_schema/3 ### 45 | 46 | `fold_schema(Schema, F, Obj) -> any()` 47 | 48 | 49 | 50 | 51 | ### on_update/4 ### 52 | 53 | `on_update(Op, Db, Table, Obj) -> any()` 54 | 55 | 56 | 57 | 58 | ### post_commit/2 ### 59 | 60 | `post_commit(X1, X2) -> any()` 61 | 62 | 63 | 64 | 65 | ### pre_commit/2 ### 66 | 67 | `pre_commit(C, X2) -> any()` 68 | 69 | 70 | 71 | 72 | ### read/1 ### 73 | 74 | `read(Db) -> any()` 75 | 76 | 77 | 78 | 79 | ### read/2 ### 80 | 81 | `read(Db, Item) -> any()` 82 | 83 | 84 | 85 | 86 | ### validate/3 ### 87 | 88 | `validate(Db, Type, Obj) -> any()` 89 | 90 | 91 | 92 | 93 | ### write/2 ### 94 | 95 | `write(Db, Schema) -> any()` 96 | 97 | 98 | -------------------------------------------------------------------------------- /doc/kvdb_schema_events.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module kvdb_schema_events # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | KVDB schema callback module for queue-related events. 11 | __Behaviours:__ [`kvdb_schema`](kvdb_schema.md). 12 | 13 | __Authors:__ Ulf Wiger ([`ulf@feuerlabs.com`](mailto:ulf@feuerlabs.com)). 14 | 15 | 16 | ## Function Index ## 17 | 18 | 19 |
cancel_notify_all_queues/2
notify_all_queues/2
notify_when_not_empty/3
on_update/4
post_commit/2
pre_commit/2
validate/3
20 | 21 | 22 | 23 | 24 | ## Function Details ## 25 | 26 | 27 | 28 | ### cancel_notify_all_queues/2 ### 29 | 30 | `cancel_notify_all_queues(Kvdb_ref, Table0) -> any()` 31 | 32 | 33 | 34 | 35 | ### notify_all_queues/2 ### 36 | 37 | `notify_all_queues(Kvdb_ref, Table0) -> any()` 38 | 39 | 40 | 41 | 42 | ### notify_when_not_empty/3 ### 43 | 44 | `notify_when_not_empty(Kvdb_ref, Table0, Q) -> any()` 45 | 46 | 47 | 48 | 49 | ### on_update/4 ### 50 | 51 | `on_update(X1, DB, Table, X4) -> any()` 52 | 53 | 54 | 55 | 56 | ### post_commit/2 ### 57 | 58 | `post_commit(X1, X2) -> any()` 59 | 60 | 61 | 62 | 63 | ### pre_commit/2 ### 64 | 65 | `pre_commit(C, X2) -> any()` 66 | 67 | 68 | 69 | 70 | ### validate/3 ### 71 | 72 | `validate(X1, X2, Obj) -> any()` 73 | 74 | 75 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @author Ulf Wiger 2 | @author Tony Rogvall 3 | 4 | @doc KVDB - Database Management System for Connected Device Management 5 | 6 | KVDB was initially designed to support the requirements of the Exosense 7 | system for managing Connected Devices, but is essentially a general-purpose 8 | DBMS. A requirement was that it should be useable both in an embedded device 9 | and on the device management server. To this end, KVDB supports a number of 10 | different storage backends, each with different characteristics. 11 | 12 | Features: 13 | 14 | 23 | 24 | See also the Wiki for further description and examples. 25 | 26 | @end -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.app 3 | -------------------------------------------------------------------------------- /include/kvdb.hrl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc kvdb definitions. 12 | %%% @end 13 | 14 | -type table() :: atom() | binary(). 15 | -type int_table_name() :: binary(). 16 | -type queue_name() :: any(). 17 | 18 | -record(kvdb_ref, {name, tref, db, mod, schema = db}). 19 | -type db_ref() :: #kvdb_ref{}. 20 | 21 | -record(db, {ref, encoding = sext, metadata, log = false, st}). 22 | -record(table, {name, 23 | type = set, 24 | encoding = sext, 25 | columns, 26 | schema, 27 | nodes = [node()], 28 | index = []}). 29 | 30 | -record(event, {event, tab, info}). 31 | 32 | -record(dbst, {encoding, 33 | type}). 34 | 35 | -type db() :: #db{}. 36 | 37 | -define(META_TABLE, <<"kvdb__META">>). 38 | 39 | -type db_name() :: any(). 40 | -type key() :: any(). 41 | -type value() :: any(). 42 | -type attr_name() :: atom(). 43 | -type attr_value() :: any(). 44 | -type attrs() :: [{atom(), any()}]. 45 | -type options() :: [{atom(), any()}]. 46 | -type basic_encoding() :: sext | raw | term. 47 | -type encoding() :: basic_encoding() | 48 | {basic_encoding(), basic_encoding()} | 49 | {basic_encoding(), basic_encoding(), basic_encoding()}. 50 | 51 | -type object() :: {key(), value()} | {key(), attrs(), value()}. 52 | -type increment() :: integer(). 53 | -type status() :: active | inactive | blocking. 54 | -type cont() :: fun(() -> {[object()], cont()} | done). 55 | -type schema_category() :: property | tabrec | global. 56 | 57 | -record(commit, {add_tables = [], 58 | del_tables = [], 59 | write = [], 60 | delete = [], 61 | events = []}). 62 | 63 | -record(thr, {writes, 64 | bytes, 65 | time}). 66 | 67 | -record(q_key, {queue, 68 | ts, 69 | key}). 70 | 71 | -define(Q_HEAD_FLOOR, -1). 72 | -define(Q_HEAD_CEIL, []). 73 | -define(Q_HEAD_KEY, <<"_KVDB_Q_HEAD_">>). 74 | -define(IS_Q_HEAD_TS(TS), TS==?Q_HEAD_FLOOR; TS==?Q_HEAD_CEIL). 75 | 76 | -define(KVDB_CATCH(Expr, Args), 77 | try Expr 78 | catch 79 | throw:{kvdb_return, __R} -> 80 | __R; 81 | throw:{kvdb_throw, __E} -> 82 | %% error(__E, Args) 83 | erlang:error(__E, erlang:get_stacktrace()) 84 | end). 85 | 86 | -define(KVDB_THROW(E), throw({kvdb_throw, E})). 87 | -define(KVDB_RETURN(R), throw({kvdb_return, R})). 88 | 89 | -define(KVDB_LOG_INSERT(Tab, Obj), {insert, Tab, Obj}). 90 | -define(KVDB_LOG_DELETE(Tab, Key), {delete, Tab, Key}). 91 | -define(KVDB_LOG_Q_INSERT(Tab, QKey, St, Obj), 92 | {q_insert, Tab, QKey, St, Obj}). 93 | -define(KVDB_LOG_Q_DELETE(Tab, QKey), {q_delete, Tab, QKey}). 94 | -define(KVDB_LOG_ADD_TABLE(Tab, TabR), {add_table, Tab, TabR}). 95 | -define(KVDB_LOG_DELETE_TABLE(Tab), {delete_table, Tab}). 96 | -define(KVDB_LOG_WRITE_META(Op), {write_meta, Op}). 97 | -define(KVDB_LOG_COMMIT(CommitRec), {commit, CommitRec}). 98 | -------------------------------------------------------------------------------- /include/kvdb_conf.hrl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc kvdb definitions. 12 | %%% @end 13 | 14 | -type key_part() :: binary() | {binary(), integer()}. 15 | -type key() :: binary(). 16 | -type attrs() :: [{atom(), any()}]. 17 | -type value() :: any(). 18 | -type conf_data() :: [conf_node() | conf_obj()]. 19 | 20 | -record(conf_tree, {root = <<>> :: key(), 21 | tree = [] :: conf_data() 22 | }). 23 | -type conf_tree() :: #conf_tree{}. 24 | 25 | -type node_key() :: key() | integer(). 26 | -type conf_obj() :: {node_key(), attrs(), value()}. 27 | -type conf_node() :: {node_key(), attrs(), value(), conf_tree()} 28 | | {node_key(), conf_tree()}. 29 | -type shift_op() :: up | down | top | bottom. 30 | 31 | 32 | -------------------------------------------------------------------------------- /include/riak_kv_wm_raw.hrl: -------------------------------------------------------------------------------- 1 | %% This file is provided to you under the Apache License, 2 | %% Version 2.0 (the "License"); you may not use this file 3 | %% except in compliance with the License. You may obtain 4 | %% a copy of the License at 5 | 6 | %% http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | %% Unless required by applicable law or agreed to in writing, 9 | %% software distributed under the License is distributed on an 10 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 | %% KIND, either express or implied. See the License for the 12 | %% specific language governing permissions and limitations 13 | %% under the License. 14 | 15 | %% Constants used by the raw_http resources 16 | 17 | %% Names of riak_object metadata fields 18 | -define(MD_CTYPE, <<"content-type">>). 19 | -define(MD_CHARSET, <<"charset">>). 20 | -define(MD_ENCODING, <<"content-encoding">>). 21 | -define(MD_VTAG, <<"X-Riak-VTag">>). 22 | -define(MD_LINKS, <<"Links">>). 23 | -define(MD_LASTMOD, <<"X-Riak-Last-Modified">>). 24 | -define(MD_USERMETA, <<"X-Riak-Meta">>). 25 | -define(MD_INDEX, <<"index">>). 26 | -define(MD_DELETED, <<"X-Riak-Deleted">>). 27 | 28 | %% Names of HTTP header fields 29 | -define(HEAD_CTYPE, "Content-Type"). 30 | -define(HEAD_VCLOCK, "X-Riak-Vclock"). 31 | -define(HEAD_LINK, "Link"). 32 | -define(HEAD_ENCODING, "Content-Encoding"). 33 | -define(HEAD_CLIENT, "X-Riak-ClientId"). 34 | -define(HEAD_USERMETA_PREFIX, "x-riak-meta-"). 35 | -define(HEAD_INDEX_PREFIX, "x-riak-index-"). 36 | -define(HEAD_DELETED, "X-Riak-Deleted"). 37 | -define(HEAD_TIMEOUT, "X-Riak-Timeout"). 38 | 39 | %% Names of JSON fields in bucket properties 40 | -define(JSON_PROPS, <<"props">>). 41 | -define(JSON_BUCKETS, <<"buckets">>). 42 | -define(JSON_KEYS, <<"keys">>). 43 | -define(JSON_LINKFUN, <<"linkfun">>). 44 | -define(JSON_MOD, <<"mod">>). 45 | -define(JSON_FUN, <<"fun">>). 46 | -define(JSON_ARG, <<"arg">>). 47 | -define(JSON_CHASH, <<"chash_keyfun">>). 48 | -define(JSON_JSFUN, <<"jsfun">>). 49 | -define(JSON_JSANON, <<"jsanon">>). 50 | -define(JSON_JSBUCKET, <<"bucket">>). 51 | -define(JSON_JSKEY, <<"key">>). 52 | -define(JSON_ALLOW_MULT, <<"allow_mult">>). 53 | -define(JSON_EXTRACT, <<"search_extractor">>). 54 | -define(JSON_EXTRACT_LEGACY, <<"rs_extractfun">>). 55 | 56 | %% Names of HTTP query parameters 57 | -define(Q_PROPS, "props"). 58 | -define(Q_BUCKETS, "buckets"). 59 | -define(Q_KEYS, "keys"). 60 | -define(Q_FALSE, "false"). 61 | -define(Q_TRUE, "true"). 62 | -define(Q_STREAM, "stream"). 63 | -define(Q_VTAG, "vtag"). 64 | -define(Q_RETURNBODY, "returnbody"). 65 | -define(Q_2I_RETURNTERMS, "return_terms"). 66 | -define(Q_2I_MAX_RESULTS, "max_results"). 67 | -define(Q_2I_CONTINUATION, "continuation"). 68 | -define(Q_RESULTS, "results"). 69 | -define(Q_RETURNVALUE, "returnvalue"). 70 | -------------------------------------------------------------------------------- /priv/all_apps.config: -------------------------------------------------------------------------------- 1 | {"leveldb",eleveldb}. 2 | {"sqlite3",sqlite3}. 3 | {"sqlite3",resource}. 4 | -------------------------------------------------------------------------------- /priv/backends.script: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | Backends = [{"leveldb",eleveldb}, 3 | {"sqlite3",sqlite3}]. 4 | case os:getenv("KVDB_BACKENDS") of 5 | Str when is_list(Str) -> 6 | Split = re:split(Str, "[, ]+", [{return,list}]), 7 | case [B || B <- Split, not lists:member( 8 | B, ["none", "ets", 9 | "leveldb", "sqlite3"])] of 10 | [] -> []; 11 | [_|_] = Unknown -> 12 | io:fwrite("Error - unknown backends: ~p~n", [Unknown]), 13 | error({unknown_backends, Unknown}) 14 | end, 15 | io:fwrite("Backends selected: ~p~n", [Split]), 16 | [App || {K, App} = Pair <- Backends, 17 | lists:member(K, Split)]; 18 | false -> 19 | [App || {_, App} <- Backends] 20 | end. 21 | -------------------------------------------------------------------------------- /priv/excluded_apps.script: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | case os:getenv("KVDB_BACKENDS") of 3 | Str when is_list(Str) -> 4 | Split = re:split(Str, "[, ]+", [{return,list}]), 5 | {ok, Backends} = 6 | file:consult(filename:join(CWD, "all_apps.config")), 7 | case [B || B <- Split, not lists:member( 8 | B, ["none", "ets", 9 | "leveldb", "sqlite3"])] of 10 | [] -> []; 11 | [_|_] = Unknown -> 12 | io:fwrite("Error - unknown backends: ~p~n", [Unknown]), 13 | error({unknown_backends, Unknown}) 14 | end, 15 | io:fwrite("Backends selected: ~p~n", [Split]), 16 | [App || {K, App} = Pair <- Backends, 17 | not lists:member(K, Split)]; 18 | false -> 19 | [] 20 | end. 21 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | {erl_first_files, ["src/kvdb.erl","src/kvdb_schema.erl"]}. 3 | {erl_opts, [debug_info, {parse_transform, lager_transform}]}. 4 | {xref_checks, [undefined_function_calls, undefined_functions, 5 | locals_not_used, deprecated_function_calls, 6 | deprecated_functions]}. 7 | {deps, 8 | [ 9 | {sqlite3, ".*", {git, "git://github.com/Feuerlabs/erlang-sqlite3.git", "1.0.1.fl.3"}}, 10 | {gproc, ".*", {git, "git://github.com/uwiger/gproc.git", "0.2.13.2"}}, 11 | {sext, ".*", {git, "git://github.com/uwiger/sext.git", "0.6"}}, 12 | {resource, ".*", {git, "git://github.com/Feuerlabs/resource.git", "0.2"}}, 13 | {eleveldb, ".*", {git, "git://github.com/Feuerlabs/eleveldb.git", "2.0.0.fl.1"}}, 14 | {edown, ".*", {git, "git://github.com/uwiger/edown.git", "HEAD"}}, 15 | {lager, ".*", {git, "git://github.com/basho/lager.git", "3.0.1"}}, 16 | {parse_trans, ".*", {git, "git://github.com/uwiger/parse_trans.git", "HEAD"}} 17 | ]}. 18 | {edoc_opts, [{doclet, edown_doclet}, 19 | {top_level_readme, 20 | {"./README.md", 21 | "http://github.com/Feuerlabs/kvdb"}}]}. 22 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %% 3 | %% If the environment variable X_COMP is set, it is an indication that self 4 | %% is being compiled as a dependency under exodev. Since exodev lists all 5 | %% dependency in its root rebar.config file in order to have a flat view of 6 | %% the dependency tree, we should nil out our local 7 | %% deps list provided by our local rebar.config 8 | %% 9 | CFG1 = case os:getenv("KVDB_BACKENDS") of 10 | Str when is_list(Str) -> 11 | Priv = filename:join(filename:dirname(SCRIPT), "priv"), 12 | {ok,Exclude} = file:script( 13 | filename:join(Priv, "excluded_apps.script"), 14 | [{'CWD', Priv}]), 15 | Deps0 = proplists:get_value(deps, CONFIG, []), 16 | Deps1 = [D || D <- Deps0, 17 | not lists:member(element(1,D), Exclude)], 18 | lists:keystore(deps, 1, CONFIG, {deps, Deps1}); 19 | false -> 20 | io:fwrite("Using all backends~n", []), 21 | CONFIG 22 | end. 23 | 24 | %% The following is a temporary fix for Feuerlabs' device SW build system 25 | case os:getenv("EXODEV_COMP") of 26 | E when E==false; E==[] -> 27 | case os:getenv("REBAR_DEPS") of 28 | D when D==false; D==[] -> CFG1; 29 | Dir -> lists:keystore(deps_dir, 1, CFG1, {deps_dir, Dir}) 30 | end; 31 | _ -> 32 | case lists:keytake(deps, 1, CFG1) of 33 | { value, _, Remainder } -> Remainder; 34 | _ -> CFG1 35 | end 36 | end. 37 | -------------------------------------------------------------------------------- /src/kvdb.app.src: -------------------------------------------------------------------------------- 1 | %%% -*- erlang -*- 2 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 3 | %%% 4 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 5 | %%% 6 | %%% This Source Code Form is subject to the terms of the Mozilla Public 7 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 8 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | %%% 10 | %%%---- END COPYRIGHT --------------------------------------------------------- 11 | %%% @author Ulf Wiger 12 | {application, kvdb, 13 | [ 14 | {description, "key value database API"}, 15 | {vsn, git}, 16 | {registered, []}, 17 | {applications, [ 18 | kernel, 19 | stdlib, 20 | gproc 21 | ]}, 22 | {mod, {kvdb_app, []}}, 23 | {start_phases, [{open_dbs, []}]}, 24 | {env, [ 25 | {backend, ets} % default 26 | %% {dir, DbDirectory} 27 | ]} 28 | ]}. 29 | -------------------------------------------------------------------------------- /src/kvdb_app.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | -module(kvdb_app). 13 | 14 | -behaviour(application). 15 | 16 | %% Application callbacks 17 | -export([start/2, stop/1, 18 | start_phase/3]). 19 | 20 | %% =================================================================== 21 | %% Application callbacks 22 | %% =================================================================== 23 | 24 | start(_StartType, _StartArgs) -> 25 | io:fwrite("starting kvdb~n", []), 26 | %% dbg:tracer(), 27 | %% dbg:tpl(kvdb,x), 28 | %% dbg:tpl(kvdb_sup,x), 29 | %% dbg:tp(kvdb_sqlite,x), 30 | %% dbg:tp(kvdb_leveldb,x), 31 | %% dbg:p(all,[c]), 32 | kvdb_sup:start_link(). 33 | 34 | start_phase(open_dbs, _, []) -> 35 | Dbs = get_databases(), 36 | io:fwrite("KVDB Dbs = ~p~n", [Dbs]), 37 | [{ok,_} = kvdb:open(Name, Opts) || {Name, Opts} <- Dbs], 38 | ok. 39 | 40 | stop(_State) -> 41 | ok. 42 | 43 | 44 | 45 | get_databases() -> 46 | %% If 'setup' is available, query for other databases 47 | OtherDBs = 48 | case lists:keymember(setup, 1, application:loaded_applications()) of 49 | true -> 50 | [DB || {_, DB} <- setup:find_env_vars(kvdb_databases)]; 51 | false -> 52 | [] 53 | end, 54 | lists:flatten( 55 | case application:get_env(databases) of 56 | {ok, DBs} when is_list(DBs) -> 57 | [DBs | OtherDBs]; 58 | _ -> 59 | OtherDBs 60 | end). 61 | -------------------------------------------------------------------------------- /src/kvdb_cron_parse.yrl: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %%---- BEGIN COPYRIGHT ------------------------------------------------------- 3 | %% 4 | %% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 5 | %% 6 | %% This Source Code Form is subject to the terms of the Mozilla Public 7 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 8 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | %% 10 | %%---- END COPYRIGHT --------------------------------------------------------- 11 | 12 | Nonterminals 13 | expr time_spec in_spec at_spec in_expr at_expr each_spec each_expr 14 | repeat_spec until_spec time_unit. 15 | 16 | Terminals 17 | integer 'in' 'at' 'each' fixnum '{' '}' ';' ':' ',' '/' '-' 18 | ms seconds hours minutes days months years times repeat until once forever 19 | daily weekly monthly yearly last_day_of_month. 20 | 21 | Rootsymbol expr. 22 | 23 | expr -> integer : {{in, ?line('$1'), [{'$1',{ms,?line('$1')}}]}, 24 | {nil,?line('$1')}, {nil, ?line('$1')}}. 25 | expr -> 'in' in_spec : {{in, ?line('$1'), '$2'}, 26 | {nil,?line('$2')}, {nil, ?line('$2')}}. 27 | expr -> 'at' at_spec : {{at, ?line('$1'), '$2'}, 28 | {nil,?line('$2')}, {nil, ?line('$2')}}. 29 | expr -> '{' time_spec '}' : {'$2', {nil,?line('$2')}, {nil, ?line('$2')}}. 30 | expr -> '{' time_spec ';' repeat_spec '}' : {'$2', '$4', {nil, ?line('$4')}}. 31 | expr -> '{' time_spec ';' repeat_spec ';' until_spec '}' : {'$2', '$4', '$6'}. 32 | 33 | time_spec -> 'in' in_spec : {in, ?line('$1'), '$2'}. 34 | time_spec -> 'at' at_spec : {at, ?line('$1'), '$2'}. 35 | 36 | repeat_spec -> repeat : {repeat, ?line('$1')}. 37 | repeat_spec -> once : {times, ?line('$1'), {integer, ?line('$1'), 1}}. 38 | repeat_spec -> integer 'times' : {times, ?line('$1'), '$1'}. 39 | repeat_spec -> 'each' each_spec : {each, ?line('$1'), '$2'}. 40 | repeat_spec -> daily : '$1'. 41 | repeat_spec -> weekly : '$1'. 42 | repeat_spec -> monthly : '$1'. 43 | repeat_spec -> yearly : '$1'. 44 | repeat_spec -> last_day_of_month : '$1'. 45 | 46 | until_spec -> forever : {nil, ?line('$1')}. 47 | until_spec -> until forever : {nil, ?line('$1')}. 48 | until_spec -> until at_spec : {at, ?line('$1'), '$2'}. 49 | until_spec -> at_spec : {at, ?line('$1'), '$1'}. 50 | 51 | in_spec -> integer : [{'$1', {ms, ?line('$1')}}]. 52 | in_spec -> in_expr : ['$1']. 53 | in_spec -> in_expr ',' in_spec : ['$1' | '$3']. 54 | 55 | in_expr -> integer time_unit : {'$1', '$2'}. 56 | in_expr -> last_day_of_month : '$1'. 57 | 58 | at_spec -> at_expr : ['$1']. 59 | at_spec -> at_expr ',' at_spec : ['$1' | '$3']. 60 | 61 | at_expr -> integer '/' integer '/' integer : 62 | {date, ?line('$1'), {y('$5'),m('$3'),d('$1')}}. 63 | at_expr -> integer '-' integer '-' integer : 64 | {date, ?line('$1'), {y('$1'),m('$3'),d('$5')}}. 65 | at_expr -> integer ':' integer ':' integer : 66 | {time, ?line('$1'), {{h('$1'),mi('$3'),s('$5')}, 0}}. 67 | at_expr -> integer ':' integer ':' fixnum : 68 | {time, ?line('$1'), 69 | {{h('$1'),mi('$3'),s(int('$5'))}, frac('$5')}}. 70 | 71 | each_spec -> each_expr : ['$1']. 72 | each_spec -> each_expr ',' each_spec : ['$1' | '$3']. 73 | 74 | each_expr -> integer time_unit : {'$1', '$2'}. 75 | 76 | 77 | time_unit -> ms : '$1'. 78 | time_unit -> seconds : '$1'. 79 | time_unit -> hours : '$1'. 80 | time_unit -> minutes : '$1'. 81 | time_unit -> days : '$1'. 82 | time_unit -> months : '$1'. 83 | time_unit -> years : '$1'. 84 | 85 | 86 | 87 | 88 | 89 | Erlang code. 90 | 91 | %% @hidden 92 | 93 | -define(line(T), line_of(T)). 94 | 95 | line_of([T|_]) -> 96 | line_of(T); 97 | line_of(T) when is_tuple(T) -> 98 | element(2, T). 99 | 100 | 101 | int({fixnum, L, {I,_}}) -> 102 | {integer, L, I}. 103 | 104 | frac({fixnum, L, {_,F}}) -> 105 | {integer, L, F}. 106 | 107 | y({integer,_,I} = T) when I >= 1000, I =< 9999 -> 108 | T; 109 | y({integer,L,I}) when I >= 0, I =< 99 -> 110 | {Y,_,_} = date(), 111 | {integer, L, (Y div 100) * 100 + I}; 112 | y(T) -> 113 | {error, ?line(T), {invalid_year, T}}. 114 | 115 | 116 | 117 | m({integer,_,I} = T) when I >= 0, I =< 12 -> 118 | T; 119 | m(T) -> 120 | {error, ?line(T), {invalid_month, T}}. 121 | 122 | d({integer, _, I} = T) when I >= 0, I =< 31 -> 123 | T; 124 | d(T) -> 125 | {error, ?line(T), {invalid_day, T}}. 126 | 127 | h({integer, _, I} = T) when I >= 0, I =< 23 -> 128 | T; 129 | h(T) -> 130 | {error, ?line(T), {invalid_hour, T}}. 131 | 132 | mi({integer, _, I} = T) when I >= 0, I =< 59 -> 133 | T; 134 | mi(T) -> 135 | {error, ?line(T), {invalid_minute, T}}. 136 | 137 | s({integer, _, I} = T) when I >= 0, I =< 59 -> 138 | T; 139 | s(T) -> 140 | {error, ?line(T), {invalid_second, T}}. 141 | -------------------------------------------------------------------------------- /src/kvdb_cron_scan.xrl: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %%---- BEGIN COPYRIGHT ------------------------------------------------------- 3 | %% 4 | %% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 5 | %% 6 | %% This Source Code Form is subject to the terms of the Mozilla Public 7 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 8 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | %% 10 | %%---- END COPYRIGHT --------------------------------------------------------- 11 | Definitions. 12 | 13 | D = [0-9] 14 | WS = ([\000-\s]|%.*) 15 | 16 | Rules. 17 | 18 | { : {token, {'{', TokenLine}}. 19 | } : {token, {'}', TokenLine}}. 20 | / : {token, {'/', TokenLine}}. 21 | , : {token, {',', TokenLine}}. 22 | : : {token, {':', TokenLine}}. 23 | ; : {token, {';', TokenLine}}. 24 | - : {token, {'-', TokenLine}}. 25 | 26 | {D}+ : 27 | {token, {integer, TokenLine, list_to_integer(TokenChars)}}. 28 | 29 | {D}+\.{D}+ : 30 | {token, {fixnum, TokenLine, mk_fixnum(TokenChars)}}. 31 | 32 | in : {token, {in, TokenLine}}. 33 | at : {token, {at, TokenLine}}. 34 | each : {token, {each, TokenLine}}. 35 | repeat : {token, {repeat, TokenLine}}. 36 | once : {token, {once, TokenLine}}. 37 | forever : {token, {forever, TokenLine}}. 38 | until : {token, {until, TokenLine}}. 39 | times : {token, {times, TokenLine}}. 40 | ms : {token, {ms, TokenLine}}. 41 | sec|secs|seconds : {token, {seconds, TokenLine}}. 42 | min|minutes : {token, {minutes, TokenLine}}. 43 | hr|hrs|hours : {token, {hours, TokenLine}}. 44 | mo|months : {token, {months, TokenLine}}. 45 | y|yr|years : {token, {years, TokenLine}}. 46 | daily : {token, {daily, TokenLine}}. 47 | monthly : {token, {monthly, TokenLine}}. 48 | weekly : {token, {weekly, TokenLine}}. 49 | annually : {token, {yearly, TokenLine}}. 50 | yearly : {token, {yearly, TokenLine}}. 51 | last_day_of_month : {token, {last_day_of_month, TokenLine}}. 52 | ldom : {token, {last_day_of_month, TokenLine}}. 53 | 54 | {WS}+ : skip_token. 55 | 56 | Erlang code. 57 | 58 | %%% @hidden 59 | 60 | mk_fixnum(Cs) -> 61 | [I,F] = re:split(Cs, <<"\\.">>, [{return, list}]), 62 | {list_to_integer(I), to_ms(F)}. 63 | 64 | to_ms([A,B,C|_]) -> list_to_integer("1" ++ [A,B,C]) - 1000; 65 | to_ms([_|_] = S) -> to_ms(S, 100). 66 | 67 | to_ms("0" ++ S, M) -> to_ms(S, M div 10); 68 | to_ms([], _) -> 0; 69 | to_ms(S, M) -> list_to_integer(S) * M. 70 | -------------------------------------------------------------------------------- /src/kvdb_cron_sup.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% 13 | -module(kvdb_cron_sup). 14 | 15 | -behaviour(supervisor). 16 | 17 | %% API 18 | -export([start_link/0]). 19 | -export([start_child/2]). 20 | %% childspec/1]). 21 | 22 | %% Supervisor callbacks 23 | -export([init/1]). 24 | 25 | %% Helper macro for declaring children of supervisor 26 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 27 | 28 | %% =================================================================== 29 | %% API functions 30 | %% =================================================================== 31 | 32 | start_link() -> 33 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 34 | 35 | %% =================================================================== 36 | %% Supervisor callbacks 37 | %% =================================================================== 38 | 39 | init([]) -> 40 | %% Children = childspecs(DBs = get_databases()), 41 | %% io:fwrite("DBs = ~p~n", [DBs]), 42 | {ok, { {simple_one_for_one, 5, 10}, 43 | [{id, {kvdb_cron, start_link, []}, 44 | transient, 5000, worker, [kvdb_cron]}] }}. 45 | 46 | 47 | start_child(Name, Options) -> 48 | supervisor:start_child(?MODULE, [Name, Options]). 49 | 50 | -------------------------------------------------------------------------------- /src/kvdb_db_sup.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% 13 | -module(kvdb_db_sup). 14 | 15 | -behaviour(supervisor). 16 | 17 | %% API 18 | -export([start_link/2, 19 | stop_child/1]). 20 | 21 | %% Supervisor callbacks 22 | -export([init/1]). 23 | 24 | %% Helper macro for declaring children of supervisor 25 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 26 | 27 | %% =================================================================== 28 | %% API functions 29 | %% =================================================================== 30 | 31 | start_link(Name, Options) -> 32 | supervisor:start_link(?MODULE, {Name, Options}). 33 | 34 | %% =================================================================== 35 | %% Supervisor callbacks 36 | %% =================================================================== 37 | 38 | init({Name, Options}) -> 39 | gproc:reg({n,l,{?MODULE, Name}}), 40 | {ok, { {rest_for_one, 5, 10}, 41 | [{proxy, {kvdb_proxy_sup, start_link, [Name, Options]}, 42 | permanent, infinity, supervisor, [kvdb_proxy_sup]}, 43 | {db, {kvdb_server, start_link, [Name, Options]}, 44 | permanent, 5000, worker, [kvdb_server]}, 45 | {cron, {kvdb_cron, start_link, [Name, Options]}, 46 | permanent, 5000, worker, [kvdb_cron]} 47 | ]}}. 48 | 49 | stop_child(Name) -> 50 | case gproc:where({n,l,{?MODULE,Name}}) of 51 | Pid when is_pid(Pid) -> 52 | supervisor:terminate_child(kvdb_sup, Pid); 53 | _ -> 54 | {error, unknown_db} 55 | end. 56 | -------------------------------------------------------------------------------- /src/kvdb_diff.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @author Tony Rogvall 12 | %%% @hidden 13 | %%% @doc 14 | %%% Simple diff algorithm 15 | %%% @end 16 | 17 | -module(kvdb_diff). 18 | 19 | -compile(export_all). 20 | 21 | %% 22 | %% diff to parts of a config tree like: 23 | %% A = <<"/devices/123/config/candidate">> 24 | %% B = <<"/devices/123/config/running">> 25 | %% paths(A,B) 26 | %% return a list of items: 27 | %% [ {add,{Key,As,Value,Tree}} | {delete,Key}, {update,Key,Value} ] 28 | %% that when applied to A make that subtree equal to B's subtree 29 | %% 30 | tree(A, B) -> 31 | tree_obj(A, prefix_read(A,A), B, prefix_read(B,B), []). 32 | 33 | tree_obj(Ax, {A,AObj={Ak,_As,Av}}, Bx, {B,BObj={Bk,Bs,Bv}}, Acc) -> 34 | if A < B -> 35 | tree_obj(Ax, prefix_next(Ax,Ak), Bx, {B,BObj}, 36 | [{delete,A}|Acc]); 37 | A > B -> 38 | tree_obj(Ax, {A,AObj}, Bx, prefix_next(Bx,Bk), 39 | [{add,{B,Bs,Bv}}|Acc]); 40 | A =:= B -> 41 | Acc1 = if Av =:= Bv -> Acc; 42 | true -> [{update,A,Bv}|Acc] 43 | end, 44 | tree_obj(Ax, prefix_next(Ax,Ak), Bx, prefix_next(Bx,Bk),Acc1) 45 | end; 46 | tree_obj(_Ax, done, _Bx, done, Acc) -> 47 | Acc; 48 | tree_obj(Ax, done, Bx, {B,{Bk,Bs,Bv}}, Acc) -> 49 | tree_obj(Ax, done, Bx, prefix_next(Bx,Bk),[{add,{B,Bs,Bv}}|Acc]); 50 | tree_obj(Ax, {A,{Ak,_,_}}, Bx, done, Acc) -> 51 | tree_obj(Ax, prefix_next(Ax,Ak), Bx, done, [{delete,A}|Acc]). 52 | 53 | prefix_read(Prefix, Key) -> 54 | case kvdb_conf:read(Key) of 55 | {ok, Obj} -> 56 | {remove_prefix(Prefix,Key),Obj}; 57 | {error,_} -> 58 | prefix_next(Prefix, Key) 59 | end. 60 | 61 | prefix_next(Prefix, Key) -> 62 | case kvdb_conf:next(Key) of 63 | {ok, Obj={Key1,_As,_Data}} -> 64 | case prefix(Prefix, Key1) of 65 | true -> 66 | {remove_prefix(Prefix,Key1), Obj}; 67 | false -> 68 | done 69 | end; 70 | done -> 71 | done 72 | end. 73 | 74 | %% Prefix = <<"">> | << "abc" >> | << "abc*edfg" >> 75 | remove_prefix(Prefix, Key) -> 76 | PrefixSz = byte_size(Prefix), 77 | case Key of 78 | <> -> 79 | Key1; 80 | <> -> 81 | Key1 82 | end. 83 | 84 | 85 | prefix(Key1, Key2) -> 86 | Key1Sz = byte_size(Key1), 87 | case Key2 of 88 | <> -> 89 | true; 90 | _ -> 91 | false 92 | end. 93 | 94 | trim_prefix(A, At) -> 95 | Parts = binary:split(A, <<"*">>, [global]), 96 | trim_parts(Parts, At). 97 | 98 | trim_parts([K|Ks], [{K,[],<<>>,T}]) -> 99 | trim_parts(Ks, T); 100 | trim_parts([], T) -> 101 | T. 102 | -------------------------------------------------------------------------------- /src/kvdb_ets_dumper.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2013. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | %% 19 | %% =================================================================== 20 | %% This code is a modified version of ets:tab2file/[2,3], 21 | %% adding a 'sync' option. This allows the dumped file to be flushed 22 | %% directly to disk. /Ulf Wiger 23 | %% =================================================================== 24 | -module(kvdb_ets_dumper). 25 | 26 | -export([tab2file/2, 27 | tab2file/3]). 28 | -export([file2tab/1, 29 | file2tab/2]). 30 | 31 | -type tab() :: atom() | tid(). 32 | %% a similar definition is also in erl_types 33 | -opaque tid() :: integer(). 34 | 35 | -define(MAJOR_F2T_VERSION,1). 36 | -define(MINOR_F2T_VERSION,0). 37 | 38 | -record(filetab_options, 39 | { 40 | object_count = false :: boolean(), 41 | md5sum = false :: boolean(), 42 | sync = false :: boolean() % UW addition 43 | }). 44 | 45 | 46 | %% file2tab/[1,2] are just here for symmetry; they pass through to ets. /UW 47 | -spec file2tab(Filename) -> {'ok', Tab} | {'error', Reason} when 48 | Filename :: file:name(), 49 | Tab :: tab(), 50 | Reason :: term(). 51 | 52 | file2tab(File) -> 53 | ets:file2tab(File). 54 | 55 | -spec file2tab(Filename, Options) -> {'ok', Tab} | {'error', Reason} when 56 | Filename :: file:name(), 57 | Tab :: tab(), 58 | Options :: [Option], 59 | Option :: {'verify', boolean()}, 60 | Reason :: term(). 61 | 62 | file2tab(File, Options) -> 63 | ets:file2tab(File, Options). 64 | 65 | 66 | -spec tab2file(Tab, Filename) -> 'ok' | {'error', Reason} when 67 | Tab :: tab(), 68 | Filename :: file:name(), 69 | Reason :: term(). 70 | 71 | tab2file(Tab, File) -> 72 | tab2file(Tab, File, []). 73 | 74 | 75 | 76 | -spec tab2file(Tab, Filename, Options) -> 'ok' | {'error', Reason} when 77 | Tab :: tab(), 78 | Filename :: file:name(), 79 | Options :: [Option], 80 | Option :: {'extended_info', [ExtInfo]}, 81 | ExtInfo :: 'md5sum' | 'object_count', 82 | Reason :: term(). 83 | 84 | tab2file(Tab, File, Options) -> 85 | try 86 | {ok, FtOptions} = parse_ft_options(Options), 87 | file:delete(File), 88 | case file:read_file_info(File) of 89 | {error, enoent} -> ok; 90 | _ -> throw(eaccess) 91 | end, 92 | Name = make_ref(), 93 | case disk_log:open([{name, Name}, {file, File}, {repair, truncate }]) of 94 | {ok, Name} -> ok; 95 | {repaired, Name} -> ok; 96 | {error, Reason} -> 97 | throw(Reason) 98 | end, 99 | try 100 | Info0 = case ets:info(Tab) of 101 | undefined -> 102 | %% erlang:error(badarg, [Tab, File, Options]); 103 | throw(badtab); 104 | I -> 105 | I 106 | end, 107 | Info = [list_to_tuple(Info0 ++ 108 | [{major_version,?MAJOR_F2T_VERSION}, 109 | {minor_version,?MINOR_F2T_VERSION}, 110 | {extended_info, 111 | ft_options_to_list(FtOptions)}])], 112 | {LogFun, InitState} = 113 | case FtOptions#filetab_options.md5sum of 114 | true -> 115 | {fun(Oldstate,Termlist) -> 116 | {NewState,BinList} = 117 | md5terms(Oldstate,Termlist), 118 | disk_log:blog_terms(Name,BinList), 119 | NewState 120 | end, 121 | erlang:md5_init()}; 122 | false -> 123 | {fun(_,Termlist) -> 124 | disk_log:log_terms(Name,Termlist), 125 | true 126 | end, 127 | true} 128 | end, 129 | ets:safe_fixtable(Tab,true), 130 | {NewState1,Num} = try 131 | NewState = LogFun(InitState,Info), 132 | dump_file( 133 | ets:select(Tab,[{'_',[],['$_']}],100), 134 | LogFun, NewState, 0) 135 | after 136 | (catch ets:safe_fixtable(Tab,false)) 137 | end, 138 | EndInfo = 139 | case FtOptions#filetab_options.object_count of 140 | true -> 141 | [{count,Num}]; 142 | false -> 143 | [] 144 | end ++ 145 | case FtOptions#filetab_options.md5sum of 146 | true -> 147 | [{md5,erlang:md5_final(NewState1)}]; 148 | false -> 149 | [] 150 | end, 151 | case EndInfo of 152 | [] -> 153 | ok; 154 | List -> 155 | LogFun(NewState1,[['$end_of_table',List]]) 156 | end, 157 | %% UW addition: 158 | case FtOptions#filetab_options.sync of 159 | true -> 160 | disk_log:sync(Name); 161 | false -> 162 | ok 163 | end, 164 | disk_log:close(Name) 165 | catch 166 | throw:TReason -> 167 | disk_log:close(Name), 168 | file:delete(File), 169 | throw(TReason); 170 | exit:ExReason -> 171 | disk_log:close(Name), 172 | file:delete(File), 173 | exit(ExReason); 174 | error:ErReason -> 175 | disk_log:close(Name), 176 | file:delete(File), 177 | erlang:raise(error,ErReason,erlang:get_stacktrace()) 178 | end 179 | catch 180 | throw:TReason2 -> 181 | {error,TReason2}; 182 | exit:ExReason2 -> 183 | {error,ExReason2} 184 | end. 185 | 186 | dump_file('$end_of_table', _LogFun, State, Num) -> 187 | {State,Num}; 188 | dump_file({Terms, Context}, LogFun, State, Num) -> 189 | Count = length(Terms), 190 | NewState = LogFun(State, Terms), 191 | dump_file(ets:select(Context), LogFun, NewState, Num + Count). 192 | 193 | 194 | ft_options_to_list(#filetab_options{md5sum = MD5, object_count = PS}) -> 195 | case PS of 196 | true -> 197 | [object_count]; 198 | _ -> 199 | [] 200 | end ++ 201 | case MD5 of 202 | true -> 203 | [md5sum]; 204 | _ -> 205 | [] 206 | end. 207 | 208 | md5terms(State, []) -> 209 | {State, []}; 210 | md5terms(State, [H|T]) -> 211 | B = term_to_binary(H), 212 | NewState = erlang:md5_update(State, B), 213 | {FinState, TL} = md5terms(NewState, T), 214 | {FinState, [B|TL]}. 215 | 216 | parse_ft_options(Options) when is_list(Options) -> 217 | {Opt,Rest} = case (catch lists:keytake(extended_info,1,Options)) of 218 | false -> 219 | {[],Options}; 220 | {value,{extended_info,L},R} when is_list(L) -> 221 | {L,R} 222 | end, 223 | parse_ft_info_options(#filetab_options{}, Opt ++ Rest); 224 | parse_ft_options(Malformed) -> 225 | throw({malformed_option, Malformed}). 226 | 227 | parse_ft_info_options(FtOpt,[]) -> 228 | {ok,FtOpt}; 229 | parse_ft_info_options(FtOpt,[object_count | T]) -> 230 | parse_ft_info_options(FtOpt#filetab_options{object_count = true}, T); 231 | parse_ft_info_options(FtOpt,[md5sum | T]) -> 232 | parse_ft_info_options(FtOpt#filetab_options{md5sum = true}, T); 233 | parse_ft_info_options(FtOpt, [sync | T]) -> 234 | parse_ft_info_options(FtOpt#filetab_options{sync = true}, T); 235 | parse_ft_info_options(_,[Unexpected | _]) -> 236 | throw({unknown_option,[{extended_info,[Unexpected]}]}); 237 | parse_ft_info_options(_,Malformed) -> 238 | throw({malformed_option,Malformed}). 239 | -------------------------------------------------------------------------------- /src/kvdb_export.erl: -------------------------------------------------------------------------------- 1 | -module(kvdb_export). 2 | 3 | -compile(export_all). 4 | -include("kvdb.hrl"). 5 | 6 | export(#kvdb_ref{mod = M, db = Db} = Ref, File) -> 7 | Format = format(File), 8 | Tabs = M:list_tables(Db), 9 | {Out, Close} = open_log(File, Format), 10 | try dump(Tabs, Ref, Out) 11 | after 12 | Close() 13 | end. 14 | 15 | import(#kvdb_ref{mod = M, db = Db}, File) -> 16 | import(M, Db, File, format(File)). 17 | 18 | import(M, Db, File) -> 19 | import(M, Db, File, format(File)). 20 | 21 | import(M, Db, File, binary) -> 22 | case disk_log:open([{name, ?MODULE}, 23 | {file, File}, 24 | {format, internal}]) of 25 | {ok, Log} -> 26 | try import_bin_log(Log, M, Db) 27 | after 28 | disk_log:close(Log) 29 | end; 30 | Error -> 31 | Error 32 | end. 33 | 34 | format(F) -> 35 | case filename:extension(F) of 36 | ".KBUPB" -> binary; 37 | _ -> error(unknown_extension) 38 | end. 39 | 40 | dump([T|Tabs], Ref, Out) -> 41 | #table{type = Type} = TabR = kvdb:info(Ref, {T, tabrec}), 42 | Out({table, T, lists:keydelete(name, 1, kvdb_lib:tabrec_to_list(TabR))}), 43 | with_tab(T, Type, Ref, Out), 44 | dump(Tabs, Ref, Out); 45 | dump([], _, _) -> 46 | ok. 47 | 48 | open_log(File, binary) -> 49 | case disk_log:open([{name, ?MODULE}, 50 | {file, File}, 51 | {format, internal}]) of 52 | {ok, Log} -> 53 | {fun(Term) -> 54 | disk_log:blog(Log, term_to_binary(Term, [compressed])) 55 | end, 56 | fun() -> 57 | disk_log:close(Log) 58 | end}; 59 | {error, Reason} -> 60 | error({Reason, {open_log, File}}) 61 | end. 62 | 63 | import_bin_log(Log, M, Db) -> 64 | import_bin_log(disk_log:bchunk(Log, start), Log, undefined, M, Db). 65 | 66 | import_bin_log({Cont, Bins}, Log, Tab, M, Db) -> 67 | Tab1 = import_bin_log_(Bins, Tab, M, Db), 68 | import_bin_log(disk_log:bchunk(Log, Cont), Log, Tab1, M, Db); 69 | import_bin_log({Cont, Bins, BadBytes}, Log, Tab, M, Db) -> 70 | io:fwrite("Warning: BadBytes = ~p~n", [BadBytes]), 71 | Tab1 = import_bin_log_(Bins, Tab, M, Db), 72 | import_bin_log(disk_log:bchunk(Log, Cont), Log, Tab1, M, Db); 73 | import_bin_log(eof, _, _, _, _) -> 74 | ok. 75 | 76 | import_bin_log_([Bin|Bins], Tab, M, Db) -> 77 | case binary_to_term(Bin) of 78 | {table, T, Opts} -> 79 | #table{type = Type} = TR = kvdb_lib:make_tabrec(T, Opts), 80 | M:add_table(Db, T, TR), 81 | import_bin_log_(Bins, {T,Type}, M, Db); 82 | {obj, Obj} -> 83 | add_to_tab(Tab, Obj, M, Db), 84 | import_bin_log_(Bins, Tab, M, Db) 85 | end; 86 | import_bin_log_([], Tab, _, _) -> 87 | Tab. 88 | 89 | add_to_tab(undefined, Obj, _, _) -> 90 | io:fwrite("Unknown tab, skipping ~p~n", [Obj]); 91 | add_to_tab({T, Type}, Obj, M, Db) -> 92 | IsQueue = kvdb_lib:valid_queue(Type), 93 | if Type == set -> 94 | M:put(Db, T, Obj); 95 | IsQueue == true -> 96 | {QK, St, O} = Obj, 97 | M:queue_insert(Db, T, QK, St, O) 98 | end. 99 | 100 | with_tab(Tab, Type, Ref, Out) -> 101 | IsQueue = kvdb_lib:valid_queue(Type), 102 | if Type == set -> 103 | Pat = make_pattern(Ref, Tab), 104 | chunk_load(kvdb:select(Ref, Tab, Pat, 1000), Out); 105 | IsQueue == true -> 106 | Filter = fun(St, QKey, Obj) -> 107 | {keep, {obj,{QKey, St, Obj}}} 108 | end, 109 | #kvdb_ref{mod = M, db = Db} = Ref, 110 | all_queues( 111 | fun(Q) -> 112 | load_queue( 113 | kvdb:list_queue(Ref, Tab, Q, Filter, false, 100), Out) 114 | end, M, Db, Tab) 115 | end. 116 | 117 | make_pattern(Db, Tab) -> 118 | case kvdb:info(Db, {Tab, encoding}) of 119 | {_,_,_} -> 120 | [{{'_','_','_'}, [], ['$_']}]; 121 | _ -> 122 | [{{'_','_'}, [], ['$_']}] 123 | end. 124 | 125 | 126 | chunk_load({Objs, Cont}, Out) -> 127 | [Out({obj, Obj}) || Obj <- Objs], 128 | chunk_load(Cont(), Out); 129 | chunk_load(done, _) -> 130 | ok. 131 | 132 | all_queues(F, M, Db, T) -> 133 | all_queues(M:first_queue(Db, T), F, M, Db, T). 134 | 135 | all_queues(done, _, _, _, _) -> 136 | done; 137 | all_queues({ok,Q}, F, M, Db, T) -> 138 | F(Q), 139 | all_queues(M:next_queue(Db, T, Q), F, M, Db, T). 140 | 141 | load_queue({Objs, Cont}, F) -> 142 | lists:foreach(F, Objs), 143 | load_queue(Cont(), F); 144 | load_queue(done, _) -> 145 | done. 146 | -------------------------------------------------------------------------------- /src/kvdb_log.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% @doc 13 | %%% Experimental logging API 14 | %%% @end 15 | -module(kvdb_log). 16 | 17 | -export([create_table/2, create_table/3, 18 | add_log/3, add_log/4]). 19 | -export([log/3, adjust/3, list/3]). 20 | 21 | -include("kvdb.hrl"). 22 | -record(i, {cur = 0, max = infinity}). 23 | 24 | create_table(Db, Table) -> 25 | create_table(Db, Table, {sext, term, term}). 26 | 27 | create_table(Db, Table, Enc) -> 28 | kvdb:add_table(Db, Table, [{type, fifo}, {encoding,Enc}]). 29 | 30 | add_log(Db, Table, Log) -> 31 | add_log(Db, Table, Log, infinity). 32 | 33 | add_log(Db, Table, Log, Max) when Max == infinity; 34 | is_integer(Max), Max > 0 -> 35 | case kvdb:is_queue_empty(Db, Table, Log) of 36 | true -> 37 | kvdb:queue_insert(Db, Table, meta_key(Log), 38 | inactive, meta_obj(Db, Table, Max)), 39 | ok; 40 | false -> 41 | {error, log_exists} 42 | end. 43 | 44 | log(_Db, _Table, _Obj) -> 45 | erlang:error(nyi). 46 | 47 | adjust(_Db, _Table, _Info) -> 48 | erlang:error(nyi). 49 | 50 | list(_Db, _Table, _Filter) -> 51 | erlang:error(nyi). 52 | 53 | meta_key(Log) -> 54 | #q_key{queue = Log, ts = 0, key = <<>>}. 55 | 56 | meta_obj(Db, Table, Max) -> 57 | case kvdb:info(Db, {Table, encoding}) of 58 | T when tuple_size(T) == 3 -> 59 | {<<>>, [], #i{max = Max}}; 60 | _ -> 61 | {<<>>, #i{max = Max}} 62 | end. 63 | -------------------------------------------------------------------------------- /src/kvdb_meta.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% @doc 13 | %%% KVDB meta-data management API 14 | %%% @end 15 | %%% 16 | -module(kvdb_meta). 17 | 18 | -export([write/3, 19 | write_new/3, 20 | read/3, 21 | delete/2, 22 | update_counter/3]). 23 | 24 | -include("kvdb.hrl"). 25 | 26 | write(#db{metadata = Ets}, Key, Value) -> 27 | ets:insert(Ets, {key(Key), Value}). 28 | 29 | write_new(#db{metadata = Ets}, Key, Value) -> 30 | ets:insert_new(Ets, {key(Key), Value}). 31 | 32 | read(#db{metadata = Ets}, Key, Default) -> 33 | case ets:lookup(Ets, key(Key)) of 34 | [] -> 35 | Default; 36 | [{_, Value}] -> 37 | Value 38 | end. 39 | 40 | delete(#db{metadata = Ets}, Key) -> 41 | %% io:fwrite("delete meta (~p) ~p~n", [Ets, Key]), 42 | ets:delete(Ets, key(Key)). 43 | 44 | update_counter(#db{metadata = Ets}, K, Incr) -> 45 | Key = key(K), 46 | try ets:update_counter(Ets, Key, Incr) 47 | catch 48 | error:_ -> 49 | ets:insert_new(Ets, {Key, 0}), 50 | ets:update_counter(Ets, Key, Incr) 51 | end. 52 | 53 | key(K) -> 54 | {'$kvdb_meta', K}. 55 | -------------------------------------------------------------------------------- /src/kvdb_paired.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Paired backend for kvdb 13 | %%% 14 | %%% NOTE: This is work in progress. Several things still do not work 15 | %%% 16 | %%% The idea with this backend is to combine two backends as a write-through 17 | %%% pair. That is: all reads are done on 'backend 1', but writes are served 18 | %%% in both 'backend 1' and 'backend 2'. An example of how to use this would 19 | %%% be e.g. a kvdb_ets backend in front of a kvdb_riak backend: 20 | %%% 21 | %%%
 22 | %%% kvdb:open(p, [{backend, kvdb_paired},
 23 | %%%               {module1, kvdb_ets},
 24 | %%%               {module2, kvdb_riak},
 25 | %%%               {options2, [{update_index, false}]}]).
 26 | %%% 
27 | %%% 28 | %%% The 'options2' list applies only to the 'backend 2' (riak in this case). 29 | %%% With `{update_index, false}' on 'backend 2', we will not maintain indexes 30 | %%% on the riak side, but rebuild them when the ets backend is populated. 31 | %%% @end 32 | -module(kvdb_paired). 33 | 34 | -behaviour(kvdb). 35 | 36 | -export([open/2, close/1]). 37 | -export([add_table/3, delete_table/2, list_tables/1]). 38 | -export([put/3, push/4, get/3, get_attrs/4, index_get/4, index_keys/4, 39 | update_counter/4, pop/3, prel_pop/3, extract/3, delete/3, 40 | list_queue/3, list_queue/6, list_queue/7, is_queue_empty/3, 41 | queue_read/3, queue_insert/5, queue_delete/3, mark_queue_object/4, 42 | queue_head_write/4, queue_head_read/3, queue_head_delete/3]). 43 | -export([first_queue/2, next_queue/3]). 44 | -export([first/2, last/2, next/3, prev/3, 45 | prefix_match/3, prefix_match/4, prefix_match_rel/5]). 46 | -export([get_schema_mod/2, 47 | schema_write/4, 48 | schema_read/3, 49 | schema_delete/3, 50 | schema_fold/3]). 51 | -export([info/2, is_table/2]). 52 | -export([dump_tables/1]). 53 | 54 | -export([proxy_childspecs/2]). 55 | 56 | -include("kvdb.hrl"). 57 | 58 | -define(if_table(Db, Tab, Expr), if_table(Db, Tab, fun() -> Expr end)). 59 | 60 | info(#db{ref = {{M,Db},_} = Ref}, What) -> 61 | case What of 62 | ref -> Ref; 63 | _ -> 64 | M:info(Db, What) 65 | end. 66 | 67 | proxy_childspecs(Name, Options) -> 68 | {M1, Opts1, M2, Opts2} = db_pair(Options), 69 | proxy_childspecs_(M1, Name, Opts1) ++ 70 | proxy_childspecs_(M2, Name, Opts2). 71 | 72 | proxy_childspecs_(M, Name, Opts) -> 73 | try M:proxy_childspecs(Name, Opts) 74 | catch 75 | error:undef -> 76 | [] 77 | end. 78 | 79 | is_table(#db{ref = {{M,Db},_}}, Tab) -> 80 | M:is_table(Db, Tab). 81 | 82 | get_schema_mod(#db{ref = {{M,Db},_}}, Default) -> 83 | M:get_schema_mod(Db, Default). 84 | 85 | schema_write(#db{ref = {{M1,Db1},{M2,Db2}}}, Cat, K, V) -> 86 | ok = M1:schema_write(Db1, Cat, K, V), 87 | ok = M2:schema_write(Db2, Cat, K, V). 88 | 89 | schema_read(#db{ref = {{M1,Db1},_}}, Cat, K) -> 90 | M1:schema_read(Db1, Cat, K). 91 | 92 | schema_delete(#db{ref = {{M1,Db1},{M2,Db2}}}, Cat, K) -> 93 | ok = M1:schema_delete(Db1, Cat, K), 94 | ok = M2:schema_delete(Db2, Cat, K). 95 | 96 | schema_fold(#db{ref = {{M1,Db1},_}}, F, A) -> 97 | M1:schema_fold(Db1, F, A). 98 | 99 | dump_tables(#db{ref = {{M,Db},_}}) -> 100 | M:dump_tables(Db). 101 | 102 | 103 | open(DbName, Options) -> 104 | {M1, O1, M2, O2} = db_pair(Options), 105 | case M1:open(DbName, O1) of 106 | {ok, Db1} -> 107 | case M2:open(DbName, O2) of 108 | {ok, Db2} -> 109 | Db = Db1#db{ref = {{M1, Db1}, {M2, Db2}}}, 110 | ok = load(Db), 111 | {ok, Db}; 112 | Error2 -> 113 | M1:close(Db1), 114 | Error2 115 | end; 116 | Error1 -> 117 | Error1 118 | end. 119 | 120 | db_pair(Options) -> 121 | {_, M1} = lists:keyfind(module1, 1, Options), 122 | {_, M2} = lists:keyfind(module2, 1, Options), 123 | {O1, O2} = split_options(Options), 124 | {M1, O1, M2, O2}. 125 | 126 | split_options(Options) -> 127 | {O1, R1} = take(options1, Options), 128 | {O2, R2} = take(options2, R1), 129 | {O1 ++ R2, O2 ++ R2}. 130 | 131 | take(K, Options) -> 132 | case lists:keytake(K, 1, Options) of 133 | {value, {_, Found}, Rest} -> 134 | {Found, Rest}; 135 | false -> 136 | {[], Options} 137 | end. 138 | 139 | load(#db{ref = {{M1,Db1}, {M2, Db2}}} = Db) -> 140 | Tabs = M2:list_tables(Db2), 141 | lists:foreach( 142 | fun(T) -> 143 | case M2:schema_read(Db2, property, {T,autoload}) of 144 | false -> 145 | M1:schema_write(Db1, property, {T, ram}, false), 146 | ok; 147 | _ -> 148 | M1:schema_write(Db1, property, {T, ram}, true), 149 | M1:delete_table(Db1, T), 150 | TabR = M2:info(Db2, {T, tabrec}), 151 | M1:add_table(Db1, T, TabR), 152 | case has_disk(M2, Db2, T) of 153 | false -> 154 | io:fwrite("Table ~s defined as {disk, false}~n", [T]), 155 | ok; 156 | _ -> 157 | case TabR#table.type of 158 | set -> 159 | chunk_load(M2:prefix_match(Db2, T, <<>>, 1000), 160 | M1, Db1, T); 161 | Type when Type==fifo; Type==lifo; 162 | Type=={keyed,fifo}; Type=={keyed,lifo} -> 163 | Filter = fun(St, QKey, Obj) -> 164 | {keep, {QKey, St, Obj}} 165 | end, 166 | all_queues( 167 | fun(Q) -> 168 | load_queue( 169 | M2:list_queue(Db2, T, Q, 170 | Filter, false, 100), 171 | M1, Db1, T) 172 | end, M2, Db2, T) 173 | end 174 | end 175 | end 176 | end, Tabs), 177 | load_schema(Db). 178 | 179 | load_schema(#db{ref = {{M1,Db1}, {M2, Db2}}}) -> 180 | M2:schema_fold( 181 | Db2, fun(C, {K,V}, _) -> 182 | case M1:schema_read(Db1, C, K) of 183 | undefined -> 184 | M1:schema_write(Db1, C, K, V); 185 | _ -> 186 | ok 187 | end 188 | end, ok). 189 | 190 | all_queues(F, M, Db, T) -> 191 | all_queues(M:first_queue(Db, T), F, M, Db, T). 192 | 193 | all_queues(done, _, _, _, _) -> 194 | done; 195 | all_queues({ok,Q}, F, M, Db, T) -> 196 | F(Q), 197 | all_queues(M:next_queue(Db, T, Q), F, M, Db, T). 198 | 199 | load_queue({Objs, Cont}, M, Db, T) -> 200 | lists:foreach( 201 | fun({QKey, St, Obj}) -> 202 | M:queue_insert(Db, T, QKey, St, Obj) 203 | end, Objs), 204 | load_queue(Cont(), M, Db, T); 205 | load_queue(done, _, _, _) -> 206 | done. 207 | 208 | 209 | 210 | chunk_load({Objs, Cont}, M, Db, T) -> 211 | [M:put(Db, T, Obj) || Obj <- Objs], 212 | chunk_load(Cont(), M, Db, T); 213 | chunk_load(done, _, _, _) -> 214 | ok. 215 | 216 | 217 | close(#db{ref = {{M1,Db1},{M2,Db2}}}) -> 218 | M1:close(Db1), 219 | M2:close(Db2). 220 | 221 | add_table(#db{ref = {{M1,Db1},{M2,Db2}}}, Table, Opts) when is_list(Opts) -> 222 | case M1:info(Db1, {Table, type}) of 223 | undefined -> 224 | _TabR = kvdb_lib:make_tabrec(Table, check_encoding(Opts, Db1)), 225 | M2:add_table(Db2, Table, Opts), 226 | M1:add_table(Db1, Table, Opts); 227 | _ -> ok 228 | end; 229 | add_table(#db{ref = {{M1,Db1},{M2,Db2}}}, Table, #table{} = TabR) -> 230 | case M1:is_table(Db1, Table) of 231 | true -> ok; 232 | false -> 233 | M2:add_table(Db2, Table, TabR), 234 | M1:add_table(Db1, Table, TabR) 235 | end. 236 | 237 | 238 | list_tables(#db{ref = {{M,Db},_}}) -> 239 | M:list_tables(Db). 240 | 241 | delete_table(#db{ref = {{M1,Db1},{M2,Db2}}}, Table) -> 242 | case M2:delete_table(Db2, Table) of 243 | ok -> 244 | M1:delete_table(Db1, Table); 245 | Other -> 246 | Other 247 | end. 248 | 249 | put(#db{ref = {{M1,Db1},{M2,Db2}}}, Table, Obj) -> 250 | case has_disk(M1, Db1, Table) of 251 | true -> 252 | case M2:put(Db2, Table, Obj) of 253 | ok -> 254 | M1:put(Db1, Table, Obj); 255 | Other -> 256 | Other 257 | end; 258 | false -> 259 | M1:put(Db1, Table, Obj) 260 | end. 261 | 262 | update_counter(#db{ref = {{M1,Db1},{M2,Db2}}}, Table, Key, Incr) -> 263 | NewValue = M1:update_counter(Db1, Table, Key, Incr), 264 | {ok, Obj} = M1:get(Db1, Table, Key), 265 | case has_disk(M2, Db2, Table) of 266 | true -> M2:put(Db2, Table, Obj); 267 | false -> ok 268 | end, 269 | NewValue. 270 | %% M2:update_counter(Db2, Table, Key, Incr), 271 | %% M1:update_counter(Db1, Table, Key, Incr). 272 | 273 | push(#db{ref = {{M1,Db1},{M2,Db2}}}, Table, Q, Obj) -> 274 | case has_disk(M1, Db1, Table) of 275 | false -> ok; 276 | _ -> 277 | M2:push(Db2, Table, Q, Obj) 278 | end, 279 | M1:push(Db1, Table, Q, Obj). 280 | 281 | get(#db{ref = {{M,Db},_}}, Tab, K) -> 282 | M:get(Db, Tab, K). 283 | 284 | get_attrs(#db{ref = {{M,Db},_}}, Tab, K, As) -> 285 | M:get_attrs(Db, Tab, K, As). 286 | 287 | index_get(#db{ref = {{M,Db},_}}, Tab, K, V) -> 288 | M:index_get(Db, Tab, K, V). 289 | 290 | index_keys(#db{ref = {{M,Db},_}}, T, K, V) -> 291 | M:index_keys(Db, T, K, V). 292 | 293 | pop(#db{ref = {{M1,Db1},{M2,Db2}}}, T, Q) -> 294 | M2:pop(Db2, T, Q), 295 | M1:pop(Db1, T, Q). 296 | 297 | prel_pop(#db{ref = {{M1,Db1},{M2,Db2}}}, T, Q) -> 298 | M2:prel_pop(Db2, T, Q), 299 | M1:prel_pop(Db1, T, Q). 300 | 301 | extract(#db{ref = {{M1,Db1},{M2,Db2}}}, T, K) -> 302 | M2:extract(Db2, T, K), 303 | M1:extract(Db1, T, K). 304 | 305 | delete(#db{ref = {{M1,Db1},{M2,Db2}}}, T, K) -> 306 | case has_disk(M1, Db1, T) of 307 | true -> M2:delete(Db2, T, K); 308 | false -> ok 309 | end, 310 | M1:delete(Db1, T, K). 311 | 312 | list_queue(#db{ref = {{M,Db},_}}, T, Q) -> 313 | M:list_queue(Db, T, Q). 314 | 315 | list_queue(#db{ref = {{M,Db},_}}, T, Q, Fltr, HeedBlock, Limit) -> 316 | M:list_queue(Db, T, Q, Fltr, HeedBlock, Limit). 317 | 318 | list_queue(#db{ref = {{M,Db},_}}, T, Q, Fltr, HeedBlock, Limit, Reverse) -> 319 | M:list_queue(Db, T, Q, Fltr, HeedBlock, Limit, Reverse). 320 | 321 | is_queue_empty(#db{ref = {{M,Db},_}}, T, Q) -> 322 | M:is_queue_empty(Db, T, Q). 323 | 324 | queue_read(#db{ref = {{M,Db},_}}, T, K) -> 325 | M:queue_read(Db, T, K). 326 | 327 | queue_insert(#db{ref = {{M1,Db1},{M2,Db2}}}, T, K, St, Obj) -> 328 | case has_disk(M1, Db1, T) of 329 | true -> M2:queue_insert(Db2, T, K, St, Obj); 330 | false -> ok 331 | end, 332 | M1:queue_insert(Db1, T, K, St, Obj). 333 | 334 | queue_delete(#db{ref = {{M1,Db1},{M2,Db2}}}, T, K) -> 335 | case has_disk(M1, Db1, T) of 336 | true -> M2:queue_delete(Db2, T, K); 337 | false -> ok 338 | end, 339 | M1:queue_delete(Db1, T, K). 340 | 341 | mark_queue_object(#db{ref = {{M1,Db1},{M2,Db2}}}, Tab, K, St) -> 342 | case has_disk(M1, Db1, Tab) of 343 | true -> M2:mark_queue_object(Db2, Tab, K, St); 344 | false -> ok 345 | end, 346 | M1:mark_queue_object(Db1, Tab, K, St). 347 | 348 | queue_head_write(#db{ref = {{M1,Db1},{M2,Db2}}}, Tab, Q, Obj) -> 349 | case has_disk(M1, Db1, Tab) of 350 | true -> M2:queue_head_write(Db2, Tab, Q, Obj); 351 | false -> ok 352 | end, 353 | M1:queue_head_write(Db1, Tab, Q, Obj). 354 | 355 | queue_head_read(#db{ref = {{M1,Db1},_}}, Tab, Q) -> 356 | M1:queue_head_read(Db1, Tab, Q). 357 | 358 | queue_head_delete(#db{ref = {{M1,Db1},{M2,Db2}}}, Tab, Q) -> 359 | case has_disk(M1, Db1, Tab) of 360 | true -> M2:queue_head_delete(Db2, Tab, Q); 361 | false -> ok 362 | end, 363 | M1:queue_head_delete(Db1, Tab, Q). 364 | 365 | first_queue(#db{ref = {{M,Db},_}}, Tab) -> 366 | M:first_queue(Db, Tab). 367 | 368 | next_queue(#db{ref = {{M,Db},_}}, Tab, Q) -> 369 | M:next_queue(Db, Tab, Q). 370 | 371 | first(#db{ref = {{M,Db},_}}, Tab) -> 372 | M:first(Db, Tab). 373 | 374 | last(#db{ref = {{M,Db},_}}, Tab) -> 375 | M:last(Db, Tab). 376 | 377 | next(#db{ref = {{M,Db},_}}, Tab, K) -> 378 | M:next(Db, Tab, K). 379 | 380 | prev(#db{ref = {{M,Db},_}}, Tab, K) -> 381 | M:prev(Db, Tab, K). 382 | 383 | prefix_match(#db{ref = {{M,Db},_}}, Tab, Pfx) -> 384 | M:prefix_match(Db, Tab, Pfx). 385 | 386 | prefix_match(#db{ref = {{M,Db},_}}, Tab, Pfx, Limit) -> 387 | M:prefix_match(Db, Tab, Pfx, Limit). 388 | 389 | prefix_match_rel(#db{ref = {{M,Db},_}}, Tab, Prefix, Start, Limit) -> 390 | M:prefix_match_rel(Db, Tab, Prefix, Start, Limit). 391 | 392 | 393 | check_encoding(Opts, #db{encoding = Enc0}) -> 394 | case lists:keymember(encoding, 1, Opts) of 395 | true -> 396 | Opts; 397 | false -> 398 | [{encoding, Enc0}|Opts] 399 | end. 400 | 401 | has_disk(M1, Db1, T) -> 402 | case M1:schema_read(Db1, property, {T, disk}) of 403 | false -> false; 404 | _ -> true 405 | end. 406 | -------------------------------------------------------------------------------- /src/kvdb_proxy_sup.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Database proxy supervisor. 13 | %%% 14 | %%% NOTE: This is work in progress, highly experimental. 15 | %%% The general idea is that a backend can specify 'proxy processes' 16 | %%% as a list of childspecs. An example of such a proxy is kvdb_riak_proxy. 17 | %%% 18 | %%% @end 19 | -module(kvdb_proxy_sup). 20 | -behaviour(supervisor). 21 | 22 | -export([start_link/2]). 23 | 24 | -export([init/1]). 25 | 26 | %% =================================================================== 27 | %% API functions 28 | %% =================================================================== 29 | 30 | start_link(Name, Options) -> 31 | supervisor:start_link(?MODULE, {Name, Options}). 32 | 33 | %% =================================================================== 34 | %% Supervisor callbacks 35 | %% =================================================================== 36 | 37 | init({Name, Options}) -> 38 | gproc:reg({n,l,{?MODULE,Name}}), 39 | Backend = proplists:get_value(backend, Options, ets), 40 | Mod = kvdb_lib:backend_mod(Backend), 41 | Children = try Mod:proxy_childspecs(Name, Options) 42 | catch 43 | error:undef -> 44 | [] 45 | end, 46 | {ok, { {one_for_one, 5, 10}, Children } }. 47 | -------------------------------------------------------------------------------- /src/kvdb_queue.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Useful queue inspection and management functions 13 | %%% @end 14 | %%% 15 | -module(kvdb_queue). 16 | 17 | -export([push/3, push/4, 18 | pop/2, pop/3, 19 | prel_pop/2, prel_pop/3, 20 | delete/3, extract/3, 21 | is_empty/3, 22 | size/3, 23 | list_queues/2, list_queues/3, 24 | list/3, list/6, list_full/3, 25 | first/2, 26 | next/3, 27 | clear_queue/3, 28 | clear_queues/2]). 29 | 30 | -export([info/4]). 31 | 32 | push(Db, Table, Obj) -> push(Db, Table, Obj). 33 | push(Db, Table, Q, Obj) -> kvdb:push(Db, Table, Q, Obj). 34 | pop(Db, Table) -> pop(Db, Table, <<>>). 35 | pop(Db, Table, Q) -> kvdb:pop(Db, Table, Q). 36 | prel_pop(Db, Table) -> prel_pop(Db, Table, <<>>). 37 | prel_pop(Db, Table, Q) -> kvdb:prel_pop(Db, Table, Q). 38 | delete(Db, Table, Key) -> kvdb:delete(Db, Table, Key). 39 | extract(Db, Table, Key) -> kvdb:extract(Db, Table, Key). 40 | is_empty(Db, Table, Q) -> kvdb:is_queue_empty(Db, Table, Q). 41 | list(Db, Table, Q) -> kvdb:list_queue(Db, Table, Q). 42 | list(Db,Tab,Q,Fltr,HeedBlock,Limit) -> 43 | kvdb:list_queue(Db, Tab, Q, Fltr, HeedBlock, Limit). 44 | list_full(Db,Tab,Q) -> 45 | list(Db, Tab, Q, fun(S,K,O) -> {keep,{S,K,O}} end, false, infinity). 46 | first(Db, Table) -> kvdb:first_queue(Db, Table). 47 | next(Db, Table, PrevQ) -> kvdb:next_queue(Db, Table, PrevQ). 48 | 49 | size(Db, Table, Q) -> 50 | try kvdb:queue_head_read(Db, Table, Q) of 51 | {error, not_found} -> 52 | 0; 53 | {ok, {_,<>}} -> Sz; 54 | {ok, {_, _, <>}} -> Sz 55 | catch 56 | error:badarg -> undefined 57 | end. 58 | 59 | list_queues(Db, Table) -> 60 | list_queues(Db, Table, 30). 61 | 62 | list_queues(Db, Table, Limit) when 63 | Limit == infinity; is_integer(Limit), Limit >= 0 -> 64 | list_queues_(first(Db, Table), Db, Table, Limit, Limit, []). 65 | 66 | clear_queue(Db, Table, Q) -> 67 | clear_queue_(kvdb:list_queue(Db, Table, Q, 68 | fun(_,K,_) -> {keep,K} end, 69 | false, infinity), Db, Table, Q). 70 | 71 | clear_queues(Db, Table) -> 72 | clear_queues_(first(Db, Table), Db, Table). 73 | 74 | clear_queues_({ok, Q}, Db, Table) -> 75 | clear_queue(Db, Table, Q), 76 | clear_queues_(next(Db, Table, Q), Db, Table); 77 | clear_queues_(done, _, _) -> 78 | done. 79 | 80 | 81 | clear_queue_(done, _, _, _) -> 82 | done; 83 | clear_queue_({Keys, Cont}, Db, Table, Q) -> 84 | lists:foreach( 85 | fun(Key) -> 86 | kvdb:delete(Db, Table, Key) 87 | end, Keys), 88 | clear_queue_(Cont(), Db, Table, Q). 89 | 90 | list_queues_({ok, Q}, Db, Table, Limit0, Limit, Acc) -> 91 | case decr(Limit) of 92 | 0 -> 93 | {lists:reverse([Q|Acc]), 94 | fun() -> 95 | Next = next(Db, Table, Q), 96 | list_queues_(Next, Db, Table, Limit0, Limit0, []) 97 | end}; 98 | Limit1 -> 99 | list_queues_(next(Db, Table, Q), Db, Table, Limit0, Limit1, [Q|Acc]) 100 | end; 101 | list_queues_(done, _Db, _Table, _Limit0, _Limit, Acc) -> 102 | if Acc == [] -> done; 103 | true -> {lists:reverse(Acc), fun() -> done end} 104 | end. 105 | 106 | decr(infinity) -> 107 | infinity; 108 | decr(I) when is_integer(I) -> 109 | I-1. 110 | 111 | 112 | info(Db, Table, Q, size) -> 113 | case kvdb:queue_head_read(Db, Table, Q) of 114 | {ok, {_, <>}} -> Sz; 115 | {ok, {_, _, <>}} -> Sz; 116 | _ -> 0 117 | end; 118 | info(Db, Table, _Q, type) -> 119 | case kvdb:info(Db, {Table, type}) of 120 | T when T==fifo; T==lifo -> T; 121 | T when element(1,T) == keyed -> T; 122 | {fifo,_} = T -> T; 123 | {lifo,_} = T -> T; 124 | undefined -> undefined; 125 | Other -> 126 | error({not_a_queue, Other}) 127 | end. 128 | -------------------------------------------------------------------------------- /src/kvdb_riak.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Highly experimental riak backend for kvdb 13 | %%% 14 | %%% NOTE: This is work in progress. Several things still do not work 15 | %%% 16 | %%% The best way to use this for now is via the kvdb_paired backend. 17 | %%% @end 18 | -module(kvdb_riak). 19 | 20 | -behaviour(kvdb). 21 | 22 | -export([open/2, close/1]). 23 | -export([add_table/3, delete_table/2, list_tables/1]). 24 | -export([put/3, push/4, get/3, get_attrs/4, index_get/4, index_keys/4, 25 | update_counter/4, pop/3, prel_pop/3, extract/3, delete/3, 26 | list_queue/3, list_queue/6, is_queue_empty/3, 27 | queue_read/3, queue_insert/5, queue_delete/3, mark_queue_object/4, 28 | queue_head_write/4, queue_head_read/3, queue_head_delete/3]). 29 | -export([first_queue/2, next_queue/3]). 30 | -export([first/2, last/2, next/3, prev/3, 31 | prefix_match/3, prefix_match/4, prefix_match_rel/5]). 32 | -export([get_schema_mod/2]). 33 | -export([info/2, is_table/2]). 34 | -export([dump_tables/1]). 35 | 36 | -export([proxy_childspecs/2]). 37 | 38 | -include("kvdb.hrl"). 39 | %% This is obviously a crude hack (riak_kv_wm_raw.hrl copied into include/) 40 | -include("riak_kv_wm_raw.hrl"). 41 | 42 | -define(if_table(Db, Tab, Expr), if_table(Db, Tab, fun() -> Expr end)). 43 | 44 | -record(ref, {bucket, 45 | update_index = true}). 46 | 47 | info(#db{} = Db, What) -> 48 | case What of 49 | tables -> list_tables(Db); 50 | encoding -> Db#db.encoding; 51 | ref -> Db#db.ref; 52 | {Tab,encoding} -> ?if_table(Db, Tab, encoding(Db, Tab)); 53 | {Tab,index } -> ?if_table(Db, Tab, index(Db, Tab)); 54 | {Tab,type } -> ?if_table(Db, Tab, type(Db, Tab)); 55 | {Tab,schema } -> ?if_table(Db, Tab, schema(Db, Tab)); 56 | {Tab,tabrec } -> schema_lookup(Db, {table, Tab}, undefined); 57 | _ -> undefined 58 | end. 59 | 60 | encoding(_, _) -> error(nyi). 61 | index(#db{ref = #ref{update_index = B}} = Db, Tab) -> 62 | if B -> schema_lookup(Db, {Tab, index}, []); 63 | true -> [] 64 | end. 65 | type(_, _) -> error(nyi). 66 | schema(_, _) -> error(nyi). 67 | 68 | schema_lookup(#db{metadata = Ets}, Key, Default) -> 69 | case ets:lookup(Ets, Key) of 70 | [] -> 71 | Default; 72 | [{_, V}] -> 73 | V 74 | end. 75 | 76 | schema_write(#db{metadata = Ets}, {_, _} = Obj) -> 77 | ets:insert(Ets, Obj). 78 | 79 | is_table(#db{metadata = ETS}, Tab) -> 80 | ets:member(ETS, {table, Tab}). 81 | 82 | get_schema_mod(Db, Default) -> 83 | case schema_lookup(Db, schema_mod, undefined) of 84 | undefined -> 85 | schema_write(Db, {schema_mod, Default}), 86 | Default; 87 | M -> 88 | M 89 | end. 90 | 91 | if_table(Db, Tab, F) -> 92 | case is_table(Db, Tab) of 93 | true -> F(); 94 | false -> undefined 95 | end. 96 | 97 | dump_tables(_) -> 98 | []. 99 | 100 | 101 | proxy_childspecs(Name, Options) -> 102 | [{riak_proxy, {kvdb_riak_proxy, start_link, [Name, Options]}, 103 | permanent, 5000, worker, [kvdb_riak_proxy]}]. 104 | 105 | open(DbName, Options) -> 106 | case kvdb_riak_proxy:get_session(DbName) of 107 | undefined -> 108 | error(no_session); % might want to improve this later 109 | Session -> 110 | Ets = ets:new(kvdb_schema, [ordered_set, public]), 111 | ets:insert(Ets, {riak_session, Session}), 112 | Ref = #ref{bucket = <<"kvdb*", (to_bin(DbName))/binary>>, 113 | update_index = proplists:get_value(update_index, 114 | Options, true)}, 115 | Db = load_schema(#db{ref = Ref, metadata = Ets}, Session), 116 | {ok, Db} 117 | end. 118 | 119 | close(Db) -> 120 | case session_pid(Db) of 121 | undefined -> ok; 122 | {client, _} -> ok; 123 | {pb, Session} -> 124 | riakc_pb_socket:stop(Session), 125 | ok 126 | end. 127 | 128 | add_table(#db{ref = Ref, metadata = Ets} = Db, Table, Opts) -> 129 | case is_table(Db, Table) of 130 | false -> 131 | TabR = case Opts of 132 | _ when is_list(Opts) -> 133 | kvdb_lib:make_tabrec(Table, Opts); 134 | #table{} -> 135 | Opts 136 | end, 137 | case session_pid(Db) of 138 | undefined -> error(no_session); 139 | Session -> 140 | riak_put(Session, Ref#ref.bucket, Table, 141 | {Table, TabR}, []), 142 | write_tabrec_ets(Ets, TabR) 143 | end; 144 | true -> 145 | {error, exists} 146 | end. 147 | 148 | 149 | list_tables(#db{metadata = Ets}) -> 150 | ets:select(Ets, [{ {{table,'$1'},'_'}, [], ['$1']}]). 151 | 152 | delete_table(#db{}, _Table) -> 153 | error(nyi). 154 | 155 | put(#db{ref = Ref} = Db, Table, {K,_} = Obj) -> 156 | case session_pid(Db) of 157 | undefined -> error(no_session); 158 | Session -> 159 | Bucket = tab_key(Ref, Table), 160 | riak_put(Session, Bucket, K, Obj, []), 161 | ok 162 | end; 163 | put(#db{ref = Ref} = Db, Table, {K, Attrs, V} = Obj) -> 164 | case session_pid(Db) of 165 | undefined -> error(no_session); 166 | Session -> 167 | Ix = index(Db, Table), 168 | IxVals = kvdb_lib:index_vals(Ix, K, Attrs, fun() -> V end), 169 | riak_put(Session, tab_key(Ref, Table), K, Obj, IxVals), 170 | ok 171 | end. 172 | 173 | riak_put({pb, Session}, Bucket, K, Val, IxVals) -> 174 | O = riakc_obj:new(Bucket, K, Val), 175 | O1 = if IxVals =/= [] -> 176 | riakc_obj:set_secondary_index( 177 | O, [{{binary_index, to_bin(N)}, [I]} 178 | || {N,I} <- IxVals]); 179 | true -> 180 | O 181 | end, 182 | riakc_pb_socket:put(Session, O1); 183 | riak_put({client, Session}, Bucket, K, Val0, IxVals) -> 184 | Val = term_to_binary(Val0), 185 | Obj = if IxVals =/= [] -> 186 | Ix = [{ix_field(Ni), Vi} || {Ni, Vi} <- IxVals], 187 | riak_object:new(Bucket, K, Val, 188 | dict:from_list( 189 | [{?MD_INDEX, Ix}])); 190 | true -> 191 | riak_object:new(Bucket, K, Val) 192 | end, 193 | riak_client:put(Obj, Session). 194 | 195 | ix_field(Name) -> 196 | <<(to_bin(Name))/binary, "_bin">>. 197 | 198 | to_bin(B) when is_binary(B) -> B; 199 | to_bin(L) when is_list(L) -> list_to_binary(L); 200 | to_bin(A) when is_atom(A) -> atom_to_binary(A, latin1). 201 | 202 | update_counter(_Db, _Table, _Key, _Incr) -> 203 | error(nyi). 204 | 205 | push(_, _, _, _) -> 206 | error(nyi). 207 | 208 | get(#db{ref = Ref} = Db, Tab, K) -> 209 | case session_pid(Db) of 210 | undefined -> error(no_session); 211 | Session -> 212 | riak_get(tab_key(Ref, Tab), K, Session) 213 | end. 214 | 215 | get_attrs(_, _, _, _) -> 216 | error(nyi). 217 | 218 | index_get(_, _, _, _) -> 219 | error(nyi). 220 | 221 | index_keys(_, _, _, _) -> 222 | error(nyi). 223 | 224 | pop(_, _, _) -> 225 | error(nyi). 226 | 227 | prel_pop(_, _, _) -> 228 | error(nyi). 229 | 230 | extract(_, _, _) -> 231 | error(nyi). 232 | 233 | delete(#db{ref = Ref} = Db, Tab, K) -> 234 | case session_pid(Db) of 235 | undefined -> error(no_session); 236 | Session -> 237 | riak_delete(tab_key(Ref, Tab), K, Session) 238 | end. 239 | 240 | list_queue(_, _, _) -> 241 | error(nyi). 242 | 243 | list_queue(_, _, _, _, _, _) -> 244 | error(nyi). 245 | 246 | is_queue_empty(_, _, _) -> 247 | error(nyi). 248 | 249 | queue_read(_, _, _) -> 250 | error(nyi). 251 | 252 | queue_insert(_, _, _, _, _) -> 253 | error(nyi). 254 | 255 | queue_delete(_, _, _) -> 256 | error(nyi). 257 | 258 | mark_queue_object(_, _, _, _) -> 259 | error(nyi). 260 | 261 | queue_head_write(_, _, _, _) -> 262 | error(nyi). 263 | 264 | queue_head_read(_, _, _) -> 265 | error(nyi). 266 | 267 | queue_head_delete(_, _, _) -> 268 | error(nyi). 269 | 270 | first_queue(_, _) -> 271 | error(nyi). 272 | 273 | next_queue(_, _, _) -> 274 | error(nyi). 275 | 276 | first(_, _) -> 277 | error(nyi). 278 | 279 | last(_, _) -> 280 | error(nyi). 281 | 282 | next(_, _, _) -> 283 | error(nyi). 284 | 285 | prev(_, _, _) -> 286 | error(nyi). 287 | 288 | prefix_match(Db, Table, Pfx) -> 289 | ?if_table(Db, Table, prefix_match_(Db, Table, Pfx)). 290 | 291 | prefix_match_(#db{ref = Ref} = Db, Table, Pfx) -> 292 | case session_pid(Db) of 293 | undefined -> error(no_session); 294 | {pb, Session} -> 295 | Bucket = tab_key(Ref, Table), 296 | case riakc_pb_socket:mapred_bucket( 297 | Session, Bucket, 298 | [kvdb_riak_mapred:map_prefix_match(Pfx), 299 | riak_kv_mapreduce:reduce_sort(true)]) of 300 | {ok, []} -> 301 | []; 302 | {ok, MapRedRes} -> 303 | lists:last(MapRedRes); 304 | Other -> 305 | error(Other) 306 | end; 307 | _ -> 308 | error(nyi) 309 | end. 310 | 311 | prefix_match(Db, Table, Pfx, infinity) -> 312 | {prefix_match(Db, Table, Pfx), fun() -> done end}; 313 | prefix_match(Db, Table, Pfx, Limit) when is_integer(Limit), Limit > 0 -> 314 | %% Fake chunking for now 315 | chunk(prefix_match(Db, Table, Pfx), Limit). 316 | 317 | chunk(L, Limit) -> 318 | if length(L) >= Limit -> 319 | {A, B} = lists:split(Limit, L), 320 | {A, fun() -> 321 | chunk(B, Limit) 322 | end}; 323 | true -> 324 | {L, fun() -> done end} 325 | end. 326 | 327 | prefix_match_rel(_, _, _, _, _) -> 328 | error(nyi). 329 | 330 | 331 | tab_key(#ref{bucket = B}, Tab) -> 332 | <>. 333 | 334 | session_pid(#db{metadata = Ets}) -> 335 | case ets:lookup(Ets, riak_session) of 336 | [] -> 337 | undefined; 338 | [{_, Pid}] -> 339 | Pid 340 | end. 341 | 342 | riak_get(Bucket, K, {pb, Session}) -> 343 | case riakc_pb_socket:get(Session, Bucket, K) of 344 | {ok, Obj} -> 345 | {ok, binary_to_term(riakc_obj:get_value(Obj))}; 346 | {error,notfound} -> 347 | {error, not_found} 348 | end; 349 | riak_get(Bucket, K, {client, C}) -> 350 | case riak_client:get(Bucket, K, C) of 351 | {ok, Obj} -> 352 | {ok, binary_to_term(riak_object:get_value(Obj))}; 353 | {error, notfound} -> {error, not_found} 354 | end. 355 | 356 | riak_delete(Bucket, K, {pb, Session}) -> 357 | case riakc_pb_socket:delete(Session, Bucket, K) of 358 | ok -> 359 | ok; 360 | {error,_} = Error -> 361 | Error 362 | end; 363 | riak_delete(Bucket, K, {client, C}) -> 364 | case riak_client:delete(Bucket, K, C) of 365 | ok -> 366 | ok; 367 | {error,_} = Error -> 368 | Error 369 | end. 370 | 371 | 372 | load_schema(#db{ref = Ref, metadata = Ets} = Db, Session) -> 373 | case riak_list_keys(Bucket = Ref#ref.bucket, Session) of 374 | {ok, Keys} -> 375 | lists:foreach( 376 | fun(K) -> 377 | case riak_get(Bucket, K, Session) of 378 | {ok, {_Tab, TabR}} -> 379 | write_tabrec_ets(Ets, TabR); 380 | {error, not_found} -> 381 | %% ? 382 | ok 383 | end 384 | end, Keys); 385 | _ -> 386 | ok 387 | end, 388 | Db. 389 | 390 | riak_list_keys(Bucket, {client, Session}) -> 391 | riak_client:list_keys(Bucket, Session); 392 | riak_list_keys(Bucket, {pb, Session}) -> 393 | riakc_pb_socket:list_keys(Session, Bucket). 394 | 395 | 396 | write_tabrec_ets(Ets, #table{name = Name, 397 | type = Type, 398 | index = Ix, 399 | encoding = Enc} = R) -> 400 | ets:insert(Ets, [{{table, Name}, R}, 401 | {{Name, type}, Type}, 402 | {{Name, index}, Ix}, 403 | {{Name, encoding, Enc}}]). 404 | -------------------------------------------------------------------------------- /src/kvdb_riak_mapred.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Riak map-reduce hooks for kvdb_riak backend 13 | %%% 14 | %%% NOTE: This is work in progress, highly experimental. 15 | %%% 16 | %%% @end 17 | -module(kvdb_riak_mapred). 18 | 19 | -compile(export_all). 20 | 21 | 22 | 23 | map_prefix_match(Pfx) -> 24 | {map, {modfun, ?MODULE, map_prefix_match}, Pfx, false}. 25 | 26 | map_prefix_match(Obj, _, Pfx) -> 27 | Sz = byte_size(Pfx), 28 | Key = riak_object:key(Obj), 29 | case Key of 30 | <> -> 31 | [binary_to_term(riak_object:get_value(Obj))]; 32 | _ -> 33 | [] 34 | end. 35 | 36 | -------------------------------------------------------------------------------- /src/kvdb_riak_proxy.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% Riak connectivity manager (proxy) for kvdb_riak backend 13 | %%% 14 | %%% NOTE: This is work in progress, highly experimental. 15 | %%% 16 | %%% @end 17 | -module(kvdb_riak_proxy). 18 | -behaviour(gen_server). 19 | 20 | -export([start_link/2]). 21 | 22 | -export([get_session/1]). 23 | 24 | -export([init/1, 25 | handle_call/3, 26 | handle_cast/2, 27 | handle_info/2, 28 | terminate/2, 29 | code_change/3]). 30 | 31 | -record(st, {session}). 32 | 33 | start_link(Name, Options) -> 34 | gen_server:start_link(?MODULE, {Name, Options}, []). 35 | 36 | get_session(Name) -> 37 | case gproc:where({n,l,{?MODULE,Name}}) of 38 | undefined -> 39 | undefined; 40 | Pid -> 41 | gen_server:call(Pid, get_session) 42 | end. 43 | 44 | init({Name, Options}) -> 45 | gproc:reg({n,l,{?MODULE, Name}}), 46 | Session = open_session(Options), 47 | {ok, #st{session = Session}}. 48 | 49 | handle_call(get_session, _, #st{session = Session} = S) -> 50 | {reply, Session, S}. 51 | 52 | handle_cast(_, S) -> 53 | {noreply, S}. 54 | 55 | handle_info(_, S) -> 56 | {noreply, S}. 57 | 58 | terminate(_, _) -> 59 | ok. 60 | 61 | code_change(_, S, _) -> 62 | {ok, S}. 63 | 64 | 65 | open_session(Options) -> 66 | case proplists:get_value(riak, Options, {client, node()}) of 67 | {client, Node} -> 68 | case riak:client_connect(Node) of 69 | {ok, Pid} -> {client, Pid}; 70 | _ -> undefined 71 | end; 72 | {pb, Conf} -> 73 | {Host, Port} = 74 | case Conf of 75 | {_,_} -> Conf; 76 | local -> 77 | case riak_api_pb_listener:get_listeners() of 78 | [{_, P}|_] -> 79 | {"127.0.0.1", P}; 80 | _ -> error(cannot_get_pb_config) 81 | end 82 | end, 83 | case riakc_pb_socket:start_link(Host, Port) of 84 | {ok, Pid} -> 85 | {pb, Pid}; 86 | _ -> 87 | undefined 88 | end 89 | end. 90 | -------------------------------------------------------------------------------- /src/kvdb_schema.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% KVDB schema callback module behavior 13 | %%% @end 14 | -module(kvdb_schema). 15 | 16 | -export([ 17 | validate/3, on_update/4 18 | ]). 19 | -export([write/2, read/1, read/2]). 20 | -export([pre_commit/2, post_commit/2]). 21 | 22 | -export([fold_schema/3, all_ok/3]). 23 | 24 | -export([behaviour_info/1]). 25 | 26 | -include("kvdb.hrl"). 27 | 28 | behaviour_info(callbacks) -> 29 | [{validate, 3}, 30 | {on_update, 4}, 31 | {pre_commit, 2}, 32 | {post_commit, 2}]; 33 | behaviour_info(_) -> 34 | undefined. 35 | 36 | fold_schema(Schema, F, Obj) when is_function(F, 2) -> 37 | if is_atom(Schema) -> 38 | F(Schema, Obj); 39 | is_list(Schema) -> 40 | lists:foldl( 41 | fun(S, Acc) -> 42 | fold_schema(S, F, Acc) 43 | end, Schema, Obj); 44 | is_tuple(Schema), tuple_size(Schema) == 1 -> 45 | {M} = Schema, 46 | Expanded = M:expand_schema(), 47 | fold_schema(Expanded, F, Obj) 48 | end. 49 | 50 | all_ok(Schema, F, Obj) -> 51 | R = if is_atom(Schema) -> F(Schema, Obj); 52 | is_list(Schema) -> catch all_ok_(Schema, F, Obj); 53 | is_tuple(Schema), tuple_size(Schema) == 1 -> 54 | {M} = Schema, 55 | catch all_ok_([_|_] = M:expand_schema(), F, Obj) 56 | end, 57 | case R of 58 | {'EXIT', Reason} -> {error, Reason}; 59 | Other -> Other 60 | end. 61 | 62 | all_ok_([H|T], F, Obj) -> 63 | case F(H, Obj) of 64 | ok -> all_ok_(T, F, Obj); 65 | Other -> Other 66 | end; 67 | all_ok_([], _, _) -> 68 | ok. 69 | 70 | 71 | validate(_Db, _Type, Obj) -> 72 | Obj. 73 | 74 | on_update(_Op, _Db, _Table, _Obj) -> 75 | ok. 76 | 77 | write(Db, Schema) -> 78 | [kvdb:put(Db, ?META_TABLE, X) || X <- Schema], 79 | ok. 80 | 81 | read(Db) -> 82 | match_(kvdb:select(Db, ?META_TABLE, [{'_',[],['$_']}], 100), []). 83 | 84 | read(Db, Item) -> 85 | case kvdb:get(Db, ?META_TABLE, Item) of 86 | {ok, {_,_,V}} -> {ok, V}; 87 | {ok, {_, V}} -> {ok, V}; 88 | Error -> 89 | Error 90 | end. 91 | 92 | match_({Objs, Cont}, Acc) -> 93 | match_(Cont(), acc_(Objs, Acc)); 94 | match_(done, Acc) -> 95 | lists:reverse(Acc). 96 | 97 | acc_([{K,_,V}|T], Acc) -> 98 | acc_(T, [{K,V}|Acc]); 99 | acc_([{_,_}|_] = L, Acc) -> 100 | lists:reverse(L) ++ Acc; 101 | acc_([], Acc) -> 102 | Acc. 103 | 104 | pre_commit(C, _) -> 105 | C. 106 | 107 | post_commit(_, _) -> 108 | ok. 109 | -------------------------------------------------------------------------------- /src/kvdb_schema_events.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @doc 12 | %%% KVDB schema callback module for queue-related events 13 | %%% @end 14 | %%% 15 | -module(kvdb_schema_events). 16 | -behaviour(kvdb_schema). 17 | 18 | -export([notify_when_not_empty/3, 19 | notify_all_queues/2, 20 | cancel_notify_all_queues/2]). 21 | 22 | -include("kvdb.hrl"). 23 | 24 | %% kvdb_schema callbacks 25 | -export([validate/3, 26 | on_update/4, 27 | pre_commit/2, 28 | post_commit/2]). 29 | 30 | -include("log.hrl"). 31 | 32 | notify_when_not_empty(#kvdb_ref{name = DBN} = Db, Table0, Q) -> 33 | Table = kvdb_lib:table_name(Table0), 34 | Evt = {kvdb, DBN, Table, Q, queue_status}, 35 | gproc_ps:notify_single_if_true( 36 | l, Evt, fun() -> not kvdb:is_queue_empty(Db,Table,Q) end, not_empty). 37 | 38 | notify_all_queues(#kvdb_ref{name = DBN}, Table0) -> 39 | Table = kvdb_lib:table_name(Table0), 40 | Evt = {kvdb, DBN, Table, queue_status}, 41 | gproc_ps:subscribe(l, Evt). 42 | 43 | cancel_notify_all_queues(#kvdb_ref{name = DBN}, Table0) -> 44 | gproc_ps:unsubscribe(l, {kvdb, DBN, kvdb_lib:table_name(Table0), 45 | queue_status}). 46 | 47 | validate(_, _, Obj) -> 48 | Obj. 49 | 50 | on_update({q_op,_,Q,true}, DB, Table, _) -> 51 | notify_queue_status(DB, Table, Q, empty), 52 | ok; 53 | on_update({q_op,_,Q,false}, DB, Table, _) -> 54 | notify_queue_status(DB, Table, Q, not_empty), 55 | ok; 56 | on_update(_, _, _, _) -> 57 | ok. 58 | 59 | pre_commit(C, _) -> 60 | C. 61 | 62 | post_commit(_, _) -> 63 | ok. 64 | 65 | notify_queue_status(#kvdb_ref{name = DBN, db = #db{metadata = Ets}} = Ref, 66 | Table, Q, Status) -> 67 | ?debug("notify_queue_statusf(Ref = ~p, ~p, ~p, ~p)~n", 68 | [Ref,Table,Q,Status]), 69 | case set_status(Ets, Table, Q, Status) of 70 | changed -> 71 | _ = gproc_ps:publish(l, {kvdb, DBN, Table, queue_status}, 72 | {Q, Status}), 73 | _ = gproc_ps:tell_singles(l, {kvdb, DBN, Table, Q, queue_status}, 74 | Status), 75 | ok; 76 | same -> 77 | ok 78 | end. 79 | 80 | set_status(Ets, Tab, Q, Status) -> 81 | Key = {q_status,Tab,Q}, 82 | Change = case Status of 83 | empty -> [{2,0},{2,-1,0,0}]; 84 | not_empty -> [{2,0},{2,1,1,1}] 85 | end, 86 | try ets:update_counter(Ets, Key, Change) of 87 | [X,X] -> 88 | same; 89 | [_,_] -> 90 | changed 91 | catch 92 | error:_ -> 93 | ets:insert(Ets, {Key, case Status of 94 | empty -> 0; 95 | not_empty -> 1 96 | end}), 97 | changed 98 | end. 99 | -------------------------------------------------------------------------------- /src/kvdb_server.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% @doc 13 | %%% KVDB Database instance owner process 14 | %%% @end 15 | %%% 16 | -module(kvdb_server). 17 | -behaviour(gen_server). 18 | 19 | -export([start_link/2, 20 | start_session/2, 21 | db/1, db/2, 22 | await/1, await/2, 23 | close/1, 24 | call/2, 25 | cast/2]). 26 | 27 | -export([begin_trans/3, 28 | start_commit/2, 29 | end_trans/2]). 30 | 31 | -export([init/1, 32 | handle_call/3, 33 | handle_cast/2, 34 | handle_info/2, 35 | terminate/2, 36 | code_change/3]). 37 | 38 | -include("kvdb.hrl"). 39 | -include("log.hrl"). 40 | -import(kvdb_lib, [table_name/1]). 41 | 42 | -record(st, {name, db, is_owner = false, 43 | switch_pending = false, 44 | transactions = [], 45 | commits = [], 46 | pending_commits = []}). 47 | 48 | -record(trans, {pid, mref, tref, role = master, db}). 49 | 50 | start_link(Name, Backend) -> 51 | gen_server:start_link(?MODULE, {owner, Name, Backend}, []). 52 | 53 | start_session(Name, Id) -> 54 | gen_server:start_link(?MODULE, session(Name, Id), []). 55 | 56 | session(Name, Id) -> 57 | {Name, session, Id}. 58 | 59 | 60 | db(Name) -> 61 | call(Name, db). 62 | 63 | db(Name, TRef) -> 64 | call(Name, {db, TRef}). 65 | 66 | begin_trans(Name, TRef, Db) -> 67 | call(Name, {begin_trans, TRef, Db}). 68 | 69 | start_commit(Name, TRef) -> 70 | try call(Name, {start_commit, TRef}) of 71 | #kvdb_ref{} = Res -> 72 | Res; 73 | Other -> 74 | erlang:error(Other) 75 | catch 76 | exit:Err -> 77 | io:fwrite(user, "kvdb_server status:~n~s~n", 78 | [print_state(Name)]), 79 | exit(Err) 80 | end. 81 | 82 | print_state(Name) -> 83 | {status,_,_,[_,_,_,_,[_,_,{data,[{"State",S}]}]]} = 84 | sys:get_status(get_pid(Name)), 85 | Sz = tuple_size(S) -1, 86 | RF = fun(st, Size) when Size == Sz -> 87 | record_info(fields, st); 88 | (_, _) -> 89 | no 90 | end, 91 | io_lib_pretty:print(S, RF). 92 | 93 | end_trans(Name, Ref) -> 94 | cast(Name, {end_trans, self(), Ref}). 95 | 96 | close(Name) -> 97 | call(Name, close). 98 | 99 | await(Name) -> 100 | await(Name, timer:minutes(1)). 101 | 102 | await(Name, Timeout) -> 103 | gproc:await({n,l,{kvdb,Name}}, Timeout), 104 | ok. 105 | 106 | cast(Name, Msg) -> 107 | gen_server:cast(gproc:where({n,l,{kvdb,Name}}), Msg). 108 | 109 | call(Name, Req) -> 110 | call(Name, Req, 5000). 111 | 112 | call(Name, Req, Timeout) -> 113 | Pid = get_pid(Name), 114 | case gen_server:call(Pid, Req, Timeout) of 115 | badarg -> 116 | ?KVDB_THROW(badarg); 117 | {badarg,_} = Err -> 118 | ?KVDB_THROW(Err); 119 | Res -> 120 | Res 121 | end. 122 | 123 | get_pid(Name) -> 124 | case Name of 125 | #kvdb_ref{name = N} -> 126 | gproc:where({n, l, {kvdb, N}}); 127 | P when is_pid(P) -> 128 | P; 129 | _ -> 130 | gproc:where({n,l,{kvdb,Name}}) 131 | end. 132 | 133 | 134 | %% @private 135 | init(Alias) -> 136 | process_flag(trap_exit, true), 137 | try init_(Alias) 138 | catch 139 | error:Reason -> 140 | Trace = erlang:get_stacktrace(), 141 | error_logger:error_report([{error_opening_kvdb_db, Alias}, 142 | {error, Reason}, 143 | {stacktrace, Trace}]), 144 | erlang:error({Reason, Trace}, [Alias]) 145 | end. 146 | 147 | init_({Name, session, _Id} = Alias) -> 148 | Db = db(Name), 149 | gproc:reg({p, l, {kvdb, session}}, Alias), 150 | gproc:reg({n, l, {kvdb, Alias}}), 151 | {ok, #st{db = Db}}; 152 | init_({owner, Name, Opts}) -> 153 | Backend = proplists:get_value(backend, Opts, ets), 154 | gproc:reg({n, l, {kvdb,Name}}, Backend), 155 | DbMod = mod(Backend), 156 | NewOpts = check_log_options( 157 | Name, 158 | lists:keystore(backend, 1, 159 | %% lists:keystore(file, 1, Opts, {file, File}), 160 | Opts, 161 | {backend, DbMod})), 162 | case do_open(Name, NewOpts) of 163 | {ok, Db} -> 164 | kvdb_cron:init_meta(Db), 165 | create_tables_(Db, Opts), 166 | case common_open(Db, NewOpts) of 167 | {ok, Db1} -> 168 | {ok, #st{name = Name, db = Db1, is_owner = true}}; 169 | {error,_} = CommonOpenErr -> 170 | CommonOpenErr 171 | end; 172 | {error,_} = Error -> 173 | io:fwrite("error opening kvdb database ~w:~n" 174 | "Error: ~p~n" 175 | "Opts = ~p~n", [Name, Error, NewOpts]), 176 | Error 177 | end. 178 | 179 | check_log_options(Name, Opts) -> 180 | case {lists:keymember(log_dir, 1, Opts), 181 | lists:keymember(log_threshold, 1, Opts)} of 182 | {false, true} -> 183 | {File, Opts1} = ensure_file(Name, Opts), 184 | [{log_dir, File ++ ".log"} | Opts1]; 185 | _ -> 186 | Opts 187 | end. 188 | 189 | ensure_file(Name, Opts) -> 190 | case lists:keyfind(file, 1, Opts) of 191 | {_, F} -> 192 | {F, Opts}; 193 | false -> 194 | F = kvdb_lib:db_file(Name), 195 | {F, [{file, F}|Opts]} 196 | end. 197 | 198 | %% @private 199 | handle_call(Req, From, St) -> 200 | try handle_call_(Req, From, St) 201 | catch 202 | error:badarg -> 203 | {reply, {badarg, erlang:get_stacktrace()}, St}; 204 | error:E -> 205 | {reply, {badarg,[E, erlang:get_stacktrace()]}, St} 206 | end. 207 | 208 | handle_call_({put, Tab, Obj}, _From, #st{db = Db} = St) -> 209 | {reply, kvdb_direct:put(Db, Tab, Obj), St}; 210 | handle_call_({update_counter, Table, Key, Incr}, _From, #st{db = Db} = St) -> 211 | {reply, kvdb_direct:update_counter(Db, Table, Key, Incr), St}; 212 | handle_call_({push, Tab, Q, Obj}, _From, #st{db = Db} = St) -> 213 | {reply, kvdb_direct:push(Db, Tab, Q, Obj), St}; 214 | handle_call_({pop, Tab, Q}, _From, #st{db = Db} = St) -> 215 | {reply, kvdb_direct:pop(Db, Tab, Q), St}; 216 | handle_call_({prel_pop, Tab, Q}, _From, #st{db = Db} = St) -> 217 | {reply, kvdb_direct:prel_pop(Db, Tab, Q), St}; 218 | handle_call_({extract, Tab, Key}, _From, #st{db = Db} = St) -> 219 | {reply, kvdb_direct:extract(Db, Tab, Key), St}; 220 | handle_call_({mark_queue_object, Table, Key, OSt}, _From, #st{db = Db} = St) -> 221 | {reply, kvdb_direct:mark_queue_object(Db, Table, Key, OSt), St}; 222 | handle_call_({delete, Tab, Key}, _From, #st{db = Db} = St) -> 223 | {reply, kvdb_direct:delete(Db, Tab, Key), St}; 224 | handle_call_({add_table, Table, Opts}, _From, #st{db = Db} = St) -> 225 | {reply, kvdb_direct:add_table(Db, Table, Opts), St}; 226 | handle_call_({delete_table, Table}, _From, #st{db = Db} = St) -> 227 | {reply, kvdb_direct:delete_table(Db, Table), St}; 228 | handle_call_(close, _From, #st{is_owner = true} = St) -> 229 | {stop, normal, ok, St}; 230 | handle_call_({begin_trans, Ref, DbT}, {Pid,_}, #st{transactions = Ts} = St) -> 231 | lager:debug("begin_trans, Ref = ~p, Pid = ~p", [Ref, Pid]), 232 | MRef = erlang:monitor(process, Pid), 233 | Ts1 = [#trans{pid = Pid, tref = Ref, mref = MRef, db = DbT}|Ts], 234 | {reply, ok, St#st{transactions = Ts1}}; 235 | handle_call_({start_commit, Ref}, {Pid,_} = From, #st{switch_pending = SwPend, 236 | transactions = Ts, 237 | db = Db} = St) -> 238 | case is_transaction_master(Pid, Ref, Ts) of 239 | yes -> 240 | case SwPend of false -> 241 | {reply, Db, St#st{commits = [Ref|St#st.commits]}}; 242 | {true,_,_} -> 243 | Pend = St#st.pending_commits, 244 | {noreply, St#st{pending_commits = [From|Pend]}} 245 | end; 246 | {no, Reason} -> 247 | {reply, {error, Reason}, St} 248 | end; 249 | handle_call_({new_log, Log}, From, #st{db = Db, commits = Commits} = St) -> 250 | %% Need to switch logs. We try to do this atomically. For individual 251 | %% updates, it's no problem, since they will call on us for the db ref. 252 | %% If there are ongoing transactions, we must wait for them to end, 253 | %% queueing new transaction requests in the meantime. 254 | case Commits of 255 | [] -> 256 | NewDb = switch_logs(Db, Log), 257 | {reply, {ok, NewDb}, logs_switched(NewDb, St)}; 258 | [_|_] -> 259 | %% io:fwrite("won't switch now; Ts = ~p~n", [Ts]), 260 | {noreply, St#st{switch_pending = {true, From, Log}}} 261 | end; 262 | handle_call_(db, _From, #st{db = Db} = St) -> 263 | {reply, Db, St}. 264 | 265 | handle_info({'DOWN', Ref, _,_,_}, #st{transactions = Ts, 266 | commits = Commits, 267 | switch_pending = Pend} = St) -> 268 | case lists:keytake(Ref, #trans.mref, Ts) of 269 | {value, #trans{tref = TRef, role = master}, Ts1} -> 270 | case Commits -- [TRef] of 271 | [] -> 272 | case Pend of 273 | {true, _From, Log} -> 274 | NewDb = switch_logs(St#st.db, Log), 275 | {noreply, logs_switched( 276 | NewDb, St#st{transactions = Ts1, 277 | commits = []})}; 278 | false -> 279 | {noreply, St#st{transactions = Ts1, 280 | commits = []}} 281 | end; 282 | Commits1 -> 283 | {noreply, St#st{transactions = Ts1, 284 | commits = Commits1}} 285 | end; 286 | {value, _, Ts1} -> 287 | {noreply, St#st{transactions = Ts1}}; 288 | false -> 289 | {noreply, St} 290 | end; 291 | handle_info(_, St) -> 292 | {noreply, St}. 293 | 294 | %% @private 295 | handle_cast({end_trans, _Pid, Ref} = _M, #st{db = Db, 296 | commits = Commits, 297 | transactions = Ts} = St) -> 298 | lager:debug("~p", [_M]), 299 | Ts1 = lists:foldr( 300 | fun(#trans{tref = R, mref = MRef}, Acc) when R =:= Ref -> 301 | erlang:demonitor(MRef), 302 | Acc; 303 | (T, Acc) -> 304 | [T|Acc] 305 | end, [], Ts), 306 | case Commits1 = Commits -- [Ref] of 307 | [] -> 308 | case St#st.switch_pending of 309 | false -> 310 | {noreply, St#st{transactions = Ts1, commits = []}}; 311 | {true, From, Log} -> 312 | NewDb = switch_logs(Db, Log), 313 | gen_server:reply(From, {ok, NewDb}), 314 | {noreply, logs_switched(NewDb, St#st{transactions = Ts1, 315 | commits = []})} 316 | end; 317 | [_|_] = Commits1 -> 318 | {noreply, St#st{transactions = Ts1, 319 | commits = Commits1}} 320 | end; 321 | handle_cast(log_threshold, #st{db = Db} = St) -> 322 | %% io:fwrite("threshold reached, ~p~n", [Db]), 323 | Me = self(), 324 | spawn_monitor(fun() -> 325 | %% io:fwrite("~p spawned to switch logs~n", [self()]), 326 | {ok, Log} = open_new_log(Db, Me), 327 | %% io:fwrite("new log opened: ~p~n", [Log]), 328 | TS = os:timestamp(), 329 | {ok, NewDb} = call(Me, {new_log, Log}, 15000), 330 | %% io:fwrite("NewDb = ~p~n", [NewDb]), 331 | #kvdb_ref{db = D} = NewDb, 332 | case kvdb_meta:read(D, last_dump, undefined) of 333 | DumpTS when DumpTS > TS -> 334 | kvdb_lib:purge_logs(D, DumpTS); 335 | %% io:fwrite("logs purged~n", []); 336 | _ -> 337 | %% io:fwrite("nothing to purge~n", []), 338 | ignore 339 | end 340 | end), 341 | {noreply, St}; 342 | handle_cast(_, St) -> 343 | {noreply, St}. 344 | 345 | %% @private 346 | terminate(_Reason, #st{db = #kvdb_ref{mod = M, db = Db}} = S) -> 347 | M:close(Db), 348 | close_log(S), 349 | ok. 350 | 351 | %% @private 352 | code_change(_FromVsn, St, _Extra) -> 353 | {ok, St}. 354 | 355 | is_transaction_master(Pid, Ref, Ts) -> 356 | case [T || #trans{pid = P, tref = R} = T <- Ts, 357 | P =:= Pid, R =:= Ref] of 358 | [#trans{role = master}] -> 359 | yes; 360 | [_] -> 361 | {no, wrong_pid}; 362 | [] -> 363 | {no, not_found} 364 | end. 365 | 366 | switch_logs(#kvdb_ref{mod = M, db = #db{log = {OldLog,_}} = Db} = Ref, Log) -> 367 | #db{} = NewDb = M:switch_logs(Db, Log), 368 | disk_log:close(OldLog), 369 | Ref#kvdb_ref{db = NewDb}. 370 | 371 | close_log(#st{db = #db{log = {Log, _}}}) -> 372 | disk_log:close(Log); 373 | close_log(_) -> 374 | ok. 375 | 376 | logs_switched(NewDb, St) -> 377 | St1 = case St#st.pending_commits of 378 | [] -> 379 | St#st{db = NewDb, 380 | switch_pending = false}; 381 | Pend -> 382 | lists:foreach(fun(From) -> 383 | gen_server:reply(From, NewDb) 384 | end, Pend), 385 | St#st{ 386 | db = NewDb, 387 | pending_commits = [], 388 | switch_pending = false} 389 | end, 390 | %% io:fwrite("logs switched; new st= ~p~n", [St1]), 391 | St1. 392 | 393 | 394 | open_new_log(#kvdb_ref{db = Db}, Pid) -> 395 | LogDir = kvdb_meta:read(Db, log_dir, undefined), 396 | kvdb_lib:open_log(kvdb_lib:log_filename(LogDir), Pid). 397 | 398 | 399 | do_open(Name, Options) when is_list(Options) -> 400 | DbMod = proplists:get_value(backend, Options, kvdb_sqlite3), 401 | Fallback = check_for_fallback(Name, Options), 402 | case DbMod:open(Name,Options) of 403 | {ok, Db} -> 404 | %% io:fwrite("opened ~p database: ~p~n", [DbMod, Options]), 405 | Default = DbMod:get_schema_mod(Db, kvdb_schema), 406 | Schema = proplists:get_value(schema, Options, Default), 407 | kvdb_meta:write(Db, name, Name), 408 | maybe_import(Fallback, DbMod, Db), 409 | {ok, #kvdb_ref{name = Name, mod = DbMod, db = Db, schema = Schema}}; 410 | Error -> 411 | io:fwrite("ERROR opening ~p database: ~p. Opts = ~p~n", 412 | [DbMod, Error, Options]), 413 | Error 414 | end. 415 | 416 | maybe_import(false, _, _) -> 417 | ok; 418 | maybe_import(Fallback, Mod, Db) -> 419 | io:fwrite("Importing from ~p~n", [Fallback]), 420 | kvdb_export:import(Mod, Db, Fallback). 421 | 422 | 423 | check_for_fallback(Name, Options) -> 424 | File = case lists:keyfind(file, 1, Options) of 425 | {_, F} -> F; 426 | false -> kvdb_lib:db_file(Name) 427 | end, 428 | io:fwrite("File = ~p~n", [File]), 429 | Dir = filename:dirname(File), 430 | lager:debug("check_for_fallback: Dir = ~p~n", [Dir]), 431 | case filelib:wildcard(to_list(Name) ++ ".KBUP?", Dir) of 432 | [] -> 433 | false; 434 | [_|_] = Files -> 435 | Sorted = lists:sort(fun fcomp/2, Files), 436 | lager:debug("Found backups: ~p~n", [Sorted]), 437 | Pick = hd(Sorted), 438 | move_file(Options), 439 | filename:join(Dir, Pick) 440 | end. 441 | 442 | to_list(A) when is_atom(A) -> atom_to_list(A); 443 | to_list(L) when is_list(L) -> L; 444 | to_list(B) when is_binary(B) -> binary_to_list(B). 445 | 446 | 447 | move_file(Options) -> 448 | case lists:keyfind(file, 1, Options) of 449 | {_, F} -> 450 | BakName = bak_name(F), 451 | file:rename(F, BakName); 452 | false -> 453 | ok 454 | end. 455 | 456 | bak_name(F) -> 457 | bak_name(F, 0). 458 | 459 | bak_name(F, N) -> 460 | case file:read_link_info(Bak = F ++ bak_ext(N)) of 461 | {ok,_} -> bak_name(F, N+1); 462 | {error,enoent} -> Bak 463 | end. 464 | 465 | bak_ext(0) -> ".bak"; 466 | bak_ext(N) -> ".bak." ++ integer_to_list(N). 467 | 468 | 469 | fcomp(A, B) -> 470 | case {filename:extension(A), filename:extension(B)} of 471 | {".KBUPB", _} -> true; 472 | {_, ".KBUPB"} -> false; 473 | {_, _} -> true % just pick one 474 | end. 475 | 476 | common_open(#kvdb_ref{mod = DbMod, db = Db} = DbRef, Options) -> 477 | case kvdb_lib:common_open(DbMod, Db, Options) of 478 | {ok, Db1} -> 479 | {ok, DbRef#kvdb_ref{db = Db1}}; 480 | Other -> 481 | Other 482 | end. 483 | 484 | 485 | mod(mnesia ) -> kvdb_mnesia; 486 | mod(leveldb) -> kvdb_leveldb; 487 | mod(sqlite3) -> kvdb_sqlite3; 488 | mod(sqlite ) -> kvdb_sqlite3; 489 | mod(ets ) -> kvdb_ets; 490 | mod(M) -> 491 | case is_behaviour(M) of 492 | true -> 493 | M % TODO: implement actual check 494 | %% false -> 495 | %% error(illegal_backend_type) 496 | end. 497 | 498 | %% name2file(X) -> 499 | %% kvdb_lib:good_string(X). 500 | 501 | 502 | 503 | %% to_atom(A) when is_atom(A) -> 504 | %% A; 505 | %% to_atom(S) when is_list(S) -> 506 | %% list_to_atom(S). 507 | 508 | 509 | is_behaviour(_M) -> 510 | %% TODO: check that exported functions match those listed in 511 | %% behaviour_info(callbacks). 512 | true. 513 | 514 | create_tables_(Db, Opts) -> 515 | case proplists:get_value(tables, Opts, []) of 516 | [] -> 517 | ok; 518 | Ts -> 519 | Tabs0 = lists:map(fun({T,Os}) -> 520 | {table_name(T), Os}; 521 | (T) -> {table_name(T),[]} 522 | end, Ts), 523 | %% We don't warn if there are more tables than we've specified, 524 | %% and we certainly don't remove them. Ok to do nothing? 525 | Tables = internal_tables() ++ Tabs0, 526 | Existing = kvdb:list_tables(Db), 527 | New = lists:filter(fun({T,_}) -> 528 | not lists:member(T, Existing) end, 529 | Tables), 530 | [kvdb_direct:add_table(Db, T, Os) || {T, Os} <- New] 531 | end. 532 | 533 | internal_tables() -> 534 | []. 535 | -------------------------------------------------------------------------------- /src/kvdb_server_sup.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% @doc 13 | %%% KVDB database instance supervisor 14 | %%% @end 15 | %%% 16 | -module(kvdb_server_sup). 17 | 18 | -behaviour(supervisor). 19 | 20 | %% API 21 | -export([start_link/0]). 22 | -export([start_child/2]). 23 | %% childspec/1]). 24 | 25 | %% Supervisor callbacks 26 | -export([init/1]). 27 | 28 | %% Helper macro for declaring children of supervisor 29 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 30 | 31 | %% =================================================================== 32 | %% API functions 33 | %% =================================================================== 34 | 35 | start_link() -> 36 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 37 | 38 | %% =================================================================== 39 | %% Supervisor callbacks 40 | %% =================================================================== 41 | 42 | init([]) -> 43 | %% Children = childspecs(DBs = get_databases()), 44 | %% io:fwrite("DBs = ~p~n", [DBs]), 45 | {ok, { {simple_one_for_one, 5, 10}, 46 | [{id, {kvdb_server, start_link, []}, 47 | transient, 5000, worker, [kvdb_server]}] }}. 48 | 49 | 50 | start_child(Name, Opts) -> 51 | supervisor:start_child(?MODULE, [Name, Opts]). 52 | 53 | -------------------------------------------------------------------------------- /src/kvdb_sup.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% @hidden 12 | %%% @doc 13 | %%% KVDB main supervisor 14 | %%% @end 15 | %%% 16 | -module(kvdb_sup). 17 | 18 | -behaviour(supervisor). 19 | 20 | %% API 21 | -export([start_link/0]). 22 | -export([start_child/2]). 23 | %% childspec/1]). 24 | 25 | %% Supervisor callbacks 26 | -export([init/1]). 27 | 28 | %% Helper macro for declaring children of supervisor 29 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 30 | 31 | %% =================================================================== 32 | %% API functions 33 | %% =================================================================== 34 | 35 | start_link() -> 36 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 37 | 38 | %% =================================================================== 39 | %% Supervisor callbacks 40 | %% =================================================================== 41 | 42 | init([]) -> 43 | %% Children = childspecs(DBs = get_databases()), 44 | %% io:fwrite("DBs = ~p~n", [DBs]), 45 | {ok, { {simple_one_for_one, 0, 10}, 46 | [{id, {kvdb_db_sup, start_link, []}, 47 | permanent, infinity, supervisor, [kvdb_db_sup]}] }}. 48 | %% [{id, {kvdb_server, start_link, []}, 49 | %% transient, 5000, worker, [kvdb_server]}] }}. 50 | 51 | 52 | start_child(Name, Opts) -> 53 | supervisor:start_child(?MODULE, [Name, Opts]). 54 | 55 | -------------------------------------------------------------------------------- /src/log.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Log macros 3 | %% 4 | -ifndef(__LOG_HRL__). 5 | -define(__LOG_HRL__, true). 6 | 7 | -compile({parse_transform, lager_transform}). 8 | 9 | %% Lager logging levels 10 | %% debug, info, notice, warning, error, critical, alert, emergency, none. 11 | 12 | -define(debug(Fmt), lager:debug(Fmt)). 13 | -define(debug(Fmt, Args), lager:debug(Fmt, Args)). 14 | -define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)). 15 | 16 | -define(info(Fmt), lager:info(Fmt)). 17 | -define(info(Fmt, Args), lager:info(Fmt, Args)). 18 | -define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)). 19 | 20 | -define(notice(Fmt), lager:notice(Fmt)). 21 | -define(notice(Fmt, Args), lager:notice(Fmt, Args)). 22 | -define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)). 23 | 24 | -define(warning(Fmt), lager:warning(Fmt)). 25 | -define(warning(Fmt, Args), lager:warning(Fmt, Args)). 26 | -define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)). 27 | 28 | -define(error(Fmt), lager:error(Fmt)). 29 | -define(error(Fmt, Args), lager:error(Fmt, Args)). 30 | -define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)). 31 | 32 | -define(critical(Fmt), lager:critical(Fmt)). 33 | -define(critical(Fmt, Args), lager:critical(Fmt, Args)). 34 | -define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)). 35 | 36 | -define(alert(Fmt), lager:alert(Fmt)). 37 | -define(alert(Fmt, Args), lager:alert(Fmt, Args)). 38 | -define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)). 39 | 40 | -define(emergency(Fmt), lager:emergency(Fmt)). 41 | -define(emergency(Fmt, Args), lager:emergency(Fmt, Args)). 42 | -define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)). 43 | 44 | -endif. 45 | -------------------------------------------------------------------------------- /test.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | [{kvdb, [ 3 | {databases, [ 4 | {s, [{backend, sqlite}, 5 | {tables, [t]} 6 | ]}, 7 | {l, [{backend, leveldb}, 8 | {tables, [t]} 9 | ]} 10 | ]} 11 | ]} 12 | ]. 13 | -------------------------------------------------------------------------------- /test/feuerlabs_eunit.hrl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | %%% 12 | %%% Helpful macros for EUnit tests 13 | 14 | %% ?_t(E): Like ?test(E), but with a 1-minute timeout, and generates a stack 15 | %% trace after exceptions. 16 | -define(_t(E), {timeout,60000, 17 | [?_test(try E catch error:_R_ -> 18 | error({_R_, erlang:get_stacktrace()}) 19 | end)]}). 20 | 21 | 22 | %% ?dbg(E): executes test case, printing the result on success, and a 23 | %% thorough error report on failure. 24 | -define(dbg(E), 25 | (fun() -> 26 | try (E) of 27 | __V -> 28 | ?debugFmt(<<"~s = ~P">>, [(??E), __V, 15]), 29 | __V 30 | catch 31 | error:__Err -> 32 | io:fwrite(user, 33 | "FAIL: test = ~s~n" 34 | "Error = ~p~n" 35 | "Trace = ~p~n", [(??E), __Err, 36 | erlang:get_stacktrace()]), 37 | error(__Err) 38 | end 39 | end)()). 40 | 41 | %% ?trace(Mods, E) Enables a call trace on the modules (or {Mod,Fun} patterns) 42 | %% in Mods, evaluates expression E and then stops the trace. 43 | -define(trace(Mods, Expr), begin dbg:tracer(), 44 | lists:foreach( 45 | fun(_M_) when is_atom(_M_) -> 46 | dbg:tpl(_M_,x); 47 | ({_M_,_F_}) -> 48 | dbg:tpl(_M_,_F_,x) 49 | end, Mods), 50 | dbg:p(all,[c]), 51 | try Expr 52 | after 53 | lists:foreach( 54 | fun(_M_) when is_atom(_M_) -> 55 | dbg:ctpl(_M_); 56 | ({_M_,_F_}) -> 57 | dbg:ctpl(_M_,_F_) 58 | end, Mods), 59 | dbg:stop() 60 | end 61 | end). 62 | -------------------------------------------------------------------------------- /test/kvdb_conf_tests.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | -module(kvdb_conf_tests). 12 | 13 | -ifdef(TEST). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | -include("feuerlabs_eunit.hrl"). 17 | 18 | -define(tab, iolist_to_binary([<<"t_">>, integer_to_list(?LINE)])). 19 | -define(mktab(T, Os), T = ?tab, ok = kvdb_conf:add_table(T, Os)). 20 | -define(my_t(E), ?_t(?dbg(E))). 21 | 22 | 23 | conf_test_() -> 24 | [ 25 | ?my_t(join_split_key()), 26 | {setup, 27 | fun() -> 28 | application:start(gproc), 29 | application:start(kvdb), 30 | kvdb_conf:open(undefined, [{backend, ets}]), 31 | ok 32 | end, 33 | fun(_) -> 34 | application:stop(kvdb), 35 | application:stop(gproc) 36 | end, 37 | [?my_t(read_write()) 38 | , ?my_t(prefix_match()) 39 | , ?my_t(next_at_level()) 40 | , ?my_t(conf_tree()) 41 | , ?my_t(write_tree()) 42 | , ?my_t(first_next_child()) 43 | , ?my_t(fold_list()) 44 | , ?my_t(fold_children()) 45 | ]}]. 46 | 47 | join_split_key() -> 48 | <<"=a*=b">> = kvdb_conf:join_key(<<"a">>, <<"b">>), 49 | <<"=a*">> = kvdb_conf:join_key(<<"a">>, <<>>), 50 | <<"=a*=b">> = kvdb_conf:join_key([<<"a">>, <<"b">>]), 51 | <<"=a*">> = kvdb_conf:join_key([<<"a">>, <<>>]), 52 | ok. 53 | 54 | 55 | read_write() -> 56 | ?mktab(T, []), 57 | ok = kvdb_conf:write(T, {<<"a">>, [{a,1}], <<"1">>}), 58 | {ok, {<<"a">>, [{a,1}], <<"1">>}} = kvdb_conf:read(T, <<"a">>), 59 | ok = kvdb_conf:delete(T, <<"a">>), 60 | {error, not_found} = kvdb_conf:read(T, <<"a">>), 61 | ok = kvdb_conf:delete_table(T). 62 | 63 | prefix_match() -> 64 | ?mktab(T, []), 65 | ok = kvdb_conf:write(T, {<<"aabbcc">>, [], <<>>}), 66 | ok = kvdb_conf:write(T, {<<"aabbdd">>, [], <<>>}), 67 | ok = kvdb_conf:write(T, {<<"aaccdd">>, [], <<>>}), 68 | {[{<<"aabbcc">>,[],<<>>},{<<"aabbdd">>,[],<<>>}],_} = 69 | kvdb_conf:prefix_match(T, <<"aabb">>), 70 | ok = kvdb_conf:delete_table(T). 71 | 72 | next_at_level() -> 73 | ?mktab(T, []), 74 | ok = kvdb_conf:write(T, {<<"aa">>, [], <<>>}), 75 | ok = kvdb_conf:write(T, {<<"aa*bb">>, [], <<>>}), 76 | ok = kvdb_conf:write(T, {<<"aa*bb*cc">>, [], <<>>}), 77 | ok = kvdb_conf:write(T, {<<"aa*bb*dd">>, [], <<>>}), 78 | ok = kvdb_conf:write(T, {<<"aa*cc">>, [], <<>>}), 79 | ok = kvdb_conf:write(T, {<<"aa*cc*cc">>, [], <<>>}), 80 | ok = kvdb_conf:write(T, {<<"aa*cc*dd">>, [], <<>>}), 81 | ok = kvdb_conf:write(T, {<<"aa*dd*dd">>, [], <<>>}), 82 | ok = kvdb_conf:write(T, {<<"bb">>, [], <<>>}), 83 | {ok, {<<"aa">>,_,_}} = kvdb_conf:first(T), 84 | {ok, <<"bb">>} = kvdb_conf:next_at_level(T, <<"aa">>), 85 | {ok, <<"aa*cc">>} = kvdb_conf:next_at_level(T, <<"aa*bb">>), 86 | {ok, <<"aa*bb*cc">>} = kvdb_conf:next_at_level(T, <<"aa*bb*">>), 87 | %% Next - find an implicit node (its children are present, so therefore, it 88 | %% exists conceptually, even if not physically present). 89 | {ok, <<"aa*dd">>} = kvdb_conf:next_at_level(T, <<"aa*cc">>), 90 | done = kvdb_conf:next_at_level(T, <<"aa*cc*dd">>), 91 | {ok, <<"aa*bb*dd">>} = kvdb_conf:next_at_level(T, <<"aa*bb*cc">>), 92 | done = kvdb_conf:next_at_level(T, <<"aa*bb*dd">>), 93 | ok = kvdb_conf:delete_table(T). 94 | 95 | first_next_tree() -> 96 | ?mktab(T, []), 97 | ok = kvdb_conf:write(T, {<<"a*b*a">>, [], <<>>}), 98 | ok = kvdb_conf:write(T, {<<"a*b*c">>, [], <<>>}), 99 | ok = kvdb_conf:write(T, {<<"a*c*a">>, [], <<>>}), 100 | ok = kvdb_conf:write(T, {<<"b*b*a">>, [], <<>>}), 101 | ok = kvdb_conf:write(T, {<<"b*b*c">>, [], <<>>}), 102 | {conf_tree, <<"a*b">>, [{<<"a">>, [], <<>>}]} = First = 103 | kvdb_conf:first_tree(T), 104 | {conf_tree, <<"b*b">>, [{<<"a">>, [], <<>>}]} = 105 | kvdb_conf:first_tree(T, First), 106 | {conf_tree, <<"b*b">>, [{<<"c">>, [], <<>>}]} = 107 | kvdb_conf:last_tree(T), 108 | ok = kvdb_conf:delete_table(T). 109 | 110 | 111 | conf_tree() -> 112 | ?mktab(T, []), 113 | ok = kvdb_conf:write( 114 | T, 115 | {kvdb_conf:join_key(<<"a">>, <<"b">>), [{b,1}],<<"1">>}), 116 | ok = kvdb_conf:write( 117 | T, 118 | {kvdb_conf:join_key(<<"a">>, <<"c">>), [{c,1}],<<"2">>}), 119 | ok = kvdb_conf:write( 120 | T, 121 | {kvdb_conf:join_key(<<"aa">>, <<"d">>), [{d,1}],<<"3">>}), 122 | [{<<"a*b">>,[{b,1}],<<"1">>}, 123 | {<<"a*c">>,[{c,1}],<<"2">>}, 124 | {<<"aa*d">>,[{d,1}],<<"3">>}] = kvdb_conf:all(T), 125 | {conf_tree, <<"a">>, [{<<"b">>, [{b,1}], <<"1">>}, 126 | {<<"c">>, [{c,1}], <<"2">>}]} = 127 | kvdb_conf:read_tree(T, <<"a">>), 128 | {conf_tree, <<"aa">>, [{<<"d">>, [{d,1}], <<"3">>}]} = 129 | kvdb_conf:read_tree(T, <<"aa">>), 130 | ok = kvdb_conf:write(T, {<<"a">>, [], <<>>}), 131 | %% <<"a">> is a separate object, so must be part of the tree. It cannot 132 | %% be lifted into the root key. 133 | {conf_tree, <<>>, [{<<"a">>, [], <<>>, 134 | [{<<"b">>, [{b,1}], <<"1">>}, 135 | {<<"c">>, [{c,1}], <<"2">>}]}]} = 136 | kvdb_conf:first_tree(T), 137 | ok = kvdb_conf:delete_table(T). 138 | 139 | write_tree() -> 140 | ?mktab(T, []), 141 | L = [{<<"a*b">>, [], <<"1">>}, 142 | {<<"a*c">>, [], <<"2">>}, 143 | {<<"a*c*a[00000001]">>,[],<<"3">>}, 144 | {<<"a*c*a[00000002]">>,[],<<"4">>}], 145 | {conf_tree, _, _} = Tree = kvdb_conf:make_tree(L), 146 | %% To write the whole tree, we must first shift the root into the tree. 147 | ok = kvdb_conf:write_tree(T, <<>>, kvdb_conf:shift_root(top, Tree)), 148 | {L,_} = kvdb_conf:prefix_match(T, <<>>), 149 | ok = kvdb_conf:delete_table(T). 150 | 151 | first_next_child() -> 152 | ?mktab(T, []), 153 | ok = kvdb_conf:write(T, {<<"a*b">>, [], <<"1">>}), 154 | ok = kvdb_conf:write(T, {<<"a*c">>, [], <<"2">>}), 155 | ok = kvdb_conf:write(T, {<<"a*c*1">>, [], <<"3">>}), 156 | ok = kvdb_conf:write(T, {<<"a*d">>, [], <<"4">>}), 157 | {ok, <<"a*b">>} = kvdb_conf:first_child(T, <<"a">>), 158 | {ok, <<"a*d">>} = kvdb_conf:last_child(T, <<"a">>), 159 | {ok, <<"a*c">>} = kvdb_conf:next_child(T, <<"a*b">>), 160 | {ok, <<"a*d">>} = kvdb_conf:next_child(T, <<"a*c">>), 161 | done = kvdb_conf:next_child(T, <<"a*d">>), 162 | ok = kvdb_conf:delete_table(T). 163 | 164 | fold_list() -> 165 | ?mktab(T, []), 166 | ok = kvdb_conf:write(T, {<<"a*b*b">>, [], <<>>}), 167 | ok = kvdb_conf:write(T, {<<"a*b*c[00000001]">>, [], <<>>}), 168 | ok = kvdb_conf:write(T, {<<"a*b*c[00000005]*d">>, [], <<>>}), 169 | ok = kvdb_conf:write(T, {<<"a*b*c[00000005]*e">>, [], <<>>}), 170 | ok = kvdb_conf:write(T, {<<"a*b*c[0000000A]">>, [], <<>>}), 171 | ok = kvdb_conf:write(T, {<<"a*b*d">>, [], <<>>}), 172 | [{10, <<"a*b*c[0000000A]">>}, 173 | {5, <<"a*b*c[00000005]">>}, 174 | {1, <<"a*b*c[00000001]">>}] = 175 | kvdb_conf:fold_list(T, fun(I,K,Acc) -> 176 | [{I,K}|Acc] 177 | end, [], <<"a*b*c">>), 178 | ok = kvdb_conf:delete_table(T). 179 | 180 | fold_children() -> 181 | ?mktab(T, []), 182 | ok = kvdb_conf:write(T, {<<"a*b">>, [], <<>>}), 183 | ok = kvdb_conf:write(T, {<<"a*b*b">>, [], <<>>}), 184 | ok = kvdb_conf:write(T, {<<"a*b*c">>, [], <<>>}), 185 | ok = kvdb_conf:write(T, {<<"a*b*d">>, [], <<>>}), 186 | ok = kvdb_conf:write(T, {<<"a*c">>, [], <<>>}), 187 | [<<"a*b*d">>, 188 | <<"a*b*c">>, 189 | <<"a*b*b">>] = 190 | kvdb_conf:fold_children(T, fun(K,Acc) -> 191 | [K|Acc] 192 | end, [], <<"a*b">>), 193 | ok = kvdb_conf:delete_table(T). 194 | 195 | 196 | -endif. 197 | -------------------------------------------------------------------------------- /test/kvdb_lib_proper.erl: -------------------------------------------------------------------------------- 1 | -module(kvdb_lib_proper). 2 | 3 | -ifdef(PROPER). 4 | 5 | -export([q_raw_keyed/0, 6 | q_raw_keyed_sorts/0, 7 | escape_unescape/0, 8 | escape_sorts/0]). 9 | 10 | -include_lib("proper/include/proper.hrl"). 11 | 12 | 13 | -type raw_key() :: binary(). 14 | 15 | q_raw_keyed() -> 16 | T = {keyed,fifo}, 17 | ?FORALL(Key, raw_key(), 18 | begin 19 | {Raw,QK} = 20 | kvdb_lib:actual_key(raw, T, <<"q">>, Key), 21 | QK == kvdb_lib:split_queue_key(raw, T, Raw) 22 | end). 23 | 24 | q_raw_keyed_sorts() -> 25 | T = {keyed,fifo}, 26 | ?FORALL({K1,K2}, {raw_key(), raw_key()}, 27 | begin 28 | {Raw1,_} = kvdb_lib:actual_key(raw, T, <<"q">>, K1), 29 | {Raw2,_} = kvdb_lib:actual_key(raw, T, <<"q">>, K2), 30 | (Raw1 > Raw2) == (K1 > K2) 31 | end). 32 | 33 | escape_sorts() -> 34 | ?FORALL({A,B}, {raw_key(), raw_key()}, 35 | begin 36 | X = kvdb_lib:escape(A), 37 | Y = kvdb_lib:escape(B), 38 | (A < B) == (X < Y) 39 | end). 40 | 41 | escape_unescape() -> 42 | ?FORALL(K, raw_key(), 43 | K == kvdb_lib:unescape(kvdb_lib:escape(K))). 44 | 45 | -endif. % PROPER 46 | -------------------------------------------------------------------------------- /test/kvdb_trans_tests.erl: -------------------------------------------------------------------------------- 1 | %%%---- BEGIN COPYRIGHT ------------------------------------------------------- 2 | %%% 3 | %%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved. 4 | %%% 5 | %%% This Source Code Form is subject to the terms of the Mozilla Public 6 | %%% License, v. 2.0. If a copy of the MPL was not distributed with this 7 | %%% file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | %%% 9 | %%%---- END COPYRIGHT --------------------------------------------------------- 10 | %%% @author Ulf Wiger 11 | -module(kvdb_trans_tests). 12 | 13 | -ifdef(TEST). 14 | 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | -define(_t(E), {timeout,60000, 18 | [?_test(try E catch error:_R_ -> 19 | error({_R_, erlang:get_stacktrace()}) 20 | end)]}). 21 | 22 | -define(tab, iolist_to_binary([<<"t_">>, integer_to_list(?LINE)])). 23 | 24 | -define(dbg(E), 25 | (fun() -> 26 | try (E) of 27 | __V -> 28 | ?debugFmt(<<"~s = ~P">>, [(??E), __V, 15]), 29 | __V 30 | catch 31 | error:__Err -> 32 | io:fwrite(user, 33 | "FAIL: test = ~s~n" 34 | "Error = ~p~n" 35 | "Trace = ~p~n", [(??E), __Err, 36 | erlang:get_stacktrace()]), 37 | error(__Err) 38 | end 39 | end)()). 40 | 41 | 42 | -define(trace(Mods, Expr), begin dbg:tracer(), 43 | lists:foreach( 44 | fun(_M_) when is_atom(_M_) -> 45 | dbg:tpl(_M_,x); 46 | ({_M_,_F_}) -> 47 | dbg:tpl(_M_,_F_,x) 48 | end, Mods), 49 | dbg:p(all,[c]), 50 | try Expr 51 | after 52 | lists:foreach( 53 | fun(_M_) when is_atom(_M_) -> 54 | dbg:ctpl(_M_); 55 | ({_M_,_F_}) -> 56 | dbg:ctpl(_M_,_F_) 57 | end, Mods), 58 | dbg:stop() 59 | end 60 | end). 61 | fill_test_() -> 62 | {setup, 63 | fun() -> 64 | ?debugVal(application:start(gproc)), 65 | ?debugVal(application:start(kvdb)), 66 | ok 67 | end, 68 | fun(_) -> 69 | ?debugVal(application:stop(kvdb)), 70 | ?debugVal(application:stop(gproc)) 71 | end, 72 | {foreachx, 73 | fun({N,Opts,D}) -> 74 | delete_files(Opts), 75 | open_db(N, Opts, D) 76 | end, 77 | [{{N,Opts,D}, fun(_, Db) -> 78 | [?_t(?dbg(fill_db(N, Db, Opts, D))) 79 | , ?_t(?dbg(add_tab_index_get(N))) 80 | , ?_t(?dbg(write_del_write(N))) 81 | , ?_t(?dbg(update_counter(N))) 82 | , ?_t(?dbg(first_next(N))) 83 | , ?_t(?dbg(get_attrs(N))) 84 | , ?_t(?dbg(q(N))) 85 | , ?_t(?dbg(q_push_pop(N))) 86 | , ?_t(?dbg(q_push_prel_pop(N))) 87 | , ?_t(?dbg(q_extract(N))) 88 | , ?_t(?dbg(q_mark_blocking(N))) 89 | , ?_t(?dbg(q_mark_inactive(N))) 90 | , ?_t(?dbg(index_get(N))) 91 | , ?_t(?dbg(index_keys(N))) 92 | , ?_t(?dbg(prefix_match(N))) 93 | , ?_t(?dbg(prefix_match_rel(N))) 94 | , ?_t(?dbg(no_lingering_monitors(N))) 95 | ] 96 | end} || 97 | {N,Opts,D} <- [new_opts(foo_10, 10)]] 98 | }}. 99 | 100 | 101 | delete_files(_Opts) -> ok. 102 | %% {_, Dir} = lists:keyfind(log_dir, 1, Opts), 103 | %% {_, File} = lists:keyfind(file, 1, Opts), 104 | %% ?debugVal({os:cmd("rm -r " ++ Dir), 105 | %% file:delete(File)}). 106 | 107 | open_db(N, Opts, D) -> 108 | {ok, Db} = kvdb:open(N, [{log_dir, filename:join(D, "kvdb.log")}, 109 | {file, filename:join(D, "kvdb.tab")} 110 | | Opts]), 111 | Db. 112 | 113 | new_opts(Name, N) -> 114 | {Name, [{backend, ets}, 115 | {log_threshold, [{writes,N}]}], dirname(Name)}. 116 | 117 | dirname(Name) -> 118 | {_,S,U} = erlang:now(), 119 | filename:join("/tmp", atom_to_list(Name) 120 | ++ "." ++ integer_to_list(S) ++ "." ++ integer_to_list(U)). 121 | 122 | 123 | fill_db(Name, _Db, Opts, D) -> 124 | %% Numbers are set pretty low right now to speed things up. 125 | %% The risks are: (1) sleep is set too low so that log switch doesn't 126 | %% occur within the batch, and (2) the number of writes is too low - really 127 | %% at *least* 2-3 log switches should take place before we end the test. 128 | ?assertMatch(ok, kvdb:add_table(Name, t, [{encoding,sext}])), 129 | Objs = [{N,a} || N <- lists:seq(1,30)], 130 | lists:foreach( 131 | fun(Obj) -> 132 | timer:sleep(30), 133 | kvdb:put(Name, t, Obj) 134 | end, Objs), 135 | ?debugFmt("closing ~p...~n", [Name]), 136 | kvdb:close(Name), 137 | ?debugFmt("DB closed. Trying to reopen...~n", []), 138 | timer:sleep(500), 139 | open_db(Name, Opts, D), 140 | Found = kvdb:prefix_match(Name, t, '_', infinity), 141 | %% io:fwrite(user, "Objs = ~p~n", [Objs]), 142 | ?assertMatch({Objs, _}, Found). 143 | 144 | add_tab_index_get(Name) -> 145 | T1 = ?tab, 146 | kvdb:in_transaction( 147 | Name, 148 | fun(_) -> 149 | ok = kvdb:add_table(Name, T1, [{type,set}, 150 | {encoding, {sext,term,term}}, 151 | {index, [a]}]), 152 | [] = kvdb:index_get(Name, T1, a, v1), 153 | [] = kvdb:index_keys(Name, T1, a, v1), 154 | ok = kvdb:put(Name, T1, {a, [{a, v1}], 1}), 155 | ok = kvdb:put(Name, T1, {b, [{a, v1}], 2}), 156 | [{a,[{a,v1}],1},{b,[{a,v1}],2}] = kvdb:index_get(Name, T1, a, v1), 157 | [a, b] = kvdb:index_keys(Name, T1, a, v1), 158 | ok 159 | end). 160 | 161 | write_del_write(Name) -> 162 | T1 = ?tab, 163 | write_del_write(Name, T1). 164 | 165 | write_del_write(Name, T1) -> 166 | ok = kvdb:add_table(Name, T1, [{type,set}, 167 | {encoding, {sext,term,sext}}]), 168 | ok = kvdb:put(Name, T1, {x, [], 1}), 169 | kvdb:in_transaction( 170 | Name, 171 | fun(_) -> 172 | ok = kvdb:delete(Name, T1, x), 173 | ok = kvdb:put(Name, T1, {x,[],1}), 174 | {ok, {x,[],1}} = kvdb:get(Name, T1, x) 175 | end), 176 | {ok, {x,[],1}} = kvdb:get(Name, T1,x), 177 | ok. 178 | 179 | update_counter(Name) -> 180 | T1 = ?tab, 181 | T2 = ?tab, 182 | ok = kvdb:add_table(Name, T1, [{type, set}, 183 | {encoding, sext}]), 184 | ok = kvdb:add_table(Name, T2, [{type, set}, 185 | {encoding, raw}]), 186 | ok = kvdb:put(Name, T1, {c, 1}), 187 | ok = kvdb:put(Name, T2, {<<"c">>, <<1>>}), 188 | Res1 = kvdb_trans:run( 189 | Name, 190 | fun(_) -> 191 | R1 = kvdb:update_counter(Name, T1, c, 1), 192 | R2 = kvdb:update_counter(Name, T1, c, 1), 193 | R3 = kvdb:update_counter(Name, T2, <<"c">>, 1), 194 | R4 = kvdb:update_counter(Name, T2, <<"c">>, 1), 195 | [R1,R2,R3,R4] 196 | end), 197 | ?assertMatch([2,3,<<2>>,<<3>>], Res1), 198 | ok = kvdb:delete_table(Name, T1), 199 | ok = kvdb:delete_table(Name, T2). 200 | 201 | first_next(Name) -> 202 | T1 = ?tab, 203 | T2 = ?tab, 204 | T3 = ?tab, 205 | [ok,ok,ok] = 206 | [kvdb:add_table(Name, T, [{type,set},{encoding,sext}]) || 207 | T <- [T1,T2,T3]], 208 | [kvdb:put(Name, T, Obj) || {T, Obj} <- [{T1, {t11,a}}, 209 | {T2, {t21,a}}, 210 | {T2, {t22,a}}, 211 | {T3, {t31,a}}]], 212 | Res = kvdb_trans:run( 213 | Name, 214 | fun(_) -> 215 | {ok,{t21,a}} = kvdb:first(Name, T2), 216 | {ok,{t22,a}} = kvdb:next(Name, T2, t21), 217 | {ok,{t21,a}} = kvdb:prev(Name, T2, t22), 218 | {ok,{t22,a}} = kvdb:last(Name, T2), 219 | done = kvdb:next(Name, T2, t22), 220 | done = kvdb:prev(Name, T2, t21) 221 | end), 222 | [ok,ok,ok] = 223 | [kvdb:delete_table(Name, T) || T <- [T1,T2,T3]], 224 | ok. 225 | 226 | get_attrs(Name) -> 227 | T1 = ?tab, 228 | T2 = ?tab, 229 | ok = kvdb:add_table(Name, T1, [{encoding,{raw,sext,term}}]), 230 | ok = kvdb:add_table(Name, T2, [{encoding,{sext,sext,term}}]), 231 | As = [{a,1}, {b,2}, {c,3}], 232 | ok = kvdb:put(Name, T1,{<<"a">>,As,1}), 233 | ok = kvdb:put(Name, T2,{a,As,1}), 234 | kvdb:transaction( 235 | Name, 236 | fun(_) -> 237 | {ok, [{a,1},{b,2}]} = kvdb:get_attrs(Name,T1,<<"a">>,[a,b]), 238 | {ok, [{a,1},{b,2}]} = kvdb:get_attrs(Name,T2,a,[a,b]), 239 | {ok, [{a,1},{b,2},{c,3}]} = 240 | kvdb:get_attrs(Name,T1,<<"a">>,all), 241 | {ok, [{a,1},{b,2},{c,3}]} = 242 | kvdb:get_attrs(Name,T2,a,all), 243 | ok = kvdb:put(Name, T1, {<<"a">>,[{a,10}],1}), 244 | ok = kvdb:put(Name, T2, {a,[{a,10}],1}), 245 | {ok, [{a,10}]} = kvdb:get_attrs(Name,T1,<<"a">>,[a,b]), 246 | {ok, [{a,10}]} = kvdb:get_attrs(Name,T2,a,[a,b]) 247 | end), 248 | ok = kvdb:delete_table(Name, T1), 249 | ok = kvdb:delete_table(Name, T2). 250 | 251 | 252 | q(Name) -> 253 | T = ?tab, 254 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 255 | kvdb:push(Name, T, <<>>, {1,a}), 256 | kvdb:push(Name, T, <<>>, {2,b}), 257 | Res1 = kvdb_trans:run( 258 | Name, fun(_) -> 259 | kvdb:list_queue(Name, T, <<>>) 260 | end), 261 | ?assertMatch({[{1,a},{2,b}], _}, Res1), 262 | kvdb_trans:run( 263 | Name, fun(_) -> 264 | kvdb:push(Name, T, <<>>, {3,c}), 265 | kvdb:push(Name, T, <<>>, {4,d}) 266 | end), 267 | Res2 = kvdb_trans:run( 268 | Name, fun(_) -> 269 | kvdb:list_queue(Name, T, <<>>) 270 | end), 271 | ?assertMatch({[{1,a},{2,b},{3,c},{4,d}], _}, Res2), 272 | ok = kvdb:delete_table(Name, T). 273 | 274 | q_push_pop(Name) -> 275 | T = ?tab, 276 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 277 | kvdb:push(Name, T, q, {1,a}), 278 | kvdb:push(Name, T, q, {2,b}), 279 | Res1 = kvdb_trans:run( 280 | Name, fun(_) -> 281 | kvdb:pop(Name, T, q) 282 | end), 283 | ?assertMatch({ok, {1,a}}, Res1), 284 | ok = kvdb:delete_table(Name, T). 285 | 286 | q_push_prel_pop(Name) -> 287 | T = ?tab, 288 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 289 | {ok, QKey1} = kvdb:push(Name, T, q, {1,a}), 290 | kvdb:push(Name, T, q, {2,b}), 291 | Res1 = kvdb_trans:run( 292 | Name, fun(_) -> 293 | ?assertMatch({ok,{1,a},QKey1}, 294 | kvdb:prel_pop(Name, T, q)), 295 | kvdb:pop(Name, T, q) 296 | end), 297 | ?assertMatch(blocked, Res1), 298 | ok = kvdb:delete_table(Name, T). 299 | 300 | q_extract(Name) -> 301 | T = ?tab, 302 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 303 | {ok, QKey} = kvdb:push(Name, T, q, {1,a}), 304 | kvdb:push(Name, T, q, {2,b}), 305 | Res1 = kvdb_trans:run( 306 | Name, fun(_) -> 307 | ?assertMatch({ok, {1,a}}, 308 | kvdb:extract(Name, T, QKey)), 309 | kvdb:pop(Name, T, q) 310 | end), 311 | ?assertMatch({ok, {2,b}}, Res1), 312 | ok = kvdb:delete_table(Name, T). 313 | 314 | q_mark_blocking(Name) -> 315 | T = ?tab, 316 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 317 | {ok, QK1} = kvdb:push(Name, T, q, {1,a}), 318 | Res1 = 319 | kvdb_trans:run( 320 | Name, fun(DbT) -> 321 | kvdb:mark_queue_object(Name, T, QK1, blocking), 322 | %% io:fwrite(user, "Tstore = ~p~n", 323 | %% [kvdb_trans:tstore_to_list(DbT)]), 324 | kvdb:pop(Name, T, q) 325 | end), 326 | ?assertMatch(blocked, Res1), 327 | Res2 = kvdb:pop(Name, T, q), 328 | ?assertMatch(blocked, Res2), 329 | ok = kvdb:delete_table(Name, T). 330 | 331 | q_mark_inactive(Name) -> 332 | T = ?tab, 333 | ok = kvdb:add_table(Name, T, [{type, fifo}, {encoding, sext}]), 334 | {ok, QK1} = kvdb:push(Name, T, q, {1,a}), 335 | {ok, _} = kvdb:push(Name, T, q, {2,b}), 336 | Res1 = 337 | kvdb_trans:run( 338 | Name, fun(DbT) -> 339 | kvdb:mark_queue_object(Name, T, QK1, inactive), 340 | kvdb:pop(Name, T, q) 341 | end), 342 | ?assertMatch({ok,{2,b}}, Res1), 343 | Res2 = kvdb:pop(Name, T, q), 344 | ?assertMatch(done, Res2), 345 | ok = kvdb:delete_table(Name, T). 346 | 347 | index_get(Name) -> 348 | T = ?tab, 349 | ok = kvdb:add_table(Name, T, [{type, set}, 350 | {encoding, {sext,sext,sext}}, 351 | {index, [a]}]), 352 | ok = kvdb:put(Name, T, {1, [{a, 1}], a}), 353 | ok = kvdb:put(Name, T, {2, [{a, 1}], b}), 354 | Res1 = kvdb_trans:run( 355 | Name, 356 | fun(_) -> 357 | ok = kvdb:put(Name, T, {3, [{a, 1}], c}), 358 | ok = kvdb:put(Name, T, {4, [{a, 1}], d}), 359 | ok = kvdb:put(Name, T, {5, [], e}), 360 | kvdb:index_get(Name, T, a, 1) 361 | end), 362 | ?assertMatch([{1,[{a,1}],a}, 363 | {2,[{a,1}],b}, 364 | {3,[{a,1}],c}, 365 | {4,[{a,1}],d}], Res1), 366 | ok = kvdb:delete_table(Name, T). 367 | 368 | index_keys(Name) -> 369 | T = ?tab, 370 | ok = kvdb:add_table(Name, T, [{type, set}, 371 | {encoding, {sext,sext,sext}}, 372 | {index, [a]}]), 373 | ok = kvdb:put(Name, T, {1, [{a, 1}], a}), 374 | ok = kvdb:put(Name, T, {2, [{a, 1}], b}), 375 | Res1 = kvdb_trans:run( 376 | Name, 377 | fun(_) -> 378 | ok = kvdb:put(Name, T, {3, [{a, 1}], c}), 379 | ok = kvdb:put(Name, T, {4, [{a, 1}], d}), 380 | ok = kvdb:put(Name, T, {5, [], e}), 381 | kvdb:index_keys(Name, T, a, 1) 382 | end), 383 | ?assertMatch([1,2,3,4], Res1), 384 | ok = kvdb:delete_table(Name, T). 385 | 386 | prefix_match(Name) -> 387 | T = ?tab, 388 | ok = kvdb:add_table(Name, T, [{type, set}, {encoding, sext}]), 389 | [ok,ok] = [kvdb:put(Name, T, Obj) || Obj <- [{2,b}, {3,c}]], 390 | Res = kvdb:transaction( 391 | Name, 392 | fun(_) -> 393 | {[{2,b},{3,c}], _} = 394 | kvdb:prefix_match(Name, T, '_', infinity), 395 | [ok,ok] = 396 | [kvdb:put(Name, T, Obj) || Obj <- [{1,a}, {4,d}]], 397 | {[{1,a}, {2,b}], C1} = 398 | kvdb:prefix_match(Name, T, '_', 2), 399 | {[{3,c}, {4,d}], C2} = C1(), 400 | done = C2() 401 | end), 402 | ok. 403 | 404 | prefix_match_rel(_) -> 405 | ok. 406 | 407 | no_lingering_monitors(Name) -> 408 | {monitored_by, Bef} = process_info(self(), monitored_by), 409 | io:fwrite(user, "monitored by Bef = ~p~n", 410 | [[process_info(P) || P <- Bef]]), 411 | T = ?tab, 412 | write_del_write(Name, T), 413 | {monitored_by, Aft} = process_info(self(), monitored_by), 414 | io:fwrite(user, "monitored by Aft = ~p~n", 415 | [[process_info(P) || P <- Aft]]), 416 | [] = Aft -- Bef. 417 | 418 | -endif. 419 | -------------------------------------------------------------------------------- /test/kvdb_usb_ets.erl: -------------------------------------------------------------------------------- 1 | -module(kvdb_usb_ets). 2 | 3 | -compile(export_all). 4 | 5 | -define(SET_SZ, 5). 6 | -define(N, 5). 7 | -define(M, 50). 8 | -define(PAR, 6). 9 | 10 | run() -> 11 | run("/Volumes/USB20FD/t1"). 12 | 13 | run2() -> 14 | run("/Users/uwiger/tmp/usb_test/t1"). 15 | 16 | run3() -> 17 | run_mnesia("/Users/uwiger/tmp/usb_test/m1"). 18 | 19 | run(Dir) -> 20 | init(), 21 | create(usb_test, [{writes, 10}], Dir), 22 | write(usb_test, ?N, ?M). 23 | 24 | run_mnesia(Dir) -> 25 | application:load(mnesia), 26 | application:set_env(mnesia, dir, Dir), 27 | application:set_env(mnesia, dump_log_write_threshold, 10), 28 | mnesia:create_schema([node()]), 29 | mnesia:start(), 30 | mnesia:create_table(t, [{disc_copies, [node()]}]), 31 | mnesia:wait_for_tables([t], 10000), 32 | ets:new(?MODULE, [ordered_set, named_table, public]), 33 | write_mnesia(?N, ?M). 34 | 35 | init() -> 36 | ets:new(?MODULE, [ordered_set, named_table, public]), 37 | kvdb:start(). 38 | 39 | create(Name, Threshold, Dir) -> 40 | Base = filename:join(Dir, kvdb_lib:good_string(Name)), 41 | kvdb:open( 42 | Name, [{file, Base ++ ".db"}, 43 | {log_dir, Base ++ ".log"}, 44 | {backend, ets}, 45 | {save_mode, [on_close, on_switch]}, 46 | {log_threshold, Threshold}]). 47 | 48 | write(Name, N, M) -> 49 | pforeach( 50 | fun({N1,M1}) -> 51 | case M1 rem 2 of 52 | 0 -> 53 | {T, ok} = timer:tc(?MODULE, write_set, [Name, N1]), 54 | log(T, ins); 55 | _ -> 56 | {T, ok} = timer:tc(?MODULE, delete_set, [Name, N1]), 57 | log(T, del) 58 | end 59 | end, [{Nx, Mx} || Mx <- lists:seq(1,M), 60 | Nx <- lists:seq(1,N)]). 61 | 62 | pforeach(F, L) -> 63 | Split = split(?PAR, L), 64 | pforeach_(F, Split). 65 | 66 | pforeach_(F, [L|T]) -> 67 | Pids = [spawn_monitor(fun() -> exit({ok, F(X)}) end) || X <- L], 68 | collect(Pids), 69 | pforeach_(F, T); 70 | pforeach_(_, []) -> 71 | ok. 72 | 73 | split(_, []) -> 74 | []; 75 | split(N, L) -> 76 | case length(L) of 77 | Len when Len > N -> 78 | {A,B} = lists:split(N, L), 79 | [A | split(N, B)]; 80 | _ -> 81 | [L] 82 | end. 83 | 84 | 85 | collect([{_,Ref}|T]) -> 86 | receive 87 | {'DOWN', Ref, _, _, Res} -> 88 | case Res of 89 | {ok, _} -> 90 | collect(T); 91 | Other -> 92 | [exit(P,kill) || {P,_} <- T], 93 | error(Other) 94 | end 95 | end; 96 | collect([]) -> 97 | ok. 98 | 99 | 100 | 101 | write_mnesia(N, M) -> 102 | lists:foreach( 103 | fun({N1,M1}) -> 104 | case M1 rem 2 of 105 | 0 -> 106 | {T, ok} = timer:tc(?MODULE, write_set_m, [N1]), 107 | log(T, ins); 108 | _ -> 109 | {T, ok} = timer:tc(?MODULE, delete_set_m, [N1]), 110 | log(T, del) 111 | end 112 | end, [{Nx, Mx} || Mx <- lists:seq(1,M), 113 | Nx <- lists:seq(1,N)]). 114 | 115 | log(T, Op) -> 116 | ets:insert(?MODULE, {erlang:now(), {T, Op}}), 117 | io:fwrite("~p: ~8w | ~.3f~n", [Op, T, T/?SET_SZ]). 118 | 119 | write_set(Name, N) -> 120 | kvdb_trans:run( 121 | Name, fun(_) -> 122 | kvdb:add_table(Name, Tab = "t_" ++ integer_to_list(N), 123 | [{encoding,sext}]), 124 | R = [kvdb:put(Name, Tab, {N1, a}) || 125 | N1 <- lists:seq(1,?SET_SZ)], 126 | true = lists:all(fun(X) -> X == ok end, R), 127 | ok 128 | end). 129 | write_set_m(N) -> 130 | mnesia:activity( 131 | transaction, 132 | fun() -> 133 | Tab = iolist_to_binary(["t_", integer_to_list(N)]), 134 | R = [mnesia:write({t, {Tab,N1}, a}) || 135 | N1 <- lists:seq(1,?SET_SZ)], 136 | true = lists:all(fun(X) -> X == ok end, R), 137 | ok 138 | end). 139 | 140 | delete_set(Name, N) -> 141 | kvdb_trans:run( 142 | Name, fun(_) -> 143 | kvdb:delete_table(Name, "t_" ++ integer_to_list(N)) 144 | end). 145 | 146 | delete_set_m(N) -> 147 | mnesia:activity( 148 | transaction, 149 | fun() -> 150 | Tab = iolist_to_binary(["t_", integer_to_list(N)]), 151 | Objs = mnesia:match_object({t, {Tab,'_'}, '_'}), 152 | [mnesia:delete({t, element(1,O)}) || O <- Objs], 153 | ok 154 | end). 155 | -------------------------------------------------------------------------------- /tetrapak/config.ini: -------------------------------------------------------------------------------- 1 | [build] 2 | version = "~t.~o~~~c" 3 | 4 | [package] 5 | maintainer = "Ulf Wiger " 6 | exclude = "\\.gitignore|README.md" 7 | architecture = host 8 | 9 | [xref] 10 | 11 | ignore_undef = [ 12 | { eleveldb, delete, 3 }, 13 | { eleveldb, get, 3 }, 14 | { eleveldb, iterator, 2 }, 15 | { eleveldb, iterator_close, 1 }, 16 | { eleveldb, iterator_move, 2 }, 17 | { eleveldb, open, 2 }, 18 | { eleveldb, put, 4 }, 19 | { eleveldb, write, 3 }, 20 | { setup, find_env_vars, 1 }, 21 | { resource, notify_when_destroyed, 2 }, 22 | { sext, decode, 1 }, 23 | { sext, decode_next, 1 }, 24 | { sext, encode, 1 }, 25 | { sext, prefix, 1 }, 26 | { sqlite3, bind, 3 }, 27 | { sqlite3, close, 1 }, 28 | { sqlite3, create_table, 3 }, 29 | { sqlite3, delete, 3 }, 30 | { sqlite3, drop_table, 2 }, 31 | { sqlite3, finalize, 2 }, 32 | { sqlite3, list_tables, 1 }, 33 | { sqlite3, next, 2 }, 34 | { sqlite3, open, 2 }, 35 | { sqlite3, prepare, 2 }, 36 | { sqlite3, read, 3 }, 37 | { sqlite3, sql_exec, 2 }, 38 | { sqlite3, sql_exec, 3 }, 39 | { sqlite3, sql_exec_script, 2 }, 40 | { sqlite3_lib, delete_sql, 3 }, 41 | { sqlite3_lib, value_to_sql, 1 }, 42 | { sqlite3_lib, write_col_sql, 1 }, 43 | { sqlite3_lib, write_value_sql, 1 } 44 | ] 45 | --------------------------------------------------------------------------------