Added test for transactions
[idzebra-moved-to-github.git] / perl / lib / IDZebra / Session.pm
1 # $Id: Session.pm,v 1.12 2003-03-05 00:28:16 pop Exp $
2
3 # Zebra perl API header
4 # =============================================================================
5 package IDZebra::Session;
6
7 use strict;
8 use warnings;
9
10
11 BEGIN {
12     use IDZebra;
13     use Scalar::Util;
14     use IDZebra::Logger qw(:flags :calls);
15     use IDZebra::Resultset;
16     use IDZebra::ScanList;
17     use IDZebra::RetrievalRecord;
18     our $VERSION = do { my @r = (q$Revision: 1.12 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; 
19 #    our @ISA = qw(IDZebra::Logger);
20 }
21
22 1;
23 # -----------------------------------------------------------------------------
24 # Class constructors, destructor
25 # -----------------------------------------------------------------------------
26 sub new {
27     my ($proto, %args) = @_;
28     my $class = ref($proto) || $proto;
29     my $self = {};
30     $self->{args} = \%args;
31     
32     bless ($self, $class);
33     $self->{cql_ct} = undef;
34     $self->{cql_mapfile} = "";
35     return ($self);
36
37     $self->{databases} = {};
38 }
39
40 sub start_service {
41     my ($self, %args) = @_;
42
43     my $zs;
44     unless (defined($self->{zs})) {
45         if (defined($args{'configFile'})) {
46             $self->{zs} = IDZebra::start($args{'configFile'});
47         } else {
48             $self->{zs} = IDZebra::start("zebra.cfg");
49         }
50     }
51 }
52
53 sub stop_service {
54     my ($self) = @_;
55     if (defined($self->{zs})) {
56         IDZebra::stop($self->{zs}) if ($self->{zs});    
57         $self->{zs} = undef;
58     }
59 }
60
61
62 sub open {
63     my ($proto,%args) = @_;
64     my $self = {};
65
66     if (ref($proto)) { $self = $proto; } else { 
67         $self = $proto->new(%args);
68     }
69
70     unless (%args) {
71         %args = %{$self->{args}};
72     }
73
74     $self->start_service(%args);
75
76     unless (defined($self->{zs})) {
77         croak ("Falied to open zebra service");
78     }    
79
80     unless (defined($self->{zh})) {
81         $self->{zh}=IDZebra::open($self->{zs}); 
82     }   
83
84     # Reset result set counter
85     $self->{rscount} = 0;
86
87     # This is needed in order to somehow initialize the service
88     $self->databases("Default");
89
90     # Load the default configuration
91     $self->group(%args);
92     
93     $self->{odr_input} = IDZebra::odr_createmem($IDZebra::ODR_DECODE);
94     $self->{odr_output} = IDZebra::odr_createmem($IDZebra::ODR_ENCODE);
95
96     return ($self);
97 }
98
99 sub checkzh {
100     my ($self) = @_;
101     unless (defined($self->{zh})) {
102         croak ("Zebra session is not opened");
103     }
104 }
105
106 sub close {
107     my ($self) = @_;
108
109     if ($self->{zh}) {
110
111         my $stats = 0; 
112         # Delete all resulsets
113         my $r = IDZebra::deleteResultSet($self->{zh},
114                                          1, #Z_DeleteRequest_all,
115                                          0,[],
116                                          $stats);
117
118         while (IDZebra::trans_no($self->{zh}) > 0) {
119             logf (LOG_WARN,"Explicitly closing transaction with session");
120             $self->end_trans;
121         }
122
123         IDZebra::close($self->{zh});
124         $self->{zh} = undef;
125     }
126     
127     if ($self->{odr_input}) {
128         IDZebra::odr_reset($self->{odr_input});
129         IDZebra::odr_destroy($self->{odr_input});
130         $self->{odr_input} = undef;  
131     }
132
133     if ($self->{odr_output}) {
134         IDZebra::odr_reset($self->{odr_output});
135         IDZebra::odr_destroy($self->{odr_output});
136         $self->{odr_output} = undef;  
137     }
138
139     $self->stop_service;
140 }
141
142 sub DESTROY {
143     my ($self) = @_;
144     logf (LOG_LOG,"DESTROY $self");
145     $self->close; 
146
147     if (defined ($self->{cql_ct})) {
148       IDZebra::cql_transform_close($self->{cql_ct});
149     }
150
151 }
152 # -----------------------------------------------------------------------------
153 # Record group selection  This is a bit nasty... but used at many places 
154 # -----------------------------------------------------------------------------
155 sub group {
156     my ($self,%args) = @_;
157     $self->checkzh;
158     if ($#_ > 0) {
159         $self->{rg} = $self->_makeRecordGroup(%args);
160         $self->_selectRecordGroup($self->{rg});
161     }
162     return($self->{rg});
163 }
164
165 sub selectRecordGroup {
166     my ($self, $groupName) = @_;
167     $self->checkzh;
168     $self->{rg} = $self->_getRecordGroup($groupName);
169     $self->_selectRecordGroup($self->{rg});
170 }
171
172 sub _displayRecordGroup {
173     my ($self, $rg) = @_;
174     print STDERR "-----\n";
175     foreach my $key qw (groupName 
176                         databaseName 
177                         path recordId 
178                         recordType 
179                         flagStoreData 
180                         flagStoreKeys 
181                         flagRw 
182                         fileVerboseLimit 
183                         databaseNamePath 
184                         explainDatabase 
185                         followLinks) {
186         print STDERR "$key:",$rg->{$key},"\n";
187     }
188 }
189
190 sub _cloneRecordGroup {
191     my ($self, $orig) = @_;
192     my $rg = IDZebra::recordGroup->new();
193     my $r = IDZebra::init_recordGroup($rg);
194     foreach my $key qw (groupName 
195                         databaseName 
196                         path 
197                         recordId 
198                         recordType 
199                         flagStoreData 
200                         flagStoreKeys 
201                         flagRw 
202                         fileVerboseLimit 
203                         databaseNamePath 
204                         explainDatabase 
205                         followLinks) {
206         $rg->{$key} = $orig->{$key} if ($orig->{$key});
207     }
208     return ($rg);
209 }
210
211 sub _getRecordGroup {
212     my ($self, $groupName, $ext) = @_;
213     my $rg = IDZebra::recordGroup->new();
214     my $r = IDZebra::init_recordGroup($rg);
215     $rg->{groupName} = $groupName if ($groupName ne "");  
216     $ext = "" unless ($ext);
217     $r = IDZebra::res_get_recordGroup($self->{zh}, $rg, $ext);
218     return ($rg);
219 }
220
221 sub _makeRecordGroup {
222     my ($self, %args) = @_;
223     my $rg;
224
225     my @keys = keys(%args);
226     unless ($#keys >= 0) {
227         return ($self->{rg});
228     }
229
230     if ($args{groupName}) {
231         $rg = $self->_getRecordGroup($args{groupName});
232     } else {
233         $rg = $self->_cloneRecordGroup($self->{rg});
234     }
235     $self->_setRecordGroupOptions($rg, %args);
236     return ($rg);
237 }
238
239 sub _setRecordGroupOptions {
240     my ($self, $rg, %args) = @_;
241
242     foreach my $key qw (databaseName 
243                         path 
244                         recordId 
245                         recordType 
246                         flagStoreData 
247                         flagStoreKeys 
248                         flagRw 
249                         fileVerboseLimit 
250                         databaseNamePath 
251                         explainDatabase 
252                         followLinks) {
253         if (defined ($args{$key})) {
254             $rg->{$key} = $args{$key};
255         }
256     }
257 }
258 sub _selectRecordGroup {
259     my ($self, $rg) = @_;
260     my $r = IDZebra::set_group($self->{zh}, $rg);
261     my $dbName;
262     unless ($dbName = $rg->{databaseName}) {
263         $dbName = 'Default';
264     }
265     unless ($self->databases($dbName)) {
266         croak("Fatal error selecting database $dbName");
267     }
268 }
269 # -----------------------------------------------------------------------------
270 # Selecting databases for search (and also for updating - internally)
271 # -----------------------------------------------------------------------------
272 sub databases {
273     my ($self, @databases) = @_;
274
275     $self->checkzh;
276
277     unless ($#_ >0) {
278         return (keys(%{$self->{databases}}));
279     }
280
281     my %tmp;
282
283     my $changed = 0;
284     foreach my $db (@databases) {
285         next if ($self->{databases}{$db});
286         $tmp{$db}++;
287         $changed++;
288     }
289
290     foreach my $db (keys (%{$self->{databases}})) {
291         $changed++ unless ($tmp{$db});
292     }
293
294     if ($changed) {
295
296         delete ($self->{databases});
297         foreach my $db (@databases) {
298             $self->{databases}{$db}++;
299         }
300
301         if (IDZebra::select_databases($self->{zh}, 
302                                                 ($#databases + 1), 
303                                                 \@databases)) {
304             logf(LOG_FATAL, 
305                  "Could not select database(s) %s errCode=%d",
306                  join(",",@databases),
307                  $self->errCode());
308             return (0);
309         } else {
310             logf(LOG_LOG,"Database(s) selected: %s",join(",",@databases));
311         }
312     }
313     return (keys(%{$self->{databases}}));
314 }
315
316 # -----------------------------------------------------------------------------
317 # Error handling
318 # -----------------------------------------------------------------------------
319 sub errCode {
320     my ($self) = @_;
321     return(IDZebra::errCode($self->{zh}));
322 }
323
324 sub errString {
325     my ($self) = @_;
326     return(IDZebra::errString($self->{zh}));
327 }
328
329 sub errAdd {
330     my ($self) = @_;
331     return(IDZebra::errAdd($self->{zh}));
332 }
333
334 # -----------------------------------------------------------------------------
335 # Transaction stuff
336 # -----------------------------------------------------------------------------
337 sub begin_trans {
338     my ($self) = @_;
339     $self->checkzh;
340     if (my $err = IDZebra::begin_trans($self->{zh},1)) {
341         if ($self->errCode == 2) {
342             croak ("TRANS_RW not allowed within TRANS_RO");
343         } else {
344             croak("Error starting transaction; code:".
345                   $self->errCode . " message: " . $self->errString);
346         }
347     }
348 }
349
350 sub end_trans {
351     my ($self) = @_;
352     $self->checkzh;
353     my $stat = IDZebra::ZebraTransactionStatus->new();
354     IDZebra::end_trans($self->{zh}, $stat);
355     return ($stat);
356 }
357
358 sub begin_read {
359     my ($self) =@_;
360     $self->checkzh;
361     return(IDZebra::begin_read($self->{zh}));
362 }
363
364 sub end_read {
365     my ($self) =@_;
366     $self->checkzh;
367     IDZebra::end_read($self->{zh});
368 }
369
370 sub shadow_enable {
371     my ($self, $value) = @_;
372     $self->checkzh;
373     if ($#_ > 0) { IDZebra::set_shadow_enable($self->{zh},$value); }
374     return (IDZebra::get_shadow_enable($self->{zh}));
375 }
376
377 sub commit {
378     my ($self) = @_;
379     $self->checkzh;
380     if ($self->shadow_enable) {
381         return(IDZebra::commit($self->{zh}));
382     }
383 }
384
385 # -----------------------------------------------------------------------------
386 # We don't really need that...
387 # -----------------------------------------------------------------------------
388 sub odr_reset {
389     my ($self, $name) = @_;
390     if ($name !~/^(input|output)$/) {
391         croak("Undefined ODR '$name'");
392     }
393   IDZebra::odr_reset($self->{"odr_$name"});
394 }
395
396 # -----------------------------------------------------------------------------
397 # Init/compact
398 # -----------------------------------------------------------------------------
399 sub init {
400     my ($self) = @_;
401     $self->checkzh;
402     return(IDZebra::init($self->{zh}));
403 }
404
405 sub compact {
406     my ($self) = @_;
407     $self->checkzh;
408     return(IDZebra::compact($self->{zh}));
409 }
410
411 sub update {
412     my ($self, %args) = @_;
413     $self->checkzh;
414     my $rg = $self->_update_args(%args);
415     $self->_selectRecordGroup($rg);
416     $self->begin_trans;
417     IDZebra::repository_update($self->{zh});
418     $self->_selectRecordGroup($self->{rg});
419     $self->end_trans;
420 }
421
422 sub delete {
423     my ($self, %args) = @_;
424     $self->checkzh;
425     my $rg = $self->_update_args(%args);
426     $self->_selectRecordGroup($rg);
427     $self->begin_trans;
428     IDZebra::repository_delete($self->{zh});
429     $self->_selectRecordGroup($self->{rg});
430     $self->end_trans;
431 }
432
433 sub show {
434     my ($self, %args) = @_;
435     $self->checkzh;
436     my $rg = $self->_update_args(%args);
437     $self->_selectRecordGroup($rg);
438     $self->begin_trans;
439     IDZebra::repository_show($self->{zh});
440     $self->_selectRecordGroup($self->{rg});
441     $self->end_trans;
442 }
443
444 sub _update_args {
445     my ($self, %args) = @_;
446     my $rg = $self->_makeRecordGroup(%args);
447     $self->_selectRecordGroup($rg);
448     return ($rg);
449 }
450
451 # -----------------------------------------------------------------------------
452 # Per record update
453 # -----------------------------------------------------------------------------
454
455 sub update_record {
456     my ($self, %args) = @_;
457     $self->checkzh;
458     return(IDZebra::update_record($self->{zh},
459                                   $self->_record_update_args(%args)));
460 }
461
462 sub delete_record {
463     my ($self, %args) = @_;
464     $self->checkzh;
465     return(IDZebra::delete_record($self->{zh},
466                                   $self->_record_update_args(%args)));
467 }
468 sub _record_update_args {
469     my ($self, %args) = @_;
470
471     my $sysno   = $args{sysno}      ? $args{sysno}      : 0;
472     my $match   = $args{match}      ? $args{match}      : "";
473     my $rectype = $args{recordType} ? $args{recordType} : "";
474     my $fname   = $args{file}       ? $args{file}       : "<no file>";
475
476     my $buff;
477
478     if ($args{data}) {
479         $buff = $args{data};
480     } 
481     elsif ($args{file}) {
482         CORE::open (F, $args{file}) || warn ("Cannot open $args{file}");
483         $buff = join('',(<F>));
484         CORE::close (F);
485     }
486     my $len = length($buff);
487
488     delete ($args{sysno});
489     delete ($args{match});
490     delete ($args{recordType});
491     delete ($args{file});
492     delete ($args{data});
493
494     my $rg = $self->_makeRecordGroup(%args);
495
496     # If no record type is given, then try to find it out from the
497     # file extension;
498     unless ($rectype) {
499         if (my ($ext) = $fname =~ /\.(\w+)$/) {
500             my $rg2 = $self->_getRecordGroup($rg->{groupName},$ext);
501             $rectype = $rg2->{recordType};
502         } 
503     }
504
505     $rg->{databaseName} = "Default" unless ($rg->{databaseName});
506
507     unless ($rectype) {
508         $rectype="";
509     }
510     return ($rg, $rectype, $sysno, $match, $fname, $buff, $len);
511 }
512
513 # -----------------------------------------------------------------------------
514 # CQL stuff
515 sub cqlmap {
516     my ($self,$mapfile) = @_;
517     if ($#_ > 0) {
518         if ($self->{cql_mapfile} ne $mapfile) {
519             unless (-f $mapfile) {
520                 croak("Cannot find $mapfile");
521             }
522             if (defined ($self->{cql_ct})) {
523               IDZebra::cql_transform_close($self->{cql_ct});
524             }
525             $self->{cql_ct} = IDZebra::cql_transform_open_fname($mapfile);
526             $self->{cql_mapfile} = $mapfile;
527         }
528     }
529     return ($self->{cql_mapfile});
530 }
531
532 sub cql2pqf {
533     my ($self, $cqlquery) = @_;
534     unless (defined($self->{cql_ct})) {
535         croak("CQL map file is not specified yet.");
536     }
537     my $res = "\0" x 2048;
538     my $r = IDZebra::cql2pqf($self->{cql_ct}, $cqlquery, $res, 2048);
539     if ($r) {
540         carp ("Error transforming CQL query: '$cqlquery', status:$r");
541     }
542     $res=~s/\0.+$//g;
543     return ($res,$r); 
544 }
545
546
547 # -----------------------------------------------------------------------------
548 # Search 
549 # -----------------------------------------------------------------------------
550 sub search {
551     my ($self, %args) = @_;
552
553     $self->checkzh;
554
555     if ($args{cqlmap}) { $self->cqlmap($args{cqlmap}); }
556
557     my $query;
558     if ($args{pqf}) {
559         $query = $args{pqf};
560     }
561     elsif ($args{cql}) {
562         my $cqlstat;
563         ($query, $cqlstat) =  $self->cql2pqf($args{cql});
564         unless ($query) {
565             croak ("Failed to transform query: '$args{cql}', ".
566                    "status: ($cqlstat)");
567         }
568     }
569     unless ($query) {
570         croak ("No query given to search");
571     }
572
573     my @origdbs;
574
575     if ($args{databases}) {
576         @origdbs = $self->databases;
577         $self->databases(@{$args{databases}});
578     }
579
580     my $rsname = $args{rsname} ? $args{rsname} : $self->_new_setname;
581
582     my $rs = $self->_search_pqf($query, $rsname);
583
584     if ($args{databases}) {
585         $self->databases(@origdbs);
586     }
587
588     if ($args{sort}) {
589         if ($rs->errCode) {
590             carp("Sort skipped due to search error: ".
591                  $rs->errCode);
592         } else {
593             $rs->sort($args{sort});
594         }
595     }
596
597     return ($rs);
598 }
599
600 sub _new_setname {
601     my ($self) = @_;
602     return ("set_".$self->{rscount}++);
603 }
604
605 sub _search_pqf {
606     my ($self, $query, $setname) = @_;
607
608     my $hits = IDZebra::search_PQF($self->{zh},
609                                    $self->{odr_input},
610                                    $self->{odr_output},
611                                    $query,
612                                    $setname);
613
614     my $rs  = IDZebra::Resultset->new($self,
615                                       name        => $setname,
616                                       recordCount => $hits,
617                                       errCode     => $self->errCode,
618                                       errString   => $self->errString);
619     return($rs);
620 }
621
622 # -----------------------------------------------------------------------------
623 # Sort
624 #
625 # Sorting of multiple result sets is not supported by zebra...
626 # -----------------------------------------------------------------------------
627
628 sub sortResultsets {
629     my ($self, $sortspec, $setname, @sets) = @_;
630
631     $self->checkzh;
632
633     if ($#sets > 0) {
634         croak ("Sorting/merging of multiple resultsets is not supported now");
635     }
636
637     my @setnames;
638     my $count = 0;
639     foreach my $rs (@sets) {
640         push (@setnames, $rs->{name});
641         $count += $rs->{recordCount};  # is this really sure ??? It doesn't 
642                                        # matter now...
643     }
644
645     my $status = IDZebra::sort($self->{zh},
646                                $self->{odr_output},
647                                $sortspec,
648                                $setname,
649                                \@setnames);
650
651     my $errCode = $self->errCode;
652     my $errString = $self->errString;
653
654     logf (LOG_LOG, "Sort status $setname: %d, errCode: %d, errString: %s", 
655           $status, $errCode, $errString);
656
657     if ($status || $errCode) {$count = 0;}
658
659     my $rs  = IDZebra::Resultset->new($self,
660                                       name        => $setname,
661                                       recordCount => $count,
662                                       errCode     => $errCode,
663                                       errString   => $errString);
664     
665     return ($rs);
666 }
667 # -----------------------------------------------------------------------------
668 # Scan
669 # -----------------------------------------------------------------------------
670 sub scan {
671     my ($self, %args) = @_;
672
673     $self->checkzh;
674
675     unless ($args{expression}) {
676         croak ("No scan expression given");
677     }
678
679     my $sl = IDZebra::ScanList->new($self,%args);
680
681     return ($sl);
682 }
683
684 # ============================================================================
685
686
687 __END__
688
689 =head1 NAME
690
691 IDZebra::Session - A Zebra database server session for update and retrieval
692
693 =head1 SYNOPSIS
694
695   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
696   $sess->open();
697
698   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
699                                  groupName  => 'demo1');
700
701   $sess->group(groupName => 'demo2');
702
703   $sess->init();
704
705   $sess->begin_trans;
706
707   $sess->update(path      =>  'lib');
708
709   my $s1=$sess->update_record(data       => $rec1,
710                               recordType => 'grs.perl.pod',
711                               groupName  => "demo1",
712                               );
713
714   my $stat = $sess->end_trans;
715
716   $sess->databases('demo1','demo2');
717
718   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
719                           cql       => 'dc.title=IDZebra',
720                           databases => [qw(demo1 demo2)]);
721   $sess->close;
722
723 =head1 DESCRIPTION
724
725 Zebra is a high-performance, general-purpose structured text indexing and retrieval engine. It reads structured records in a variety of input formats (eg. email, XML, MARC) and allows access to them through exact boolean search expressions and relevance-ranked free-text queries. 
726
727 Zebra supports large databases (more than ten gigabytes of data, tens of millions of records). It supports incremental, safe database updates on live systems. You can access data stored in Zebra using a variety of Index Data tools (eg. YAZ and PHP/YAZ) as well as commercial and freeware Z39.50 clients and toolkits. 
728
729 =head1 OPENING AND CLOSING A ZEBRA SESSIONS
730
731 For the time beeing only local database services are supported, the same way as calling zebraidx or zebrasrv from the command shell. In order to open a local Zebra database, with a specific configuration file, use
732
733   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
734   $sess->open();
735
736 or
737
738   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg');
739
740 where $sess is going to be the object representing a Zebra Session. Whenever this variable gets out of scope, the session is closed, together with all active transactions, etc... Anyway, if you'd like to close the session, just say:
741
742   $sess->close();
743
744 This will
745   - close all transactions
746   - destroy all result sets
747   - close the session
748
749 In the future different database access methods are going to be available, 
750 like:
751
752   $sess = IDZebra::Session->open(server => 'ostrich.technomat.hu:9999');
753
754 You can also use the B<record group> arguments described below directly when calling the constructor, or the open method:
755
756   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
757                                  groupName  => 'demo');
758
759
760 =head1 RECORD GROUPS 
761
762 If you manage different sets of records that share common characteristics, you can organize the configuration settings for each type into "groups". See the Zebra manual on the configuration file (zebra.cfg). 
763
764 For each open session a default record group is assigned. You can configure it in the constructor, or by the B<set_group> method:
765
766   $sess->group(groupName => ..., ...)
767
768 The following options are available:
769
770 =over 4
771
772 =item B<groupName>
773
774 This will select the named record group, and load the corresponding settings from the configuration file. All subsequent values will overwrite those...
775
776 =item B<databaseName>
777
778 The name of the (logical) database the updated records will belong to. 
779
780 =item B<path>
781
782 This path is used for directory updates (B<update>, B<delete> methods);
783  
784 =item B<recordId>
785
786 This option determines how to identify your records. See I<Zebra manual: Locating Records>
787
788 =item B<recordType>
789
790 The record type used for indexing. 
791
792 =item B<flagStoreData> 
793
794 Specifies whether the records should be stored internally in the Zebra system files. If you want to maintain the raw records yourself, this option should be false (0). If you want Zebra to take care of the records for you, it should be true(1). 
795
796 =item B<flagStoreKeys>
797
798 Specifies whether key information should be saved for a given group of records. If you plan to update/delete this type of records later this should be specified as 1; otherwise it should be 0 (default), to save register space. 
799
800 =item B<flagRw>
801
802 ?
803
804 =item B<fileVerboseLimit>
805
806 Skip log messages, when doing a directory update, and the specified number of files are processed...
807
808 =item B<databaseNamePath>
809
810 ?
811
812 =item B<explainDatabase>
813
814 The name of the explain database to be used
815
816 =item B<followLinks>              
817
818 Follow links when doing directory update.
819
820 =back
821
822 You can use the same parameters calling all update methods.
823
824 =head1 TRANSACTIONS (WRITE LOCKS)
825
826 A transaction is a block of record update (insert / modify / delete) procedures. So, all call to such function will implicitly start a transaction, unless one is started by
827
828   $sess->begin_trans;
829
830 For multiple per record updates it's efficient to start transactions explicitly: otherwise registers (system files, vocabularies, etc..) are updated one by one. After finishing all requested updates, use
831
832   $stat = $sess->end_trans;
833
834 The return value is a ZebraTransactionStatus object, containing the following members as a hash reference:
835
836   $stat->{processed} # Number of records processed
837   $stat->{updated}   # Number of records processed
838   $stat->{deleted}   # Number of records processed
839   $stat->{inserted}  # Number of records processed
840   $stat->{stime}     # System time used
841   $stat->{utime}     # User time used
842
843 =head1 UPDATING DATA
844
845 There are two ways to update data in a Zebra database using the perl API. You can update an entire directory structure just the way it's done by zebraidx:
846
847   $sess->update(path      =>  'lib');
848
849 This will update the database with the files in directory "lib", according to the current record group settings.
850
851   $sess->update();
852
853 This will update the database with the files, specified by the default record group setting. I<path> has to be specified there...
854
855   $sess->update(groupName => 'demo1',
856                 path      =>  'lib');
857
858 Update the database with files in "lib" according to the settings of group "demo1"
859
860   $sess->delete(groupName => 'demo1',
861                 path      =>  'lib');
862
863 Delete the records derived from the files in directory "lib", according to the "demo1" group settings. Sounds complex? Read zebra documentation about identifying records.
864
865 You can also update records one by one, even directly from the memory:
866
867   $sysno = $sess->update_record(data       => $rec1,
868                                 recordType => 'grs.perl.pod',
869                                 groupName  => "demo1");
870
871 This will update the database with the given record buffer. Note, that in this case recordType is explicitly specified, as there is no filename given, and for the demo1 group, no default record type is specified. The return value is the system assigned id of the record.
872
873 You can also index a single file:
874
875   $sysno = $sess->update_record(file => "lib/IDZebra/Data1.pm");
876
877 Or, provide a buffer, and a filename (where filename will only be used to identify the record, if configured that way, and possibly to find out it's record type):
878
879   $sysno = $sess->update_record(data => $rec1,
880                                 file => "lib/IDZebra/Data1.pm");
881
882 And some crazy stuff:
883
884   $sysno = $sess->delete_record(sysno => $sysno);
885
886 where sysno in itself is sufficient to identify the record
887
888   $sysno = $sess->delete_record(data => $rec1,
889                                 recordType => 'grs.perl.pod',
890                                 groupName  => "demo1");
891
892 This case the record is extracted, and if already exists, located in the database, then deleted... 
893
894   $sysno = $sess->delete_record(data       => $rec1,
895                                 match      => $myid,
896                                 recordType => 'grs.perl.pod',
897                                 groupName  => "demo1");
898
899 Don't try this at home! This case, the record identifier string (which is normally generated according to the rules set in recordId directive of zebra.cfg) is provided directly....
900
901
902 B<Important:> Note, that one record can be updated only once within a transaction - all subsequent updates are skipped. 
903
904 =head1 DATABASE SELECTION
905
906 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. 
907
908 For searching, you can select databases by calling:
909
910   $sess->databases('db1','db2');
911
912 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:
913   
914   @dblist = $sess->databases();
915
916 =head1 SEARCHING
917
918 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:
919
920   $rs = $sess->search(databases => [qw(demo1,demo2)], # optional
921                       pqf       => '@attr 1=4 computer');
922
923 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.
924
925 =head2 CCL searching
926
927 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. 
928
929 The CCL searching is not currently supported by this API.
930
931 =head2 CQL searching
932
933 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. 
934
935 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:
936
937   $sess->cqlmap($mapfile);
938
939 Or, you can directly provide the I<mapfile> parameter for the search:
940
941   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
942                           cql       => 'dc.title=IDZebra');
943
944 As you see, CQL searching is so simple: just give the query in the I<cql> parameter.
945
946 =head1 RESULTSETS
947
948 As you have seen, the result of the search request is a I<Resultset> object.
949 It contains number of hits, and search status, and can be used to sort and retrieve the resulting records.
950
951   $count = $rs->count;
952
953   printf ("RS Status is %d (%s)\n", $rs->errCode, $rs->errString);
954
955 I<$rs-E<gt>errCode> is 0, if there were no errors during search. Read the I<IDZebra::Resultset> manpage for more details.
956
957 =head1 MISC FUNCTIONS
958
959 =head1 COPYRIGHT
960
961 Fill in
962
963 =head1 AUTHOR
964
965 Peter Popovics, pop@technomat.hu
966
967 =head1 SEE ALSO
968
969 IDZebra, IDZebra::Data1, Zebra documentation
970
971 =cut