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