Scan feature added, not documented yet
[idzebra-moved-to-github.git] / perl / lib / IDZebra / Session.pm
1 # $Id: Session.pm,v 1.11 2003-03-04 19:33:52 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.11 $ =~ /\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     IDZebra::begin_trans($self->{zh});
341 }
342
343 sub end_trans {
344     my ($self) = @_;
345     $self->checkzh;
346     my $stat = IDZebra::ZebraTransactionStatus->new();
347     IDZebra::end_trans($self->{zh}, $stat);
348     return ($stat);
349 }
350
351 sub begin_read {
352     my ($self) =@_;
353     $self->checkzh;
354     return(IDZebra::begin_read($self->{zh}));
355 }
356
357 sub end_read {
358     my ($self) =@_;
359     $self->checkzh;
360     IDZebra::end_read($self->{zh});
361 }
362
363 sub shadow_enable {
364     my ($self, $value) = @_;
365     $self->checkzh;
366     if ($#_ > 0) { IDZebra::set_shadow_enable($self->{zh},$value); }
367     return (IDZebra::get_shadow_enable($self->{zh}));
368 }
369
370 sub commit {
371     my ($self) = @_;
372     $self->checkzh;
373     if ($self->shadow_enable) {
374         return(IDZebra::commit($self->{zh}));
375     }
376 }
377
378 # -----------------------------------------------------------------------------
379 # We don't really need that...
380 # -----------------------------------------------------------------------------
381 sub odr_reset {
382     my ($self, $name) = @_;
383     if ($name !~/^(input|output)$/) {
384         croak("Undefined ODR '$name'");
385     }
386   IDZebra::odr_reset($self->{"odr_$name"});
387 }
388
389 # -----------------------------------------------------------------------------
390 # Init/compact
391 # -----------------------------------------------------------------------------
392 sub init {
393     my ($self) = @_;
394     $self->checkzh;
395     return(IDZebra::init($self->{zh}));
396 }
397
398 sub compact {
399     my ($self) = @_;
400     $self->checkzh;
401     return(IDZebra::compact($self->{zh}));
402 }
403
404 sub update {
405     my ($self, %args) = @_;
406     $self->checkzh;
407     my $rg = $self->_update_args(%args);
408     $self->_selectRecordGroup($rg);
409     $self->begin_trans;
410     IDZebra::repository_update($self->{zh});
411     $self->_selectRecordGroup($self->{rg});
412     $self->end_trans;
413 }
414
415 sub delete {
416     my ($self, %args) = @_;
417     $self->checkzh;
418     my $rg = $self->_update_args(%args);
419     $self->_selectRecordGroup($rg);
420     $self->begin_trans;
421     IDZebra::repository_delete($self->{zh});
422     $self->_selectRecordGroup($self->{rg});
423     $self->end_trans;
424 }
425
426 sub show {
427     my ($self, %args) = @_;
428     $self->checkzh;
429     my $rg = $self->_update_args(%args);
430     $self->_selectRecordGroup($rg);
431     $self->begin_trans;
432     IDZebra::repository_show($self->{zh});
433     $self->_selectRecordGroup($self->{rg});
434     $self->end_trans;
435 }
436
437 sub _update_args {
438     my ($self, %args) = @_;
439     my $rg = $self->_makeRecordGroup(%args);
440     $self->_selectRecordGroup($rg);
441     return ($rg);
442 }
443
444 # -----------------------------------------------------------------------------
445 # Per record update
446 # -----------------------------------------------------------------------------
447
448 sub update_record {
449     my ($self, %args) = @_;
450     $self->checkzh;
451     return(IDZebra::update_record($self->{zh},
452                                   $self->_record_update_args(%args)));
453 }
454
455 sub delete_record {
456     my ($self, %args) = @_;
457     $self->checkzh;
458     return(IDZebra::delete_record($self->{zh},
459                                   $self->_record_update_args(%args)));
460 }
461 sub _record_update_args {
462     my ($self, %args) = @_;
463
464     my $sysno   = $args{sysno}      ? $args{sysno}      : 0;
465     my $match   = $args{match}      ? $args{match}      : "";
466     my $rectype = $args{recordType} ? $args{recordType} : "";
467     my $fname   = $args{file}       ? $args{file}       : "<no file>";
468
469     my $buff;
470
471     if ($args{data}) {
472         $buff = $args{data};
473     } 
474     elsif ($args{file}) {
475         CORE::open (F, $args{file}) || warn ("Cannot open $args{file}");
476         $buff = join('',(<F>));
477         CORE::close (F);
478     }
479     my $len = length($buff);
480
481     delete ($args{sysno});
482     delete ($args{match});
483     delete ($args{recordType});
484     delete ($args{file});
485     delete ($args{data});
486
487     my $rg = $self->_makeRecordGroup(%args);
488
489     # If no record type is given, then try to find it out from the
490     # file extension;
491     unless ($rectype) {
492         if (my ($ext) = $fname =~ /\.(\w+)$/) {
493             my $rg2 = $self->_getRecordGroup($rg->{groupName},$ext);
494             $rectype = $rg2->{recordType};
495         } 
496     }
497
498     $rg->{databaseName} = "Default" unless ($rg->{databaseName});
499
500     unless ($rectype) {
501         $rectype="";
502     }
503     return ($rg, $rectype, $sysno, $match, $fname, $buff, $len);
504 }
505
506 # -----------------------------------------------------------------------------
507 # CQL stuff
508 sub cqlmap {
509     my ($self,$mapfile) = @_;
510     if ($#_ > 0) {
511         if ($self->{cql_mapfile} ne $mapfile) {
512             unless (-f $mapfile) {
513                 croak("Cannot find $mapfile");
514             }
515             if (defined ($self->{cql_ct})) {
516               IDZebra::cql_transform_close($self->{cql_ct});
517             }
518             $self->{cql_ct} = IDZebra::cql_transform_open_fname($mapfile);
519             $self->{cql_mapfile} = $mapfile;
520         }
521     }
522     return ($self->{cql_mapfile});
523 }
524
525 sub cql2pqf {
526     my ($self, $cqlquery) = @_;
527     unless (defined($self->{cql_ct})) {
528         croak("CQL map file is not specified yet.");
529     }
530     my $res = "\0" x 2048;
531     my $r = IDZebra::cql2pqf($self->{cql_ct}, $cqlquery, $res, 2048);
532     if ($r) {
533         carp ("Error transforming CQL query: '$cqlquery', status:$r");
534     }
535     $res=~s/\0.+$//g;
536     return ($res,$r); 
537 }
538
539
540 # -----------------------------------------------------------------------------
541 # Search 
542 # -----------------------------------------------------------------------------
543 sub search {
544     my ($self, %args) = @_;
545
546     $self->checkzh;
547
548     if ($args{cqlmap}) { $self->cqlmap($args{cqlmap}); }
549
550     my $query;
551     if ($args{pqf}) {
552         $query = $args{pqf};
553     }
554     elsif ($args{cql}) {
555         my $cqlstat;
556         ($query, $cqlstat) =  $self->cql2pqf($args{cql});
557         unless ($query) {
558             croak ("Failed to transform query: '$args{cql}', ".
559                    "status: ($cqlstat)");
560         }
561     }
562     unless ($query) {
563         croak ("No query given to search");
564     }
565
566     my @origdbs;
567
568     if ($args{databases}) {
569         @origdbs = $self->databases;
570         $self->databases(@{$args{databases}});
571     }
572
573     my $rsname = $args{rsname} ? $args{rsname} : $self->_new_setname;
574
575     my $rs = $self->_search_pqf($query, $rsname);
576
577     if ($args{databases}) {
578         $self->databases(@origdbs);
579     }
580
581     if ($args{sort}) {
582         if ($rs->errCode) {
583             carp("Sort skipped due to search error: ".
584                  $rs->errCode);
585         } else {
586             $rs->sort($args{sort});
587         }
588     }
589
590     return ($rs);
591 }
592
593 sub _new_setname {
594     my ($self) = @_;
595     return ("set_".$self->{rscount}++);
596 }
597
598 sub _search_pqf {
599     my ($self, $query, $setname) = @_;
600
601     my $hits = IDZebra::search_PQF($self->{zh},
602                                    $self->{odr_input},
603                                    $self->{odr_output},
604                                    $query,
605                                    $setname);
606
607     my $rs  = IDZebra::Resultset->new($self,
608                                       name        => $setname,
609                                       recordCount => $hits,
610                                       errCode     => $self->errCode,
611                                       errString   => $self->errString);
612     return($rs);
613 }
614
615 # -----------------------------------------------------------------------------
616 # Sort
617 #
618 # Sorting of multiple result sets is not supported by zebra...
619 # -----------------------------------------------------------------------------
620
621 sub sortResultsets {
622     my ($self, $sortspec, $setname, @sets) = @_;
623
624     $self->checkzh;
625
626     if ($#sets > 0) {
627         croak ("Sorting/merging of multiple resultsets is not supported now");
628     }
629
630     my @setnames;
631     my $count = 0;
632     foreach my $rs (@sets) {
633         push (@setnames, $rs->{name});
634         $count += $rs->{recordCount};  # is this really sure ??? It doesn't 
635                                        # matter now...
636     }
637
638     my $status = IDZebra::sort($self->{zh},
639                                $self->{odr_output},
640                                $sortspec,
641                                $setname,
642                                \@setnames);
643
644     my $errCode = $self->errCode;
645     my $errString = $self->errString;
646
647     logf (LOG_LOG, "Sort status $setname: %d, errCode: %d, errString: %s", 
648           $status, $errCode, $errString);
649
650     if ($status || $errCode) {$count = 0;}
651
652     my $rs  = IDZebra::Resultset->new($self,
653                                       name        => $setname,
654                                       recordCount => $count,
655                                       errCode     => $errCode,
656                                       errString   => $errString);
657     
658     return ($rs);
659 }
660 # -----------------------------------------------------------------------------
661 # Scan
662 # -----------------------------------------------------------------------------
663 sub scan {
664     my ($self, %args) = @_;
665
666     $self->checkzh;
667
668     unless ($args{expression}) {
669         croak ("No scan expression given");
670     }
671
672     my $sl = IDZebra::ScanList->new($self,%args);
673
674     return ($sl);
675 }
676
677 # ============================================================================
678
679
680 __END__
681
682 =head1 NAME
683
684 IDZebra::Session - A Zebra database server session for update and retrieval
685
686 =head1 SYNOPSIS
687
688   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
689   $sess->open();
690
691   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
692                                  groupName  => 'demo1');
693
694   $sess->group(groupName => 'demo2');
695
696   $sess->init();
697
698   $sess->begin_trans;
699
700   $sess->update(path      =>  'lib');
701
702   my $s1=$sess->update_record(data       => $rec1,
703                               recordType => 'grs.perl.pod',
704                               groupName  => "demo1",
705                               );
706
707   my $stat = $sess->end_trans;
708
709   $sess->databases('demo1','demo2');
710
711   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
712                           cql       => 'dc.title=IDZebra',
713                           databases => [qw(demo1 demo2)]);
714   $sess->close;
715
716 =head1 DESCRIPTION
717
718 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. 
719
720 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. 
721
722 =head1 OPENING AND CLOSING A ZEBRA SESSIONS
723
724 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
725
726   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
727   $sess->open();
728
729 or
730
731   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg');
732
733 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:
734
735   $sess->close();
736
737 This will
738   - close all transactions
739   - destroy all result sets
740   - close the session
741
742 In the future different database access methods are going to be available, 
743 like:
744
745   $sess = IDZebra::Session->open(server => 'ostrich.technomat.hu:9999');
746
747 You can also use the B<record group> arguments described below directly when calling the constructor, or the open method:
748
749   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
750                                  groupName  => 'demo');
751
752
753 =head1 RECORD GROUPS 
754
755 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). 
756
757 For each open session a default record group is assigned. You can configure it in the constructor, or by the B<set_group> method:
758
759   $sess->group(groupName => ..., ...)
760
761 The following options are available:
762
763 =over 4
764
765 =item B<groupName>
766
767 This will select the named record group, and load the corresponding settings from the configuration file. All subsequent values will overwrite those...
768
769 =item B<databaseName>
770
771 The name of the (logical) database the updated records will belong to. 
772
773 =item B<path>
774
775 This path is used for directory updates (B<update>, B<delete> methods);
776  
777 =item B<recordId>
778
779 This option determines how to identify your records. See I<Zebra manual: Locating Records>
780
781 =item B<recordType>
782
783 The record type used for indexing. 
784
785 =item B<flagStoreData> 
786
787 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). 
788
789 =item B<flagStoreKeys>
790
791 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. 
792
793 =item B<flagRw>
794
795 ?
796
797 =item B<fileVerboseLimit>
798
799 Skip log messages, when doing a directory update, and the specified number of files are processed...
800
801 =item B<databaseNamePath>
802
803 ?
804
805 =item B<explainDatabase>
806
807 The name of the explain database to be used
808
809 =item B<followLinks>              
810
811 Follow links when doing directory update.
812
813 =back
814
815 You can use the same parameters calling all update methods.
816
817 =head1 TRANSACTIONS (WRITE LOCKS)
818
819 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
820
821   $sess->begin_trans;
822
823 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
824
825   $stat = $sess->end_trans;
826
827 The return value is a ZebraTransactionStatus object, containing the following members as a hash reference:
828
829   $stat->{processed} # Number of records processed
830   $stat->{updated}   # Number of records processed
831   $stat->{deleted}   # Number of records processed
832   $stat->{inserted}  # Number of records processed
833   $stat->{stime}     # System time used
834   $stat->{utime}     # User time used
835
836 =head1 UPDATING DATA
837
838 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:
839
840   $sess->update(path      =>  'lib');
841
842 This will update the database with the files in directory "lib", according to the current record group settings.
843
844   $sess->update();
845
846 This will update the database with the files, specified by the default record group setting. I<path> has to be specified there...
847
848   $sess->update(groupName => 'demo1',
849                 path      =>  'lib');
850
851 Update the database with files in "lib" according to the settings of group "demo1"
852
853   $sess->delete(groupName => 'demo1',
854                 path      =>  'lib');
855
856 Delete the records derived from the files in directory "lib", according to the "demo1" group settings. Sounds complex? Read zebra documentation about identifying records.
857
858 You can also update records one by one, even directly from the memory:
859
860   $sysno = $sess->update_record(data       => $rec1,
861                                 recordType => 'grs.perl.pod',
862                                 groupName  => "demo1");
863
864 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.
865
866 You can also index a single file:
867
868   $sysno = $sess->update_record(file => "lib/IDZebra/Data1.pm");
869
870 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):
871
872   $sysno = $sess->update_record(data => $rec1,
873                                 file => "lib/IDZebra/Data1.pm");
874
875 And some crazy stuff:
876
877   $sysno = $sess->delete_record(sysno => $sysno);
878
879 where sysno in itself is sufficient to identify the record
880
881   $sysno = $sess->delete_record(data => $rec1,
882                                 recordType => 'grs.perl.pod',
883                                 groupName  => "demo1");
884
885 This case the record is extracted, and if already exists, located in the database, then deleted... 
886
887   $sysno = $sess->delete_record(data       => $rec1,
888                                 match      => $myid,
889                                 recordType => 'grs.perl.pod',
890                                 groupName  => "demo1");
891
892 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....
893
894
895 B<Important:> Note, that one record can be updated only once within a transaction - all subsequent updates are skipped. 
896
897 =head1 DATABASE SELECTION
898
899 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. 
900
901 For searching, you can select databases by calling:
902
903   $sess->databases('db1','db2');
904
905 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:
906   
907   @dblist = $sess->databases();
908
909 =head1 SEARCHING
910
911 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:
912
913   $rs = $sess->search(databases => [qw(demo1,demo2)], # optional
914                       pqf       => '@attr 1=4 computer');
915
916 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.
917
918 =head2 CCL searching
919
920 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. 
921
922 The CCL searching is not currently supported by this API.
923
924 =head2 CQL searching
925
926 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. 
927
928 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:
929
930   $sess->cqlmap($mapfile);
931
932 Or, you can directly provide the I<mapfile> parameter for the search:
933
934   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
935                           cql       => 'dc.title=IDZebra');
936
937 As you see, CQL searching is so simple: just give the query in the I<cql> parameter.
938
939 =head1 RESULTSETS
940
941 As you have seen, the result of the search request is a I<Resultset> object.
942 It contains number of hits, and search status, and can be used to sort and retrieve the resulting records.
943
944   $count = $rs->count;
945
946   printf ("RS Status is %d (%s)\n", $rs->errCode, $rs->errString);
947
948 I<$rs-E<gt>errCode> is 0, if there were no errors during search. Read the I<IDZebra::Resultset> manpage for more details.
949
950 =head1 MISC FUNCTIONS
951
952 =head1 COPYRIGHT
953
954 Fill in
955
956 =head1 AUTHOR
957
958 Peter Popovics, pop@technomat.hu
959
960 =head1 SEE ALSO
961
962 IDZebra, IDZebra::Data1, Zebra documentation
963
964 =cut