Initial commit
This commit is contained in:
commit
4a2b2d6ac3
32 changed files with 8041 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
*.o
|
||||||
|
.eunit/
|
||||||
|
c_src/perftools-*/
|
||||||
|
c_src/perftools/
|
||||||
|
c_src/snappy-*/
|
||||||
|
c_src/snappy/
|
||||||
|
c_src/leveldb-*/
|
||||||
|
c_src/leveldb/
|
||||||
|
ebin/
|
||||||
|
priv/lib/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
648
README.md
Normal file
648
README.md
Normal file
|
@ -0,0 +1,648 @@
|
||||||
|
|
||||||
|
|
||||||
|
#LETS - LevelDB-based Erlang Term Storage#
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (c) 2011 by Joseph Wayne Norton
|
||||||
|
|
||||||
|
__Authors:__ Joseph Wayne Norton ([`norton@alum.mit.edu`](mailto:norton@alum.mit.edu)).<p>LETS is an alternative Erlang Term Storage using LevelDB as the
|
||||||
|
storage implementation. LETS tries to address some bad properties of
|
||||||
|
ETS and DETS. ETS is limited by physical memory. DETS is limited by
|
||||||
|
a 2 GB file size limitation and does not implement ordered sets. LETS
|
||||||
|
has neither of these limitations.</p>
|
||||||
|
<p>For testing and comparison purposes, LETS supports three
|
||||||
|
implementations:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>drv</tt> C+\+ Driver with LevelDB backend <em>(default)</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>nif</tt> C+\+ NIF with LevelDB backend
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ets</tt> Erlang ETS backend
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>LETS is not intended to be an exact clone of ETS. The currently
|
||||||
|
supported APIs are:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>destroy/2</tt> <em>only driver and nif implementations</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>repair/2</tt> <em>only driver and nif implementations</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert_new/2</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete_all_objects/1</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>lookup/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>first/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>next/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>info/2</tt> <em>only a subset of items</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>tab2list/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><em>This repository is experimental in nature - use at your own risk and
|
||||||
|
please contribute if you find LETS useful.</em></p>
|
||||||
|
|
||||||
|
|
||||||
|
##Quick Start Recipe##
|
||||||
|
|
||||||
|
|
||||||
|
<p>To download and build the lets application in one shot, please follow
|
||||||
|
this recipe:</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ git clone git://github.com/norton/snappy.git snappy
|
||||||
|
$ git clone git://github.com/norton/leveldb.git leveldb
|
||||||
|
$ git clone git://github.com/norton/lets.git lets
|
||||||
|
$ cd lets
|
||||||
|
$ ./rebar get-deps
|
||||||
|
$ ./rebar clean
|
||||||
|
$ ./rebar compile</tt></pre>
|
||||||
|
|
||||||
|
<p>For an alternative recipe with other "features" albeit more complex,
|
||||||
|
please read further.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Documentation##
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="_where_should_i_start">Where should I start?</h3>
|
||||||
|
<p>This README is the only bit of documentation right now.</p>
|
||||||
|
<p>The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
|
||||||
|
"tests/qc" directory should be helpful for understanding the
|
||||||
|
specification and behavior of ETS and LETS. These QC tests also
|
||||||
|
illustrate several strategies for testing Erlang Driver-based and
|
||||||
|
NIF-based implementations.</p>
|
||||||
|
<h3 id="_what_is_ets_and_dets">What is ETS and DETS?</h3>
|
||||||
|
<p>ETS and DETS are Erlang/OTP's standard library modules for Erlang
|
||||||
|
term storage. ETS is a memory-based implementation. DETS is a
|
||||||
|
disk-based implementation.</p>
|
||||||
|
<p>See <a href="http://www.erlang.org/doc/man/ets.html">http://www.erlang.org/doc/man/ets.html</a> and
|
||||||
|
<a href="http://www.erlang.org/doc/man/dets.html">http://www.erlang.org/doc/man/dets.html</a> for further details.</p>
|
||||||
|
<h3 id="_what_is_leveldb">What is LevelDB?</h3>
|
||||||
|
<p>LevelDB is a fast key-value storage library written at Google that
|
||||||
|
provides an ordered mapping from string keys to string values.</p>
|
||||||
|
<p>See <a href="http://code.google.com/p/leveldb/">http://code.google.com/p/leveldb/</a> for further details.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To download##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Configure your e-mail and name for Git
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ git config \--global user.email "you@example.com"
|
||||||
|
$ git config \--global user.name "Your Name"</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Install Repo
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir -p ~/bin
|
||||||
|
$ wget -O - https://github.com/android/tools_repo/raw/master/repo > ~/bin/repo
|
||||||
|
$ perl -i.bak -pe 's!git://android.git.kernel.org/tools/repo.git!git://github.com/android/tools_repo.git!;' ~/bin/repo
|
||||||
|
$ chmod a+x ~/bin/repo</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Caution
|
||||||
|
</td>
|
||||||
|
<td class="content">Since access to kernel.org has been shutdown due to hackers,
|
||||||
|
fetch and replace repo tool with android's GitHub repository mirror.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Create working directory
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ repo init -u git://github.com/norton/manifests.git -m lets-default.xml</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Note
|
||||||
|
</td>
|
||||||
|
<td class="content">Your "Git" identity is needed during the init step. Please
|
||||||
|
enter the name and email of your GitHub account if you have one. Team
|
||||||
|
members having read-write access are recommended to use "repo init -u
|
||||||
|
<a href="mailto:git@github.com">git@github.com</a>:norton/manifests.git -m lets-default-rw.xml".</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">If you want to checkout the latest development version, please
|
||||||
|
append " -b dev" to the repo init command.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Download Git repositories
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name
|
||||||
|
$ repo sync</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<p>For futher information and help for related tools, please refer to the
|
||||||
|
following links:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Erlang - <a href="http://www.erlang.org/">http://www.erlang.org/</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>R14B03 or newer, R14B04 has been tested most recently</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Git - <a href="http://git-scm.com/">http://git-scm.com/</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Git 1.5.4 or newer, Git 1.7.7 has been tested recently</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<em>required for Repo and GitHub</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
GitHub - <a href="https://github.com">https://github.com</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Python - <a href="http://www.python.org">http://www.python.org</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Python 2.4 or newer, Python 2.7.1 has been tested most recently
|
||||||
|
(CAUTION: Python 3.x might be too new)</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<em>required for Repo</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Rebar - <a href="https://github.com/basho/rebar/wiki">https://github.com/basho/rebar/wiki</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Repo - <a href="http://source.android.com/source/git-repo.md">http://source.android.com/source/git-repo.html</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To build - basic recipe##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Get and install an erlang system <a href="http://www.erlang.org">http://www.erlang.org</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Build
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To build - optional features##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="upperalpha">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyzer Testing <em>basic recipe</em>
|
||||||
|
</p>
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Build Dialyzer's PLT <em>(required once)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make build-plt</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">Check Makefile and dialyzer's documentation for further
|
||||||
|
information.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyze with specs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make dialyze</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Caution
|
||||||
|
</td>
|
||||||
|
<td class="content">If you manually run dialyzer with the "-r" option, execute
|
||||||
|
"make clean compile" first to avoid finding duplicate beam files
|
||||||
|
underneath rebar's .eunit directory. Check Makefile for further
|
||||||
|
information.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyze without specs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make dialyze-nospec</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To test - QuickCheck##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Make sure QuickCheck is in your Erlang code path. One simple way
|
||||||
|
to accomplish this is by adding the code path to your <tt>~/.erlang</tt>
|
||||||
|
resource file.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/quviq/eqc-X.Y.Z/ebin").</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Compile for QuickCheck
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-eqc eqc-compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Run 5,000 QuickCheck tests
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">For testing LevelDB directly using the C bindings, try
|
||||||
|
<tt>qc_statemc_lets:run(5000)</tt>.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To test - Proper##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Make sure Proper is in your Erlang code path. One simple way to
|
||||||
|
accomplish this is by adding the code path to your <tt>~/.erlang</tt>
|
||||||
|
resource file.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/proper/ebin").</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Compile for Proper
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-proper proper-compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Run 5,000 Proper tests
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Roadmap##
|
||||||
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Documentation
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Explain how to run QuickCheck/Proper tests using a new rebar
|
||||||
|
plugin.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Explain how to build and to run lets with valgrind enabled
|
||||||
|
OTP/Erlang virtual machine
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Bugs
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
LevelDB - Reappearing "ghost" key after 17 steps
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=44">http://code.google.com/p/leveldb/issues/detail?id=44</a>)
|
||||||
|
<br>
|
||||||
|
NOTE: LET's QC tests are hard-coded not to close and then to
|
||||||
|
reopen a LevelDB database until this bug has been fixed.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Performance
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Update driver implementation to use Erlang's asynchronous driver
|
||||||
|
thread pool for all LevelDB operations.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Testing
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Functional
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Update test model to include LevelDB's database, read, and
|
||||||
|
write options. These options have not undergone any explicit
|
||||||
|
testing.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Performance (TBD)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Stability (TBD)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
New APIs (TBD)
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert_new/2</tt>
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=42">http://code.google.com/p/leveldb/issues/detail?id=42</a>)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete_all_objects/1</tt>
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=43">http://code.google.com/p/leveldb/issues/detail?id=43</a>)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Existing APIs (TBD)
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt> - automatically detect and prevent multiple callers of the
|
||||||
|
same Erlang virtual machine from simultaneously opening the same
|
||||||
|
LevelDB. Excerpt from the Google leveldb mailing list:
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Sanjay Ghemawat
|
||||||
|
View profile
|
||||||
|
More options Sep 30, 1:04 am
|
||||||
|
On Thu, Sep 29, 2011 at 8:30 AM, Joseph Wayne Norton wrote:
|
||||||
|
> Hans -
|
||||||
|
> Thanks. Is is correct to assume that it is the caller's responsibility to
|
||||||
|
> ensure this does not happen?
|
||||||
|
|
||||||
|
leveldb guarantees that it will catch when two distinct processes
|
||||||
|
try to open the db concurrently. However it doesn't guarantee what happens
|
||||||
|
if the same process tries to do so and therefore it is the caller's
|
||||||
|
responsibility
|
||||||
|
to check for concurrent opens from the same process.
|
||||||
|
This is ugly, but the unix file locking primitives are very annoying in
|
||||||
|
this regard. I'll think about whether or not we should clean up the spec
|
||||||
|
by doing extra checks inside the leveldb implementation.</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt> - investigate if LevelDB's snapshot feature is useful (or
|
||||||
|
not) for LETS
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>info/2</tt> - investigate if LevelDB's implementation can (easily)
|
||||||
|
support size and memory info items
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
consider adding explicit read_options and write_options for LET's
|
||||||
|
operations (rather than just <tt>new/2</tt>, <tt>destroy/2</tt>, and <tt>repair/2</tt>
|
||||||
|
operations).
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Modules##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="0" summary="list of modules">
|
||||||
|
<tr><td><a href="https://github.com/norton/lets/blob/master/doc/lets.md" class="module">lets</a></td></tr>
|
||||||
|
<tr><td><a href="https://github.com/norton/lets/blob/master/doc/lets_drv.md" class="module">lets_drv</a></td></tr>
|
||||||
|
<tr><td><a href="https://github.com/norton/lets/blob/master/doc/lets_ets.md" class="module">lets_ets</a></td></tr>
|
||||||
|
<tr><td><a href="https://github.com/norton/lets/blob/master/doc/lets_nif.md" class="module">lets_nif</a></td></tr></table>
|
||||||
|
|
91
c_src/build_deps.sh
Executable file
91
c_src/build_deps.sh
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# The MIT License
|
||||||
|
#
|
||||||
|
# Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
SNAPPY_VSN=HEAD
|
||||||
|
LEVELDB_VSN=HEAD
|
||||||
|
|
||||||
|
if [ `basename $PWD` != "c_src" ]; then
|
||||||
|
pushd c_src
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASEDIR="$PWD"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
clean)
|
||||||
|
rm -rf snappy snappy-$SNAPPY_VSN
|
||||||
|
rm -rf leveldb leveldb-$LEVELDB_VSN
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
# snappy
|
||||||
|
if [ ! -f $BASEDIR/snappy/lib/libsnappy.a ]; then
|
||||||
|
LIBTOOLIZE=libtoolize
|
||||||
|
($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
|
||||||
|
LIBTOOLIZE=glibtoolize
|
||||||
|
($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
|
||||||
|
echo
|
||||||
|
echo "You must have libtool (& friends) installed to compile Judy."
|
||||||
|
echo
|
||||||
|
exit -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(cd ../../snappy && git archive --format=tar --prefix=snappy-$SNAPPY_VSN/ $SNAPPY_VSN) \
|
||||||
|
| tar xf -
|
||||||
|
(cd snappy-$SNAPPY_VSN && \
|
||||||
|
sed -ibak '/^AC_ARG_WITH.*$/, /^fi$/d' configure.ac
|
||||||
|
)
|
||||||
|
(cd snappy-$SNAPPY_VSN && \
|
||||||
|
rm -rf autom4te.cache && \
|
||||||
|
aclocal -I m4 && \
|
||||||
|
autoheader && \
|
||||||
|
$LIBTOOLIZE --copy && \
|
||||||
|
automake --add-missing --copy && \
|
||||||
|
autoconf)
|
||||||
|
(cd snappy-$SNAPPY_VSN && \
|
||||||
|
./configure $CONFFLAGS \
|
||||||
|
--enable-static \
|
||||||
|
--disable-shared \
|
||||||
|
--with-pic \
|
||||||
|
--prefix=$BASEDIR/snappy && \
|
||||||
|
make install)
|
||||||
|
fi
|
||||||
|
# leveldb
|
||||||
|
if [ ! -f $BASEDIR/leveldb/lib/libleveldb.a ]; then
|
||||||
|
(cd ../../leveldb && git archive --format=tar --prefix=leveldb-$LEVELDB_VSN/ $LEVELDB_VSN) \
|
||||||
|
| tar xf -
|
||||||
|
(cd leveldb-$LEVELDB_VSN && \
|
||||||
|
echo "echo \"PLATFORM_CFLAGS+=-fPIC -I$BASEDIR/snappy/include\" >> build_config.mk" >> build_detect_platform &&
|
||||||
|
echo "echo \"PLATFORM_LDFLAGS+=-L $BASEDIR/snappy/lib -lsnappy\" >> build_config.mk" >> build_detect_platform &&
|
||||||
|
make SNAPPY=1 && \
|
||||||
|
mkdir -p $BASEDIR/leveldb/include/leveldb && \
|
||||||
|
install include/leveldb/*.h $BASEDIR/leveldb/include/leveldb && \
|
||||||
|
mkdir -p $BASEDIR/leveldb/lib && \
|
||||||
|
install libleveldb.a $BASEDIR/leveldb/lib)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
751
c_src/lets_drv.cc
Normal file
751
c_src/lets_drv.cc
Normal file
|
@ -0,0 +1,751 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "lets_drv.h"
|
||||||
|
#include "lets_drv_lib.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define GOTOBADARG { fprintf(stderr, "GOTOBADARG %s:%d\n", __FILE__, __LINE__); goto badarg; }
|
||||||
|
#else
|
||||||
|
#define GOTOBADARG { goto badarg; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define LETS_BADARG 0x00
|
||||||
|
#define LETS_TRUE 0x01
|
||||||
|
#define LETS_END_OF_TABLE 0x02
|
||||||
|
|
||||||
|
#define LETS_OPEN6 0x00
|
||||||
|
#define LETS_DESTROY6 0x01
|
||||||
|
#define LETS_REPAIR6 0x02
|
||||||
|
#define LETS_INSERT2 0x03
|
||||||
|
#define LETS_INSERT3 0x04
|
||||||
|
#define LETS_INSERT_NEW2 0x05
|
||||||
|
#define LETS_INSERT_NEW3 0x06
|
||||||
|
#define LETS_DELETE1 0x07
|
||||||
|
#define LETS_DELETE2 0x08
|
||||||
|
#define LETS_DELETE_ALL_OBJECTS1 0x09
|
||||||
|
#define LETS_LOOKUP2 0x0A
|
||||||
|
#define LETS_FIRST1 0x0B
|
||||||
|
#define LETS_NEXT2 0x0C
|
||||||
|
#define LETS_INFO_MEMORY1 0x0D
|
||||||
|
#define LETS_INFO_SIZE1 0x0E
|
||||||
|
|
||||||
|
// DrvData
|
||||||
|
typedef struct {
|
||||||
|
ErlDrvPort port;
|
||||||
|
lets_impl impl;
|
||||||
|
} DrvData;
|
||||||
|
|
||||||
|
struct DrvAsync {
|
||||||
|
DrvData* drvdata;
|
||||||
|
ErlDrvTermData caller;
|
||||||
|
int command;
|
||||||
|
|
||||||
|
leveldb::Slice skey;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
DrvAsync(DrvData* d, ErlDrvTermData c, int cmd) :
|
||||||
|
drvdata(d), caller(c), command(cmd) {
|
||||||
|
}
|
||||||
|
DrvAsync(DrvData* d, ErlDrvTermData c, int cmd, char* key, int keylen) :
|
||||||
|
drvdata(d), caller(c), command(cmd), skey((const char*) key, keylen) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(char* key, int keylen, char* blob, int bloblen) {
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
leveldb::Slice sblob((const char*) blob, bloblen);
|
||||||
|
batch.Put(skey, sblob);
|
||||||
|
}
|
||||||
|
|
||||||
|
void del(char* key, int keylen) {
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
batch.Delete(skey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void lets_output_open6(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_destroy6(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_repair6(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_insert2(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_insert3(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
// static void lets_output_insert_new2(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
// static void lets_output_insert_new3(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_delete1(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_delete2(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
// static void lets_output_delete_all_objects1(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_lookup2(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_first1(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
static void lets_output_next2(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
// static void lets_output_info_memory1(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
// static void lets_output_info_size1(DrvData* d, char* buf, int len, int* index, int items);
|
||||||
|
|
||||||
|
static void
|
||||||
|
driver_send_int(DrvData* d, const int i)
|
||||||
|
{
|
||||||
|
ErlDrvTermData caller = driver_caller(d->port);
|
||||||
|
ErlDrvTermData spec[] = {
|
||||||
|
ERL_DRV_PORT, driver_mk_port(d->port),
|
||||||
|
ERL_DRV_INT, i,
|
||||||
|
ERL_DRV_TUPLE, 2,
|
||||||
|
};
|
||||||
|
driver_send_term(d->port, caller, spec, sizeof(spec) / sizeof(spec[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
driver_send_binary(DrvData* d, const char *buf, const ErlDrvUInt len)
|
||||||
|
{
|
||||||
|
ErlDrvTermData caller = driver_caller(d->port);
|
||||||
|
ErlDrvTermData spec[] = {
|
||||||
|
ERL_DRV_PORT, driver_mk_port(d->port),
|
||||||
|
ERL_DRV_INT, LETS_TRUE,
|
||||||
|
ERL_DRV_BUF2BINARY, (ErlDrvTermData) buf, len,
|
||||||
|
ERL_DRV_TUPLE, 3,
|
||||||
|
};
|
||||||
|
driver_send_term(d->port, caller, spec, sizeof(spec) / sizeof(spec[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Callbacks
|
||||||
|
//
|
||||||
|
static ErlDrvEntry drv_driver_entry = {
|
||||||
|
drv_init,
|
||||||
|
drv_start,
|
||||||
|
drv_stop,
|
||||||
|
drv_output,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
(char*) "lets_drv",
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
drv_ready_async,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
ERL_DRV_EXTENDED_MARKER,
|
||||||
|
ERL_DRV_EXTENDED_MAJOR_VERSION,
|
||||||
|
ERL_DRV_EXTENDED_MINOR_VERSION,
|
||||||
|
ERL_DRV_FLAG_USE_PORT_LOCKING,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
DRIVER_INIT (lets_drv) // must match name in driver_entry
|
||||||
|
{
|
||||||
|
return &drv_driver_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
drv_init()
|
||||||
|
{
|
||||||
|
if (!lets_drv_lib_init()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErlDrvData
|
||||||
|
drv_start(ErlDrvPort port, char* command)
|
||||||
|
{
|
||||||
|
DrvData* d;
|
||||||
|
|
||||||
|
if (port == NULL) {
|
||||||
|
return ERL_DRV_ERROR_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((d = (DrvData*) driver_alloc(sizeof(DrvData))) == NULL) {
|
||||||
|
errno = ENOMEM;
|
||||||
|
return ERL_DRV_ERROR_ERRNO;
|
||||||
|
} else {
|
||||||
|
memset(d, 0, sizeof(DrvData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// port
|
||||||
|
d->port = port;
|
||||||
|
|
||||||
|
return (ErlDrvData) d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drv_stop(ErlDrvData handle)
|
||||||
|
{
|
||||||
|
DrvData* d = (DrvData*) handle;
|
||||||
|
|
||||||
|
// alive
|
||||||
|
d->impl.alive = 0;
|
||||||
|
|
||||||
|
// db
|
||||||
|
delete d->impl.db;
|
||||||
|
|
||||||
|
// db_block_cache
|
||||||
|
delete d->impl.db_block_cache;
|
||||||
|
|
||||||
|
// name
|
||||||
|
delete d->impl.name;
|
||||||
|
|
||||||
|
driver_free(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drv_output(ErlDrvData handle, char* buf, int len)
|
||||||
|
{
|
||||||
|
DrvData* d = (DrvData*) handle;
|
||||||
|
int ng, index, version, items;
|
||||||
|
char command;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
{
|
||||||
|
int term_type, size_needed;
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_version(buf, &index, &version);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_get_type(buf, &index, &term_type, &size_needed);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
fprintf(stderr, "DEBUG %c %d: ", term_type, (int) size_needed);
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_version(buf, &index, &version);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ei_print_term(stderr, buf, &index);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_version(buf, &index, &version);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_decode_tuple_header(buf, &index, &items);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = (items < 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_decode_char(buf, &index, &command);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
items--;
|
||||||
|
switch (command) {
|
||||||
|
case LETS_OPEN6:
|
||||||
|
ng = (items != 6);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_open6(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_DESTROY6:
|
||||||
|
ng = (items != 6);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_destroy6(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_REPAIR6:
|
||||||
|
ng = (items != 6);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_repair6(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_INSERT2:
|
||||||
|
ng = (items != 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_insert2(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_INSERT3:
|
||||||
|
ng = (items != 2);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_insert3(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_INSERT_NEW2:
|
||||||
|
ng = (items != 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
GOTOBADARG;
|
||||||
|
break;
|
||||||
|
case LETS_INSERT_NEW3:
|
||||||
|
ng = (items != 2);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
GOTOBADARG;
|
||||||
|
break;
|
||||||
|
case LETS_DELETE1:
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_delete1(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_DELETE2:
|
||||||
|
ng = (items != 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_delete2(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_DELETE_ALL_OBJECTS1:
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
GOTOBADARG;
|
||||||
|
break;
|
||||||
|
case LETS_LOOKUP2:
|
||||||
|
ng = (items != 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_lookup2(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_FIRST1:
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_first1(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_NEXT2:
|
||||||
|
ng = (items != 1);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
lets_output_next2(d, buf, len, &index, items);
|
||||||
|
break;
|
||||||
|
case LETS_INFO_MEMORY1:
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
GOTOBADARG;
|
||||||
|
break;
|
||||||
|
case LETS_INFO_SIZE1:
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
GOTOBADARG;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drv_ready_async(ErlDrvData handle, ErlDrvThreadData async_data)
|
||||||
|
{
|
||||||
|
DrvData* d = (DrvData*) handle;
|
||||||
|
DrvAsync* a = (DrvAsync*) async_data;
|
||||||
|
|
||||||
|
(void) d;
|
||||||
|
(void) a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
lets_output_do(void* async_data)
|
||||||
|
{
|
||||||
|
DrvAsync* a = (DrvAsync*) async_data;
|
||||||
|
|
||||||
|
(void) a;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Commands
|
||||||
|
//
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_create6(const char op, DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
char type;
|
||||||
|
char privacy;
|
||||||
|
char *name;
|
||||||
|
long namelen;
|
||||||
|
char* options;
|
||||||
|
int options_len;
|
||||||
|
char* read_options;
|
||||||
|
int read_options_len;
|
||||||
|
char* write_options;
|
||||||
|
int write_options_len;
|
||||||
|
|
||||||
|
int ng, term_type, size_needed;
|
||||||
|
|
||||||
|
if (!ei_inspect_atom(buf, index, (char*) "set")) {
|
||||||
|
type = SET;
|
||||||
|
} else if (!ei_inspect_atom(buf, index, (char*) "ordered_set")) {
|
||||||
|
type = ORDERED_SET;
|
||||||
|
} else {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ei_inspect_atom(buf, index, (char*) "private")) {
|
||||||
|
privacy = PRIVATE;
|
||||||
|
} else if (!ei_inspect_atom(buf, index, (char*) "protected")) {
|
||||||
|
privacy = PROTECTED;
|
||||||
|
} else if (!ei_inspect_atom(buf, index, (char*) "public")) {
|
||||||
|
privacy = PUBLIC;
|
||||||
|
} else {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &name, &namelen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
if (!namelen) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_get_type(buf, index, &term_type, &size_needed);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
if (!(term_type == ERL_LIST_EXT || term_type == ERL_NIL_EXT)) GOTOBADARG;
|
||||||
|
options = buf + *index;
|
||||||
|
options_len = size_needed;
|
||||||
|
ng = ei_skip_term(buf, index);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_get_type(buf, index, &term_type, &size_needed);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
if (!(term_type == ERL_LIST_EXT || term_type == ERL_NIL_EXT)) GOTOBADARG;
|
||||||
|
read_options = buf + *index;
|
||||||
|
read_options_len = size_needed;
|
||||||
|
ng = ei_skip_term(buf, index);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
ng = ei_get_type(buf, index, &term_type, &size_needed);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
if (!(term_type == ERL_LIST_EXT || term_type == ERL_NIL_EXT)) GOTOBADARG;
|
||||||
|
write_options = buf + *index;
|
||||||
|
write_options_len = size_needed;
|
||||||
|
ng = ei_skip_term(buf, index);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
|
||||||
|
if (!lets_init(d->impl, type, privacy, name, namelen)) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lets_parse_options(d->impl, options, options_len)) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
if (!lets_parse_read_options(d->impl, read_options, read_options_len)) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
if (!lets_parse_write_options(d->impl, write_options, write_options_len)) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lets_create(d->impl, op)) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_open6(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
lets_output_create6(OPEN, d, buf, len, index, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_destroy6(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
lets_output_create6(DESTROY, d, buf, len, index, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_repair6(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
lets_output_create6(REPAIR, d, buf, len, index, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_insert2(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
int ng, arity;
|
||||||
|
char *key;
|
||||||
|
long keylen;
|
||||||
|
char *blob;
|
||||||
|
long bloblen;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
ng = ei_decode_list_header(buf, index, &items);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
if (!items) {
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (items) {
|
||||||
|
ng = ei_decode_tuple_header(buf, index, &arity);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = (arity != 2);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &blob, &bloblen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
leveldb::Slice sblob((const char*) blob, bloblen);
|
||||||
|
batch.Put(skey, sblob);
|
||||||
|
items--;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng = ei_decode_list_header(buf, index, &items);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = d->impl.db->Write(d->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_insert3(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
int ng;
|
||||||
|
char *key;
|
||||||
|
long keylen;
|
||||||
|
char *blob;
|
||||||
|
long bloblen;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &blob, &bloblen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
{
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
leveldb::Slice sblob((const char*) blob, bloblen);
|
||||||
|
batch.Put(skey, sblob);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = d->impl.db->Write(d->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_delete1(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
leveldb::WriteOptions db_write_options;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// alive
|
||||||
|
d->impl.alive = 0;
|
||||||
|
|
||||||
|
db_write_options.sync = true;
|
||||||
|
status = d->impl.db->Write(db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TBD This is quite risky ... need to re-consider.
|
||||||
|
// delete d->impl.db;
|
||||||
|
// d->impl.db = NULL;
|
||||||
|
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_delete2(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
int ng;
|
||||||
|
char *key;
|
||||||
|
long keylen;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
{
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
batch.Delete(skey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = d->impl.db->Write(d->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_lookup2(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
int ng;
|
||||||
|
char *key;
|
||||||
|
long keylen;
|
||||||
|
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
it->Seek(skey);
|
||||||
|
if (!it->Valid() || it->key().compare(skey) != 0) {
|
||||||
|
driver_send_int(d, LETS_TRUE);
|
||||||
|
delete it;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_binary(d, it->value().data(), it->value().size());
|
||||||
|
delete it;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_first1(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
it->SeekToFirst();
|
||||||
|
if (!it->Valid()) {
|
||||||
|
driver_send_int(d, LETS_END_OF_TABLE);
|
||||||
|
delete it;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_binary(d, it->key().data(), it->key().size());
|
||||||
|
delete it;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_output_next2(DrvData* d, char* buf, int len, int* index, int items)
|
||||||
|
{
|
||||||
|
int ng;
|
||||||
|
char *key;
|
||||||
|
long keylen;
|
||||||
|
|
||||||
|
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
|
||||||
|
if (ng) GOTOBADARG;
|
||||||
|
|
||||||
|
if (!d->impl.alive) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
GOTOBADARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key, keylen);
|
||||||
|
it->Seek(skey);
|
||||||
|
if (!it->Valid()) {
|
||||||
|
driver_send_int(d, LETS_END_OF_TABLE);
|
||||||
|
delete it;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->key().compare(skey) == 0) {
|
||||||
|
it->Next();
|
||||||
|
if (!it->Valid()) {
|
||||||
|
driver_send_int(d, LETS_END_OF_TABLE);
|
||||||
|
delete it;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_send_binary(d, it->key().data(), it->key().size());
|
||||||
|
delete it;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
badarg:
|
||||||
|
driver_send_int(d, LETS_BADARG);
|
||||||
|
}
|
48
c_src/lets_drv.h
Normal file
48
c_src/lets_drv.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef LETS_DRV_H
|
||||||
|
#define LETS_DRV_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ei.h>
|
||||||
|
#include <erl_driver.h>
|
||||||
|
|
||||||
|
// @doc driver init
|
||||||
|
DRIVER_INIT(lib_lets_drv);
|
||||||
|
|
||||||
|
// @doc driver callbacks
|
||||||
|
int drv_init(void);
|
||||||
|
|
||||||
|
ErlDrvData drv_start(ErlDrvPort port, char* command);
|
||||||
|
void drv_stop(ErlDrvData);
|
||||||
|
void drv_output(ErlDrvData drv_data, char* buf, int len);
|
||||||
|
void drv_ready_async(ErlDrvData, ErlDrvThreadData);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LETS_DRV_H */
|
455
c_src/lets_drv_lib.cc
Normal file
455
c_src/lets_drv_lib.cc
Normal file
|
@ -0,0 +1,455 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "lets_drv_lib.h"
|
||||||
|
|
||||||
|
#include <ei.h>
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static bool
|
||||||
|
return_false(unsigned line) {
|
||||||
|
fprintf(stderr, "FALSE %s:%d\n", __FILE__, line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#define FALSE return_false(__LINE__)
|
||||||
|
#else
|
||||||
|
#define FALSE false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_drv_lib_init()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_init(lets_impl& impl,
|
||||||
|
const char type, const char privacy, const char* name, const size_t namelen)
|
||||||
|
{
|
||||||
|
impl.type = type;
|
||||||
|
impl.privacy = privacy;
|
||||||
|
impl.name = new std::string(name, namelen);
|
||||||
|
if (!impl.name) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl.db_options = leveldb::Options();
|
||||||
|
impl.db_read_options = leveldb::ReadOptions();
|
||||||
|
impl.db_write_options = leveldb::WriteOptions();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_create(lets_impl& impl,
|
||||||
|
const char op)
|
||||||
|
{
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
// db
|
||||||
|
switch (op) {
|
||||||
|
case OPEN:
|
||||||
|
status = leveldb::DB::Open(impl.db_options, impl.name->c_str(), &(impl.db));
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
} else {
|
||||||
|
// alive
|
||||||
|
impl.alive = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DESTROY:
|
||||||
|
status = DestroyDB(impl.name->c_str(), impl.db_options);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REPAIR:
|
||||||
|
status = RepairDB(impl.name->c_str(), impl.db_options);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_options(lets_impl& impl,
|
||||||
|
const char* buf, int len)
|
||||||
|
{
|
||||||
|
int index, ng, items, arity, term_type, size_needed;
|
||||||
|
char atom[MAXATOMLEN];
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items < 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
if (!items) return true;
|
||||||
|
|
||||||
|
while (items) {
|
||||||
|
ng = ei_get_type(buf, &index, &term_type, &size_needed);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
switch (term_type) {
|
||||||
|
case ERL_ATOM_EXT:
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "create_if_missing") == 0) {
|
||||||
|
impl.db_options.create_if_missing = true;
|
||||||
|
} else if (strcmp(atom, "error_if_exists") == 0) {
|
||||||
|
impl.db_options.error_if_exists = true;
|
||||||
|
} else if (strcmp(atom, "paranoid_checks") == 0) {
|
||||||
|
impl.db_options.paranoid_checks = true;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ERL_SMALL_TUPLE_EXT:
|
||||||
|
ng = ei_decode_tuple_header(buf, &index, &arity);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (arity != 2);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
unsigned long val;
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "create_if_missing") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_options.create_if_missing = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_options.create_if_missing = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atom, "error_if_exists") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_options.error_if_exists = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_options.error_if_exists = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atom, "paranoid_checks") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_options.paranoid_checks = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_options.paranoid_checks = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atom, "write_buffer_size") == 0) {
|
||||||
|
ng = ei_decode_ulong(buf, &index, &val);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
impl.db_options.write_buffer_size = val;
|
||||||
|
} else if (strcmp(atom, "max_open_files") == 0) {
|
||||||
|
ng = ei_decode_ulong(buf, &index, &val);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
impl.db_options.max_open_files = val;
|
||||||
|
} else if (strcmp(atom, "block_cache_size") == 0) {
|
||||||
|
ng = ei_decode_ulong(buf, &index, &val);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
impl.db_block_cache_size = val;
|
||||||
|
impl.db_block_cache = leveldb::NewLRUCache(impl.db_block_cache_size);
|
||||||
|
impl.db_options.block_cache = impl.db_block_cache;
|
||||||
|
if (!impl.db_options.block_cache) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atom, "block_size") == 0) {
|
||||||
|
ng = ei_decode_ulong(buf, &index, &val);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
impl.db_options.block_size = val;
|
||||||
|
} else if (strcmp(atom, "block_restart_interval") == 0) {
|
||||||
|
ng = ei_decode_ulong(buf, &index, &val);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
impl.db_options.block_restart_interval = val;
|
||||||
|
} else if (strcmp(atom, "compression") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "no") == 0) {
|
||||||
|
impl.db_options.compression = leveldb::kNoCompression;
|
||||||
|
} else if (strcmp(atom, "snappy") == 0) {
|
||||||
|
impl.db_options.compression = leveldb::kSnappyCompression;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
items--;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_read_options(lets_impl& impl,
|
||||||
|
const char* buf, int len)
|
||||||
|
{
|
||||||
|
int index, ng, items, arity, term_type, size_needed;
|
||||||
|
char atom[MAXATOMLEN];
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items < 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
if (!items) return true;
|
||||||
|
|
||||||
|
while (items) {
|
||||||
|
ng = ei_get_type(buf, &index, &term_type, &size_needed);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
switch (term_type) {
|
||||||
|
case ERL_ATOM_EXT:
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "verify_checksums") == 0) {
|
||||||
|
impl.db_read_options.verify_checksums = true;
|
||||||
|
} else if (strcmp(atom, "fill_cache") == 0) {
|
||||||
|
impl.db_read_options.fill_cache = true;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ERL_SMALL_TUPLE_EXT:
|
||||||
|
ng = ei_decode_tuple_header(buf, &index, &arity);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (arity != 2);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "verify_checksums") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_read_options.verify_checksums = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_read_options.verify_checksums = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (strcmp(atom, "fill_cache") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_read_options.fill_cache = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_read_options.fill_cache = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
items--;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_write_options(lets_impl& impl,
|
||||||
|
const char* buf, int len)
|
||||||
|
{
|
||||||
|
int index, ng, items, arity, term_type, size_needed;
|
||||||
|
char atom[MAXATOMLEN];
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items < 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
if (!items) return true;
|
||||||
|
|
||||||
|
while (items) {
|
||||||
|
ng = ei_get_type(buf, &index, &term_type, &size_needed);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
switch (term_type) {
|
||||||
|
case ERL_ATOM_EXT:
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "sync") == 0) {
|
||||||
|
impl.db_write_options.sync = true;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ERL_SMALL_TUPLE_EXT:
|
||||||
|
ng = ei_decode_tuple_header(buf, &index, &arity);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (arity != 2);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "sync") == 0) {
|
||||||
|
ng = ei_decode_atom(buf, &index, atom);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
if (strcmp(atom, "true") == 0) {
|
||||||
|
impl.db_write_options.sync = true;
|
||||||
|
} else if (strcmp(atom, "false") == 0) {
|
||||||
|
impl.db_write_options.sync = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
items--;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng = ei_decode_list_header(buf, &index, &items);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
ng = (items != 0);
|
||||||
|
if (ng) return FALSE;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// KEEP AS PLACEHOLDER UNTIL PATCHES ARE ACCEPTED BY Erlang/OTP TEAM
|
||||||
|
//
|
||||||
|
|
||||||
|
/*
|
||||||
|
* %CopyrightBegin%
|
||||||
|
*
|
||||||
|
* Copyright Ericsson AB 1998-2010. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Erlang Public License,
|
||||||
|
* Version 1.1, (the "License"); you may not use this file except in
|
||||||
|
* compliance with the License. You should have received a copy of the
|
||||||
|
* Erlang Public License along with this software. If not, it can be
|
||||||
|
* retrieved online at http://www.erlang.org/.
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS"
|
||||||
|
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||||
|
* the License for the specific language governing rights and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* %CopyrightEnd%
|
||||||
|
*
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define get8(s) \
|
||||||
|
((s) += 1, \
|
||||||
|
((unsigned char *)(s))[-1] & 0xff)
|
||||||
|
|
||||||
|
#define get16be(s) \
|
||||||
|
((s) += 2, \
|
||||||
|
(((((unsigned char *)(s))[-2] << 8) | \
|
||||||
|
((unsigned char *)(s))[-1])) & 0xffff)
|
||||||
|
|
||||||
|
#define get32be(s) \
|
||||||
|
((s) += 4, \
|
||||||
|
((((unsigned char *)(s))[-4] << 24) | \
|
||||||
|
(((unsigned char *)(s))[-3] << 16) | \
|
||||||
|
(((unsigned char *)(s))[-2] << 8) | \
|
||||||
|
((unsigned char *)(s))[-1]))
|
||||||
|
|
||||||
|
// This function inspects an atom from the binary format. The p
|
||||||
|
// parameter is the name of the atom and the name should be
|
||||||
|
// zero-terminated. If the name is equal to the atom in binary
|
||||||
|
// format, returns 0. Otherwise, return -1. If name is NULL, no
|
||||||
|
// comparison is done and returns 0.
|
||||||
|
|
||||||
|
int
|
||||||
|
ei_inspect_atom(const char *buf, int *index, char *p)
|
||||||
|
{
|
||||||
|
const char *s = buf + *index;
|
||||||
|
const char *s0 = s;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (get8(s) != ERL_ATOM_EXT) return -1;
|
||||||
|
|
||||||
|
len = get16be(s);
|
||||||
|
|
||||||
|
if (len > MAXATOMLEN) return -1;
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
if (len != (int) strlen(p)) return -1;
|
||||||
|
if (memcmp(p, s, len)) return -1;
|
||||||
|
}
|
||||||
|
s += len;
|
||||||
|
*index += s-s0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function inspects a binary from the binary format. The p
|
||||||
|
// parameter is set to the address of the binary. The len parameter
|
||||||
|
// is set to the actual size of the binary.
|
||||||
|
|
||||||
|
int
|
||||||
|
ei_inspect_binary(const char *buf, int *index, void **p, long *lenp)
|
||||||
|
{
|
||||||
|
const char *s = buf + *index;
|
||||||
|
const char *s0 = s;
|
||||||
|
long len;
|
||||||
|
|
||||||
|
if (get8(s) != ERL_BINARY_EXT) return -1;
|
||||||
|
|
||||||
|
len = get32be(s);
|
||||||
|
if (p) *p = (void*) s;
|
||||||
|
s += len;
|
||||||
|
|
||||||
|
if (lenp) *lenp = len;
|
||||||
|
*index += s-s0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
100
c_src/lets_drv_lib.h
Normal file
100
c_src/lets_drv_lib.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef LETS_DRV_LIB_H
|
||||||
|
#define LETS_DRV_LIB_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "leveldb/db.h"
|
||||||
|
#include "leveldb/cache.h"
|
||||||
|
#include "leveldb/slice.h"
|
||||||
|
#include "leveldb/write_batch.h"
|
||||||
|
|
||||||
|
#include "erl_driver.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long buflen;
|
||||||
|
char buf[1];
|
||||||
|
} Binary;
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
SET = 0x0,
|
||||||
|
ORDERED_SET = 0x1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PrivacyType {
|
||||||
|
PRIVATE = 0x0,
|
||||||
|
PROTECTED = 0x1,
|
||||||
|
PUBLIC = 0x2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DBOpType {
|
||||||
|
OPEN = 0x0,
|
||||||
|
DESTROY = 0x1,
|
||||||
|
REPAIR = 0x2
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char alive;
|
||||||
|
char type;
|
||||||
|
char privacy;
|
||||||
|
std::string* name;
|
||||||
|
leveldb::Options db_options;
|
||||||
|
leveldb::ReadOptions db_read_options;
|
||||||
|
leveldb::WriteOptions db_write_options;
|
||||||
|
size_t db_block_cache_size;
|
||||||
|
leveldb::Cache* db_block_cache;
|
||||||
|
leveldb::DB* db;
|
||||||
|
ErlDrvUInt64 db_memory;
|
||||||
|
ErlDrvUInt64 db_size;
|
||||||
|
} lets_impl;
|
||||||
|
|
||||||
|
// prototypes
|
||||||
|
extern bool lets_drv_lib_init();
|
||||||
|
|
||||||
|
extern bool lets_init(lets_impl& impl,
|
||||||
|
const char type, const char privacy, const char* name, const size_t namelen);
|
||||||
|
extern bool lets_create(lets_impl& impl,
|
||||||
|
const char op);
|
||||||
|
|
||||||
|
extern bool lets_parse_options(lets_impl& impl,
|
||||||
|
const char* buf, int len);
|
||||||
|
extern bool lets_parse_read_options(lets_impl& impl,
|
||||||
|
const char* buf, int len);
|
||||||
|
extern bool lets_parse_write_options(lets_impl& impl,
|
||||||
|
const char* buf, int len);
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
extern int ei_inspect_atom(const char *buf, int *index, char *p);
|
||||||
|
extern int ei_inspect_binary(const char *buf, int *index, void **p, long *lenp);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LETS_DRV_LIB_H */
|
578
c_src/lets_nif.cc
Normal file
578
c_src/lets_nif.cc
Normal file
|
@ -0,0 +1,578 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "lets_nif.h"
|
||||||
|
#include "lets_nif_lib.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static inline ERL_NIF_TERM
|
||||||
|
make_badarg(ErlNifEnv* env, const leveldb::Status& status, unsigned line=0) {
|
||||||
|
if (line) {
|
||||||
|
fprintf(stderr, "MAKEBADARG %s:%d %s\n", __FILE__, line, status.ToString().c_str());
|
||||||
|
}
|
||||||
|
return enif_make_badarg(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define MAKEBADARG(env, status) make_badarg(env, status, __LINE__)
|
||||||
|
#else
|
||||||
|
#define MAKEBADARG(env, status) make_badarg(env, status)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// NifHandle
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
lets_impl impl;
|
||||||
|
} lets_nif_handle;
|
||||||
|
|
||||||
|
static ErlNifResourceType* lets_nif_RESOURCE = NULL;
|
||||||
|
static unsigned lets_nif_RESOURCE_SIZE = sizeof(lets_nif_handle);
|
||||||
|
static ErlNifFunc nif_funcs[] =
|
||||||
|
{
|
||||||
|
{"impl_open", 6, lets_nif_open6},
|
||||||
|
{"impl_destroy", 6, lets_nif_destroy6},
|
||||||
|
{"impl_repair", 6, lets_nif_repair6},
|
||||||
|
{"impl_insert", 2, lets_nif_insert2},
|
||||||
|
{"impl_insert", 3, lets_nif_insert3},
|
||||||
|
{"impl_insert_new", 2, lets_nif_insert_new2},
|
||||||
|
{"impl_insert_new", 3, lets_nif_insert_new3},
|
||||||
|
{"impl_delete", 1, lets_nif_delete1},
|
||||||
|
{"impl_delete", 2, lets_nif_delete2},
|
||||||
|
{"impl_delete_all_objects", 1, lets_nif_delete_all_objects1},
|
||||||
|
{"impl_lookup", 2, lets_nif_lookup2},
|
||||||
|
{"impl_first", 1, lets_nif_first1},
|
||||||
|
{"impl_next", 2, lets_nif_next2},
|
||||||
|
{"impl_info_memory", 1, lets_nif_info_memory1},
|
||||||
|
{"impl_info_size", 1, lets_nif_info_size1},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
lets_nif_resource_dtor(ErlNifEnv* env, void* arg)
|
||||||
|
{
|
||||||
|
lets_nif_handle* h = (lets_nif_handle*) arg;
|
||||||
|
|
||||||
|
// alive
|
||||||
|
h->impl.alive = 0;
|
||||||
|
|
||||||
|
// db
|
||||||
|
delete h->impl.db;
|
||||||
|
|
||||||
|
// db_block_cache
|
||||||
|
delete h->impl.db_block_cache;
|
||||||
|
|
||||||
|
// name
|
||||||
|
delete h->impl.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
|
||||||
|
{
|
||||||
|
if (!lets_nif_lib_init(env)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErlNifResourceFlags flags = (ErlNifResourceFlags) (ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
|
||||||
|
lets_nif_RESOURCE = enif_open_resource_type(env, NULL,
|
||||||
|
"lets_nif_resource",
|
||||||
|
&lets_nif_resource_dtor,
|
||||||
|
flags, NULL);
|
||||||
|
if (lets_nif_RESOURCE == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_INIT(lets_nif, nif_funcs, &on_load, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
static ERL_NIF_TERM
|
||||||
|
db_create6(const char op, ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[], lets_nif_handle** reth)
|
||||||
|
{
|
||||||
|
char type;
|
||||||
|
char privacy;
|
||||||
|
ErlNifBinary path;
|
||||||
|
ERL_NIF_TERM options;
|
||||||
|
unsigned options_len;
|
||||||
|
ERL_NIF_TERM read_options;
|
||||||
|
unsigned read_options_len;
|
||||||
|
ERL_NIF_TERM write_options;
|
||||||
|
unsigned write_options_len;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (enif_is_identical(argv[0], lets_atom_set)) {
|
||||||
|
type = SET;
|
||||||
|
} else if (enif_is_identical(argv[0], lets_atom_ordered_set)) {
|
||||||
|
type = ORDERED_SET;
|
||||||
|
} else {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enif_is_identical(argv[1], lets_atom_private)) {
|
||||||
|
privacy = PRIVATE;
|
||||||
|
} else if (enif_is_identical(argv[1], lets_atom_protected)) {
|
||||||
|
privacy = PROTECTED;
|
||||||
|
} else if (enif_is_identical(argv[1], lets_atom_public)) {
|
||||||
|
privacy = PUBLIC;
|
||||||
|
} else {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enif_inspect_binary(env, argv[2], &path) || !path.size) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enif_get_list_length(env, argv[3], &options_len)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
options = argv[3];
|
||||||
|
|
||||||
|
if (!enif_get_list_length(env, argv[4], &read_options_len)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
read_options = argv[4];
|
||||||
|
|
||||||
|
if (!enif_get_list_length(env, argv[5], &write_options_len)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
write_options = argv[5];
|
||||||
|
|
||||||
|
lets_nif_handle* h = (lets_nif_handle*) enif_alloc_resource(lets_nif_RESOURCE, lets_nif_RESOURCE_SIZE);
|
||||||
|
if (!h) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
memset(h, 0, lets_nif_RESOURCE_SIZE);
|
||||||
|
|
||||||
|
if (!lets_init(h->impl, type, privacy, (const char*) path.data, path.size)) {
|
||||||
|
enif_release_resource(h);
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lets_parse_options(env, h->impl, options, options_len)) {
|
||||||
|
enif_release_resource(h);
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!lets_parse_read_options(env, h->impl, read_options, read_options_len)) {
|
||||||
|
enif_release_resource(h);
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!lets_parse_write_options(env, h->impl, write_options, write_options_len)) {
|
||||||
|
enif_release_resource(h);
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lets_create(h->impl, op)) {
|
||||||
|
enif_release_resource(h);
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
*reth = h;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_open6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h = NULL;
|
||||||
|
ERL_NIF_TERM badarg = db_create6(OPEN, env, argc, argv, &h);
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
return badarg;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM result = enif_make_resource(env, h);
|
||||||
|
enif_release_resource(h);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_destroy6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h = NULL;
|
||||||
|
ERL_NIF_TERM badarg = db_create6(DESTROY, env, argc, argv, &h);
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
return badarg;
|
||||||
|
}
|
||||||
|
|
||||||
|
enif_release_resource(h);
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_repair6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h = NULL;
|
||||||
|
ERL_NIF_TERM badarg = db_create6(REPAIR, env, argc, argv, &h);
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
return badarg;
|
||||||
|
}
|
||||||
|
|
||||||
|
enif_release_resource(h);
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ERL_NIF_TERM list;
|
||||||
|
unsigned list_len;
|
||||||
|
ERL_NIF_TERM head, tail;
|
||||||
|
int arity;
|
||||||
|
const ERL_NIF_TERM* tuple;
|
||||||
|
ErlNifBinary key;
|
||||||
|
ErlNifBinary blob;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_get_list_length(env, argv[1], &list_len)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
list = argv[1];
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (enif_get_list_cell(env, list, &head, &tail)) {
|
||||||
|
if (enif_get_tuple(env, head, &arity, &tuple) && arity == 2) {
|
||||||
|
if (!enif_inspect_binary(env, tuple[0], &key)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, tuple[1], &blob)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key.data, key.size);
|
||||||
|
leveldb::Slice sblob((const char*) blob.data, blob.size);
|
||||||
|
batch.Put(skey, sblob);
|
||||||
|
} else {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
list = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = h->impl.db->Write(h->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ErlNifBinary key;
|
||||||
|
ErlNifBinary blob;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, argv[1], &key)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, argv[2], &blob)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key.data, key.size);
|
||||||
|
leveldb::Slice sblob((const char*) blob.data, blob.size);
|
||||||
|
batch.Put(skey, sblob);
|
||||||
|
|
||||||
|
status = h->impl.db->Write(h->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert_new2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
leveldb::Status status;
|
||||||
|
// @TODO not supported by leveldb
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert_new3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
leveldb::Status status;
|
||||||
|
// @TODO not supported by leveldb
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
leveldb::WriteOptions db_write_options;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alive
|
||||||
|
h->impl.alive = 0;
|
||||||
|
|
||||||
|
db_write_options.sync = true;
|
||||||
|
status = h->impl.db->Write(db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TBD This is quite risky ... need to re-consider.
|
||||||
|
// delete h->impl.db;
|
||||||
|
// h->impl.db = NULL;
|
||||||
|
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ErlNifBinary key;
|
||||||
|
leveldb::WriteBatch batch;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, argv[1], &key)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key.data, key.size);
|
||||||
|
batch.Delete(skey);
|
||||||
|
|
||||||
|
status = h->impl.db->Write(h->impl.db_write_options, &batch);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete_all_objects1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
leveldb::Status status;
|
||||||
|
// @TODO not supported by leveldb
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_lookup2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ErlNifBinary key;
|
||||||
|
ERL_NIF_TERM blob = 0;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, argv[1], &key)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key.data, key.size);
|
||||||
|
it->Seek(skey);
|
||||||
|
if (!it->Valid() || it->key().compare(skey) != 0) {
|
||||||
|
delete it;
|
||||||
|
return lets_atom_true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = it->value().size();
|
||||||
|
unsigned char* b = enif_make_new_binary(env, size, &blob);
|
||||||
|
if (!b) {
|
||||||
|
delete it;
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(b, it->value().data(), size);
|
||||||
|
delete it;
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ERL_NIF_TERM first_key = 0;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
it->SeekToFirst();
|
||||||
|
if (!it->Valid()) {
|
||||||
|
delete it;
|
||||||
|
return lets_atom_end_of_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = it->key().size();
|
||||||
|
unsigned char* k = enif_make_new_binary(env, size, &first_key);
|
||||||
|
if (!k) {
|
||||||
|
delete it;
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(k, it->key().data(), size);
|
||||||
|
delete it;
|
||||||
|
return first_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
ErlNifBinary key;
|
||||||
|
ERL_NIF_TERM next_key = 0;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
if (!enif_inspect_binary(env, argv[1], &key)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
|
||||||
|
if (!it) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
leveldb::Slice skey((const char*) key.data, key.size);
|
||||||
|
it->Seek(skey);
|
||||||
|
if (!it->Valid()) {
|
||||||
|
delete it;
|
||||||
|
return lets_atom_end_of_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->key().compare(skey) == 0) {
|
||||||
|
it->Next();
|
||||||
|
if (!it->Valid()) {
|
||||||
|
delete it;
|
||||||
|
return lets_atom_end_of_table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = it->key().size();
|
||||||
|
unsigned char* k = enif_make_new_binary(env, size, &next_key);
|
||||||
|
if (!k) {
|
||||||
|
delete it;
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(k, it->key().data(), size);
|
||||||
|
delete it;
|
||||||
|
return next_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_info_memory1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
// ERL_NIF_TERM info;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO implementation
|
||||||
|
// info = enif_make_uint64(env, h->impl.db_memory);
|
||||||
|
|
||||||
|
// return info;
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_info_size1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
||||||
|
{
|
||||||
|
lets_nif_handle* h;
|
||||||
|
// ERL_NIF_TERM info;
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h->impl.alive) {
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO implementation
|
||||||
|
// info = enif_make_uint64(env, h->impl.db_size);
|
||||||
|
|
||||||
|
// return info;
|
||||||
|
return MAKEBADARG(env, status);
|
||||||
|
}
|
68
c_src/lets_nif.h
Normal file
68
c_src/lets_nif.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef LETS_NIF_H
|
||||||
|
#define LETS_NIF_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <erl_nif.h>
|
||||||
|
|
||||||
|
// Prototypes
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_open6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_destroy6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_repair6(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert_new2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_insert_new3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_delete_all_objects1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_lookup2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_info_memory1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
ERL_NIF_TERM
|
||||||
|
lets_nif_info_size1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LETS_NIF_H */
|
300
c_src/lets_nif_lib.cc
Normal file
300
c_src/lets_nif_lib.cc
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "lets_nif_lib.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static bool
|
||||||
|
return_false(unsigned line) {
|
||||||
|
fprintf(stderr, "FALSE %s:%d\n", __FILE__, line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#define FALSE return_false(__LINE__)
|
||||||
|
#else
|
||||||
|
#define FALSE false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
ERL_NIF_TERM lets_atom_true = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_false = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_set = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_ordered_set = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_private = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_protected = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_public = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_create_if_missing = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_error_if_exists = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_paranoid_checks = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_write_buffer_size = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_max_open_files = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_block_cache_size = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_block_size = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_compression = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_no = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_snappy = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_block_restart_interval = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_verify_checksums = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_fill_cache = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_sync = 0;
|
||||||
|
ERL_NIF_TERM lets_atom_end_of_table = 0;
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_nif_lib_init(ErlNifEnv* env)
|
||||||
|
{
|
||||||
|
lets_atom_true = enif_make_atom(env, "true");
|
||||||
|
lets_atom_false = enif_make_atom(env, "false");
|
||||||
|
lets_atom_set = enif_make_atom(env, "set");
|
||||||
|
lets_atom_ordered_set = enif_make_atom(env, "ordered_set");
|
||||||
|
lets_atom_private = enif_make_atom(env, "private");
|
||||||
|
lets_atom_protected = enif_make_atom(env, "protected");
|
||||||
|
lets_atom_public = enif_make_atom(env, "public");
|
||||||
|
lets_atom_create_if_missing = enif_make_atom(env, "create_if_missing");
|
||||||
|
lets_atom_error_if_exists = enif_make_atom(env, "error_if_exists");
|
||||||
|
lets_atom_paranoid_checks = enif_make_atom(env, "paranoid_checks");
|
||||||
|
lets_atom_write_buffer_size = enif_make_atom(env, "write_buffer_size");
|
||||||
|
lets_atom_max_open_files = enif_make_atom(env, "max_open_files");
|
||||||
|
lets_atom_block_cache_size = enif_make_atom(env, "block_cache_size");
|
||||||
|
lets_atom_block_size = enif_make_atom(env, "block_size");
|
||||||
|
lets_atom_compression = enif_make_atom(env, "compression");
|
||||||
|
lets_atom_no = enif_make_atom(env, "no");
|
||||||
|
lets_atom_snappy = enif_make_atom(env, "snappy");
|
||||||
|
lets_atom_block_restart_interval = enif_make_atom(env, "block_restart_interval");
|
||||||
|
lets_atom_verify_checksums = enif_make_atom(env, "verify_checksums");
|
||||||
|
lets_atom_fill_cache = enif_make_atom(env, "fill_cache");
|
||||||
|
lets_atom_sync = enif_make_atom(env, "sync");
|
||||||
|
lets_atom_end_of_table = enif_make_atom(env, "$end_of_table");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_init(lets_impl& impl,
|
||||||
|
const char type, const char privacy, const char* name, const size_t namelen)
|
||||||
|
{
|
||||||
|
impl.type = type;
|
||||||
|
impl.privacy = privacy;
|
||||||
|
impl.name = new std::string(name, namelen);
|
||||||
|
if (!impl.name) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl.db_options = leveldb::Options();
|
||||||
|
impl.db_read_options = leveldb::ReadOptions();
|
||||||
|
impl.db_write_options = leveldb::WriteOptions();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_create(lets_impl& impl,
|
||||||
|
const char op)
|
||||||
|
{
|
||||||
|
leveldb::Status status;
|
||||||
|
|
||||||
|
// db
|
||||||
|
switch (op) {
|
||||||
|
case OPEN:
|
||||||
|
status = leveldb::DB::Open(impl.db_options, impl.name->c_str(), &(impl.db));
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
} else {
|
||||||
|
// alive
|
||||||
|
impl.alive = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DESTROY:
|
||||||
|
status = DestroyDB(impl.name->c_str(), impl.db_options);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REPAIR:
|
||||||
|
status = RepairDB(impl.name->c_str(), impl.db_options);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len)
|
||||||
|
{
|
||||||
|
ERL_NIF_TERM head, tail;
|
||||||
|
int arity;
|
||||||
|
const ERL_NIF_TERM* tuple;
|
||||||
|
|
||||||
|
while (enif_get_list_cell(env, options, &head, &tail)) {
|
||||||
|
if (enif_is_identical(head, lets_atom_create_if_missing)) {
|
||||||
|
impl.db_options.create_if_missing = true;
|
||||||
|
} else if (enif_is_identical(head, lets_atom_error_if_exists)) {
|
||||||
|
impl.db_options.error_if_exists = true;
|
||||||
|
} else if (enif_is_identical(head, lets_atom_paranoid_checks)) {
|
||||||
|
impl.db_options.paranoid_checks = true;
|
||||||
|
} else if (enif_get_tuple(env, head, &arity, &tuple) && arity == 2) {
|
||||||
|
unsigned int val;
|
||||||
|
if (enif_is_identical(tuple[0], lets_atom_create_if_missing)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_options.create_if_missing = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_options.create_if_missing = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_error_if_exists)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_options.error_if_exists = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_options.error_if_exists = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_paranoid_checks)) {
|
||||||
|
impl.db_options.paranoid_checks = true;
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_options.paranoid_checks = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_options.paranoid_checks = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_write_buffer_size) &&
|
||||||
|
enif_get_uint(env, tuple[1], &val)) {
|
||||||
|
impl.db_options.write_buffer_size = val;
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_max_open_files) &&
|
||||||
|
enif_get_uint(env, tuple[1], &val)) {
|
||||||
|
impl.db_options.max_open_files = val;
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_block_cache_size) &&
|
||||||
|
enif_get_uint(env, tuple[1], &val)) {
|
||||||
|
impl.db_block_cache_size = val;
|
||||||
|
impl.db_block_cache = leveldb::NewLRUCache(impl.db_block_cache_size);
|
||||||
|
impl.db_options.block_cache = impl.db_block_cache;
|
||||||
|
if (!impl.db_options.block_cache) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_block_size) &&
|
||||||
|
enif_get_uint(env, tuple[1], &val)) {
|
||||||
|
impl.db_options.block_size = val;
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_block_restart_interval) &&
|
||||||
|
enif_get_uint(env, tuple[1], &val)) {
|
||||||
|
impl.db_options.block_restart_interval = val;
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_compression)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_no)) {
|
||||||
|
impl.db_options.compression = leveldb::kNoCompression;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_snappy)) {
|
||||||
|
impl.db_options.compression = leveldb::kSnappyCompression;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
options = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_read_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len)
|
||||||
|
{
|
||||||
|
ERL_NIF_TERM head, tail;
|
||||||
|
int arity;
|
||||||
|
const ERL_NIF_TERM* tuple;
|
||||||
|
|
||||||
|
// TODO: snapshot
|
||||||
|
while (enif_get_list_cell(env, options, &head, &tail)) {
|
||||||
|
if (enif_is_identical(head, lets_atom_verify_checksums)) {
|
||||||
|
impl.db_read_options.verify_checksums = true;
|
||||||
|
} else if (enif_is_identical(head, lets_atom_fill_cache)) {
|
||||||
|
impl.db_read_options.fill_cache = true;
|
||||||
|
} else if (enif_get_tuple(env, head, &arity, &tuple) && arity == 2) {
|
||||||
|
if (enif_is_identical(tuple[0], lets_atom_verify_checksums)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_read_options.verify_checksums = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_read_options.verify_checksums = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else if (enif_is_identical(tuple[0], lets_atom_fill_cache)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_read_options.fill_cache = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_read_options.fill_cache = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
options = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lets_parse_write_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len)
|
||||||
|
{
|
||||||
|
ERL_NIF_TERM head, tail;
|
||||||
|
int arity;
|
||||||
|
const ERL_NIF_TERM* tuple;
|
||||||
|
|
||||||
|
// TODO: snapshot
|
||||||
|
while (enif_get_list_cell(env, options, &head, &tail)) {
|
||||||
|
if (enif_is_identical(head, lets_atom_sync)) {
|
||||||
|
impl.db_write_options.sync = true;
|
||||||
|
} else if (enif_get_tuple(env, head, &arity, &tuple) && arity == 2) {
|
||||||
|
if (enif_is_identical(tuple[0], lets_atom_sync)) {
|
||||||
|
if (enif_is_identical(tuple[1], lets_atom_true)) {
|
||||||
|
impl.db_write_options.sync = true;
|
||||||
|
} else if (enif_is_identical(tuple[1], lets_atom_false)) {
|
||||||
|
impl.db_write_options.sync = false;
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
options = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
119
c_src/lets_nif_lib.h
Normal file
119
c_src/lets_nif_lib.h
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef LETS_NIF_LIB_H
|
||||||
|
#define LETS_NIF_LIB_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "leveldb/db.h"
|
||||||
|
#include "leveldb/cache.h"
|
||||||
|
#include "leveldb/slice.h"
|
||||||
|
#include "leveldb/write_batch.h"
|
||||||
|
|
||||||
|
#include "erl_nif.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long buflen;
|
||||||
|
char buf[1];
|
||||||
|
} Binary;
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
SET = 0x0,
|
||||||
|
ORDERED_SET = 0x1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PrivacyType {
|
||||||
|
PRIVATE = 0x0,
|
||||||
|
PROTECTED = 0x1,
|
||||||
|
PUBLIC = 0x2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DBOpType {
|
||||||
|
OPEN = 0x0,
|
||||||
|
DESTROY = 0x1,
|
||||||
|
REPAIR = 0x2
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char alive;
|
||||||
|
char type;
|
||||||
|
char privacy;
|
||||||
|
std::string* name;
|
||||||
|
leveldb::Options db_options;
|
||||||
|
leveldb::ReadOptions db_read_options;
|
||||||
|
leveldb::WriteOptions db_write_options;
|
||||||
|
size_t db_block_cache_size;
|
||||||
|
leveldb::Cache* db_block_cache;
|
||||||
|
leveldb::DB* db;
|
||||||
|
ErlNifUInt64 db_memory;
|
||||||
|
ErlNifUInt64 db_size;
|
||||||
|
} lets_impl;
|
||||||
|
|
||||||
|
extern ERL_NIF_TERM lets_atom_true;
|
||||||
|
extern ERL_NIF_TERM lets_atom_false;
|
||||||
|
extern ERL_NIF_TERM lets_atom_set;
|
||||||
|
extern ERL_NIF_TERM lets_atom_ordered_set;
|
||||||
|
extern ERL_NIF_TERM lets_atom_private;
|
||||||
|
extern ERL_NIF_TERM lets_atom_protected;
|
||||||
|
extern ERL_NIF_TERM lets_atom_public;
|
||||||
|
extern ERL_NIF_TERM lets_atom_create_if_missing;
|
||||||
|
extern ERL_NIF_TERM lets_atom_error_if_exists;
|
||||||
|
extern ERL_NIF_TERM lets_atom_paranoid_checks;
|
||||||
|
extern ERL_NIF_TERM lets_atom_write_buffer_size;
|
||||||
|
extern ERL_NIF_TERM lets_atom_max_open_files;
|
||||||
|
extern ERL_NIF_TERM lets_atom_block_cache_size;
|
||||||
|
extern ERL_NIF_TERM lets_atom_block_size;
|
||||||
|
extern ERL_NIF_TERM lets_atom_compression;
|
||||||
|
extern ERL_NIF_TERM lets_atom_no;
|
||||||
|
extern ERL_NIF_TERM lets_atom_snappy;
|
||||||
|
extern ERL_NIF_TERM lets_atom_block_restart_interval;
|
||||||
|
extern ERL_NIF_TERM lets_atom_verify_checksums;
|
||||||
|
extern ERL_NIF_TERM lets_atom_fill_cache;
|
||||||
|
extern ERL_NIF_TERM lets_atom_sync;
|
||||||
|
extern ERL_NIF_TERM lets_atom_end_of_table;
|
||||||
|
|
||||||
|
// prototypes
|
||||||
|
extern bool lets_nif_lib_init(ErlNifEnv* env);
|
||||||
|
|
||||||
|
extern bool lets_init(lets_impl& impl,
|
||||||
|
const char type, const char privacy, const char* name, const size_t namelen);
|
||||||
|
extern bool lets_create(lets_impl& impl,
|
||||||
|
const char op);
|
||||||
|
|
||||||
|
extern bool lets_parse_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len);
|
||||||
|
extern bool lets_parse_read_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len);
|
||||||
|
extern bool lets_parse_write_options(ErlNifEnv* env, lets_impl& impl,
|
||||||
|
ERL_NIF_TERM& options, const ERL_NIF_TERM& options_len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LETS_NIF_LIB_H */
|
648
doc/README.md
Normal file
648
doc/README.md
Normal file
|
@ -0,0 +1,648 @@
|
||||||
|
|
||||||
|
|
||||||
|
#LETS - LevelDB-based Erlang Term Storage#
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (c) 2011 by Joseph Wayne Norton
|
||||||
|
|
||||||
|
__Authors:__ Joseph Wayne Norton ([`norton@alum.mit.edu`](mailto:norton@alum.mit.edu)).<p>LETS is an alternative Erlang Term Storage using LevelDB as the
|
||||||
|
storage implementation. LETS tries to address some bad properties of
|
||||||
|
ETS and DETS. ETS is limited by physical memory. DETS is limited by
|
||||||
|
a 2 GB file size limitation and does not implement ordered sets. LETS
|
||||||
|
has neither of these limitations.</p>
|
||||||
|
<p>For testing and comparison purposes, LETS supports three
|
||||||
|
implementations:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>drv</tt> C+\+ Driver with LevelDB backend <em>(default)</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>nif</tt> C+\+ NIF with LevelDB backend
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ets</tt> Erlang ETS backend
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>LETS is not intended to be an exact clone of ETS. The currently
|
||||||
|
supported APIs are:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>destroy/2</tt> <em>only driver and nif implementations</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>repair/2</tt> <em>only driver and nif implementations</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert_new/2</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete_all_objects/1</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>lookup/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>first/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>next/2</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>info/2</tt> <em>only a subset of items</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>tab2list/1</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><em>This repository is experimental in nature - use at your own risk and
|
||||||
|
please contribute if you find LETS useful.</em></p>
|
||||||
|
|
||||||
|
|
||||||
|
##Quick Start Recipe##
|
||||||
|
|
||||||
|
|
||||||
|
<p>To download and build the lets application in one shot, please follow
|
||||||
|
this recipe:</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ git clone git://github.com/norton/snappy.git snappy
|
||||||
|
$ git clone git://github.com/norton/leveldb.git leveldb
|
||||||
|
$ git clone git://github.com/norton/lets.git lets
|
||||||
|
$ cd lets
|
||||||
|
$ ./rebar get-deps
|
||||||
|
$ ./rebar clean
|
||||||
|
$ ./rebar compile</tt></pre>
|
||||||
|
|
||||||
|
<p>For an alternative recipe with other "features" albeit more complex,
|
||||||
|
please read further.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Documentation##
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="_where_should_i_start">Where should I start?</h3>
|
||||||
|
<p>This README is the only bit of documentation right now.</p>
|
||||||
|
<p>The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
|
||||||
|
"tests/qc" directory should be helpful for understanding the
|
||||||
|
specification and behavior of ETS and LETS. These QC tests also
|
||||||
|
illustrate several strategies for testing Erlang Driver-based and
|
||||||
|
NIF-based implementations.</p>
|
||||||
|
<h3 id="_what_is_ets_and_dets">What is ETS and DETS?</h3>
|
||||||
|
<p>ETS and DETS are Erlang/OTP's standard library modules for Erlang
|
||||||
|
term storage. ETS is a memory-based implementation. DETS is a
|
||||||
|
disk-based implementation.</p>
|
||||||
|
<p>See <a href="http://www.erlang.org/doc/man/ets.html">http://www.erlang.org/doc/man/ets.html</a> and
|
||||||
|
<a href="http://www.erlang.org/doc/man/dets.html">http://www.erlang.org/doc/man/dets.html</a> for further details.</p>
|
||||||
|
<h3 id="_what_is_leveldb">What is LevelDB?</h3>
|
||||||
|
<p>LevelDB is a fast key-value storage library written at Google that
|
||||||
|
provides an ordered mapping from string keys to string values.</p>
|
||||||
|
<p>See <a href="http://code.google.com/p/leveldb/">http://code.google.com/p/leveldb/</a> for further details.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To download##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Configure your e-mail and name for Git
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ git config \--global user.email "you@example.com"
|
||||||
|
$ git config \--global user.name "Your Name"</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Install Repo
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir -p ~/bin
|
||||||
|
$ wget -O - https://github.com/android/tools_repo/raw/master/repo > ~/bin/repo
|
||||||
|
$ perl -i.bak -pe 's!git://android.git.kernel.org/tools/repo.git!git://github.com/android/tools_repo.git!;' ~/bin/repo
|
||||||
|
$ chmod a+x ~/bin/repo</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Caution
|
||||||
|
</td>
|
||||||
|
<td class="content">Since access to kernel.org has been shutdown due to hackers,
|
||||||
|
fetch and replace repo tool with android's GitHub repository mirror.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Create working directory
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ repo init -u git://github.com/norton/manifests.git -m lets-default.xml</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Note
|
||||||
|
</td>
|
||||||
|
<td class="content">Your "Git" identity is needed during the init step. Please
|
||||||
|
enter the name and email of your GitHub account if you have one. Team
|
||||||
|
members having read-write access are recommended to use "repo init -u
|
||||||
|
<a href="mailto:git@github.com">git@github.com</a>:norton/manifests.git -m lets-default-rw.xml".</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">If you want to checkout the latest development version, please
|
||||||
|
append " -b dev" to the repo init command.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Download Git repositories
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name
|
||||||
|
$ repo sync</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<p>For futher information and help for related tools, please refer to the
|
||||||
|
following links:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Erlang - <a href="http://www.erlang.org/">http://www.erlang.org/</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>R14B03 or newer, R14B04 has been tested most recently</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Git - <a href="http://git-scm.com/">http://git-scm.com/</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Git 1.5.4 or newer, Git 1.7.7 has been tested recently</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<em>required for Repo and GitHub</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
GitHub - <a href="https://github.com">https://github.com</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Python - <a href="http://www.python.org">http://www.python.org</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Python 2.4 or newer, Python 2.7.1 has been tested most recently
|
||||||
|
(CAUTION: Python 3.x might be too new)</strong>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<em>required for Repo</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Rebar - <a href="https://github.com/basho/rebar/wiki">https://github.com/basho/rebar/wiki</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Repo - <a href="http://source.android.com/source/git-repo.md">http://source.android.com/source/git-repo.html</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To build - basic recipe##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Get and install an erlang system <a href="http://www.erlang.org">http://www.erlang.org</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Build
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To build - optional features##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="upperalpha">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyzer Testing <em>basic recipe</em>
|
||||||
|
</p>
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Build Dialyzer's PLT <em>(required once)</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make build-plt</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">Check Makefile and dialyzer's documentation for further
|
||||||
|
information.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyze with specs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make dialyze</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Caution
|
||||||
|
</td>
|
||||||
|
<td class="content">If you manually run dialyzer with the "-r" option, execute
|
||||||
|
"make clean compile" first to avoid finding duplicate beam files
|
||||||
|
underneath rebar's .eunit directory. Check Makefile for further
|
||||||
|
information.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Dialyze without specs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make dialyze-nospec</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To test - QuickCheck##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Make sure QuickCheck is in your Erlang code path. One simple way
|
||||||
|
to accomplish this is by adding the code path to your <tt>~/.erlang</tt>
|
||||||
|
resource file.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/quviq/eqc-X.Y.Z/ebin").</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Compile for QuickCheck
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-eqc eqc-compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Run 5,000 QuickCheck tests
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......</tt></pre>
|
||||||
|
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td class="icon">
|
||||||
|
Tip
|
||||||
|
</td>
|
||||||
|
<td class="content">For testing LevelDB directly using the C bindings, try
|
||||||
|
<tt>qc_statemc_lets:run(5000)</tt>.</td>
|
||||||
|
</tr></table>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##To test - Proper##
|
||||||
|
|
||||||
|
|
||||||
|
<ol class="arabic">
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Make sure Proper is in your Erlang code path. One simple way to
|
||||||
|
accomplish this is by adding the code path to your <tt>~/.erlang</tt>
|
||||||
|
resource file.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/proper/ebin").</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Compile for Proper
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-proper proper-compile</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Run 5,000 Proper tests
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Roadmap##
|
||||||
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Documentation
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Explain how to run QuickCheck/Proper tests using a new rebar
|
||||||
|
plugin.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Explain how to build and to run lets with valgrind enabled
|
||||||
|
OTP/Erlang virtual machine
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Bugs
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
LevelDB - Reappearing "ghost" key after 17 steps
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=44">http://code.google.com/p/leveldb/issues/detail?id=44</a>)
|
||||||
|
<br>
|
||||||
|
NOTE: LET's QC tests are hard-coded not to close and then to
|
||||||
|
reopen a LevelDB database until this bug has been fixed.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Performance
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Update driver implementation to use Erlang's asynchronous driver
|
||||||
|
thread pool for all LevelDB operations.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Testing
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Functional
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Update test model to include LevelDB's database, read, and
|
||||||
|
write options. These options have not undergone any explicit
|
||||||
|
testing.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Performance (TBD)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Stability (TBD)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
New APIs (TBD)
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>insert_new/2</tt>
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=42">http://code.google.com/p/leveldb/issues/detail?id=42</a>)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>delete_all_objects/1</tt>
|
||||||
|
(<a href="http://code.google.com/p/leveldb/issues/detail?id=43">http://code.google.com/p/leveldb/issues/detail?id=43</a>)
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Existing APIs (TBD)
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt> - automatically detect and prevent multiple callers of the
|
||||||
|
same Erlang virtual machine from simultaneously opening the same
|
||||||
|
LevelDB. Excerpt from the Google leveldb mailing list:
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Sanjay Ghemawat
|
||||||
|
View profile
|
||||||
|
More options Sep 30, 1:04 am
|
||||||
|
On Thu, Sep 29, 2011 at 8:30 AM, Joseph Wayne Norton wrote:
|
||||||
|
> Hans -
|
||||||
|
> Thanks. Is is correct to assume that it is the caller's responsibility to
|
||||||
|
> ensure this does not happen?
|
||||||
|
|
||||||
|
leveldb guarantees that it will catch when two distinct processes
|
||||||
|
try to open the db concurrently. However it doesn't guarantee what happens
|
||||||
|
if the same process tries to do so and therefore it is the caller's
|
||||||
|
responsibility
|
||||||
|
to check for concurrent opens from the same process.
|
||||||
|
This is ugly, but the unix file locking primitives are very annoying in
|
||||||
|
this regard. I'll think about whether or not we should clean up the spec
|
||||||
|
by doing extra checks inside the leveldb implementation.</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>new/2</tt> - investigate if LevelDB's snapshot feature is useful (or
|
||||||
|
not) for LETS
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>info/2</tt> - investigate if LevelDB's implementation can (easily)
|
||||||
|
support size and memory info items
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
consider adding explicit read_options and write_options for LET's
|
||||||
|
operations (rather than just <tt>new/2</tt>, <tt>destroy/2</tt>, and <tt>repair/2</tt>
|
||||||
|
operations).
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##Modules##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="0" summary="list of modules">
|
||||||
|
<tr><td><a href="lets.md" class="module">lets</a></td></tr>
|
||||||
|
<tr><td><a href="lets_drv.md" class="module">lets_drv</a></td></tr>
|
||||||
|
<tr><td><a href="lets_ets.md" class="module">lets_ets</a></td></tr>
|
||||||
|
<tr><td><a href="lets_nif.md" class="module">lets_nif</a></td></tr></table>
|
||||||
|
|
3
doc/edoc-info
Normal file
3
doc/edoc-info
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{application,lets}.
|
||||||
|
{packages,[]}.
|
||||||
|
{modules,[lets,lets_drv,lets_ets,lets_nif]}.
|
796
doc/lets.md
Normal file
796
doc/lets.md
Normal file
|
@ -0,0 +1,796 @@
|
||||||
|
|
||||||
|
|
||||||
|
#Module lets#
|
||||||
|
* [Data Types](#types)
|
||||||
|
* [Function Index](#index)
|
||||||
|
* [Function Details](#functions)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types"></a>
|
||||||
|
|
||||||
|
##Data Types##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-db_opts">db_opts()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>db_opts() = {db, [{path, [file:filename()](file.md#type-filename)} | create_if_missing | {create_if_missing, boolean()} | error_if_exists | {error_if_exists, boolean()} | paranoid_checks | {paranoid_checks, boolean()} | {write_buffer_size, pos_integer()} | {max_open_files, pos_integer()} | {block_cache_size, pos_integer()} | {block_size, pos_integer()} | {block_restart_interval, pos_integer()}]}</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-db_read_opts">db_read_opts()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>db_read_opts() = {db_read, [verify_checksums | {verify_checksums, boolean()} | fill_cache | {fill_cache, boolean()}]}</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-db_write_opts">db_write_opts()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>db_write_opts() = {db_write, [sync | {sync, boolean()}]}</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-ets_opt">ets_opt()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>ets_opt() = set | ordered_set | named_table | {key_pos, pos_integer()} | public | protected | private | compressed</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-impl_opt">impl_opt()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>impl_opt() = drv | nif | ets</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-key">key()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>key() = binary()</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-object">object()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>object() = term()</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-opts">opts()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>opts() = [[ets_opt()](#type-ets_opt) | [impl_opt()](#type-impl_opt) | [db_opts()](#type-db_opts) | [db_read_opts()](#type-db_read_opts) | [db_write_opts()](#type-db_write_opts)]</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###<a name="type-tab">tab()</a>##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
__abstract datatype__: `tab()`
|
||||||
|
<a name="index"></a>
|
||||||
|
|
||||||
|
##Function Index##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-1">delete/1</a></td><td><p>Deletes the entire table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td><p>Deletes all objects with the key <tt>Key</tt> from the table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#delete_all_objects-1">delete_all_objects/1</a></td><td><p>Delete all objects in the table <tt>Tab</tt>. The operation is
|
||||||
|
guaranteed to be atomic and isolated. This function only applies
|
||||||
|
to the <tt>ets</tt> implementation.</p>.</td></tr><tr><td valign="top"><a href="#destroy-2">destroy/2</a></td><td><p>Destroy the contents of the specified table. This function
|
||||||
|
only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>.</td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td><p>Returns the first key <tt>Key</tt> in the table <tt>Tab</tt>. If the table
|
||||||
|
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td><p>Returns information about the table <tt>Tab</tt> as a list of <tt>{Item,
|
||||||
|
Value}</tt> tuples.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid +Item+ options are:</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>owner</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>name</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>named_table</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>type</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>keypos</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>protection</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>compressed</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>memory</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>size</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>.</td></tr><tr><td valign="top"><a href="#insert-2">insert/2</a></td><td><p>Inserts the object or all of the objects in the list
|
||||||
|
<tt>ObjectOrObjects</tt> into the table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td><p>This function works exactly like <tt>insert/2</tt>, with the
|
||||||
|
exception that instead of overwriting objects with the same key, it
|
||||||
|
simply returns false. This function only applies to the <tt>ets</tt>
|
||||||
|
implementation.</p>.</td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td><p>Returns a list of all objects with the key <tt>Key</tt> in the table
|
||||||
|
<tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#new-2">new/2</a></td><td><p>Creates a new table and returns a table identifier which can
|
||||||
|
be used in subsequent operations. The table identifier can be sent
|
||||||
|
to other processes so that a table can be shared between different
|
||||||
|
processes within a node.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LETS properties for +Options+ are:</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>set</tt> The table is a set table - one key, one object, no order
|
||||||
|
among objects. This is the default table type.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ordered_set</tt> The table is an ordered_set table - one key, one
|
||||||
|
object, ordered in Erlang term order, which is the order implied
|
||||||
|
by the <tt><</tt> and <tt>></tt> operators.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>named_table</tt> If this option is present, the name <tt>Name</tt> is
|
||||||
|
associated with the table identifier. <em>only the ets
|
||||||
|
implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{key_pos,pos_integer()}</tt> Specfies which element in the stored
|
||||||
|
tuples should be used as key. By default, it is the first
|
||||||
|
element, i.e. <tt>Pos=1</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>public</tt> Any process may read or write to the table.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>protected</tt> The owner process can read and write to the table.
|
||||||
|
Other processes can only read the table. This is the default
|
||||||
|
setting for the access rights.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>private</tt> Only the owner process can read or write to the table.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>compressed</tt> If this option is present, the table data will be
|
||||||
|
stored in a compressed format.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>drv</tt> If this option is present, the table data will be stored
|
||||||
|
with LevelDB backend via an Erlang Driver. This is the default
|
||||||
|
setting for the table implementation.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>nif</tt> If this option is present, the table data will be stored
|
||||||
|
with LevelDB backend via an Erlang NIF.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ets</tt> If this option is present, the table data will be stored
|
||||||
|
with ETS as the backend.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db, [db_opts()]}</tt> LevelDB database options.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db_read, [db_read_opts()]}</tt> LevelDB read options.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db_write, [db_write_opts()]}</tt> LevelDB write options.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB database properties for +db_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{path, file:filename()}</tt> Open the database with the specified
|
||||||
|
path. The default is <tt>Name</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>create_if_missing | {create_if_missing, boolean()}</tt> If <tt>true</tt>,
|
||||||
|
the database will be created if it is missing. The default is
|
||||||
|
<tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>error_if_exists | {error_if_exists, boolean()}</tt> If <tt>true</tt>, an
|
||||||
|
error is raised if the database already exists. The default is
|
||||||
|
<tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>paranoid_checks | {paranoid_checks, boolean()}</tt> If <tt>true</tt>, the
|
||||||
|
implementation will do aggressive checking of the data it is
|
||||||
|
processing and will stop early if it detects any errors. The
|
||||||
|
default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{write_buffer_size, pos_integer()}</tt> The default is 4MB.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{max_open_files, pos_integer()}</tt> The default is 1000.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_cache_size, pos_integer()}</tt> The default is 8MB.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_size, pos_integer()}</tt> The default is 4K.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_restart_interval, pos_integer()}</tt> The default is 16.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB read properties for +db_read_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>verify_checksums | {verify_checksums, boolean()}</tt> If <tt>true</tt>, all
|
||||||
|
data read from underlying storage will be verified against
|
||||||
|
corresponding checksums. The default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>fill_cache | {fill_cache, boolean()}</tt> If <tt>true</tt>, the data read
|
||||||
|
should be cached in memory. The default is <tt>true</tt>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB write properties for +db_write_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>sync | {sync, boolean()}</tt> If <tt>true</tt>, the write will be flushed
|
||||||
|
from the operating system buffer cache before the write is
|
||||||
|
considered complete. The default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td><p>Returns the next key <tt>Key2</tt>, following the key <tt>Key1</tt> in the
|
||||||
|
table <tt>Tab</tt>. If there is no next key, <tt><em>$end_of_table</em></tt> is
|
||||||
|
returned.</p>.</td></tr><tr><td valign="top"><a href="#repair-2">repair/2</a></td><td><p>If a table cannot be opened, you may attempt to call this
|
||||||
|
method to resurrect as much of the contents of the table as
|
||||||
|
possible. Some data may be lost, so be careful when calling this
|
||||||
|
function on a table that contains important information. This
|
||||||
|
function only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>.</td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td><p>Returns a list of all objects in the table <tt>Tab</tt>. The
|
||||||
|
operation is <strong>not</strong> guaranteed to be atomic and isolated.</p>.</td></tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<a name="functions"></a>
|
||||||
|
|
||||||
|
##Function Details##
|
||||||
|
|
||||||
|
<a name="delete-1"></a>
|
||||||
|
|
||||||
|
###delete/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>delete(Tab::[tab()](#type-tab)) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Deletes the entire table <tt>Tab</tt>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:delete/1](ets.md#delete-1).<a name="delete-2"></a>
|
||||||
|
|
||||||
|
###delete/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>delete(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Deletes all objects with the key <tt>Key</tt> from the table <tt>Tab</tt>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:delete/2](ets.md#delete-2).<a name="delete_all_objects-1"></a>
|
||||||
|
|
||||||
|
###delete_all_objects/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>delete_all_objects(Tab::[tab()](#type-tab)) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Delete all objects in the table <tt>Tab</tt>. The operation is
|
||||||
|
guaranteed to be atomic and isolated. This function only applies
|
||||||
|
to the <tt>ets</tt> implementation.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:delete_all_objects/1](ets.md#delete_all_objects-1).<a name="destroy-2"></a>
|
||||||
|
|
||||||
|
###destroy/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>destroy(Name::atom(), Options::[opts()](#type-opts)) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Destroy the contents of the specified table. This function
|
||||||
|
only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>
|
||||||
|
<a name="first-1"></a>
|
||||||
|
|
||||||
|
###first/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>first(Tab::[tab()](#type-tab)) -> [key()](#type-key) | '$end_of_table'</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Returns the first key <tt>Key</tt> in the table <tt>Tab</tt>. If the table
|
||||||
|
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:first/1](ets.md#first-1).<a name="info-2"></a>
|
||||||
|
|
||||||
|
###info/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>info(Tab::[tab()](#type-tab), Item::atom()) -> term()</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Returns information about the table <tt>Tab</tt> as a list of <tt>{Item,
|
||||||
|
Value}</tt> tuples.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid +Item+ options are:</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>owner</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>name</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>named_table</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>type</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>keypos</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>protection</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>compressed</tt>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>memory</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>size</tt> <em>only the ets implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:info/2](ets.md#info-2).<a name="insert-2"></a>
|
||||||
|
|
||||||
|
###insert/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>insert(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Inserts the object or all of the objects in the list
|
||||||
|
<tt>ObjectOrObjects</tt> into the table <tt>Tab</tt>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:insert/2](ets.md#insert-2).<a name="insert_new-2"></a>
|
||||||
|
|
||||||
|
###insert_new/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>insert_new(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>This function works exactly like <tt>insert/2</tt>, with the
|
||||||
|
exception that instead of overwriting objects with the same key, it
|
||||||
|
simply returns false. This function only applies to the <tt>ets</tt>
|
||||||
|
implementation.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:insert_new/2](ets.md#insert_new-2).<a name="lookup-2"></a>
|
||||||
|
|
||||||
|
###lookup/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>lookup(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -> [[object()](#type-object)]</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Returns a list of all objects with the key <tt>Key</tt> in the table
|
||||||
|
<tt>Tab</tt>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:lookup/2](ets.md#lookup-2).<a name="new-2"></a>
|
||||||
|
|
||||||
|
###new/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>new(Name::atom(), Options::[opts()](#type-opts)) -> [tab()](#type-tab)</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Creates a new table and returns a table identifier which can
|
||||||
|
be used in subsequent operations. The table identifier can be sent
|
||||||
|
to other processes so that a table can be shared between different
|
||||||
|
processes within a node.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LETS properties for +Options+ are:</tt></pre>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>set</tt> The table is a set table - one key, one object, no order
|
||||||
|
among objects. This is the default table type.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ordered_set</tt> The table is an ordered_set table - one key, one
|
||||||
|
object, ordered in Erlang term order, which is the order implied
|
||||||
|
by the <tt><</tt> and <tt>></tt> operators.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>named_table</tt> If this option is present, the name <tt>Name</tt> is
|
||||||
|
associated with the table identifier. <em>only the ets
|
||||||
|
implementation</em>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{key_pos,pos_integer()}</tt> Specfies which element in the stored
|
||||||
|
tuples should be used as key. By default, it is the first
|
||||||
|
element, i.e. <tt>Pos=1</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>public</tt> Any process may read or write to the table.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>protected</tt> The owner process can read and write to the table.
|
||||||
|
Other processes can only read the table. This is the default
|
||||||
|
setting for the access rights.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>private</tt> Only the owner process can read or write to the table.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>compressed</tt> If this option is present, the table data will be
|
||||||
|
stored in a compressed format.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>drv</tt> If this option is present, the table data will be stored
|
||||||
|
with LevelDB backend via an Erlang Driver. This is the default
|
||||||
|
setting for the table implementation.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>nif</tt> If this option is present, the table data will be stored
|
||||||
|
with LevelDB backend via an Erlang NIF.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>ets</tt> If this option is present, the table data will be stored
|
||||||
|
with ETS as the backend.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db, [db_opts()]}</tt> LevelDB database options.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db_read, [db_read_opts()]}</tt> LevelDB read options.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{db_write, [db_write_opts()]}</tt> LevelDB write options.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB database properties for +db_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{path, file:filename()}</tt> Open the database with the specified
|
||||||
|
path. The default is <tt>Name</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>create_if_missing | {create_if_missing, boolean()}</tt> If <tt>true</tt>,
|
||||||
|
the database will be created if it is missing. The default is
|
||||||
|
<tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>error_if_exists | {error_if_exists, boolean()}</tt> If <tt>true</tt>, an
|
||||||
|
error is raised if the database already exists. The default is
|
||||||
|
<tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>paranoid_checks | {paranoid_checks, boolean()}</tt> If <tt>true</tt>, the
|
||||||
|
implementation will do aggressive checking of the data it is
|
||||||
|
processing and will stop early if it detects any errors. The
|
||||||
|
default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{write_buffer_size, pos_integer()}</tt> The default is 4MB.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{max_open_files, pos_integer()}</tt> The default is 1000.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_cache_size, pos_integer()}</tt> The default is 8MB.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_size, pos_integer()}</tt> The default is 4K.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>{block_restart_interval, pos_integer()}</tt> The default is 16.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB read properties for +db_read_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>verify_checksums | {verify_checksums, boolean()}</tt> If <tt>true</tt>, all
|
||||||
|
data read from underlying storage will be verified against
|
||||||
|
corresponding checksums. The default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>fill_cache | {fill_cache, boolean()}</tt> If <tt>true</tt>, the data read
|
||||||
|
should be cached in memory. The default is <tt>true</tt>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<pre><tt>Valid LevelDB write properties for +db_write_opts()+ are:</tt></pre>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<tt>sync | {sync, boolean()}</tt> If <tt>true</tt>, the write will be flushed
|
||||||
|
from the operating system buffer cache before the write is
|
||||||
|
considered complete. The default is <tt>false</tt>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:new/2](ets.md#new-2).<a name="next-2"></a>
|
||||||
|
|
||||||
|
###next/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>next(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -> [key()](#type-key) | '$end_of_table'</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Returns the next key <tt>Key2</tt>, following the key <tt>Key1</tt> in the
|
||||||
|
table <tt>Tab</tt>. If there is no next key, <tt><em>$end_of_table</em></tt> is
|
||||||
|
returned.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:next/2](ets.md#next-2).<a name="repair-2"></a>
|
||||||
|
|
||||||
|
###repair/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>repair(Name::atom(), Options::[opts()](#type-opts)) -> true</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>If a table cannot be opened, you may attempt to call this
|
||||||
|
method to resurrect as much of the contents of the table as
|
||||||
|
possible. Some data may be lost, so be careful when calling this
|
||||||
|
function on a table that contains important information. This
|
||||||
|
function only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>
|
||||||
|
<a name="tab2list-1"></a>
|
||||||
|
|
||||||
|
###tab2list/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>tab2list(Tab::[tab()](#type-tab)) -> [[object()](#type-object)]</pre>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p>Returns a list of all objects in the table <tt>Tab</tt>. The
|
||||||
|
operation is <strong>not</strong> guaranteed to be atomic and isolated.</p>
|
||||||
|
|
||||||
|
|
||||||
|
__See also:__ [ets:tab2list/1](ets.md#tab2list-1).
|
147
doc/lets_drv.md
Normal file
147
doc/lets_drv.md
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
|
||||||
|
#Module lets_drv#
|
||||||
|
* [Function Index](#index)
|
||||||
|
* [Function Details](#functions)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="index"></a>
|
||||||
|
|
||||||
|
##Function Index##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<a name="functions"></a>
|
||||||
|
|
||||||
|
##Function Details##
|
||||||
|
|
||||||
|
<a name="delete-2"></a>
|
||||||
|
|
||||||
|
###delete/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Drv) -> any()`
|
||||||
|
|
||||||
|
<a name="delete-3"></a>
|
||||||
|
|
||||||
|
###delete/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Drv, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="delete_all_objects-2"></a>
|
||||||
|
|
||||||
|
###delete_all_objects/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete_all_objects(Tab, Drv) -> any()`
|
||||||
|
|
||||||
|
<a name="destroy-4"></a>
|
||||||
|
|
||||||
|
###destroy/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="first-2"></a>
|
||||||
|
|
||||||
|
###first/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`first(Tab, Drv) -> any()`
|
||||||
|
|
||||||
|
<a name="info_memory-2"></a>
|
||||||
|
|
||||||
|
###info_memory/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_memory(Tab, Drv) -> any()`
|
||||||
|
|
||||||
|
<a name="info_size-2"></a>
|
||||||
|
|
||||||
|
###info_size/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_size(Tab, Drv) -> any()`
|
||||||
|
|
||||||
|
<a name="insert-3"></a>
|
||||||
|
|
||||||
|
###insert/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert(Tab, Drv, Object) -> any()`
|
||||||
|
|
||||||
|
<a name="insert_new-3"></a>
|
||||||
|
|
||||||
|
###insert_new/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert_new(Tab, Drv, Object) -> any()`
|
||||||
|
|
||||||
|
<a name="lookup-3"></a>
|
||||||
|
|
||||||
|
###lookup/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`lookup(Tab, Drv, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="next-3"></a>
|
||||||
|
|
||||||
|
###next/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`next(Tab, Drv, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="open-4"></a>
|
||||||
|
|
||||||
|
###open/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="repair-4"></a>
|
||||||
|
|
||||||
|
###repair/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="tab2list-2"></a>
|
||||||
|
|
||||||
|
###tab2list/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`tab2list(Tab, Drv) -> any()`
|
||||||
|
|
147
doc/lets_ets.md
Normal file
147
doc/lets_ets.md
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
|
||||||
|
#Module lets_ets#
|
||||||
|
* [Function Index](#index)
|
||||||
|
* [Function Details](#functions)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="index"></a>
|
||||||
|
|
||||||
|
##Function Index##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-1">destroy/1</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-1">open/1</a></td><td></td></tr><tr><td valign="top"><a href="#repair-1">repair/1</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<a name="functions"></a>
|
||||||
|
|
||||||
|
##Function Details##
|
||||||
|
|
||||||
|
<a name="delete-2"></a>
|
||||||
|
|
||||||
|
###delete/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Ets) -> any()`
|
||||||
|
|
||||||
|
<a name="delete-3"></a>
|
||||||
|
|
||||||
|
###delete/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Ets, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="delete_all_objects-2"></a>
|
||||||
|
|
||||||
|
###delete_all_objects/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete_all_objects(Tab, Ets) -> any()`
|
||||||
|
|
||||||
|
<a name="destroy-1"></a>
|
||||||
|
|
||||||
|
###destroy/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`destroy(Tab) -> any()`
|
||||||
|
|
||||||
|
<a name="first-2"></a>
|
||||||
|
|
||||||
|
###first/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`first(Tab, Ets) -> any()`
|
||||||
|
|
||||||
|
<a name="info_memory-2"></a>
|
||||||
|
|
||||||
|
###info_memory/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_memory(Tab, Ets) -> any()`
|
||||||
|
|
||||||
|
<a name="info_size-2"></a>
|
||||||
|
|
||||||
|
###info_size/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_size(Tab, Ets) -> any()`
|
||||||
|
|
||||||
|
<a name="insert-3"></a>
|
||||||
|
|
||||||
|
###insert/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert(Tab, Ets, ObjectOrObjects) -> any()`
|
||||||
|
|
||||||
|
<a name="insert_new-3"></a>
|
||||||
|
|
||||||
|
###insert_new/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert_new(Tab, Ets, ObjectOrObjects) -> any()`
|
||||||
|
|
||||||
|
<a name="lookup-3"></a>
|
||||||
|
|
||||||
|
###lookup/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`lookup(Tab, Ets, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="next-3"></a>
|
||||||
|
|
||||||
|
###next/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`next(Tab, Ets, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="open-1"></a>
|
||||||
|
|
||||||
|
###open/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`open(Tab) -> any()`
|
||||||
|
|
||||||
|
<a name="repair-1"></a>
|
||||||
|
|
||||||
|
###repair/1##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`repair(Tab) -> any()`
|
||||||
|
|
||||||
|
<a name="tab2list-2"></a>
|
||||||
|
|
||||||
|
###tab2list/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`tab2list(Tab, Ets) -> any()`
|
||||||
|
|
147
doc/lets_nif.md
Normal file
147
doc/lets_nif.md
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
|
||||||
|
#Module lets_nif#
|
||||||
|
* [Function Index](#index)
|
||||||
|
* [Function Details](#functions)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="index"></a>
|
||||||
|
|
||||||
|
##Function Index##
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<a name="functions"></a>
|
||||||
|
|
||||||
|
##Function Details##
|
||||||
|
|
||||||
|
<a name="delete-2"></a>
|
||||||
|
|
||||||
|
###delete/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Nif) -> any()`
|
||||||
|
|
||||||
|
<a name="delete-3"></a>
|
||||||
|
|
||||||
|
###delete/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete(Tab, Nif, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="delete_all_objects-2"></a>
|
||||||
|
|
||||||
|
###delete_all_objects/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`delete_all_objects(Tab, Nif) -> any()`
|
||||||
|
|
||||||
|
<a name="destroy-4"></a>
|
||||||
|
|
||||||
|
###destroy/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="first-2"></a>
|
||||||
|
|
||||||
|
###first/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`first(Tab, Nif) -> any()`
|
||||||
|
|
||||||
|
<a name="info_memory-2"></a>
|
||||||
|
|
||||||
|
###info_memory/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_memory(Tab, Nif) -> any()`
|
||||||
|
|
||||||
|
<a name="info_size-2"></a>
|
||||||
|
|
||||||
|
###info_size/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`info_size(Tab, Nif) -> any()`
|
||||||
|
|
||||||
|
<a name="insert-3"></a>
|
||||||
|
|
||||||
|
###insert/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert(Tab, Nif, Object) -> any()`
|
||||||
|
|
||||||
|
<a name="insert_new-3"></a>
|
||||||
|
|
||||||
|
###insert_new/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`insert_new(Tab, Nif, Object) -> any()`
|
||||||
|
|
||||||
|
<a name="lookup-3"></a>
|
||||||
|
|
||||||
|
###lookup/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`lookup(Tab, Nif, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="next-3"></a>
|
||||||
|
|
||||||
|
###next/3##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`next(Tab, Nif, Key) -> any()`
|
||||||
|
|
||||||
|
<a name="open-4"></a>
|
||||||
|
|
||||||
|
###open/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="repair-4"></a>
|
||||||
|
|
||||||
|
###repair/4##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
|
||||||
|
|
||||||
|
<a name="tab2list-2"></a>
|
||||||
|
|
||||||
|
###tab2list/2##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`tab2list(Tab, Nif) -> any()`
|
||||||
|
|
318
doc/overview.edoc
Normal file
318
doc/overview.edoc
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
%% -*- Doc -*-
|
||||||
|
%% vim: set syntax=asciidoc:
|
||||||
|
@author Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
@copyright 2011 by Joseph Wayne Norton
|
||||||
|
@title LETS - LevelDB-based Erlang Term Storage
|
||||||
|
@doc
|
||||||
|
LETS is an alternative Erlang Term Storage using LevelDB as the
|
||||||
|
storage implementation. LETS tries to address some bad properties of
|
||||||
|
ETS and DETS. ETS is limited by physical memory. DETS is limited by
|
||||||
|
a 2 GB file size limitation and does not implement ordered sets. LETS
|
||||||
|
has neither of these limitations.
|
||||||
|
|
||||||
|
For testing and comparison purposes, LETS supports three
|
||||||
|
implementations:
|
||||||
|
|
||||||
|
- +drv+ C\+\+ Driver with LevelDB backend _(default)_
|
||||||
|
- +nif+ C\+\+ NIF with LevelDB backend
|
||||||
|
- +ets+ Erlang ETS backend
|
||||||
|
|
||||||
|
LETS is not intended to be an exact clone of ETS. The currently
|
||||||
|
supported APIs are:
|
||||||
|
|
||||||
|
- +new/2+
|
||||||
|
- +destroy/2+ _only driver and nif implementations_
|
||||||
|
- +repair/2+ _only driver and nif implementations_
|
||||||
|
- +insert/2+
|
||||||
|
- +insert_new/2+ _only the ets implementation_
|
||||||
|
- +delete/1+
|
||||||
|
- +delete/2+
|
||||||
|
- +delete_all_objects/1+ _only the ets implementation_
|
||||||
|
- +lookup/2+
|
||||||
|
- +first/1+
|
||||||
|
- +next/2+
|
||||||
|
- +info/2+ _only a subset of items_
|
||||||
|
- +tab2list/1+
|
||||||
|
|
||||||
|
_This repository is experimental in nature - use at your own risk and
|
||||||
|
please contribute if you find LETS useful._
|
||||||
|
|
||||||
|
== Quick Start Recipe
|
||||||
|
|
||||||
|
To download and build the lets application in one shot, please follow
|
||||||
|
this recipe:
|
||||||
|
|
||||||
|
------
|
||||||
|
$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ git clone git://github.com/norton/snappy.git snappy
|
||||||
|
$ git clone git://github.com/norton/leveldb.git leveldb
|
||||||
|
$ git clone git://github.com/norton/lets.git lets
|
||||||
|
$ cd lets
|
||||||
|
$ ./rebar get-deps
|
||||||
|
$ ./rebar clean
|
||||||
|
$ ./rebar compile
|
||||||
|
------
|
||||||
|
|
||||||
|
For an alternative recipe with other "features" albeit more complex,
|
||||||
|
please read further.
|
||||||
|
|
||||||
|
== Documentation
|
||||||
|
|
||||||
|
=== Where should I start?
|
||||||
|
|
||||||
|
This README is the only bit of documentation right now.
|
||||||
|
|
||||||
|
The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
|
||||||
|
"tests/qc" directory should be helpful for understanding the
|
||||||
|
specification and behavior of ETS and LETS. These QC tests also
|
||||||
|
illustrate several strategies for testing Erlang Driver-based and
|
||||||
|
NIF-based implementations.
|
||||||
|
|
||||||
|
=== What is ETS and DETS?
|
||||||
|
|
||||||
|
ETS and DETS are Erlang/OTP\'s standard library modules for Erlang
|
||||||
|
term storage. ETS is a memory-based implementation. DETS is a
|
||||||
|
disk-based implementation.
|
||||||
|
|
||||||
|
See http://www.erlang.org/doc/man/ets.html and
|
||||||
|
http://www.erlang.org/doc/man/dets.html for further details.
|
||||||
|
|
||||||
|
=== What is LevelDB?
|
||||||
|
|
||||||
|
LevelDB is a fast key-value storage library written at Google that
|
||||||
|
provides an ordered mapping from string keys to string values.
|
||||||
|
|
||||||
|
See http://code.google.com/p/leveldb/ for further details.
|
||||||
|
|
||||||
|
== To download
|
||||||
|
|
||||||
|
1. Configure your e-mail and name for Git
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ git config \--global user.email "you@example.com"
|
||||||
|
$ git config \--global user.name "Your Name"
|
||||||
|
------
|
||||||
|
|
||||||
|
2. Install Repo
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ mkdir -p ~/bin
|
||||||
|
$ wget -O - https://github.com/android/tools_repo/raw/master/repo > ~/bin/repo
|
||||||
|
$ perl -i.bak -pe 's!git://android.git.kernel.org/tools/repo.git!git://github.com/android/tools_repo.git!;' ~/bin/repo
|
||||||
|
$ chmod a+x ~/bin/repo
|
||||||
|
------
|
||||||
|
+
|
||||||
|
CAUTION: Since access to kernel.org has been shutdown due to hackers,
|
||||||
|
fetch and replace repo tool with android\'s GitHub repository mirror.
|
||||||
|
|
||||||
|
3. Create working directory
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ mkdir working-directory-name
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ repo init -u git://github.com/norton/manifests.git -m lets-default.xml
|
||||||
|
------
|
||||||
|
+
|
||||||
|
NOTE: Your "Git" identity is needed during the init step. Please
|
||||||
|
enter the name and email of your GitHub account if you have one. Team
|
||||||
|
members having read-write access are recommended to use "repo init -u
|
||||||
|
git@github.com:norton/manifests.git -m lets-default-rw.xml".
|
||||||
|
+
|
||||||
|
TIP: If you want to checkout the latest development version, please
|
||||||
|
append " -b dev" to the repo init command.
|
||||||
|
|
||||||
|
4. Download Git repositories
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name
|
||||||
|
$ repo sync
|
||||||
|
------
|
||||||
|
|
||||||
|
For futher information and help for related tools, please refer to the
|
||||||
|
following links:
|
||||||
|
|
||||||
|
- Erlang - http://www.erlang.org/
|
||||||
|
* *R14B03 or newer, R14B04 has been tested most recently*
|
||||||
|
- Git - http://git-scm.com/
|
||||||
|
* *Git 1.5.4 or newer, Git 1.7.7 has been tested recently*
|
||||||
|
* _required for Repo and GitHub_
|
||||||
|
- GitHub - https://github.com
|
||||||
|
- Python - http://www.python.org
|
||||||
|
* *Python 2.4 or newer, Python 2.7.1 has been tested most recently
|
||||||
|
(CAUTION: Python 3.x might be too new)*
|
||||||
|
* _required for Repo_
|
||||||
|
- Rebar - https://github.com/basho/rebar/wiki
|
||||||
|
- Repo - http://source.android.com/source/git-repo.html
|
||||||
|
|
||||||
|
== To build - basic recipe
|
||||||
|
|
||||||
|
1. Get and install an erlang system http://www.erlang.org
|
||||||
|
|
||||||
|
2. Build
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make compile
|
||||||
|
------
|
||||||
|
|
||||||
|
== To build - optional features
|
||||||
|
|
||||||
|
A. Dialyzer Testing _basic recipe_
|
||||||
|
1. Build Dialyzer\'s PLT _(required once)_
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make build-plt
|
||||||
|
------
|
||||||
|
+
|
||||||
|
TIP: Check Makefile and dialyzer\'s documentation for further
|
||||||
|
information.
|
||||||
|
|
||||||
|
2. Dialyze with specs
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make dialyze
|
||||||
|
------
|
||||||
|
+
|
||||||
|
CAUTION: If you manually run dialyzer with the "-r" option, execute
|
||||||
|
"make clean compile" first to avoid finding duplicate beam files
|
||||||
|
underneath rebar\'s .eunit directory. Check Makefile for further
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. Dialyze without specs
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make dialyze-nospec
|
||||||
|
------
|
||||||
|
|
||||||
|
== To test - QuickCheck
|
||||||
|
|
||||||
|
1. Make sure QuickCheck is in your Erlang code path. One simple way
|
||||||
|
to accomplish this is by adding the code path to your +~/.erlang+
|
||||||
|
resource file.
|
||||||
|
+
|
||||||
|
------
|
||||||
|
true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/quviq/eqc-X.Y.Z/ebin").
|
||||||
|
------
|
||||||
|
|
||||||
|
2. Compile for QuickCheck
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-eqc eqc-compile
|
||||||
|
------
|
||||||
|
|
||||||
|
3. Run 5,000 QuickCheck tests
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......
|
||||||
|
------
|
||||||
|
+
|
||||||
|
TIP: For testing LevelDB directly using the C bindings, try
|
||||||
|
+qc_statemc_lets:run(5000)+.
|
||||||
|
|
||||||
|
== To test - Proper
|
||||||
|
|
||||||
|
1. Make sure Proper is in your Erlang code path. One simple way to
|
||||||
|
accomplish this is by adding the code path to your +~/.erlang+
|
||||||
|
resource file.
|
||||||
|
+
|
||||||
|
------
|
||||||
|
true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/proper/ebin").
|
||||||
|
------
|
||||||
|
|
||||||
|
2. Compile for Proper
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src
|
||||||
|
$ make clean
|
||||||
|
$ make compile-proper proper-compile
|
||||||
|
------
|
||||||
|
|
||||||
|
3. Run 5,000 Proper tests
|
||||||
|
+
|
||||||
|
------
|
||||||
|
$ cd working-directory-name/src/lib/lets/.eunit
|
||||||
|
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
|
||||||
|
|
||||||
|
1> qc_statem_lets:run(5000).
|
||||||
|
.......
|
||||||
|
------
|
||||||
|
|
||||||
|
== Roadmap
|
||||||
|
|
||||||
|
- Documentation
|
||||||
|
* Explain how to run QuickCheck/Proper tests using a new rebar
|
||||||
|
plugin.
|
||||||
|
* Explain how to build and to run lets with valgrind enabled
|
||||||
|
OTP/Erlang virtual machine
|
||||||
|
|
||||||
|
- Bugs
|
||||||
|
* LevelDB - Reappearing "ghost" key after 17 steps
|
||||||
|
(http://code.google.com/p/leveldb/issues/detail?id=44)
|
||||||
|
+
|
||||||
|
NOTE: LET\'s QC tests are hard-coded not to close and then to
|
||||||
|
reopen a LevelDB database until this bug has been fixed.
|
||||||
|
|
||||||
|
- Performance
|
||||||
|
* Update driver implementation to use Erlang\'s asynchronous driver
|
||||||
|
thread pool for all LevelDB operations.
|
||||||
|
|
||||||
|
- Testing
|
||||||
|
* Functional
|
||||||
|
** Update test model to include LevelDB\'s database, read, and
|
||||||
|
write options. These options have not undergone any explicit
|
||||||
|
testing.
|
||||||
|
* Performance (TBD)
|
||||||
|
* Stability (TBD)
|
||||||
|
|
||||||
|
- New APIs (TBD)
|
||||||
|
* +insert_new/2+
|
||||||
|
(http://code.google.com/p/leveldb/issues/detail?id=42)
|
||||||
|
|
||||||
|
* +delete_all_objects/1+
|
||||||
|
(http://code.google.com/p/leveldb/issues/detail?id=43)
|
||||||
|
|
||||||
|
- Existing APIs (TBD)
|
||||||
|
* +new/2+ - automatically detect and prevent multiple callers of the
|
||||||
|
same Erlang virtual machine from simultaneously opening the same
|
||||||
|
LevelDB. Excerpt from the Google leveldb mailing list:
|
||||||
|
+
|
||||||
|
------
|
||||||
|
Sanjay Ghemawat
|
||||||
|
View profile
|
||||||
|
More options Sep 30, 1:04 am
|
||||||
|
On Thu, Sep 29, 2011 at 8:30 AM, Joseph Wayne Norton wrote:
|
||||||
|
> Hans -
|
||||||
|
> Thanks. Is is correct to assume that it is the caller's responsibility to
|
||||||
|
> ensure this does not happen?
|
||||||
|
|
||||||
|
leveldb guarantees that it will catch when two distinct processes
|
||||||
|
try to open the db concurrently. However it doesn't guarantee what happens
|
||||||
|
if the same process tries to do so and therefore it is the caller's
|
||||||
|
responsibility
|
||||||
|
to check for concurrent opens from the same process.
|
||||||
|
This is ugly, but the unix file locking primitives are very annoying in
|
||||||
|
this regard. I'll think about whether or not we should clean up the spec
|
||||||
|
by doing extra checks inside the leveldb implementation.
|
||||||
|
------
|
||||||
|
|
||||||
|
* +new/2+ - investigate if LevelDB\'s snapshot feature is useful (or
|
||||||
|
not) for LETS
|
||||||
|
|
||||||
|
* +info/2+ - investigate if LevelDB\'s implementation can (easily)
|
||||||
|
support size and memory info items
|
||||||
|
|
||||||
|
* consider adding explicit read_options and write_options for LET\'s
|
||||||
|
operations (rather than just +new/2+, +destroy/2+, and +repair/2+
|
||||||
|
operations).
|
||||||
|
|
||||||
|
@end
|
72
rebar.config
Normal file
72
rebar.config
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
%%% -*- mode: erlang -*-
|
||||||
|
|
||||||
|
%% Require OTP version R13B04 or R14
|
||||||
|
{require_otp_vsn, "R13B04|R14"}.
|
||||||
|
|
||||||
|
%% Depends
|
||||||
|
{deps_dir, "../"}.
|
||||||
|
{deps, [{meck, "", {git, "git://github.com/norton/meck.git"}}
|
||||||
|
, {edown, "", {git, "git://github.com/norton/edown.git"}}
|
||||||
|
, {asciiedoc, "", {git, "git://github.com/norton/asciiedoc.git"}}
|
||||||
|
, {qc, "", {git, "git://github.com/norton/qc.git"}}
|
||||||
|
, {sext, "", {git, "git://github.com/norton/sext.git"}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
%% Erlang compiler options
|
||||||
|
{erl_opts, [debug_info, warnings_as_errors
|
||||||
|
]}.
|
||||||
|
%% Erlang edoc options for asciiedown_doclet
|
||||||
|
{edoc_opts, [{doclet, asciiedown_doclet}
|
||||||
|
, {app_default, "http://www.erlang.org/doc/man"}
|
||||||
|
, {new, true}
|
||||||
|
, {packages, false}
|
||||||
|
, {stylesheet, ""} % don't copy stylesheet.css
|
||||||
|
, {image, ""} % don't copy erlang.png
|
||||||
|
, {top_level_readme, {"./README.md", "https://github.com/norton/lets"}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
%% Erlang edoc options for asciiedoc_doclet
|
||||||
|
%% {edoc_opts, [{doclet, asciiedoc_doclet}
|
||||||
|
%% , {app_default, "http://www.erlang.org/doc/man"}
|
||||||
|
%% , {new, true}
|
||||||
|
%% , {packages, false}
|
||||||
|
%% ]}.
|
||||||
|
|
||||||
|
%% Eunit compiler options
|
||||||
|
{eunit_compile_opts, [
|
||||||
|
%% {d, 'QC', true}
|
||||||
|
%% For Proper, comment QC_EQC, uncomment
|
||||||
|
%% QC_PROPER, and re-compile
|
||||||
|
%% , {d, 'QC_PROPER', true}
|
||||||
|
%% For QuickCheck, comment QC_PROPER, uncomment
|
||||||
|
%% QC_EQC, and re-compile
|
||||||
|
%% , {d, 'QC_EQC', true}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
%% EUnit options
|
||||||
|
{eunit_opts, [verbose]}.
|
||||||
|
|
||||||
|
%% Port compilation environment variables.
|
||||||
|
{port_envs, [
|
||||||
|
%% DRV_CFLAGS
|
||||||
|
{"DRV_CFLAGS", "$DRV_CFLAGS -fPIC -g -Werror"},
|
||||||
|
{"DRV_CFLAGS", "$DRV_CFLAGS -I c_src/leveldb/include"},
|
||||||
|
%% DRV_LDFLAGS
|
||||||
|
{"DRV_LDFLAGS", "$DRV_LDFLAGS c_src/leveldb/lib/libleveldb.a c_src/snappy/lib/libsnappy.a -lstdc++"}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
%% Pre command hooks
|
||||||
|
{pre_hooks, [{clean, "c_src/build_deps.sh clean"},
|
||||||
|
{compile, "c_src/build_deps.sh c_src/leveldb/lib/libleveldb.a"}]}.
|
||||||
|
|
||||||
|
%% port_sources
|
||||||
|
{port_sources, ["c_src/lets_drv_lib.cc"
|
||||||
|
, "c_src/lets_drv.cc"
|
||||||
|
, "c_src/lets_nif_lib.cc"
|
||||||
|
, "c_src/lets_nif.cc"
|
||||||
|
]}.
|
||||||
|
|
||||||
|
%% so_specs
|
||||||
|
{so_specs, [{"priv/lib/lets_drv.so", ["c_src/lets_drv_lib.o", "c_src/lets_drv.o"]}
|
||||||
|
, {"priv/lib/lets_nif.so", ["c_src/lets_nif_lib.o", "c_src/lets_nif.o"]}
|
||||||
|
]}.
|
33
src/lets.app.src
Normal file
33
src/lets.app.src
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
%%% -*- mode: erlang -*-
|
||||||
|
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
{application, lets,
|
||||||
|
[
|
||||||
|
{description, "LevelDB ETS"},
|
||||||
|
{vsn, git},
|
||||||
|
{registered, []},
|
||||||
|
{applications, [kernel, stdlib, sasl]},
|
||||||
|
{modules, [lets, lets_drv, lets_ets, lets_nif]}
|
||||||
|
]
|
||||||
|
}.
|
510
src/lets.erl
Normal file
510
src/lets.erl
Normal file
|
@ -0,0 +1,510 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(lets).
|
||||||
|
|
||||||
|
-include("lets.hrl").
|
||||||
|
|
||||||
|
%% External exports
|
||||||
|
-export([new/2
|
||||||
|
, destroy/2
|
||||||
|
, repair/2
|
||||||
|
, insert/2
|
||||||
|
, insert_new/2
|
||||||
|
, delete/1
|
||||||
|
, delete/2
|
||||||
|
, delete_all_objects/1
|
||||||
|
, lookup/2
|
||||||
|
, first/1
|
||||||
|
, next/2
|
||||||
|
, info/2
|
||||||
|
, tab2list/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export_type([tab/0]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Types/Specs/Records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-opaque tab() :: #tab{}.
|
||||||
|
|
||||||
|
-type opts() :: [ets_opt() | impl_opt() | db_opts() | db_read_opts() | db_write_opts()].
|
||||||
|
-type ets_opt() :: set | ordered_set | named_table | {key_pos,pos_integer()} | public | protected | private | compressed.
|
||||||
|
-type impl_opt() :: drv | nif | ets.
|
||||||
|
|
||||||
|
-type db_opts() :: {db, [{path,file:filename()} | create_if_missing | {create_if_missing,boolean()} | error_if_exists | {error_if_exists,boolean()} | paranoid_checks | {paranoid_checks,boolean()} | {write_buffer_size,pos_integer()} | {max_open_files,pos_integer()} | {block_cache_size,pos_integer()} | {block_size,pos_integer()} | {block_restart_interval,pos_integer()}]}.
|
||||||
|
-type db_read_opts() :: {db_read, [verify_checksums | {verify_checksums,boolean()} | fill_cache | {fill_cache,boolean()}]}.
|
||||||
|
-type db_write_opts() :: {db_write, [sync | {sync,boolean()}]}.
|
||||||
|
|
||||||
|
-type key() :: binary().
|
||||||
|
-type object() :: term().
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Creates a new table and returns a table identifier which can
|
||||||
|
%% be used in subsequent operations. The table identifier can be sent
|
||||||
|
%% to other processes so that a table can be shared between different
|
||||||
|
%% processes within a node.
|
||||||
|
%%
|
||||||
|
%% Valid LETS properties for +Options+ are:
|
||||||
|
%%
|
||||||
|
%% - +set+ The table is a set table - one key, one object, no order
|
||||||
|
%% among objects. This is the default table type.
|
||||||
|
%%
|
||||||
|
%% - +ordered_set+ The table is an ordered_set table - one key, one
|
||||||
|
%% object, ordered in Erlang term order, which is the order implied
|
||||||
|
%% by the +<+ and +>+ operators.
|
||||||
|
%%
|
||||||
|
%% - +named_table+ If this option is present, the name +Name+ is
|
||||||
|
%% associated with the table identifier. _only the ets
|
||||||
|
%% implementation_
|
||||||
|
%%
|
||||||
|
%% - +{key_pos,pos_integer()}+ Specfies which element in the stored
|
||||||
|
%% tuples should be used as key. By default, it is the first
|
||||||
|
%% element, i.e. +Pos=1+.
|
||||||
|
%%
|
||||||
|
%% - +public+ Any process may read or write to the table.
|
||||||
|
%%
|
||||||
|
%% - +protected+ The owner process can read and write to the table.
|
||||||
|
%% Other processes can only read the table. This is the default
|
||||||
|
%% setting for the access rights.
|
||||||
|
%%
|
||||||
|
%% - +private+ Only the owner process can read or write to the table.
|
||||||
|
%%
|
||||||
|
%% - +compressed+ If this option is present, the table data will be
|
||||||
|
%% stored in a compressed format.
|
||||||
|
%%
|
||||||
|
%% - +drv+ If this option is present, the table data will be stored
|
||||||
|
%% with LevelDB backend via an Erlang Driver. This is the default
|
||||||
|
%% setting for the table implementation.
|
||||||
|
%%
|
||||||
|
%% - +nif+ If this option is present, the table data will be stored
|
||||||
|
%% with LevelDB backend via an Erlang NIF.
|
||||||
|
%%
|
||||||
|
%% - +ets+ If this option is present, the table data will be stored
|
||||||
|
%% with ETS as the backend.
|
||||||
|
%%
|
||||||
|
%% - +{db, [db_opts()]}+ LevelDB database options.
|
||||||
|
%%
|
||||||
|
%% - +{db_read, [db_read_opts()]}+ LevelDB read options.
|
||||||
|
%%
|
||||||
|
%% - +{db_write, [db_write_opts()]}+ LevelDB write options.
|
||||||
|
%%
|
||||||
|
%% Valid LevelDB database properties for +db_opts()+ are:
|
||||||
|
%%
|
||||||
|
%% - +{path, file:filename()}+ Open the database with the specified
|
||||||
|
%% path. The default is +Name+.
|
||||||
|
%%
|
||||||
|
%% - +create_if_missing | {create_if_missing, boolean()}+ If +true+,
|
||||||
|
%% the database will be created if it is missing. The default is
|
||||||
|
%% +false+.
|
||||||
|
%%
|
||||||
|
%% - +error_if_exists | {error_if_exists, boolean()}+ If +true+, an
|
||||||
|
%% error is raised if the database already exists. The default is
|
||||||
|
%% +false+.
|
||||||
|
%%
|
||||||
|
%% - +paranoid_checks | {paranoid_checks, boolean()}+ If +true+, the
|
||||||
|
%% implementation will do aggressive checking of the data it is
|
||||||
|
%% processing and will stop early if it detects any errors. The
|
||||||
|
%% default is +false+.
|
||||||
|
%%
|
||||||
|
%% - +{write_buffer_size, pos_integer()}+ The default is 4MB.
|
||||||
|
%%
|
||||||
|
%% - +{max_open_files, pos_integer()}+ The default is 1000.
|
||||||
|
%%
|
||||||
|
%% - +{block_cache_size, pos_integer()}+ The default is 8MB.
|
||||||
|
%%
|
||||||
|
%% - +{block_size, pos_integer()}+ The default is 4K.
|
||||||
|
%%
|
||||||
|
%% - +{block_restart_interval, pos_integer()}+ The default is 16.
|
||||||
|
%%
|
||||||
|
%% Valid LevelDB read properties for +db_read_opts()+ are:
|
||||||
|
%%
|
||||||
|
%% - +verify_checksums | {verify_checksums, boolean()}+ If +true+, all
|
||||||
|
%% data read from underlying storage will be verified against
|
||||||
|
%% corresponding checksums. The default is +false+.
|
||||||
|
%%
|
||||||
|
%% - +fill_cache | {fill_cache, boolean()}+ If +true+, the data read
|
||||||
|
%% should be cached in memory. The default is +true+.
|
||||||
|
%%
|
||||||
|
%% Valid LevelDB write properties for +db_write_opts()+ are:
|
||||||
|
%%
|
||||||
|
%% - +sync | {sync, boolean()}+ If +true+, the write will be flushed
|
||||||
|
%% from the operating system buffer cache before the write is
|
||||||
|
%% considered complete. The default is +false+.
|
||||||
|
%%
|
||||||
|
%% @end
|
||||||
|
%% @see ets:new/2
|
||||||
|
|
||||||
|
-spec new(Name::atom(), Options::opts()) -> tab().
|
||||||
|
new(Name, Opts) ->
|
||||||
|
create(open, Name, Opts).
|
||||||
|
|
||||||
|
%% @doc Destroy the contents of the specified table. This function
|
||||||
|
%% only applies to +driver+ and +nif+ implementations.
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
-spec destroy(Name::atom(), Options::opts()) -> true.
|
||||||
|
destroy(Name, Opts) ->
|
||||||
|
create(destroy, Name, Opts).
|
||||||
|
|
||||||
|
|
||||||
|
%% @doc If a table cannot be opened, you may attempt to call this
|
||||||
|
%% method to resurrect as much of the contents of the table as
|
||||||
|
%% possible. Some data may be lost, so be careful when calling this
|
||||||
|
%% function on a table that contains important information. This
|
||||||
|
%% function only applies to +driver+ and +nif+ implementations.
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
-spec repair(Name::atom(), Options::opts()) -> true.
|
||||||
|
repair(Name, Opts) ->
|
||||||
|
create(repair, Name, Opts).
|
||||||
|
|
||||||
|
%% @doc Inserts the object or all of the objects in the list
|
||||||
|
%% +ObjectOrObjects+ into the table +Tab+.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:insert/2
|
||||||
|
|
||||||
|
-spec insert(Tab::tab(), ObjectOrObjects::object() | [object()]) -> true.
|
||||||
|
insert(Tab, ObjectOrObjects) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:insert(Tab, Impl, ObjectOrObjects)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc This function works exactly like +insert/2+, with the
|
||||||
|
%% exception that instead of overwriting objects with the same key, it
|
||||||
|
%% simply returns false. This function only applies to the +ets+
|
||||||
|
%% implementation.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:insert_new/2
|
||||||
|
|
||||||
|
-spec insert_new(tab(), object() | [object()]) -> true.
|
||||||
|
insert_new(Tab, ObjectOrObjects) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:insert_new(Tab, Impl, ObjectOrObjects)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Deletes the entire table +Tab+.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:delete/1
|
||||||
|
|
||||||
|
-spec delete(tab()) -> true.
|
||||||
|
delete(Tab) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:delete(Tab, Impl)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Deletes all objects with the key +Key+ from the table +Tab+.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:delete/2
|
||||||
|
|
||||||
|
-spec delete(tab(), key()) -> true.
|
||||||
|
delete(Tab, Key) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:delete(Tab, Impl, Key)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Delete all objects in the table +Tab+. The operation is
|
||||||
|
%% guaranteed to be atomic and isolated. This function only applies
|
||||||
|
%% to the +ets+ implementation.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:delete_all_objects/1
|
||||||
|
|
||||||
|
-spec delete_all_objects(tab()) -> true.
|
||||||
|
delete_all_objects(Tab) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:delete_all_objects(Tab, Impl)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Returns a list of all objects with the key +Key+ in the table
|
||||||
|
%% +Tab+.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:lookup/2
|
||||||
|
|
||||||
|
-spec lookup(tab(), key()) -> [object()].
|
||||||
|
lookup(Tab, Key) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:lookup(Tab, Impl, Key)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Returns the first key +Key+ in the table +Tab+. If the table
|
||||||
|
%% is empty, +'$end_of_table'+ will be returned.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:first/1
|
||||||
|
|
||||||
|
-spec first(tab()) -> key() | '$end_of_table'.
|
||||||
|
first(Tab) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:first(Tab, Impl)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Returns the next key +Key2+, following the key +Key1+ in the
|
||||||
|
%% table +Tab+. If there is no next key, +'$end_of_table'+ is
|
||||||
|
%% returned.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:next/2
|
||||||
|
|
||||||
|
-spec next(tab(), key()) -> key() | '$end_of_table'.
|
||||||
|
next(Tab, Key) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:next(Tab, Impl, Key)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Returns information about the table +Tab+ as a list of +{Item,
|
||||||
|
%% Value}+ tuples.
|
||||||
|
%%
|
||||||
|
%% Valid +Item+ options are:
|
||||||
|
%%
|
||||||
|
%% - +owner+
|
||||||
|
%% - +name+
|
||||||
|
%% - +named_table+ _only the ets implementation_
|
||||||
|
%% - +type+
|
||||||
|
%% - +keypos+
|
||||||
|
%% - +protection+
|
||||||
|
%% - +compressed+
|
||||||
|
%% - +memory+ _only the ets implementation_
|
||||||
|
%% - +size+ _only the ets implementation_
|
||||||
|
%%
|
||||||
|
%% @end
|
||||||
|
%% @see ets:info/2
|
||||||
|
|
||||||
|
-spec info(tab(), atom()) -> term().
|
||||||
|
info(Tab, Item) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
case Item of
|
||||||
|
owner ->
|
||||||
|
Tab#tab.owner;
|
||||||
|
name ->
|
||||||
|
Tab#tab.name;
|
||||||
|
named_table ->
|
||||||
|
Tab#tab.named_table;
|
||||||
|
type ->
|
||||||
|
Tab#tab.type;
|
||||||
|
keypos ->
|
||||||
|
Tab#tab.keypos;
|
||||||
|
protection ->
|
||||||
|
Tab#tab.protection;
|
||||||
|
compressed ->
|
||||||
|
Tab#tab.compressed;
|
||||||
|
memory ->
|
||||||
|
Mod:info_memory(Tab, Impl);
|
||||||
|
size ->
|
||||||
|
Mod:info_size(Tab, Impl);
|
||||||
|
_ ->
|
||||||
|
erlang:error(badarg, [Tab, Item])
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Returns a list of all objects in the table +Tab+. The
|
||||||
|
%% operation is *not* guaranteed to be atomic and isolated.
|
||||||
|
%% @end
|
||||||
|
%% @see ets:tab2list/1
|
||||||
|
|
||||||
|
-spec tab2list(tab()) -> [object()].
|
||||||
|
tab2list(Tab) ->
|
||||||
|
case check_access(Tab) of
|
||||||
|
undefined ->
|
||||||
|
erlang:error(badarg, [Tab]);
|
||||||
|
{Mod, Impl} ->
|
||||||
|
Mod:tab2list(Tab, Impl)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal functions
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
check_access(#tab{protection=Protection, owner=Owner, drv=Drv, nif=undefined, ets=undefined})
|
||||||
|
when Protection==public orelse Owner==self() ->
|
||||||
|
{lets_drv, Drv};
|
||||||
|
check_access(#tab{protection=Protection, owner=Owner, drv=undefined, nif=Nif, ets=undefined})
|
||||||
|
when Protection==public orelse Owner==self() ->
|
||||||
|
{lets_nif, Nif};
|
||||||
|
check_access(#tab{protection=Protection, owner=Owner, drv=undefined, nif=undefined, ets=Ets})
|
||||||
|
when Protection==public orelse Owner==self() ->
|
||||||
|
{lets_ets, Ets};
|
||||||
|
check_access(_Tab) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
create(Op, Name, Opts) ->
|
||||||
|
case options(Opts) of
|
||||||
|
{POpts, []} ->
|
||||||
|
POpts;
|
||||||
|
{POpts, BadArgs} ->
|
||||||
|
erlang:error(badarg, [Name, BadArgs])
|
||||||
|
end,
|
||||||
|
|
||||||
|
Owner = self(),
|
||||||
|
NamedTable = proplists:get_bool(named_table, POpts),
|
||||||
|
Type =
|
||||||
|
case proplists:get_bool(ordered_set, POpts) of
|
||||||
|
true ->
|
||||||
|
ordered_set;
|
||||||
|
false ->
|
||||||
|
set
|
||||||
|
end,
|
||||||
|
KeyPos = proplists:get_value(keypos, POpts, 1),
|
||||||
|
Protection =
|
||||||
|
case proplists:get_bool(private, POpts) of
|
||||||
|
true ->
|
||||||
|
private;
|
||||||
|
false ->
|
||||||
|
case proplists:get_bool(protected, POpts) of
|
||||||
|
true ->
|
||||||
|
protected;
|
||||||
|
false ->
|
||||||
|
case proplists:get_bool(public, POpts) of
|
||||||
|
true ->
|
||||||
|
public;
|
||||||
|
false ->
|
||||||
|
protected
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Compressed = proplists:get_bool(compressed, POpts),
|
||||||
|
Drv = proplists:get_bool(drv, POpts),
|
||||||
|
Nif = proplists:get_bool(nif, POpts),
|
||||||
|
Ets = proplists:get_bool(ets, POpts),
|
||||||
|
|
||||||
|
Tab = #tab{owner=Owner,
|
||||||
|
name=Name,
|
||||||
|
named_table=NamedTable,
|
||||||
|
type=Type,
|
||||||
|
keypos=KeyPos,
|
||||||
|
protection=Protection,
|
||||||
|
compressed=Compressed},
|
||||||
|
|
||||||
|
DBOptions = fix_db_options(Tab, proplists:get_value(db, POpts, [])),
|
||||||
|
DBReadOptions = proplists:get_value(db_read, POpts, []),
|
||||||
|
DBWriteOptions = proplists:get_value(db_write, POpts, []),
|
||||||
|
|
||||||
|
if Drv ->
|
||||||
|
lets_drv:Op(Tab, DBOptions, DBReadOptions, DBWriteOptions);
|
||||||
|
Nif ->
|
||||||
|
lets_nif:Op(Tab, DBOptions, DBReadOptions, DBWriteOptions);
|
||||||
|
Ets ->
|
||||||
|
lets_ets:Op(Tab);
|
||||||
|
true ->
|
||||||
|
lets_drv:Op(Tab, DBOptions, DBReadOptions, DBWriteOptions)
|
||||||
|
end.
|
||||||
|
|
||||||
|
fix_db_options(#tab{name=Name, compressed=Compressed}, Options) ->
|
||||||
|
fix_db_options_compression(Compressed, fix_db_options_path(Name, Options)).
|
||||||
|
|
||||||
|
fix_db_options_path(Name, Options) ->
|
||||||
|
case proplists:lookup(path, Options) of
|
||||||
|
none ->
|
||||||
|
[{path, binify(Name)}|Options];
|
||||||
|
{path, Path} ->
|
||||||
|
[{path, binify(Path)}|proplists:delete(path, Options)]
|
||||||
|
end.
|
||||||
|
|
||||||
|
fix_db_options_compression(false, Options) ->
|
||||||
|
[{compression, no}|Options];
|
||||||
|
fix_db_options_compression(true, Options) ->
|
||||||
|
[{compression, snappy}|Options].
|
||||||
|
|
||||||
|
binify(X) when is_atom(X) ->
|
||||||
|
list_to_binary(atom_to_list(X));
|
||||||
|
binify(X) when is_list(X) ->
|
||||||
|
list_to_binary(X);
|
||||||
|
binify(X) when is_binary(X) ->
|
||||||
|
X.
|
||||||
|
|
||||||
|
options(Options) ->
|
||||||
|
Keys = [set, ordered_set, named_table, keypos, public, protected, private, compressed, drv, nif, ets, db, db_read, db_write],
|
||||||
|
options(Options, Keys).
|
||||||
|
|
||||||
|
options(Options, Keys) when is_list(Options) ->
|
||||||
|
options(Options, Keys, []);
|
||||||
|
options(Option, Keys) ->
|
||||||
|
options([Option], Keys, []).
|
||||||
|
|
||||||
|
options(Options, [Key|Keys], L) when is_list(Options) ->
|
||||||
|
case proplists:lookup(Key, Options) of
|
||||||
|
none ->
|
||||||
|
options(Options, Keys, L);
|
||||||
|
{Key, Value} ->
|
||||||
|
case Key of
|
||||||
|
Key when Key == db; Key == db_read; Key == db_write ->
|
||||||
|
sub_options(Key, Value, Options, Keys, L);
|
||||||
|
_ ->
|
||||||
|
NewOptions = proplists:delete(Key, Options),
|
||||||
|
options(NewOptions, Keys, [{Key,Value}|L])
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
options(Options, [], L) ->
|
||||||
|
{lists:reverse(L), Options}.
|
||||||
|
|
||||||
|
sub_options(db=Key, Value, Options, Keys, L) ->
|
||||||
|
SubKeys = [path, create_if_missing, error_if_exists, paranoid_checks, write_buffer_size, max_open_files, block_cache_size, block_size, block_restart_interval],
|
||||||
|
sub_options(Key, Value, Options, Keys, L, SubKeys);
|
||||||
|
sub_options(db_read=Key, Value, Options, Keys, L) ->
|
||||||
|
%% @TODO snapshot
|
||||||
|
SubKeys = [verify_checksums, fill_cache],
|
||||||
|
sub_options(Key, Value, Options, Keys, L, SubKeys);
|
||||||
|
sub_options(db_write=Key, Value, Options, Keys, L) ->
|
||||||
|
%% @TODO snapshot
|
||||||
|
SubKeys = [sync],
|
||||||
|
sub_options(Key, Value, Options, Keys, L, SubKeys).
|
||||||
|
|
||||||
|
sub_options(Key, Value, Options, Keys, L, SubKeys) ->
|
||||||
|
case options(Value, SubKeys) of
|
||||||
|
{NewValue, []} ->
|
||||||
|
NewOptions = proplists:delete(Key, Options),
|
||||||
|
options(NewOptions, Keys, [{Key,NewValue}|L]);
|
||||||
|
{_NewValue, _} ->
|
||||||
|
options(Options, Keys, L)
|
||||||
|
end.
|
40
src/lets.hrl
Normal file
40
src/lets.hrl
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-ifndef(lets).
|
||||||
|
-define(lets, true).
|
||||||
|
|
||||||
|
-opaque nif() :: term().
|
||||||
|
|
||||||
|
-record(tab, {owner :: pid(),
|
||||||
|
name :: atom(),
|
||||||
|
named_table=false :: boolean(),
|
||||||
|
type=set :: set|ordered_set,
|
||||||
|
keypos=1 :: pos_integer(),
|
||||||
|
protection=protected :: public|protected|private,
|
||||||
|
compressed=false :: boolean(),
|
||||||
|
drv :: port() | undefined,
|
||||||
|
nif :: nif() | undefined,
|
||||||
|
ets :: ets:tab() | undefined
|
||||||
|
}).
|
||||||
|
|
||||||
|
-endif. % -ifndef(lets).
|
278
src/lets_drv.erl
Normal file
278
src/lets_drv.erl
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(lets_drv).
|
||||||
|
|
||||||
|
-include("lets.hrl").
|
||||||
|
|
||||||
|
%% External exports
|
||||||
|
-export([open/4
|
||||||
|
, destroy/4
|
||||||
|
, repair/4
|
||||||
|
, insert/3
|
||||||
|
, insert_new/3
|
||||||
|
, delete/2
|
||||||
|
, delete/3
|
||||||
|
, delete_all_objects/2
|
||||||
|
, lookup/3
|
||||||
|
, first/2
|
||||||
|
, next/3
|
||||||
|
, info_memory/2
|
||||||
|
, info_size/2
|
||||||
|
, tab2list/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Types/Specs/Records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(LETS_BADARG, 16#00).
|
||||||
|
-define(LETS_TRUE, 16#01).
|
||||||
|
-define(LETS_END_OF_TABLE, 16#02).
|
||||||
|
|
||||||
|
-define(LETS_OPEN6, 16#00).
|
||||||
|
-define(LETS_DESTROY6, 16#01).
|
||||||
|
-define(LETS_REPAIR6, 16#02).
|
||||||
|
-define(LETS_INSERT2, 16#03).
|
||||||
|
-define(LETS_INSERT3, 16#04).
|
||||||
|
-define(LETS_INSERT_NEW2, 16#05).
|
||||||
|
-define(LETS_INSERT_NEW3, 16#06).
|
||||||
|
-define(LETS_DELETE1, 16#07).
|
||||||
|
-define(LETS_DELETE2, 16#08).
|
||||||
|
-define(LETS_DELETE_ALL_OBJECTS1, 16#09).
|
||||||
|
-define(LETS_LOOKUP2, 16#0A).
|
||||||
|
-define(LETS_FIRST1, 16#0B).
|
||||||
|
-define(LETS_NEXT2, 16#0C).
|
||||||
|
-define(LETS_INFO_MEMORY1, 16#0D).
|
||||||
|
-define(LETS_INFO_SIZE1, 16#0E).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
init() ->
|
||||||
|
Path =
|
||||||
|
case code:priv_dir(lets) of
|
||||||
|
{error, bad_name} ->
|
||||||
|
"../priv/lib";
|
||||||
|
Dir ->
|
||||||
|
filename:join([Dir, "lib"])
|
||||||
|
end,
|
||||||
|
case erl_ddll:load_driver(Path, lets_drv) of
|
||||||
|
ok -> ok;
|
||||||
|
{error, already_loaded} -> ok;
|
||||||
|
{error, permanent} -> ok;
|
||||||
|
{error, {open_error, _}=Err} ->
|
||||||
|
FormattedErr = erl_ddll:format_error(Err),
|
||||||
|
error_logger:error_msg("Failed to load the driver library lets_drv. "
|
||||||
|
++ "Error: ~p, Path: ~p~n",
|
||||||
|
[FormattedErr,
|
||||||
|
filename:join(Path, lets_drv)
|
||||||
|
]),
|
||||||
|
erlang:exit({Err, FormattedErr})
|
||||||
|
end,
|
||||||
|
open_port({spawn, "lets_drv"}, [binary]).
|
||||||
|
|
||||||
|
open(#tab{name=_Name, named_table=_Named, type=Type, protection=Protection}=Tab, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
Drv = impl_open(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions),
|
||||||
|
%% @TODO implement named Drv (of sorts)
|
||||||
|
Tab#tab{drv=Drv}.
|
||||||
|
|
||||||
|
destroy(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
impl_destroy(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
|
||||||
|
|
||||||
|
repair(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
impl_repair(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
|
||||||
|
|
||||||
|
insert(#tab{keypos=KeyPos, type=Type}, Drv, Object) when is_tuple(Object) ->
|
||||||
|
Key = element(KeyPos,Object),
|
||||||
|
Val = Object,
|
||||||
|
impl_insert(Drv, encode(Type, Key), encode(Type, Val));
|
||||||
|
insert(#tab{keypos=KeyPos, type=Type}, Drv, Objects) when is_list(Objects) ->
|
||||||
|
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
|
||||||
|
impl_insert(Drv, List).
|
||||||
|
|
||||||
|
insert_new(#tab{keypos=KeyPos, type=Type}, Drv, Object) when is_tuple(Object) ->
|
||||||
|
Key = element(KeyPos,Object),
|
||||||
|
Val = Object,
|
||||||
|
impl_insert_new(Drv, encode(Type, Key), encode(Type, Val));
|
||||||
|
insert_new(#tab{keypos=KeyPos, type=Type}, Drv, Objects) when is_list(Objects) ->
|
||||||
|
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
|
||||||
|
impl_insert_new(Drv, List).
|
||||||
|
|
||||||
|
delete(_Tab, Drv) ->
|
||||||
|
impl_delete(Drv).
|
||||||
|
|
||||||
|
delete(#tab{type=Type}, Drv, Key) ->
|
||||||
|
impl_delete(Drv, encode(Type, Key)).
|
||||||
|
|
||||||
|
delete_all_objects(_Tab, Drv) ->
|
||||||
|
impl_delete_all_objects(Drv).
|
||||||
|
|
||||||
|
lookup(#tab{type=Type}, Drv, Key) ->
|
||||||
|
case impl_lookup(Drv, encode(Type, Key)) of
|
||||||
|
true ->
|
||||||
|
[];
|
||||||
|
Object when is_binary(Object) ->
|
||||||
|
[decode(Type, Object)]
|
||||||
|
end.
|
||||||
|
|
||||||
|
first(#tab{type=Type}, Drv) ->
|
||||||
|
case impl_first(Drv) of
|
||||||
|
'$end_of_table' ->
|
||||||
|
'$end_of_table';
|
||||||
|
Key ->
|
||||||
|
decode(Type, Key)
|
||||||
|
end.
|
||||||
|
|
||||||
|
next(#tab{type=Type}, Drv, Key) ->
|
||||||
|
case impl_next(Drv, encode(Type, Key)) of
|
||||||
|
'$end_of_table' ->
|
||||||
|
'$end_of_table';
|
||||||
|
Next ->
|
||||||
|
decode(Type, Next)
|
||||||
|
end.
|
||||||
|
|
||||||
|
info_memory(_Tab, Drv) ->
|
||||||
|
case impl_info_memory(Drv) of
|
||||||
|
Memory when is_integer(Memory) ->
|
||||||
|
erlang:round(Memory / erlang:system_info(wordsize));
|
||||||
|
Else ->
|
||||||
|
Else
|
||||||
|
end.
|
||||||
|
|
||||||
|
info_size(_Tab, Drv) ->
|
||||||
|
impl_info_size(Drv).
|
||||||
|
|
||||||
|
tab2list(Tab, Drv) ->
|
||||||
|
tab2list(Tab, Drv, impl_first(Drv), []).
|
||||||
|
|
||||||
|
tab2list(_Tab, _Drv, '$end_of_table', Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
tab2list(#tab{type=Type}=Tab, Drv, Key, Acc) ->
|
||||||
|
NewAcc =
|
||||||
|
case impl_lookup(Drv, Key) of
|
||||||
|
true ->
|
||||||
|
%% @NOTE This is not an atomic operation
|
||||||
|
Acc;
|
||||||
|
Object when is_binary(Object) ->
|
||||||
|
[decode(Type, Object)|Acc]
|
||||||
|
end,
|
||||||
|
tab2list(Tab, Drv, impl_next(Drv, Key), NewAcc).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal functions
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
encode(set, Term) ->
|
||||||
|
term_to_binary(Term);
|
||||||
|
encode(ordered_set, Term) ->
|
||||||
|
sext:encode(Term).
|
||||||
|
|
||||||
|
decode(set, Term) ->
|
||||||
|
binary_to_term(Term);
|
||||||
|
decode(ordered_set, Term) ->
|
||||||
|
sext:decode(Term).
|
||||||
|
|
||||||
|
impl_open(Type, Protection, Path, Options, ReadOptions, WriteOptions) ->
|
||||||
|
Drv = init(),
|
||||||
|
true = call(Drv, {?LETS_OPEN6, Type, Protection, Path, Options, ReadOptions, WriteOptions}),
|
||||||
|
Drv.
|
||||||
|
|
||||||
|
impl_destroy(Type, Protection, Path, Options, ReadOptions, WriteOptions) ->
|
||||||
|
Drv = init(),
|
||||||
|
true = call(Drv, {?LETS_OPEN6, Type, Protection, Path, Options, ReadOptions, WriteOptions}),
|
||||||
|
_ = port_close(Drv),
|
||||||
|
_ = erl_ddll:unload(lets_drv),
|
||||||
|
true.
|
||||||
|
|
||||||
|
impl_repair(Type, Protection, Path, Options, ReadOptions, WriteOptions) ->
|
||||||
|
Drv = init(),
|
||||||
|
true = call(Drv, {?LETS_REPAIR6, Type, Protection, Path, Options, ReadOptions, WriteOptions}),
|
||||||
|
_ = port_close(Drv),
|
||||||
|
_ = erl_ddll:unload(lets_drv),
|
||||||
|
true.
|
||||||
|
|
||||||
|
impl_insert(Drv, Key, Object) ->
|
||||||
|
call(Drv, {?LETS_INSERT3, Key, Object}).
|
||||||
|
|
||||||
|
impl_insert(Drv, List) ->
|
||||||
|
call(Drv, {?LETS_INSERT2, List}).
|
||||||
|
|
||||||
|
impl_insert_new(Drv, Key, Object) ->
|
||||||
|
call(Drv, {?LETS_INSERT_NEW3, Key, Object}).
|
||||||
|
|
||||||
|
impl_insert_new(Drv, List) ->
|
||||||
|
call(Drv, {?LETS_INSERT_NEW2, List}).
|
||||||
|
|
||||||
|
impl_delete(Drv) ->
|
||||||
|
Res = call(Drv, {?LETS_DELETE1}),
|
||||||
|
_ = port_close(Drv),
|
||||||
|
_ = erl_ddll:unload(lets_drv),
|
||||||
|
Res.
|
||||||
|
|
||||||
|
impl_delete(Drv, Key) ->
|
||||||
|
call(Drv, {?LETS_DELETE2, Key}).
|
||||||
|
|
||||||
|
impl_delete_all_objects(Drv) ->
|
||||||
|
call(Drv, {?LETS_DELETE_ALL_OBJECTS1}).
|
||||||
|
|
||||||
|
impl_lookup(Drv, Key) ->
|
||||||
|
call(Drv, {?LETS_LOOKUP2, Key}).
|
||||||
|
|
||||||
|
impl_first(Drv) ->
|
||||||
|
call(Drv, {?LETS_FIRST1}).
|
||||||
|
|
||||||
|
impl_next(Drv, Key) ->
|
||||||
|
call(Drv, {?LETS_NEXT2, Key}).
|
||||||
|
|
||||||
|
impl_info_memory(Drv) ->
|
||||||
|
call(Drv, {?LETS_INFO_MEMORY1}).
|
||||||
|
|
||||||
|
impl_info_size(Drv) ->
|
||||||
|
call(Drv, {?LETS_INFO_SIZE1}).
|
||||||
|
|
||||||
|
call(Drv, Tuple) ->
|
||||||
|
Data = term_to_binary(Tuple),
|
||||||
|
port_command(Drv, Data),
|
||||||
|
receive
|
||||||
|
{Drv, ?LETS_TRUE, Reply} ->
|
||||||
|
Reply;
|
||||||
|
{Drv, ?LETS_TRUE} ->
|
||||||
|
true;
|
||||||
|
{Drv, ?LETS_END_OF_TABLE} ->
|
||||||
|
'$end_of_table';
|
||||||
|
{Drv, ?LETS_BADARG} ->
|
||||||
|
erlang:error(badarg, [Drv])
|
||||||
|
%% after 1000 ->
|
||||||
|
%% receive X ->
|
||||||
|
%% erlang:error(timeout, [Drv, X])
|
||||||
|
%% after 0 ->
|
||||||
|
%% erlang:error(timeout, [Drv])
|
||||||
|
%% end
|
||||||
|
end.
|
99
src/lets_ets.erl
Normal file
99
src/lets_ets.erl
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(lets_ets).
|
||||||
|
|
||||||
|
-include("lets.hrl").
|
||||||
|
|
||||||
|
%% External exports
|
||||||
|
-export([open/1
|
||||||
|
, destroy/1
|
||||||
|
, repair/1
|
||||||
|
, insert/3
|
||||||
|
, insert_new/3
|
||||||
|
, delete/2
|
||||||
|
, delete/3
|
||||||
|
, delete_all_objects/2
|
||||||
|
, lookup/3
|
||||||
|
, first/2
|
||||||
|
, next/3
|
||||||
|
, info_memory/2
|
||||||
|
, info_size/2
|
||||||
|
, tab2list/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Types/Specs/Records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
open(#tab{name=Name, named_table=NamedTable, type=Type, keypos=KeyPos, protection=Protection, compressed=Compressed}=Tab) ->
|
||||||
|
Opts =
|
||||||
|
[Type, {keypos,KeyPos}, Protection] ++
|
||||||
|
[named_table || NamedTable ] ++
|
||||||
|
[compressed || Compressed ],
|
||||||
|
Ets = ets:new(Name, Opts),
|
||||||
|
Tab#tab{ets=Ets}.
|
||||||
|
|
||||||
|
destroy(#tab{}) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
repair(#tab{}) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
insert(_Tab, Ets, ObjectOrObjects) ->
|
||||||
|
ets:insert(Ets, ObjectOrObjects).
|
||||||
|
|
||||||
|
insert_new(_Tab, Ets, ObjectOrObjects) ->
|
||||||
|
ets:insert_new(Ets, ObjectOrObjects).
|
||||||
|
|
||||||
|
delete(_Tab, Ets) ->
|
||||||
|
ets:delete(Ets).
|
||||||
|
|
||||||
|
delete(_Tab, Ets, Key) ->
|
||||||
|
ets:delete(Ets, Key).
|
||||||
|
|
||||||
|
delete_all_objects(_Tab, Ets) ->
|
||||||
|
ets:delete_all_objects(Ets).
|
||||||
|
|
||||||
|
lookup(_Tab, Ets, Key) ->
|
||||||
|
ets:lookup(Ets, Key).
|
||||||
|
|
||||||
|
first(_Tab, Ets) ->
|
||||||
|
ets:first(Ets).
|
||||||
|
|
||||||
|
next(_Tab, Ets, Key) ->
|
||||||
|
ets:next(Ets, Key).
|
||||||
|
|
||||||
|
info_memory(_Tab, Ets) ->
|
||||||
|
ets:info(Ets, memory).
|
||||||
|
|
||||||
|
info_size(_Tab, Ets) ->
|
||||||
|
ets:info(Ets, size).
|
||||||
|
|
||||||
|
tab2list(_Tab, Ets) ->
|
||||||
|
ets:tab2list(Ets).
|
219
src/lets_nif.erl
Normal file
219
src/lets_nif.erl
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(lets_nif).
|
||||||
|
|
||||||
|
-include("lets.hrl").
|
||||||
|
|
||||||
|
%% External exports
|
||||||
|
-export([open/4
|
||||||
|
, destroy/4
|
||||||
|
, repair/4
|
||||||
|
, insert/3
|
||||||
|
, insert_new/3
|
||||||
|
, delete/2
|
||||||
|
, delete/3
|
||||||
|
, delete_all_objects/2
|
||||||
|
, lookup/3
|
||||||
|
, first/2
|
||||||
|
, next/3
|
||||||
|
, info_memory/2
|
||||||
|
, info_size/2
|
||||||
|
, tab2list/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-on_load(init/0).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Types/Specs/Records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(NIF_STUB, nif_stub_error(?LINE)).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
init() ->
|
||||||
|
Path =
|
||||||
|
case code:priv_dir(lets) of
|
||||||
|
{error, bad_name} ->
|
||||||
|
"../priv/lib";
|
||||||
|
Dir ->
|
||||||
|
filename:join([Dir, "lib"])
|
||||||
|
end,
|
||||||
|
erlang:load_nif(filename:join(Path, "lets_nif"), 0).
|
||||||
|
|
||||||
|
open(#tab{name=_Name, named_table=_Named, type=Type, protection=Protection}=Tab, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
Nif = impl_open(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions),
|
||||||
|
%% @TODO implement named Nif (of sorts)
|
||||||
|
Tab#tab{nif=Nif}.
|
||||||
|
|
||||||
|
destroy(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
impl_destroy(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
|
||||||
|
|
||||||
|
repair(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
|
||||||
|
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
|
||||||
|
impl_repair(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
|
||||||
|
|
||||||
|
insert(#tab{keypos=KeyPos, type=Type}, Nif, Object) when is_tuple(Object) ->
|
||||||
|
Key = element(KeyPos,Object),
|
||||||
|
Val = Object,
|
||||||
|
impl_insert(Nif, encode(Type, Key), encode(Type, Val));
|
||||||
|
insert(#tab{keypos=KeyPos, type=Type}, Nif, Objects) when is_list(Objects) ->
|
||||||
|
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
|
||||||
|
impl_insert(Nif, List).
|
||||||
|
|
||||||
|
insert_new(#tab{keypos=KeyPos, type=Type}, Nif, Object) when is_tuple(Object) ->
|
||||||
|
Key = element(KeyPos,Object),
|
||||||
|
Val = Object,
|
||||||
|
impl_insert_new(Nif, encode(Type, Key), encode(Type, Val));
|
||||||
|
insert_new(#tab{keypos=KeyPos, type=Type}, Nif, Objects) when is_list(Objects) ->
|
||||||
|
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
|
||||||
|
impl_insert_new(Nif, List).
|
||||||
|
|
||||||
|
delete(_Tab, Nif) ->
|
||||||
|
impl_delete(Nif).
|
||||||
|
|
||||||
|
delete(#tab{type=Type}, Nif, Key) ->
|
||||||
|
impl_delete(Nif, encode(Type, Key)).
|
||||||
|
|
||||||
|
delete_all_objects(_Tab, Nif) ->
|
||||||
|
impl_delete_all_objects(Nif).
|
||||||
|
|
||||||
|
lookup(#tab{type=Type}, Nif, Key) ->
|
||||||
|
case impl_lookup(Nif, encode(Type, Key)) of
|
||||||
|
true ->
|
||||||
|
[];
|
||||||
|
Object when is_binary(Object) ->
|
||||||
|
[decode(Type, Object)]
|
||||||
|
end.
|
||||||
|
|
||||||
|
first(#tab{type=Type}, Nif) ->
|
||||||
|
case impl_first(Nif) of
|
||||||
|
'$end_of_table' ->
|
||||||
|
'$end_of_table';
|
||||||
|
Key ->
|
||||||
|
decode(Type, Key)
|
||||||
|
end.
|
||||||
|
|
||||||
|
next(#tab{type=Type}, Nif, Key) ->
|
||||||
|
case impl_next(Nif, encode(Type, Key)) of
|
||||||
|
'$end_of_table' ->
|
||||||
|
'$end_of_table';
|
||||||
|
Next ->
|
||||||
|
decode(Type, Next)
|
||||||
|
end.
|
||||||
|
|
||||||
|
info_memory(_Tab, Nif) ->
|
||||||
|
case impl_info_memory(Nif) of
|
||||||
|
Memory when is_integer(Memory) ->
|
||||||
|
erlang:round(Memory / erlang:system_info(wordsize));
|
||||||
|
Else ->
|
||||||
|
Else
|
||||||
|
end.
|
||||||
|
|
||||||
|
info_size(_Tab, Nif) ->
|
||||||
|
impl_info_size(Nif).
|
||||||
|
|
||||||
|
tab2list(Tab, Nif) ->
|
||||||
|
tab2list(Tab, Nif, impl_first(Nif), []).
|
||||||
|
|
||||||
|
tab2list(_Tab, _Nif, '$end_of_table', Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
tab2list(#tab{type=Type}=Tab, Nif, Key, Acc) ->
|
||||||
|
NewAcc =
|
||||||
|
case impl_lookup(Nif, Key) of
|
||||||
|
true ->
|
||||||
|
%% @NOTE This is not an atomic operation
|
||||||
|
Acc;
|
||||||
|
Object when is_binary(Object) ->
|
||||||
|
[decode(Type, Object)|Acc]
|
||||||
|
end,
|
||||||
|
tab2list(Tab, Nif, impl_next(Nif, Key), NewAcc).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal functions
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
encode(set, Term) ->
|
||||||
|
term_to_binary(Term);
|
||||||
|
encode(ordered_set, Term) ->
|
||||||
|
sext:encode(Term).
|
||||||
|
|
||||||
|
decode(set, Term) ->
|
||||||
|
binary_to_term(Term);
|
||||||
|
decode(ordered_set, Term) ->
|
||||||
|
sext:decode(Term).
|
||||||
|
|
||||||
|
nif_stub_error(Line) ->
|
||||||
|
erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}).
|
||||||
|
|
||||||
|
impl_open(_Type, _Protection, _Path, _Options, _ReadOptions, _WriteOptions) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_destroy(_Type, _Protection, _Path, _Options, _ReadOptions, _WriteOptions) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_repair(_Type, _Protection, _Path, _Options, _ReadOptions, _WriteOptions) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_insert(_Nif, _Key, _Object) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_insert(_Nif, _List) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_insert_new(_Nif, _Key, _Object) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_insert_new(_Nif, _List) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_delete(_Nif) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_delete(_Nif, _Key) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_delete_all_objects(_Nif) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_lookup(_Nif, _Key) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_first(_Nif) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_next(_Nif, _Key) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_info_memory(_Nif) ->
|
||||||
|
?NIF_STUB.
|
||||||
|
|
||||||
|
impl_info_size(_Nif) ->
|
||||||
|
?NIF_STUB.
|
197
test/qc/qc_lets_proxy.erl
Normal file
197
test/qc/qc_lets_proxy.erl
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_lets_proxy).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([%% test
|
||||||
|
teardown/1
|
||||||
|
, is_table/1
|
||||||
|
%% lets
|
||||||
|
, new/2
|
||||||
|
, new/3
|
||||||
|
, destroy/3
|
||||||
|
, repair/3
|
||||||
|
, insert/2
|
||||||
|
, insert_new/2
|
||||||
|
, delete/1
|
||||||
|
, delete/2
|
||||||
|
, delete_all_objects/1
|
||||||
|
, lookup/2
|
||||||
|
, first/1
|
||||||
|
, next/2
|
||||||
|
, info/2
|
||||||
|
, tab2list/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-record(state, {tab}).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
teardown(Name) ->
|
||||||
|
qc_lets_raw:teardown(Name).
|
||||||
|
|
||||||
|
is_table(Tab) ->
|
||||||
|
is_pid(Tab).
|
||||||
|
|
||||||
|
new(Name, Options) ->
|
||||||
|
{ok, Pid} = gen_server:start({local, Name}, ?MODULE, [Name, Options], []),
|
||||||
|
Pid.
|
||||||
|
|
||||||
|
new(_Tab, Name, Options) ->
|
||||||
|
%% _Tab is to help control generators and shrinking
|
||||||
|
new(Name, Options).
|
||||||
|
|
||||||
|
destroy(Tab, Name, Options) ->
|
||||||
|
qc_lets_raw:destroy(Tab, Name, Options).
|
||||||
|
|
||||||
|
repair(Tab, Name, Options) ->
|
||||||
|
qc_lets_raw:repair(Tab, Name, Options).
|
||||||
|
|
||||||
|
insert(Tab, ObjOrObjs) ->
|
||||||
|
gen_server:call(Tab, {insert, ObjOrObjs}).
|
||||||
|
|
||||||
|
insert_new(Tab, ObjOrObjs) ->
|
||||||
|
gen_server:call(Tab, {insert_new, ObjOrObjs}).
|
||||||
|
|
||||||
|
delete(Tab) ->
|
||||||
|
gen_server:call(Tab, delete).
|
||||||
|
|
||||||
|
delete(Tab, Key) ->
|
||||||
|
gen_server:call(Tab, {delete, Key}).
|
||||||
|
|
||||||
|
delete_all_objects(Tab) ->
|
||||||
|
gen_server:call(Tab, delete_all_objects).
|
||||||
|
|
||||||
|
lookup(Tab, Key) ->
|
||||||
|
gen_server:call(Tab, {lookup, Key}).
|
||||||
|
|
||||||
|
first(Tab) ->
|
||||||
|
gen_server:call(Tab, first).
|
||||||
|
|
||||||
|
next(Tab, Key) ->
|
||||||
|
gen_server:call(Tab, {next, Key}).
|
||||||
|
|
||||||
|
info(Tab, Item) ->
|
||||||
|
gen_server:call(Tab, {info, Item}).
|
||||||
|
|
||||||
|
tab2list(Tab) ->
|
||||||
|
gen_server:call(Tab, tab2list).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% gen_server callbacks
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% Initializes the server
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
init([Name, Options]) ->
|
||||||
|
Tab = qc_lets_raw:new(Name, Options),
|
||||||
|
{ok, #state{tab=Tab}}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% Handling call messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_call({insert, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:insert(Tab, ObjOrObjs),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call({insert_new, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:insert_new(Tab, ObjOrObjs),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call(delete, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:delete(Tab),
|
||||||
|
{stop, normal, Reply, State#state{tab=undefined}};
|
||||||
|
handle_call({delete, Key}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:delete(Tab, Key),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call(delete_all_objects, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:delete_all_objects(Tab),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call({lookup, Key}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:lookup(Tab, Key),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call(first, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:first(Tab),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call({next, Key}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:next(Tab, Key),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call({info, Item}, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:info(Tab, Item),
|
||||||
|
{reply, Reply, State};
|
||||||
|
handle_call(tab2list, _From, #state{tab=Tab}=State) ->
|
||||||
|
Reply = qc_lets_raw:tab2list(Tab),
|
||||||
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% Handling cast messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% Handling all non call/cast messages
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% This function is called by a gen_server when it is about to
|
||||||
|
%% terminate. It should be the opposite of Module:init/1 and do any
|
||||||
|
%% necessary cleaning up. When it returns, the gen_server terminates
|
||||||
|
%% with Reason. The return value is ignored.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
terminate(_Reason, #state{tab=undefined}) ->
|
||||||
|
ok;
|
||||||
|
terminate(_Reason, #state{tab=Tab}) ->
|
||||||
|
qc_lets_raw:delete(Tab).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc
|
||||||
|
%% Convert process state when code is changed
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
116
test/qc/qc_lets_raw.erl
Normal file
116
test/qc/qc_lets_raw.erl
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_lets_raw).
|
||||||
|
|
||||||
|
-include("../../src/lets.hrl").
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([%% test
|
||||||
|
teardown/1
|
||||||
|
, is_table/1
|
||||||
|
%% lets
|
||||||
|
, new/2
|
||||||
|
, new/3
|
||||||
|
, destroy/3
|
||||||
|
, repair/3
|
||||||
|
, insert/2
|
||||||
|
, insert_new/2
|
||||||
|
, delete/1
|
||||||
|
, delete/2
|
||||||
|
, delete_all_objects/1
|
||||||
|
, lookup/2
|
||||||
|
, first/1
|
||||||
|
, next/2
|
||||||
|
, info/2
|
||||||
|
, tab2list/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
teardown(Name) ->
|
||||||
|
%% @TODO make this more robust
|
||||||
|
catch ets:delete(Name),
|
||||||
|
catch exit(whereis(Name), kill),
|
||||||
|
os:cmd("rm -rf " ++ ?MODULE_STRING).
|
||||||
|
|
||||||
|
is_table(Res) ->
|
||||||
|
is_record(Res, tab).
|
||||||
|
|
||||||
|
new(Name, Options) ->
|
||||||
|
ok = filelib:ensure_dir(?MODULE_STRING),
|
||||||
|
catch lets:new(Name, filter_options(Options)).
|
||||||
|
|
||||||
|
new(_Tab, Name, Options) ->
|
||||||
|
%% _Tab is to help control generators and shrinking
|
||||||
|
new(Name, Options).
|
||||||
|
|
||||||
|
destroy(_Tab, Name, Options) ->
|
||||||
|
%% _Tab is to help control generators and shrinking
|
||||||
|
catch lets:destroy(Name, filter_options(Options)).
|
||||||
|
|
||||||
|
repair(_Tab, Name, Options) ->
|
||||||
|
%% _Tab is to help control generators and shrinking
|
||||||
|
catch lets:repair(Name, filter_options(Options)).
|
||||||
|
|
||||||
|
insert(Tab, ObjOrObjs) ->
|
||||||
|
catch lets:insert(Tab, ObjOrObjs).
|
||||||
|
|
||||||
|
insert_new(Tab, ObjOrObjs) ->
|
||||||
|
catch lets:insert_new(Tab, ObjOrObjs).
|
||||||
|
|
||||||
|
delete(Tab) ->
|
||||||
|
catch lets:delete(Tab).
|
||||||
|
|
||||||
|
delete(Tab, Key) ->
|
||||||
|
catch lets:delete(Tab, Key).
|
||||||
|
|
||||||
|
delete_all_objects(Tab) ->
|
||||||
|
catch lets:delete_all_objects(Tab).
|
||||||
|
|
||||||
|
lookup(Tab, Key) ->
|
||||||
|
catch lets:lookup(Tab, Key).
|
||||||
|
|
||||||
|
first(Tab) ->
|
||||||
|
catch lets:first(Tab).
|
||||||
|
|
||||||
|
next(Tab, Key) ->
|
||||||
|
catch lets:next(Tab, Key).
|
||||||
|
|
||||||
|
info(Tab, Item) ->
|
||||||
|
catch lets:info(Tab, Item).
|
||||||
|
|
||||||
|
tab2list(Tab) ->
|
||||||
|
catch lets:tab2list(Tab).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
filter_options(Options) ->
|
||||||
|
X = proplists:get_value(db, Options, []),
|
||||||
|
Y = [{path, ?MODULE_STRING}|proplists:delete(path, X)],
|
||||||
|
[{db, Y}|proplists:delete(db, Options)].
|
118
test/qc/qc_lets_slave_proxy.erl
Normal file
118
test/qc/qc_lets_slave_proxy.erl
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_lets_slave_proxy).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([%% test
|
||||||
|
teardown/1
|
||||||
|
, is_table/1
|
||||||
|
%% lets
|
||||||
|
, new/2
|
||||||
|
, new/3
|
||||||
|
, destroy/3
|
||||||
|
, repair/3
|
||||||
|
, insert/2
|
||||||
|
, insert_new/2
|
||||||
|
, delete/1
|
||||||
|
, delete/2
|
||||||
|
, delete_all_objects/1
|
||||||
|
, lookup/2
|
||||||
|
, first/1
|
||||||
|
, next/2
|
||||||
|
, info/2
|
||||||
|
, tab2list/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
teardown(Name) ->
|
||||||
|
stop_slave(),
|
||||||
|
qc_lets_proxy:teardown(Name).
|
||||||
|
|
||||||
|
is_table(Tab) ->
|
||||||
|
qc_lets_proxy:is_table(Tab).
|
||||||
|
|
||||||
|
new(Name, Options) ->
|
||||||
|
call_slave(new, [Name, Options]).
|
||||||
|
|
||||||
|
new(_Tab, Name, Options) ->
|
||||||
|
%% _Tab is to help control generators and shrinking
|
||||||
|
new(Name, Options).
|
||||||
|
|
||||||
|
destroy(Tab, Name, Options) ->
|
||||||
|
qc_lets_raw:destroy(Tab, Name, Options).
|
||||||
|
|
||||||
|
repair(Tab, Name, Options) ->
|
||||||
|
qc_lets_raw:repair(Tab, Name, Options).
|
||||||
|
|
||||||
|
insert(Tab, ObjOrObjs) ->
|
||||||
|
qc_lets_proxy:insert(Tab, ObjOrObjs).
|
||||||
|
|
||||||
|
insert_new(Tab, ObjOrObjs) ->
|
||||||
|
qc_lets_proxy:insert_new(Tab, ObjOrObjs).
|
||||||
|
|
||||||
|
delete(Tab) ->
|
||||||
|
qc_lets_proxy:delete(Tab).
|
||||||
|
|
||||||
|
delete(Tab, Key) ->
|
||||||
|
qc_lets_proxy:delete(Tab, Key).
|
||||||
|
|
||||||
|
delete_all_objects(Tab) ->
|
||||||
|
qc_lets_proxy:delete_all_objects(Tab).
|
||||||
|
|
||||||
|
lookup(Tab, Key) ->
|
||||||
|
qc_lets_proxy:lookup(Tab, Key).
|
||||||
|
|
||||||
|
first(Tab) ->
|
||||||
|
qc_lets_proxy:first(Tab).
|
||||||
|
|
||||||
|
next(Tab, Key) ->
|
||||||
|
qc_lets_proxy:next(Tab, Key).
|
||||||
|
|
||||||
|
info(Tab, Item) ->
|
||||||
|
qc_lets_proxy:info(Tab, Item).
|
||||||
|
|
||||||
|
tab2list(Tab) ->
|
||||||
|
qc_lets_proxy:tab2list(Tab).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
stop_slave() ->
|
||||||
|
qc_slave:stop_slave(?MODULE).
|
||||||
|
|
||||||
|
call_slave(Function, Args) ->
|
||||||
|
Slave = qc_slave:restart_slave(?MODULE),
|
||||||
|
case rpc:call(Slave, qc_lets_proxy, Function, Args) of
|
||||||
|
{badrpc, {'EXIT', _}=Exit} ->
|
||||||
|
Exit;
|
||||||
|
{badrpc, _}=BadRpc ->
|
||||||
|
BadRpc;
|
||||||
|
Res ->
|
||||||
|
Res
|
||||||
|
end.
|
281
test/qc/qc_leveldb.erl
Normal file
281
test/qc/qc_leveldb.erl
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_leveldb).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([%% test
|
||||||
|
setup/0
|
||||||
|
, teardown/0
|
||||||
|
, is_db/1
|
||||||
|
%% lets
|
||||||
|
, open/0, open/1
|
||||||
|
, reopen/0, reopen/1
|
||||||
|
, close/1
|
||||||
|
, put/2, put/3
|
||||||
|
, delete/2, delete/3
|
||||||
|
, get/2, get/3
|
||||||
|
, first/1, first/2
|
||||||
|
, last/1, last/2
|
||||||
|
, next/2, next/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
setup() ->
|
||||||
|
Options = [{c_src,"../c_src/leveldb/include/leveldb/c.h"},
|
||||||
|
{additional_files, ["../c_src/leveldb/lib/libleveldb.a", "../c_src/snappy/lib/libsnappy.a"]},
|
||||||
|
{cflags, "-lstdc++ -lpthread"}],
|
||||||
|
eqc_c:start(leveldb, Options).
|
||||||
|
|
||||||
|
teardown() ->
|
||||||
|
os:cmd("rm -rf " ++ ?MODULE_STRING).
|
||||||
|
|
||||||
|
is_db({ptr, {struct, leveldb_t}, _}) ->
|
||||||
|
true;
|
||||||
|
is_db(_) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
open() ->
|
||||||
|
Options = leveldb:leveldb_options_create(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_options_set_create_if_missing(Options, 1),
|
||||||
|
leveldb:leveldb_options_set_error_if_exists(Options, 1),
|
||||||
|
open(Options)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_options_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
open(Options) ->
|
||||||
|
ErrPtr = errptr(),
|
||||||
|
try
|
||||||
|
case leveldb:leveldb_open(Options, ?MODULE_STRING, ErrPtr) of
|
||||||
|
{ptr, {struct, leveldb_t}, 0} ->
|
||||||
|
read_errptr(ErrPtr);
|
||||||
|
{ptr, {struct, leveldb_t}, _}=Db ->
|
||||||
|
Db
|
||||||
|
end
|
||||||
|
after
|
||||||
|
free_ptr(ErrPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
reopen() ->
|
||||||
|
Options = leveldb:leveldb_options_create(),
|
||||||
|
try
|
||||||
|
reopen(Options)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_options_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
reopen(Options) ->
|
||||||
|
ErrPtr = errptr(),
|
||||||
|
try
|
||||||
|
case leveldb:leveldb_open(Options, ?MODULE_STRING, ErrPtr) of
|
||||||
|
{ptr, {struct, leveldb_t}, 0} ->
|
||||||
|
read_errptr(ErrPtr);
|
||||||
|
{ptr, {struct, leveldb_t}, _}=Db ->
|
||||||
|
Db
|
||||||
|
end
|
||||||
|
after
|
||||||
|
free_ptr(ErrPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
close(Db) ->
|
||||||
|
ok == leveldb:leveldb_close(Db).
|
||||||
|
|
||||||
|
put(Db, Obj) ->
|
||||||
|
Options = leveldb:leveldb_writeoptions_create(),
|
||||||
|
try
|
||||||
|
put(Db, Options, Obj)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_writeoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
put(Db, Options, {obj,Key,Val}) ->
|
||||||
|
ErrPtr = errptr(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_put(Db, Options, binary_to_list(Key), byte_size(Key), binary_to_list(Val), byte_size(Val), ErrPtr),
|
||||||
|
read_errptr(ErrPtr)
|
||||||
|
after
|
||||||
|
free_ptr(ErrPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete(Db, Key) ->
|
||||||
|
Options = leveldb:leveldb_writeoptions_create(),
|
||||||
|
try
|
||||||
|
delete(Db, Options, Key)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_writeoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete(Db, Options, Key) ->
|
||||||
|
ErrPtr = errptr(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_delete(Db, Options, binary_to_list(Key), byte_size(Key), ErrPtr),
|
||||||
|
read_errptr(ErrPtr)
|
||||||
|
after
|
||||||
|
free_ptr(ErrPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
get(Db, Key) ->
|
||||||
|
Options = leveldb:leveldb_readoptions_create(),
|
||||||
|
try
|
||||||
|
get(Db, Options, Key)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_readoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
get(Db, Options, Key) ->
|
||||||
|
ErrPtr = errptr(),
|
||||||
|
LenPtr = lenptr(),
|
||||||
|
try
|
||||||
|
case leveldb:leveldb_get(Db, Options, binary_to_list(Key), byte_size(Key), LenPtr, ErrPtr) of
|
||||||
|
0 ->
|
||||||
|
read_errptr(ErrPtr);
|
||||||
|
ValPtr ->
|
||||||
|
read_binary(ValPtr, LenPtr)
|
||||||
|
end
|
||||||
|
after
|
||||||
|
free_ptr(ErrPtr),
|
||||||
|
free_ptr(LenPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
first(Db) ->
|
||||||
|
Options = leveldb:leveldb_readoptions_create(),
|
||||||
|
try
|
||||||
|
first(Db, Options)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_readoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
first(Db, Options) ->
|
||||||
|
Iter = leveldb:leveldb_create_iterator(Db, Options),
|
||||||
|
LenPtr = lenptr(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_iter_seek_to_first(Iter),
|
||||||
|
case leveldb:leveldb_iter_valid(Iter) of
|
||||||
|
0 ->
|
||||||
|
true;
|
||||||
|
1 ->
|
||||||
|
KeyPtr = leveldb:leveldb_iter_key(Iter, LenPtr),
|
||||||
|
read_binary(KeyPtr, LenPtr)
|
||||||
|
end
|
||||||
|
after
|
||||||
|
leveldb:leveldb_iter_destroy(Iter),
|
||||||
|
free_ptr(LenPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
last(Db) ->
|
||||||
|
Options = leveldb:leveldb_readoptions_create(),
|
||||||
|
try
|
||||||
|
last(Db, Options)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_readoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
last(Db, Options) ->
|
||||||
|
Iter = leveldb:leveldb_create_iterator(Db, Options),
|
||||||
|
LenPtr = lenptr(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_iter_seek_to_last(Iter),
|
||||||
|
case leveldb:leveldb_iter_valid(Iter) of
|
||||||
|
0 ->
|
||||||
|
true;
|
||||||
|
1 ->
|
||||||
|
KeyPtr = leveldb:leveldb_iter_key(Iter, LenPtr),
|
||||||
|
read_binary(KeyPtr, LenPtr)
|
||||||
|
end
|
||||||
|
after
|
||||||
|
leveldb:leveldb_iter_destroy(Iter),
|
||||||
|
free_ptr(LenPtr)
|
||||||
|
end.
|
||||||
|
|
||||||
|
next(Db, Key) ->
|
||||||
|
Options = leveldb:leveldb_readoptions_create(),
|
||||||
|
try
|
||||||
|
next(Db, Key, Options)
|
||||||
|
after
|
||||||
|
leveldb:leveldb_readoptions_destroy(Options)
|
||||||
|
end.
|
||||||
|
|
||||||
|
next(Db, Key, Options) ->
|
||||||
|
Iter = leveldb:leveldb_create_iterator(Db, Options),
|
||||||
|
LenPtr = lenptr(),
|
||||||
|
LenPtr1 = lenptr(),
|
||||||
|
try
|
||||||
|
leveldb:leveldb_iter_seek(Iter, binary_to_list(Key), byte_size(Key)),
|
||||||
|
case leveldb:leveldb_iter_valid(Iter) of
|
||||||
|
0 ->
|
||||||
|
true;
|
||||||
|
1 ->
|
||||||
|
KeyPtr = leveldb:leveldb_iter_key(Iter, LenPtr),
|
||||||
|
K = read_binary(KeyPtr, LenPtr),
|
||||||
|
if K =/= Key ->
|
||||||
|
K;
|
||||||
|
true ->
|
||||||
|
leveldb:leveldb_iter_next(Iter),
|
||||||
|
case leveldb:leveldb_iter_valid(Iter) of
|
||||||
|
0 ->
|
||||||
|
true;
|
||||||
|
1 ->
|
||||||
|
KeyPtr1 = leveldb:leveldb_iter_key(Iter, LenPtr1),
|
||||||
|
read_binary(KeyPtr1, LenPtr1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
after
|
||||||
|
leveldb:leveldb_iter_destroy(Iter),
|
||||||
|
free_ptr(LenPtr),
|
||||||
|
free_ptr(LenPtr1)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
errptr() ->
|
||||||
|
eqc_c:alloc({ptr, char}, [0]).
|
||||||
|
|
||||||
|
lenptr() ->
|
||||||
|
eqc_c:alloc(unsigned_long, 0).
|
||||||
|
|
||||||
|
free_ptr(Ptr) ->
|
||||||
|
eqc_c:free(Ptr).
|
||||||
|
|
||||||
|
read_errptr(ErrPtr) ->
|
||||||
|
case eqc_c:read_string(eqc_c:deref(ErrPtr)) of
|
||||||
|
[] ->
|
||||||
|
true;
|
||||||
|
Err ->
|
||||||
|
Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
read_lenptr(LenPtr) ->
|
||||||
|
eqc_c:deref(LenPtr).
|
||||||
|
|
||||||
|
read_binary({ptr, char, 0}, _LenPtr) ->
|
||||||
|
true;
|
||||||
|
read_binary(ValPtr, LenPtr) ->
|
||||||
|
list_to_binary(eqc_c:read_array(ValPtr, read_lenptr(LenPtr))).
|
446
test/qc/qc_statem_lets.erl
Normal file
446
test/qc/qc_statem_lets.erl
Normal file
|
@ -0,0 +1,446 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_statem_lets).
|
||||||
|
|
||||||
|
-ifdef(QC).
|
||||||
|
|
||||||
|
%% qc_statem Callbacks
|
||||||
|
-behaviour(qc_statem).
|
||||||
|
-export([command_gen/2]).
|
||||||
|
-export([initial_state/0, state_is_sane/1, next_state/3, precondition/2, postcondition/3]).
|
||||||
|
-export([commands_setup/1, commands_teardown/1, commands_teardown/2]).
|
||||||
|
|
||||||
|
%% @NOTE For boilerplate exports, see "qc_statem.hrl"
|
||||||
|
-include_lib("qc/include/qc_statem.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% defines, types, records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%-define(IMPL, qc_lets_raw).
|
||||||
|
%%-define(IMPL, qc_lets_proxy).
|
||||||
|
-define(IMPL, qc_lets_slave_proxy).
|
||||||
|
|
||||||
|
-define(TAB, ?MODULE).
|
||||||
|
-define(INT_KEYS, lists:seq(0,10)).
|
||||||
|
-define(FLOAT_KEYS, [float(Key) || Key <- ?INT_KEYS]).
|
||||||
|
-define(BINARY_KEYS, [term_to_binary(Key) || Key <- ?INT_KEYS]).
|
||||||
|
|
||||||
|
-record(obj, {key :: integer() | float() | binary(), val :: integer() | float() | binary()}).
|
||||||
|
|
||||||
|
-type obj() :: #obj{}.
|
||||||
|
-type ets_type() :: set | ordered_set. %% default is set
|
||||||
|
-type ets_impl() :: drv | nif | ets. %% default is drv
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
parallel=false :: boolean(),
|
||||||
|
type=undefined :: undefined | ets_type(),
|
||||||
|
impl=undefined :: undefined | ets_impl(),
|
||||||
|
exists=false :: boolean(),
|
||||||
|
tab=undefined :: undefined | tuple(),
|
||||||
|
objs=[] :: [obj()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% qc_statem Callbacks
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
command_gen(Mod,#state{parallel=false}=S) ->
|
||||||
|
serial_command_gen(Mod,S);
|
||||||
|
command_gen(Mod,#state{parallel=true}=S) ->
|
||||||
|
parallel_command_gen(Mod,S).
|
||||||
|
|
||||||
|
serial_command_gen(_Mod,#state{tab=undefined, type=undefined, impl=undefined}=S) ->
|
||||||
|
{call,?IMPL,new,[?TAB,gen_options(new,S)]};
|
||||||
|
serial_command_gen(_Mod,#state{tab=undefined}=S) ->
|
||||||
|
{call,?IMPL,new,[undefined,?TAB,gen_options(new,S)]};
|
||||||
|
serial_command_gen(_Mod,#state{tab=Tab, type=Type}=S) ->
|
||||||
|
%% @TODO insert/3, insert_new/3, delete/3, delete_all_objs/2 write_gen_options
|
||||||
|
%% @TODO lookup/3 read_gen_options
|
||||||
|
oneof([{call,?IMPL,insert,[Tab,oneof([gen_obj(S),gen_objs(S)])]}]
|
||||||
|
++ [{call,?IMPL,insert_new,[Tab,oneof([gen_obj(S),gen_objs(S)])]} || Type == ets]
|
||||||
|
%% @TODO ++ [{call,?IMPL,delete,[Tab]}]
|
||||||
|
++ [{call,?IMPL,delete,[Tab,gen_key(S)]}]
|
||||||
|
++ [{call,?IMPL,delete_all_objs,[Tab]} || Type == ets]
|
||||||
|
++ [{call,?IMPL,lookup,[Tab,gen_key(S)]}]
|
||||||
|
++ [{call,?IMPL,first,[Tab]}]
|
||||||
|
++ [{call,?IMPL,next,[Tab,gen_key(S)]}]
|
||||||
|
%% @TODO info
|
||||||
|
++ [{call,?IMPL,tab2list,[Tab]}]
|
||||||
|
).
|
||||||
|
|
||||||
|
parallel_command_gen(_Mod,#state{tab=undefined, type=undefined, impl=undefined}=S) ->
|
||||||
|
{call,?IMPL,new,[?TAB,gen_options(new,S)]};
|
||||||
|
parallel_command_gen(_Mod,#state{tab=Tab, type=Type}=S) ->
|
||||||
|
%% @TODO insert/3, insert_new/3, delete_all_objs/2 write_gen_options
|
||||||
|
%% @TODO lookup/3 read_gen_options
|
||||||
|
oneof([{call,?IMPL,insert,[Tab,oneof([gen_obj(S),gen_objs(S)])]}]
|
||||||
|
++ [{call,?IMPL,insert_new,[Tab,oneof([gen_obj(S),gen_objs(S)])]} || Type == ets]
|
||||||
|
++ [{call,?IMPL,delete,[Tab,gen_key(S)]}]
|
||||||
|
++ [{call,?IMPL,delete_all_objs,[Tab]} || Type == ets]
|
||||||
|
++ [{call,?IMPL,lookup,[Tab,gen_key(S)]}]
|
||||||
|
++ [{call,?IMPL,first,[Tab]}]
|
||||||
|
++ [{call,?IMPL,next,[Tab,gen_key(S)]}]
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec initial_state() -> #state{}.
|
||||||
|
initial_state() ->
|
||||||
|
?LET(Parallel,parameter(parallel,false),
|
||||||
|
#state{parallel=Parallel}).
|
||||||
|
|
||||||
|
-spec state_is_sane(#state{}) -> boolean().
|
||||||
|
state_is_sane(_S) ->
|
||||||
|
%% @TODO
|
||||||
|
true.
|
||||||
|
|
||||||
|
-spec next_state(#state{}, term(), tuple()) -> #state{}.
|
||||||
|
next_state(#state{tab=undefined, type=undefined, impl=undefined}=S, V, {call,_,new,[?TAB,Options]}) ->
|
||||||
|
%% @TODO Options
|
||||||
|
case [proplists:get_bool(X, Options) || X <- [set, ordered_set]] of
|
||||||
|
[_, false] ->
|
||||||
|
Type = set;
|
||||||
|
[false, true] ->
|
||||||
|
Type = ordered_set
|
||||||
|
end,
|
||||||
|
case [proplists:get_bool(X, Options) || X <- [drv, nif, ets]] of
|
||||||
|
[_, false, false] ->
|
||||||
|
Impl = drv;
|
||||||
|
[false, true, _] ->
|
||||||
|
Impl = nif;
|
||||||
|
[false, false, true] ->
|
||||||
|
Impl = ets
|
||||||
|
end,
|
||||||
|
S#state{type=Type, impl=Impl, exists=true, tab=V};
|
||||||
|
next_state(#state{tab=undefined}=S, V, {call,_,new,[_Tab,?TAB,Options]}) ->
|
||||||
|
%% @TODO Options
|
||||||
|
case [proplists:get_bool(X, Options) || X <- [set, ordered_set]] of
|
||||||
|
[_, false] ->
|
||||||
|
Type = set;
|
||||||
|
[false, true] ->
|
||||||
|
Type = ordered_set
|
||||||
|
end,
|
||||||
|
case [proplists:get_bool(X, Options) || X <- [drv, nif, ets]] of
|
||||||
|
[_, false, false] ->
|
||||||
|
Impl = drv;
|
||||||
|
[false, true, _] ->
|
||||||
|
Impl = nif;
|
||||||
|
[false, false, true] ->
|
||||||
|
Impl = ets
|
||||||
|
end,
|
||||||
|
S#state{type=Type, impl=Impl, exists=true, tab=V};
|
||||||
|
next_state(#state{impl=Impl}=S, _V, {call,_,destroy,[_Tab,?TAB,_Options]})
|
||||||
|
when Impl /= ets ->
|
||||||
|
S#state{tab=undefined, exists=false, objs=[]};
|
||||||
|
next_state(S, _V, {call,_,insert,[_Tab,Objs]}) when is_list(Objs) ->
|
||||||
|
insert_objs(S, Objs);
|
||||||
|
next_state(S, _V, {call,_,insert,[_Tab,Obj]}) ->
|
||||||
|
insert_objs(S, [Obj]);
|
||||||
|
next_state(#state{impl=ets}=S, _V, {call,_,insert_new,[_Tab,Objs]}) when is_list(Objs) ->
|
||||||
|
insert_new_objs(S, Objs);
|
||||||
|
next_state(#state{impl=ets}=S, _V, {call,_,insert_new,[_Tab,Obj]}) ->
|
||||||
|
insert_new_objs(S, [Obj]);
|
||||||
|
next_state(S, _V, {call,_,insert_new,[_Tab,_ObjOrObjs]}) ->
|
||||||
|
S;
|
||||||
|
next_state(#state{impl=ets}=S, _V, {call,_,delete,[_Tab]}) ->
|
||||||
|
S#state{tab=undefined, exists=false, objs=[]};
|
||||||
|
next_state(#state{exists=Exists}=S, _V, {call,_,delete,[_Tab]}) ->
|
||||||
|
S#state{tab=undefined, exists=Exists};
|
||||||
|
next_state(S, _V, {call,_,delete,[_Tab,Key]}) ->
|
||||||
|
S#state{objs=keydelete(Key, S)};
|
||||||
|
next_state(#state{impl=ets}=S, _V, {call,_,delete_all_objs,[_Tab]}) ->
|
||||||
|
S#state{objs=[]};
|
||||||
|
next_state(S, _V, {call,_,delete_all_objs,[_Tab]}) ->
|
||||||
|
S;
|
||||||
|
next_state(S, _V, {call,_,_,_}) ->
|
||||||
|
S.
|
||||||
|
|
||||||
|
-spec precondition(#state{}, tuple()) -> boolean().
|
||||||
|
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,new,[?TAB,Options]}) ->
|
||||||
|
L = proplists:get_value(db, Options, []),
|
||||||
|
proplists:get_bool(create_if_missing, L) andalso proplists:get_bool(error_if_exists, L);
|
||||||
|
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,new,[_Tab,?TAB,Options]}) ->
|
||||||
|
L = proplists:get_value(db, Options, []),
|
||||||
|
proplists:get_bool(create_if_missing, L) andalso proplists:get_bool(error_if_exists, L);
|
||||||
|
precondition(#state{tab=Tab}, {call,_,new,[?TAB,_Options]}) when Tab /= undefined ->
|
||||||
|
false;
|
||||||
|
precondition(#state{tab=Tab}, {call,_,new,[_Tab,?TAB,_Options]}) when Tab /= undefined ->
|
||||||
|
false;
|
||||||
|
precondition(_S, {call,_,_,_}) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
-spec postcondition(#state{}, tuple(), term()) -> boolean().
|
||||||
|
postcondition(#state{tab=undefined}, {call,_,new,[?TAB,_Options]}, Res) ->
|
||||||
|
?IMPL:is_table(Res);
|
||||||
|
postcondition(_S, {call,_,new,[?TAB,_Options]}, Res) ->
|
||||||
|
case Res of
|
||||||
|
{'EXIT', {badarg, _}} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
postcondition(#state{tab=undefined}, {call,_,new,[_Tab,?TAB,_Options]}, Res) ->
|
||||||
|
?IMPL:is_table(Res);
|
||||||
|
postcondition(_S, {call,_,destroy,[_Tab,?TAB,_Options]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(_S, {call,_,repair,[_Tab,?TAB,_Options]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(_S, {call,_,insert,[_Tab,_ObjOrObjs]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(#state{impl=ets}=S, {call,_,insert_new,[_Tab,Objs]}, Res) when is_list(Objs) ->
|
||||||
|
Res =:= has_insert_new_objs(S, Objs);
|
||||||
|
postcondition(#state{impl=ets}=S, {call,_,insert_new,[_Tab,Obj]}, Res) ->
|
||||||
|
Res =:= has_insert_new_objs(S, [Obj]);
|
||||||
|
postcondition(_S, {call,_,insert_new,[_Tab,_ObjOrObjs]}, {'EXIT',{badarg,_}}) ->
|
||||||
|
true;
|
||||||
|
postcondition(_S, {call,_,delete,[_Tab]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(_S, {call,_,delete,[_Tab,_Key]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(#state{impl=ets}=_S, {call,_,delete_all_objs,[_Tab]}, Res) ->
|
||||||
|
Res =:= true;
|
||||||
|
postcondition(_S, {call,_,delete_all_objs,[_Tab]}, {'EXIT',{badarg,_}}) ->
|
||||||
|
true;
|
||||||
|
postcondition(S, {call,_,lookup,[_Tab,Key]}, Res) ->
|
||||||
|
Res =:= keyfind(Key, S);
|
||||||
|
postcondition(#state{objs=[]}, {call,_,first,[_Tab]}, Res) ->
|
||||||
|
Res =:= '$end_of_table';
|
||||||
|
postcondition(#state{type=set}=S, {call,_,first,[_Tab]}, Res) ->
|
||||||
|
keymember(Res, S);
|
||||||
|
postcondition(#state{type=ordered_set}=S, {call,_,first,[_Tab]}, Res) ->
|
||||||
|
#obj{key=K} = hd(sort(S)),
|
||||||
|
Res =:= K;
|
||||||
|
postcondition(#state{impl=ets, type=set}=S, {call,_,next,[_Tab, Key]}, {'EXIT',{badarg,_}}) ->
|
||||||
|
not keymember(Key, S);
|
||||||
|
postcondition(#state{impl=ets, type=set}=S, {call,_,next,[_Tab, Key]}, '$end_of_table') ->
|
||||||
|
keymember(Key, S);
|
||||||
|
postcondition(#state{impl=ets, type=set}=S, {call,_,next,[_Tab, Key]}, Res) ->
|
||||||
|
keymember(Key, S) andalso keymember(Res, S);
|
||||||
|
postcondition(#state{type=set}, {call,_,next,[_Tab, _Key]}, '$end_of_table') ->
|
||||||
|
true;
|
||||||
|
postcondition(#state{type=set}=S, {call,_,next,[_Tab, _Key]}, Res) ->
|
||||||
|
keymember(Res, S);
|
||||||
|
postcondition(#state{type=ordered_set, objs=[]}, {call,_,next,[_Tab, _Key]}, Res) ->
|
||||||
|
Res =:= '$end_of_table';
|
||||||
|
postcondition(#state{type=ordered_set}=S, {call,_,next,[_Tab, Key]}, Res) ->
|
||||||
|
case lists:dropwhile(fun(#obj{key=X}) -> lteq(X, Key, S) end, sort(S)) of
|
||||||
|
[] ->
|
||||||
|
Res =:= '$end_of_table';
|
||||||
|
[#obj{key=K}|_] ->
|
||||||
|
Res =:= K
|
||||||
|
end;
|
||||||
|
postcondition(#state{type=set}=S, {call,_,tab2list,[_Tab]}, Res) ->
|
||||||
|
[] == (S#state.objs -- Res);
|
||||||
|
postcondition(#state{type=ordered_set}=S, {call,_,tab2list,[_Tab]}, Res) ->
|
||||||
|
sort(S) =:= Res;
|
||||||
|
postcondition(_S, {call,_,_,_}, _Res) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
-spec commands_setup(boolean()) -> {ok, term()}.
|
||||||
|
commands_setup(_Hard) ->
|
||||||
|
?IMPL:teardown(?TAB),
|
||||||
|
{ok, unused}.
|
||||||
|
|
||||||
|
-spec commands_teardown(term()) -> ok.
|
||||||
|
commands_teardown(unused) ->
|
||||||
|
?IMPL:teardown(?TAB),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-spec commands_teardown(term(), #state{}) -> ok.
|
||||||
|
commands_teardown(Ref, _State) ->
|
||||||
|
commands_teardown(Ref).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
gen_options(Op,#state{tab=undefined, type=undefined, impl=undefined}=S) ->
|
||||||
|
?LET({Type,Impl}, {gen_ets_type(), gen_ets_impl()},
|
||||||
|
gen_options(Op,S#state{type=Type, impl=Impl}));
|
||||||
|
gen_options(Op,#state{type=Type, impl=drv=Impl}=S) ->
|
||||||
|
[Type, public, named_table, {keypos,#obj.key}, Impl]
|
||||||
|
++ gen_leveldb_options(Op,S);
|
||||||
|
gen_options(Op,#state{type=Type, impl=nif=Impl}=S) ->
|
||||||
|
[Type, public, named_table, {keypos,#obj.key}, Impl]
|
||||||
|
++ gen_leveldb_options(Op,S);
|
||||||
|
gen_options(_Op,#state{type=Type, impl=ets=Impl}) ->
|
||||||
|
[Type, public, named_table, {keypos,#obj.key}, Impl].
|
||||||
|
|
||||||
|
gen_leveldb_options(Op,S) ->
|
||||||
|
[gen_db_options(Op,S), gen_db_read_options(Op,S), gen_db_write_options(Op,S)].
|
||||||
|
|
||||||
|
gen_db_options(new,#state{exists=Exists}) ->
|
||||||
|
ExistsOptions = if Exists -> []; true -> [create_if_missing, error_if_exists] end,
|
||||||
|
?LET(Options, ulist(gen_db_options()), {db, Options ++ ExistsOptions});
|
||||||
|
gen_db_options(_Op,_S) ->
|
||||||
|
?LET(Options, ulist(gen_db_options()), {db, Options}).
|
||||||
|
|
||||||
|
gen_db_read_options(_Op,_S) ->
|
||||||
|
?LET(Options, ulist(gen_db_read_options()), {db_read, Options}).
|
||||||
|
|
||||||
|
gen_db_write_options(_Op,_S) ->
|
||||||
|
?LET(Options, ulist(gen_db_write_options()), {db_write, Options}).
|
||||||
|
|
||||||
|
gen_db_options() ->
|
||||||
|
oneof([paranoid_checks, {paranoid_checks,gen_boolean()}, {write_buffer_size,gen_pos_integer()}, {max_open_files,gen_pos_integer()}, {block_cache_size,gen_pos_integer()}, {block_size,gen_pos_integer()}, {block_restart_interval,gen_pos_integer()}]).
|
||||||
|
|
||||||
|
gen_db_read_options() ->
|
||||||
|
oneof([verify_checksums, {verify_checksums,gen_boolean()}, fill_cache, {fill_cache,gen_boolean()}]).
|
||||||
|
|
||||||
|
gen_db_write_options() ->
|
||||||
|
oneof([sync, {sync,gen_boolean()}]).
|
||||||
|
|
||||||
|
gen_boolean() ->
|
||||||
|
oneof([true, false]).
|
||||||
|
|
||||||
|
gen_pos_integer() ->
|
||||||
|
nat().
|
||||||
|
|
||||||
|
gen_ets_type() ->
|
||||||
|
noshrink(oneof([set, ordered_set])).
|
||||||
|
|
||||||
|
gen_ets_impl() ->
|
||||||
|
%% @NOTE Remove one or two of these to restrict to a particular
|
||||||
|
%% implementation.
|
||||||
|
noshrink(oneof([drv,nif,ets])).
|
||||||
|
|
||||||
|
gen_integer_key() ->
|
||||||
|
oneof(?INT_KEYS).
|
||||||
|
|
||||||
|
gen_float_key() ->
|
||||||
|
oneof(?FLOAT_KEYS).
|
||||||
|
|
||||||
|
gen_binary_key() ->
|
||||||
|
oneof(?BINARY_KEYS).
|
||||||
|
|
||||||
|
gen_key() ->
|
||||||
|
frequency([{5, gen_integer_key()}, {1, gen_float_key()}, {1, gen_binary_key()}]).
|
||||||
|
|
||||||
|
gen_key(#state{objs=[]}) ->
|
||||||
|
gen_key();
|
||||||
|
gen_key(#state{objs=Objs}) ->
|
||||||
|
oneof([?LET(Obj, oneof(Objs), Obj#obj.key), gen_key()]).
|
||||||
|
|
||||||
|
gen_int_or_float_or_bin() ->
|
||||||
|
frequency([{5, int()}, {1, real()}, {1, binary()}]).
|
||||||
|
|
||||||
|
gen_val() ->
|
||||||
|
gen_int_or_float_or_bin().
|
||||||
|
|
||||||
|
gen_obj() ->
|
||||||
|
#obj{key=gen_key(), val=gen_val()}.
|
||||||
|
|
||||||
|
gen_obj(#state{objs=[]}) ->
|
||||||
|
gen_obj();
|
||||||
|
gen_obj(#state{objs=Objs}) ->
|
||||||
|
oneof([oneof(Objs), gen_obj()]).
|
||||||
|
|
||||||
|
gen_objs(S) ->
|
||||||
|
frequency([{9, non_empty(list(gen_obj(S)))}, {1, list(gen_obj(S))}]).
|
||||||
|
|
||||||
|
insert_objs(S, []) ->
|
||||||
|
S;
|
||||||
|
insert_objs(S, [#obj{key=K}=Obj|T]) ->
|
||||||
|
case keymember(K, S) of
|
||||||
|
false ->
|
||||||
|
insert_objs(S#state{objs=[Obj|S#state.objs]}, T);
|
||||||
|
true ->
|
||||||
|
insert_objs(S#state{objs=keyreplace(K, Obj, S)}, T)
|
||||||
|
end.
|
||||||
|
|
||||||
|
insert_new_objs(S, L) ->
|
||||||
|
insert_new_objs(S, lists:reverse(L), S).
|
||||||
|
|
||||||
|
insert_new_objs(S, [], _S0) ->
|
||||||
|
S;
|
||||||
|
insert_new_objs(S, [#obj{key=K}=Obj|T], S0) ->
|
||||||
|
case keymember(K, S) of
|
||||||
|
false ->
|
||||||
|
NewT = keydelete(K, T, S),
|
||||||
|
insert_new_objs(S#state{objs=[Obj|S#state.objs]}, NewT, S0);
|
||||||
|
true ->
|
||||||
|
S0
|
||||||
|
end.
|
||||||
|
|
||||||
|
has_insert_new_objs(S, L) ->
|
||||||
|
has_insert_new_objs(S, lists:reverse(L), true).
|
||||||
|
|
||||||
|
has_insert_new_objs(_S, [], Bool) ->
|
||||||
|
Bool;
|
||||||
|
has_insert_new_objs(S, [#obj{key=K}=Obj|T], _Bool) ->
|
||||||
|
case keymember(K, S) of
|
||||||
|
false ->
|
||||||
|
NewT = keydelete(K, T, S),
|
||||||
|
has_insert_new_objs(S#state{objs=[Obj|S#state.objs]}, NewT, true);
|
||||||
|
true ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
keydelete(X, #state{objs=L}=S) ->
|
||||||
|
keydelete(X, L, S).
|
||||||
|
|
||||||
|
keydelete(X, L, S) ->
|
||||||
|
lists:filter(fun(#obj{key=K}) -> neq(X, K, S) end, L).
|
||||||
|
|
||||||
|
keyreplace(X, Y, #state{objs=L}=S) ->
|
||||||
|
lists:map(fun(Z=#obj{key=K}) -> case eq(X, K, S) of true -> Y; false -> Z end end, L).
|
||||||
|
|
||||||
|
keyfind(X, #state{objs=L}=S) ->
|
||||||
|
lists:filter(fun(#obj{key=K}) -> eq(X, K, S) end, L).
|
||||||
|
|
||||||
|
keymember(X, S) ->
|
||||||
|
[] /= keyfind(X, S).
|
||||||
|
|
||||||
|
eq(X, Y, #state{type=set, impl=ets}) ->
|
||||||
|
X =:= Y;
|
||||||
|
eq(X, Y, #state{type=ordered_set, impl=ets}) ->
|
||||||
|
X == Y;
|
||||||
|
eq(X, Y, #state{type=set}) ->
|
||||||
|
term_to_binary(X) == term_to_binary(Y);
|
||||||
|
eq(X, Y, #state{type=ordered_set}) ->
|
||||||
|
sext:encode(X) == sext:encode(Y).
|
||||||
|
|
||||||
|
neq(X, Y, #state{type=set, impl=ets}) ->
|
||||||
|
X =/= Y;
|
||||||
|
neq(X, Y, #state{type=ordered_set, impl=ets}) ->
|
||||||
|
X /= Y;
|
||||||
|
neq(X, Y, #state{type=set}) ->
|
||||||
|
term_to_binary(X) /= term_to_binary(Y);
|
||||||
|
neq(X, Y, #state{type=ordered_set}) ->
|
||||||
|
sext:encode(X) /= sext:encode(Y).
|
||||||
|
|
||||||
|
lteq(X, Y, #state{impl=ets}) ->
|
||||||
|
X =< Y;
|
||||||
|
lteq(X, Y, #state{type=set}) ->
|
||||||
|
term_to_binary(X) =< term_to_binary(Y);
|
||||||
|
lteq(X, Y, #state{type=ordered_set}) ->
|
||||||
|
sext:encode(X) =< sext:encode(Y).
|
||||||
|
|
||||||
|
sort(#state{impl=ets, objs=L}) ->
|
||||||
|
lists:sort(L);
|
||||||
|
sort(#state{objs=L}) ->
|
||||||
|
[ sext:decode(X) || X <- lists:sort([ sext:encode(Y) || Y <- L ]) ].
|
||||||
|
|
||||||
|
-endif. %% -ifdef(QC).
|
237
test/qc/qc_statemc_lets.erl
Normal file
237
test/qc/qc_statemc_lets.erl
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
%%% The MIT License
|
||||||
|
%%%
|
||||||
|
%%% Copyright (C) 2011 by Joseph Wayne Norton <norton@alum.mit.edu>
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in
|
||||||
|
%%% all copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
%%% THE SOFTWARE.
|
||||||
|
|
||||||
|
-module(qc_statemc_lets).
|
||||||
|
|
||||||
|
-ifdef(QC).
|
||||||
|
-ifdef(EQC).
|
||||||
|
|
||||||
|
%% qc_statem Callbacks
|
||||||
|
-behaviour(qc_statem).
|
||||||
|
-export([command_gen/2]).
|
||||||
|
-export([initial_state/0, state_is_sane/1, next_state/3, precondition/2, postcondition/3]).
|
||||||
|
-export([commands_setup/1, commands_teardown/1, commands_teardown/2]).
|
||||||
|
|
||||||
|
%% @NOTE For boilerplate exports, see "qc_statem.hrl"
|
||||||
|
-include_lib("eqc/include/eqc_c.hrl").
|
||||||
|
-include_lib("qc/include/qc_statem.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% defines, types, records
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(IMPL, qc_leveldb).
|
||||||
|
|
||||||
|
-record(obj, {key :: binary(), val :: binary()}).
|
||||||
|
|
||||||
|
-type obj() :: #obj{}.
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
parallel=false :: boolean(),
|
||||||
|
exists=false :: boolean(),
|
||||||
|
db=undefined :: undefined | term(),
|
||||||
|
objs=[] :: [obj()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% qc_statem Callbacks
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
command_gen(Mod,#state{parallel=false}=S) ->
|
||||||
|
serial_command_gen(Mod,S);
|
||||||
|
command_gen(Mod,#state{parallel=true}=S) ->
|
||||||
|
parallel_command_gen(Mod,S).
|
||||||
|
|
||||||
|
serial_command_gen(_Mod,#state{db=undefined, exists=false}) ->
|
||||||
|
{call,?IMPL,open,[]};
|
||||||
|
serial_command_gen(_Mod,#state{db=undefined, exists=true}) ->
|
||||||
|
{call,?IMPL,reopen,[]};
|
||||||
|
serial_command_gen(_Mod,#state{db=Db}=S) ->
|
||||||
|
oneof([{call,?IMPL,close,[Db]},
|
||||||
|
{call,?IMPL,put,[Db,gen_obj(S)]},
|
||||||
|
{call,?IMPL,delete,[Db,gen_key(S)]},
|
||||||
|
{call,?IMPL,get,[Db,gen_key(S)]},
|
||||||
|
{call,?IMPL,first,[Db]},
|
||||||
|
{call,?IMPL,last,[Db]},
|
||||||
|
{call,?IMPL,next,[Db,gen_key(S)]}
|
||||||
|
]).
|
||||||
|
|
||||||
|
parallel_command_gen(_Mod,#state{db=undefined}) ->
|
||||||
|
{call,?IMPL,open,[]};
|
||||||
|
parallel_command_gen(_Mod,#state{db=Db}=S) ->
|
||||||
|
oneof([{call,?IMPL,put,[Db,gen_obj(S)]},
|
||||||
|
{call,?IMPL,delete,[Db,gen_key(S)]},
|
||||||
|
{call,?IMPL,get,[Db,gen_key(S)]}
|
||||||
|
]).
|
||||||
|
|
||||||
|
-spec initial_state() -> #state{}.
|
||||||
|
initial_state() ->
|
||||||
|
?LET(Parallel,parameter(parallel,false),
|
||||||
|
#state{parallel=Parallel}).
|
||||||
|
|
||||||
|
-spec state_is_sane(#state{}) -> boolean().
|
||||||
|
state_is_sane(_S) ->
|
||||||
|
%% @TODO
|
||||||
|
true.
|
||||||
|
|
||||||
|
-spec next_state(#state{}, term(), tuple()) -> #state{}.
|
||||||
|
next_state(#state{db=undefined, exists=false}=S, V, {call,_,open,[]}) ->
|
||||||
|
S#state{db=V, exists=true};
|
||||||
|
next_state(#state{db=undefined, exists=true}=S, V, {call,_,reopen,[]}) ->
|
||||||
|
S#state{db=V, exists=true};
|
||||||
|
next_state(#state{db=Db}=S, _V, {call,_,close,[Db]}) when Db /= undefined ->
|
||||||
|
S#state{db=undefined};
|
||||||
|
next_state(S, _V, {call,_,put,[_Db,Obj]}) ->
|
||||||
|
insert_obj(S, Obj);
|
||||||
|
next_state(S, _V, {call,_,delete,[_Db,Key]}) ->
|
||||||
|
delete_obj(S, Key);
|
||||||
|
next_state(S, _V, {call,_,_,_}) ->
|
||||||
|
S.
|
||||||
|
|
||||||
|
-spec precondition(#state{}, tuple()) -> boolean().
|
||||||
|
precondition(#state{exists=true}, {call,_,open,[]}) ->
|
||||||
|
false;
|
||||||
|
precondition(#state{exists=false}, {call,_,reopen,[]}) ->
|
||||||
|
false;
|
||||||
|
precondition(#state{db=Db}, {call,_,open,[]}) when Db /= undefined->
|
||||||
|
false;
|
||||||
|
precondition(#state{db=Db}, {call,_,reopen,[]}) when Db /= undefined->
|
||||||
|
false;
|
||||||
|
precondition(_S, {call,_,_,_}) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
-spec postcondition(#state{}, tuple(), term()) -> boolean().
|
||||||
|
postcondition(#state{exists=false}, {call,_,open,[]}, Res) ->
|
||||||
|
?IMPL:is_db(Res);
|
||||||
|
postcondition(#state{exists=true}, {call,_,reopen,[]}, Res) ->
|
||||||
|
?IMPL:is_db(Res);
|
||||||
|
postcondition(#state{db=Db}, {call,_,close,[_Db]}, Res) ->
|
||||||
|
Res andalso Db /= undefined;
|
||||||
|
postcondition(_S, {call,_,put,[_Db,_]}, Res) ->
|
||||||
|
Res;
|
||||||
|
postcondition(_S, {call,_,delete,[_Db,_]}, Res) ->
|
||||||
|
Res;
|
||||||
|
postcondition(S, {call,_,get,[_Db,Key]}, Res) ->
|
||||||
|
Res =:= get_val(S, Key);
|
||||||
|
postcondition(#state{objs=[]}, {call,_,first,[_Db]}, Res) ->
|
||||||
|
Res;
|
||||||
|
postcondition(S, {call,_,first,[_Db]}, Res) ->
|
||||||
|
#obj{key=K} = hd(sort_objs(S)),
|
||||||
|
Res =:= K;
|
||||||
|
postcondition(#state{objs=[]}, {call,_,last,[_Db]}, Res) ->
|
||||||
|
Res;
|
||||||
|
postcondition(S, {call,_,last,[_Db]}, Res) ->
|
||||||
|
#obj{key=K} = hd(lists:reverse(sort_objs(S))),
|
||||||
|
Res =:= K;
|
||||||
|
postcondition(S, {call,_,next,[_Db,Key]}, Res) ->
|
||||||
|
case lists:dropwhile(fun(#obj{key=X}) -> X =< Key end, sort_objs(S)) of
|
||||||
|
[] ->
|
||||||
|
Res;
|
||||||
|
[#obj{key=K}|_] ->
|
||||||
|
Res =:= K
|
||||||
|
end;
|
||||||
|
postcondition(_S, {call,_,_,_}, _Res) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
-spec commands_setup(boolean()) -> {ok, term()}.
|
||||||
|
commands_setup(_Hard) ->
|
||||||
|
?IMPL:setup(),
|
||||||
|
teardown(),
|
||||||
|
{ok, unused}.
|
||||||
|
|
||||||
|
-spec commands_teardown(term()) -> ok.
|
||||||
|
commands_teardown(unused) ->
|
||||||
|
teardown(),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-spec commands_teardown(term(), #state{}) -> ok.
|
||||||
|
commands_teardown(Ref, _State) ->
|
||||||
|
commands_teardown(Ref).
|
||||||
|
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
teardown() ->
|
||||||
|
?IMPL:teardown().
|
||||||
|
|
||||||
|
gen_bytes() ->
|
||||||
|
?LET(B, list(choose(0,127)), list_to_binary(B)).
|
||||||
|
|
||||||
|
gen_key() ->
|
||||||
|
gen_bytes().
|
||||||
|
|
||||||
|
gen_val() ->
|
||||||
|
gen_bytes().
|
||||||
|
|
||||||
|
gen_obj() ->
|
||||||
|
#obj{key=gen_key(), val=gen_val()}.
|
||||||
|
|
||||||
|
gen_key(#state{objs=[]}) ->
|
||||||
|
gen_key();
|
||||||
|
gen_key(#state{objs=Objs}) ->
|
||||||
|
oneof([?LET(Obj, oneof(Objs), Obj#obj.key), gen_key()]).
|
||||||
|
|
||||||
|
gen_obj(#state{objs=[]}) ->
|
||||||
|
gen_obj();
|
||||||
|
gen_obj(#state{objs=Objs}) ->
|
||||||
|
oneof([oneof(Objs), gen_obj()]).
|
||||||
|
|
||||||
|
insert_obj(S, #obj{key=K}=Obj) ->
|
||||||
|
case keymember(K, S) of
|
||||||
|
false ->
|
||||||
|
S#state{objs=[Obj|S#state.objs]};
|
||||||
|
true ->
|
||||||
|
S#state{objs=keyreplace(K, Obj, S)}
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete_obj(S, K) ->
|
||||||
|
S#state{objs=keydelete(K, S)}.
|
||||||
|
|
||||||
|
get_val(S, K) ->
|
||||||
|
case keyfind(K, S) of
|
||||||
|
[] ->
|
||||||
|
true;
|
||||||
|
[#obj{val=Val}] ->
|
||||||
|
Val
|
||||||
|
end.
|
||||||
|
|
||||||
|
sort_objs(#state{objs=Objs}) ->
|
||||||
|
lists:sort(Objs).
|
||||||
|
|
||||||
|
keydelete(X, #state{objs=L}) ->
|
||||||
|
lists:filter(fun(#obj{key=K}) -> K =/= X end, L).
|
||||||
|
|
||||||
|
keyreplace(X, Y, #state{objs=L}) ->
|
||||||
|
lists:map(fun(Z=#obj{key=K}) -> case K =:= X of true -> Y; false -> Z end end, L).
|
||||||
|
|
||||||
|
keyfind(X, #state{objs=L}) ->
|
||||||
|
lists:filter(fun(#obj{key=K}) -> K =:= X end, L).
|
||||||
|
|
||||||
|
keymember(X, S) ->
|
||||||
|
[] /= keyfind(X, S).
|
||||||
|
|
||||||
|
-endif. %% -ifdef(EQC).
|
||||||
|
-endif. %% -ifdef(QC).
|
Loading…
Reference in a new issue