The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

no warnings;
################################################################################
sub add_vocabularies {
my ($item, @items) = @_;
while (@items) {
my $name = shift @items;
my $options = {};
if (@items > 0 && ref $items [0] eq HASH) {
$options = shift @items;
}
next
if $options -> {off};
$options -> {item} = $item;
my $table_name = $options -> {name} || $name;
$item -> {$name} = sql_select_vocabulary ($table_name, $options);
if ($options -> {ids}) {
ref $options -> {ids} eq HASH or $options -> {ids} = {table => $options -> {ids}};
$options -> {ids} -> {from} ||= 'id_' . en_unplural ($_REQUEST {type});
$options -> {ids} -> {to} ||= 'id_' . en_unplural ($table_name);
$options -> {ids} -> {name} ||= $options -> {ids} -> {to};
$_REQUEST {"__checkboxes_$options->{ids}->{to}"} = $options -> {ids} -> {table};
$item -> {$options -> {ids} -> {name}} = [sql_select_col ("SELECT $options->{ids}->{to} FROM $options->{ids}->{table} WHERE fake = 0 AND $options->{ids}->{from} = ?", $item -> {id})];
}
}
return $item;
}
################################################################################
sub sql_weave_model {
my ($db_model) = @_;
my @tables = grep {$_ ne $conf -> {systables} -> {log}} map {lc} get_tables ();
foreach my $table_name (@tables) {
my $def = $db_model -> {tables} -> {$table_name};
$def -> {name} = $table_name;
foreach my $column_name (keys %{$def -> {columns}}) {
$def -> {columns} -> {$column_name} -> {name} = $column_name;
$def -> {columns} -> {$column_name} -> {table_name} = $table_name;
}
$db_model -> {aliases} -> {$table_name} = $def;
foreach my $alias (@{$def -> {aliases}}) {
$db_model -> {aliases} -> {$alias} = $def;
}
}
foreach my $table_name (@tables) {
my $def = $db_model -> {aliases} -> {$table_name};
foreach my $column_name (keys %{$def -> {columns}}) {
my $column_def = $def -> {columns} -> {$column_name};
$column_name =~ /^ids?_(.*)/ or next;
my $target2 = $1;
my $target1 = $target2;
if ($target2 =~ /y$/) {
$target1 =~ s{y$}{ies};
}
else {
$target1 .= 's';
}
my $referenced_table_def = undef;
if ($column_def -> {ref}) {
$referenced_table_def = $db_model -> {aliases} -> {$column_def -> {ref}}
}
else {
$referenced_table_def =
$db_model -> {aliases} -> {$target1} ||
$db_model -> {aliases} -> {$target2} ||
$db_model -> {aliases} -> {'voc_' . $target1} ||
$db_model -> {aliases} -> {'voc_' . $target2} ||
undef;
}
$referenced_table_def or next;
$referenced_table_def -> {references} ||= [];
push @{$referenced_table_def -> {references}}, $column_def;
}
}
}
################################################################################
sub check_systables {
foreach (qw(
__access_log
__queries
__defaults
__benchmarks
__request_benchmarks
__last_update
__moved_links
__required_files
__screenshots
cache_html
log
roles
sessions
users
)) {
$conf -> {systables} -> {$_} ||= $_;
}
}
################################################################################
sub sql_assert_core_tables {
$db or return;
$model_update or die "\$db && !\$model_update ?!! Can't believe it.\n";
return if $model_update -> {core_ok};
my $time = time;
$model_update -> assert (
tables => {
$conf -> {systables} -> {__last_update} => {
columns => {
id => {TYPE_NAME => 'bigint', _EXTRA => 'auto_increment', _PK => 1},
pid => {TYPE_NAME => 'int'},
unix_ts => {TYPE_NAME => 'bigint'},
},
},
},
prefix => 'sql_assert_core_tables#',
);
sql_version ();
$model_update -> {core_ok} = 1;
__log_profilinig ($time, ' <sql_assert_core_tables>');
}
################################################################################
sub sql_temporality_callback {
my ($self, %params) = @_;
my $needed_tables = $params {tables};
foreach my $name (keys (%$needed_tables)) {
sql_is_temporal_table ($name) or next;
my $log_def = Storable::dclone ($needed_tables -> {$name});
foreach my $key (keys %{$log_def -> {columns}}) {
delete $log_def -> {columns} -> {$key} -> {_EXTRA};
delete $log_def -> {columns} -> {$key} -> {_PK};
}
$log_def -> {columns} -> {id} -> {TYPE_NAME} ||= 'int';
delete $log_def -> {data};
$log_def -> {keys} ||= {};
$log_def -> {keys} -> {__id} = 'id';
$log_def -> {columns} -> {__dt} = {
TYPE_NAME => 'datetime',
};
$log_def -> {columns} -> {__id} = {
TYPE_NAME => 'int',
_EXTRA => 'auto_increment',
_PK => 1,
};
$log_def -> {columns} -> {__op} = {
TYPE_NAME => 'int',
};
$log_def -> {columns} -> {__id_log} = {
TYPE_NAME => 'int',
};
$log_def -> {columns} -> {__is_actual} = {
TYPE_NAME => 'tinyint',
NULLABLE => 0,
COLUMN_DEF => 0,
};
$params {tables} -> {'__log_' . $name} = $log_def;
}
}
################################################################################
sub sql_is_temporal_table {
if (ref $conf -> {db_temporality} eq ARRAY) {
$conf -> {db_temporality} = {(map {$_ => 1} @{$conf -> {db_temporality}})};
}
my ($name) = @_;
return 0 if $name =~ /^__log_/;
if (ref $conf -> {db_temporality} eq HASH) {
return $conf -> {db_temporality} -> {$name};
}
else {
return $conf -> {db_temporality};
}
}
################################################################################
sub sql_reconnect {
my $time = time;
our $db, $model_update, $SQL_VERSION;
if ($db && ($preconf -> {no_model_update} || ($model_update && $model_update -> {core_ok}))) {
$db -> ping and return
$time = __log_profilinig ($time, ' sql_reconnect: ping OK');
}
$db = DBI -> connect ($preconf -> {db_dsn}, $preconf -> {db_user}, $preconf -> {db_password}, {
RaiseError => 1,
AutoCommit => 1,
LongReadLen => 1000000,
LongTruncOk => 1,
InactiveDestroy => 0,
});
$time = __log_profilinig ($time, " sql_reconnect: connected to $preconf->{db_dsn}");
unless ($INC_FRESH {db_driver}) {
my $driver_name = $db -> get_info ($GetInfoType {SQL_DBMS_NAME});
$driver_name =~ s{\W}{}gsm;
my $path = __FILE__;
$path =~ s{(.)SQL\.pm$}{${1}SQL$1Dialect$1${driver_name}.pm};
do $path;
die $@ if $@;
$INC_FRESH {db_driver} = time;
$SQL_VERSION = {driver => $driver_name};
$time = __log_profilinig ($time, " sql_reconnect: $driver_name is loaded");
}
sql_version ();
$time = __log_profilinig ($time, " sql_reconnect: driver version is $SQL_VERSION->{string}");
unless ($preconf -> {no_model_update}) {
if ($model_update) {
$model_update -> {db} = $db;
}
else {
$model_update = $_NEW_PACKAGE -> new (
$db,
before_assert => $conf -> {'db_temporality'} ? \&sql_temporality_callback : undef,
schema => $preconf -> {db_schema},
);
$time = __log_profilinig ($time, ' sql_reconnect: $model_update created');
}
}
}
################################################################################
sub sql_disconnect {
eval { $db -> disconnect };
undef $db;
}
################################################################################
sub sql_select_vocabulary {
my ($table_name, $options) = @_;
$options -> {order} ||= '2';
my $filter = '1=1';
my $limit = '';
if ($_REQUEST {__read_only}) {
if ($options -> {field} && $options -> {item}) {
my $id = 0 + $options -> {item} -> {$options -> {field}};
$filter .= ' AND id = ' . $id;
}
else {
$filter .= ' AND fake <= 0';
}
}
else {
$filter .= ' AND fake = 0';
}
$filter .= " AND $options->{filter}" if $options -> {filter};
my @params = ();
if ($options -> {in}) {
my $in = $options -> {in};
my $ref = ref $in;
if ($ref eq SCALAR) {
my $tied = tied $$in;
if (_sql_ok_subselects ()) {
$filter .= " AND id IN ($tied->{sql})";
push @params, @{$tied -> {params}};
}
else {
$filter .= " AND id IN ($$in)";
}
}
elsif ($ref eq ARRAY) {
@$in > 0 or return [];
$in = join ',', @$in;
$filter .= " AND id IN ($in)";
}
elsif (!$ref) {
$in =~ /\d/ or return [];
$filter .= " AND id IN ($in)";
}
else {
die "Wrong IN list";
}
}
if ($options -> {not_in}) {
my $in = $options -> {not_in};
my $ref = ref $in;
if ($ref eq SCALAR) {
my $tied = tied $$in;
if (_sql_ok_subselects ()) {
$filter .= " AND id NOT IN ($tied->{sql})";
push @params, @{$tied -> {params}};
}
else {
$filter .= " AND id NOT IN ($$in)";
}
}
elsif ($ref eq ARRAY) {
@$in > 0 or return [];
$in = join ',', @$in;
$filter .= " AND id NOT IN ($in)";
}
elsif (!$ref) {
$in =~ /\d/ or return [];
$filter .= " AND id NOT IN ($in)";
}
else {
die "Wrong [NOT] IN list";
}
}
if ($preconf -> {subset} && $table_name eq $conf -> {systables} -> {roles}) {
$filter .= " AND name IN ('-1'";
foreach my $name (keys %{$preconf -> {subset_names}}) {
$filter .= ", '";
$filter .= $name;
$filter .= "'";
}
$filter .= ")";
}
$limit = "LIMIT $options->{limit}" if $options -> {limit};
$options -> {label} ||= 'label';
if ($options -> {label} ne 'label') {
$options -> {label} =~ s/ AS.*//i;
$options -> {label} .= ' AS label';
}
$options -> {label} .= ', parent' if $options -> {tree};
my @list;
tie @list, 'Eludia::Tie::Vocabulary', {
sql => "SELECT id, $$options{label}, fake FROM $table_name WHERE $filter ORDER BY $$options{order} $limit",
params => \@params,
_REQUEST => \%_REQUEST,
package => current_package (),
tree => $options -> {tree},
};
return \@list;
}
################################################################################
sub sql_select_id {
my ($table, $values, @lookup_field_sets) = @_;
my $result = {};
my $table_safe = sql_table_name ($table);
my %values = ();
my $forced = {};
foreach my $key (keys %$values) {
$key =~ /^(\-?)(.*)$/;
$forced -> {$2} = 1 if $1;
$values {$2} = $values -> {$key};
}
$values = \%values;
exists $values -> {fake} or $values -> {fake} = 0;
@lookup_field_sets = (['label']) if @lookup_field_sets == 0;
my $options = ref $lookup_field_sets [-1] eq HASH ? pop @lookup_field_sets : {};
my $record = {};
my $auto_commit = $db -> {AutoCommit};
eval { $db -> {AutoCommit} = 0; };
sql_lock ($table);
eval {
foreach my $lookup_fields (@lookup_field_sets) {
if (ref $lookup_fields eq CODE) {
next if &$lookup_fields ();
return 0;
}
my $sql = "SELECT * FROM $table_safe WHERE fake <= 0";
my @params = ();
foreach my $lookup_field (@$lookup_fields) {
my $value = $values -> {$lookup_field};
if ($value eq '' && $SQL_VERSION -> {driver} eq 'Oracle') {
$value = undef;
}
if (defined $value) {
$sql .= " AND $lookup_field = ?";
push @params, $values -> {$lookup_field};
}
else {
$sql .= " AND $lookup_field IS NULL";
}
}
$sql .= " ORDER BY fake DESC, id DESC";
$record = sql_select_hash ($sql, @params);
last if $record -> {id};
}
unless ($_REQUEST {_no_search_merged_record}) {
while (my $id = ($record -> {is_merged_to} || $record -> {id_merged_to})) {
$record = sql_select_hash ($table, $id);
}
}
if ($record -> {id}) {
my @keys = ();
my @values = ();
foreach my $key (keys %$values) {
($forced -> {$key} && $values -> {$key} ne $record -> {$key}) or $record -> {$key} eq '' or next;
$result -> {update} -> {$key} = {old => $record -> {$key}, new => $values -> {$key}};
push @keys, $key;
push @values, $values -> {$key};
}
if (@keys) {
sql_do ('UPDATE ' . $table_safe . ' SET ' . (join ', ', map {"$_ = ?"} @keys) . ' WHERE id = ?', @values, $record -> {id});
}
}
unless ($record -> {id}) {
$record -> {id} = sql_do_insert ($table, $values);
$result -> {insert} = $values;
}
};
sql_unlock ($table);
if ($auto_commit) {
eval {
$db -> commit;
$db -> {AutoCommit} = 1;
};
}
return $options -> {show_diff} && wantarray ? ($record -> {id}, $result) : $record -> {id};
}
################################################################################
sub sql_do_relink {
my ($table_name, $old_ids, $new_id, $options) = @_;
sql_weave_model ($DB_MODEL);
ref $old_ids eq ARRAY or $old_ids = [$old_ids];
my $column_name = '';
$column_name = 'is_merged_to' if $DB_MODEL -> {tables} -> {$table_name} -> {columns} -> {is_merged_to};
$column_name = 'id_merged_to' if $DB_MODEL -> {tables} -> {$table_name} -> {columns} -> {id_merged_to};
my $record = sql_select_hash ($table_name, $new_id);
my @empty_fields = ();
foreach my $key (keys %$record) {
next if $options -> {no_update};
next if $record -> {$key} . '' ne '';
next if $key eq 'id';
next if $key eq 'fake';
next if $key eq 'is_merged_to';
next if $key eq 'id_merged_to';
push @empty_fields, $key;
}
my $moved_links_table = sql_table_name ($conf -> {systables} -> {__moved_links});
foreach my $old_id (@$old_ids) {
warn "relink $table_name: $old_id -> $new_id";
my $record = sql_select_hash ($table_name, $old_id);
foreach my $empty_field (@empty_fields) {
$_REQUEST {'_' . $empty_field} ||= $record -> {$empty_field};
}
foreach my $column_def (@{$DB_MODEL -> {aliases} -> {$table_name} -> {references}}) {
next
if $DB_MODEL -> {tables} -> {$column_def -> {table_name}} -> {sql};
warn "relink $$column_def{table_name} ($$column_def{name}): $old_id -> $new_id";
if ($column_def -> {TYPE_NAME} =~ /int/) {
sql_do (<<EOS, $old_id);
INSERT INTO $moved_links_table
(table_name, column_name, id_from, id_to)
SELECT
'$$column_def{table_name}' AS table_name,
'$$column_def{name}' AS column_name,
id AS id_from,
'$old_id' AS id_to
FROM
$$column_def{table_name}
WHERE
$$column_def{name} = ?
EOS
sql_do ("UPDATE $$column_def{table_name} SET $$column_def{name} = ? WHERE $$column_def{name} = ?", $new_id, $old_id);
}
else {
my $_old_id = ',' . $old_id . ',';
my $_new_id = ',' . $new_id . ',';
sql_do (<<EOS, '%' . $old_id . '%');
INSERT INTO $moved_links_table
(table_name, column_name, id_from, id_to)
SELECT
'$$column_def{table_name}' AS table_name,
'$$column_def{name}' AS column_name,
id AS id_from,
'$_old_id' AS id_to
FROM
$$column_def{table_name}
WHERE
$$column_def{name} LIKE ?
EOS
sql_do ("UPDATE $$column_def{table_name} SET $$column_def{name} = REPLACE($$column_def{name}, ?, ?) WHERE $$column_def{name} LIKE ?", $_old_id, $_new_id, '%' . $_old_id . '%');
}
}
if ($column_name) {
sql_do ("UPDATE $table_name SET fake = -1, $column_name = ? WHERE id = ?", $new_id, $old_id);
}
else {
sql_do ("UPDATE $table_name SET fake = -1 WHERE id = ?", $old_id);
}
}
sql_do_update ($table_name, \@empty_fields) if @empty_fields > 0;
delete $DB_MODEL -> {aliases};
}
################################################################################
sub sql_undo_relink {
sql_weave_model ($DB_MODEL);
my ($table_name, $old_ids) = @_;
ref $old_ids eq ARRAY or $old_ids = [$old_ids];
my $moved_links_table = sql_table_name ($conf -> {systables} -> {__moved_links});
foreach my $old_id (@$old_ids) {
$old_id > 0 or next;
warn "undo relink $table_name: $old_id";
my $record = sql_select_hash ($table_name, $old_id);
foreach my $column_def (@{$DB_MODEL -> {aliases} -> {$table_name} -> {references}}) {
my $from = <<EOS;
FROM
$moved_links_table
WHERE
table_name = '$$column_def{table_name}'
AND column_name = '$$column_def{name}'
AND id_to = $old_id
EOS
my $ids = sql_select_ids ("SELECT id_from $from");
sql_do ("DELETE $from");
warn "undo relink $$column_def{table_name} ($$column_def{name}): $old_id";
if ($column_def -> {TYPE_NAME} =~ /int/) {
sql_do ("UPDATE $$column_def{table_name} SET $$column_def{name} = ? WHERE id IN ($ids)", $old_id);
}
else {
$old_id_ = $old_id . ',';
sql_do ("UPDATE $$column_def{table_name} SET $$column_def{name} = CONCAT($$column_def{name}, ?) WHERE id IN ($ids)", $old_id_);
}
}
}
delete $DB_MODEL -> {aliases};
}
################################################################################
sub assert_fake_key {
my ($table_name) = @_;
$DB_MODEL -> {tables} -> {$table_name} or return;
return if $DB_MODEL -> {tables} -> {$table_name} -> {keys} -> {fake};
$model_update -> assert (
tables => {
$table_name => {
keys => {fake => 'fake'},
},
},
prefix => 'assert_fake_key#',
);
}
################################################################################
sub is_recyclable {
my ($table_name) = @_;
return 0 if $table_name eq $conf -> {systables} -> {log};
return 0 if $table_name eq $conf -> {systables} -> {sessions};
if (ref $conf -> {core_recycle_ids} eq ARRAY) {
$conf -> {core_recycle_ids} = {map {$_ => 1} @{$conf -> {core_recycle_ids}}}
}
return 1 if $conf -> {core_recycle_ids} == 1 || $conf -> {core_recycle_ids} -> {$table_name};
return 0;
}
################################################################################
sub delete_fakes {
my ($table_name) = @_;
$table_name ||= $_REQUEST {type};
return if ($_REQUEST {__delete_fakes} -> {$table_name} ||= is_recyclable ($table_name));
assert_fake_key ($table_name);
my ($ids, $in_clause) = sql_select_ids (<<EOS);
SELECT
$table_name.id
FROM
$table_name
LEFT JOIN $conf->{systables}->{sessions} ON $table_name.fake = $conf->{systables}->{sessions}.id
WHERE
$table_name.fake > 0
AND $conf->{systables}->{sessions}.id_user IS NULL
EOS
sql_do ("DELETE FROM $table_name WHERE id IN ($in_clause)");
$_REQUEST {__delete_fakes} -> {$table_name} = 1;
}
################################################################################
sub __log_sql_profilinig {
my ($options) = @_;
$_REQUEST {__sql_time} += 1000 * (time - $options -> {time});
}
################################################################################
sub sql_extract_params {
my ($sql, @params) = @_;
return ($sql, @params) if $sql !~ /^\s*(SELECT|INSERT|UPDATE|DELETE)/i;
my $sql1 = '';
my @params1 = ();
my $i = 0;
my $flag = $sql =~ /SELECT/i ? 0 : 1;
my $flag1 = 1;
$sql =~ m{
(
(?:>=|<=|==) # >=, <= and == operators
|
[\(\),=;] # punctuation (parenthesis, comma)
|
\'\'(?!\') # empty single quoted string
|
\"\"(?!\"") # empty double quoted string #"
|
".*?(?:(?:""){1,}"|(?<!["\\])"(?!")|\\"{2})
# anything inside double quotes, ungreedy
|
'.*?(?:(?:''){1,}'|(?<!['\\])'(?!')|\\'{2})
# anything inside single quotes, ungreedy.
|
--[\ \t\S]* # comments
|
\#[\ \t\S]* # mysql style comments
|
/\*[\ \t\n\S]*?\*/ # C style comments
|
[^\s\(\),=;]+ # everything that doesn't matches with above
|
\n # newline
|
[\t\ ]+ # any kind of white spaces
)
}smxgo
) {
$token =~ s{\s+}{ }gsm;
if (
$token =~ /^--\s/
|| $token =~ /^\/\*\s*[^\+]/ || $token =~ /^\#*\s/
) {
$token = ' ';
}
else {
$flag = 1 if $token =~ /^FROM$/i;
$flag1 = 1 if $token =~ /^END$/i;
$flag = 0 if $token =~ /^ORDER$/i || $token =~ /^GROUP$/i || $token =~ /^SELECT$/i;
$flag1 = 0 if $token =~ /^CASE$/i;
if ($token eq '?') {
push @params1, $params [$i ++];
}
elsif (
$token =~ /^0(\d+)$/
) {
$token = $1;
}
elsif (
($flag && $flag1) && (
$token =~ /^(\-?\d+)$/
|| $token =~ /^\'(.*?)\'$/
)
) {
my $value = $1;
$value =~ s{\\\'}{\'}gsm; #'
push @params1, $value;
$token = '?';
}
}
$token =~ /^\"(.*?)\"$/ or $token = uc $token;
$sql1 .= ' ';
$sql1 .= $token;
$sql1 .= ' ';
}
$sql1 =~ s{\s+$}{};
$sql1 =~ s{^\s+}{};
$sql1 =~ s{\s+}{ }g;
$sql = $sql1;
return ($sql1, @params1);
}
################################################################################
sub sql_adjust_fake_filter {
my ($sql, $options) = @_;
$options -> {fake} or return $sql;
my $where = 'WHERE ';
my $fake = $_REQUEST {fake} || 0;
my $condition = $fake =~ /\,/ ? "IN ($fake)" : '=' . $fake;
foreach my $table (split /\,/, $options -> {fake}) {
$where .= "$table.fake $condition AND ";
}
$sql =~ s{where}{$where}i;
return $sql;
}
################################################################################
sub __log_request_profilinig {
my ($request_time) = @_;
return unless ($preconf -> {core_debug_profiling} > 2 && $model_update -> {core_ok});
my $c = $r -> connection;
$_REQUEST {_id_request_log} = sql_do_insert ($conf -> {systables} -> {__request_benchmarks}, {
id_user => $_USER -> {id},
ip => $ENV {REMOTE_ADDR},
ip_fw => $ENV {HTTP_X_FORWARDED_FOR},
fake => 0,
type => $_REQUEST {type},
mac => get_mac (),
request_time => int ($request_time),
connection_id => $c -> id (),
connection_no => $c -> keepalives (),
});
my $request_benchmarks_table = sql_table_name ($conf -> {systables} -> {__request_benchmarks});
sql_do ("UPDATE $request_benchmarks_table SET params = ? WHERE id = ?",
Data::Dumper -> Dump ([\%_REQUEST], ['_REQUEST']), $_REQUEST {_id_request_log});
}
################################################################################
sub __log_request_finish_profilinig {
my ($options) = @_;
return
unless ($preconf -> {core_debug_profiling} > 2 && $model_update -> {core_ok});
my $time = time;
my $request_benchmarks_table = sql_table_name ($conf -> {systables} -> {__request_benchmarks});
sql_do ("UPDATE $request_benchmarks_table SET application_time = ?, sql_time = ?, response_time = ?, bytes_sent = ?, is_gzipped = ? WHERE id = ?",
int ($options -> {application_time}),
int ($options -> {sql_time}),
$options -> {out_html_time} ? int (1000 * (time - $options -> {out_html_time})) : 0,
$r -> bytes_sent,
$options -> {is_gzipped},
$options -> {id_request_log},
);
}
################################################################################
sub sql_select_ids {
my ($sql, @params) = @_;
my $ids;
my $tied = tie $ids, 'Eludia::Tie::IdsList', {
sql => $sql,
_REQUEST => \%_REQUEST,
package => __PACKAGE__,
params => \@params,
db => $db,
sql_translator_ref => get_sql_translator_ref(),
};
return wantarray ? (
$ids,
wantarray && _sql_ok_subselects () ? $tied -> _sql : $ids,
) : $ids;
}
################################################################################
sub sql_upload_files {
my ($options) = @_;
my @nos = ();
foreach my $k (keys %_REQUEST) {
$k =~ /^_$options->{name}_(\d+)$/ or next;
$_REQUEST {$k} or next;
push @nos, $1;
}
@nos > 0 or return;
my ($table, $field) = split /\./, $_REQUEST {"__$options->{name}_file_field"};
$options -> {id} ||= $_REQUEST {id};
sql_do ("UPDATE $table SET fake = -1 WHERE $field = ?", $options -> {id});
my $name = $options -> {name};
my $id = $options -> {id};
$options -> {table} = $table;
$options -> {file_name_column} = 'file_name';
$options -> {size_column} = 'file_size';
$options -> {type_column} = 'file_type';
$options -> {path_column} = 'file_path';
$options -> {body_column} = 'file_body' if $model_update -> get_columns ($table) -> {file_body};
foreach my $no (sort {$a <=> $b} @nos) {
$options -> {name} = "${name}_${no}";
$options -> {id} = sql_do_insert ($table => {
$field => $id,
fake => 0,
});
sql_upload_file ($options);
}
sql_select_loop ("SELECT * FROM $table WHERE $field = ? AND fake = -1", sub {
my $path = $i -> {$options -> {path_column}} or return;
unlink $r -> document_root . $path;
}, $id);
sql_do ("DELETE FROM $table WHERE $field = ? AND fake = -1", $id);
}
################################################################################
################################################################################
#package DBIx::ModelUpdate;
################################################################################
sub new {
my ($package_name, $db, @options) = @_;
my $driver_name = $db -> get_info ($GetInfoType {SQL_DBMS_NAME});
$driver_name =~ s{\s}{}gsm;
die $@ if $@;
my $self = bless ({
db => $db,
driver_name => $driver_name,
quote => $db -> get_info ($GetInfoType {SQL_IDENTIFIER_QUOTE_CHAR}),
@options
}, $package_name);
if ($driver_name eq 'Oracle') {
$self -> {characterset} = sql_select_scalar ('SELECT VALUE FROM V$NLS_PARAMETERS WHERE PARAMETER = ?', 'NLS_CHARACTERSET');
$self -> {schema} ||= uc $db -> {Username};
}
$self -> {schema} ||= '';
return $self;
}
################################################################################
sub sql_assert_default_columns {
my ($needed_tables, $params) = @_;
my $default_columns = $params -> {default_columns} or return $needed_tables;
foreach my $name (keys %$needed_tables) {
my $definition = $needed_tables -> {$name};
next if $definition -> {sql};
next if $definition -> {columns} -> {id};
foreach my $dc_name (keys %$default_columns) {
$definition -> {columns} -> {$dc_name} ||= Storable::dclone $default_columns -> {$dc_name};
}
}
return $needed_tables;
}
################################################################################
sub assert {
my ($self, %params) = @_;
my $core_debug_sql_do = $preconf -> {core_debug_sql_do};
$preconf -> {core_debug_sql_do} = 1;
my ($tables, my $new_checksums) = checksum_filter (db_model => $params {prefix},
sql_assert_default_columns (Storable::dclone ($params {tables}), \%params)
);
my $objects = [\my @tables, \my @views];
while (my ($name, $object) = each %$tables) {
next if $object -> {off};
$object -> {name} = $name;
push @{$objects -> [$object -> {sql} ? 1 : 0]}, $object;
}
wish (tables => Storable::dclone \@tables, {});
foreach my $table (@tables) {
wish (table_columns => [map {{name => $_, %{$table -> {columns} -> {$_}}}} (keys %{$table -> {columns}})], {table => $table -> {name}}) if exists $table -> {columns};
wish (table_keys => [map {{name => $_, parts => $table -> {keys} -> {$_}}} (keys %{$table -> {keys}})], {table => $table -> {name}, table_def => $table}) if exists $table -> {keys};
if (exists $table -> {data} && ref $table -> {data} eq ARRAY && @{$table -> {data}} > 0) {
wish (table_data => $table -> {data}, {
table => $table -> {name},
key => exists $table -> {data} -> [0] -> {id} ? 'id' : 'name',
});
}
}
wish (views => \@views, {});
$preconf -> {core_debug_sql_do} = $core_debug_sql_do;
checksum_write ('db_model', $new_checksums);
}
#############################################################################
sub sql_store_ids {
my $options;
if (@_ == 2 && !ref $_[0] && !ref $_[1]) {
$options = {
table => $_[0],
key => $_[1],
}
}
elsif (ref $_[0] eq HASH) {
$options = $_[0];
}
else {
die "Wrong parameters for sql_store_ids: " . Dumper (\@_);
}
$options -> {root} ||= {'id_' . en_unplural ($_REQUEST {type}) => $_REQUEST {id}};
wish (table_data => [map {{ fake => 0, $options -> {key} => $_ }} get_ids ($options -> {key})], $options);
}
#############################################################################
sub sql_clone {
my ($table, $data, %fields) = @_;
my $clone = {%$data, %fields};
delete $clone -> {id};
$clone -> {id} = sql_do_insert ($table => $clone);
return $clone;
}
#############################################################################
sub require_wish ($) {
return if $INC_FRESH {"Wish::$_[0]"};
foreach my $key (map {"Eludia/SQL$_/Wish/$_[0].pm"} ('', '/Dialect/' . $SQL_VERSION -> {driver})) {
eval {require $key};
delete $INC {$key};
}
$INC_FRESH {"Wish::$_[0]"} = 1;
}
#############################################################################
sub wish {
my ($type, $items, $options) = @_;
require_wish $type;
&{"wish_to_adjust_options_for_$type"} ($options);
foreach my $i (@$items) { &{"wish_to_clarify_demands_for_$type"} ($i, $options) }
my $existing = &{"wish_to_explore_existing_$type"} ($options);
my $todo = {};
my @key = @{$options -> {key}};
foreach my $new (@$items) {
my $old = delete $existing -> {join '_', @$new {@key}} or (push @{$todo -> {create}}, $new) and next;
&{"wish_to_update_demands_for_$type"} ($old, $new, $options);
next if Dumper ($new) eq Dumper ($old);
&{"wish_to_schedule_modifications_for_$type"} ($old, $new, $todo, $options);
}
&{"wish_to_schedule_cleanup_for_$type"} ($existing, $todo, $options);
foreach my $action (keys %$todo) { &{"wish_to_actually_${action}_${type}"} ($todo -> {$action}, $options) }
}
#############################################################################
sub get_tables {
my ($self, $table) = @_;
require_wish 'tables';
return sort keys %{wish_to_explore_existing_tables ()};
}
#############################################################################
sub get_columns {
my ($self, $table) = @_;
require_wish 'table_columns';
wish_to_adjust_options_for_table_columns (my $options = {table => $table});
return wish_to_explore_existing_table_columns ($options);
}
#############################################################################
sub get_keys {
my ($self, $table) = @_;
require_wish 'table_keys';
wish_to_adjust_options_for_table_keys (my $options = {table => $table});
my %keys = ();
foreach my $i (values %{wish_to_explore_existing_table_keys ($options)}) {
if ($i -> {global_name} =~ /.*?$options->{table}_/i) {
$i -> {global_name} = $';
}
$keys {lc $i -> {global_name}} = join ', ', @{$i -> {parts}}
}
return \%keys;
}
#############################################################################
sub sql_table_name {$_[0]}
1;