Bivio::SQL::ListQuery
# Copyright (c) 1999-2012 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::SQL::ListQuery;
use strict;
use Bivio::Base 'Collection.Attributes';
# C<Bivio::SQL::ListQuery> describes a query for a
# L<Bivio::Biz::ListModel|Bivio::Biz::ListModel>. The query
# inputs come from the request, but all values are defaulted if
# the request query is empty.
#
# L<Bivio::Biz::ListModel::execute|Bivio::Biz::ListModel/"execute"> creates a
# C<ListQuery>.
#
#
# All attributes exist, but may be undef.
# Use L<get|"get"> to ensure there are no spelling errors in your code.
#
#
# auth_id : Bivio::Type::PrimaryId
#
# The auth_id extracted from the request.
#
# count : int
#
# Number of lines on a page. Not passed in the query, set by the
# caller of L<new|"new">.
#
# begin_date : Bivio::Type::Date
#
# If supplied, then is the last date to return (inclusive). Allows you to
# specify an explicit date range.
#
# You can pass C<begin_date> in the query string and it will be parsed as a date.
#
# You cannot pass I<begin_date> and I<interval>.
#
# date : Bivio::Type::Date
#
# Arbitrary date used to load the ListModel. Bivio::Type::DateTime
# and a Bivio::Type::Date are both acceptable.
#
# You can pass C<date>, C<end_date>, or C<report_date> in the query string and it
# will be parsed as a date.
#
# Will be set to DateTime-E<gt>local_end_of_today if C<undef>
# and support has I<want_date> set.
#
# has_next : boolean
#
# If there are unreturned items as a result of this query, is true.
# This value may be invalid until the query is executed by
# L<Bivio::SQL::ListSupport|Bivio::SQL::ListSupport>.
# Initialized to false.
#
# has_prev : boolean
#
# Are there items prior to this query?
# This value may be invalid until the query is executed by
# L<Bivio::SQL::ListSupport|Bivio::SQL::ListSupport>.
# Initialized to false.
#
# interval : Bivio::Type::DateInterval
#
# A L<Bivio::Type::DateInterval|Bivio::Type::DateInterval>. Does not default.
# You can pass C<interval> in the query string and it will be parsed
# as an interval.
#
# Accepts a ref or tries to convert unsafe_from_any. Is left at C<undef>,
# if not set.
#
# You cannot pass I<begin_date> and I<interval>.
#
# list_support : Bivio::SQL::ListSupport
#
# Support instance used to create this query.
#
# next : array_ref
#
# primary key of previous item.
#
# next_page : int
#
# page number of previous page.
#
# order_by : array_ref
#
# List of fields to order_by as supplied by the user and filled in
# from model's order_by names. Even indexed elements are the name
# and odd elements are true if ascending and false if descending.
# It is an array_ref to preserve the order specified by the user.
#
# You can pass C<order_by> in the query string and it will be parsed, if
# C<o> is not set.
#
# page_count : int
#
# Total number of pages that can be returned by this the query. Only set if
# I<want_page_count> is true.
#
# page_number : int
#
# Page number on which I<this> is on or the page we are viewing.
# Incoming is ignored if I<this>, because is set by
# L<Bivio::SQL::ListSupport|Bivio::SQL::ListSupport>.
# Page numbers are one-based.
#
# You can pass C<page_number> in the query string and it will be parsed, if
# C<n> is not set.
#
# parent_id : string
#
# Primary id of parent list. It is used to further qualify a
# list of lists.
#
# You can pass C<parent_id> in the query string and it will be parsed, if
# C<p> is not set.
#
# prev : array_ref
#
# primary key of next item.
#
# prev_page : int
#
# page number of next page.
#
# row_count : int
#
# Total number of rows that can be returned by this the query. Only set if
# I<want_page_count> is true.
#
# search : string
#
# An arbitrary search string. The only parsing done by this module
# is to trim blanks and set to undef if zero length.
#
# You can pass C<search> in the query string and it will be parsed
# if the single character attribute doesn't exist.
#
# this : array_ref
#
# The primary key values for this item. The query should be to
# find this primary key. There should only be one row returned.
#
# You can pass C<this> in the query string and it will be parsed, if
# C<t> is not set.
#
# want_first_only : boolean (optional)
#
# Set this to true if you want to get the first element and
# set it as I<this>. Used by
# L<Bivio::SQL::ListModel::load|Bivio::SQL::ListModel/"load">.
#
# want_only_one_order_by : boolean [0]
#
# When preparing the C<ORDER BY> clause, only include the first order by column,
# and ignore the rest. This performance option is necessary for certain complex
# queries with Postgres. Don't use this unless you are really sure you need it.
# I<want_only_one_order_by> does not affect I<order_by> of the query, but the
# implementation of the ORDER BY clause in
# L<Bivio::SQL::ListSupport|Bivio::SQL::ListSupport>.
#
# want_page_count : boolean (optional)
#
# Set this to true if you want to count the number of pages.
my($_A) = __PACKAGE__->use('IO.Alert');
my($_D) = __PACKAGE__->use('Type.Date');
my($_DI) = __PACKAGE__->use('Type.DateInterval');
my($_DT) = __PACKAGE__->use('Type.DateTime');
my($_I) = __PACKAGE__->use('Type.Integer');
my($_PI) = __PACKAGE__->use('Type.PrimaryId');
my($_PS) = __PACKAGE__->use('Type.PageSize');
my($_S) = __PACKAGE__->use('Type.String');
my($_COUNT_TYPE) = $_I->new(1, $_PS->get_max);
my(%_QUERY_TO_FIELDS) = (
'b' => 'begin_date',
'd' => 'date',
'c' => 'count',
'i' => 'interval',
'n' => 'page_number',
'o' => 'order_by',
'p' => 'parent_id',
's' => 'search',
't' => 'this',
);
my(@_QUERY_FIELDS) = sort(values(%_QUERY_TO_FIELDS));
my(%_ATTR_TO_CHAR) = map {
($_QUERY_TO_FIELDS{$_}, $_);
} keys(%_QUERY_TO_FIELDS);
# Separates elements in a multivalued primary key.
# Tightly coupled with $Bivio::Biz::FormContext::_HASH_CHAR
my($_SEPARATOR) = "\177";
my($_SEPARATOR_AS_QUERY) = $_S->to_query($_SEPARATOR);
sub DEFAULT_MAX_COUNT {
return $_COUNT_TYPE->get_max;
}
sub FIRST_PAGE {
# Returns 1.
return 1;
}
sub as_string {
my($self) = @_;
my($sep) = 0;
return !ref($self) ? $self
: $_A->format_args(
'ListQuery[',
map({
my($c, $n) = ($_, $_QUERY_TO_FIELDS{$_});
my($v) = $self->unsafe_get($n || $c);
!defined($v) ? () : (($sep++ ? '&' : ''), $c, '=', $v);
} sort(keys(%_QUERY_TO_FIELDS), 'auth_id')),
']',
);
}
sub clean_raw {
my(undef, $query, $support) = @_;
# Removes any raw query keys that aren't part of the "valid" set. If
# the model has I<other_query_keys>, these will not be removed.
# Used by
# L<Bivio::Biz::ListModel::parse_query_from_request|Bivio::Biz::ListModel/"parse_query_from_request">.
#
# Returns I<query>.
$_A->warn_deprecated('must pass ListSupport')
unless $support;
my($oqk) = _other_query_keys($support);
foreach my $k (keys(%$query)) {
delete($query->{$k})
unless $_QUERY_TO_FIELDS{$k}
|| $_ATTR_TO_CHAR{$k}
|| $oqk && grep($k =~ $_, @$oqk);
}
return $query;
}
sub format_uri {
my($self, $support, $new_attrs) = @_;
# Lets you override any I<new_attrs>, useful for other_query_keys at this time.
# I<new_attrs> defaults to empty.
return _format_uri({
%{$self->internal_get},
%{$new_attrs || {}},
}, $support);
}
sub format_uri_for_any_list {
my($self, $support, $new_attrs) = @_;
# Returns the query without a this or page_number with overrides in
# I<new_attrs>.
my(%attrs) = %{$self->internal_get};
$attrs{this} = undef;
$attrs{page_number} = undef;
while ($new_attrs and my($k, $v) = each(%$new_attrs)) {
$attrs{$k} = $v;
}
return _format_uri(\%attrs, $support);
}
sub format_uri_for_next {
my($self, $support) = @_;
# Generates the query string (URL-encoded) for I<next> this.
my(%attrs) = %{$self->internal_get()};
$attrs{this} = $attrs{next};
$attrs{page_number} = undef;
return _format_uri(\%attrs, $support);
}
sub format_uri_for_next_page {
my($self, $support) = @_;
# Generates the query string (URL-encoded) for next page.
my(%attrs) = %{$self->internal_get()};
$attrs{this} = undef;
$attrs{page_number} = $attrs{next_page};
return _format_uri(\%attrs, $support);
}
sub format_uri_for_prev {
my($self, $support) = @_;
# Generates the query string (URL-encoded) for I<prev> this.
my(%attrs) = %{$self->internal_get()};
$attrs{this} = $attrs{prev};
$attrs{page_number} = undef;
return _format_uri(\%attrs, $support);
}
sub format_uri_for_prev_page {
my($self, $support) = @_;
# Generates the query string (URL-encoded) for prev page.
my(%attrs) = %{$self->internal_get()};
$attrs{this} = undef;
$attrs{page_number} = $attrs{prev_page};
return _format_uri(\%attrs, $support);
}
sub format_uri_for_this {
my($self, $support, $this_row) = @_;
# Generates the query string (URL-encoded) for the primary key
# of I<this_row> using the current query parameters.
my(%attrs) = ref($self) ? %{$self->internal_get()} : ();
$attrs{this} = $this_row;
$attrs{page_number} = undef;
$attrs{order_by} = undef;
return _format_uri(\%attrs, $support);
}
sub format_uri_for_this_as_parent {
my($self, $support, $this_row) = @_;
# Generates the query string (URL-encoded) for this query's I<this> as
# a parent query (p=)
my($parent_id) = ref($self)
? delete($self->internal_get->{parent_id})
: undef;
my($res) = $self->format_uri_for_this($support, $this_row);
#TODO: Wow is this a hack!
$res =~ s/\bt=/p=/;
$self->internal_get->{parent_id} = $parent_id
if defined($parent_id);
return $res;
}
sub format_uri_for_this_child {
my($self, $support, $this_row) = @_;
# Generates the query string (URL-encoded) for the primary key
# of I<this_row> as the parent_id. There is no page number.
my(%attrs) = %{$self->internal_get()};
$attrs{parent_id} = $this_row->{$support->get('primary_key_names')->[0]};
$attrs{this} = undef;
$attrs{page_number} = undef;
# At this point, we lose context. The query is "owned" relative to
# the child, so the order by goes away
$attrs{order_by} = undef || $this_row->{'ListQuery.order_by'};
$attrs{search} = undef;
return _format_uri(\%attrs, $support);
}
sub format_uri_for_this_page {
my($self, $support, $new_attrs) = @_;
# Generates the query string (URL-encoded) for this page.
# I<new_attrs> can be passed in to extend or override existing attributes.
my(%attrs) = (%{$self->internal_get()}, %$new_attrs);
$attrs{this} = undef;
return _format_uri(\%attrs, $support);
}
sub format_uri_for_this_parent {
my($self, $support) = @_;
# Generates the query string (URL-encoded) for this query's parent as
# this.
my($attrs) = $self->internal_get();
#TODO: Need to know which detail_model this is bound
# Format explicitly, because breaks all the rules
die('no parent_id associated with query') unless $attrs->{parent_id};
my($res) = 't='.$attrs->{parent_id};
# We don't know ordering, because this is the parent list
return $res;
}
sub format_uri_for_this_path {
my($self, $support, $this_row) = @_;
# Generates the query string (URL-encoded) for this detail, but doesn't
# include "this".
#
# May be called statically iwc the version is pulled from support.
my(%attrs) = ref($self) ? %{$self->internal_get()} : ();
$attrs{this} = undef;
$attrs{page_number} = undef;
return _format_uri(\%attrs, $support);
}
sub get_hidden_field_values {
my($self, $support) = @_;
my($attrs) = $self->internal_get();
my($columns) = $support->get('columns');
my($ob) = $attrs->{order_by};
# Since this is a search, there is no page_number or this, but
# there may be need to be a parent_id.
my(@res) = ();
if ($ob) {
my($o);
for (my($i) = 0; $i < int(@$ob); $i += 2) {
$o .= $columns->{$ob->[$i]}->{order_by_index}
. ($ob->[$i+1] ? 'a' : 'd');
}
push(@res, 'o' => $o);
}
return \@res;
}
sub get_sort_order_for_type {
my(undef, $type) = @_;
# Returns the default sort order for the type.
# Dates and Primary ids are reverse sorted by default. PrimaryIds
# are used in entry/transaction lists to allow predictive ordering of
# entries entered at the same time (a transaction is composed of several
# entries).
return (UNIVERSAL::isa($type, 'Bivio::Type::DateTime')
|| UNIVERSAL::isa($type, 'Bivio::Type::PrimaryId'))
? 0 : 1,
}
sub initialize_support {
my($proto, $support) = @_;
# Should only be called by L<Bivio::SQL::Support|Bivio::SQL::Support>.
#
# Sets up the default order_by on the I<support>.
return unless $support->unsafe_get('order_by');
# This requires intimate knowledge of format_uri and parse_order_by
my($attrs) = {};
# Always fills in defaults
_parse_order_by($attrs, $support, 'Bivio::Die');
# format_uri needs default_order_by_query to be set
$support->put(default_order_by_query => '');
$support->put(default_order_by_query => _format_uri($attrs, $support));
return;
}
sub new {
my($proto, $attrs, $support, $die) = @_;
# Creates a new ListQuery. I<auth_id> must be set in I<query> if required.
#
# B<I<query> will be subsumed by this module. Do not use it again.>
foreach my $k (@_QUERY_FIELDS) {
&{\&{'_parse_'.$k}}($attrs, $support, $die);
}
return _new($proto, $attrs, $support, $die);
}
sub set_request_this {
my(undef, $req, $this_id) = @_;
# Set I<this_id> on I<req>'s query. I<this_id> must be
# L<Bivio::Type::PrimaryId|Bivio::Type::PrimaryId>.
die('this_id must be a PrimaryId')
if defined($this_id) && $this_id !~ /^\d+$/;
my($query) = $req->get('query') || {};
$query->{t} = $_PI->to_literal($this_id);
$req->put(query => $query);
return;
}
sub to_char {
my(undef, $attr_name) = @_;
# Returns the character for the specified field. This value is a
# constant.
die($attr_name, ': unknown query attribute')
unless defined($_ATTR_TO_CHAR{$attr_name});
return $_ATTR_TO_CHAR{$attr_name};
}
sub unauth_new {
my($proto, $attrs, $support, $model) = @_;
if ($proto->is_blesser_of($support, 'Bivio::Biz::Model')) {
$_A->warn_deprecated('switch $model and $support');
($support, $model) = ($model, $support);
}
foreach my $k (@_QUERY_FIELDS) {
(\&{'_parse_'.$k})->($attrs, $support, $model)
unless exists($attrs->{$k});
}
return _new($proto, $attrs, $support, $model);
}
sub _die {
my($die, $code, $attrs, $value) = @_;
# Calls Bivio::Die::die with appropriate params
$attrs = {message => $attrs} unless ref($attrs);
$attrs->{class} = __PACKAGE__;
$attrs->{entity} = $value;
$die ||= 'Bivio::Die';
$die->throw_die($code, $attrs, caller);
}
sub _format_uri {
my($attrs, $support) = @_;
# Formats the uri for the configuration parameters specified.
my($res) = '';
my($columns) = $support->get('columns');
$res .= 't=' . _format_uri_primary_key($attrs->{this}, $support) . '&'
if $attrs->{this};
$res .= 'p=' . _get_parent_id_type($attrs, $support)->to_query(
$attrs->{parent_id}) . '&'
if $attrs->{parent_id};
$res .= 'n=' . $_I->to_query($attrs->{page_number}) . '&'
if defined($attrs->{page_number})
&& $attrs->{page_number} > FIRST_PAGE();
if ($attrs->{order_by} && @{$attrs->{order_by}}) {
my($ob) = $attrs->{order_by};
my($s) = 'o=';
for (my($i) = 0; $i < int(@$ob); $i += 2) {
b_die('order by column missing index: ', $ob->[$i])
unless defined($columns->{$ob->[$i]}->{order_by_index});
$s .= $columns->{$ob->[$i]}->{order_by_index}
. ($ob->[$i+1] ? 'a' : 'd');
}
$res .= $s . '&'
if $s ne $support->get('default_order_by_query');
}
$res .= 's=' . $_S->to_query($attrs->{search}) . '&'
if defined($attrs->{search});
$res .= 'b=' . $_DT->to_query($attrs->{begin_date}) . '&'
if defined($attrs->{begin_date});
$res .= 'd=' . $_DT->to_query($attrs->{date}) . '&'
if defined($attrs->{date});
$res .= 'i=' . $_DI->to_query($attrs->{interval}) . '&'
if defined($attrs->{interval});
if (my $oqk = _other_query_keys($support)) {
foreach my $k (grep(!$_ATTR_TO_CHAR{$_}, sort(keys(%$attrs)))) {
$res .= $k . "=" . $_S->to_query($attrs->{$k}) . '&'
if defined($attrs->{$k})
&& grep($k =~ $_, @$oqk);
}
}
chop($res);
return $res;
}
sub _format_uri_primary_key {
my($pk, $support) = @_;
# Returns primary key formatted for a uri.
my($res) = '';
my($pk_cols) = $support->get('primary_key');
my($is_array) = ref($pk) eq 'ARRAY';
# NOTE: Nice to agree with PropertyModel::format_query
for (my($i) = 0; $i < int(@$pk_cols); $i++) {
$res .= $_SEPARATOR_AS_QUERY if length($res);
$res .= $pk_cols->[$i]->{type}->to_query(
$is_array ? $pk->[$i] : $pk->{$pk_cols->[$i]->{name}});
}
return $res;
}
sub _get_parent_id_type {
my($attrs, $support) = @_;
my($type) = $support->unsafe_get('parent_id_type');
unless ($type) {
my($primary_key) = $support->unsafe_get('primary_key');
if ($primary_key && int(@$primary_key)) {
$type = $primary_key->[0]->{type};
}
}
return $type || $_PI;
}
sub _new {
my($proto, $attrs, $support, $die) = @_;
@{$attrs}{qw(has_prev has_next prev next prev_page next_page list_support)}
= (0, 0, undef, undef, undef, undef, $support);
_die($die, 'CORRUPT_QUERY',
{
message => 'cannot have both interval and begin_date',
begin_date => $attrs->{begin_date},
},
$attrs->{interval},
) if $attrs->{interval} && $attrs->{begin_date};
return $proto->SUPER::new($attrs);
}
sub _other_query_keys {
my($support) = @_;
return
unless $support
and my $res = $support->unsafe_get('other_query_keys');
return [map(ref($_) ? $_ : qr{^$_$}s, @$res)];
}
sub _parse_begin_date {
my($attrs, $support, $die) = @_;
# Parses the "begin_date" attribute.
$attrs->{begin_date} = _parse_date_value(
$attrs->{b} || $attrs->{begin_date} || undef,
$support, $die, 0);
return;
}
sub _parse_count {
my($attrs, $support, $die) = @_;
# Parse the count string.
my($c) = $_COUNT_TYPE->from_literal($attrs->{'c'} || $attrs->{'count'});
$attrs->{count} = $c
if defined($c);
return;
}
sub _parse_date {
my($attrs, $support, $die) = @_;
# Parses the "date" attribute.
$attrs->{date} = _parse_date_value(
$attrs->{d} || $attrs->{date} || $attrs->{end_date}
|| $attrs->{report_date} || undef,
$support, $die, $support->unsafe_get('want_date'));
return;
}
sub _parse_date_value {
my($literal, $support, $die, $want_date) = @_;
# Parses the literal and returns a Type::Date. We handle both a literal
# DateTime (J SSSSS) and a Date (mm/dd/yyyy). We also check for report_date
# and date passed in. If the date is invalid, we set it to undef or now
# depending on whether support is passed in or not.
#
# Backwards compatibility issues: Default to Bivio::Type::DateTime for type.
my($type) = $support->unsafe_get('date');
$type = $type ? $type->{type} : $_DT;
return $want_date ? $type->get_default : undef
unless $literal;
my($value, $e) = $type->from_literal($literal);
return $value if $value;
#TODO: can we get rid of this?
# Try a date first, because that's the common case
($value, $e) = $_D->from_literal($literal);
return $value if $value;
($value, $e) = $_DT->from_literal($literal);
_die($die, 'CORRUPT_QUERY', {
message => 'invalid date',
type_error => $e,
},
$literal) unless $value;
return $value;
}
sub _parse_interval {
my($attrs, $support, $die) = @_;
# Parses an interval as an unsafe_from_any or literal ref.
my($literal) = $attrs->{i};
$literal = $attrs->{interval} unless defined($literal);
# Passed internally?
if (ref($literal)) {
# Already parsed, is a reference
_die(
$die, 'CORRUPT_QUERY', {
message => 'not a Bivio::Type::DateInterval',
},
$literal,
) unless UNIVERSAL::isa($literal, 'Bivio::Type::DateInterval');
$attrs->{interval} = $literal;
return;
}
# Empty?
unless (defined($literal) && length($literal)) {
$attrs->{interval} = undef;
return;
}
# Parse
my($value, $e) = $_DI->unsafe_from_any($literal);
_die($die, 'CORRUPT_QUERY', {
message => 'invalid interval',
type_error => $e,
},
$literal) unless $value;
$attrs->{interval} = $value;
return;
}
sub _parse_order_by {
my($attrs, $support, $die) = @_;
# Creates a hash of order_by values. The default is the order returned from
# the model. The value is a list of numbers followed by letters ('a' or 'd'),
# e.g. 1a3d.
#
# The "order_by" attributes is defined as a array_ref (which can be turned
# into a hash) of column name followed by either true (ascending)
# or false (descending).
my($orig_value) = $attrs->{o} || $attrs->{order_by} || '';
my($res) = $attrs->{order_by} = [];
my($order_by, $columns)
= $support->unsafe_get('order_by_names', 'columns');
return
unless $order_by;
my($value) = $orig_value;
if (ref($value) eq 'ARRAY') {
@$res = @{__PACKAGE__->map_by_two(
sub {
my($col, $dir) = @_;
_die($die, 'CORRUPT_QUERY', 'unknown column', $col)
unless grep($col eq $_, @$order_by);
return ($col, $dir =~ /asc|1/ ? 1 : 0);
},
$value,
)};
}
else {
while (length($value)) {
_die($die, 'CORRUPT_QUERY', 'invalid order_by',
$orig_value) unless $value =~ s/^(\d+)([ad])//;
my($index, $dir) = ($1, $2);
unless ($order_by->[$index]) {
b_warn('unknown order_by column: ', $index);
@$res = ();
return;
}
push(@$res, $order_by->[$index], $dir eq 'a' ? 1 : 0);
}
}
foreach my $ob (@$order_by) {
push(@$res, $ob, $columns->{$ob}->{sort_order})
unless grep($_ eq $ob, @$res);
}
return;
}
sub _parse_page_number {
my($attrs, $support, $die) = @_;
# If not set or invalid, will be set to zero.
# Returns undef if no page number.
($attrs->{page_number}) = $_I->from_literal(
$attrs->{'n'} || $attrs->{page_number});
# Set page_number to 1 by default (if invalid)
$attrs->{page_number} = FIRST_PAGE()
unless defined($attrs->{page_number})
&& $attrs->{page_number} >= FIRST_PAGE();
return;
}
sub _parse_parent_id {
my($attrs, $support, $die) = @_;
my($err);
($attrs->{parent_id}, $err)
= _get_parent_id_type($attrs, $support)->from_literal(
$attrs->{'p'} || $attrs->{parent_id});
return
if $attrs->{parent_id};
#SECURITY: Do not remove this check
_die($die, CORRUPT_QUERY => {
message => 'bad or missing parent_id',
type_error => $err,
}, 'parent_id',
) if $support->unsafe_get('parent_id');
return;
}
sub _parse_pk {
my($attrs, $support, $die, $tag, $name) = @_;
# Parse the primary key. The $_SEPARATOR is a special character
# that is unlikely to appear in primary keys.
#
# Allows $tag or $name in the query.
my($value) = $attrs->{$tag} || $attrs->{$name};
unless (defined($value)) {
$attrs->{$name} = undef;
return;
}
my($res) = $attrs->{$name} = [];
my($pk)
= [ref($value) eq 'ARRAY' ? @$value : split(/$_SEPARATOR/o, $value)];
foreach my $t (@{$support->get('primary_key_types')}) {
#TODO: Need to check for correct number of $_SEPARATOR values
my($literal) = shift(@$pk);
my($v, $err) = $t->from_literal($literal);
_die($die, 'CORRUPT_QUERY', {
message => "invalid $name",
error => $err,
}, $literal) unless defined($v);
push(@$res, $v);
}
return;
}
sub _parse_search {
my($attrs, $support, $die) = @_;
# Parse the search string. Make sure it doesn't have blanks. Allows
# "s" or "search" to be supplied.
my($value) = defined($attrs->{'s'}) ? $attrs->{'s'} : $attrs->{'search'};
if (defined($value)) {
$value =~ s/^\s+|\s+$//g;
if (length($value)) {
$attrs->{search} = $value;
return;
}
}
$attrs->{search} = undef;
return;
}
sub _parse_this {
# The this value's primary key.
_parse_pk(@_, 't', 'this');
return;
}
1;