diff --git a/README.md b/README.md
index de5a1b6..103c348 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,9 @@ This module is Perl5 binding for Unqlite.
If you want to know more information about Unqlite, see [http://unqlite.org/](http://unqlite.org/).
-Current version of Unqlite.pm supports only some `kv_*` methods. Patches welcome.
+This version of Unqlite.pm does not provides document store feature. Patches welcome.
+
+__You can use Unqlite.pm as DBM__.
# METHODS
@@ -48,6 +50,72 @@ Current version of Unqlite.pm supports only some `kv_*` methods. Patches welcome
This API returns stringified version of `$db->rc()`. It's not human readable but it's better than magic number.
+- `my $cursor = $db->cursor_init()`
+
+ Create new cursor object.
+
+# Unqlite::Cursor
+
+Unqlite supports cursor for iterating entries.
+
+Here is example code:
+
+ my $cursor = $db->cursor_init();
+ my @ret;
+ for ($cursor->first_entry; $cursor->valid_entry; $cursor->next_entry) {
+ push @ret, $cursor->key(), $cursor->data()
+ }
+
+## METHODS
+
+- `$cursor->first_entry()`
+
+ Seek cursor to first entry.
+
+ Return true if succeeded, false otherwise.
+
+- `$cursor->last_entry()`
+
+ Seek cursor to last entry.
+
+ Return true if succeeded, false otherwise.
+
+- `$cursor->valid_entry()`
+
+ This will return 1 when valid. 0 otherwise
+
+- `$cursor->key()`
+
+ Get current entry's key.
+
+- `$cursor->data()`
+
+ Get current entry's data.
+
+- `$cursor->next_entry()`
+
+ Seek cursor to next entry.
+
+- `$cursor->prev_entry()`
+
+ Seek cursor to previous entry.
+
+ Return true if succeeded, false otherwise.
+
+- `$cursor->seek($key, $opt=UNQLITE_CURSOR_MATCH_EXACT)`
+
+ Seek cursor to ` $key `.
+
+ You can specify the option as ` $opt `. Please see [http://unqlite.org/c\_api/unqlite\_kv\_cursor.html](http://unqlite.org/c\_api/unqlite\_kv\_cursor.html) for more details.
+
+ Return true if succeeded, false otherwise.
+
+- `$cursor->delete_entry()`
+
+ Delete the database entry pointed by the cursor.
+
+ Return true if succeeded, false otherwise.
+
# LICENSE
Copyright (C) tokuhirom.
diff --git a/lib/Unqlite.pm b/lib/Unqlite.pm
index db41563..389c41a 100644
--- a/lib/Unqlite.pm
+++ b/lib/Unqlite.pm
@@ -39,6 +39,63 @@ sub errstr {
if ($rc==UNQLITE_LOCKERR()) { return "UNQLITE_LOCKERR" }
}
+sub cursor_init {
+ my $self = shift;
+ bless [$self->_cursor_init(), $self], 'Unqlite::Cursor';
+}
+
+package Unqlite::Cursor;
+
+sub first_entry {
+ my $self = shift;
+ _first_entry($self->[0]);
+}
+
+sub key {
+ my $self = shift;
+ _key($self->[0]);
+}
+
+sub data {
+ my $self = shift;
+ _data ($self->[0]);
+}
+
+sub next_entry {
+ my $self = shift;
+ _next_entry($self->[0]);
+}
+
+sub valid_entry {
+ my $self = shift;
+ _valid_entry($self->[0]);
+}
+
+sub seek {
+ my $self = shift;
+ _seek($self->[0], @_);
+}
+
+sub delete_entry {
+ my $self = shift;
+ _delete_entry($self->[0]);
+}
+
+sub prev_entry {
+ my $self = shift;
+ _prev_entry($self->[0]);
+}
+
+sub last_entry {
+ my $self = shift;
+ _last_entry($self->[0]);
+}
+
+sub DESTROY {
+ my $self = shift;
+ _release($self->[0], $self->[1]);
+}
+
1;
__END__
@@ -66,7 +123,9 @@ This module is Perl5 binding for Unqlite.
If you want to know more information about Unqlite, see L.
-Current version of Unqlite.pm supports only some C methods. Patches welcome.
+This version of Unqlite.pm does not provides document store feature. Patches welcome.
+
+B.
=head1 METHODS
@@ -96,6 +155,76 @@ Return code from unqlite. It may updates after any Unqlite API call.
This API returns stringified version of C<< $db->rc() >>. It's not human readable but it's better than magic number.
+=item C<< my $cursor = $db->cursor_init() >>
+
+Create new cursor object.
+
+=back
+
+=head1 Unqlite::Cursor
+
+Unqlite supports cursor for iterating entries.
+
+Here is example code:
+
+ my $cursor = $db->cursor_init();
+ my @ret;
+ for ($cursor->first_entry; $cursor->valid_entry; $cursor->next_entry) {
+ push @ret, $cursor->key(), $cursor->data()
+ }
+
+=head2 METHODS
+
+=over 4
+
+=item C<< $cursor->first_entry() >>
+
+Seek cursor to first entry.
+
+Return true if succeeded, false otherwise.
+
+=item C<< $cursor->last_entry() >>
+
+Seek cursor to last entry.
+
+Return true if succeeded, false otherwise.
+
+=item C<< $cursor->valid_entry() >>
+
+This will return 1 when valid. 0 otherwise
+
+=item C<< $cursor->key() >>
+
+Get current entry's key.
+
+=item C<< $cursor->data() >>
+
+Get current entry's data.
+
+=item C<< $cursor->next_entry() >>
+
+Seek cursor to next entry.
+
+=item C<< $cursor->prev_entry() >>
+
+Seek cursor to previous entry.
+
+Return true if succeeded, false otherwise.
+
+=item C<< $cursor->seek($key, $opt=UNQLITE_CURSOR_MATCH_EXACT) >>
+
+Seek cursor to C< $key >.
+
+You can specify the option as C< $opt >. Please see L for more details.
+
+Return true if succeeded, false otherwise.
+
+=item C<< $cursor->delete_entry() >>
+
+Delete the database entry pointed by the cursor.
+
+Return true if succeeded, false otherwise.
+
=back
=head1 LICENSE
diff --git a/lib/Unqlite.xs b/lib/Unqlite.xs
index b0b4b30..b8d98c2 100644
--- a/lib/Unqlite.xs
+++ b/lib/Unqlite.xs
@@ -17,12 +17,11 @@ extern "C" {
#define XS_STATE(type, x) (INT2PTR(type, SvROK(x) ? SvIV(SvRV(x)) : SvIV(x)))
-/* #define XS_STRUCT2OBJ(sv, class, obj) if (obj == NULL) { sv_setsv(sv, &PL_sv_undef); } else { sv_setref_pv(sv, class, (void *) obj); } */
#define XS_STRUCT2OBJ(sv, class, obj) \
sv = newSViv(PTR2IV(obj)); \
- sv = newRV_noinc(sv); \
- sv_bless(sv, gv_stashpv(class, 1)); \
- SvREADONLY_on(sv);
+ sv = newRV_noinc(sv); \
+ sv_bless(sv, gv_stashpv(class, 1)); \
+ SvREADONLY_on(sv);
#define SETRC(rc) \
{ \
@@ -30,11 +29,6 @@ extern "C" {
SvIV_set(i, rc); \
}
-/*
- { HV * const stash = gv_stashpvn("Unqlite", strlen("Unqlite"), TRUE); \
- (void)hv_store(stash, "rc", 2, newSViv(rc), 0); } */
-
-
MODULE = Unqlite PACKAGE = Unqlite
PROTOTYPES: DISABLE
@@ -66,6 +60,10 @@ BOOT:
newCONSTSUB(stash, "UNQLITE_READ_ONLY", newSViv(UNQLITE_READ_ONLY));
newCONSTSUB(stash, "UNQLITE_LOCKERR", newSViv(UNQLITE_LOCKERR));
+ newCONSTSUB(stash, "UNQLITE_CURSOR_MATCH_EXACT", newSViv(UNQLITE_CURSOR_MATCH_EXACT));
+ newCONSTSUB(stash, "UNQLITE_CURSOR_MATCH_LE", newSViv(UNQLITE_CURSOR_MATCH_LE));
+ newCONSTSUB(stash, "UNQLITE_CURSOR_MATCH_GE", newSViv(UNQLITE_CURSOR_MATCH_GE));
+
SV*
open(klass, filename, mode=UNQLITE_OPEN_CREATE)
const char *klass;
@@ -200,3 +198,213 @@ CODE:
rc = unqlite_close(pdb);
SETRC(rc);
+SV*
+_cursor_init(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+ unqlite_kv_cursor* cursor;
+CODE:
+ unqlite *pdb = XS_STATE(unqlite*, self);
+ rc = unqlite_kv_cursor_init(pdb, &cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ sv = newSViv(PTR2IV(cursor));
+ sv = newRV_noinc(sv);
+ SvREADONLY_on(sv);
+ RETVAL = sv;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+
+MODULE = Unqlite PACKAGE = Unqlite::Cursor
+
+SV*
+_first_entry(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_first_entry(cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+int
+_valid_entry(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ /* This will return 1 when valid. 0 otherwise */
+ rc = unqlite_kv_cursor_valid_entry(cursor);
+ RETVAL = rc;
+OUTPUT:
+ RETVAL
+
+SV*
+_next_entry(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_next_entry(cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+SV*
+_last_entry(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_last_entry(cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+SV*
+_prev_entry(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_prev_entry(cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+SV*
+_key(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+ int nbytes;
+ char*buf;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_key(cursor, NULL, &nbytes);
+ SETRC(rc);
+ if (rc!=UNQLITE_OK) {
+ RETVAL = &PL_sv_undef;
+ goto last;
+ }
+ Newxz(buf, nbytes, char);
+ rc = unqlite_kv_cursor_key(cursor, buf, &nbytes);
+ SETRC(rc);
+ sv = newSVpv(buf, nbytes);
+ Safefree(buf);
+ RETVAL = sv;
+ last:
+OUTPUT:
+ RETVAL
+
+SV*
+_data(self)
+ SV * self;
+PREINIT:
+ SV * sv;
+ int rc;
+ unqlite_int64 nbytes;
+ char*buf;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_data(cursor, NULL, &nbytes);
+ SETRC(rc);
+ if (rc!=UNQLITE_OK) {
+ RETVAL = &PL_sv_undef;
+ goto last;
+ }
+ Newxz(buf, nbytes, char);
+ rc = unqlite_kv_cursor_data(cursor, buf, &nbytes);
+ SETRC(rc);
+ sv = newSVpv(buf, nbytes);
+ Safefree(buf);
+ RETVAL = sv;
+ last:
+OUTPUT:
+ RETVAL
+
+void
+_release(self, db)
+ SV * self;
+ SV * db;
+CODE:
+ unqlite *pdb = XS_STATE(unqlite*, db);
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ unqlite_kv_cursor_release(pdb, cursor);
+
+SV*
+_seek(self, key_s, opt=UNQLITE_CURSOR_MATCH_EXACT)
+ SV * self;
+ SV * key_s;
+ int opt;
+PREINIT:
+ STRLEN len;
+ char * key;
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ key = SvPV(key_s, len);
+ rc = unqlite_kv_cursor_seek(cursor, key, len, opt);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
+
+SV*
+_delete_entry(self)
+ SV * self;
+PREINIT:
+ int rc;
+CODE:
+ unqlite_kv_cursor *cursor = XS_STATE(unqlite_kv_cursor*, self);
+ rc = unqlite_kv_cursor_delete_entry(cursor);
+ SETRC(rc);
+ if (rc == UNQLITE_OK) {
+ RETVAL = &PL_sv_yes;
+ } else {
+ RETVAL = &PL_sv_undef;
+ }
+OUTPUT:
+ RETVAL
diff --git a/t/02_cursor.t b/t/02_cursor.t
new file mode 100644
index 0000000..c5d0b5b
--- /dev/null
+++ b/t/02_cursor.t
@@ -0,0 +1,63 @@
+use strict;
+use Test::More;
+
+use File::Temp qw(tempdir);
+use Unqlite;
+
+my $tmp = tempdir( CLEANUP => 1 );
+
+my $db = Unqlite->open("$tmp/foo.db");
+{
+ isa_ok($db, 'Unqlite');
+
+ ok($db->kv_store("foo", "bar"));
+ ok($db->kv_store("hoge", "fuga"));
+}
+
+{
+ my $cursor = $db->cursor_init();
+ $cursor->first_entry;
+ is($cursor->valid_entry(), 1);
+ is($cursor->key(), 'hoge');
+ is($cursor->data(), 'fuga');
+ ok($cursor->next_entry());
+ is($cursor->valid_entry(), 1);
+ is($cursor->key(), 'foo');
+ is($cursor->data(), 'bar');
+ ok(!$cursor->next_entry());
+ is($cursor->valid_entry(), 0);
+}
+
+{
+ my $cursor = $db->cursor_init();
+ my @ret;
+ for ($cursor->first_entry; $cursor->valid_entry; $cursor->next_entry) {
+ push @ret, $cursor->key(), $cursor->data()
+ }
+ is_deeply(\@ret, [qw(hoge fuga foo bar)]);
+}
+
+{
+ my $cursor = $db->cursor_init();
+ $cursor->last_entry;
+ is($cursor->valid_entry(), 1);
+ is($cursor->key(), 'foo');
+ is($cursor->data(), 'bar');
+ ok($cursor->prev_entry());
+ is($cursor->valid_entry(), 1);
+ is($cursor->key(), 'hoge');
+ is($cursor->data(), 'fuga');
+ ok(!$cursor->prev_entry());
+ is($cursor->valid_entry(), 0);
+}
+
+{
+ my $cursor = $db->cursor_init();
+ ok(!$cursor->seek("NON EXISTENT"));
+ ok($cursor->seek("foo"));
+ is($cursor->valid_entry(), 1);
+ $cursor->delete_entry();
+}
+
+done_testing;
+