1 package Newslash::Model::Cowrapper;
2 # Cowrapper - Connection Wrapper for MySQL
12 use List::Util qw(any);
16 use DateTime::Format::MySQL;
21 return unless my $flag = shift;
23 if ($flag eq '-base') {
25 } elsif ($flag eq '-strict') {
32 push @{"${caller}::ISA"}, $flag;
35 $_->import for qw(strict warings utf8);
36 feature->import(':5.10');
40 my ($self, @rest) = @_;
41 return $self->{options} if @rest == 0;
43 my $hash = $self->{options} || {};
46 $node = $hash->{$node};
54 my $options = shift || {};
57 bless {options => {%$options, @_},
66 my ($self, $value) = @_;
68 $self->{_last_sql} = $value;
70 return $self->{_last_sql};
74 my ($self, $value) = @_;
76 $self->{_last_attr} = $value;
78 return $self->{_last_attr};
82 my ($self, $sql, $attr) = @_;
84 $self->{_last_sql} = $sql;
85 $self->{_last_attr} = $attr;
87 return ($self->{_last_sql}, $self->{_last_attr});
95 sub _dump_last_query {
98 return "+---------\n>> LAST QUERY of $cls: \n" .
99 $self->_last_sql . Dumper($self->_last_attr) .
103 ######### connect/disconnect functions
107 my $options = shift || {};
109 if ($self->transaction_mode) {
110 return $self->{_tr_dbh};
113 my $DB_HOST = $self->{options}->{Database}->{host};
114 my $DB_NAME = $self->{options}->{Database}->{name};
115 my $DB_USER = $self->{options}->{Database}->{user};
116 my $DB_PASSWORD = $self->{options}->{Database}->{password};
118 my $settings = 'mysql_read_default_group=libmysqlclient;mysql_read_default_file=/etc/mysql/my.cnf';
120 mysql_enable_utf8 => 1,
121 ShowErrorStatement => 1,
122 Callbacks => { # hack to use utf8mb4. see http://d.hatena.ne.jp/hirose31/20141028/1414496347
124 shift->do('SET NAMES utf8mb4');
131 my $dbh = DBI->connect("DBI:mysql:$DB_NAME:$DB_HOST;$settings", $DB_USER, $DB_PASSWORD, $attr);
133 $self->{_tr_dbh} = $dbh;
140 return if $self->transaction_mode;
141 return if !$self->{_tr_dbh};
143 return $self->{_tr_dbh}->disconnect;
147 ########## Transaction related functions
149 sub transaction_mode {
151 return 0 if !defined $self->{_transactions};
152 return ($self->{_transactions} > 0);
155 sub start_transaction {
157 my $options = shift || {};
159 if (!defined $self->{_transactions}) {
160 $self->{_transactions} = 0;
163 # already transaction mode
164 if ($self->transaction_mode) {
165 $self->{_transactions} += 1;
166 return $self->{_tr_dbh};
169 # start new transaction
170 $options->{AutoCommit} = 0;
171 $self->connect_db($options);
172 $self->{_transactions} = 1;
174 return $self->{_tr_dbh};
177 # use external defined dbh
178 sub use_transaction {
183 $self->{_transactions} = 2;
184 $self->{_tr_dbh} = $dbh;
192 return if !$self->transaction_mode;
194 if ($self->{_transactions} == 1) {
195 $self->{_tr_dbh}->commit;
196 $self->{_tr_dbh}->disconnect;
198 delete $self->{_tr_dbh};
200 $self->{_transactions} -= 1;
205 return if !$self->transaction_mode;
207 #$self->set_error($self->{_tr_dbh}->errstr);
209 $self->{_tr_dbh}->rollback;
210 $self->{_tr_dbh}->disconnect;
211 delete $self->{_tr_dbh};
212 $self->{_transactions} = 0;
215 ########## Utility functions
218 my ($self, $table) = @_;
220 my $dbh = $self->connect_db;
221 my $DB_NAME = $self->{options}->{Database}->{name};
224 SELECT * FROM information_schema.TABLES
225 WHERE TABLE_SCHEMA = ?
229 my $sth = $dbh->prepare($sql);
230 $sth->execute($DB_NAME, $table);
231 my $rs = $sth->fetchall_arrayref({});
232 $self->disconnect_db;
241 if ($self->options->{readonly}) {
242 $self->set_error("readonly mode");
248 ########## error handling
251 $self->{_error} = undef;
252 $self->{_errorno} = undef;
256 my ($self, $error, $errorno) =@_;
257 $self->{_error} = $error;
258 $self->{_errorno} = $errorno if $errorno;
261 my ($self, $errorno) =@_;
262 $self->{_errorno} = $errorno;
267 return $self->{_error};
272 return $self->{_errorno};
275 ########## Utility functions
276 sub calculate_time_range {
277 my ($self, $params) = @_;
279 my $date = $params->{date};
280 my $year = $params->{year};
281 my $month = $params->{month};
282 my $day = $params->{day};
284 my $years = $params->{years};
285 my $months = $params->{months};
286 my $days = $params->{days};
287 my $weeks = $params->{weeks};
288 my $hours = $params->{hours};
289 my $minutes = $params->{minutes};
291 my $until = $params->{until};
292 my $since = $params->{since};
294 my $offset_sec = $params->{offset_sec};
295 my ($dt_since, $dt_until);
297 my $range_mode = ($years || $months || $days || $weeks || $hours || $minutes);
301 if (ref($since) eq 'HASH') {
304 elsif ($since =~ m/^\d{4}-\d{2}-\d{2}$/) {
305 $dt_since = DateTime::Format::MySQL->parse_date($since);
308 $dt_since = DateTime::Format::MySQL->parse_datetime($since);
312 if (ref($until) eq 'HASH') {
315 elsif ($until =~ m/^\d{4}-\d{2}-\d{2}$/) {
316 $dt_until = DateTime::Format::MySQL->parse_date($until);
319 $dt_until = DateTime::Format::MySQL->parse_datetime($until);
323 $dt_since = DateTime->new(year => $year,
324 month => $month || 1,
328 if ($date eq 'today') {
329 $dt_since = DateTime->today;
332 $dt_since = DateTime::Format::MySQL->parse_date($date);
343 $dt_until = $dt_since->clone;
344 $dt_until->add("years" => $years) if $years;
345 $dt_until->add("months" => $months) if $months;
346 $dt_until->add("days" => $days) if $days;
347 $dt_until->add("weeks" => $weeks) if $weeks;
348 $dt_until->add("hours" => $hours) if $hours;
349 $dt_until->add("minutes" => $minutes) if $minutes;
352 $dt_until = DateTime->now;
353 $dt_since = $dt_until->clone;
354 $dt_since->add("years" => -$years) if $years;
355 $dt_since->add("months" => -$months) if $months;
356 $dt_since->add("days" => -$days) if $days;
357 $dt_since->add("weeks" => -$weeks) if $weeks;
358 $dt_since->add("hours" => -$hours) if $hours;
359 $dt_since->add("minutes" => -$minutes) if $minutes;
364 $term = "month" if $month;
365 $term = "day" if $day;
367 $dt_until = $dt_since->clone;
368 $dt_until->add("${term}s" => 1);
372 $dt_since->add(seconds => -$offset_sec) if $dt_since;
373 $dt_until->add(seconds => -$offset_sec) if $dt_until;
377 $from = DateTime::Format::MySQL->format_datetime($dt_since) if $dt_since;
378 $to = DateTime::Format::MySQL->format_datetime($dt_until) if $dt_until;
384 =head2 build_where_clause(unique_keys => $unique_keys,
386 timestamps => $timestamps,
387 where => $where_params,
390 build SQL WHERE clause.
400 STRING or ARRAYREF or HASHREF.
401 If STRING, $keys contains unique keys.
402 If ARRAREF, @$keys contains unique keys.
403 If HASHREF, %$keys is { key1 => alias1, key2 => alias2, ... }.
405 'unique key' is a column name defined with 'UNIQUE' or 'PRIMARY'.
407 If you want to use key which is not correspod to table's column,
408 use alias. When aliases given, the aliases are replaced to keys in SQL.
414 STRING or ARRAYREF or HASHREF.
415 If STRING, $keys contains non-unique keys.
416 If ARRAREF, @$keys contains non-unique keys.
417 If HASHREF, %$keys is { key1 => alias1, key2 => alias2, ... }.
423 HASHREF to query parameters
427 when list context, returns ($clause, \@values, $unique).
428 when scalar context, return hashref like:
429 { clause => $clause, values => \@values, unique => $unique };
435 sub build_where_clause {
441 if ($args->{keys} || $args->{unique_keys}) {
442 $uniques = $args->{unique_keys};
443 $keys = $args->{keys};
446 $uniques = $self->unique_keys;
447 $keys = $self->get_keys("non-unique");
449 my $timestamp = $args->{timestamp} || $self->{timestamp};
451 my $params = $args->{params};
452 my $where = $args->{where} || $params;
457 # check and process "OR" parameter
458 if (defined $where->{OR}) {
459 my $sub_params = $where->{OR};
460 croak "invalid OR parameter" if (ref($sub_params) ne "HASH");
462 my ($sub_clause, $sub_values, $sub_unique) = $self->_parse_where_clause($uniques, $keys, $timestamp, $sub_params);
463 my $or_clause = join(" OR ", @$sub_clause);
465 push @clauses, "( $or_clause )";
466 push @values, @$sub_values;
470 my ($sub_clauses, $sub_values, $unique) = $self->_parse_where_clause($uniques, $keys, $timestamp, $where);
471 push @clauses, @$sub_clauses;
472 push @values, @$sub_values;
476 $clause = "WHERE " . join(" AND ", @clauses);
479 return wantarray ? ($clause, \@values, $unique)
480 : { clause => $clause,
482 unique => $unique, };
487 sub _parse_where_clause {
488 my ($self, $uniques, $keys, $timestamp, $params) = @_;
495 my $decode_param = sub {
498 # simply equal clause
500 return ("$k = ?", $v);
504 if (ref($v) eq 'ARRAY') {
505 my $placeholder = join(", ", map { "?" } @$v);
506 return ("$k IN ($placeholder)", $v);
509 # gt/lt/ge/le (>, <, >=, <=)
510 if (ref($v) eq 'HASH') {
511 return ("$k > ?", $v->{gt}) if defined $v->{gt};
512 return ("$k < ?", $v->{lt}) if defined $v->{lt};
513 return ("$k >= ?", $v->{ge}) if defined $v->{ge};
514 return ("$k <= ?", $v->{le}) if defined $v->{le};
517 die "invalid query parameter: $k, $v";
521 my $decode_and_set_params = sub {
522 my ($k, $p, $is_unique) = @_;
523 my ($c, $v) = $decode_param->($k, $p);
524 push @clauses, $c if $c;
526 if (ref($v) eq 'ARRAY') {
531 $unique = 1 if $is_unique;
537 # reqularize keys to hashref
538 my $ky = $self->_keys_to_hash($keys);
540 for my $k (keys(%$ky)) {
541 if (defined $params->{$k}) {
542 $decode_and_set_params->($ky->{$k}, $params->{$k});
546 # reqularize uniques to hashref
547 my $u = $self->_keys_to_hash($uniques);
549 for my $k (keys(%$u)) {
550 if (defined $params->{$k}) {
551 $decode_and_set_params->($u->{$k}, $params->{$k}, 1);
556 # year, month, day, years, months, days, date
558 my ($begin, $end) = $self->calculate_time_range($params);
560 push @clauses, "$timestamp >= ?";
561 push @values, $begin;
564 push @clauses, "$timestamp < ?";
569 return (\@clauses, \@values, $unique);
572 # reqularize keys to hashref
574 my ($self, $keys) = @_;
575 return if !defined $keys;
578 if(ref($keys) eq 'HASH') {
581 elsif (ref($keys) eq 'ARRAY') {
587 elsif (!ref($keys)) {
588 $ky = { $keys => $keys };
593 =head2 build_limit_clause(params => \@params)
595 build SQL's LIMIT clause.
605 HASHREF to query parameters
609 when list context, returns ($clause, \@values).
610 when scalar context, return hashref like:
611 { clause => $clause, values => \@values };
617 sub build_limit_clause {
621 my $params = $args->{params};
622 my $limit = $args->{default};
623 my $offset = $args->{default_offset};
625 $limit = $args->{default_limit} if defined $args->{default_limit};
626 $limit = $params->{limit} if defined $params->{limit};
627 $offset = $params->{offset} if defined $params->{offset};
631 if (defined $limit) {
632 push @clauses, "LIMIT ?";
633 push @values, $limit;
635 if (defined $offset) {
636 push @clauses, "OFFSET ?";
637 push @values, $offset;
640 my $clause = join(" ", @clauses);
642 return wantarray ? ($clause, \@values)
643 : { clause => $clause, values => \@values };
646 =head2 build_order_by_clause(keys => $keys, params => \@params)
648 build SQL ORDER BY clause.
659 If ARRAREF, @$keys contains selectSable columns.
660 If HASHREF, %$keys is { key1 => alias1, key2 => alias2, ... }.
664 HASHREF to query parameters
668 when list context, returns ($clause, \@values).
669 when scalar context, return hashref like:
670 { clause => $clause, values => \@values };
676 sub build_order_by_clause {
680 my $unique_keys = $args->{unique_keys} || $self->unique_keys;
681 my $keys = $args->{keys} || $self->get_keys("all");
683 my $params = $args->{params};
684 my $order_by = $params->{order_by};
686 if (!$keys || !$params || !$order_by) {
687 return wantarray ? ("", [])
688 : { clause => "", values => [] };
696 # convert $keys to hash style
697 if (ref($keys) eq 'HASH') {
698 $k_names = [keys %$keys];
701 if (ref($keys) eq 'ARRAY') {
704 elsif (!ref($keys)) {
708 # convert $order_by to hash style
709 if (ref($order_by) eq "ARRAY") {
711 for my $item (@$order_by) {
712 $hash->{$item} = "ASC";
716 elsif (!ref($order_by)) {
717 $order_by = {$order_by => "ASC"};
720 # when $order_by is not ARRAY or SCALAR or HASH,
721 # this block is passed.
722 if (ref($order_by) eq "HASH") {
723 for my $k (keys %$order_by) {
724 next if !any {$_ eq $k} @$k_names;
726 my $order = uc($order_by->{$k});
728 if ($use_alias && $keys->{$k}) {
729 $target = $keys->{$k};
731 push @clauses, "$target $order" if $target;
737 $clause = "ORDER BY " . join(", ", @clauses);
740 return wantarray ? ($clause, \@values)
741 : { clause => $clause, values => \@values };
744 ########## Insert method
748 return if $self->check_readonly;
751 my $table = $args->{table} || $self->primary_table;
753 $self->set_error("table not given", -1);
759 $keys = $self->_build_keys($args->{keys});
762 $keys = $self->get_keys("all");
765 my $params = $args->{params};
767 $self->set_error("params not given", -1);
775 # extract key and values
776 for my $k (keys %$keys) {
777 next if !defined $params->{$k};
778 if (!ref($args->{$k})) {
779 push @cols, $keys->{$k};
780 push @values, $params->{$k};
781 push @placeholders, "?";
783 elsif(ref($args->{$k}) eq "HASH") {
784 for my $subkey (keys %{$params->{$k}}) {
785 if (lc($subkey) eq "function") {
786 push @cols, $keys->{$k};
787 push @placeholders, $params->{$k}->{$subkey};
793 if (!@cols || !@values) {
794 $self->set_error("no valid values", -1);
798 my $cols_clause = join(", ", @cols);
799 my $values_clause = join(", ", @placeholders);
802 my $sql = "INSERT INTO $table ($cols_clause) VALUES ($values_clause)";
803 $self->_last_query($sql, \@values);
806 #warn Dumper @values;
808 my $dbh = $self->connect_db;
809 my $rs = $dbh->do($sql, undef, @values);
810 $self->disconnect_db;
816 ########## Select method
818 =head2 generic_select(table => $table, uniques => $uniques, keys => $keys, params => $params)
820 build SQL's ORDER BY clause.
834 ARRAYREF to unique keys. 'unique key' is a column name
835 defined with 'UNIQUE' or 'PRIMARY'.
841 ARRAYREF to acceptable keys (column names)
847 HASHREF to query parameters
851 when list context, returns ($clause, \@values).
852 when scalar context, return hashref like:
853 { clause => $clause, values => \@values };
862 my $table = $args->{table} || $self->primary_table;
864 $self->set_error("table no given");
867 my $params = $args->{params} || {};
869 if ($args->{uniques}) {
870 warn "Cowrapper::generic_select(): 'uniques' parameter is deprecated. use 'unique_keys'.";
871 $args->{unique_keys} ||= $args->{uniques};
873 my $uniques = $args->{unique_keys};
874 my $keys = $args->{keys};
875 my $timestamp = $args->{timestamp};
878 my ($values, $orderby, $limit, $where, $unique_query);
880 ($where, $values, $unique_query) = $self->build_where_clause(unique_keys => $uniques,
882 timestamp => $timestamp,
884 push @arguments, @$values if @$values;
886 ($orderby, $values) = $self->build_order_by_clause(keys => $keys, params => $params);
887 push @arguments, @$values if @$values;
889 ($limit, $values) = $self->build_limit_clause(params => $params);
890 push @arguments, @$values if @$values;
892 my $dbh = $self->connect_db;
893 my $generic_sql = <<"EOSQL";
900 my $sql = $args->{sql} || $generic_sql;
901 $self->_last_query($sql, \@arguments);
903 my $sth = $dbh->prepare($sql);
904 $sth->execute(@arguments);
905 my $rs = $sth->fetchall_arrayref(+{});
907 $self->set_error("select failed", $dbh->errstr, $dbh->err);
908 $self->disconnect_db;
911 $self->disconnect_db;
915 return $rs->[0] if @$rs;
921 ########## Count method
923 =head2 generic_count(
925 target => $target_column,
926 timestamp => $timestamp_column,
930 offset_sec => $offset_sec,
931 join => $join_clause,
932 where => $where_clause)
950 =item $timestamp_column
960 target month (omissible)
964 target day (omissible)
968 additional WHERE clause (must not include 'WHERE' !; omissible)
972 additional JOIN clause (must include 'JOIN' !; omissible)
986 my $table = $params->{table} || $self->primary_table;
987 my $target = $params->{target};
988 my $timestamp = $params->{timestamp} || $self->timestamp;
990 return if (!$table || !$target || !$timestamp || !$params->{year});
993 $term = "month" if !$params->{day};
994 $term = "year" if !$params->{month};
996 my ($year, $month, $day) = ($params->{year}, $params->{month}, $params->{day});
997 $year = 1 if (!$year || $year !~ m/^[0-9]{4}$/);
998 $month = 1 if (!$month || $month !~ m/^(1[0-2]|0?[0-9])$/);
999 $day = 1 if (!$day || $day !~ m/^(3[0-1]|[1-2][0-9]|0?[0-9])$/);
1001 my $offset = $params->{offset_sec} || 0;
1002 $offset = 0 if $offset !~ m/^[+-]?[0-9]+$/;
1004 my $dt = DateTime->new(year => $year,
1007 $dt->add(seconds => -$offset);
1008 my $dt_string = DateTime::Format::MySQL->format_datetime($dt);
1010 # create end of term datetime
1011 # we must consider timezone offset, so use relative day/month.
1012 # why use "DATE_ADD(?, INTERVAL 1 MONTH)" ? bacause, DATE_ADD function add simply 30 days...
1013 my $dt_end = DateTime->new(year => $year,
1016 if ($term eq "month") {
1017 $dt_end->add(months => 1);
1019 elsif ($term eq "year") {
1020 $dt_end->add(years => 1);
1022 $dt->add(seconds => -$offset);
1023 my $dt_end_string = DateTime::Format::MySQL->format_datetime($dt_end);
1027 my $where_clause = "";
1028 if ($params->{where}) {
1029 $where_clause = "$params->{where} AND ";
1031 my $join_clause = $params->{join} || "";
1034 if ($term eq "day") {
1035 # `stories` table not contain display/non-display flag,
1038 SELECT COUNT($table.$target) AS count FROM $table
1041 $table.$timestamp >= ?
1042 AND $table.$timestamp < DATE_ADD(?, INTERVAL 1 DAY)
1044 push @attrs, $dt_string, $dt_string;
1046 elsif ($term eq "month") {
1048 SELECT TIMESTAMPDIFF(DAY, ?, $table.$timestamp) AS day,
1049 COUNT($table.$target) AS count
1053 $table.$timestamp >= ?
1054 AND $table.$timestamp < ?
1055 GROUP BY TIMESTAMPDIFF(DAY, ?, $table.$timestamp)
1058 push @attrs, $dt_string, $dt_string, $dt_end_string, $dt_string;
1060 elsif ($term eq "year") {
1062 SELECT TIMESTAMPDIFF(MONTH, ?, $table.$timestamp) AS month,
1063 COUNT($table.$target) AS count
1067 $table.$timestamp >= ?
1068 AND $table.$timestamp < ?
1069 GROUP BY TIMESTAMPDIFF(MONTH, ?, $table.$timestamp)
1072 push @attrs, $dt_string, $dt_string, $dt_end_string, $dt_string;
1074 my $dbh = $self->connect_db;
1075 my $sth = $dbh->prepare($sql);
1077 $self->_last_query($sql, \@attrs);
1078 $sth->execute(@attrs);
1079 my $rs = $sth->fetchall_arrayref({});
1082 $self->disconnect_db;
1085 $self->disconnect_db;
1089 if ($term eq "day") {
1090 return $rs->[0]->{count};
1092 elsif ($term eq "month") {
1095 elsif ($term eq "year") {
1102 for my $counts (@$rs) {
1103 # day / month is differential from base datetime, so add 1
1104 $hash->{$counts->{$key} + 1} = $counts->{count};
1112 ########## Update method
1114 =head2 generic_update(table => $table,
1115 updatable_keys => $updatables,
1116 addable_keys => $addables,
1117 condition_keys => $conditions,
1120 execute UPDATE SQL command.
1134 ARRAYREF or HASHREF to updatable keys. 'updatable key' is a updatable column name.
1138 ARRAYREF or HASHREF to addable keys. 'addable key' is a addable column name.
1142 ARRAYREF or HASHREF to conditional keys. 'conditional key' is a column name which can use in WHERE clause.
1146 HASHREF to query parameters
1150 when list context, returns ($clause, \@values).
1151 when scalar context, return hashref like:
1152 { clause => $clause, values => \@values };
1158 sub generic_update {
1160 return if $self->check_readonly;
1164 my $table = $args->{table} || $self->primary_table;
1166 $self->set_error("table given", -1);
1170 my $updatables = $args->{updatable_keys} || $self->get_keys("updatable");
1171 my $addables = $args->{addable_keys} || $self->get_keys("addable");
1173 my $conditions = $args->{condition_keys};
1174 my $params = $args->{params} || {};
1175 my $where = $args->{where} || $params->{where};
1178 $conditions = $args->{condition_keys} || $self->get_keys("all");
1181 $conditions = $args->{condition_keys} || $self->get_keys("primary");
1184 if (!$conditions || !%$conditions) {
1185 $self->set_error("no_condition_keys", -1);
1188 my $u_hash = $self->_keys_to_hash($updatables);
1189 #my $a_hash = $self->_keys_to_hash($addables);
1191 # first, create WHERE clause
1192 my ($where_clause, $where_values) = $self->build_where_clause(keys => $conditions,
1195 if (!$where_clause) {
1196 $self->set_error("no_where_clauses", -1);
1204 for my $col (keys %$u_hash) {
1205 next if !defined $params->{$col};
1206 my $c = $u_hash->{$col};
1208 # if $params->{$col} is SCALAR, set to the value
1209 if (!ref($params->{$col})) {
1210 push @values, $params->{$col};
1211 push @set_clauses, "$c = ?";
1213 # if $params->{$col} is HASH, do given operation
1214 elsif (ref($params->{$col}) eq 'HASH') {
1217 for my $k (keys %{$params->{$col}}) {
1218 $p->{lc($k)} = $params->{$col}->{$k};
1221 if (defined $p->{max} && defined $p->{min}) {
1222 push @values, $p->{min};
1223 push @values, $p->{max};
1224 push @values, $p->{add};
1225 push @set_clauses, "$c = GREATEST(?, LEAST(?, $c + ?))";
1227 elsif (defined $p->{max}) {
1228 push @values, $p->{min};
1229 push @values, $p->{add};
1230 push @set_clauses, "$c = GREATEST(?, $c + ?)";
1232 elsif (defined $p->{min}) {
1233 push @values, $p->{min};
1234 push @values, $p->{add};
1235 push @set_clauses, "$c = LEAST(?, $c + ?)";
1238 push @values, $p->{add};
1239 push @set_clauses, "$c = $c + ?";
1242 if ($p->{function}) {
1243 push @set_clauses, "$c = ($p->{function})";
1248 return 0 if !@set_clauses;
1249 my $set_clause = join(", ", @set_clauses);
1251 my $sql = "UPDATE $table SET $set_clause $where_clause";
1252 push @values, @$where_values;
1253 $self->_last_query($sql, \@values);
1256 #warn Dumper @values;
1258 my $dbh = $self->connect_db;
1259 my $rs = $dbh->do($sql, undef, @values);
1260 $self->disconnect_db;
1265 ########## count helper
1267 sub build_interval_times {
1270 my $params = $attr->{params} || {};
1273 return if !$params->{year};
1274 $target = "month" if !$params->{day};
1275 $target = "year" if !$params->{month};
1277 my ($year, $month, $day) = ($params->{year}, $params->{month}, $params->{day});
1278 $year = 1 if (!$year || $year !~ m/^[0-9]{4}$/);
1279 $month = 1 if (!$month || $month !~ m/^(1[0-2]|0?[0-9])$/);
1280 $day = 1 if (!$day || $day !~ m/^(3[0-1]|[1-2][0-9]|0?[0-9])$/);
1282 my $offset = $params->{offset_sec} || 0;
1283 $offset = 0 if $offset !~ m/^[+-]?[0-9]+$/;
1285 my $dt = DateTime->new(year => $year,
1288 $dt->add(seconds => -$offset);
1289 my $dt_string = DateTime::Format::MySQL->format_datetime($dt);
1291 # create end of term datetime
1292 # why use "DATE_ADD(?, INTERVAL 1 MONTH)" ? bacause, this function add simply 30 days...
1293 my $dt_end = DateTime->new(year => $year,
1296 if ($target eq "month") {
1297 $dt_end->add(months => 1);
1299 elsif ($target eq "year") {
1300 $dt_end->add(years => 1);
1302 $dt->add(seconds => -$offset);
1303 my $dt_end_string = DateTime::Format::MySQL->format_datetime($dt_end);
1305 return wantarray ? ($target, $dt_string, $dt_end_string)
1306 : { target => $target,
1307 start => $dt_string,
1308 end => $dt_end_string };
1312 ######### data export / import functions
1314 =head2 export_json()
1316 export as json data.
1318 $obj->export_json(file => "foobar.json",
1320 exclude => [qw(foo bar)],
1321 sort_key => [qw(hoge moge)],
1340 my $table = $params->{table};
1343 my $keys = $params->{sort_key} || [];
1348 my $query_params = {};
1349 if ($params->{sort_key}) {
1350 $query_params->{order_by} = $keys;
1353 my $datas = $self->generic_select(table => $table, keys => $keys, params => $query_params);
1356 my $exclude = $params->{exclude} || [];
1357 if (!ref($exclude)) {
1358 $exclude = [$exclude];
1361 for my $data (@$datas) {
1362 for my $k (@$exclude) {
1367 #warn Dumper($datas);
1373 if ($params->{file}) {
1374 my $bin_data = $j->utf8->encode($datas);
1375 my $fh = FileHandle->new($params->{file}, "w");
1377 $self->set_error($!);
1380 $fh->print($bin_data);
1384 return JSON::to_json($datas);
1387 =head2 import_json()
1389 export as json data.
1391 $obj->export_json(file => "foobar.json",
1393 exclude => [qw(foo bar)],
1397 $obj->export_json(json => '{ "foo": "bar", "hoge": 1 }',
1399 exclude => [qw(foo bar)],
1419 my $table = $params->{table};
1422 if ($params->{json} && $params->{file}) {
1427 if ($params->{file}) {
1428 my $fh = FileHandle->new($params->{file}, "r");
1430 $self->set_error($!);
1433 my $json = do { local $/; <$fh> };
1435 $datas = JSON::decode_json($json);
1437 if ($params->{json}) {
1438 $datas = JSON::from_json($params->{json});
1442 return if ref($datas) ne "ARRAY";
1444 my $primary_key = $params->{unique_key};
1445 my $exclude = $params->{exclude} || [];
1446 if (!ref($exclude)) {
1447 $exclude = [$exclude];
1450 my $dbh = $self->start_transaction;
1452 for my $data (@$datas) {
1453 for my $k (@$exclude) {
1461 for my $k (keys(%$data)) {
1462 #my $quoted_k = '`' . $k . '`';
1463 my $quoted_k = $dbh->quote_identifier($k);
1464 push @cols, $quoted_k;
1466 push @vals, $data->{$k};
1467 if ($k ne $primary_key) {
1468 push @updates, "$quoted_k = ?";
1469 push @update_vals, $data->{$k};
1472 my $cols_clause = join(", ", @cols);
1473 my $placeholders = join(", ", @plhs);
1474 my $update_clauses = join(", ", @updates);
1476 my $sql = <<"EOSQL";
1481 ON DUPLICATE KEY UPDATE
1484 push @vals, @update_vals;
1485 $self->_last_query($sql, \@vals);
1487 my $rs = $dbh->do($sql, undef, @vals);
1498 ########## virtual method for O/R mapping function
1499 sub key_definition { return {}; }
1502 my ($self, $target) = @_;
1503 return {} if !$target;
1504 return $target if ref($target) eq "HASH";
1505 if (!ref($target)) {
1506 $target = [$target];
1509 my $def = $self->key_definition || {};
1511 if ($def->{basename}) {
1512 $basename = "$def->{basename}.";
1514 elsif ($def->{table}) {
1515 $basename = "$def->{table}.";
1519 for my $uk (@$target) {
1520 $rs->{$uk} = "$basename$uk";
1521 for my $k (keys %{$def->{aliases}}) {
1522 if ($uk eq $def->{aliases}->{$k}) {
1523 $rs->{$k} = "$basename$uk";
1533 my $def = $self->key_definition || {};
1534 return $def->{table};
1539 my $def = $self->key_definition || {};
1540 return $self->_build_keys($def->{primary});
1545 my $def = $self->key_definition || {};
1546 return $self->_build_keys($def->{timestamp});
1551 my $def = $self->key_definition || {};
1552 return $self->_build_keys($def->{datetime});
1557 my $def = $self->key_definition || {};
1558 return $self->_build_keys($def->{other});
1563 my $def = $self->key_definition || {};
1564 my $uniq = $self->_build_keys($def->{unique});
1565 my $primary = $self->_build_keys($def->{primary});
1566 return $self->_merge_keys($uniq, $primary);
1571 my $def = $self->key_definition || {};
1572 return $self->_build_keys($def->{addable});
1578 my $def = $self->key_definition || {};
1581 if (defined $def->{$target}) {
1582 push @keys, @{_to_array($def->{$target})};
1584 elsif ($target eq "all") {
1585 push @keys, $def->{primary} if $def->{primary};
1587 @{_to_array($def->{unique})},
1588 @{_to_array($def->{datetime})},
1589 @{_to_array($def->{addable})},
1590 @{_to_array($def->{other})};
1592 elsif ($target eq "non-unique") {
1594 @{_to_array($def->{datetime})},
1595 @{_to_array($def->{addable})},
1596 @{_to_array($def->{other})};
1598 elsif ($target eq "updatable") {
1600 @{_to_array($def->{unique})},
1601 @{_to_array($def->{datetime})},
1602 @{_to_array($def->{addable})},
1603 @{_to_array($def->{other})};
1606 return $self->_build_keys(\@keys) if @keys;
1612 return [] if !defined $item;
1613 return $item if (ref($item) eq 'ARRAY');
1614 return [$item] if !ref($item);
1623 next if !defined $keys;
1624 if (ref($keys) eq 'ARRAY') {
1625 for my $k (@$keys) {
1629 elsif (ref($keys) eq 'HASH') {
1630 for my $k (keys %$keys) {
1631 $rs->{$k} = $keys->{$k};
1634 elsif (!ref($keys)) {
1635 $rs->{$keys} = $keys;
1642 ########## END OF FILE