Boulder::Store(3pm)

๐Ÿ“ฆ
version 1.30 ยท 4 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

๐Ÿ’พ Boulder::Store โ€” the vibe โœจ

okay so bestie, welcome to Boulder::Store, aka Simple persistent storage for Stone tag/value objects ๐Ÿซถ she's a Perl module (the 3pm in Boulder::Store(3pm) is just her little manual-page badge, iykyk) and she is so ready to hold your Stones for you. literally obsessed. ๐Ÿ’…


โœจ SYNOPSIS โœจ

Boulder:Store;

my $store=new Boulder::Store('test.db',1);
my $s = new Stone (Name=>'george',
                   Age=>23,
                   Sex=>M,
                   Address=>{
                          Street=>'29 Rockland drive',
                          Town=>'Fort Washington',
                          ZIP=>'77777'
                          }
                      );
$store->put($s);
$store->put(new Stone(Name=>'fred',
                      Age=>30,
                      Sex=>M,
                      Address=>{
                                  Street=>'19 Gravel Path',
                                  Town=>'Bedrock',
                                  ZIP=>'12345'},
                      Phone=>{
                                Day=>'111-1111',
                                Eve=>'222-2222'
                                }
                            ));
$store->put(new Stone(Name=>'andrew',
                      Age=>18,
                      Sex=>M));

$store->add_index('Name');

my $stone = $store->get(0);
print "name = ",$stone->Name;

๐ŸŒธ DESCRIPTION ๐ŸŒธ

so Boulder::Store gives you persistent storage for Boulder objects using a simple DB_File implementation. no cap, it's simple on purpose. ๐ŸŽ€

to use her you need to have Berkeley db installed (she also answers to libdb, same girl different name ๐Ÿ’) plus the Perl DB_File module. if you don't already have Berkeley db, go peek at the DB_File package โ€” it's got the details on how to get it. ๐Ÿฆ‹

she also comes with an unsophisticated query mechanism that takes advantage of indexes you specify. and bestie, the way that despite her lack of sophistication the query system is often very helpful? iconic. she's not fancy, she just works. periodt. โœจ


๐Ÿ‘‘ CLASS METHODS ๐Ÿ‘‘

  • $store = Boulder::Store->new("database/path",$writable) โ€” the new() method creates a brand new Boulder::Store object and pairs her up with the database file you hand her in the first parameter. and yes, undef is a totally valid pathname โ€” in that case all the methods still work, the data just isn't stored anywhere (ghost store, main character energy ๐Ÿซฅ). the second parameter should be a true value if you want the database opened for writing; otherwise it's opened read only. ๐Ÿ”’

bc the underlying storage implementation is not multi-user, only one process can have the database for writing at a time. there's an fcntl()-based locking mechanism doing the heavy lifting, giving a process that has the database open for writing exclusive access to it. this also stops the database from being opened for reading while another process is writing to it โ€” and that's a good thing, we love data integrity in this house. ๐Ÿ’– multiple processes can open the database read only at the same time, no drama.

physically, the data lives in a human-readable file with the extension ".data". cute and legible, we stan. ๐Ÿ“„


๐Ÿฆ‹ OBJECT METHODS ๐Ÿฆ‹

  • $stone = $store->read_record(@taglist) โ€” the semantics of this call are exactly the same as in Boulder::Stream, fr fr. Stones come back in sequential order, starting with the first record. on top of their built-in tags, every stone you get back from this call carries an extra tag called "record_no" โ€” that's the zero-based record number of the stone in the database. wanna start over from the beginning? use the reset() method. ๐Ÿ”

if you call read_record() in an array context, it returns a list of all the stones in the database that contain one or more of the tags you provided. slay.

  • $stone = $store->write_record($stone [,$index]) โ€” same semantics as Boulder::Stream. a stone gets appended to the end of the database. if it works, this call returns the record number of the new entry. ๐ŸŽ‰ and if you pass that optional second parameter, you get to control where the stone lands: a positive numeric index writes the stone into the database at that position, and a value of -1 uses the Stone's internal record number (if it has one) to figure out where to place it. ๐Ÿ’…
  • $stone = $store->get($record_no) โ€” this is your random access to the database. give her a record number and this call returns the stone stored at that position. no waiting, just grab and go. โšก
  • $record_number = $store->put($stone,$record_no) โ€” and this is the random write. give her a record number and this call stores the stone at the indicated position, replacing whatever was there before. out with the old, in with the new. ๐Ÿ™ˆ

if you don't provide a record number, this call will look for a 'record_no' tag inside the stone itself and put it back in that position. so you can pull a stone out of the database, modify it, and put it right back without ever stressing about its record number. iconic behaviour honestly. if no record is found in the stone, then the effect is identical to write_record(). ๐Ÿช„

the record number of the inserted stone is returned from this call, or -1 if an error occurred. ๐Ÿ’€

  • $store->delete($stone), Boulder::Store::delete($record_no) โ€” these delete a stone from the database. you can hand her either the record number or a stone containing the 'record_no' tag.
โš ๏ธ warning bestie: if the database is heavily indexed, deletes can be time-consuming โ€” it requires the index to be brought back into synch. ๐Ÿ˜ญ
  • $record_count = $store->length() โ€” this returns the length of the database, in records. simple. clean. perfect. โœจ
  • $store->reset() โ€” this resets the database, nullifying any queries in effect and causing read_record() to begin fetching stones from the first record again. fresh start energy. ๐ŸŒˆ
  • $store->query(%query_array) โ€” this creates a query on the database, used for selecting stones in read_record(). the query is an associative array, and three types of key/value pairs are allowed:

(1) $index=>$value โ€” tells Boulder::Store to look for stones containing the specified tags in which the tag's value (determined by the Stone index() method) exactly matches the provided value. example:

$db->query('STS.left_primer.length'=>30);

only the non-bracketed forms of the index string are allowed โ€” and yeah, this is probably a bug... ๐Ÿ™ˆ if the tag path was declared to be an index, then this search will be fast. otherwise Boulder::Store has to iterate over every record in the database. ๐Ÿ˜ฎโ€๐Ÿ’จ

(2) EVAL=>'expression' โ€” tells Boulder::Store to look for stones in which the provided expression evaluates to true. when the expression is evaluated, the variable $s gets set to the current record's stone. and as a shortcut, you can use "<index.string>" as shorthand for "$s->index('index.string')". ๐Ÿงš

(3) EVAL=>['expression1','expression2','expression3'...] โ€” this lets you provide a whole bunch of expressions, and it's exactly equivalent to:

EVAL=>'(expression1) && (expression2) && (expression3)'

you can mix query types in the parameter you give to query(). for example, here's how to look up all stones in which the sex is male and the age is greater than 30:

$db->query('sex'=>'M',EVAL=>'<age> > 30');

when a query is in effect, read_record() returns only Stones that satisfy the query. in an array context, read_record() returns a list of all Stones that satisfy the query. and when no more satisfactory Stones are found, read_record() returns undef until a new query is entered or reset() is called. she's patient like that. ๐Ÿ’œ

  • $store->add_index(@indices) โ€” declare one or more tag paths to be part of a fast index. read_record() will take advantage of this record when processing queries. example:
$db->add_index('age','sex','person.pets');

you can add indexes any time you like โ€” when the database is first created, or later. but there's a trade off: write_record(), put(), and other data-modifying calls will become slower as more indexes are added. ๐Ÿ‘‘

the index is stored in an external file with the extension ".index". an index file is created even if you haven't indexed any tags. noted. ๐Ÿ“

  • $store->reindex_all() โ€” call this if the index gets screwed up (or lost). it rebuilds it from scratch. glow-up, but for indexes. โœจ

๐Ÿ˜ญ CAVEATS ๐Ÿ˜ญ

Boulder::Store makes heavy use of the flock() call in order to avoid corruption of DB_File databases when multiple processes try to write simultaneously.

โš ๏ธ but here's the tea: flock() may not work correctly across NFS mounts, particularly on Linux machines that are not running the rpc.lockd daemon. please confirm that your flock() works across NFS before attempting to use Boulder::Store. and if the store.t test hangs during testing, this is the likely culprit. ๐Ÿ’€

๐ŸŽ€ AUTHOR ๐ŸŽ€

Lincoln D. Stein <lstein@cshl.org>, Cold Spring Harbor Laboratory, Cold Spring Harbor, NY.

this module can be used and distributed on the same terms as Perl itself. ๐Ÿซถ


๐ŸŒˆ SEE ALSO ๐ŸŒˆ

  • Boulder
  • Boulder::Stream
  • Stone

and that's the whole page bestie!! ๐Ÿ’– this one shipped with perl v5.34.0 and is dated 2022-06-08 โ€” so go forth, store your Stones, and remember: one writer at a time, and check your flock() across NFS. mwah. ๐Ÿ’…โœจ๐ŸŒธ

๐Ÿ“ฆ where this page comes from