Skip to content

Commit a3ae79e

Browse files
committed
Add an extra RV to the relationship resolver
A certain spot in the codebase check whether a relationship is "simple". This additional flag allows to consider coderef conditions as well, instead of simply punting with "not a HASH? - no can do" See next commit for the actual switchover While at it fix a subtle bug introduced in b5ce674 - originally the helper is_literal_value recognized -ident as a valid literal. Later on during the migration into SQLA this logic was lost (I do not exactly recall the details), yet the DBIC side was never adjusted. All callsites were audited to make sure nothing else was missed.
1 parent ea3ee77 commit a3ae79e

File tree

3 files changed

+90
-6
lines changed

3 files changed

+90
-6
lines changed

lib/DBIx/Class/ResultSource.pm

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use DBIx::Class::Carp;
1919
use DBIx::Class::_Util qw(
2020
UNRESOLVABLE_CONDITION
2121
dbic_internal_try fail_on_internal_call
22-
refdesc emit_loud_diag
22+
refdesc emit_loud_diag dump_value
2323
);
2424
use DBIx::Class::SQLMaker::Util qw( normalize_sqla_condition extract_equality_conditions );
2525
use DBIx::Class::ResultSource::FromSpec::Util 'fromspec_columns_info';
@@ -2238,6 +2238,7 @@ Internals::SvREADONLY($UNRESOLVABLE_CONDITION => 1);
22382238
## returns a hash
22392239
# condition => (a valid *likely fully qualified* sqla cond structure)
22402240
# identity_map => (a hashref of foreign-to-self *unqualified* column equality names)
2241+
# identity_map_matches_condition => (boolean, indicates whether the entire condition is expressed in the identity-map)
22412242
# join_free_condition => (a valid *fully qualified* sqla cond structure, maybe unset)
22422243
# inferred_values => (in case of an available join_free condition, this is a hashref of
22432244
# *unqualified* column/value *EQUALITY* pairs, representing an amalgamation
@@ -2591,29 +2592,51 @@ sub _resolve_relationship_condition {
25912592
"Unable to complete value inferrence - $exception_rel_id results in expression(s) instead of definitive values: %s",
25922593
do {
25932594
# FIXME - used for diag only, but still icky
2594-
my $sqlm = $self->schema->storage->sql_maker;
2595+
my $sqlm =
2596+
dbic_internal_try { $self->schema->storage->sql_maker }
2597+
||
2598+
(
2599+
require DBIx::Class::SQLMaker
2600+
and
2601+
DBIx::Class::SQLMaker->new
2602+
)
2603+
;
25952604
local $sqlm->{quote_char};
25962605
local $sqlm->{_dequalify_idents} = 1;
25972606
($sqlm->_recurse_where({ -and => \@nonvalues }))[0]
25982607
}
25992608
)) if @nonvalues;
26002609

2601-
26022610
$ret->{inferred_values} ||= {};
26032611

26042612
$ret->{inferred_values}{$_} = $args->{infer_values_based_on}{$_}
26052613
for keys %{$args->{infer_values_based_on}};
26062614
}
26072615

2616+
my $identity_map_incomplete;
2617+
26082618
# add the identities based on the main condition
26092619
# (may already be there, since easy to calculate on the fly in the HASH case)
26102620
if ( ! $ret->{identity_map} ) {
26112621

26122622
my $col_eqs = extract_equality_conditions($ret->{condition});
26132623

2624+
$identity_map_incomplete++ if (
2625+
$ret->{condition} eq UNRESOLVABLE_CONDITION
2626+
or
2627+
(
2628+
keys %{$ret->{condition}}
2629+
!=
2630+
keys %$col_eqs
2631+
)
2632+
);
2633+
26142634
my $colinfos;
26152635
for my $lhs (keys %$col_eqs) {
26162636

2637+
# start with the assumption it won't work
2638+
$identity_map_incomplete++;
2639+
26172640
next if $col_eqs->{$lhs} eq UNRESOLVABLE_CONDITION;
26182641

26192642
# there is no way to know who is right and who is left in a cref
@@ -2626,8 +2649,17 @@ sub _resolve_relationship_condition {
26262649

26272650
next unless $colinfos->{$lhs}; # someone is engaging in witchcraft
26282651

2629-
if ( my $rhs_ref = is_literal_value( $col_eqs->{$lhs} ) ) {
2630-
2652+
if( my $rhs_ref =
2653+
(
2654+
ref $col_eqs->{$lhs} eq 'HASH'
2655+
and
2656+
keys %{$col_eqs->{$lhs}} == 1
2657+
and
2658+
exists $col_eqs->{$lhs}{-ident}
2659+
)
2660+
? [ $col_eqs->{$lhs}{-ident} ] # repack to match the RV of is_literal_value
2661+
: is_literal_value( $col_eqs->{$lhs} )
2662+
) {
26312663
if (
26322664
$colinfos->{$rhs_ref->[0]}
26332665
and
@@ -2637,6 +2669,9 @@ sub _resolve_relationship_condition {
26372669
? ( $ret->{identity_map}{$colinfos->{$lhs}{-colname}} = $colinfos->{$rhs_ref->[0]}{-colname} )
26382670
: ( $ret->{identity_map}{$colinfos->{$rhs_ref->[0]}{-colname}} = $colinfos->{$lhs}{-colname} )
26392671
;
2672+
2673+
# well, what do you know!
2674+
$identity_map_incomplete--;
26402675
}
26412676
}
26422677
elsif (
@@ -2656,6 +2691,10 @@ sub _resolve_relationship_condition {
26562691
}
26572692
}
26582693

2694+
$ret->{identity_map_matches_condition} = ($identity_map_incomplete ? 0 : 1)
2695+
if $ret->{identity_map};
2696+
2697+
26592698
# FIXME - temporary, to fool the idiotic check in SQLMaker::_join_condition
26602699
$ret->{condition} = { -and => [ $ret->{condition} ] } unless (
26612700
$ret->{condition} eq UNRESOLVABLE_CONDITION
@@ -2667,6 +2706,50 @@ sub _resolve_relationship_condition {
26672706
)
26682707
);
26692708

2709+
2710+
if( DBIx::Class::_ENV_::ASSERT_NO_INCONSISTENT_RELATIONSHIP_RESOLUTION ) {
2711+
2712+
my $sqlm =
2713+
dbic_internal_try { $self->schema->storage->sql_maker }
2714+
||
2715+
(
2716+
require DBIx::Class::SQLMaker
2717+
and
2718+
DBIx::Class::SQLMaker->new
2719+
)
2720+
;
2721+
2722+
local $sqlm->{_dequalify_idents} = 1;
2723+
2724+
my ($cond_as_sql, $identmap_as_sql) = map
2725+
{ join ' : ', map { defined $_ ? $_ : '{UNDEF}' } $sqlm->_recurse_where($_) }
2726+
(
2727+
$ret->{condition},
2728+
2729+
{ map {
2730+
# inverse because of how the idmap is declared
2731+
$ret->{identity_map}{$_} => { -ident => $_ }
2732+
} keys %{$ret->{identity_map}} },
2733+
)
2734+
;
2735+
2736+
emit_loud_diag(
2737+
confess => 1,
2738+
msg => sprintf (
2739+
"Resolution of %s produced inconsistent metadata:\n\n"
2740+
. "returned value of 'identity_map_matches_condition': %s\n"
2741+
. "returned 'condition' rendered as de-qualified SQL: %s\n"
2742+
. "returned 'identity_map' rendered as de-qualified SQL: %s\n\n"
2743+
. "The condition declared on the misclassified relationship is: %s ",
2744+
$exception_rel_id,
2745+
( $ret->{identity_map_matches_condition} || 0 ),
2746+
$cond_as_sql,
2747+
$identmap_as_sql,
2748+
dump_value( $rel_info->{cond} ),
2749+
),
2750+
) if ( $ret->{identity_map_matches_condition} xor ( $cond_as_sql eq $identmap_as_sql ) );
2751+
}
2752+
26702753
$ret;
26712754
}
26722755

lib/DBIx/Class/SQLMaker.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ sub _assert_bindval_matches_bindtype () { 1 };
191191

192192
# poor man's de-qualifier
193193
sub _quote {
194-
$_[0]->next::method( ( $_[0]{_dequalify_idents} and ! ref $_[1] )
194+
$_[0]->next::method( ( $_[0]{_dequalify_idents} and defined $_[1] and ! ref $_[1] )
195195
? $_[1] =~ / ([^\.]+) $ /x
196196
: $_[1]
197197
);

lib/DBIx/Class/_Util.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ BEGIN {
5252
DBIC_ASSERT_NO_INTERNAL_INDIRECT_CALLS
5353
DBIC_ASSERT_NO_ERRONEOUS_METAINSTANCE_USE
5454
DBIC_ASSERT_NO_FAILING_SANITY_CHECKS
55+
DBIC_ASSERT_NO_INCONSISTENT_RELATIONSHIP_RESOLUTION
5556
DBIC_STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE
5657
DBIC_STRESSTEST_COLUMN_INFO_UNAWARE_STORAGE
5758
)

0 commit comments

Comments
 (0)