DB-Object

 view release on metacpan or  search on metacpan

lib/DB/Object.pm  view on Meta::CPAN

        return( $self->error( "This method must be called by the database object itself." ) );
    }

    # Cannot be called without using a DB::Object
    my $dbo         = $self;
    my $cache_size  = $dbo->cache_size // 0;
    my $cache       = $dbo->{cache};
    # We want to know what kind of cache we are expected to use
    my $cache_query = $opts->{cache_query} // $dbo->cache_query // 1;
    my $cache_dir   = $opts->{cache_dir} // $dbo->cache_dir // $self->sys_tmpdir;
    my $ext         = $opts->{extension} // 'bin';

    my $use_file_cache = ( defined( $cache_query ) && $cache_query eq 'file' ) ? 1 : 0;

    # Not a hard error
    if( $cache_query !~ /^1|0|file$/ )
    {
        warn( "Unknown value for cache_query '$cache_query'" ) if( $self->_is_warnings_enabled( 'DB::Object' ) );
    }

    my $serialiser = $opts->{serialiser} || $self->serialiser || $DB::Object::SERIALISER || 'Storable';
    # If the serialiser selected is the default one, and we have Sereal installed, we use it instead.
    if( $serialiser eq 'Storable' && $self->_load_class( 'Sereal' ) )
    {
        $serialiser = 'Sereal';
    }
    $name = lc( $serialiser ) . "-${name}";

    # We make sure the extension passed to '_get_cache_filepath' is coherent with ours
    my $cache_file = $self->_get_cache_filepath( %$opts, name => $name, extension => $ext, prefix => 'query' ) ||
        return( $self->pass_error );
    my $cache_key = $cache_file->basename( ".${ext}" );

    my $lock;
    my $repo = Module::Generic::Global->new( queries_cache => $base_class,
        max_size    => $cache_size,
        serialiser  => $serialiser,
        key         => $cache_key,
    ) || return( $self->pass_error( Module::Generic::Global->error ) );
    # The lock gets removed automatically when it gets out of scope, so we store it in $lock
    $lock = $repo->lock;

    if( $cache_query eq 'file' )
    {
        # If it is already there, we return immediately. There is nothing more to do.
        if( $cache_file->exists &&
            !$cache_file->is_file )
        {
            return( $self->error( "There already existing a cache file $cache_file, but it is not a file!" ) );
        }
        if( $cache_file->exists &&
            $cache_file->is_file &&
            !$cache_file->is_empty )
        {
            return( $sth );
        }
        $self->Module::Generic::serialise( $sth,
            file       => $cache_file,
            lock       => 1,
            serialiser => $serialiser,
            ( $serialiser eq 'Sereal' ? ( freeze_callbacks => 1 ) : () ),
        ) || return( $self->pass_error );
    }
    elsif( $cache_query )
    {
        $repo->set( $sth );
        $repo->unlock;
    }
    return( $sth );
}

sub cache_size { return( shift->_set_get_scalar( 'cache_size', @_ ) ); }

sub cache_table { return( shift->_set_get_scalar( 'cache_table', @_ ) ); }

sub cache_tables { return( shift->_set_get_object( 'cache_tables', 'DB::Object::Cache::Tables', @_ ) ); }

sub check_driver()
{
    my $self   = shift( @_ );
    my $driver = shift( @_ ) || return( $self->error( "No SQL driver provided to check" ) );
    my $ok     = undef();
    local $_;
    my @drivers = $self->available_drivers();
    foreach( @drivers ) 
    {
        if( m/$driver/s )
        {
            $ok++;
            last;
        }
    }
    return( $ok );
}

sub connect
{
    my $this  = shift( @_ );
    my $class = ref( $this ) || $this;
    my $base_class = $this->base_class;
    my $opts  = $this->_get_args_as_hash( @_ );
    # We pass the arguments so that debug and other init parameters can be set early
    my $that  = ref( $this ) ? $this : $this->Module::Generic::new( debug => $opts->{debug} );
    my $param = $that->_connection_params2hash( $opts ) || return( $this->error( "No valid connection parameters found" ) );
    $param    = $that->_connection_params2hash_driver( $param );
    my $driver2pack = 
    {
        mysql  => 'DB::Object::Mysql',
        Pg     => 'DB::Object::Postgres',
        SQLite => 'DB::Object::SQLite',
    };
    my $pack2driver = +{ map{ $driver2pack->{ $_ } => $_ } keys( %$driver2pack ) };
    my $driver_class;
    if( exists( $param->{driver} ) )
    {
        if( !exists( $driver2pack->{ $param->{driver} } ) )
        {
            return( $this->error( "Driver $param->{driver} is not supported." ) );
        }
        # For example, will make this object a DB::ObjectD::Postgres object
        $driver_class  = $driver2pack->{ $param->{driver} };

lib/DB/Object.pm  view on Meta::CPAN

            # Unexpected file name format; ignore it
            warn( "Ignoring cache file with unexpected name format: '$f'" ) if( $self->_is_warnings_enabled( 'DB::Object' ) );
            return;
        }

        $self->_load_class( $base_class, { force => 1 } ) || return( $self->pass_error );
        $self->_load_class( 'DB::Object::Tables', { force => 1 } ) || return( $self->pass_error );
        $self->_load_class( 'DB::Object::Constraint::Foreign', { force => 1 } ) || return( $self->pass_error );
        $self->_load_class( "${base_class}::Tables", { force => 1 } ) || return( $self->pass_error );
        my $tbl  = $self->Module::Generic::deserialise(
            file => $f,
            lock => 1,
            serialiser => $serialiser,
        ) || return;
        unless( $self->_is_a( $tbl => "${base_class}::Tables" ) )
        {
            warn( "Deserialised object from file '$f' is not a ${base_class}::Tables object, but a '", ref( $tbl ), " object." ) if( $self->_is_warnings_enabled( 'DB::Object' ) );
            return;
        }

        # bless( $tbl => $table_class );
        $tbl->attach( $dbo ) || return( $self->pass_error( $tbl->error ) );
        return( $tbl );
    };

    # The caller wants the whole shebang
    if( !defined( $table ) )
    {
        # The caller wants all the table objects for that host and database
        my @files = $cache_dir->glob( "${prefix}-[a-zA-Z0-9_][a-zA-Z0-9_\\-\\.]*.bin" );
        my $cache = {};
        foreach my $f ( @files )
        {
            my $table  = $deserialise_from_file->( $f ) || next;
            my $name   = $f->basename( '.bin' );
            if( index( $name, $prefix ) != 0 )
            {
                # Unexpected file name format; ignore it
                warn( "Ignoring cache file with unexpected name format: '$f'" ) if( $self->_is_warnings_enabled( 'DB::Object' ) );
                next;
            }
            $name = [split( /-/, $name )]->[-1];
            $cache->{ $name } = $table;
        }
        return( $cache );
    }
    # We are given a table object, we store it in the repository. This is mutator mode
    elsif( $self->_is_a( $table => "${base_class}::Tables" ) )
    {
        my $name = $table->name || 
            return( $self->error( "No table name associated with this table object." ) );
        if( $name !~ /^\w+[\w\-\.]*$/ )
        {
            return( $self->error( "The table name '$name' contains illegal characters." ) );
        }
        my $file = $cache_dir->child( "${prefix}-${name}.bin" );
        my $tbl  = $self->Module::Generic::serialise( $table,
            file => $file,
            lock => 1,
            serialiser => $serialiser,
            ( $serialiser eq 'Sereal' ? ( freeze_callbacks => 1 ) : () ),
        ) || return( $self->pass_error );
        return(1);
    }
    # The user has provided a table name, and thus expects the related cloned table object from the cache table repository.
    # This is accessor mode.
    elsif( !ref( $table ) || ( ref( $table ) && $self->_can_overload( $table => '""' ) ) )
    {
        $table = "$table";
        if( $table !~ /^\w+[\w\-\.]*$/ )
        {
            return( $self->error( "The table name '$table' contains illegal characters." ) );
        }
        my $cache_file = $cache_dir->child( "${prefix}-${table}.bin" );
        if( !$cache_file->exists || $cache_file->is_empty || !$cache_file->is_file )
        {
            return( '' );
        }
        my $tbl = $deserialise_from_file->( $cache_file ) || return( '' );
        return( $tbl );
    }
    else
    {
        return( $self->error( "I do not understand the value provided for the 'table' option -> ", $self->_str_val( $table ) ) );
    }
}

sub _cache_this
{
    my $self    = shift( @_ );
    # When this method is accessed by method from package DB::Object::Statement, they CAN NOT
    # implicitly pass the statement string or they would risk to modify the previous stored
    # query object they represent.
    # For instance:
    # $obj->select->join( 'some_table', { 'parameter', 'list' } )->fetchrow_hashref()
    # here the first query is prepared and cached and its resulting object is passed on to join
    # here join will rebuild the query, but will search first if there was one already cached
    # if join passes implictly the statement string, this means it will modify the cached query select()
    # has just previously stored... This is why method such as join must pass explicitly the query string
    my $q           = shift( @_ );
    my $query       = ( ref( $q ) && $q->isa( 'DB::Object::Query' ) ) ? $q->as_string : $q;
    my $dbo         = $self->{dbo} || $self->{database_object} || $self;
    my $base_class  = $self->base_class;
    my $cache       = $dbo->{cache};
    my $bind        = $dbo->{bind};
    my $cache_dir   = $dbo->cache_dir // $self->new_tempdir;
    my $cache_query = $dbo->cache_query;
    my $cache_size  = $dbo->cache_size // 0;
    my $serialiser  = $dbo->serialiser || $DB::Object::SERIALISER || 'Storable';

    my $use_file_cache  = ( defined( $cache_query ) && $cache_query eq 'file' ) ? 1 : 0;

    my( $repo, $sth, $cache_file, $checksum, $cache_key );
    my $cached_sth = '';
    my $host     = $dbo->host;
    my $database = $dbo->database;
    my $table    = ( ref( $q ) && $dbo->_is_a( $q => "${base_class}::Query" ) && $q->table_object ) ? $q->table_object->name : '';

    # Compute cache_file (and cache key) early when caching is enabled and cache_dir exists.
    # Even if cache_query is "file", we also want a small hot cache in memory.
    if( $cache )



( run in 1.639 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )