DBIO

 view release on metacpan or  search on metacpan

lib/DBIO/Storage/DBI.pm  view on Meta::CPAN

package DBIO::Storage::DBI;
# ABSTRACT: DBI storage handler
# -*- mode: cperl; cperl-indent-level: 2 -*-

use strict;
use warnings;

use base qw/DBIO::Storage::QueryRewrite DBIO::Storage::DBI::Capabilities DBIO::Storage::DBI::DataTypeClassifier DBIO::Storage::DBI::AccessBroker DBIO::Storage/;
use mro 'c3';

use DBIO::Carp;
use Scalar::Util qw/refaddr weaken reftype blessed/;
use Context::Preserve 'preserve_context';
use Try::Tiny;
use DBIO::Util qw(is_plain_value is_literal_value);
use DBIO::Util qw(quote_sub perlstring serialize dump_value sigwarn_silencer is_windows is_dev_release old_mro help_url);
use DBIO::Skills;
use DBIO::Storage::Composed;
use namespace::clean;

# default cursor class, overridable in connect_info attributes
__PACKAGE__->cursor_class('DBIO::Storage::DBI::Cursor');

__PACKAGE__->mk_group_accessors('inherited' => qw/
  sql_quote_char sql_name_sep
/);

# Class-level hook to redact bind values before they are interpolated into
# the trace output. The default is the identity function (returns the value
# unchanged) which preserves the historical plaintext trace behavior.
# Install a custom coderef on the storage class:
#
#   __PACKAGE__->redact_bind_value(sub {
#     my ($colname, $value) = @_;
#     return $colname eq 'password' ? '***' : $value;
#   });
#
# The coderef receives the column name (or undef if no column metadata is
# attached to the bind slot) and the raw bind value, and must return the
# value to interpolate into the trace. The original bind value passed to
# the database is *not* modified - only the trace string is.
__PACKAGE__->mk_classdata('redact_bind_value' => sub { return $_[1] });

__PACKAGE__->mk_group_accessors('component_class' => qw/sql_maker_class datetime_parser_type/);

__PACKAGE__->sql_maker_class('DBIO::SQLMaker');
__PACKAGE__->datetime_parser_type('DateTime::Format::MySQL'); # historic default

# Identifier quoting is ON by default (security hardening). Concrete drivers
# override sql_quote_char with their RDBMS-native quote; the bare base storage
# falls back to the ANSI SQL standard double quote so the default-on path never
# has to warn about a missing quote_char.
__PACKAGE__->sql_quote_char('"');
__PACKAGE__->sql_name_sep('.');

__PACKAGE__->mk_group_accessors('simple' => qw/
  _connect_info _dbio_connect_attributes _driver_determined
  _dbh _dbh_details _conn_pid _sql_maker _sql_maker_opts _dbh_autocommit
  _perform_autoinc_retrieval _autoinc_supplied_for_op _async_mode
/);

# Async mode registry (ADR 0030): maps a connect-time mode name to the embedded
# backend class that answers the six *_async methods for an instance connected
# with { async => $mode }. Generic modes (e.g. 'forked', 'future_io') register
# on a base class and are inherited by every driver; native modes (e.g. 'ev')
# are registered by the concrete driver storage class, so the same logical mode
# name resolves to a DB-specific backend. Lookup walks the instance's MRO, so a
# driver registration shadows/extends the generic ones. 'immediate' is the
# in-process degrade mode (no event loop, no embedded backend): it maps to the
# immediate Future class and is recognised by NOT being a DBIO::Storage::Async
# subclass. Core registers only 'immediate' -- 'forked'/'future_io'/'ev' come
# from their add-on/driver dists, never auto-wired here.

lib/DBIO/Storage/DBI.pm  view on Meta::CPAN


    if (
      # only fire when invoked on an instance, a valid class-based invocation
      # would e.g. be setting a default for an inherited accessor
      ref $_[0]
        and
      ! $_[0]->{_driver_determined}
        and
      ! $_[0]->{_in_determine_driver}
        and
      # if this is a known *setter* - just set it, no need to connect
      # and determine the driver
      ( %1$s or @_ <= 1 )
        and
      # Only try to determine stuff if we have *something* that either is or can
      # provide a DSN. Allows for bare $schema's generated with a plain ->connect()
      # to still be marginally useful
      $_[0]->_dbi_connect_info->[0]
    ) {
      $_[0]->_determine_driver;

      goto $_[0]->can(%2$s);
    }

    goto $orig;
EOC
}


sub new {
  my $new = shift->next::method(@_);

  $new->_sql_maker_opts({});
  $new->_dbh_details({});
  $new->{_in_do_block} = 0;

  # read below to see what this does
  $new->_arm_global_destructor;

  $new;
}

# This is hack to work around perl shooting stuff in random
# order on exit(). If we do not walk the remaining storage
# objects in an END block, there is a *small but real* chance
# of a fork()ed child to kill the parent's shared DBI handle,
# *before perl reaches the DESTROY in this package*
# Yes, it is ugly and effective.
# Additionally this registry is used by the CLONE method to
# make sure no handles are shared between threads
{
  my %seek_and_destroy;

  sub _arm_global_destructor {

    # quick "garbage collection" pass - prevents the registry
    # from slowly growing with a bunch of undef-valued keys
    defined $seek_and_destroy{$_} or delete $seek_and_destroy{$_}
      for keys %seek_and_destroy;

    weaken (
      $seek_and_destroy{ refaddr($_[0]) } = $_[0]
    );
  }

  END {
    local $?; # just in case the DBI destructor changes it somehow

    # destroy just the object if not native to this process
    $_->_verify_pid for (grep
      { defined $_ }
      values %seek_and_destroy
    );
  }

  sub CLONE {
    # As per DBI's recommendation, DBIO disconnects all handles as
    # soon as possible (DBIO will reconnect only on demand from within
    # the thread)
    my @instances = grep { defined $_ } values %seek_and_destroy;
    %seek_and_destroy = ();

    for (@instances) {
      $_->_dbh(undef);

      $_->transaction_depth(0);
      $_->savepoints([]);

      # properly renumber existing refs
      $_->_arm_global_destructor
    }
  }
}

sub DESTROY {
  $_[0]->_verify_pid unless is_windows();
  # some databases spew warnings on implicit disconnect
  local $SIG{__WARN__} = sub {};
  $_[0]->_dbh(undef);

  # this op is necessary, since the very last perl runtime statement
  # triggers a global destruction shootout, and the $SIG localization
  # may very well be destroyed before perl actually gets to do the
  # $dbh undef
  1;
}

# handle pid changes correctly - do not destroy parent's connection
sub _verify_pid {

  my $pid = $_[0]->_conn_pid;

  if( defined $pid and $pid != $$ and my $dbh = $_[0]->_dbh ) {
    $dbh->{InactiveDestroy} = 1;
    $_[0]->_dbh(undef);
    $_[0]->transaction_depth(0);
    $_[0]->savepoints([]);
  }

  return;
}

lib/DBIO/Storage/DBI.pm  view on Meta::CPAN

      # layers (with their plain ::Async mixins) sit more-specific than the
      # driver in ref($self)'s MRO -- probing those would find a layer's async
      # mixin (not a DBIO::Storage::Async) before the driver's real adapter and
      # croak. For a non-composed instance the base IS ref($self) (karr #70).
      my $class = $self->_async_resolution_class;
      my @tried;
      for my $pkg (@{ mro::get_linear_isa($class) }) {
        last if $pkg eq __PACKAGE__;   # stop strictly BEFORE the generic base
        my $adapter = $pkg . '::Async';
        push @tried, $adapter;
        # Genuinely absent -> keep walking; present-but-unloadable (its own
        # async dependency missing, or broken) -> fail loud with the cause.
        next unless $self->_try_load_async_class($adapter, 'future_io adapter');

        # Loaded -- it MUST be a proper backend or we fail loud, naming it;
        # never silently skip past it to a parent's adapter (ADR 0030 §3).
        $self->throw_exception(
          "driver $class does not support future_io -- "
          . "$adapter loaded but is not a DBIO::Storage::Async"
        ) unless $adapter->isa('DBIO::Storage::Async');

        $backend_class = $adapter;
        last;
      }

      $self->throw_exception(
        "driver $class does not support future_io -- no ${class}::Async adapter "
        . "found on it or any parent (tried: @{[ join ', ', @tried ]})"
      ) unless defined $backend_class;
    }
  }
  else {
    $backend_class = $self->_resolve_async_mode_class($mode);

    $self->throw_exception(
      "async mode '$mode' is not available -- no driver or add-on registers it"
    ) if !defined $backend_class;

    $self->throw_exception(
      "async mode '$mode' is not available -- install $backend_class"
    ) unless $self->load_optional_class($backend_class);
  }

  # A non-DBIO::Storage::Async target (the 'immediate' Future class) is the
  # in-process degrade mode: no embedded backend is built.
  return $self->{_async_storage_obj} = undef
    unless $backend_class->isa('DBIO::Storage::Async');

  # --- WP2 (karr #70): async mirror composition. The transport class is now
  # resolved; mirror each registered SYNC storage layer onto its ASYNC
  # counterpart and compose them over the transport, so an extension's async
  # behaviour rides every transport just as its sync behaviour rides every
  # driver (one behaviour, one transport). Per sync layer L, in registration
  # order:
  #   * L->async_layer_class($mode) if L defines it -- a package (must load, or
  #     croak) or undef (fall back to convention);
  #   * otherwise the convention sibling "${L}::Async" via load_optional_class;
  #   * absent -> skip L silently (a sync-only extension, e.g. PostGIS).
  # This does NOT touch the transport resolution above (ADR 0030: the mode
  # stays explicit; only the layer CLASSES are added by composition).
  # A storage with no live schema (weakened ref already gone) can carry no
  # registered layers, so there is nothing to mirror.
  my $schema = $self->schema;
  my @async_layers;
  for my $sync_layer ($schema ? @{ $schema->storage_layers } : ()) {
    my $async_layer;

    if ($sync_layer->can('async_layer_class')) {
      $async_layer = $sync_layer->async_layer_class($mode);
      if (defined $async_layer) {
        # An explicitly declared class MUST load: genuinely absent -> the
        # "could not be loaded" croak; present-but-unloadable -> _try_load's
        # cause-naming croak (never a raw compile stack).
        $self->throw_exception(
          "async layer class '$async_layer' for storage layer '$sync_layer' "
        . "(async mode '$mode') could not be loaded"
        ) unless $self->_try_load_async_class(
          $async_layer, "async layer class for storage layer '$sync_layer'"
        );
      }
    }

    if (!defined $async_layer) {
      my $candidate = "${sync_layer}::Async";
      # Convention sibling: genuinely absent -> skip L silently (sync-only
      # extension, e.g. PostGIS); present-but-unloadable -> fail loud naming
      # the cause, never a silent feature loss nor a raw compile stack.
      $async_layer = $candidate
        if $self->_try_load_async_class($candidate, 'async layer');
    }

    push @async_layers, $async_layer if defined $async_layer;
  }

  if (@async_layers) {
    # Capability gate: an async layer may only compose over a transport that
    # provides every capability it declares as required. A shortfall croaks
    # naming the layer, the missing capabilities and the transport -- a
    # transport gap becomes that transport's ticket, never a silent feature loss.
    my %have = map { $_ => 1 } $backend_class->transport_capabilities;
    for my $async_layer (@async_layers) {
      next unless $async_layer->can('required_transport_capabilities');
      my @missing = grep { !$have{$_} }
        $async_layer->required_transport_capabilities;
      next unless @missing;
      $self->throw_exception(
        "async layer '$async_layer' requires transport "
      . (@missing == 1 ? 'capability' : 'capabilities') . " '"
      . join("', '", @missing) . "' which transport '$backend_class' does not "
      . "provide -- upgrade the transport '$backend_class' (or its "
      . "distribution) or choose another async mode"
      );
    }

    $backend_class =
      DBIO::Storage::Composed->compose($backend_class, \@async_layers);
  }

  # Build the embedded backend (ADR 0028 mechanism): ->new($schema) fed this
  # storage's DBI-form connect_info.
  my $async = $backend_class->new($self->schema);

lib/DBIO/Storage/DBI.pm  view on Meta::CPAN

        "The 'rebase_sqlmaker' target class '$requested_base_class' must inherit from SQL::Abstract or SQL::Abstract::Classic"
      ) unless $requested_base_class->isa( 'SQL::Abstract' )
            or $requested_base_class->isa( 'SQL::Abstract::Classic' );

      $self->inject_base( $synthetic_class, $old_class, $requested_base_class );

      Class::C3->reinitialize
        if old_mro();
    }
  }

  # force re-build on next access for this particular $storage instance
  $self->sql_maker_class( $synthetic_class );
  $self->_sql_maker( undef );
}

sub _connect {
  my $self = shift;

  if ($self->access_broker) {
    my $broker_info = $self->_current_dbi_connect_info($self->access_broker_mode);
    # Broker returns HASHREF; normalize to ARRAYREF for _normalize_connect_info
    $broker_info = [$broker_info] if ref $broker_info eq 'HASH';
    my $info = $self->_normalize_connect_info($broker_info);

    my %attrs = (
      %{ $self->_default_dbi_connect_attributes || {} },
      %{ $info->{attributes} || {} },
    );

    my @args = @{ $info->{arguments} };

    # _dbio_connect_attributes keeps the full set (including DBIO-private
    # keys like ignore_version) for introspection by upper layers.
    # _dbi_connect_info must only contain attrs that DBI/DBD understands.
    my %dbi_attrs = %attrs;
    delete @dbi_attrs{qw( ignore_version )};

    push @args, \%dbi_attrs if keys %dbi_attrs and ref $args[0] ne 'CODE';

    $self->_dbi_connect_info(\@args);
    $self->_dbio_connect_attributes(\%attrs);
  }

  my $info = $self->_dbi_connect_info;

  $self->throw_exception("You did not provide any connection_info")
    unless defined $info->[0];

  my ($old_connect_via, $dbh);

  local $DBI::connect_via = 'connect' if $INC{'Apache/DBI.pm'} && $ENV{MOD_PERL};

  # this odd anonymous coderef dereference is in fact really
  # necessary to avoid the unwanted effect described in perl5
  # RT#75792
  #
  # in addition the coderef itself can't reside inside the try{} block below
  # as it somehow triggers a leak under perl -d
  my $dbh_error_handler_installer = sub {
    weaken (my $weak_self = $_[0]);

    # the coderef is blessed so we can distinguish it from externally
    # supplied handles (which must be preserved)
    $_[1]->{HandleError} = bless sub {
      if ($weak_self) {
        $weak_self->throw_exception("DBI Exception: $_[0]");
      }
      else {
        # the handler may be invoked by something totally out of
        # the scope of DBIO
        DBIO::Exception->throw("DBI Exception (unhandled by DBIO, ::Schema GCed): $_[0]");
      }
    }, '__DBIO__DBH__ERROR__HANDLER__';
  };

  try {
    if(ref $info->[0] eq 'CODE') {
      $dbh = $info->[0]->();
    }
    else {
      require DBI;
      $dbh = DBI->connect(@$info);
    }

    die $DBI::errstr unless $dbh;

    die sprintf ("%s fresh DBI handle with a *false* 'Active' attribute. "
      . 'This handle is disconnected as far as DBIO is concerned, and we can '
      . 'not continue',
      ref $info->[0] eq 'CODE'
        ? "Connection coderef $info->[0] returned a"
        : 'DBI->connect($schema->storage->connect_info) resulted in a'
    ) unless $dbh->FETCH('Active');

    # sanity checks unless asked otherwise
    unless ($self->unsafe) {

      $self->throw_exception(
        'Refusing clobbering of {HandleError} installed on externally supplied '
       ."DBI handle $dbh. Either remove the handler or use the 'unsafe' attribute."
      ) if $dbh->{HandleError} and ref $dbh->{HandleError} ne '__DBIO__DBH__ERROR__HANDLER__';

      # Default via _default_dbi_connect_attributes is 1, hence it was an explicit
      # request, or an external handle. Complain and set anyway
      unless ($dbh->{RaiseError}) {
        carp( ref $info->[0] eq 'CODE'

          ? "The 'RaiseError' of the externally supplied DBI handle is set to false. "
           ."DBIO will toggle it back to true, unless the 'unsafe' connect "
           .'attribute has been supplied'

          : 'RaiseError => 0 supplied in your connection_info, without an explicit '
           .'unsafe => 1. Toggling RaiseError back to true'
        );

        $dbh->{RaiseError} = 1;
      }

      $dbh_error_handler_installer->($self, $dbh);
    }



( run in 0.534 second using v1.01-cache-2.11-cpan-995e09ba956 )