File indexing completed on 2024-04-06 12:22:58
0001
0002
0003 use warnings;
0004 use strict;
0005
0006 use DBI;
0007
0008 package CondDB::MySQL;
0009
0010 our $MAXNAME = 32;
0011
0012
0013 sub new {
0014 my $proto = shift;
0015 my $class = ref($proto) || $proto;
0016 my $this = {};
0017
0018 $this->{counter} = 0;
0019 $this->{max_cnt} = 100;
0020
0021 my %args = @_;
0022 $this->{check_overlap} = 1;
0023
0024 bless($this, $class);
0025 return $this;
0026 }
0027
0028 sub DESTROY {
0029 my $this = shift;
0030 if ($this->{transaction}) {
0031 $this->commit();
0032 }
0033
0034 foreach (keys %{$this->{prepared}}) {
0035 $this->{prepared}->{$_}->finish();
0036 }
0037
0038 foreach (keys %{$this->{prepared_overlap}}) {
0039 $this->{prepared_overlap}->{$_}->finish();
0040 }
0041
0042 $this->{dbh}->disconnect() if defined $this->{dbh};
0043 }
0044
0045 sub set_option {
0046 my $this = shift;
0047 my %args = @_;
0048
0049 if (exists $args{-check_overlap}) {
0050 $this->{check_overlap} = !!$args{-check_overlap};
0051 }
0052 }
0053
0054
0055 sub connect {
0056 my $this = shift;
0057 my %args = @_;
0058 my $host = $args{-host};
0059 my $user = $args{-user};
0060 my $pass = $args{-pass};
0061 my $db_opts = $args{-db_opts};
0062
0063 unless ($host) {
0064 die "ERROR: Must give at least host to connect():, $!"
0065 }
0066
0067 $db_opts->{AutoCommit} = 1;
0068
0069 my $db = $args{-db};
0070
0071 my $dsn;
0072 if ($db) {
0073 $dsn = "DBI:mysql:database=$db;host=$host";
0074 } else {
0075 $dsn = "DBI:mysql:host=$host";
0076 }
0077
0078 my $dbh = DBI->connect($dsn, $user, $pass, $db_opts)
0079 or die "Database connection failed, $DBI::errstr";
0080
0081 $this->{host} = $host;
0082 $this->{user} = $user;
0083 $this->{db} = $db;
0084 $this->{db_opts} = $db_opts;
0085 $this->{dbh} = $dbh;
0086
0087 return 1;
0088 }
0089
0090 sub begin_work {
0091 my $this = shift;
0092 $this->{dbh}->begin_work();
0093 $this->{transaction} = 1;
0094 }
0095
0096 sub commit {
0097 my $this = shift;
0098 $this->{dbh}->commit();
0099 $this->{transaction} = 0;
0100 }
0101
0102 sub rollback {
0103 my $this = shift;
0104 $this->{dbh}->rollback();
0105 $this->{transaction} = 0;
0106 }
0107
0108
0109 sub newdb {
0110 my $this = shift;
0111 my $dbh = $this->ensure_connect();
0112
0113 my %args = @_;
0114 my $name = $args{-name} or die "ERROR: Must give name to newdb(), $!";
0115 $dbh->do() or die "ERROR: DB creation failed, ".
0116 $dbh->errstr;
0117 $dbh->do();
0118
0119 my $sql =<<END_SQL;
0120 CREATE TABLE conditionDescription (
0121 name varchar($MAXNAME) NOT NULL,
0122 description text,
0123 units varchar(255) default NULL,
0124 datatype char(1) NOT NULL,
0125 datasize int NOT NULL default '1',
0126 hasError tinyint(1) NOT NULL default '0',
0127 PRIMARY KEY (name)
0128 ) TYPE=InnoDB;
0129 END_SQL
0130
0131 $dbh->do($sql);
0132
0133 $sql =<<END_SQL;
0134 CREATE TABLE viewDescription (
0135 name varchar($MAXNAME) NOT NULL,
0136 description text,
0137 id1name varchar($MAXNAME) default NULL,
0138 id2name varchar($MAXNAME) default NULL,
0139 id3name varchar($MAXNAME) default NULL,
0140 PRIMARY KEY (name)
0141 ) TYPE=InnoDB;
0142 END_SQL
0143
0144 $dbh->do($sql);
0145
0146 $sql =<<END_SQL;
0147 CREATE TABLE channelView (
0148 name varchar($MAXNAME) NOT NULL,
0149 id1 int(11) default NULL,
0150 id2 int(11) default NULL,
0151 id3 int(11) default NULL,
0152 maps_to varchar($MAXNAME) NOT NULL,
0153 logic_id int(11) NOT NULL,
0154 UNIQUE (name, id1, id2, id3, logic_id),
0155 INDEX maps_to (maps_to),
0156 INDEX logic_id (logic_id)
0157 ) TYPE=InnoDB;
0158 END_SQL
0159 $dbh->do($sql);
0160
0161 $sql = <<END_SQL;
0162 CREATE TABLE runs (
0163 run_number INT PRIMARY KEY,
0164 since DATETIME,
0165 till DATETIME,
0166 status TINYINT DEFAULT 1,
0167 comments TEXT,
0168 INDEX (since, till)
0169 ) Type=InnoDB
0170 END_SQL
0171
0172 $dbh->do($sql);
0173
0174 $this->{db} = $name;
0175 return 1;
0176 }
0177
0178
0179 sub destroydb {
0180 my $this = shift;
0181 my $dbh = $this->ensure_connect();
0182
0183 my %args = @_;
0184 my $name = $args{-name} or die "Must give name to newdb(), $!";
0185
0186 $dbh->do()
0187 or die "destroydb failed, ".$dbh->errstr;
0188
0189 return 1;
0190 }
0191
0192
0193 sub selectdb {
0194 my $this = shift;
0195 my $dbh = $this->ensure_connect();
0196
0197 my %args = @_;
0198 my $name = $args{-name} or die "Must give name to newdb(), $!";
0199
0200 $dbh->do();
0201 }
0202
0203
0204
0205 sub new_channelView_type {
0206 my $this = shift;
0207 my $dbh = $this->ensure_connect();
0208
0209 my %args = @_;
0210 my $name = $args{-name};
0211 my $description = $args{-description};
0212 my ($id1name, $id2name, $id3name) = @{$args{-idnames}};
0213
0214 check_names($name, $id1name, $id2name, $id3name);
0215
0216 my $sql;
0217 { no warnings;
0218 $sql = ;
0219 $sql =~ s/\"\"/NULL/g;
0220 }
0221 $dbh->do($sql);
0222
0223 { no warnings;
0224 $sql = ;
0225 $sql =~ s/\"\"/NULL/g;
0226 }
0227 $dbh->do($sql);
0228
0229 { no warnings;
0230 $sql =
0231
0232
0233
0234
0235 ;
0236 $sql =~ s/\"\"/NULL/g;
0237 }
0238 $dbh->do($sql);
0239
0240 return 1;
0241 }
0242
0243
0244 sub new_cond_type {
0245 my $this = shift;
0246 my $dbh = $this->ensure_connect();
0247
0248 my %args = @_;
0249 my $name = $args{-name} or die "ERROR: -name required, $!";
0250 my $description = $args{-description};
0251 my $datasize = $args{-datasize};
0252 my $units = $args{-units};
0253 my $datatype = $args{-datatype} or die "ERROR: -datatype required, $!";
0254 my $hasError = $args{-hasError};
0255 my $ddl = $args{-ddl};
0256
0257 $name = "COND_".$name unless $name =~ /^COND_/;
0258 check_names($name);
0259
0260 $datasize = 1 unless defined $datasize;
0261 $hasError = 0 unless defined $hasError;
0262
0263 if ($datasize < 1 || $datasize >= 100) {
0264 die "ERROR: datasize is out of range [1, 99], $!\n";
0265 }
0266
0267 my $typecode;
0268 my $mysqltype;
0269 if ($datatype =~ /float/) {
0270 $typecode = "f";
0271 $mysqltype = "float";
0272 } elsif ($datatype =~ /int/) {
0273 $typecode = "i";
0274 $mysqltype = "int";
0275 } elsif ($datatype =~ /string/) {
0276 $typecode = "s";
0277 $mysqltype = "varchar(255)";
0278 } else {
0279 die "ERROR: unknown datatype, $!";
0280 }
0281
0282 my $sql;
0283
0284 { no warnings;
0285 $sql = ;
0286 }
0287 $dbh->do($sql);
0288
0289 { no warnings;
0290 $sql = ;
0291 }
0292 $dbh->do($sql);
0293
0294
0295 { no warnings;
0296 $sql =
0297
0298 ;
0299 if (!$hasError) {
0300 for (0..$datasize-1) {
0301 $sql .= sprintf "value%02d $mysqltype,", $_;
0302 }
0303 } else {
0304 for (0..$datasize-1) {
0305 $sql .= sprintf "value%02d $mysqltype, error%02d $mysqltype,", $_, $_;
0306 }
0307 }
0308 $sql .=
0309
0310 ;
0311 }
0312 $dbh->do($sql);
0313 $ddl = $sql if $ddl;
0314 { no warnings;
0315 $sql =
0316
0317
0318
0319
0320
0321 ;
0322 $sql =~ s/\"\"/NULL/g;
0323 }
0324 $dbh->do($sql);
0325
0326 if ($ddl) { return $ddl; }
0327 else { return 1; }
0328 }
0329
0330
0331 sub insert_channel {
0332 my $this = shift;
0333 my $dbh = $this->ensure_connect();
0334
0335 my %args = @_;
0336
0337 my $name = $args{-name};
0338 my ($id1, $id2, $id3) = @{ $args{-channel_ids} };
0339 my $maps_to = $args{-maps_to};
0340 my $logic_id = $args{-logic_id};
0341
0342
0343 $maps_to = $name unless $maps_to;
0344
0345
0346
0347 my $sql;
0348 $sql =
0349
0350
0351
0352
0353
0354 ;
0355
0356 my $sth = $dbh->prepare_cached($sql);
0357 $sth->execute($name, $id1, $id2, $id3, $maps_to, $logic_id);
0358
0359 return 1;
0360 }
0361
0362
0363 sub insert_condition {
0364 my $this = shift;
0365 my $dbh = $this->ensure_connect();
0366
0367 my %args = @_;
0368
0369 foreach () {
0370 unless (defined $args{$_}) {
0371 die "ERROR: $_ required, $!";
0372 }
0373 }
0374
0375 my $name = $args{-name};
0376 my $logic_id = $args{-logic_id};
0377 my $IoV = $args{-IoV};
0378 my $value = $args{-value};
0379 my $error = $args{-error};
0380 my $values = $args{-values};
0381 my $errors = $args{-errors};
0382 my $hasError = (defined $error || defined $errors);
0383
0384 $name = "COND_".$name unless $name =~ /^COND_/;
0385
0386 if ((defined $value && defined $values) ||
0387 (defined $error && defined $errors)) {
0388 die "ERROR: defined input of both scalar and array\n";
0389 }
0390
0391
0392 unless (check_iov($IoV)) {
0393 die "ERROR: IoV ($IoV->{since}, $IoV->{till}) fails validation\n";
0394 }
0395
0396
0397 if ($this->{check_overlap}) {
0398 my $overlap = $this->is_overlap(-name=>$name,
0399 -logic_id=>$logic_id,
0400 -IoV=>$IoV);
0401 if ($overlap) {
0402 die "ERROR: overlapping condition:\n", $overlap;
0403 }
0404 }
0405
0406
0407 unless (exists $this->{prepared}->{$name}) {
0408 $this->prepare_cached(-name=>$name);
0409 }
0410
0411 if ($this->{counter} == 0) { $this->begin_work(); }
0412
0413 my @vars = ($logic_id, $IoV->{since}, $IoV->{till});
0414 if (defined $value) {
0415 push @vars, $value;
0416 push @vars, $error if $hasError;
0417 } elsif (defined $values && !defined $errors) {
0418 push @vars, @{$values};
0419 } elsif (defined $values && defined $errors) {
0420 my $num_vals = scalar @{$values};
0421 my $num_errs = scalar @{$errors};
0422 unless ($num_vals == $num_errs) {
0423 die "ERROR: Number of values different than number of errors, $!";
0424 }
0425 for (0..$num_vals-1) {
0426 push @vars, shift @{$values}, shift @{$errors};
0427 }
0428 } else {
0429 die "ERROR: undefined data input\n";
0430 }
0431
0432
0433
0434 my $sth = $this->{prepared}->{$name};
0435 $sth->execute(@vars);
0436
0437
0438 $this->{counter}++;
0439 if ($this->{counter} >= $this->{max_cnt}) {
0440 $this->commit();
0441 $this->{counter} = 0;
0442 }
0443 return 1;
0444 }
0445
0446
0447 sub insert_run {
0448 my $this = shift;
0449 my $dbh = $this->{dbh};
0450
0451 my %args = @_;
0452 my $run_number = $args{-run_number};
0453 my $IoV = $args{-IoV};
0454
0455 unless (defined $run_number) {
0456 die "ERROR: insert_run needs -run_number\n";
0457 }
0458
0459
0460 unless (check_iov($IoV)) {
0461 die "ERROR: IoV ($IoV->{since}, $IoV->{till}) fails validation\n";
0462 }
0463
0464 my $sql = ;
0465 my $insert = $dbh->prepare_cached($sql);
0466
0467 $insert->execute($run_number, $IoV->{since}, $IoV->{till});
0468
0469 return 1;
0470 }
0471
0472
0473 sub update_run {
0474 my $this = shift;
0475
0476 my %args = @_;
0477
0478 my $run = $args{-run_number};
0479 my $status = $args{-status};
0480 my $comments = $args{-comments};
0481
0482 unless (defined $run && defined $status) {
0483 die "Need to at least send a status to update_run. skipping.\n";
0484 }
0485
0486 unless (defined $comments) {
0487 $comments = "NULL";
0488 }
0489
0490 my $sql =
0491 ;
0492
0493 $this->{dbh}->do($sql);
0494 return 1;
0495 }
0496
0497 sub prepare_cached {
0498 my $this = shift;
0499 my $dbh = $this->ensure_connect();
0500
0501 my %args = @_;
0502
0503 unless (defined $args{'-name'}) {
0504 die "ERROR: $_ required, $!";
0505 }
0506
0507 my $name = $args{-name};
0508
0509 $name = "COND_".$name unless $name =~ /^COND_/;
0510 my $desc = $this->get_conditionDescription(-name=>$name);
0511 unless (defined $desc) {
0512 die "ERROR: Condition $name is not defined in the DB\n";
0513 }
0514 my $hasError = $desc->{hasError};
0515 my $datasize = $desc->{datasize};
0516
0517 my $sql;
0518 { no warnings;
0519 $sql =
0520
0521
0522 ;
0523 my @fields;
0524 for (0..$datasize-1) {
0525 push @fields, sprintf("value%02d=?", $_);
0526 if ($hasError) {
0527 push @fields, sprintf("error%02d=?", $_);
0528 }
0529 }
0530 $sql .= join ',', @fields;
0531 }
0532 my $sth = $dbh->prepare_cached($sql);
0533 $this->{prepared}->{$name} = $sth;
0534
0535 return 1;
0536 }
0537
0538
0539
0540 sub is_overlap {
0541 my $this = shift;
0542 my $dbh = $this->ensure_connect();
0543
0544 my %args = @_;
0545 my $name = $args{-name};
0546 my $logic_id = $args{-logic_id};
0547 my $IoV = $args{-IoV};
0548
0549 my $t1 = $IoV->{since};
0550 my $t2 = $IoV->{till};
0551
0552 my $sql;
0553
0554 unless (exists $this->{prepared_overlap}->{$name}) {
0555 $this->prepare_overlap_check(-name=>$name);
0556 }
0557
0558 my $sth = $this->{prepared_overlap}->{$name};
0559 $sth->execute($logic_id, $t1, $t2, $t1, $t2, $t1, $t2);
0560
0561 my ($db_id, $db_t1, $db_t2) = $sth->fetchrow_array();
0562
0563 if ($db_id) {
0564 my $in_str = "input: ". join ' ', $logic_id, $t1, $t2;
0565 my $db_str = " db: ". join ' ', $db_id, $db_t1, $db_t2;
0566 return "$in_str\n$db_str";
0567 } else {
0568 return 0;
0569 }
0570 }
0571
0572 sub prepare_overlap_check {
0573 my $this = shift;
0574 my $dbh = $this->ensure_connect();
0575
0576 my %args = @_;
0577
0578 unless (defined $args{'-name'}) {
0579 die "ERROR: $_ required, $!";
0580 }
0581
0582 my $name = $args{-name};
0583
0584 $name = "COND_".$name unless $name =~ /^COND_/;
0585
0586 my $sql;
0587 { no warnings;
0588
0589 $sql =
0590
0591
0592
0593
0594
0595 ;
0596 }
0597
0598
0599
0600
0601
0602
0603
0604
0605
0606
0607
0608
0609
0610
0611
0612
0613 my $sth = $dbh->prepare_cached($sql);
0614 $this->{prepared_overlap}->{$name} = $sth;
0615
0616 return 1;
0617 }
0618
0619
0620
0621
0622
0623
0624
0625 sub get_condition {
0626 my $this = shift;
0627 my $dbh = $this->ensure_connect();
0628
0629 my %args = @_;
0630 my $name = $args{-name};
0631 my $logic_id = $args{-logic_id};
0632 my $time = $args{-time};
0633
0634 $name = "COND_".$name unless $name =~ /^COND_/;
0635
0636 my @fields;
0637 $name = "COND_".$name unless $name =~ /^COND_/;
0638 my $desc = $this->get_conditionDescription(-name=>$name);
0639 unless (defined $desc) {
0640 die "ERROR: Condition $name is not defined in the DB\n";
0641 }
0642 my $hasError = $desc->{hasError};
0643 my $datasize = $desc->{datasize};
0644
0645 for (0..$datasize-1) {
0646 push @fields, sprintf("value%02d", $_);
0647 if ($hasError) {
0648 push @fields, sprintf("error%02d", $_);
0649 }
0650 }
0651 my $fields = join ',', @fields;
0652
0653 my $sql;
0654 { no warnings;
0655 $sql =
0656
0657
0658 ;
0659 }
0660
0661 my @results = @{$dbh->selectall_arrayref($sql)};
0662 if (scalar @results > 1) {
0663 warn "ERROR: Overlapping IoV found in table $name, logic_id, $logic_id, ".
0664 "time $time";
0665 }
0666
0667 if (scalar @{$results[0]} == 1) {
0668 return $results[0][0];
0669 } else {
0670 return @{$results[0]};
0671 }
0672 }
0673
0674
0675 sub get_channelView {
0676 my $this = shift;
0677 my $dbh = $this->ensure_connect();
0678
0679 my %args = @_;
0680 my $name = $args{-name};
0681 my $maps_to = $args{-maps_to};
0682
0683
0684 $maps_to = $name unless $maps_to;
0685
0686 my $sql;
0687 { no warnings;
0688 $sql =
0689 ;
0690 }
0691
0692
0693
0694
0695
0696
0697
0698
0699
0700
0701
0702
0703
0704
0705
0706
0707
0708
0709
0710
0711
0712
0713
0714 my $view = {};
0715 my @results = @{$dbh->selectall_arrayref($sql)};
0716
0717 if (scalar @results == 0) {
0718 return undef;
0719 }
0720
0721 foreach (@results) {
0722 my ($id1, $id2, $id3, $logic_id) = map {defined $_ ? $_ : ''} @{$_};
0723 $view->{$id1}->{$id2}->{$id3} = $logic_id;
0724 }
0725
0726 return $view;
0727 }
0728
0729
0730 sub get_channelView_logic_ids {
0731 my $this = shift;
0732 my $dbh = $this->ensure_connect();
0733
0734 my %args = @_;
0735 my $name = $args{-name};
0736 my $maps_to = $args{-maps_to};
0737
0738
0739 $maps_to = $name unless $maps_to;
0740
0741 my $sql;
0742 $sql =
0743 ;
0744 return @{$dbh->selectcol_arrayref($sql)};
0745 }
0746
0747
0748 sub hasError {
0749 my $this = shift;
0750 my $dbh = $this->ensure_connect();
0751
0752 my %args = @_;
0753 my $name = $args{-name};
0754
0755 my $desc = $this->get_conditionDescription(-name=>$name);
0756 return $desc->{hasError};
0757 }
0758
0759
0760 sub get_conditionDescription {
0761 my $this = shift;
0762 my $dbh = $this->ensure_connect();
0763
0764 my %args = @_;
0765 my $name = $args{-name};
0766
0767 my $sql;
0768 { no warnings;
0769 $sql = ;
0770 }
0771
0772 return $dbh->selectrow_hashref($sql);
0773 }
0774
0775
0776 sub get_view_description {
0777 }
0778
0779
0780 sub ensure_connect {
0781 my $this = shift;
0782
0783 unless (exists $this->{dbh} && defined $this->{dbh}) {
0784 die "ERROR: Not connected to database.\n";
0785 }
0786
0787
0788
0789 return $this->{dbh};
0790 }
0791
0792
0793
0794
0795
0796 sub check_names {
0797 no warnings;
0798 foreach (@_) {
0799 my $count = length $_;
0800 if ($count > $MAXNAME) {
0801 die "ERROR: Name \"$_\" is too long. Names for conditions and ids ".
0802 "can only be $MAXNAME characters long\n";
0803 }
0804 }
0805 }
0806
0807 sub check_date {
0808 my $date = shift;
0809
0810 return 0 unless defined $date;
0811
0812 my @date = ($date =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
0813
0814 foreach (@date) {
0815 return 0 unless defined $_;
0816 }
0817
0818 if ($date[0] < 0 || $date[0] > 9999 ||
0819 $date[1] < 1 || $date[1] > 12 ||
0820 $date[2] < 1 || $date[2] > 31 ||
0821 $date[3] < 0 || $date[3] > 23 ||
0822 $date[4] < 0 || $date[4] > 59 ||
0823 $date[5] < 0 || $date[5] > 59) {
0824 return 0;
0825 }
0826
0827 return 1;
0828 }
0829
0830 sub check_iov {
0831 my $IoV = shift;
0832 return (check_date($IoV->{since}) &&
0833 check_date($IoV->{till}) &&
0834 ($IoV->{since} lt $IoV->{till})
0835 );
0836 }
0837
0838 1;