Added documentation and test for the IDZebra::Resultset object
[idzebra-moved-to-github.git] / perl / lib / IDZebra / Session.pm
index 1a395f0..74b6b80 100644 (file)
@@ -1,18 +1,22 @@
-#!/usr/bin/perl
-# ============================================================================
+# $Id: Session.pm,v 1.9 2003-03-03 12:14:27 pop Exp $
+# 
 # Zebra perl API header
 # =============================================================================
-use strict;
-# ============================================================================
 package IDZebra::Session;
-use IDZebra;
-use IDZebra::Logger qw(:flags :calls);
-#use IDZebra::Repository;
-use IDZebra::Resultset;
-use Scalar::Util;
-use Carp;
+
 use strict;
-our @ISA = qw(IDZebra::Logger);
+use warnings;
+
+
+BEGIN {
+    use IDZebra;
+    use Scalar::Util;
+    use IDZebra::Logger qw(:flags :calls);
+    use IDZebra::Resultset;
+    use IDZebra::RetrievalRecord;
+    our $VERSION = do { my @r = (q$Revision: 1.9 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; 
+#    our @ISA = qw(IDZebra::Logger);
+}
 
 1;
 # -----------------------------------------------------------------------------
@@ -26,6 +30,7 @@ sub new {
     
     bless ($self, $class);
     $self->{cql_ct} = undef;
+    $self->{cql_mapfile} = "";
     return ($self);
 
     $self->{databases} = {};
@@ -79,7 +84,7 @@ sub open {
     $self->{rscount} = 0;
 
     # This is needed in order to somehow initialize the service
-    $self->select_databases("Default");
+    $self->databases("Default");
 
     # Load the default configuration
     $self->group(%args);
@@ -90,10 +95,25 @@ sub open {
     return ($self);
 }
 
+sub checkzh {
+    my ($self) = @_;
+    unless (defined($self->{zh})) {
+       croak ("Zebra session is not opened");
+    }
+}
+
 sub close {
     my ($self) = @_;
 
     if ($self->{zh}) {
+
+       my $stats = 0; 
+       # Delete all resulsets
+       my $r = IDZebra::deleteResultSet($self->{zh},
+                                        1, #Z_DeleteRequest_all,
+                                        0,[],
+                                        $stats);
+
        while (IDZebra::trans_no($self->{zh}) > 0) {
            logf (LOG_WARN,"Explicitly closing transaction with session");
            $self->end_trans;
@@ -126,12 +146,14 @@ sub DESTROY {
     if (defined ($self->{cql_ct})) {
       IDZebra::cql_transform_close($self->{cql_ct});
     }
+
 }
 # -----------------------------------------------------------------------------
 # Record group selection  This is a bit nasty... but used at many places 
 # -----------------------------------------------------------------------------
 sub group {
     my ($self,%args) = @_;
+    $self->checkzh;
     if ($#_ > 0) {
        $self->{rg} = $self->_makeRecordGroup(%args);
        $self->_selectRecordGroup($self->{rg});
@@ -141,6 +163,7 @@ sub group {
 
 sub selectRecordGroup {
     my ($self, $groupName) = @_;
+    $self->checkzh;
     $self->{rg} = $self->_getRecordGroup($groupName);
     $self->_selectRecordGroup($self->{rg});
 }
@@ -190,7 +213,7 @@ sub _getRecordGroup {
     my $r = IDZebra::init_recordGroup($rg);
     $rg->{groupName} = $groupName if ($groupName ne "");  
     $ext = "" unless ($ext);
-    my $r = IDZebra::res_get_recordGroup($self->{zh}, $rg, $ext);
+    $r = IDZebra::res_get_recordGroup($self->{zh}, $rg, $ext);
     return ($rg);
 }
 
@@ -238,22 +261,35 @@ sub _selectRecordGroup {
     unless ($dbName = $rg->{databaseName}) {
        $dbName = 'Default';
     }
-    if ($self->select_databases($dbName)) {
+    unless ($self->databases($dbName)) {
        croak("Fatal error selecting database $dbName");
     }
 }
 # -----------------------------------------------------------------------------
 # Selecting databases for search (and also for updating - internally)
 # -----------------------------------------------------------------------------
-sub select_databases {
+sub databases {
     my ($self, @databases) = @_;
 
+    $self->checkzh;
+
+    unless ($#_ >0) {
+       return (keys(%{$self->{databases}}));
+    }
+
+    my %tmp;
+
     my $changed = 0;
     foreach my $db (@databases) {
        next if ($self->{databases}{$db});
+       $tmp{$db}++;
        $changed++;
     }
 
+    foreach my $db (keys (%{$self->{databases}})) {
+       $changed++ unless ($tmp{$db});
+    }
+
     if ($changed) {
 
        delete ($self->{databases});
@@ -261,19 +297,19 @@ sub select_databases {
            $self->{databases}{$db}++;
        }
 
-       if (my $res = IDZebra::select_databases($self->{zh}, 
+       if (IDZebra::select_databases($self->{zh}, 
                                                ($#databases + 1), 
                                                \@databases)) {
            logf(LOG_FATAL, 
                 "Could not select database(s) %s errCode=%d",
                 join(",",@databases),
                 $self->errCode());
-           return ($res);
+           return (0);
        } else {
            logf(LOG_LOG,"Database(s) selected: %s",join(",",@databases));
        }
     }
-    return (0);
+    return (keys(%{$self->{databases}}));
 }
 
 # -----------------------------------------------------------------------------
@@ -299,11 +335,13 @@ sub errAdd {
 # -----------------------------------------------------------------------------
 sub begin_trans {
     my ($self) = @_;
+    $self->checkzh;
     IDZebra::begin_trans($self->{zh});
 }
 
 sub end_trans {
     my ($self) = @_;
+    $self->checkzh;
     my $stat = IDZebra::ZebraTransactionStatus->new();
     IDZebra::end_trans($self->{zh}, $stat);
     return ($stat);
@@ -311,22 +349,26 @@ sub end_trans {
 
 sub begin_read {
     my ($self) =@_;
+    $self->checkzh;
     return(IDZebra::begin_read($self->{zh}));
 }
 
 sub end_read {
     my ($self) =@_;
+    $self->checkzh;
     IDZebra::end_read($self->{zh});
 }
 
 sub shadow_enable {
     my ($self, $value) = @_;
+    $self->checkzh;
     if ($#_ > 0) { IDZebra::set_shadow_enable($self->{zh},$value); }
     return (IDZebra::get_shadow_enable($self->{zh}));
 }
 
 sub commit {
     my ($self) = @_;
+    $self->checkzh;
     if ($self->shadow_enable) {
        return(IDZebra::commit($self->{zh}));
     }
@@ -348,16 +390,19 @@ sub odr_reset {
 # -----------------------------------------------------------------------------
 sub init {
     my ($self) = @_;
+    $self->checkzh;
     return(IDZebra::init($self->{zh}));
 }
 
 sub compact {
     my ($self) = @_;
+    $self->checkzh;
     return(IDZebra::compact($self->{zh}));
 }
 
 sub update {
     my ($self, %args) = @_;
+    $self->checkzh;
     my $rg = $self->_update_args(%args);
     $self->_selectRecordGroup($rg);
     $self->begin_trans;
@@ -368,6 +413,7 @@ sub update {
 
 sub delete {
     my ($self, %args) = @_;
+    $self->checkzh;
     my $rg = $self->_update_args(%args);
     $self->_selectRecordGroup($rg);
     $self->begin_trans;
@@ -378,6 +424,7 @@ sub delete {
 
 sub show {
     my ($self, %args) = @_;
+    $self->checkzh;
     my $rg = $self->_update_args(%args);
     $self->_selectRecordGroup($rg);
     $self->begin_trans;
@@ -399,12 +446,14 @@ sub _update_args {
 
 sub update_record {
     my ($self, %args) = @_;
+    $self->checkzh;
     return(IDZebra::update_record($self->{zh},
                                  $self->_record_update_args(%args)));
 }
 
 sub delete_record {
     my ($self, %args) = @_;
+    $self->checkzh;
     return(IDZebra::delete_record($self->{zh},
                                  $self->_record_update_args(%args)));
 }
@@ -422,9 +471,9 @@ sub _record_update_args {
        $buff = $args{data};
     } 
     elsif ($args{file}) {
-       open (F, $args{file}) || warn ("Cannot open $args{file}");
+       CORE::open (F, $args{file}) || warn ("Cannot open $args{file}");
        $buff = join('',(<F>));
-       close (F);
+       CORE::close (F);
     }
     my $len = length($buff);
 
@@ -447,7 +496,6 @@ sub _record_update_args {
 
     $rg->{databaseName} = "Default" unless ($rg->{databaseName});
 
-#    print STDERR "$rectype,$sysno,$match,$fname,$len\n";
     unless ($rectype) {
        $rectype="";
     }
@@ -480,9 +528,11 @@ sub cql2pqf {
     }
     my $res = "\0" x 2048;
     my $r = IDZebra::cql2pqf($self->{cql_ct}, $cqlquery, $res, 2048);
-    unless ($r) {return (undef)};
+    if ($r) {
+       carp ("Error transforming CQL query: '$cqlquery', status:$r");
+    }
     $res=~s/\0.+$//g;
-    return ($res); 
+    return ($res,$r); 
 }
 
 
@@ -492,6 +542,8 @@ sub cql2pqf {
 sub search {
     my ($self, %args) = @_;
 
+    $self->checkzh;
+
     if ($args{cqlmap}) { $self->cqlmap($args{cqlmap}); }
 
     my $query;
@@ -499,17 +551,33 @@ sub search {
        $query = $args{pqf};
     }
     elsif ($args{cql}) {
-       unless ($query = $self->cql2pqf($args{cql})) {
-           croak ("Invalid CQL query: '$args{cql}'");
+       my $cqlstat;
+       ($query, $cqlstat) =  $self->cql2pqf($args{cql});
+       unless ($query) {
+           croak ("Failed to transform query: '$args{cql}', ".
+                  "status: ($cqlstat)");
        }
     }
     unless ($query) {
        croak ("No query given to search");
     }
 
+    my @origdbs;
+
+    if ($args{databases}) {
+       @origdbs = $self->databases;
+       $self->databases(@{$args{databases}});
+    }
+
     my $rsname = $args{rsname} ? $args{rsname} : $self->_new_setname;
 
-    return ($self->_search_pqf($query, $rsname));
+    my $rs = $self->_search_pqf($query, $rsname);
+
+    if ($args{databases}) {
+       $self->databases(@origdbs);
+    }
+
+    return ($rs);
 }
 
 sub _new_setname {
@@ -534,15 +602,6 @@ sub _search_pqf {
     return($rs);
 }
 
-sub search_cql {
-    my ($self, $query, $transfile) = @_;
-}
-
-
-sub search_ccl {
-    my ($self, $query, $transfile) = @_;
-}
-
 # -----------------------------------------------------------------------------
 # Sort
 #
@@ -552,6 +611,8 @@ sub search_ccl {
 sub sortResultsets {
     my ($self, $sortspec, $setname, @sets) = @_;
 
+    $self->checkzh;
+
     my @setnames;
     my $count = 0;
     foreach my $rs (@sets) {
@@ -580,6 +641,8 @@ sub sortResultsets {
     return ($rs);
 }
 
+# ============================================================================
+
 
 __END__
 
@@ -592,8 +655,29 @@ IDZebra::Session - A Zebra database server session for update and retrieval
   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
   $sess->open();
 
-  $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg');
+  $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
+                                groupName  => 'demo1');
+
+  $sess->group(groupName => 'demo2');
+
+  $sess->init();
+
+  $sess->begin_trans;
+
+  $sess->update(path      =>  'lib');
+
+  my $s1=$sess->update_record(data       => $rec1,
+                             recordType => 'grs.perl.pod',
+                             groupName  => "demo1",
+                             );
+
+  my $stat = $sess->end_trans;
 
+  $sess->databases('demo1','demo2');
+
+  my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
+                         cql       => 'dc.title=IDZebra',
+                         databases => [qw(demo1 demo2)]);
   $sess->close;
 
 =head1 DESCRIPTION
@@ -651,7 +735,7 @@ This will select the named record group, and load the corresponding settings fro
 
 =item B<databaseName>
 
-The name of the (logical) database the updated records will belong to.
+The name of the (logical) database the updated records will belong to. 
 
 =item B<path>
 
@@ -777,8 +861,60 @@ Don't try this at home! This case, the record identifier string (which is normal
 
 B<Important:> Note, that one record can be updated only once within a transaction - all subsequent updates are skipped. 
 
+=head1 DATABASE SELECTION
+
+Within a zebra repository you can define logical databases. You can either do this by record groups, or by providing the databaseName argument for update methods. For each record the database name it belongs to is stored. 
+
+For searching, you can select databases by calling:
+
+  $sess->databases('db1','db2');
+
+This will not do anything if the given and only the given databases are already selected. You can get the list of the actually selected databases, by calling:
+  
+  @dblist = $sess->databases();
+
 =head1 SEARCHING
 
+It's nice to be able to store data in your repository... But it's useful to reach it as well. So this is how to do searching:
+
+  $rs = $sess->search(databases => [qw(demo1,demo2)], # optional
+                      pqf       => '@attr 1=4 computer');
+
+This is going to execute a search in databases demo1 and demo2, for title 'com,puter'. This is a PQF (Prefix Query Format) search, see YAZ documentation for details. The database selection is optional: if it's provided, the given list of databases is selected for this particular search, then the original selection is restored.
+
+=head2 CCL searching
+
+Not all users enjoy typing in prefix query structures and numerical attribute values, even in a minimalistic test client. In the library world, the more intuitive Common Command Language (or ISO 8777) has enjoyed some popularity - especially before the widespread availability of graphical interfaces. It is still useful in applications where you for some reason or other need to provide a symbolic language for expressing boolean query structures. 
+
+The CCL searching is not currently supported by this API.
+
+=head2 CQL searching
+
+CQL - Common Query Language - was defined for the SRW protocol. In many ways CQL has a similar syntax to CCL. The objective of CQL is different. Where CCL aims to be an end-user language, CQL is the protocol query language for SRW. 
+
+In order to map CQL queries to Zebra internal search structures, you have to define a mapping, the way it is described in YAZ documentation: I<Specification of CQL to RPN mapping>. The mapping is interpreted by the method:
+
+  $sess->cqlmap($mapfile);
+
+Or, you can directly provide the I<mapfile> parameter for the search:
+
+  my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
+                         cql       => 'dc.title=IDZebra');
+
+As you see, CQL searching is so simple: just give the query in the I<cql> parameter.
+
+=head1 RESULTSETS
+
+As you have seen, the result of the search request is a I<Resultset> object.
+It contains number of hits, and search status, and can be used to sort and retrieve the resulting records.
+
+  $count = $rs->count;
+
+  printf ("RS Status is %d (%s)\n", $rs->errCode, $rs->errString);
+
+I<$rs-E<gt>errCode> is 0, if there were no errors during search. Read the I<IDZebra::Resultset> manpage for more details.
+
+=head1 MISC FUNCTIONS
 
 =head1 COPYRIGHT