Bivio::Biz::PropertyModel
# Copyright (c) 1999-2010 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::Biz::PropertyModel;
use strict;
use Bivio::Base 'Biz.Model';
b_use('IO.Trace');
our($_TRACE);
my($_PI) = b_use('Type.PrimaryId');
my($_PS) = b_use('SQL.PropertySupport');
my($_HANDLERS) = b_use('Biz.Registrar')->new;
b_use('Bivio.Cache')->init;
sub assert_properties {
my($self, $values) = @_;
foreach my $k (@{$self->get_info('column_names')}) {
$self->die($k, ': missing column')
unless exists($values->{$k});
}
return $values;
}
sub cascade_delete {
my($self, $query) = @_;
my($support) = $self->internal_get_sql_support;
# DEPRECATED: cascade_delete_children is not used anywhere
my($method) = $support->get('cascade_delete_children') ? 'cascade_delete'
: 'delete_all';
my($properties) = $query || $self->get_shallow_copy;
foreach my $c (@{$support->get_children}) {
my($child) = $self->new_other($c->[0]);
my($key_map) = $c->[1];
my($child_query) = {
map({
my($ck) = $key_map->{$_};
exists($properties->{$ck}) ? ($_ => $properties->{$ck}) : ();
} keys(%$key_map)),
};
$child->$method($child_query)
if %$child_query;
}
$query ? $self->delete_all($query) : $self->delete;
return;
}
sub create {
my($self, $new_values) = @_;
# Creates a new model in the database with the specified values. After creation,
# this instance takes ownership of I<new_values>. Dies on error.
#
# Returns I<self>.
$new_values = _dup($new_values);
my($sql_support) = $self->internal_get_sql_support;
# Make sure all columns are defined
my($n);
foreach $n (@{$sql_support->get('column_names')}) {
$new_values->{$n} = undef unless exists($new_values->{$n});
}
$sql_support->create($new_values, $self);
$self->internal_load_properties($new_values);
$self->internal_data_modification(create => $new_values);
return $self;
}
sub create_from_literals {
my($self, $values) = @_;
# Converts and validates I<values> calling
# L<Bivio::Type::from_literal_or_die|Bivio::Type/"from_literal_or_die">
# for types of I<values>.
#
# B<Note: I<from_literal_or_die> dies on NULL.>
#
# Only validates keys which exist, i.e. primary keys or values which
# are defaulted by L<create|"create"> are not validated.
$values = _dup($values);
while (my($k, $v) = each(%$values)) {
$values->{$k} = $self->get_field_type($k)->from_literal_or_die($v);
}
return $self->create($values);
}
sub create_or_unauth_update {
# B<DEPRECATED>
Bivio::IO::Alert->warn_deprecated('Use unauth_create_or_update instead.');
return shift->unauth_create_or_update(@_);
}
sub create_or_update {
my($self, $new_values) = @_;
# Tries to load the model based on its primary key values.
# If the load is successful, the model will be updated with
# the new values. Otherwise, a new model is created.
#
# Adds auth_id to I<new_values>.
#
# See also L<unauth_create_or_update|"unauth_create_or_update">.
return $self->unauth_create_or_update(_add_auth_id($self, $new_values));
}
sub delete {
my($self) = @_;
# Deletes the current model from the database. Dies on error.
#
# If I<load_args> is supplied, deletes the model specified by
# I<load_args> and not the current model. May be called statically.
my($query, $l);
if (@_ <= 1) {
$l = $query = $self->internal_get;
}
else {
($self, $query) = _load_args(@_);
$self = $self->new()
unless ref($self);
$l = $self->internal_prepare_query($query = _add_auth_id($self, $query));
}
$self->internal_data_modification(delete => $query);
return $self->internal_get_sql_support->delete($l, $self);
}
sub delete_all {
my($self, $query) = @_;
Bivio::IO::Alert->warn_deprecated('missing query args')
unless $query && %$query;
# Deletes all the models of this type with the specified (possibly
# partial key) query. Returns the number of models deleted.
$self = $self->new
unless ref($self);
# Should not be a lot except in test case
$query = _add_auth_id($self, $query);
$self->internal_data_modification(delete_all => $query);
my($rows) = $self->internal_get_sql_support->delete_all(
$self->internal_prepare_query($query),
$self,
);
_trace($rows, ' ', ref($self)) if $_TRACE;
return $rows;
}
sub execute_auth_user {
my($proto, $req) = @_;
# Loads this auth user's data as if realm_id.
$proto->new($req)->load_for_auth_user;
return 0;
}
sub execute_load {
my($proto, $req) = @_;
# Loads this model using no params except auth_id.
$proto->new($req)->load();
return 0;
}
sub execute_load_parent {
my($proto, $req) = @_;
# Loads a new instance of this class using the request using
# the "parent" on the request query.
# See L<load_parent_from_request|"load_parent_from_request">
$proto->new($req)->load_parent_from_request();
return 0;
}
sub execute_load_this {
my($proto, $req) = @_;
# Loads a new instance of this class using the request using
# the "this" on the request.
# See L<load_this_from_request|"load_this_from_request">
$proto->new($req)->load_this_from_request();
return 0;
}
sub execute_unauth_delete_this {
my($proto, $req) = @_;
# Deletes the "this" model by calling get_model for this model.
# Deletes "this" from list query and query.
my($lm) = $req->get('list_model');
$lm->set_cursor_or_not_found(0);
$lm->get_model($proto->simple_package_name)->unauth_delete;
$lm->get_query->delete('this');
delete($req->get('query')->{Bivio::SQL::ListQuery->to_char('this')});
return;
}
sub execute_unauth_load_this {
my($proto, $req) = @_;
# Loads a new instance of this class using the request using
# the "this" on the request.
# See L<unauth_load_this_from_request|"unauth_load_this_from_request">
my($self) = $proto->new($req);
$self->throw_die('MODEL_NOT_FOUND')
unless $self->unauth_load_this_from_request;
return 0;
}
sub format_query_for_parent {
my($self) = @_;
# Query string used to identify this instance using "parent" key,
# so can be used with I<load_parent_from_request>.
return Bivio::SQL::ListQuery->format_uri_for_this_as_parent(
$self->internal_get_sql_support, $self->internal_get);
}
sub format_query_for_this {
my($self, $query) = @_;
# Query string used to identify this instance. If supplied I<load_query>,
# must contain primary keys for the model.
return Bivio::SQL::ListQuery->format_uri_for_this(
$self->internal_get_sql_support, $query || $self->internal_get);
}
sub get_keys {
# B<DEPRECATED>
#
# Returns a copy of the I<column_names> attribute.
#
# TODO: Need to move this up to Model, but I think this might break ListModel and
# FormModel, because get_keys returns all keys and column_names only returns
# declared columns. Some models don't declare all their columns.
return [@{shift->get_info('column_names')}];
}
sub get_qualified_field_name {
# Returns qualified field name (Model.field) for field.
return shift->simple_package_name . '.' . shift(@_);
}
sub get_qualified_field_name_list {
my($self) = @_;
return map($self->get_qualified_field_name($_), @{$self->get_keys});
}
sub generate_unique_for_field {
my($self, $field, $generate_method, $check_method) = @_;
$generate_method ||= 'generate';
$check_method ||= 'unauth_load';
my($type) = $self->get_field_type($field);
my($value);
my($i) = 0;
until ($value) {
b_die($type, ': infinite loop generating value')
if ++$i > 10;
my($v) = $type->$generate_method;
$value = ref($check_method) eq 'CODE'
? $check_method->($v)
: $self->$check_method({
$field => $v,
}) ? undef
: $v;
}
return $value;
}
sub internal_data_modification {
my($self, $op, $query) = @_;
$_HANDLERS->call_fifo(
handle_property_model_modification => [$self, $op, {%{$query || {}}}]);
return;
}
sub internal_get_target {
my($self, $model, $model_prefix, $values) = @_;
# Returns the class, target model and optional model prefix. This method is used
# by subclasses when defining a method which can operate on self, on another
# model target, or $values. For an example, see RealmOwner.format_email.
# If values is undef, internal_get is called on the model.
$model ||= $self;
return (
ref($self) || $self,
$model,
$model_prefix || '',
$values || $model->internal_get,
);
}
sub internal_initialize {
return {};
}
sub internal_initialize_sql_support {
my($proto, $stmt, $config) = @_;
# Returns the L<Bivio::SQL::PropertySupport|Bivio::SQL::PropertySupport>
# for this class. Calls L<internal_initialize|"internal_initialize">
# to get the hash_ref to initialize the sql support instance.
die('cannot create anonymous PropertyModels') if $config;
$config = $proto->internal_initialize;
$config->{class} = ref($proto) || $proto;
my($support) = $_PS->new($config);
_register_with_parents($proto, $support);
return $support;
}
sub internal_load_properties {
my($self, $values, $ephemeral) = @_;
# Loads model with values as properties. DOES NOT MAKE A COPY of values.
$self->internal_clear_model_cache;
$self->assert_properties($values)
unless __PACKAGE__ eq (caller)[0];
$self->internal_put($values);
$self->put_on_request
unless $ephemeral || $self->is_ephemeral;
return $self;
}
sub internal_prepare_query {
# Returns I<query> after fixing it up.
shift;
return shift;
}
sub internal_unique_load_values {
my($self, $values) = @_;
# Returns hash_ref which is extracted from $values to unauth_load a
# unique row in the table. These are not the primary keys. If those
# are available in $values, they will be extracted first. However,
# typically, they are not available, and you want to load by another
# externally provided value, e.g. RealmOwner.name, which is not the primary key
# of the RealmOwner table.
return;
}
sub internal_unload {
my($self) = @_;
# Clears the model state, if loaded. Deletes from request.
return $self
unless $self->is_loaded;
_unload($self, 1);
return $self;
}
sub is_loaded {
# Returns true if the model is loaded (or created), i.e. contains
# valid values.
# If we have values, we're loaded.
return %{shift->internal_get} ? 1 : 0;
}
sub iterate_next_and_load {
my($self) = shift;
# Will iterate to the next row and load the model with the row.
# Can be used to update a row.
#
# Returns false if there is no next.
#
# B<Deprecated form accepts an iterator as the first argument.>
my(undef, $row) = $self->internal_iterate_next(@_, {});
return $row ? _load($self, $row, 1) : _unload($self, 1);
}
sub iterate_start {
my($self, $order_by, $query) = _iterate_params(@_);
# Begins an iteration which can be used to iterate the rows for this
# realm with L<iterate_next|"iterate_next">,
# L<iterate_next_and_load|"iterate_next_and_load">, or
# L<iterate_next_and_load_new|"iterate_next_and_load_new">.
# L<iterate_end|"iterate_end"> should be called when you are through
# with the iteration.
#
# I<order_by> is an SQL C<ORDER BY> clause without the keywords C<ORDER BY>.
#
# I<query> is the same as in L<load|"load">.
#
# B<Deprecated form returns the iterator.>
$self->throw_die('DIE', 'no auth_id')
unless $self->get_request->get('auth_id');
return $self->unauth_iterate_start(
$order_by, _add_auth_id($self, $query || {}));
}
sub load {
my($self) = shift;
# Loads the model or dies if not found or other error.
# Subclasses shouldn't override this method.
#
# Note: I<query> may be modified by this method.
#
# Returns I<self>.
#
#
# B<DEPRECATED>
return $self
if $self->unsafe_load(@_);
_die_not_found($self, \@_, caller);
# DOES NOT RETURN
}
sub load_for_auth_user {
my($self) = @_;
# Loads the model for auth_user.
return $self->unauth_load_or_die({
realm_id => (
$self->get_request->get('auth_user') || $self->die('no auth_user')
)->get('realm_id'),
});
}
sub load_from_properties {
my($self, $values) = @_;
my($support) = $self->internal_get_sql_support;
return $self->internal_load_properties(
$self->assert_properties({
map({
my($cn) = $support->extract_column_name($_);
$support->has_columns($cn) ? ($cn => $values->{$_}) : ();
} keys(%$values)),
}),
);
}
sub load_parent_from_request {
my($self) = @_;
# Parses the query from the request (or list_model) and then L<load|"load">.
# Uses "parent" key in query or list model.
#
# See also L<unsafe_load_parent_from_request|"unsafe_load_parent_from_request">.
my($q) = _parse_query($self, 1);
$self->throw_die(Bivio::DieCode::CORRUPT_QUERY(),
{message => 'see previous warning, too'}, caller)
unless $q;
return $self->load($q);
}
sub load_this_from_request {
my($self) = @_;
# Parses the query from the request (or list_model) and then L<load|"load">.
# Uses "this" key in query or list model.
#
# See also L<unsafe_load_this_from_request|"unsafe_load_this_from_request">.
my($q) = _parse_query($self, 0);
$self->throw_die(Bivio::DieCode::CORRUPT_QUERY(),
{message => 'see previous warning, too'}, caller)
unless $q;
return $self->load($q);
}
sub merge_initialize_info {
my($proto, $parent, $child) = @_;
Bivio::Die->die('columns, if defined, must be a hash_ref')
unless ref($parent->{columns} || {}) eq 'HASH'
&& ref($child->{columns} || {}) eq 'HASH';
return shift->SUPER::merge_initialize_info($parent, {
%$child,
columns => {
%{delete($parent->{columns}) || {}},
%{$child->{columns} || {}},
},
});
}
sub new {
my($self) = shift->SUPER::new(@_);
# Create a new PropertyModel associated with the request.
_unload($self, 0);
return $self;
}
sub register_child_model {
my($self, $class) = @_;
return
if $self->simple_package_name eq $class;
return shift->internal_get_sql_support_no_assert
->register_child_model(@_);
}
sub register_handler {
shift;
$_HANDLERS->push_object(@_);
return;
}
sub rows_exist {
my($self, $query) = @_;
return $self->unauth_rows_exist(_add_auth_id($self, $query));
}
sub test_unauth_delete_all {
my($self, $query) = @_;
$self->req->assert_test;
$self->internal_data_modification(delete_all => $query);
my($res) = $self->internal_get_sql_support->delete_all(
$self->internal_prepare_query(_dup($query)),
$self,
);
return $res;
}
sub unauth_create_or_update {
my($self, $new_values) = @_;
# Tries to load the model based on its primary key values.
# If the load is successful, the model will be updated with
# the new values. Otherwise, a new model is created.
#
# Calls L<unauth_load|"unauth_load">.
#
# See also L<create_or_update|"create_or_update">.
my($pk_values) = _get_primary_keys($self, $new_values)
|| $self->internal_unique_load_values($new_values);
return $pk_values && $self->unauth_load($pk_values)
? $self->update($new_values) : $self->create($new_values);
}
sub unauth_create_or_update_keys {
my($self, $values, $load_pkeys) = @_;
# Create model or update values that may include primary keys.
# I<load_pkeys> is the set of primary key/value pairs to use when
# trying to load the model.
# If I<load_pkeys> is supplied, deletes the model specified by
# I<load_pkeys> and not the current model.
return $self->unauth_create_or_update($values)
unless grep({exists($values->{$_})}
@{$self->get_info('primary_key_names')});
$self->unauth_load({
map({$_ => exists($load_pkeys->{$_})
? $load_pkeys->{$_}
: $values->{$_}
} @{$self->get_info('primary_key_names')}),
})
unless $load_pkeys;
if ($self->is_loaded()) {
foreach my $field (@{$self->get_info('column_names')}) {
$values->{$field} = $self->get($field)
unless exists($values->{field});
}
$self->delete();
}
return $self->create($values);
}
sub unauth_create_unless_exists {
my($self, $values) = @_;
my($pk_values) = _get_primary_keys($self, $values)
|| $self->internal_unique_load_values($values);
b_die($values, ': no primary key values')
unless $pk_values;
return $self
if $self->unauth_load($pk_values);
return $self->create($values);
}
sub unauth_delete {
my($self, $load_args) = @_;
# Deletes the current model from the database. Doesn't check
# auth_id. Dies on error.
#
# If I<load_args> is supplied, deletes the model specified by
# I<load_args> and not the current model. May be called statically.
# Just like L<unauth_load|"unauth_load">.
#
# Note: I<query> may be modified by this method.
$load_args ||= $self->internal_get;
$self = $self->new()
unless ref($self);
$self->die('load_args or model must not be empty')
unless %$load_args;
$self->internal_data_modification(delete => $load_args);
my($res) = $self->internal_get_sql_support->delete(
$self->internal_prepare_query({%$load_args}));
return $res;
}
sub unauth_delete_by_realm_id {
my($self, $field, $auth_id) = @_;
_assert_realm_id_field($self, $field);
$self->new->do_iterate(
sub {
shift->unauth_delete;
return 1;
},
'unauth_iterate_start',
{$field => $auth_id},
);
return;
}
sub unauth_iterate_start {
my($self, $order_by, $query) = _iterate_params(@_);
# B<Do not use this method unless you are sure the user is authorized
# to access all realms or all rows of the table.>
#
# Begins an iteration which can be used to iterate the rows for this
# realm with L<iterate_next|"iterate_next">,
# L<iterate_next_and_load|"iterate_next_and_load">, or
# L<iterate_next_and_load_new|"iterate_next_and_load_new">.
# L<iterate_end|"iterate_end"> should be called when you are through
# with the iteration.
#
# I<order_by> is an SQL C<ORDER BY> clause without the keywords
# C<ORDER BY>.
#
# I<query> is the same as in L<load|"load">.
#
# B<Deprecated form returns the iterator.>
return $self->internal_put_iterator(
$self->internal_get_sql_support->iterate_start(
$self,
$order_by,
$self->internal_prepare_query($query),
),
);
}
sub unauth_load {
my($self, $query) = _load_args(@_);
# Loads the model as with L<unsafe_load|"unsafe_load">. However, does
# not insert security realm into query params. Use this when you
# B<are certain> there are no security issues involved with loading
# the data.
#
# On success, saves model in request and returns true.
#
# Returns false if not found. Dies on any other errors.
#
# Subclasses should override this method if there model doesn't match
# the usual property model. L<unsafe_load|"unsafe_load"> and
# L<load|"load"> call this method.
#
# Note: I<query> may be modified by this method.
#
#
# B<DEPRECATED>
# Don't bother checking query. Will kick back if empty.
my($values) = $self->internal_get_sql_support->unsafe_load(
$self->internal_prepare_query(_dup($query)),
$self,
);
return $values ? _load($self, $values) : _unload($self, 1);
}
sub unauth_load_or_die {
my($self) = shift;
# See L<unauth_load|"unauth_load"> for params. Throws a C<MODEL_NOT_FOUND>
# exception if the load fails.
#
# Returns I<self>.
#
#
# B<DEPRECATED>
return $self if $self->unauth_load(@_);
_die_not_found($self, \@_, caller);
# DOES NOT RETURN
}
sub unauth_load_parent_from_request {
my($self) = @_;
# Parses the query from the request (or list_model) and then
# L<unauth_load|"unauth_load">. If there is no query or the query is
# corrupt, returns false.
#
# See also L<load_parent_from_request|"load_parent_from_request">.
my($q) = _parse_query($self, 1);
return $q ? $self->unauth_load($q) : 0;
}
sub unauth_load_self {
my($self) = @_;
shift->unauth_load(@_);
return $self;
}
sub unauth_load_this_from_request {
my($self) = @_;
# Parses the query from the request (or list_model) and then
# L<unauth_load|"unauth_load">. If there is no query or the query is
# corrupt, returns false.
#
# See also L<load_this_from_request|"load_this_from_request">.
my($q) = _parse_query($self, 0);
return $q ? $self->unauth_load($q) : 0;
}
sub unauth_rows_exist {
my($self, $query) = @_;
my($res) = 0;
$self->do_iterate(
sub {$res++},
'unauth_iterate_start',
$self->get_info('primary_key_names')->[0],
$query,
);
return $res;
}
sub unsafe_load {
my($self, $query) = _load_args(@_);
# Loads the model. On success, saves model in request and returns true.
#
# Returns false if not found. Dies on all other errors.
#
# Subclasses shouldn't override this method.
#
# B<This method will be dynamically overridden. See
# L<internal_initialize_sql_support|"internal_initialize_sql_support">>.
#
# Note: I<query> may be modified by this method.
#
#
# B<DEPRECATED>
return $self->unauth_load(_add_auth_id($self, $query));
}
sub unsafe_load_first {
my($self) = shift;
$self->iterate_start(@_);
my($ok) = $self->iterate_next_and_load;
$self->iterate_end;
return $ok ? $self : undef;
}
sub unsafe_load_parent_from_request {
my($self) = @_;
# Loads this model from the parent value in the query on the request.
my($q) = _parse_query($self, 1);
return $q ? $self->unsafe_load($q) : 0;
}
sub unsafe_load_this_from_request {
my($self) = @_;
# Parses the query from the request (or list_model) and then
# L<unsafe_load|"unsafe_load">. If there is no query or the query is corrupt,
# returns false.
#
# See also L<load_this_from_request|"load_this_from_request">.
my($q) = _parse_query($self, 0);
return $q ? $self->unsafe_load($q) : 0;
}
sub update {
my($self, $new_values) = @_;
# Updates the current model's values.
# NOTE: load() should be called prior to an update.
#
# Returns I<self>.
$new_values = _dup($new_values);
b_die('model is not loaded')
unless $self->is_loaded;
$self->internal_clear_model_cache;
my($properties) = $self->internal_get;
$self->internal_get_sql_support->update($properties, $new_values, $self);
foreach my $n (keys(%$new_values)) {
$properties->{$n} = $new_values->{$n};
}
$self->internal_data_modification(update => $new_values);
return $self;
}
sub _add_auth_id {
my($self, $query) = @_;
# Adds the auth_id field and value to the query, if defined. Returns
# the query.
#
# Ensure we are only accessing data from the realm we are authorized
# to operate in.
$query = _dup($query);
my($sql_support) = $self->internal_get_sql_support;
my($auth_field) = $sql_support->get('auth_id');
# Warn if we are overriding an existing value for auth_id
if ($auth_field) {
my($id) = $self->get_request->get('auth_id');
my($n) = $auth_field->{name};
Bivio::IO::Alert->warn_deprecated(
$self, ": overriding $n=$query->{$n} in query with auth_id=$id",
" from request. You might need to call an unauth_* method instead"
) if exists($query->{$n}) && $query->{$n} ne $id;
# Bivio::Die->die() if exists($query->{$n}) && $query->{$n} ne $id;
$query->{$n} = $id;
}
return $query;
}
sub _assert_realm_id_field {
my($self, $field) = @_;
if (my $aid = $self->internal_get_sql_support->get('auth_id')) {
return
if $aid->{name} eq $field;
}
my($which) = $self;
foreach my $sanity (0 .. 20) {
# dies if there's no parent
my($pf) = $which->get_field_info($field, 'parent_field');
my($pm) = $which->get_field_info($field, 'parent_model');
return
if $pm eq 'RealmOwner'
|| $pf eq 'realm_id';
$which = $which->get_instance($pm);
}
$self->die($field, ': not a RealmOwner.realm_id');
# DOES NOT RETURN
}
sub _default_order_by {
return join(
',',
map($_->{sql_name} . ' ' . ($_->{sort_order} ? 'ASC' : 'DESC'),
@{shift->get_info('primary_key')}));
}
sub _die_not_found {
my($self, $args, $pkg, $file, $line) = @_;
# Dies with appropriate exception.
($self, $args) = _load_args($self, @$args);
$self->throw_die(Bivio::DieCode->MODEL_NOT_FOUND, $args, $pkg,
$file, $line);
# DOES NOT RETURN
}
sub _dup {
my($v) = @_;
return $v ? {%$v} : {};
}
sub _get_primary_keys {
my($self, $new_values) = @_;
# If new_values contains all primary keys, returns a copy as a hash_ref.
# Else returns undef.
my(%pk_values);
my($have_keys) = 1;
foreach my $pk (@{$self->get_info('primary_key_names')}) {
unless (exists($new_values->{$pk})) {
$have_keys = 0;
_trace($pk, ': missing primary key') if $_TRACE;
last;
}
$pk_values{$pk} = $new_values->{$pk};
}
return $have_keys ? \%pk_values : undef;
}
sub _iterate_params {
my($self, $order_by, $query) = @_;
($order_by, $query) = (undef, $order_by)
if ref($order_by) eq 'HASH';
return ($self, $order_by || _default_order_by($self), _dup($query));
}
sub _load {
# Initializes the self with values and returns 1.
shift->internal_load_properties(@_);
return 1;
}
sub _load_args {
my($self) = shift;
# Parses load args and returns ($self, $query).
return ($self, (int(@_) == 1 ? @_ : {@_}));
}
sub _parse_query {
my($self, $want_parent) = @_;
# Does work of unsafe_load_this/parent_from_request.
my($req) = $self->get_request;
my($support) = $self->internal_get_sql_support;
my($list_model, $q) = $req->unsafe_get('list_model', 'query');
# Pass a copy of the query, because it is trashed by ListQuery.
my($query) = $list_model ? $list_model->get_query()
: $q ? Bivio::SQL::ListQuery->new({
%$q,
auth_id => $req->get('auth_id'),
count => 1}, $support, $self)
: undef;
# No query is not a _query_err
return undef unless $query;
# Use this if available, else parent_id
my($key);
my($pk_cols) = $support->get('primary_key');
if ($want_parent) {
my($parent_id) = $query->get('parent_id');
# parent_id has some restrictions, check them
#TODO: why can't we have a _query_err here. Too many messages otherwise.
return undef unless $parent_id;
#TODO: Need to make this work more cleanly
my($i) = int(@$pk_cols);
die('expecting one or two primary key columns for parent_id')
if $i > 2;
if ($i == 1) {
$key = [$parent_id];
}
else {
#TODO: Make this cleaner
# This is a hack. We need to add in the auth_id to the
# query so that the code creating @query works. However,
# load overrides auth_id always.
my($auth_col) = $support->get('auth_id');
my($auth_id) = $req->get('auth_id');
if ($auth_col->{name} eq $pk_cols->[0]->{name}) {
$key = [$auth_id, $parent_id];
}
elsif ($auth_col->{name} eq $pk_cols->[1]->{name}) {
$key = [$parent_id, $auth_id];
}
else {
# Should never happen, of course
die('(auth_id, parent_id) not primary key');
}
}
}
else {
$key = $query->get('this');
return _query_err($self, 'missing this') unless $key;
}
# Create the query acceptable to load (which always adds auth_id)
my(%query) = ();
my($i) = 0;
foreach my $col (@$pk_cols) {
return _query_err($self, "column $col->{name} is NULL")
unless defined($key->[$i]);
$query{$col->{name}} = $key->[$i++];
}
return \%query;
}
sub _query_err {
my($self, $msg) = @_;
# Outputs a warning and returns undef.
$self->get_request->warn($self, ' query error: ', $msg);
return undef;
}
sub _register_with_parents {
my($proto, $support) = @_;
my($class, $parents) = $support->get(qw(class parents));
while (my($parent, $key_map) = each(%$parents)) {
$proto->get_instance($parent)
->register_child_model($class, $key_map);
}
return;
}
sub _unload {
my($self, $delete_from_request) = @_;
# Always returns false.
$self->internal_clear_model_cache;
$self->internal_put({});
$self->delete_from_request
if $delete_from_request && !$self->is_ephemeral;
return 0;
}
1;