Changeset 53

Show
Ignore:
Timestamp:
11/17/05 11:14:21 (5 years ago)
Author:
bradley
Message:

Added preliminary support for MySQL. It works fine for the most part,
but MySQL (3.x and 4.x any way) doesnt store the comments, so the user
has to change them manually (they default to the same as the table/column
names tho).

Location:
trunk/DB/Table
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • trunk/DB/Table/Row/Row.pm

    r50 r53  
    157157defined by the string $whereClause. Any place-holders to be interpolated should contain 
    158158question marks, as with the DBI, and the actual values should be passed in an array-reference 
    159 specifined by $bindParams. $options is an optional hash-reference which can define other 
    160 selection options, such as how to order the results and limit the results returned. 
    161 It has 3 elements: pageLength and pageOffset, and orderBy. pageLength specifies 
    162 how many rows to return, while pageOffset specifies where to start counting from. 
    163 orderBy can be a string such as 'username, password' which will then order the results 
    164 first by the username column, then by the password column. By default, rows are ordered 
    165 by primary key. 
     159specifined by $bindParams. 
     160 
     161If specified, $options should be a hash-reference which can be used to control the specifics of 
     162the query that you wish to run. 
     163 
     164The keys of the options hash are as follows: 
     165 
     166    pageLength 
     167    pageOffset 
     168    orderBy 
     169    count 
     170    distinct 
     171    fields 
     172 
     173pageLength can be used to limit the number of results returned. Setting it to 5 will return the 
     174first 5 results. 
     175 
     176pageOffset can be used to skip the first N rows in the result set. Together with pageLength, 
     177this makes up the LIMIT clause. pageLength must be specified before pageOffset is obeyed. 
     178 
     179orderBy can be used control the order of the rows returned. By default, they are returned 
     180in Primary Key order, but you can specify a comma-seperated list of fields to sort by, 
     181such as:  {orderBy => 'groupId, userName'} 
     182 
     183If 'count' is specified, then only the number of rows are returned, rather than the 
     184rows themselves. This is a boolean option. 
     185 
     186If 'distinct' is specified, then only unique rows are returned. Can be used with 'count' 
     187so that only unique rows are counted. 
     188 
     189If 'fields' is specified, then it should be an array reference, and can be used to specify 
     190which fields are to be returned. By default, all fields in the table are selected. The row 
     191will be marked read-only unless you specify the primary key fields, because the primary key 
     192must be known if it is to be updated. Can be used with 'distinct' to pull a unique subset 
     193of the table.  
    166194 
    167195  Example: 
     
    193221    $options->{'pageOffset'} ||= 0; 
    194222    $options->{'orderBy'}    ||= join(', ', $table->primaryKeys()); 
     223    $options->{'count'}      ||= 0; 
     224    $options->{'distinct'}   ||= 0; 
     225 
     226    my @defaultFields = ($table->primaryKeys(), $table->fields()); 
     227    $options->{'fields'}     ||= \@defaultFields; 
    195228 
    196229    my $limitClause = ''; 
    197230    if ($options->{'pageLength'} > 0) 
    198231    { 
    199         $limitClause = sprintf(' LIMIT %d OFFSET %d', $options->{'pageLength'}, $options->{'pageOffset'}); 
     232        # %d is safe to use without having to worry about SQL injection etc. 
     233        if (defined ($dbh->{'mysql_serverinfo'})) # Because MySQL 3.x doesnt support the OFFSET clause 
     234        { 
     235            $limitClause = sprintf(' LIMIT %d, %d', $options->{'pageOffset'}, $options->{'pageLength'}); 
     236        } 
     237        else 
     238        { 
     239            $limitClause = sprintf(' LIMIT %d OFFSET %d', $options->{'pageLength'}, $options->{'pageOffset'}); 
     240        } 
     241    } 
     242    my $distinctClause = ''; 
     243    if ($options->{'distinct'}) 
     244    { 
     245        $distinctClause = 'DISTINCT'; 
     246    } 
     247    my $fieldsList = join(',', @{$options->{'fields'}}); 
     248    if ($options->{'count'}) 
     249    { 
     250        $fieldsList = 'count(*)'; 
    200251    } 
    201252 
     
    203254    if ($where ne '') 
    204255    { 
    205         $get_obj_sql = sprintf("SELECT %s, %s FROM %s WHERE %s ORDER BY %s%s", 
    206                               join(', ', $table->primaryKeys()), 
    207                               join(', ', $table->fields()), 
     256        $get_obj_sql = sprintf("SELECT %s %s FROM %s WHERE %s ORDER BY %s%s", 
     257                              $distinctClause, 
     258                              $fieldsList, 
    208259                              $table->name(), 
    209260                              $where, 
     
    213264    else 
    214265    { 
    215         $get_obj_sql = sprintf("SELECT %s, %s FROM %s ORDER BY %s%s", 
    216                               join(', ', $table->primaryKeys()), 
    217                               join(', ', $table->fields()), 
     266        $get_obj_sql = sprintf("SELECT %s %s FROM %s ORDER BY %s%s", 
     267                              $distinctClause, 
     268                              $fieldsList, 
    218269                              $table->name(), 
    219270                              $options->{'orderBy'}, 
     
    234285      }; 
    235286 
    236     my %row = (_dbh   => $dbh, 
    237                _table => $table); 
    238     $get_obj_sth->bind_columns(map { \$row{$_} } $table->primaryKeys(), $table->fields()); 
     287    if ($options->{'count'}) 
     288    { 
     289        my $result = $get_obj_sth->fetchall_arrayref()->[0]->[0]; 
     290        $get_obj_sth->finish; 
     291        return $result; 
     292    } 
     293 
     294    # Mark each row as read-only unless we have all the primary key fields 
     295    # needed to do an update accuratley. 
     296    my $readonly = 0; 
     297    foreach my $pKey ($table->primaryKeys) 
     298    { 
     299        my $found = 0; 
     300        foreach my $f (@{$options->{'fields'}}) 
     301        { 
     302            $found = 1 if ($f eq $pKey); 
     303        } 
     304        $readonly = 1 unless ($found); 
     305    } 
     306    my %row = (_dbh      => $dbh, 
     307               _table    => $table, 
     308               _readonly => $readonly); 
     309    $get_obj_sth->bind_columns(map { \$row{$_} } @{$options->{'fields'}}); 
    239310    my @rows; 
    240311    while ($get_obj_sth->fetch) 
     
    317388    my $class = ref($ref) || $ref; 
    318389 
    319     my $dbh    = shift || confess("Usage: $class->searchByString(\$dbh, \$table, \$string);"); 
    320     my $table  = shift || confess("Usage: $class->searchByString(\$dbh, \$table, \$string);"); 
    321     my $string = shift || confess("Usage: $class->searchByString(\$dbh, \$table, \$string);"); 
    322     my $options = shift; 
    323  
    324     my $where = join(' OR ', map { "LOWER($_\::text) LIKE ?" } $table->fields()); 
    325     my @params = map { lc("\%$string\%") } $table->fields(); 
     390    my $dbh    = shift  || confess("Usage: $class->searchByString(\$dbh, \$table, \$string [, \$options ]);"); 
     391    my $table  = shift  || confess("Usage: $class->searchByString(\$dbh, \$table, \$string [, \$options ]);"); 
     392    my $string = shift  || confess("Usage: $class->searchByString(\$dbh, \$table, \$string [, \$options ]);"); 
     393    my $options = shift || {}; 
     394 
     395    my @fields; 
     396    foreach my $f ($table->fields()) # boolean types cannot be cast to text. 
     397    {                                # so we skip them. 
     398        unless ($table->field($f)->type eq 'boolean') 
     399        { 
     400            push @fields, $f; 
     401        } 
     402    } 
     403 
     404    my $where = join(' OR ', map { "LOWER($_\::text) LIKE ?" } @fields); 
     405    my @params = map { lc("\%$string\%") } @fields; 
    326406 
    327407    my @rows = $class->getRowsWhere($dbh, $table, $where, \@params, $options); 
     
    419499    # In most cases it is, but this wont work if it isnt. 
    420500 
    421     # TODO: Use $dbh->last_insert_id() instead of this. 
    422     my $seqName = sprintf("%s_%s_seq", $self->{'_table'}->name(), ($self->{'_table'}->primaryKeys())[0]); 
    423     my $lastIdSql = "SELECT CURRVAL(?)"; 
    424     my $lastIdSth = $self->{'_dbh'}->prepare_cached($lastIdSql, {pg_prepare_now => 1}, 3) 
    425       or confess (sprintf("Could not prepare_cached(%s): Error Code %d (%s)", $lastIdSql, $self->{'_dbh'}->err, $self->{'_dbh'}->errstr)); 
    426  
    427     $lastIdSth->execute($seqName); 
    428     my $newId = $lastIdSth->fetchall_arrayref()->[0]->[0]; 
     501    my $newId = $self->{'_dbh'}->last_insert_id(undef, undef, $self->{'_table'}->name(), undef); 
    429502 
    430503    $self->{($self->{'_table'}->primaryKeys())[0]} = $newId; 
     
    718791    } 
    719792 
     793    if ($self->{'_readonly'}) 
     794    { 
     795        confess("Attempt to modify a read-only row"); 
     796    } 
     797 
    720798    my $oldValue = $self->$fieldName; 
    721799    if ($self->{'_table'}->field($fieldName)->{'is_array'}) 
     
    734812# /* setValue */ }}}  
    735813 
     814# /* _arrayToString */ {{{ 
     815# Internal function to support arrays. 
    736816sub _arrayToString 
    737817{ 
     
    762842    return '{' . join(',', @values) . '}'; 
    763843} 
     844# /* _arrayToString */ }}} 
    764845 
    765846# /* AUTOLOAD */ {{{ 
  • trunk/DB/Table/Table.pm

    r51 r53  
    131131                  field      => {}}; 
    132132 
     133    # Primary Key Info returns an undefined statement handle on MySQL. Primary Keys 
     134    # are therefore retreived using column_info() (see below) when using MySQL. 
    133135    my $pkeySth = $dbh->primary_key_info(undef, undef, $tableName); 
    134136    if (defined ($pkeySth)) 
     
    142144    } 
    143145 
     146    # TODO: This needs testing on MySQL. 
    144147    my $fkeySth = $dbh->foreign_key_info(undef, undef, undef, 
    145148                                         undef, undef, $tableName); 
     
    158161    } 
    159162 
     163    # TODO: This needs testing on MySQL. 
    160164    my $refKeySth = $dbh->foreign_key_info(undef, undef, $tableName, 
    161165                                           undef, undef, undef); 
     
    177181 
    178182    # We map types into standard names so that code using us can rely on a type 
    179     # being the same regardless of back-end. 
     183    # being the same regardless of back-end. MySQL always have to try and be different. 
    180184    my $typeMap = {varchar => 'character varying', 
    181185                   int     => 'integer', 
     
    186190        $c->{'COLUMN_NAME'} =~ s/(^\"|\"$)//g; # See comment against $tableName 
    187191 
     192        # primary_key_info() does not work on MySQL, so we use this instead. 
    188193        if ($c->{'mysql_is_pri_key'}) 
    189194        { 
     
    194199        push @{$table->{'fields'}}, $c->{'COLUMN_NAME'} unless ($table->{'field'}->{$c->{'COLUMN_NAME'}}->{'pkey'}); 
    195200 
     201        # Full support for postgres arrays should really be done in the DBD. There is an array_support branch in 
     202        # CVS for this, but I havent tested it and fixes in the main branch have not been merged into it. 
     203        # For now, only single-dimentional arrays work. Sort of. 
    196204        my $is_array = 0; 
    197205        if ($c->{'TYPE_NAME'} =~ s/\[\]$//g) 
     
    210218        $table->{'field'}->{$c->{'COLUMN_NAME'}}->{'is_array'} = $is_array; 
    211219        $table->{'field'}->{$c->{'COLUMN_NAME'}}->{'position'} = $c->{'ORDINAL_POSITION'}; 
    212         # There is no equivalent in MySQL, but if the user wishes to customise 
    213         # this manually they may. The evaluation/checking of the constraint should 
    214         # still work. 
     220 
     221        # TODO: Need to see if there is some sort of equivalent in MySQL. 
     222        #       MySQL is always playing catch-up on the feature list... 
    215223        $table->{'field'}->{$c->{'COLUMN_NAME'}}->{'constraint'} = $c->{'pg_constraint'}; 
    216224        $table->{'field'}->{$c->{'COLUMN_NAME'}}->{'validate'} = { regex => '^.*$', 
     
    218226    } 
    219227 
     228    # Another MySQL thing: column_info retrieves the columns in an aparently arbitory order, not 
     229    # sorted by ordinal position as one might expect. 
    220230    my @sortedFields = sort { $table->{'field'}->{$a}->{'position'} <=> $table->{'field'}->{$b}->{'position'} } @{$table->{'fields'}}; 
    221231    $table->{'fields'} = \@sortedFields;