From 15245d2e1c120b124e89a946b63e8b3c526f7089 Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Thu, 4 Jul 2013 03:47:37 +0900 Subject: [PATCH] cursor support --- README.md | 70 ++++++++++++++- lib/Unqlite.pm | 131 +++++++++++++++++++++++++++- lib/Unqlite.xs | 226 +++++++++++++++++++++++++++++++++++++++++++++++-- t/02_cursor.t | 63 ++++++++++++++ 4 files changed, 479 insertions(+), 11 deletions(-) create mode 100644 t/02_cursor.t 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; +