cursor support
This commit is contained in:
parent
9156d52871
commit
15245d2e1c
4 changed files with 479 additions and 11 deletions
70
README.md
70
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.
|
||||
|
|
131
lib/Unqlite.pm
131
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<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
|
||||
|
|
220
lib/Unqlite.xs
220
lib/Unqlite.xs
|
@ -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
63
t/02_cursor.t
Normal 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;
|
||||
|
Loading…
Add table
Reference in a new issue