/usr/local/CPAN/MusicRoom/MusicRoom/LogicalModel.pm
# This should map from a logical model to a physical one,
# for the moment just use it to list physical tables
package MusicRoom::LogicalModel;
use strict;
use warnings;
use Carp;
use IO::File;
use MusicRoom;
use MusicRoom::Date;
use MusicRoom::File;
use MusicRoom::Artist;
use MusicRoom::Song;
use MusicRoom::Album;
use MusicRoom::Track;
use MusicRoom::Zone;
# Globally available enum definitions
my %enums =
(
format => [MusicRoom::File::formats()],
);
my @databases =
(
core =>
[
MusicRoom::Artist::table_spec(),
MusicRoom::Song::table_spec(),
MusicRoom::Album::table_spec(),
MusicRoom::Zone::table_spec(),
MusicRoom::Track::table_spec(),
],
);
# Convert the ordered list (which we need to know what
# order to create things in) into a set of nested
# hashes (which will be easier to use for most tasks)
my(%databases,%db_idxes);
for(my $db_idx=0;$db_idx<=$#databases;$db_idx += 2)
{
my $db_name = $databases[$db_idx];
croak("Two databases called $db_name")
if(defined $databases{$db_name});
croak("Cannot use empty name as db_name")
if($db_name eq "");
my %db_tables = ();
for(my $tab_idx=0;$tab_idx<=$#{$databases[$db_idx+1]};$tab_idx += 2)
{
my $tab_name = $databases[$db_idx+1]->[$tab_idx];
croak("Two tables called $tab_name in $db_name")
if(defined $db_tables{$tab_name});
croak("Cannot use empty name as table name in $db_name")
if($tab_name eq "");
my %tab_attribs = ();
for(my $atr_idx=0;$atr_idx<=$#{$databases[$db_idx+1]->[$tab_idx+1]};$atr_idx += 2)
{
my $attrib = $databases[$db_idx+1]->[$tab_idx+1]->[$atr_idx];
croak("Two attributes called $attrib in $db_name:$tab_name")
if(defined $tab_attribs{$attrib});
croak("Cannot use empty name as attribute name in $db_name:$tab_name")
if($tab_name eq "");
$tab_attribs{$attrib} =
$tab_attribs{$databases[$db_idx+1]->[$tab_idx+1]->[$atr_idx+1]};
}
$tab_attribs{""} = $tab_idx;
$db_tables{$tab_name} = \%tab_attribs;
}
$db_tables{""} = $db_idx;
$databases{$db_name} = \%db_tables;
}
sub list_dbs
{
my @dbs = ();
for(my $db_idx=0;$db_idx<=$#databases;$db_idx += 2)
{
push @dbs,$databases[$db_idx];
}
return @dbs;
}
sub list_physical_tables
{
# Provide a list of the physical tables that are held in
# a named database
my($db_name) = @_;
croak("$db_name: is not a listed database")
if(!defined $databases{$db_name});
my $db_idx = $databases{$db_name}->{""};
my @tables = ();
for(my $tab_idx=0;$tab_idx<=$#{$databases[$db_idx+1]};$tab_idx += 2)
{
push @tables,$databases[$db_idx+1]->[$tab_idx];
}
return @tables;
}
sub get_physical_columns
{
my($db_name,$tab_name) = @_;
croak("$db_name: is not a listed database")
if(!defined $databases{$db_name});
my $db_idx = $databases{$db_name}->{""};
croak("$db_name:$tab_name is not a listed table")
if(!defined $databases{$db_name}->{$tab_name});
my $tab_idx = $databases{$db_name}->{$tab_name}->{""};
my @attribs = ();
for(my $atr_idx=0;$atr_idx<=$#{$databases[$db_idx+1]->[$tab_idx+1]};$atr_idx += 2)
{
push @attribs,$databases[$db_idx+1]->[$tab_idx+1]->[$atr_idx];
}
return @attribs;
}
sub get_column_spec
{
my($db_name,$tab_name,$attrib) = @_;
croak("$db_name: is not a listed database")
if(!defined $databases{$db_name});
my $db_idx = $databases{$db_name}->{""};
croak("$db_name:$tab_name is not a listed table")
if(!defined $databases{$db_name}->{$tab_name});
my $tab_idx = $databases{$db_name}->{$tab_name}->{""};
for(my $atr_idx=0;$atr_idx<=$#{$databases[$db_idx+1]->[$tab_idx+1]};$atr_idx += 2)
{
return $databases[$db_idx+1]->[$tab_idx+1]->[$atr_idx+1]
if($databases[$db_idx+1]->[$tab_idx+1]->[$atr_idx] eq $attrib);
}
croak("$db_name:$tab_name.$attrib is not a listed attribute");
return undef;
}
sub get_physical_column
{
my($db_name,$tab_name,$attrib,$quiet) = @_;
my $spec = get_column_spec($db_name,$tab_name,$attrib);
return "CHAR(8)"
if($spec eq "STN");
return "CHAR($1)"
if($spec =~ /^text\:(\d+)$/i);
return "INTEGER"
if($spec =~ /^integer$/i);
# For the moment all bools are ints
return "INTEGER"
if($spec =~ /^boolean$/i);
if(ref($spec) eq "ARRAY")
{
# This is a list of values that this slot can have, for the
# moment lets just create a text entry that is long enough for
# all of them
my $max_len = 0;
foreach my $val (@{$spec})
{
$max_len = length($val) if(length($val) > $max_len);
}
croak("Empty enum in ${db_name}:${tab_name}.${attrib}?")
if($max_len == 0);
$max_len += 2;
return "CHAR($max_len)";
}
elsif($spec =~ /^enum\.(\S+)$/i)
{
# For the moment we'll store enumerations in strings just long
# enough to hold the maximum
my $enum = $1;
croak("There is no $enum enum defined for ${db_name}:$tab_name.$attrib")
if(!defined $enums{$enum});
my $max_len = 0;
foreach my $val (@{$enums{$enum}})
{
$max_len = length($val) if(length($val) > $max_len);
}
croak("Empty enum $enum?") if($max_len == 0);
$max_len += 2;
return "CHAR($max_len)";
}
if($spec =~ /^(\w+)\:(\w+)\.(\w+)$/)
{
# This duplicates an entry from another table
my $far_spec = get_physical_column($1,$2,$3,1);
return $far_spec if(defined $far_spec);
}
elsif($spec =~ /^(\w+)\.(\w+)$/)
{
# This duplicates an entry from another table
my $far_spec = get_physical_column($db_name,$1,$2,1);
return $far_spec if(defined $far_spec);
}
croak("Cannot convert \"$spec\" into physical form") if(!$quiet);
}
1;