Aion-Query
view release on metacpan or search on metacpan
lib/Aion/Query.pm view on Meta::CPAN
use 5.22.0;
no strict; no warnings; no diagnostics;
use common::sense;
our $VERSION = "0.0.6";
use Aion::Format qw//;
use Aion::Format::Json qw//;
use B qw//;
use DBI qw//;
use Scalar::Util qw//;
use List::Util qw//;
use Exporter qw/import/;
our @EXPORT = our @EXPORT_OK = grep {
ref \$Aion::Query::{$_} eq "GLOB"
&& *{$Aion::Query::{$_}}{CODE} && !/^(_|(NaN|import)\z)/n
} keys %Aion::Query::;
use config {
DSN => undef,
DRV => 'mysql',
BASE => 'BASE',
HOST => undef,
PORT => undef,
SOCK => undef,
USER => 'root',
PASS => 123,
CONN => undef,
DEBUG => 0,
MAX_QUERY_ERROR => 1000,
BQ => 1,
};
# ФоÑмиÑÑÐµÑ DSN на оÑнове конÑига
our $DEFAULT_DSN;
sub default_dsn() {
$DEFAULT_DSN //= do {
if(defined DSN) {DSN}
elsif(DRV =~ /mysql|mariadb/i) {
my $sock = SOCK;
$sock //= "/var/run/mysqld/mysqld.sock" if !defined HOST;
"DBI:${\ DRV}:database=${\ BASE};${\
(defined(HOST)?
'host=' . HOST
. (defined(PORT)? ':' . PORT: ())
. ';': ()
)
}${\ (defined($sock)? 'mysql_socket=' . $sock: ()) }"
}
elsif(DRV =~ /sqlite/i) { "DBI:${\ DRV}:dbname=${\ BASE}" }
else { die "Using DSN! DRV: ${\ DRV} is'nt supported." }
}
}
my $CONN;
sub default_connect_options() {
return default_dsn, USER, PASS, $CONN //= CONN // do {
if(DRV =~ /mysql|mariadb/i) {[
"SET NAMES utf8",
"SET sql_mode='NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'",
]}
else {[]}
};
}
# ÐÐ¾Ð½Ð½ÐµÐºÑ Ðº базе и id коннекÑа
sub base_connect {
my ($dsn, $user, $password, $conn) = @_;
my $base = DBI->connect($dsn, $user, $password, {
RaiseError => 1,
PrintError => 0,
$dsn =~ /^DBI:mysql/i ? (mysql_enable_utf8 => 1): (),
}) or die "Connect to db failed";
$base->do($_) for @$conn;
return $base unless wantarray;
my ($base_connection_id) = $dsn =~ /^DBI:(mysql|mariadb)/i
? $base->selectrow_array("SELECT connection_id()")
: -1;
return $base, $base_connection_id;
}
# ÐÑовеÑка коннекÑа и пеÑеконнекÑ
sub connect_respavn {
my ($base) = @_;
$base->disconnect, undef $base if $base and !$base->ping;
($_[0], $_[1]) = base_connect(default_connect_options) if !$base;
return;
}
# РеÑÑаÑÑ ÐºÐ¾Ð½Ð½ÐµÐºÑа
sub connect_restart {
my ($base, $base_connection_id) = @_;
$base->disconnect if $base;
($_[0], $_[1]) = base_connect(default_connect_options());
return;
}
# ÐниÑиализаÑÐ¸Ñ ÐÐ
our $base; our $base_connection_id;
END {
$base->disconnect if $base;
}
# возможно вÑполнÑеÑÑÑ Ð·Ð°Ð¿ÑÐ¾Ñ - нÑжно его ÑбиÑÑ
sub query_stop {
return if $base_connection_id == -1;
# вÑпомогаÑелÑное подклÑÑение
my $signal = base_connect(default_connect_options());
$signal->do("KILL HARD " . ($base_connection_id + 0));
$signal->disconnect;
return;
}
# ÐапÑоÑÑ Ðº базе
our @DEBUG;
sub sql_debug(@) {
my ($fn, $query) = @_;
my $msg = "$fn: " . (ref $query? np($query): $query);
push @DEBUG, $msg;
print STDERR $msg, "\n" if DEBUG;
}
# sub debug_html {
# join "", map { ("<p class='debug'>", to_html($_), "</p>\n") } @DEBUG;
# }
# sub debug_text {
# return "" if !@DEBUG;
# join "", map { "$_\n\n" } @DEBUG, "";
# }
# sub debug_array {
# return if !@DEBUG;
# $_[0]->{SQL_DEBUG} = \@DEBUG;
# return;
# }
sub LAST_INSERT_ID() {
$base->last_insert_id
}
# ÐÑеобÑазÑÐµÑ Ð² бинаÑнÑÑ ÑÑÑÐ¾ÐºÑ Ð¿ÑинÑÑÑÑ Ð² MYSQL
sub _to_hex_str($) {
my ($s) = @_;
no utf8;
use bytes;
$s =~ s/./sprintf "%02X", ord $&/gaes;
"X'$s'"
}
# ÐÐ´ÐµÑ Ð¿ÐµÑекодиÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ñимволов:
# Рбазе иÑполÑзÑеÑÑÑ cp1251, поÑÑÐ¾Ð¼Ñ ÑимволÑ, коÑоÑÑе в Ð½ÐµÑ Ð½Ðµ вÑ
одÑÑ, нÑжно пеÑевеÑÑи в поÑледоваÑелÑноÑÑи.
# Ðид поÑледоваÑелÑноÑÑи: °ЧÐСÐÐ_Ð_254-ÑиÑной ÑиÑÑеме; \x7F
# Ðнак ° вÑбÑан поÑомÑ, ÑÑо он вÑÑе 127, ÑооÑвеÑÑÑвенно ÑÑÑока из Ð±Ð°Ð·Ñ Ð´Ð°Ð½Ð½ÑÑ
, ÑодеÑжаÑÐ°Ñ ÑакÑÑ Ð¿Ð¾ÑледоваÑелÑноÑÑÑ,
# бÑÐ´ÐµÑ Ñ Ñлагом utf8, ÑÑо необÑ
одимо Ð´Ð»Ñ Ð¾Ð±ÑаÑного пеÑекодиÑованиÑ.
sub _recode_cp1251 {
my ($s) = @_;
return $s unless BQ;
$s =~ s/°|[^\Q$Aion::Format::CIF\E]/"°${\ to_radix(ord $&, 254) }\x7F"/ge;
$s
}
sub quote(;$);
sub quote(;$) {
my $k = @_ == 0? $_: $_[0];
my $ref;
!defined($k)? "NULL":
ref $k eq "ARRAY" && ref $k->[0] eq "ARRAY"?
join(", ", map { join "", "(", join(", ", map quote, @$_), ")" } @$k):
ref $k eq "ARRAY"? join("", join(", ", map quote, @$k)):
ref $k eq "HASH"?
join(", ", map { join "", $_, " = ", quote $k->{$_} } sort keys %$k):
ref $k eq "REF" && ref $$k eq "ARRAY"?
join(" ", List::Util::pairmap { join " ", "WHEN", quote $a, "THEN", quote $b } @$$k):
ref $k eq "SCALAR"? $$k:
Scalar::Util::blessed $k ? $k:
ref $k ne ""? die "Something strange: `$k`":
$k =~ /^-?(?:0|[1-9]\d*)(\.\d+)?\z/a
&& ($ref = ref B::svref_2object(@_ == 0? \$_: \$_[0])
) ne "B::PV"? (
!$1 && $ref eq "B::NV"? "$k.0": $k
):
!utf8::is_utf8($k)? (
$k =~ /[^\t\n -~]/a ? _to_hex_str($k): #$base->quote($k, DBI::SQL_BINARY):
Aion::Format::to_str($k)
):
Aion::Format::to_str(_recode_cp1251($k))
}
sub _set_type {
my ($type, $x) = @_;
if(ref $x eq "ARRAY") {
[map _set_type($type, $_), @$x]
}
elsif(ref $x eq "HASH") {
+{ map ($_ => _set_type($type, $type->{$_})), keys %$x }
}
elsif(ref $type eq "SCALAR") {
\_set_type($type, $$x);
}
elsif($type eq "^") {
int $x
}
elsif($type eq "~") {
"$x"
}
elsif($type eq ".") {
$x+1.e-100
}
else {
die "_set_type($type): type does not exist"
}
}
sub _set_params {
my ($query, $param) = @_;
$query =~ s!:([~\.^])?([a-z_]\w*)!
exists $param->{$2}? do {
my $x = $param->{$2};
defined $1 ? quote _set_type($1, $x): quote $x
}: die "The :$1 parameter was not passed."!ige;
$query
}
# ÐÐµÐ»Ð°ÐµÑ Ð¿Ð¾Ð´ÑÑановки
sub query_prepare (@) {
my ($query, %param) = @_;
$query =~ s!
^(?<sep>[\ \t]*) (?<if>\w+)>> [\ \t]* (?<code>.*)
| ^(?<sep>[\ \t]*) (?<for>\w+)\*>> [\ \t]* (?<code>.*)
| (?<param> : [~\.^]? [a-z_]\w*)
!
exists $+{if}? ($param{$+{if}}? $+{sep} . _set_params($+{code}, \%param): ""):
exists $+{for}? do {
my ($sep, $param, $code) = @+{qw/sep for code/};
join "\n", map { local $param{'_'} = $_; _set_params("$sep$code", \%param) } @{$param{$param}}
}:
_set_params($+{param}, \%param)
!imgex;
$query
}
# ÐÑполнÑÐµÑ sql-запÑоÑ
sub query_do($;$) {
my ($query, $columns) = @_;
sql_debug query => $query;
connect_respavn($base, $base_connection_id);
my $res = eval {
if($query =~ /^\s*(select|show|desc(ribe)?)\b/in) {
my $r = @_>1? do {
my $sth = $base->prepare($query);
$sth->execute;
$_[1] = [@{$sth->{NAME}}];
my $res = $sth->fetchall_arrayref({});
$sth->finish;
$res
}: $base->selectall_arrayref($query, { Slice => {} });
if(defined $r and BQ) {
for my $row (@$r) {
for my $k (keys %$row) {
$row->{$k} =~ s/°([^\x7F]{1,7})\x7F/chr from_radix($1, 254)/ge if utf8::is_utf8($row->{$k});
}
}
}
$r
} else {
0 + $base->do($query)
}
};
die +(length($query)>MAX_QUERY_ERROR? substr($query, 0, MAX_QUERY_ERROR) . " ...": $query) . "\n\n$@" if $@;
$res
}
sub query_ref(@) {
my ($query, %kw) = @_;
my $map = delete $kw{MAP};
$query = query_prepare($query, %kw) if @_>1;
my $res = query_do($query);
if($map && ref $res eq "ARRAY") {
eval "require $map" or die unless UNIVERSAL::can($map, "new");
[map { $map->new(%$_) } @$res]
} else {
$res
}
}
sub query(@) {
my $ref = query_ref(@_);
wantarray && ref $ref? @$ref: $ref;
}
# ÐозвÑаÑÐ°ÐµÑ sth
sub query_sth(@) {
my ($query, %kw) = @_;
$query = query_prepare($query, %kw) if @_>1;
my $sth = $base->prepare($query);
$sth->execute;
$sth
}
# ÐÐ»Ñ ÑлайÑа
#
# query_slice word => "id", "SELECT word, id FROM word WHERE word in (1,2,3)" -> { 1 => 10, 2 => 20 }
#
# query_slice word => {}, "SELECT word, id FROM word WHERE word in (1,2,3)" -> { 1 => {id => 10, word => 1} }
#
# query_slice word => ["id"], "SELECT word, id FROM word WHERE word in (1,2,3)" -> { 1 => [10, 20], 2 => [30] }
#
# query_slice word => [], "SELECT word, id FROM word WHERE word in (1,2,3)" -> { 1 => [{id => 10, word => 1}, {id => 20, word => 2}] }
#
# query_slice word => [[]], "SELECT word, id FROM word WHERE word in (1,2,3)" -> [ [{id => 10, word => 1}, {id => 20, word => 2}], ... ]
#
# TODO: query_slice [] => word, "SELECT word, id FROM word WHERE word in (1,2,3)" -> [{id => 10, word => 1}, {id => 20, word => 2}]
#
# TODO: [ "id", "name", "jinni" ] -> [{ id=>1, items => [{ name => "hi!", items => [{ jinni=>2, items => [{...}] }] }] }]
#
sub query_slice(@);
sub query_slice(@) {
my ($key, $val, @args) = @_;
( run in 1.553 second using v1.01-cache-2.11-cpan-39bf76dae61 )