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