Added log prefix features. Fixed handling of "0" values in data1.
[idzebra-moved-to-github.git] / perl / lib / IDZebra / Session.pm
1 # $Id: Session.pm,v 1.16 2003-05-07 20:51:12 pop Exp $
2
3 # Zebra perl API header
4 # =============================================================================
5 package IDZebra::Session;
6
7 use strict;
8 use warnings;
9 use Carp;
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     require Exporter;
19     our $VERSION = do { my @r = (q$Revision: 1.16 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; 
20     our @ISA = qw(IDZebra::Logger Exporter);
21     our @EXPORT = qw (TRANS_RW TRANS_RO);
22 }
23
24 use constant TRANS_RW => 1;
25 use constant TRANS_RO => 0;
26
27 1;
28 # -----------------------------------------------------------------------------
29 # Class constructors, destructor
30 # -----------------------------------------------------------------------------
31 sub new {
32     my ($proto, %args) = @_;
33     my $class = ref($proto) || $proto;
34     my $self = {};
35     $self->{args} = \%args;
36     
37     bless ($self, $class);
38     $self->{cql_ct} = undef;
39     $self->{cql_mapfile} = "";
40     return ($self);
41
42     $self->{databases} = {};
43 }
44
45 sub start_service {
46     my ($self, %args) = @_;
47
48     my $zs;
49     unless (defined($self->{zs})) {
50         if (defined($args{'configFile'})) {
51             $self->{zs} = IDZebra::start($args{'configFile'});
52         } else {
53             $self->{zs} = IDZebra::start("zebra.cfg");
54         }
55     }
56 }
57
58 sub stop_service {
59     my ($self) = @_;
60     if (defined($self->{zs})) {
61         IDZebra::stop($self->{zs}) if ($self->{zs});    
62         $self->{zs} = undef;
63     }
64 }
65
66
67 sub open {
68     my ($proto,%args) = @_;
69     my $self = {};
70
71     if (ref($proto)) { $self = $proto; } else { 
72         $self = $proto->new(%args);
73     }
74
75     unless (%args) {
76         %args = %{$self->{args}};
77     }
78
79     $self->start_service(%args);
80
81     unless (defined($self->{zs})) {
82         croak ("Falied to open zebra service");
83     }    
84
85     unless (defined($self->{zh})) {
86         $self->{zh}=IDZebra::open($self->{zs}); 
87     }   
88
89     # Reset result set counter
90     $self->{rscount} = 0;
91
92     # This is needed in order to somehow initialize the service
93     $self->databases("Default");
94
95     # Load the default configuration
96     $self->group(%args);
97
98
99     # Set shadow usage
100     my $shadow = defined($args{shadow}) ? $args{shadow} : 0;
101     $self->shadow($shadow);
102     
103     $self->{odr_input} = IDZebra::odr_createmem($IDZebra::ODR_DECODE);
104     $self->{odr_output} = IDZebra::odr_createmem($IDZebra::ODR_ENCODE);
105
106     return ($self);
107 }
108
109 sub checkzh {
110     my ($self) = @_;
111     unless (defined($self->{zh})) {
112         croak ("Zebra session is not opened");
113     }
114 }
115
116 sub close {
117     my ($self) = @_;
118
119     if ($self->{zh}) {
120
121         my $stats = 0; 
122         # Delete all resulsets
123         my $r = IDZebra::deleteResultSet($self->{zh},
124                                          1, #Z_DeleteRequest_all,
125                                          0,[],
126                                          $stats);
127
128         while (IDZebra::trans_no($self->{zh}) > 0) {
129             logf (LOG_WARN,"Explicitly closing transaction with session");
130             $self->end_trans;
131         }
132
133         IDZebra::close($self->{zh});
134         $self->{zh} = undef;
135     }
136     
137     if ($self->{odr_input}) {
138         IDZebra::odr_reset($self->{odr_input});
139         IDZebra::odr_destroy($self->{odr_input});
140         $self->{odr_input} = undef;  
141     }
142
143     if ($self->{odr_output}) {
144         IDZebra::odr_reset($self->{odr_output});
145         IDZebra::odr_destroy($self->{odr_output});
146         $self->{odr_output} = undef;  
147     }
148
149     $self->stop_service;
150 }
151
152 sub DESTROY {
153     my ($self) = @_;
154     logf (LOG_LOG,"DESTROY $self");
155     $self->close; 
156
157     if (defined ($self->{cql_ct})) {
158       IDZebra::cql_transform_close($self->{cql_ct});
159     }
160
161 }
162 # -----------------------------------------------------------------------------
163 # Record group selection  This is a bit nasty... but used at many places 
164 # -----------------------------------------------------------------------------
165 sub group {
166     my ($self,%args) = @_;
167     $self->checkzh;
168     if ($#_ > 0) {
169         $self->{rg} = $self->_makeRecordGroup(%args);
170         $self->_selectRecordGroup($self->{rg});
171     }
172     return($self->{rg});
173 }
174
175 sub selectRecordGroup {
176     my ($self, $groupName) = @_;
177     $self->checkzh;
178     $self->{rg} = $self->_getRecordGroup($groupName);
179     $self->_selectRecordGroup($self->{rg});
180 }
181
182 sub _displayRecordGroup {
183     my ($self, $rg) = @_;
184     print STDERR "-----\n";
185     foreach my $key qw (groupName 
186                         databaseName 
187                         path recordId 
188                         recordType 
189                         flagStoreData 
190                         flagStoreKeys 
191                         flagRw 
192                         fileVerboseLimit 
193                         databaseNamePath 
194                         explainDatabase 
195                         followLinks) {
196         print STDERR "$key:",$rg->{$key},"\n";
197     }
198 }
199
200 sub _cloneRecordGroup {
201     my ($self, $orig) = @_;
202     my $rg = IDZebra::recordGroup->new();
203     my $r = IDZebra::init_recordGroup($rg);
204     foreach my $key qw (groupName 
205                         databaseName 
206                         path 
207                         recordId 
208                         recordType 
209                         flagStoreData 
210                         flagStoreKeys 
211                         flagRw 
212                         fileVerboseLimit 
213                         databaseNamePath 
214                         explainDatabase 
215                         followLinks) {
216         $rg->{$key} = $orig->{$key} if ($orig->{$key});
217     }
218     return ($rg);
219 }
220
221 sub _getRecordGroup {
222     my ($self, $groupName, $ext) = @_;
223     my $rg = IDZebra::recordGroup->new();
224     my $r = IDZebra::init_recordGroup($rg);
225     $rg->{groupName} = $groupName if ($groupName ne "");  
226     $ext = "" unless ($ext);
227     $r = IDZebra::res_get_recordGroup($self->{zh}, $rg, $ext);
228     return ($rg);
229 }
230
231 sub _makeRecordGroup {
232     my ($self, %args) = @_;
233     my $rg;
234
235     my @keys = keys(%args);
236     unless ($#keys >= 0) {
237         return ($self->{rg});
238     }
239
240     if ($args{groupName}) {
241         $rg = $self->_getRecordGroup($args{groupName});
242     } else {
243         $rg = $self->_cloneRecordGroup($self->{rg});
244     }
245     $self->_setRecordGroupOptions($rg, %args);
246     return ($rg);
247 }
248
249 sub _setRecordGroupOptions {
250     my ($self, $rg, %args) = @_;
251
252     foreach my $key qw (databaseName 
253                         path 
254                         recordId 
255                         recordType 
256                         flagStoreData 
257                         flagStoreKeys 
258                         flagRw 
259                         fileVerboseLimit 
260                         databaseNamePath 
261                         explainDatabase 
262                         followLinks) {
263         if (defined ($args{$key})) {
264             $rg->{$key} = $args{$key};
265         }
266     }
267 }
268 sub _selectRecordGroup {
269     my ($self, $rg) = @_;
270
271     my $r = IDZebra::set_group($self->{zh}, $rg);
272     my $dbName;
273     unless ($dbName = $rg->{databaseName}) {
274         $dbName = 'Default';
275     }
276     unless ($self->databases($dbName)) {
277         croak("Fatal error selecting database $dbName");
278     }
279 }
280 # -----------------------------------------------------------------------------
281 # Selecting databases for search (and also for updating - internally)
282 # -----------------------------------------------------------------------------
283 sub databases {
284     my ($self, @databases) = @_;
285
286     $self->checkzh;
287
288     unless ($#_ >0) {
289         return (keys(%{$self->{databases}}));
290     }
291
292     my %tmp;
293     my $changed = 0;
294     foreach my $db (@databases) {
295         $tmp{$db}++;
296         next if ($self->{databases}{$db});
297         $changed++;
298     }
299
300     foreach my $db (keys (%{$self->{databases}})) {
301         $changed++ unless ($tmp{$db});
302     }
303
304     if ($changed) {
305
306         delete ($self->{databases});
307         foreach my $db (@databases) {
308             $self->{databases}{$db}++;
309         }
310
311         if (IDZebra::select_databases($self->{zh}, 
312                                                 ($#databases + 1), 
313                                                 \@databases)) {
314             logf(LOG_FATAL, 
315                  "Could not select database(s) %s errCode=%d",
316                  join(",",@databases),
317                  $self->errCode());
318             return (0);
319         } else {
320             logf(LOG_LOG,"Database(s) selected: %s",join(",",@databases));
321         }
322     }
323     return (keys(%{$self->{databases}}));
324 }
325
326 # -----------------------------------------------------------------------------
327 # Error handling
328 # -----------------------------------------------------------------------------
329 sub errCode {
330     my ($self) = @_;
331     return(IDZebra::errCode($self->{zh}));
332 }
333
334 sub errString {
335     my ($self) = @_;
336     return(IDZebra::errString($self->{zh}));
337 }
338
339 sub errAdd {
340     my ($self) = @_;
341     return(IDZebra::errAdd($self->{zh}));
342 }
343
344 # -----------------------------------------------------------------------------
345 # Transaction stuff
346 # -----------------------------------------------------------------------------
347 sub begin_trans {
348     my ($self, $m) = @_;
349     $m = TRANS_RW unless (defined ($m));
350     if (my $err = IDZebra::begin_trans($self->{zh},$m)) {
351         if ($self->errCode == 2) {
352             croak ("TRANS_RW not allowed within TRANS_RO");
353         } else {
354             croak("Error starting transaction; code:".
355                   $self->errCode . " message: " . $self->errString);
356         }
357     }
358 }
359
360 sub end_trans {
361     my ($self) = @_;
362     $self->checkzh;
363     my $stat = IDZebra::ZebraTransactionStatus->new();
364     IDZebra::end_trans($self->{zh}, $stat);
365     return ($stat);
366 }
367
368 sub shadow {
369     my ($self, $value) = @_;
370     $self->checkzh;
371     if ($#_ > 0) { 
372         $value = 0 unless (defined($value));
373         my $r =IDZebra::set_shadow_enable($self->{zh},$value); 
374     }
375     return (IDZebra::get_shadow_enable($self->{zh}));
376 }
377
378 sub commit {
379     my ($self) = @_;
380     $self->checkzh;
381     if ($self->shadow) {
382         return(IDZebra::commit($self->{zh}));
383     }
384 }
385
386 # -----------------------------------------------------------------------------
387 # We don't really need that...
388 # -----------------------------------------------------------------------------
389 sub odr_reset {
390     my ($self, $name) = @_;
391     if ($name !~/^(input|output)$/) {
392         croak("Undefined ODR '$name'");
393     }
394   IDZebra::odr_reset($self->{"odr_$name"});
395 }
396
397 # -----------------------------------------------------------------------------
398 # Init/compact
399 # -----------------------------------------------------------------------------
400 sub init {
401     my ($self) = @_;
402     $self->checkzh;
403     return(IDZebra::init($self->{zh}));
404 }
405
406 sub compact {
407     my ($self) = @_;
408     $self->checkzh;
409     return(IDZebra::compact($self->{zh}));
410 }
411
412 sub update {
413     my ($self, %args) = @_;
414     $self->checkzh;
415     my $rg = $self->_update_args(%args);
416     $self->_selectRecordGroup($rg);
417     $self->begin_trans;
418     IDZebra::repository_update($self->{zh});
419     $self->_selectRecordGroup($self->{rg});
420     $self->end_trans;
421 }
422
423 sub delete {
424     my ($self, %args) = @_;
425     $self->checkzh;
426     my $rg = $self->_update_args(%args);
427     $self->_selectRecordGroup($rg);
428     $self->begin_trans;
429     IDZebra::repository_delete($self->{zh});
430     $self->_selectRecordGroup($self->{rg});
431     $self->end_trans;
432 }
433
434 sub show {
435     my ($self, %args) = @_;
436     $self->checkzh;
437     my $rg = $self->_update_args(%args);
438     $self->_selectRecordGroup($rg);
439     $self->begin_trans;
440     IDZebra::repository_show($self->{zh});
441     $self->_selectRecordGroup($self->{rg});
442     $self->end_trans;
443 }
444
445 sub _update_args {
446     my ($self, %args) = @_;
447     my $rg = $self->_makeRecordGroup(%args);
448     $self->_selectRecordGroup($rg);
449     return ($rg);
450 }
451
452 # -----------------------------------------------------------------------------
453 # Per record update
454 # -----------------------------------------------------------------------------
455 sub insert_record {
456     my ($self, %args) = @_;
457     $self->checkzh;
458     return(IDZebra::insert_record($self->{zh},
459                                   $self->_record_update_args(%args)));
460 }
461
462 sub update_record {
463     my ($self, %args) = @_;
464     $self->checkzh;
465     return(IDZebra::update_record($self->{zh},
466                                   $self->_record_update_args(%args)));
467 }
468
469 sub delete_record {
470     my ($self, %args) = @_;
471     $self->checkzh;
472     return(IDZebra::delete_record($self->{zh},
473                                   $self->_record_update_args(%args)));
474 }
475
476 sub _record_update_args {
477     my ($self, %args) = @_;
478
479     my $sysno   = $args{sysno}      ? $args{sysno}      : 0;
480     my $match   = $args{match}      ? $args{match}      : "";
481     my $rectype = $args{recordType} ? $args{recordType} : "";
482     my $fname   = $args{file}       ? $args{file}       : "<no file>";
483     my $force   = $args{force}      ? $args{force}      : 0;
484
485     my $buff;
486
487     if ($args{data}) {
488         $buff = $args{data};
489     } 
490     elsif ($args{file}) {
491         CORE::open (F, $args{file}) || warn ("Cannot open $args{file}");
492         $buff = join('',(<F>));
493         CORE::close (F);
494     }
495     my $len = length($buff);
496
497     delete ($args{sysno});
498     delete ($args{match});
499     delete ($args{recordType});
500     delete ($args{file});
501     delete ($args{data});
502     delete ($args{force});
503
504     my $rg = $self->_makeRecordGroup(%args);
505
506     # If no record type is given, then try to find it out from the
507     # file extension;
508     unless ($rectype) {
509         if (my ($ext) = $fname =~ /\.(\w+)$/) {
510             my $rg2 = $self->_getRecordGroup($rg->{groupName},$ext);
511             $rectype = $rg2->{recordType};
512         } 
513     }
514
515     $rg->{databaseName} = "Default" unless ($rg->{databaseName});
516
517     unless ($rectype) {
518         $rectype="";
519     }
520     return ($rg, $rectype, $sysno, $match, $fname, $buff, $len, $force);
521 }
522
523 # -----------------------------------------------------------------------------
524 # CQL stuff
525 sub cqlmap {
526     my ($self,$mapfile) = @_;
527     if ($#_ > 0) {
528         if ($self->{cql_mapfile} ne $mapfile) {
529             unless (-f $mapfile) {
530                 croak("Cannot find $mapfile");
531             }
532             if (defined ($self->{cql_ct})) {
533               IDZebra::cql_transform_close($self->{cql_ct});
534             }
535             $self->{cql_ct} = IDZebra::cql_transform_open_fname($mapfile);
536             $self->{cql_mapfile} = $mapfile;
537         }
538     }
539     return ($self->{cql_mapfile});
540 }
541
542 sub cql2pqf {
543     my ($self, $cqlquery) = @_;
544     unless (defined($self->{cql_ct})) {
545         croak("CQL map file is not specified yet.");
546     }
547     my $res = "\0" x 2048;
548     my $r = IDZebra::cql2pqf($self->{cql_ct}, $cqlquery, $res, 2048);
549     if ($r) {
550 #       carp ("Error transforming CQL query: '$cqlquery', status:$r");
551     }
552     $res=~s/\0.+$//g;
553     return ($res,$r); 
554 }
555
556
557 # -----------------------------------------------------------------------------
558 # Search 
559 # -----------------------------------------------------------------------------
560 sub search {
561     my ($self, %args) = @_;
562
563     $self->checkzh;
564
565     if ($args{cqlmap}) { $self->cqlmap($args{cqlmap}); }
566
567     my $query;
568     if ($args{pqf}) {
569         $query = $args{pqf};
570     }
571     elsif ($args{cql}) {
572         my $cqlstat;
573         ($query, $cqlstat) =  $self->cql2pqf($args{cql});
574         unless ($query) {
575             croak ("Failed to transform query: '$args{cql}', ".
576                    "status: ($cqlstat)");
577         }
578     }
579     unless ($query) {
580         croak ("No query given to search");
581     }
582
583     my @origdbs;
584
585     if ($args{databases}) {
586         @origdbs = $self->databases;
587         $self->databases(@{$args{databases}});
588     }
589
590     my $rsname = $args{rsname} ? $args{rsname} : $self->_new_setname;
591
592     my $rs = $self->_search_pqf($query, $rsname);
593
594     if ($args{databases}) {
595         $self->databases(@origdbs);
596     }
597
598     if ($args{sort}) {
599         if ($rs->errCode) {
600             carp("Sort skipped due to search error: ".
601                  $rs->errCode);
602         } else {
603             $rs->sort($args{sort});
604         }
605     }
606
607     return ($rs);
608 }
609
610 sub _new_setname {
611     my ($self) = @_;
612     return ("set_".$self->{rscount}++);
613 }
614
615 sub _search_pqf {
616     my ($self, $query, $setname) = @_;
617
618     my $hits = IDZebra::search_PQF($self->{zh},
619                                    $self->{odr_input},
620                                    $self->{odr_output},
621                                    $query,
622                                    $setname);
623
624     my $rs  = IDZebra::Resultset->new($self,
625                                       name        => $setname,
626                                       recordCount => $hits,
627                                       errCode     => $self->errCode,
628                                       errString   => $self->errString);
629     return($rs);
630 }
631
632 # -----------------------------------------------------------------------------
633 # Sort
634 #
635 # Sorting of multiple result sets is not supported by zebra...
636 # -----------------------------------------------------------------------------
637
638 sub sortResultsets {
639     my ($self, $sortspec, $setname, @sets) = @_;
640
641     $self->checkzh;
642
643     if ($#sets > 0) {
644         croak ("Sorting/merging of multiple resultsets is not supported now");
645     }
646
647     my @setnames;
648     my $count = 0;
649     foreach my $rs (@sets) {
650         push (@setnames, $rs->{name});
651         $count += $rs->{recordCount};  # is this really sure ??? It doesn't 
652                                        # matter now...
653     }
654
655     my $status = IDZebra::sort($self->{zh},
656                                $self->{odr_output},
657                                $sortspec,
658                                $setname,
659                                \@setnames);
660
661     my $errCode = $self->errCode;
662     my $errString = $self->errString;
663
664     logf (LOG_LOG, "Sort status $setname: %d, errCode: %d, errString: %s", 
665           $status, $errCode, $errString);
666
667     if ($status || $errCode) {$count = 0;}
668
669     my $rs  = IDZebra::Resultset->new($self,
670                                       name        => $setname,
671                                       recordCount => $count,
672                                       errCode     => $errCode,
673                                       errString   => $errString);
674     
675     return ($rs);
676 }
677 # -----------------------------------------------------------------------------
678 # Scan
679 # -----------------------------------------------------------------------------
680 sub scan {
681     my ($self, %args) = @_;
682
683     $self->checkzh;
684
685     unless ($args{expression}) {
686         croak ("No scan expression given");
687     }
688
689     my $sl = IDZebra::ScanList->new($self,%args);
690
691     return ($sl);
692 }
693
694 # ============================================================================
695
696 __END__
697
698 =head1 NAME
699
700 IDZebra::Session - A Zebra database server session for update and retrieval
701
702 =head1 SYNOPSIS
703
704   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
705   $sess->open();
706
707   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
708                                  groupName  => 'demo1');
709
710   $sess->group(groupName => 'demo2');
711
712   $sess->init();
713
714   $sess->begin_trans;
715
716   $sess->update(path      =>  'lib');
717
718   my $s1=$sess->update_record(data       => $rec1,
719                               recordType => 'grs.perl.pod',
720                               groupName  => "demo1",
721                               );
722
723   my $stat = $sess->end_trans;
724
725   $sess->databases('demo1','demo2');
726
727   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
728                           cql       => 'dc.title=IDZebra',
729                           databases => [qw(demo1 demo2)]);
730   $sess->close;
731
732 =head1 DESCRIPTION
733
734 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. 
735
736 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. 
737
738 =head1 OPENING AND CLOSING A ZEBRA SESSIONS
739
740 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
741
742   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
743   $sess->open();
744
745 or
746
747   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg');
748
749 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:
750
751   $sess->close();
752
753 This will
754   - close all transactions
755   - destroy all result sets and scan lists 
756   - close the session
757
758 Note, that if I<shadow registers> are enabled, the changes will not be committed automatically.
759
760 In the future different database access methods are going to be available, 
761 like:
762
763   $sess = IDZebra::Session->open(server => 'ostrich.technomat.hu:9999');
764
765 You can also use the B<record group> arguments described below directly when calling the constructor, or the open method:
766
767   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
768                                  groupName  => 'demo');
769
770
771 =head1 RECORD GROUPS 
772
773 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). 
774
775 For each open session a default record group is assigned. You can configure it in the constructor, or by the B<group> method:
776
777   $sess->group(groupName => ..., ...)
778
779 The following options are available:
780
781 =over 4
782
783 =item B<groupName>
784
785 This will select the named record group, and load the corresponding settings from the configuration file. All subsequent values will overwrite those...
786
787 =item B<databaseName>
788
789 The name of the (logical) database the updated records will belong to. 
790
791 =item B<path>
792
793 This path is used for directory updates (B<update>, B<delete> methods);
794  
795 =item B<recordId>
796
797 This option determines how to identify your records. See I<Zebra manual: Locating Records>
798
799 =item B<recordType>
800
801 The record type used for indexing. 
802
803 =item B<flagStoreData> 
804
805 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). 
806
807 =item B<flagStoreKeys>
808
809 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. 
810
811 =item B<flagRw>
812
813 ?
814
815 =item B<fileVerboseLimit>
816
817 Skip log messages, when doing a directory update, and the specified number of files are processed...
818
819 =item B<databaseNamePath>
820
821 ?
822
823 =item B<explainDatabase>
824
825 The name of the explain database to be used
826
827 =item B<followLinks>              
828
829 Follow links when doing directory update.
830
831 =back
832
833 You can use the same parameters calling all update methods.
834
835 =head1 TRANSACTIONS (READ / WRITE LOCKS)
836
837 A transaction is a block of record update (insert / modify / delete) or retrieval procedures. So, all call to such function will implicitly start a transaction, unless one is already started by
838
839   $sess->begin_trans;
840
841 or 
842
843   $sess->begin_trans(TRANS_RW)
844
845 (these two are equivalents). The effect of this call is a kind of lock: if you call is a write lock is put on the registers, so other processes trying to update the database will be blocked. If there is already an RW (Read-Write) transaction opened by another process, the I<begin_trans> call will be blocked.
846
847 You can also use
848
849   $sess->begin_trans(TRANS_RO),
850
851 if you would like to put on a "read lock". This one is B<deprecated>, as while you have explicitly opened a transaction for read, you can't open another one for update. For example:
852
853   $sess->begin_trans(TRANS_RO);
854   $sess->begin_tran(TRANS_RW); # invalid, die here
855   $sess->end_trans;
856   $sess->end_trans;
857
858 is invalid, but
859
860   $sess->begin_tran(TRANS_RW); 
861   $sess->begin_trans(TRANS_RO);
862   $sess->end_trans;
863   $sess->end_trans;
864
865 is valid, but probably useless. Note again, that for each retrieval call, an RO transaction is opened. I<TRANS_RW> and I<TRANS_RO> are exported by default by IDZebra::Session.pm.
866
867 For multiple per-record I<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
868
869   $stat = $sess->end_trans;
870
871 The return value is a ZebraTransactionStatus object, containing the following members as a hash reference:
872
873   $stat->{processed} # Number of records processed
874   $stat->{updated}   # Number of records processed
875   $stat->{deleted}   # Number of records processed
876   $stat->{inserted}  # Number of records processed
877   $stat->{stime}     # System time used
878   $stat->{utime}     # User time used
879
880 Normally, if the perl code dies due to some runtime error, or the session is closed, then the API attempts to close all pending transactions.
881
882 =head1 THE SHADOW REGISTERS
883
884 The Zebra server supports updating of the index structures. That is, you can add, modify, or remove records from databases managed by Zebra without rebuilding the entire index. Since this process involves modifying structured files with various references between blocks of data in the files, the update process is inherently sensitive to system crashes, or to process interruptions: Anything but a successfully completed update process will leave the register files in an unknown state, and you will essentially have no recourse but to re-index everything, or to restore the register files from a backup medium. Further, while the update process is active, users cannot be allowed to access the system, as the contents of the register files may change unpredictably. 
885
886 You can solve these problems by enabling the shadow register system in Zebra. During the updating procedure, zebraidx will temporarily write changes to the involved files in a set of "shadow files", without modifying the files that are accessed by the active server processes. If the update procedure is interrupted by a system crash or a signal, you simply repeat the procedure - the register files have not been changed or damaged, and the partially written shadow files are automatically deleted before the new updating procedure commences. 
887
888 At the end of the updating procedure (or in a separate operation, if you so desire), the system enters a "commit mode". First, any active server processes are forced to access those blocks that have been changed from the shadow files rather than from the main register files; the unmodified blocks are still accessed at their normal location (the shadow files are not a complete copy of the register files - they only contain those parts that have actually been modified). If the commit process is interrupted at any point during the commit process, the server processes will continue to access the shadow files until you can repeat the commit procedure and complete the writing of data to the main register files. You can perform multiple update operations to the registers before you commit the changes to the system files, or you can execute the commit operation at the end of each update operation. When the commit phase has completed successfully, any running server processes are instructed to switch their operations to the new, operational register, and the temporary shadow files are deleted. 
889
890 By default, (in the API !) the use of shadow registers is disabled. If zebra is configured that way (there is a "shadow" entry in zebra.cfg), then the shadow system can be enabled by calling:
891
892  $sess->shadow(1);
893
894 or disabled by
895
896  $sess->shadow(0);
897
898 If shadow system is enabled, then you have to commit changes you did, by calling:
899  
900  $sess->commit;
901
902 Note, that you can also determine shadow usage in the session constructor:
903
904  $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
905                                 shadow    => 1);
906  
907 Changes to I<shadow> will not have effect, within a I<transaction> (ie.: a transaction is started either with shadow enabled or disabled). For more details, read Zebra documentation: I<Safe Updating - Using Shadow Registers>.
908
909 =head1 UPDATING DATA
910
911 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:
912
913   $sess->update(path      =>  'lib');
914
915 This will update the database with the files in directory "lib", according to the current record group settings.
916
917   $sess->update();
918
919 This will update the database with the files, specified by the default record group setting. I<path> has to be specified there...
920
921   $sess->update(groupName => 'demo1',
922                 path      =>  'lib');
923
924 Update the database with files in "lib" according to the settings of group "demo1"
925
926   $sess->delete(groupName => 'demo1',
927                 path      =>  'lib');
928
929 Delete the records derived from the files in directory "lib", according to the "demo1" group settings. Sounds complex? Read zebra documentation about identifying records.
930
931 You can also update records one by one, even directly from the memory:
932
933   $sysno = $sess->update_record(data       => $rec1,
934                                 recordType => 'grs.perl.pod',
935                                 groupName  => "demo1");
936
937 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.
938
939 You can also index a single file:
940
941   $sysno = $sess->update_record(file => "lib/IDZebra/Data1.pm");
942
943 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):
944
945   $sysno = $sess->update_record(data => $rec1,
946                                 file => "lib/IDZebra/Data1.pm");
947
948 And some crazy stuff:
949
950   $sysno = $sess->delete_record(sysno => $sysno);
951
952 where sysno in itself is sufficient to identify the record
953
954   $sysno = $sess->delete_record(data => $rec1,
955                                 recordType => 'grs.perl.pod',
956                                 groupName  => "demo1");
957
958 This case the record is extracted, and if already exists, located in the database, then deleted... 
959
960   $sysno = $sess->update_record(data       => $rec1,
961                                 match      => $myid,
962                                 recordType => 'grs.perl.pod',
963                                 groupName  => "demo1");
964
965 Don't try this at home! This case, the record identifier string (which is normally generated according to the rules set in I<recordId> member of the record group, or in the I<recordId> parameter) is provided directly.... Looks much better this way:
966
967   $sysno = $sess->update_record(data          => $rec1,
968                                 databaseName  => 'books',
969                                 recordId      => '(bib1,ISBN)',
970                                 recordType    => 'grs.perl.pod',
971                                 flagStoreData => 1,
972                                 flagStoreKeys => 1);
973
974 You can notice, that it's not necessary to define a record group in zebra.cfg: you can do it "on the fly" in your code.
975
976 B<Important:> Note, that one record can be updated only once within a transaction - all subsequent updates are skipped. If you'd like to override this feature, use the I<force=E<gt>1> flag:
977
978   $sysno = $sess->update_record(data       => $rec1,
979                                 recordType => 'grs.perl.pod',
980                                 groupName  => "demo1",
981                                 force      => 1);
982
983 If you don't like to update the record, if it alerady exists, use the I<insert_record> method:
984
985   $sysno = $sess->insert_record(data       => $rec1,
986                                 recordType => 'grs.perl.pod',
987                                 groupName  => "demo1");
988
989 In this case, sysno will be -1, if the record could not be added, because there was already one in the database, with the same record identifier (generated according to the I<recordId> setting).
990
991 =head1 DATABASE SELECTION
992
993 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. 
994
995 For searching, you can select databases by calling:
996
997   $sess->databases('db1','db2');
998
999 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:
1000   
1001   @dblist = $sess->databases();
1002
1003 =head1 SEARCHING
1004
1005 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:
1006
1007   $rs = $sess->search(databases => [qw(demo1,demo2)], # optional
1008                       pqf       => '@attr 1=4 computer');
1009
1010 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.
1011
1012 =head2 CCL searching
1013
1014 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. 
1015
1016 The CCL searching is not currently supported by this API.
1017
1018 =head2 CQL searching
1019
1020 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. 
1021
1022 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:
1023
1024   $sess->cqlmap($mapfile);
1025
1026 Or, you can directly provide the I<mapfile> parameter for the search:
1027
1028   $rs = $sess->search(cqlmap    => 'demo/cql.map',
1029                       cql       => 'dc.title=IDZebra');
1030
1031 As you see, CQL searching is so simple: just give the query in the I<cql> parameter.
1032
1033 =head2 Sorting
1034
1035 If you'd like the search results to be sorted, use the I<sort> parameter:
1036
1037   $rs = $sess->search(cql       => 'IDZebra',
1038                       sort      => '1=4 ia');
1039   
1040 Note, that B<currently> this is (almost) equivalent to
1041
1042   $rs = $sess->search(cql       => 'IDZebra');
1043   $rs->sort('1=4 ia');
1044   
1045 but in the further versions of Zebra and this API a single phase search and sort will take place, optimizing performance. For more details on sorting, see I<IDZebra::ResultSet> manpage.
1046
1047 =head1 RESULTSETS
1048
1049 As you have seen, the result of the search request is a I<Resultset> object.
1050 It contains number of hits, and search status, and can be used to sort and retrieve the resulting records.
1051
1052   $count = $rs->count;
1053
1054   printf ("RS Status is %d (%s)\n", $rs->errCode, $rs->errString);
1055
1056 I<$rs-E<gt>errCode> is 0, if there were no errors during search. Read the I<IDZebra::Resultset> manpage for more details.
1057
1058 =head1 SCANNING
1059
1060 Zebra supports scanning index values. The result of the 
1061
1062   $sl = $sess->scan(expression => "a");
1063
1064 call is an I<IDZebra::ScanList> object, what you can use to list the values. The scan expression has to be provided in a PQF like format. Examples:
1065
1066 B< a> (scan trough words of "default", "Any" indexes)
1067
1068
1069 B< @attr 1=1016 a> (same effect)
1070
1071
1072 B< @attr 1=4 @attr 6=2 a>  (scan trough titles as phrases)
1073
1074 An illegal scan expression will cause your code to die. If you'd like to select databases just for the scan call, you can optionally use the I<databases> parameter:
1075
1076   $sl = $sess->scan(expression => "a",
1077                     databases  => [qw(demo1 demo2)]);
1078   
1079 You can use the I<IDZebra::ScanList> object returned by the i<scan> method, to reach the result. Check I<IDZebra::ScanList> manpage for more details.
1080
1081 =head1 SESSION STATUS AND ERRORS
1082
1083 Most of the API calls causes die, if an error occures. You avoid this, by using eval {} blocks. The following methods are available to get the status of Zebra service:
1084
1085 =over 4
1086
1087 =item B<errCode>
1088
1089 The Zebra provided error code... (for the result of the last call);
1090
1091 =item B<errString>
1092
1093 Error string corresponding to the message
1094
1095 =item B<errAdd>
1096
1097 Additional information for the status
1098
1099 =back
1100
1101 This functionality may change, see TODO.
1102
1103 =head1 LOGGING AND MISC. FUNCTIONS
1104
1105 Zebra provides logging facility for the internal events, and also for application developers trough the API. See manpage I<IDZebra::Logger> for details.
1106
1107 =over 4
1108
1109 =item B<IDZebra::LogFile($filename)>
1110
1111 Will set the output file for logging. By default it's STDERR;
1112
1113 =item B<IDZebra::LogLevel(15)>
1114
1115 Set log level. 0 for no logs. See IDZebra::Logger for usable flags.
1116
1117 =back
1118
1119 Some other functions
1120
1121 =over 4
1122
1123 =item B<$sess-E<gt>init>
1124
1125 Initialize, and clean registers. This will remove all data!
1126
1127 =item B<$sess-E<gt>compact>
1128
1129 Compact the registers (? does this work)
1130
1131 =item B<$sess-E<gt>show>
1132
1133 Doesn't have too much meaning. Don't try :)
1134
1135 =back
1136
1137 =head1 TODO
1138
1139 =over 4
1140
1141 =item B<Clean up error handling>
1142
1143 By default all zebra errors should cause die. (such situations could be avoided by using eval {}), and then check for errCode, errString... An optional flag or package variable should be introduced to override this, and skip zebra errors, to let the user decide what to do.
1144
1145 =item B<Make the package self-distributable>
1146
1147 Build and link with installed header and library files
1148
1149 =item B<Testing>
1150
1151 Test shadow system, unicode...
1152
1153 =item B<C API>
1154
1155 Cleanup, arrange, remove redundancy
1156
1157 =back
1158
1159 =head1 COPYRIGHT
1160
1161 Fill in
1162
1163 =head1 AUTHOR
1164
1165 Peter Popovics, pop@technomat.hu
1166
1167 =head1 SEE ALSO
1168
1169 Zebra documentation, Zebra::ResultSet, Zebra::ScanList, Zebra::Logger manpages
1170
1171 =cut