package
DBIx::Class::ResultSource::RowParser;
assemble_simple_parser
assemble_collapsing_parser
)
;
sub
_resolve_prefetch {
my
(
$self
,
$pre
,
$alias
,
$alias_map
,
$order
,
$pref_path
) =
@_
;
$pref_path
||= [];
if
(not
defined
$pre
or not
length
$pre
) {
return
();
}
elsif
(
ref
$pre
eq
'ARRAY'
) {
return
map
{
$self
->_resolve_prefetch(
$_
,
$alias
,
$alias_map
,
$order
, [
@$pref_path
] ) }
@$pre
;
}
elsif
(
ref
$pre
eq
'HASH'
) {
my
@ret
=
map
{
$self
->_resolve_prefetch(
$_
,
$alias
,
$alias_map
,
$order
, [
@$pref_path
] ),
$self
->related_source(
$_
)->_resolve_prefetch(
$pre
->{
$_
},
"${alias}.$_"
,
$alias_map
,
$order
, [
@$pref_path
,
$_
] )
}
keys
%$pre
;
return
@ret
;
}
elsif
(
ref
$pre
) {
$self
->throw_exception(
"don't know how to resolve prefetch reftype "
.
ref
(
$pre
));
}
else
{
my
$p
=
$alias_map
;
$p
=
$p
->{
$_
}
for
(
@$pref_path
,
$pre
);
$self
->throw_exception (
"Unable to resolve prefetch '$pre' - join alias map does not contain an entry for path: "
.
join
(
' -> '
,
@$pref_path
,
$pre
)
)
if
(
ref
$p
->{-join_aliases} ne
'ARRAY'
or not @{
$p
->{-join_aliases}} );
my
$as
=
shift
@{
$p
->{-join_aliases}};
my
$rel_info
=
$self
->relationship_info(
$pre
);
$self
->throw_exception(
$self
->source_name .
" has no such relationship '$pre'"
)
unless
$rel_info
;
my
$as_prefix
= (
$alias
=~ /^.*?\.(.+)$/ ? $1.
'.'
:
''
);
return
map
{ [
"${as}.$_"
,
"${as_prefix}${pre}.$_"
, ] }
$self
->related_source(
$pre
)->columns;
}
}
sub
_mk_row_parser {
my
(
$self
,
$args
,
$attrs
) =
@_
;
die
"HRI without pruning makes zero sense"
if
(
$args
->{hri_style} && !
$args
->{prune_null_branches} );
my
%common
= (
hri_style
=>
$args
->{hri_style},
prune_null_branches
=>
$args
->{prune_null_branches},
val_index
=> {
map
{
$args
->{inflate_map}[
$_
] =>
$_
}
( 0 .. $
},
);
my
$check_null_columns
;
my
$src
= (!
$args
->{collapse} ) ? assemble_simple_parser(\
%common
) :
do
{
my
$collapse_map
=
$self
->_resolve_collapse ({
as
=> {
map
{
ref
$attrs
->{
select
}[
$common
{val_index}{
$_
}] ? () : (
$_
=>
$common
{val_index}{
$_
} ) }
keys
%{
$common
{val_index}}
},
premultiplied
=>
$args
->{premultiplied},
});
$check_null_columns
=
$collapse_map
->{-identifying_columns}
if
@{
$collapse_map
->{-identifying_columns}};
assemble_collapsing_parser({
%common
,
collapse_map
=>
$collapse_map
,
});
};
utf8::upgrade(
$src
)
if
DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE;
return
(
$args
->{
eval
} ? (
eval
"sub $src"
||
die
$@ ) :
$src
,
$check_null_columns
,
);
}
sub
_resolve_collapse {
my
(
$self
,
$args
,
$common_args
) =
@_
;
$args
->{_rel_chain} ||= [
$self
->source_name ];
unless
(
$common_args
->{_as_fq_idx}) {
$common_args
->{_as_fq_idx} = { %{
$args
->{as}} };
$args
->{_is_top_level} = 1;
};
my
(
$my_cols
,
$rel_cols
);
for
(
keys
%{
$args
->{as}}) {
if
(
$_
=~ /^ ([^\.]+) \. (.+) /x) {
$rel_cols
->{$1}{$2} = 1;
}
else
{
$my_cols
->{
$_
} = {};
}
}
my
$relinfo
;
for
my
$rel
(
keys
%$rel_cols
) {
my
$inf
=
$self
->relationship_info (
$rel
);
$relinfo
->{
$rel
} = {
is_single
=> (
$inf
->{attrs}{accessor} &&
$inf
->{attrs}{accessor} ne
'multi'
),
is_inner
=> ( (
$inf
->{attrs}{join_type} ||
''
) !~ /^left/i),
rsrc
=>
$self
->related_source(
$rel
),
};
my
$cond
=
$inf
->{cond};
if
(
ref
$cond
eq
'HASH'
and
keys
%$cond
and
!
defined
first {
$_
!~ /^foreign\./ } (
keys
%$cond
)
and
!
defined
first {
$_
!~ /^self\./ } (
values
%$cond
)
) {
for
my
$f
(
keys
%$cond
) {
my
$s
=
$cond
->{
$f
};
$_
=~ s/^ (?: foreign | self ) \.//x
for
(
$f
,
$s
);
$relinfo
->{
$rel
}{fk_map}{
$s
} =
$f
;
}
}
}
for
my
$rel
(
grep
{
$relinfo
->{
$_
}{is_inner} }
keys
%$relinfo
) {
my
$ri
=
$relinfo
->{
$rel
};
for
(
keys
%{
$ri
->{fk_map}} ) {
$my_cols
->{
$_
} ||= {
via_fk
=>
"$rel.$ri->{fk_map}{$_}"
}
if
defined
$rel_cols
->{
$rel
}{
$ri
->{fk_map}{
$_
}}
}
}
my
$assumed_from_parent
;
if
( !
$args
->{_parent_info}{underdefined} and !
$args
->{_parent_info}{rev_rel_is_optional} ) {
for
my
$col
(
values
%{
$args
->{_parent_info}{rel_condition} || {}} ) {
next
if
exists
$my_cols
->{
$col
};
$my_cols
->{
$col
} = {
via_collapse
=>
$args
->{_parent_info}{collapse_on_idcols} };
$assumed_from_parent
->{columns}{
$col
}++;
}
}
if
(
$my_cols
) {
my
$ci
=
$self
->columns_info;
$my_cols
->{
$_
}{colinfo} =
$ci
->{
$_
}
for
keys
%$my_cols
;
}
my
$collapse_map
;
unless
(
$collapse_map
->{-identifying_columns}) {
$collapse_map
->{-identifying_columns} =
$args
->{_parent_info}{collapse_on_idcols}
if
$args
->{_parent_info}{collapser_reusable};
}
if
(
!
$collapse_map
->{-identifying_columns}
and
$my_cols
and
my
$idset
=
$self
->_identifying_column_set ({
map
{
$_
=>
$my_cols
->{
$_
}{colinfo} }
keys
%$my_cols
})
) {
my
@reduced_set
=
grep
{ !
$assumed_from_parent
->{columns}{
$_
} }
@$idset
;
$collapse_map
->{-identifying_columns} = [ __unique_numlist(
@{
$args
->{_parent_info}{collapse_on_idcols}||[] },
(
map
{
my
$fqc
=
join
(
'.'
,
@{
$args
->{_rel_chain}}[1 .. $
(
$my_cols
->{
$_
}{via_fk} ||
$_
),
);
$common_args
->{_as_fq_idx}->{
$fqc
};
}
@reduced_set
),
)];
}
unless
(
$collapse_map
->{-identifying_columns}) {
my
@candidates
;
for
my
$rel
(
keys
%$relinfo
) {
next
unless
(
$relinfo
->{
$rel
}{is_single} &&
$relinfo
->{
$rel
}{is_inner});
if
(
my
$rel_collapse
=
$relinfo
->{
$rel
}{rsrc}->_resolve_collapse ({
as
=>
$rel_cols
->{
$rel
},
_rel_chain
=> [ @{
$args
->{_rel_chain}},
$rel
],
_parent_info
=> {
underdefined
=> 1 },
},
$common_args
)) {
push
@candidates
,
$rel_collapse
->{-identifying_columns};
}
}
if
(
@candidates
) {
(
$collapse_map
->{-identifying_columns}) =
sort
{
scalar
@$a
<=>
scalar
@$b
} (
@candidates
);
}
}
if
(
!
$collapse_map
->{-identifying_columns}
and
!
$args
->{premultiplied}
and
$args
->{_is_top_level}
) {
my
(
@collapse_sets
,
$uncollapsible_chain
);
for
my
$rel
(
keys
%$relinfo
) {
next
if
(
$relinfo
->{
$rel
}{is_single} &&
$relinfo
->{
$rel
}{is_inner});
if
(
my
$clps
=
$relinfo
->{
$rel
}{rsrc}->_resolve_collapse ({
as
=>
$rel_cols
->{
$rel
},
_rel_chain
=> [ @{
$args
->{_rel_chain}},
$rel
],
_parent_info
=> {
underdefined
=> 1 },
},
$common_args
) ) {
if
(
$relinfo
->{
$rel
}{is_single}) {
push
@collapse_sets
,
$clps
->{-identifying_columns};
}
elsif
(!
$relinfo
->{
$rel
}{fk_map}) {
$uncollapsible_chain
= 1;
last
;
}
else
{
my
$defined_cols_parent_side
;
for
my
$fq_col
(
grep
{ /^
$rel
\.[^\.]+$/ }
keys
%{
$args
->{as}} ) {
my
(
$col
) =
$fq_col
=~ /([^\.]+)$/;
$defined_cols_parent_side
->{
$_
} =
$args
->{as}{
$fq_col
}
for
grep
{
$relinfo
->{
$rel
}{fk_map}{
$_
} eq
$col
}
keys
%{
$relinfo
->{
$rel
}{fk_map}}
;
}
if
(
my
$set
=
$self
->_identifying_column_set([
keys
%$defined_cols_parent_side
]) ) {
push
@collapse_sets
, [
sort
map
{
$defined_cols_parent_side
->{
$_
} }
@$set
];
}
else
{
$uncollapsible_chain
= 1;
last
;
}
}
}
else
{
$uncollapsible_chain
= 1;
last
;
}
}
unless
(
$uncollapsible_chain
) {
$collapse_map
->{-identifying_columns} = [];
$collapse_map
->{-identifying_columns_variants} = [
sort
{
(
scalar
@$a
) <=> (
scalar
@$b
) or max(
@$a
) <=> max(
@$b
)
}
@collapse_sets
];
}
}
if
(
$args
->{_parent_info}{underdefined}) {
return
$collapse_map
->{-identifying_columns} ?
$collapse_map
:
undef
}
elsif
(!
$collapse_map
->{-identifying_columns}) {
$self
->throw_exception (
sprintf
"Unable to calculate a definitive collapse column set for %s%s: fetch more unique non-nullable columns"
,
$self
->source_name,
@{
$args
->{_rel_chain}} > 1
?
sprintf
(
' (last member of the %s chain)'
,
join
' -> '
, @{
$args
->{_rel_chain}} )
:
''
,
);
}
$collapse_map
->{-identifying_columns} = [ __unique_numlist(
@{
$args
->{_parent_info}{collapse_on_idcols}||[] },
@{
$collapse_map
->{-identifying_columns} },
)];
my
@id_sets
;
for
my
$rel
(
sort
keys
%$relinfo
) {
$collapse_map
->{
$rel
} =
$relinfo
->{
$rel
}{rsrc}->_resolve_collapse ({
as
=> {
map
{
$_
=> 1 } (
keys
%{
$rel_cols
->{
$rel
}} ) },
_rel_chain
=> [ @{
$args
->{_rel_chain}},
$rel
],
_parent_info
=> {
collapse_on_idcols
=> [ @{
$collapse_map
->{-identifying_columns}} ],
rel_condition
=>
$relinfo
->{
$rel
}{fk_map},
is_optional
=> !
$relinfo
->{
$rel
}{is_inner},
rev_rel_is_optional
=> ( first
{
ref
$_
->{cond} eq
'HASH'
and (
$_
->{attrs}{join_type}||
''
) !~ /^left/i }
values
%{
$self
->reverse_relationship_info(
$rel
) },
) ? 0 : 1,
collapser_reusable
=> (
$relinfo
->{
$rel
}{is_single}
&&
$relinfo
->{
$rel
}{is_inner}
&&
@{
$collapse_map
->{-identifying_columns}}
) ? 1 : 0,
},
},
$common_args
);
$collapse_map
->{
$rel
}{-is_single} = 1
if
$relinfo
->{
$rel
}{is_single};
$collapse_map
->{
$rel
}{-is_optional} ||= 1
unless
$relinfo
->{
$rel
}{is_inner};
}
return
$collapse_map
;
}
sub
__unique_numlist {
sort
{
$a
<=>
$b
}
keys
%{ {
map
{
$_
=> 1 }
@_
}}
}
1;