my $name = $self->name;
$name = $$name if (ref $name eq 'SCALAR');
+ $name =~ s/ ^ [^\.]+ \. //x; # strip possible schema qualifier
return join '_', $name, @$cols;
}
# Check foreign and self are right in cond
if ( (ref $cond ||'') eq 'HASH') {
- for (keys %$cond) {
- $self->throw_exception("Keys of condition should be of form 'foreign.col', not '$_'")
- if /\./ && !/^foreign\./;
- }
+ $_ =~ /^foreign\./ or $self->throw_exception("Malformed relationship condition key '$_': must be prefixed with 'foreign.'")
+ for keys %$cond;
+
+ $_ =~ /^self\./ or $self->throw_exception("Malformed relationship condition value '$_': must be prefixed with 'self.'")
+ for values %$cond;
}
my %rels = %{ $self->_relationships };
return undef;
}
+sub _minimal_valueset_satisfying_constraint {
+ my $self = shift;
+ my $args = { ref $_[0] eq 'HASH' ? %{ $_[0] } : @_ };
+
+ $args->{columns_info} ||= $self->columns_info;
+
+ my $vals = $self->storage->_extract_fixed_condition_columns(
+ $args->{values},
+ ($args->{carp_on_nulls} ? 'consider_nulls' : undef ),
+ );
+
+ my $cols;
+ for my $col ($self->unique_constraint_columns($args->{constraint_name}) ) {
+ if( ! exists $vals->{$col} ) {
+ $cols->{missing}{$col} = 1;
+ }
+ elsif( ! defined $vals->{$col} ) {
+ $cols->{$args->{carp_on_nulls} ? 'undefined' : 'missing'}{$col} = 1;
+ }
+ else {
+ $cols->{present}{$col} = 1;
+ }
+
+ $cols->{fc}{$col} = 1 if (
+ ! ( $cols->{missing} || {})->{$col}
+ and
+ keys %{ $args->{columns_info}{$col}{_filter_info} || {} }
+ );
+ }
+
+ $self->throw_exception( sprintf ( "Unable to satisfy requested constraint '%s', missing values for column(s): %s",
+ $args->{constraint_name},
+ join (', ', map { "'$_'" } sort keys %{$cols->{missing}} ),
+ ) ) if $cols->{missing};
+
+ $self->throw_exception( sprintf (
+ "Unable to satisfy requested constraint '%s', FilterColumn values not usable for column(s): %s",
+ $args->{constraint_name},
+ join (', ', map { "'$_'" } sort keys %{$cols->{fc}}),
+ )) if $cols->{fc};
+
+ if (
+ $cols->{undefined}
+ and
+ !$ENV{DBIC_NULLABLE_KEY_NOWARN}
+ ) {
+ carp_unique ( sprintf (
+ "NULL/undef values supplied for requested unique constraint '%s' (NULL "
+ . 'values in column(s): %s). This is almost certainly not what you wanted, '
+ . 'though you can set DBIC_NULLABLE_KEY_NOWARN to disable this warning.',
+ $args->{constraint_name},
+ join (', ', map { "'$_'" } sort keys %{$cols->{undefined}}),
+ ));
+ }
+
+ return { map
+ { $_ => $vals->{$_} }
+ ( keys %{$cols->{present}}, keys %{$cols->{undefined}} )
+ };
+}
+
# Returns the {from} structure used to express JOIN conditions
sub _resolve_join {
my ($self, $join, $alias, $seen, $jpath, $parent_force_left) = @_;
# _resolve_relationship_condition always returns qualified cols even in the
# case of join_free_condition, but nothing downstream expects this
- if (ref $res[0] eq 'HASH' and ($is_objlike[0] or $is_objlike[1]) ) {
+ if ($rc->{join_free_condition} and ref $res[0] eq 'HASH') {
$res[0] = { map
{ ($_ =~ /\.(.+)/) => $res[0]{$_} }
keys %{$res[0]}
# metadata
#
## self-explanatory API, modeled on the custom cond coderef:
-# rel_name
-# foreign_alias
-# foreign_values
-# self_alias
-# self_result_object
-# require_join_free_condition
-# infer_values_based_on (either not supplied or a hashref, implies require_join_free_condition)
-# condition (optional, derived from $self->rel_info(rel_name))
+# rel_name => (scalar)
+# foreign_alias => (scalar)
+# foreign_values => (either not supplied, or a hashref, or a foreign ResultObject (to be ->get_columns()ed), or plain undef )
+# self_alias => (scalar)
+# self_result_object => (either not supplied or a result object)
+# require_join_free_condition => (boolean, throws on failure to construct a JF-cond)
+# infer_values_based_on => (either not supplied or a hashref, implies require_join_free_condition)
+# condition => (sqla cond struct, optional, defeaults to from $self->rel_info(rel_name)->{cond})
#
## returns a hash
-# condition
-# identity_map
-# join_free_condition (maybe unset)
-# inferred_values (always either complete or unset)
+# condition => (a valid *likely fully qualified* sqla cond structure)
+# identity_map => (a hashref of foreign-to-self *unqualified* column equality names)
+# join_free_condition => (a valid *fully qualified* sqla cond structure, maybe unset)
+# inferred_values => (in case of an available join_free condition, this is a hashref of
+# *unqualified* column/value *EQUALITY* pairs, representing an amalgamation
+# of the JF-cond parse and infer_values_based_on
+# always either complete or unset)
#
sub _resolve_relationship_condition {
my $self = shift;
my $exception_rel_id = "relationship '$args->{rel_name}' on source '@{[ $self->source_name ]}'";
my $rel_info = $self->relationship_info($args->{rel_name})
- or $self->throw_exception( "No such $exception_rel_id" );
+# TEMP
+# or $self->throw_exception( "No such $exception_rel_id" );
+ or carp_unique("Requesting resolution on non-existent $exception_rel_id: fix your code *soon*, as it will break with the next major version");
$self->throw_exception("No practical way to resolve $exception_rel_id between two data structures")
if exists $args->{self_result_object} and exists $args->{foreign_values};
$args->{condition} ||= $rel_info->{cond};
- my $rel_rsrc = $self->related_source($args->{rel_name});
-
- if (exists $args->{self_result_object}) {
- $self->throw_exception( "Argument 'self_result_object' must be an object of class '@{[ $self->result_class ]}'" )
- unless defined blessed $args->{self_result_object};
+ $self->throw_exception( "Argument 'self_result_object' must be an object of class '@{[ $self->result_class ]}'" )
+ if (
+ exists $args->{self_result_object}
+ and
+ ( ! defined blessed $args->{self_result_object} or ! $args->{self_result_object}->isa($self->result_class) )
+ )
+ ;
- $self->throw_exception( "Object '$args->{self_result_object}' must be of class '@{[ $self->result_class ]}'" )
- unless $args->{self_result_object}->isa($self->result_class);
- }
+ my $rel_rsrc = $self->related_source($args->{rel_name});
if (exists $args->{foreign_values}) {
if (defined blessed $args->{foreign_values}) {
- $self->throw_exception( "Object supplied as 'foreign_values' ($args->{foreign_values}) must be of class '$rel_info->{class}'" )
- unless $args->{foreign_values}->isa($rel_info->{class});
+
+ $self->throw_exception( "Objects supplied as 'foreign_values' ($args->{foreign_values}) must inherit from DBIx::Class::Row" )
+ unless $args->{foreign_values}->isa('DBIx::Class::Row');
+
+ carp_unique(
+ "Objects supplied as 'foreign_values' ($args->{foreign_values}) "
+ . "usually should inherit from the related ResultClass ('@{[ $rel_rsrc->result_class ]}'), "
+ . "perhaps you've made a mistake invoking the condition resolver?"
+ ) unless $args->{foreign_values}->isa($rel_rsrc->result_class);
$args->{foreign_values} = { $args->{foreign_values}->get_columns };
}
) for keys %{ $args->{foreign_values} ||= {} };
}
else {
- $self->throw_exception( "Argument 'foreign_values' must be either an object inheriting from '$rel_info->{class}' or a hash reference or undef" );
+ $self->throw_exception(
+ "Argument 'foreign_values' must be either an object inheriting from '@{[ $rel_rsrc->result_class ]}', "
+ . "or a hash reference, or undef"
+ );
}
}
($ret->{condition}, $ret->{join_free_condition}, my @extra) = $args->{condition}->($cref_args);
- # FIXME sanity check
- carp_unique('A custom condition coderef can return at most 2 conditions: extra return values discarded')
+ # sanity check
+ $self->throw_exception("A custom condition coderef can return at most 2 conditions, but $exception_rel_id returned extra values: @extra")
if @extra;
if (my $jfc = $ret->{join_free_condition}) {
next if $col_eqs->{$lhs} eq UNRESOLVABLE_CONDITION;
my ($rhs) = @{ is_literal_value( $ret->{condition}{$lhs} ) || next };
- # there is no way to know who is right and who is left
- # therefore the ugly scan below
+ # there is no way to know who is right and who is left in a cref
+ # therefore a full blown resolution call
$colinfos ||= $storage->_resolve_column_info([
{ -alias => $args->{self_alias}, -rsrc => $self },
{ -alias => $args->{foreign_alias}, -rsrc => $rel_rsrc },
]);
- my ($l_col, $l_alias, $r_col, $r_alias) = map {
- ( reverse $_ =~ / ^ (?: ([^\.]+) $ | ([^\.]+) \. (.+) ) /x )[0,1]
- } ($lhs, $rhs);
+ my ($l_col, $r_col) = map { $_ =~ / ([^\.]+) $ /x } ($lhs, $rhs);
if (
$colinfos->{$l_col}