cursor support

This commit is contained in:
tokuhirom 2013-07-04 03:47:37 +09:00
parent 9156d52871
commit 15245d2e1c
4 changed files with 479 additions and 11 deletions

View file

@ -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.

View file

@ -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<http://unqlite.org/>.
Current version of Unqlite.pm supports only some C<kv_*> methods. Patches welcome.
This version of Unqlite.pm does not provides document store feature. Patches welcome.
B<You can use Unqlite.pm as DBM>.
=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<http://unqlite.org/c_api/unqlite_kv_cursor.html> 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

View file

@ -17,7 +17,6 @@ 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); \
@ -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

63
t/02_cursor.t Normal file
View file

@ -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;