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