Added documentation and test for the IDZebra::Resultset object
[idzebra-moved-to-github.git] / perl / lib / IDZebra / Session.pm
1 # $Id: Session.pm,v 1.9 2003-03-03 12:14:27 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.9 $ =~ /\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     return ($rs);
581 }
582
583 sub _new_setname {
584     my ($self) = @_;
585     return ("set_".$self->{rscount}++);
586 }
587
588 sub _search_pqf {
589     my ($self, $query, $setname) = @_;
590
591     my $hits = IDZebra::search_PQF($self->{zh},
592                                    $self->{odr_input},
593                                    $self->{odr_output},
594                                    $query,
595                                    $setname);
596
597     my $rs  = IDZebra::Resultset->new($self,
598                                       name        => $setname,
599                                       recordCount => $hits,
600                                       errCode     => $self->errCode,
601                                       errString   => $self->errString);
602     return($rs);
603 }
604
605 # -----------------------------------------------------------------------------
606 # Sort
607 #
608 # Sorting of multiple result sets is not supported by zebra...
609 # -----------------------------------------------------------------------------
610
611 sub sortResultsets {
612     my ($self, $sortspec, $setname, @sets) = @_;
613
614     $self->checkzh;
615
616     my @setnames;
617     my $count = 0;
618     foreach my $rs (@sets) {
619         push (@setnames, $rs->{name});
620         $count += $rs->{recordCount};  # is this really sure ??? It doesn't 
621                                        # matter now...
622     }
623
624     my $status = IDZebra::sort($self->{zh},
625                                $self->{odr_output},
626                                $sortspec,
627                                $setname,
628                                \@setnames);
629
630     my $errCode = $self->errCode;
631     my $errString = $self->errString;
632
633     if ($status || $errCode) {$count = 0;}
634
635     my $rs  = IDZebra::Resultset->new($self,
636                                       name        => $setname,
637                                       recordCount => $count,
638                                       errCode     => $errCode,
639                                       errString   => $errString);
640     
641     return ($rs);
642 }
643
644 # ============================================================================
645
646
647 __END__
648
649 =head1 NAME
650
651 IDZebra::Session - A Zebra database server session for update and retrieval
652
653 =head1 SYNOPSIS
654
655   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
656   $sess->open();
657
658   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
659                                  groupName  => 'demo1');
660
661   $sess->group(groupName => 'demo2');
662
663   $sess->init();
664
665   $sess->begin_trans;
666
667   $sess->update(path      =>  'lib');
668
669   my $s1=$sess->update_record(data       => $rec1,
670                               recordType => 'grs.perl.pod',
671                               groupName  => "demo1",
672                               );
673
674   my $stat = $sess->end_trans;
675
676   $sess->databases('demo1','demo2');
677
678   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
679                           cql       => 'dc.title=IDZebra',
680                           databases => [qw(demo1 demo2)]);
681   $sess->close;
682
683 =head1 DESCRIPTION
684
685 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. 
686
687 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. 
688
689 =head1 OPENING AND CLOSING A ZEBRA SESSIONS
690
691 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
692
693   $sess = IDZebra::Session->new(configFile => 'demo/zebra.cfg');
694   $sess->open();
695
696 or
697
698   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg');
699
700 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:
701
702   $sess->close();
703
704 This will
705   - close all transactions
706   - destroy all result sets
707   - close the session
708
709 In the future different database access methods are going to be available, 
710 like:
711
712   $sess = IDZebra::Session->open(server => 'ostrich.technomat.hu:9999');
713
714 You can also use the B<record group> arguments described below directly when calling the constructor, or the open method:
715
716   $sess = IDZebra::Session->open(configFile => 'demo/zebra.cfg',
717                                  groupName  => 'demo');
718
719
720 =head1 RECORD GROUPS 
721
722 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). 
723
724 For each open session a default record group is assigned. You can configure it in the constructor, or by the B<set_group> method:
725
726   $sess->group(groupName => ..., ...)
727
728 The following options are available:
729
730 =over 4
731
732 =item B<groupName>
733
734 This will select the named record group, and load the corresponding settings from the configuration file. All subsequent values will overwrite those...
735
736 =item B<databaseName>
737
738 The name of the (logical) database the updated records will belong to. 
739
740 =item B<path>
741
742 This path is used for directory updates (B<update>, B<delete> methods);
743  
744 =item B<recordId>
745
746 This option determines how to identify your records. See I<Zebra manual: Locating Records>
747
748 =item B<recordType>
749
750 The record type used for indexing. 
751
752 =item B<flagStoreData> 
753
754 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). 
755
756 =item B<flagStoreKeys>
757
758 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. 
759
760 =item B<flagRw>
761
762 ?
763
764 =item B<fileVerboseLimit>
765
766 Skip log messages, when doing a directory update, and the specified number of files are processed...
767
768 =item B<databaseNamePath>
769
770 ?
771
772 =item B<explainDatabase>
773
774 The name of the explain database to be used
775
776 =item B<followLinks>              
777
778 Follow links when doing directory update.
779
780 =back
781
782 You can use the same parameters calling all update methods.
783
784 =head1 TRANSACTIONS (WRITE LOCKS)
785
786 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
787
788   $sess->begin_trans;
789
790 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
791
792   $stat = $sess->end_trans;
793
794 The return value is a ZebraTransactionStatus object, containing the following members as a hash reference:
795
796   $stat->{processed} # Number of records processed
797   $stat->{updated}   # Number of records processed
798   $stat->{deleted}   # Number of records processed
799   $stat->{inserted}  # Number of records processed
800   $stat->{stime}     # System time used
801   $stat->{utime}     # User time used
802
803 =head1 UPDATING DATA
804
805 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:
806
807   $sess->update(path      =>  'lib');
808
809 This will update the database with the files in directory "lib", according to the current record group settings.
810
811   $sess->update();
812
813 This will update the database with the files, specified by the default record group setting. I<path> has to be specified there...
814
815   $sess->update(groupName => 'demo1',
816                 path      =>  'lib');
817
818 Update the database with files in "lib" according to the settings of group "demo1"
819
820   $sess->delete(groupName => 'demo1',
821                 path      =>  'lib');
822
823 Delete the records derived from the files in directory "lib", according to the "demo1" group settings. Sounds complex? Read zebra documentation about identifying records.
824
825 You can also update records one by one, even directly from the memory:
826
827   $sysno = $sess->update_record(data       => $rec1,
828                                 recordType => 'grs.perl.pod',
829                                 groupName  => "demo1");
830
831 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.
832
833 You can also index a single file:
834
835   $sysno = $sess->update_record(file => "lib/IDZebra/Data1.pm");
836
837 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):
838
839   $sysno = $sess->update_record(data => $rec1,
840                                 file => "lib/IDZebra/Data1.pm");
841
842 And some crazy stuff:
843
844   $sysno = $sess->delete_record(sysno => $sysno);
845
846 where sysno in itself is sufficient to identify the record
847
848   $sysno = $sess->delete_record(data => $rec1,
849                                 recordType => 'grs.perl.pod',
850                                 groupName  => "demo1");
851
852 This case the record is extracted, and if already exists, located in the database, then deleted... 
853
854   $sysno = $sess->delete_record(data       => $rec1,
855                                 match      => $myid,
856                                 recordType => 'grs.perl.pod',
857                                 groupName  => "demo1");
858
859 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....
860
861
862 B<Important:> Note, that one record can be updated only once within a transaction - all subsequent updates are skipped. 
863
864 =head1 DATABASE SELECTION
865
866 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. 
867
868 For searching, you can select databases by calling:
869
870   $sess->databases('db1','db2');
871
872 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:
873   
874   @dblist = $sess->databases();
875
876 =head1 SEARCHING
877
878 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:
879
880   $rs = $sess->search(databases => [qw(demo1,demo2)], # optional
881                       pqf       => '@attr 1=4 computer');
882
883 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.
884
885 =head2 CCL searching
886
887 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. 
888
889 The CCL searching is not currently supported by this API.
890
891 =head2 CQL searching
892
893 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. 
894
895 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:
896
897   $sess->cqlmap($mapfile);
898
899 Or, you can directly provide the I<mapfile> parameter for the search:
900
901   my $rs1 = $sess->search(cqlmap    => 'demo/cql.map',
902                           cql       => 'dc.title=IDZebra');
903
904 As you see, CQL searching is so simple: just give the query in the I<cql> parameter.
905
906 =head1 RESULTSETS
907
908 As you have seen, the result of the search request is a I<Resultset> object.
909 It contains number of hits, and search status, and can be used to sort and retrieve the resulting records.
910
911   $count = $rs->count;
912
913   printf ("RS Status is %d (%s)\n", $rs->errCode, $rs->errString);
914
915 I<$rs-E<gt>errCode> is 0, if there were no errors during search. Read the I<IDZebra::Resultset> manpage for more details.
916
917 =head1 MISC FUNCTIONS
918
919 =head1 COPYRIGHT
920
921 Fill in
922
923 =head1 AUTHOR
924
925 Peter Popovics, pop@technomat.hu
926
927 =head1 SEE ALSO
928
929 IDZebra, IDZebra::Data1, Zebra documentation
930
931 =cut