=head1 NAME

SuikaWiki::DB::Logical --- SuikaWiki WikiDatabase: Logical database consists of multiple datasources

=head1 DESCRIPTION

This module provides a logical database implementation.  It does not itself have
physical datasource at all.  With database property name asspcoatopms, multiple 
database instances that have common WikiDatabase interface can be used as 
datasources.

This module is part of SuikaWiki.

=cut

package SuikaWiki::DB::Logical;
use strict;
our $VERSION=do{my @r=(q$Revision: 1.5 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
require SuikaWiki::DB::Util;
push our @ISA, 'SuikaWiki::DB::Util::template';

# new: inherited

sub get ($$$) {
  my ($self, $prop, $key) = @_;
  if ($self->{prop}->{$prop}) {
    local $Error::Depth = $Error::Depth + 1;
    unless ($self->{opened}->{$prop}) {
      $self->open_prop (prop => $prop);
    }
    return $self->{prop}->{$prop}->{-db}->get 
      ($self->{prop}->{$prop}->{-prop},
       $self->{prop}->{$prop}->{-key_mapper}->($self, $key));
  } else {
    return undef;
  }
}

sub set ($$$$) {
  my ($self, $prop, $key => $value) = @_;
  if ($self->{prop}->{$prop}) {
    local $Error::Depth = $Error::Depth + 1;
    unless ($self->{opened}->{$prop}) {
      $self->open_prop (prop => $prop);
    }
    $self->{prop}->{$prop}->{-db}->set
      ($self->{prop}->{$prop}->{-prop},
       $self->{prop}->{$prop}->{-key_mapper}->($self, $key) => $value);
  } else {
    report SuikaWiki::DB::Util::Error
      -type => 'KEY_SAVE',
      -object => $self, method => 'set',
      prop => $prop, key => $key;
  }
}

sub exist ($$$) {
  my ($self, $prop, $key) = @_;
  if ($self->{prop}->{$prop}) {
    local $Error::Depth = $Error::Depth + 1;
    unless ($self->{opened}->{$prop}) {
      $self->open_prop (prop => $prop);
    }
    return $self->{prop}->{$prop}->{-db}->exist
      ($self->{prop}->{$prop}->{-prop},
       $self->{prop}->{$prop}->{-key_mapper}->($self, $key));
  } else {
    return 0;
  }
}

sub delete ($$$) {
  my ($self, $prop, $key) = @_;
  if ($self->{prop}->{$prop}) {
    local $Error::Depth = $Error::Depth + 1;
    unless ($self->{opened}->{$prop}) {
      $self->open_prop (prop => $prop);
    }
    $self->{prop}->{$prop}->{-db}->delete
      ($self->{prop}->{$prop}->{-prop},
       $self->{prop}->{$prop}->{-key_mapper}->($self, $key));
  }
}

sub keys ($$;%) {
  my ($self, $prop, %opt) = @_;
  if ($self->{prop}->{$prop}) {
    local $Error::Depth = $Error::Depth + 1;
    unless ($self->{opened}->{$prop}) {
      $self->open_prop (prop => $prop);
    }
    $opt{-ns} = $self->{prop}->{$prop}->{-key_mapper}->($self, $opt{-ns});
    return
      map {$self->{prop}->{$prop}->{-key_rev_mapper}->($self, $_)}
      $self->{prop}->{$prop}->{-db}->keys
      ($self->{prop}->{$prop}->{-prop}, %opt);
  } else {
    return ();
  }
}

# close: Inherited

=head1 METHODS

This module provides common interface of SuikaWiki WikiDatabase
modules. See documentation of C<SuikaWiki::DB>.

In addition, this module implements additional methods.

=over 4

=item _set_prop_db ($prop_name, {options})

Addosiates actual database with property name of this logical (virtual)
database, or remove its association by specifying C<undef> instead of
C<{options}>.

Options:

=over 4

=item -db => $database_instance (REQUIRED)

Instance of database module, which implements common WikiDatabase interface.

=item -prop => $property_name (Default: same as $prop_name)

Property name used to access to $database_instance.  With this option,
different property name from datasource's one can be used,
eg. maps $prop_name eq 'subdb_foo' to $property_name eq 'foo'.

=item -key_mapper => sub ($self, $key) (Default: sub {$key})

Mapper from this virtual database's keyname to sub database's one.

=item -key_rev_mapper => sub ($self, $key) (Default: sub {$key})

Mapper from sub database's keyname to this virtual database's keyname.

Note that -key_mapper (-key_rev_mapper ($key)) need not equal to $key,
although it seems an unusual case.

=back

=cut

sub _set_prop_db ($$$) {
  my ($self, $prop, $db_and_opt) = @_;
  $self->{prop}->{$prop} = $db_and_opt;
  $db_and_opt->{-prop} = $prop unless defined $db_and_opt->{-prop};
  $db_and_opt->{-key_mapper} ||= \&__default_key_mapper;
  $db_and_opt->{-key_rev_mapper} ||= \&__default_key_rev_mapper;
  $db_and_opt->{-db_close} ||= sub {
    my %opt = @_;
    local $Error::Depth = $Error::Depth + 1;
    $opt{prop_info}->{-db}->close_prop (prop => $opt{prop_info}->{-prop});
  };
}

sub __default_key_mapper ($$) {
  #my ($self, $key) = @_;
  #$key;
  $_[1];
}
sub __default_key_rev_mapper ($$) {
  #my ($self, $key) = @_;
  #$key;
  $_[1];
}

sub ___open_prop ($$) {
  my ($self, $opt) = @_;
  return "0 but true" if defined $self->{prop}->{$opt->{prop}}->{-db};
  local $Error::Depth = $Error::Depth + 2;
  $self->{prop}->{$opt->{prop}}->{-db} 
    = $self->{prop}->{$opt->{prop}}->{-db_open}->(metadb => $self);
  1;
}

sub ___close_prop ($$) {
  my ($self, $opt) = @_;
  return "0 but true" unless $self->{opened}->{$opt->{prop}};
  local $Error::Depth = $Error::Depth + 2;
  $self->{prop}->{$_}->{-db_close}->(metadb => $self, 
                                     prop_info => $self->{prop}->{$_});
  delete $self->{prop}->{$_}->{-db}
    if $self->{prop}->{$_}->{-db_open};
  1;
}

=back

=head1 LICENSE

Copyright 2003 Wakaba <w@suika.fam.cx>

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut

1; # $Date: 2003/12/06 02:19:09 $