DBIx-NinjaORM
view release on metacpan or search on metacpan
lib/DBIx/NinjaORM.pm view on Meta::CPAN
my $object = My::Model::Book->new();
=item * Retrieving a single object from the database.
# Retrieve by ID.
my $object = My::Model::Book->new( { id => 3 } )
// die 'Book #3 does not exist';
# Retrieve by unique field.
my $object = My::Model::Book->new( { isbn => '9781449303587' } )
// die 'Book with ISBN 9781449303587 does not exist';
=back
When retrieving a single object from the database, the first argument should be
a hashref containing the following information to select a single row:
=over 4
=item * id
The ID for the primary key on the underlying table. C<id> is an alias for the
primary key field name.
my $object = My::Model::Book->new( { id => 3 } )
// die 'Book #3 does not exist';
=item * A unique field
Allows passing a unique field and its value, in order to load the
corresponding object from the database.
my $object = My::Model::Book->new( { isbn => '9781449303587' } )
// die 'Book with ISBN 9781449303587 does not exist';
Note that unique fields need to be defined in C<static_class_info()>, in the
C<unique_fields> key.
=back
This method also supports the following optional arguments, passed in a hash
after the filtering criteria above-mentioned:
=over 4
=item * skip_cache (default: 0)
By default, if cache is enabled with C<object_cache_time()> in
C<static_class_info()>, then C<new> attempts to load the object from the cache
first. Setting C<skip_cache> to 1 forces the ORM to load the values from the
database.
my $object = My::Model::Book->new(
{ isbn => '9781449303587' },
skip_cache => 1,
) // die 'Book with ISBN 9781449303587 does not exist';
=item * lock (default: 0)
By default, the underlying row is not locked when retrieving an object via
C<new()>. Setting C<lock> to 1 forces the ORM to bypass the cache if any, and
to lock the rows in the database as it retrieves them.
my $object = My::Model::Book->new(
{ isbn => '9781449303587' },
lock => 1,
) // die 'Book with ISBN 9781449303587 does not exist';
=back
=cut
sub new
{
my ( $class, $filters, %args ) = @_;
# If filters exist, they need to be a hashref.
croak 'The first argument must be a hashref containing filtering criteria'
if defined( $filters ) && !Data::Validate::Type::is_hashref( $filters );
# Check if we have a unique identifier passed.
# Note: passing an ID is a subcase of passing field defined as unique, but
# unique_fields() doesn't include the primary key name.
my $unique_field;
foreach my $field ( 'id', @{ $class->get_info('unique_fields') // [] } )
{
next
if ! exists( $filters->{ $field } );
# If the field exists in the list of filters, it needs to be
# defined. Being undefined probably indicates a problem in the calling code.
croak "Called new() with '$field' declared but not defined"
if ! defined( $filters->{ $field } );
# Detect if we're passing two unique fields to retrieve the object. This is
# obviously bad.
croak "Called new() with the unique argument '$field', but already found another unique argument '$unique_field'"
if defined( $unique_field );
$unique_field = $field;
}
# Retrieve the object.
my $self;
if ( defined( $unique_field ) )
{
my $objects = $class->retrieve_list(
{
$unique_field => $filters->{ $unique_field },
},
skip_cache => $args{'skip_cache'},
lock => $args{'lock'} ? 1 : 0,
);
my $objects_count = scalar( @$objects );
if ( $objects_count == 0 )
{
# No row found.
$self = undef;
}
lib/DBIx/NinjaORM.pm view on Meta::CPAN
my $table_schema = $class->get_table_schema();
croak "Failed to retrieve schema for table '$table_name'"
if !defined( $table_schema );
my $column_names = $table_schema->get_column_names();
croak "Failed to retrieve column names for table '$table_name'"
if !defined( $column_names );
my @filtered_fields = ();
if ( defined( $args{'exclude_fields'} ) && !defined( $args{'select_fields'} ) )
{
my %excluded_fields = map { $_ => 1 } @{ $args{'exclude_fields'} };
foreach my $field ( @$column_names )
{
$excluded_fields{ $field }
? delete( $excluded_fields{ $field } )
: push( @filtered_fields, $field );
}
croak "The following excluded fields are not valid: " . join( ', ', keys %excluded_fields )
if scalar( keys %excluded_fields ) != 0;
}
elsif ( !defined( $args{'exclude_fields'} ) && defined( $args{'select_fields'} ) )
{
my %selected_fields = map { $_ => 1 } @{ $args{'select_fields'} };
croak 'The primary key must be in the list of selected fields'
if defined( $primary_key_name ) && !$selected_fields{ $primary_key_name };
foreach my $field ( @$column_names )
{
next if !$selected_fields{ $field };
push( @filtered_fields, $field );
delete( $selected_fields{ $field } );
}
croak "The following restricted fields are not valid: " . join( ', ', keys %selected_fields )
if scalar( keys %selected_fields ) != 0;
}
else
{
croak "The 'exclude_fields' and 'select_fields' options are not compatible, use one or the other";
}
croak "No fields left after filtering out the excluded/restricted fields"
if scalar( @filtered_fields ) == 0;
$fields = join(
', ',
map { "$quoted_table_name.$_" } @filtered_fields
);
}
else
{
$fields = $quoted_table_name . '.*';
}
$fields .= ', ' . $args{'query_extensions'}->{'joined_fields'}
if defined( $args{'query_extensions'}->{'joined_fields'} );
# We need to make an exception for lock=1 when using SQLite, since
# SQLite doesn't support FOR UPDATE.
# Per http://sqlite.org/cvstrac/wiki?p=UnsupportedSql, the entire
# database is locked when updating any bit of it, so we can simply
# ignore the locking request here.
my $lock = '';
if ( $args{'lock'} )
{
my $database_type = $dbh->{'Driver'}->{'Name'} || '';
if ( $database_type eq 'SQLite' )
{
$log->info(
'SQLite does not support locking since only one process at a time is ',
'allowed to update a given SQLite database, so lock=1 is ignored.',
);
}
else
{
$lock = 'FOR UPDATE';
}
}
# Check if we need to paginate.
my $pagination_info = {};
if ( defined( $args{'pagination'} ) )
{
# Allow for pagination => 1 as a shortcut to get all the defaults.
$args{'pagination'} = {}
if !Data::Validate::Type::is_hashref( $args{'pagination'} ) && ( $args{'pagination'} eq '1' );
# Set defaults.
$pagination_info->{'per_page'} = ( $args{'pagination'}->{'per_page'} || '' ) =~ m/^\d+$/
? $args{'pagination'}->{'per_page'}
: 20;
# Count the total number of results.
my $count_data = $dbh->selectrow_arrayref(
sprintf(
q|
SELECT COUNT(*)
FROM %s
%s
%s
|,
$quoted_table_name,
$joins,
$where,
),
{},
map { @$_ } @$where_values,
);
$pagination_info->{'total_count'} = defined( $count_data ) && scalar( @$count_data ) != 0
? $count_data->[0]
: undef;
# Calculate what the max page can be.
$pagination_info->{'page_max'} = int( ( $pagination_info->{'total_count'} - 1 ) / $pagination_info->{'per_page'} ) + 1;
# Determine what the current page is.
$pagination_info->{'page'} = ( ( $args{'pagination'}->{'page'} || '' ) =~ m/^\d+$/ ) && ( $args{'pagination'}->{'page'} > 0 )
? $pagination_info->{'page_max'} < $args{'pagination'}->{'page'}
? $pagination_info->{'page_max'}
: $args{'pagination'}->{'page'}
: 1;
# Set LIMIT and OFFSET.
$limit = "LIMIT $pagination_info->{'per_page'} "
. 'OFFSET ' . ( ( $pagination_info->{'page'} - 1 ) * $pagination_info->{'per_page'} );
}
# If we need to lock the rows and there's joins, let's do this in two steps:
# 1) Lock the rows without join.
# 2) Using the IDs found, do another select to retrieve the data with the joins.
if ( ( $lock ne '' ) && ( $joins ne '' ) )
{
my $query = sprintf(
q|
SELECT %s
FROM %s
%s
ORDER BY %s ASC
%s
%s
|,
$quoted_primary_key_name,
$quoted_table_name,
$where,
$quoted_primary_key_name,
$limit,
$lock,
);
my @query_values = map { @$_ } @$where_values;
$log->debugf(
"Performing pre-locking query:\n%s\nValues:\n%s",
$query,
\@query_values,
) if $args{'show_queries'};
my $locked_ids;
try
{
local $dbh->{'RaiseError'} = 1;
$locked_ids = $dbh->selectall_arrayref(
$query,
{
Columns => [ 1 ],
},
@query_values
);
}
catch
{
$log->fatalf(
"Could not select rows in pre-locking query: %s\nQuery: %s\nValues:\n%s",
$_,
$query,
\@query_values,
);
croak "Failed select: $_";
};
if ( !defined( $locked_ids ) || ( scalar( @$locked_ids ) == 0 ) )
{
return [];
}
$where = sprintf(
'WHERE %s.%s IN ( %s )',
$quoted_table_name,
$quoted_primary_key_name,
join( ', ', ( ('?') x scalar( @$locked_ids ) ) ),
);
$where_values = [ [ map { $_->[0] } @$locked_ids ] ];
$lock = '';
}
# Prepare the query elements.
my $query = sprintf(
q|
SELECT %s
FROM %s
%s %s %s %s %s
|,
$fields,
$quoted_table_name,
$joins,
$where,
$order_by,
$limit,
$lock,
);
my @query_values = map { @$_ } @$where_values;
$log->debugf(
"Performing query:\n%s\nValues:\n%s",
$query,
\@query_values,
) if $args{'show_queries'};
# Retrieve the objects.
my $sth;
try
{
local $dbh->{'RaiseError'} = 1;
$sth = $dbh->prepare( $query );
$sth->execute( @query_values );
}
catch
{
$log->fatalf(
"Could not select rows: %s\nQuery: %s\nValues: %s",
$_,
$query,
\@query_values,
);
croak "Failed select: $_";
};
my $object_list = [];
while ( my $ref = $sth->fetchrow_hashref() )
{
my $object = Storable::dclone( $ref );
bless( $object, $class );
$object->reorganize_non_native_fields();
# Add a flag to distinguish objects that were populated via
# retrieve_list_nocache(), as those objects are known for sure to contain
# all the keys for columns that exist in the database. We also won't have to
# worry about missing defaults, like insert() would have to.
$object->{'_populated_by_retrieve_list'} = 1;
# Add cache debugging information.
$object->{'_debug'}->{'list_cache_used'} = 0;
( run in 1.490 second using v1.01-cache-2.11-cpan-5b529ec07f3 )