CXC-DB-DDL
view release on metacpan or search on metacpan
lib/CXC/DB/DDL/Util.pm view on Meta::CPAN
package CXC::DB::DDL::Util;
# ABSTRACT: CXC::DB::DDL utilities
use v5.26;
use strict;
use warnings;
use experimental 'signatures', 'postderef', 'declared_refs';
our $VERSION = '0.21';
use List::Util qw( pairs first );
use Sub::Util qw( set_subname );
use Module::Runtime ();
use Import::Into;
use Digest::MD5;
use Package::Stash;
use Ref::Util ();
use Hash::Util ();
use DBI ();
use DBI::Const::GetInfoType;
use CXC::DB::DDL::Constants -all;
use namespace::clean;
use base 'Exporter::Tiny';
use constant BASE_TYPE_PACKAGE => __PACKAGE__ . '::Type';
use constant DEFAULT_FIELD_CLASS => 'CXC::DB::DDL::Field';
our %EXPORT_TAGS = (
schema_funcs => [qw( xFIELDS xCHECK xTYPE )],
misc => [ 'SQL_TYPE_NAMES', 'SQL_TYPE_VALUES', 'sqlt_entity_map', 'db_version' ],
);
our @EXPORT_OK = ( map { Ref::Util::is_arrayref( $_ ) ? $_->@* : () } values %EXPORT_TAGS );
my sub gen_package_name;
my sub types;
my sub croak {
require Carp;
goto \&Carp::croak;
}
my %CACHE = (
'DBI' => {
tag => ':sql_types',
types => {
package => gen_package_name( 'DBI' ),
},
subs => {},
fields => {
default => +( DEFAULT_FIELD_CLASS ),
},
},
);
my sub is_supported_dbd ( $dbd ) {
my %supported;
@supported{ 'DBI', SUPPORTED_DBDS, keys %CACHE } = ();
return exists $supported{$dbd};
}
sub gen_package_name ( $dbd, @xtra ) {
# create a unique class for this blend
return join q{::}, BASE_TYPE_PACKAGE, Digest::MD5::md5_hex( $dbd // (), @xtra );
}
my sub init ( $globals ) {
# we can reach this sub through multiple paths; only init once.
return if exists $globals->{ __PACKAGE__() };
# request to add support for specified DBD?
if ( my $request = $globals->{add_dbd} ) {
Ref::Util::is_hashref( $request )
or croak( "add_dbd: expected the DBD entry to be a hashref, got @{[ ref $request ]} " );
my ( $dbd, $tag, $field_class, $type_class )
= $request->@{ 'dbd', 'tag', 'field_class', 'type_class' };
defined( $dbd ) && defined( $tag ) && defined( $field_class )
or croak(
sprintf( 'add_dbd: missing dbd (%s), tag(%s), or field_class(%s)',
map { $_ // 'undef' } ( $dbd, $tag, $field_class ), ) );
# silently ignores attempts to redefine. should it warn?
if ( !exists $CACHE{$dbd} ) {
$CACHE{$dbd} = {
tag => $tag,
fields => {
default => $field_class,
},
types => {
class => $type_class,
package => gen_package_name( $dbd ),
},
subs => {},
};
}
# load the dbd types by default.
$globals->{dbd} //= $dbd;
}
my %stash;
# request particular dbd or fallback to generic DBI support
my $dbd = $globals->{dbd} // 'DBI';
Ref::Util::is_ref( $dbd )
and croak( 'dbd: value must be a scalar' );
defined( my $cache = $CACHE{$dbd} )
or croak( "dbd: unsupported DBD: $dbd" );
$stash{dbd} = $dbd;
$stash{cache} = $cache;
# Field wrappers generated by mk_field
# override field_class?
$stash{field_class} = $globals->{field_class} // $cache->{fields}{default};
$stash{fields} = $cache->{fields}{ $stash{field_class} } //= {};
$globals->{ __PACKAGE__() } = \%stash;
return;
}
# load the types for DBI and requested DBD's into individual
# packages and create a merged hash of names and subs
# cached by a hash of the DBD names.
sub types ( $dbd, $collection = 'all' ) {
defined( my $cache = $CACHE{$dbd} )
or croak( "types: unsupported dbd: $dbd" );
return $cache->{types}{$collection} if defined $cache->{types}{$collection};
my %symbol;
my $stash = Package::Stash->new( $cache->{types}{package} );
my $module = Module::Runtime::use_module( $dbd eq 'DBI' ? $dbd : "DBD::$dbd" );
$module->import::into( $stash->name, $cache->{tag} );
my $lsymbol = $stash->get_all_symbols( 'CODE' );
my @from_keys = keys $lsymbol->%*;
# strip off SQL_ from DBI types
my @to_keys
= $dbd eq 'DBI'
? map { s/^SQL_//r } @from_keys
: @from_keys;
# if this is a DBD specific set of types, and an object is
# requested, make one. This prevents collisions when the DBD
# type code is the same as a standard SQL_TYPE_xxxx code.
# The class MUST alread be loaded, so we don't have to
# worry about where it is defined (inner package, etc.)
if ( my $type_class = $cache->{types}{class} ) {
my %to_key;
@to_key{@from_keys} = @to_keys;
for my $from ( @from_keys ) {
my $to = $to_key{$from};
my $value = $lsymbol->{$from}->();
$symbol{$to} = set_subname "DBD_TYPE_$to", sub { $type_class->new( $from, $value ) };
}
}
else {
( run in 1.703 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )