1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22class DB { 23 24 const SCHEMA_FILE = 'schema.inc.php'; 25 26 const DBEXECUTE_ERROR = 1; 27 const RESERVEIDS_ERROR = 2; 28 const SCHEMA_ERROR = 3; 29 const INPUT_ERROR = 4; 30 31 const TABLE_TYPE_CONFIG = 1; 32 const TABLE_TYPE_HISTORY = 2; 33 34 const FIELD_TYPE_INT = 'int'; 35 const FIELD_TYPE_CHAR = 'char'; 36 const FIELD_TYPE_ID = 'id'; 37 const FIELD_TYPE_FLOAT = 'float'; 38 const FIELD_TYPE_UINT = 'uint'; 39 const FIELD_TYPE_BLOB = 'blob'; 40 const FIELD_TYPE_TEXT = 'text'; 41 42 private static $schema = null; 43 44 /** 45 * @var DbBackend 46 */ 47 private static $dbBackend; 48 49 /** 50 * Get necessary DB class. 51 * 52 * @return DbBackend 53 */ 54 public static function getDbBackend() { 55 global $DB; 56 57 if (!self::$dbBackend) { 58 switch ($DB['TYPE']) { 59 case ZBX_DB_MYSQL: 60 self::$dbBackend = new MysqlDbBackend(); 61 break; 62 case ZBX_DB_POSTGRESQL: 63 self::$dbBackend = new PostgresqlDbBackend(); 64 break; 65 case ZBX_DB_ORACLE: 66 self::$dbBackend = new OracleDbBackend(); 67 break; 68 case ZBX_DB_DB2: 69 self::$dbBackend = new Db2DbBackend(); 70 break; 71 case ZBX_DB_SQLITE3: 72 self::$dbBackend = new SqliteDbBackend(); 73 break; 74 } 75 } 76 77 return self::$dbBackend; 78 } 79 80 private static function exception($code, $error) { 81 throw new DBException($error, $code); 82 } 83 84 /** 85 * Reserve ids for primary key of passed table. 86 * If record for table does not exist or value is out of range, ids record is created 87 * using maximum id from table or minimum allowed value. 88 * 89 * @throw APIException 90 * 91 * @static 92 * 93 * @param string $table table name 94 * @param int $count number of ids to reserve 95 * 96 * @return string 97 */ 98 protected static function reserveIds($table, $count) { 99 global $DB; 100 101 $tableSchema = self::getSchema($table); 102 $id_name = $tableSchema['key']; 103 104 $sql = 'SELECT nextid'. 105 ' FROM ids'. 106 ' WHERE table_name='.zbx_dbstr($table). 107 ' AND field_name='.zbx_dbstr($id_name); 108 109 // SQLite3 does not support this syntax. Since we are in transaction, it can be ignored. 110 if ($DB['TYPE'] != ZBX_DB_SQLITE3) { 111 $sql = $sql.' FOR UPDATE'; 112 } 113 114 $res = DBfetch(DBselect($sql)); 115 116 if ($res) { 117 $maxNextId = bcadd($res['nextid'], $count, 0); 118 119 if (bccomp($maxNextId, ZBX_DB_MAX_ID) == 1) { 120 $nextid = self::refreshIds($table, $count); 121 } 122 else { 123 $sql = 'UPDATE ids'. 124 ' SET nextid='.$maxNextId. 125 ' WHERE table_name='.zbx_dbstr($table). 126 ' AND field_name='.zbx_dbstr($id_name); 127 128 if (!DBexecute($sql)) { 129 self::exception(self::DBEXECUTE_ERROR, 'DBEXECUTE_ERROR'); 130 } 131 132 $nextid = bcadd($res['nextid'], 1, 0); 133 } 134 } 135 136 /* 137 * Detect either the query is executable at all? If query is valid and schema is correct but query still cannot 138 * be executed, then there is a good chance that previous transaction has left row level lock unreleased or it 139 * is still running. In such a case execution must be stopped, otherwise it will call self::refreshIds method. 140 */ 141 elseif (!DBexecute($sql)) { 142 self::exception(self::DBEXECUTE_ERROR, 143 _('Your database is not working properly. Please wait a few minutes and try to repeat this action. If the problem still persists, please contact system administrator. The problem might be caused by long running transaction or row level lock accomplished by your database management system.') 144 ); 145 } 146 // If query is executable, but still returns false, only then call refreshIds. 147 else { 148 $nextid = self::refreshIds($table, $count); 149 } 150 151 return $nextid; 152 } 153 154 /** 155 * Refresh id record for given table. 156 * Record is deleted and then created again with value of maximum id from table or minimum allowed. 157 * 158 * @throw APIException 159 * 160 * @static 161 * 162 * @param string $table table name 163 * @param int $count number of ids to reserve 164 * 165 * @return string 166 */ 167 private static function refreshIds($table, $count) { 168 $tableSchema = self::getSchema($table); 169 $id_name = $tableSchema['key']; 170 171 // when we reach the maximum ID, we try to refresh them to check if any IDs have been freed 172 $sql = 'DELETE FROM ids WHERE table_name='.zbx_dbstr($table).' AND field_name='.zbx_dbstr($id_name); 173 174 if (!DBexecute($sql)) { 175 self::exception(self::DBEXECUTE_ERROR, 'DBEXECUTE_ERROR'); 176 } 177 178 $row = DBfetch(DBselect('SELECT MAX('.$id_name.') AS id FROM '.$table)); 179 180 $nextid = ($row && $row['id']) ? $row['id'] : 0; 181 182 $maxNextId = bcadd($nextid, $count, 0); 183 184 if (bccomp($maxNextId, ZBX_DB_MAX_ID) == 1) { 185 self::exception( 186 self::RESERVEIDS_ERROR, __METHOD__.' ID greater than maximum allowed for table "'.$table.'"' 187 ); 188 } 189 190 $sql = 'INSERT INTO ids (table_name,field_name,nextid)'. 191 ' VALUES ('.zbx_dbstr($table).','.zbx_dbstr($id_name).','.$maxNextId.')'; 192 193 if (!DBexecute($sql)) { 194 self::exception(self::DBEXECUTE_ERROR, 'DBEXECUTE_ERROR'); 195 } 196 197 $nextid = bcadd($nextid, 1, 0); 198 199 return $nextid; 200 } 201 202 /** 203 * Returns the array describing the database schema. 204 * 205 * If the $table parameter is passed, the method will return the schema for the given table, 206 * otherwise - for the whole database. 207 * 208 * @static 209 * 210 * @throws APIException if the given table does not exist 211 * 212 * @param string $table 213 * 214 * @return array 215 */ 216 public static function getSchema($table = null) { 217 if (is_null(self::$schema)) { 218 self::$schema = include(dirname(__FILE__).'/../../'.self::SCHEMA_FILE); 219 } 220 221 if (is_null($table)) { 222 return self::$schema; 223 } 224 elseif (isset(self::$schema[$table])) { 225 return self::$schema[$table]; 226 } 227 else { 228 self::exception(self::SCHEMA_ERROR, _s('Table "%1$s" does not exist.', $table)); 229 } 230 } 231 232 /** 233 * Returns the names of the fields that are used as the primary key of the table. 234 * 235 * @static 236 * 237 * @param string $tableName 238 * 239 * @return string|array 240 */ 241 protected static function getPk($tableName) { 242 $schema = self::getSchema($tableName); 243 244 return $schema['key']; 245 } 246 247 /** 248 * Returns true if the table $tableName has the $fieldName field. 249 * 250 * @static 251 * 252 * @param string $tableName 253 * @param string $fieldName 254 * 255 * @return bool 256 */ 257 public static function hasField($tableName, $fieldName) { 258 $schema = self::getSchema($tableName); 259 260 return isset($schema['fields'][$fieldName]); 261 } 262 263 private static function addMissingFields($tableSchema, $values) { 264 global $DB; 265 266 if ($DB['TYPE'] == ZBX_DB_MYSQL) { 267 foreach ($tableSchema['fields'] as $name => $field) { 268 if ($field['type'] == DB::FIELD_TYPE_TEXT && !$field['null']) { 269 foreach ($values as &$value) { 270 if (!isset($value[$name])) { 271 $value[$name] = ''; 272 } 273 } 274 unset($value); 275 } 276 } 277 } 278 279 return $values; 280 } 281 282 public static function getDefaults($table) { 283 $table = self::getSchema($table); 284 285 $defaults = []; 286 foreach ($table['fields'] as $name => $field) { 287 if (isset($field['default'])) { 288 $defaults[$name] = $field['default']; 289 } 290 } 291 return $defaults; 292 } 293 294 /** 295 * Returns the default value of the given field. 296 * 297 * @param string $table name of the table 298 * @param string $field name of the field 299 * 300 * @return string|null 301 */ 302 public static function getDefault($table, $field) { 303 $table = self::getSchema($table); 304 $field = $table['fields'][$field]; 305 306 return isset($field['default']) ? $field['default'] : null; 307 } 308 309 public static function checkValueTypes($table, &$values) { 310 global $DB; 311 $tableSchema = self::getSchema($table); 312 313 foreach ($values as $field => $value) { 314 if (!isset($tableSchema['fields'][$field])) { 315 unset($values[$field]); 316 continue; 317 } 318 319 if (isset($tableSchema['fields'][$field]['ref_table'])) { 320 if ($tableSchema['fields'][$field]['null']) { 321 $values[$field] = ($values[$field] == '0') ? NULL : $values[$field]; 322 } 323 } 324 325 if (is_null($values[$field])) { 326 if ($tableSchema['fields'][$field]['null']) { 327 $values[$field] = 'NULL'; 328 } 329 elseif (isset($tableSchema['fields'][$field]['default'])) { 330 $values[$field] = zbx_dbstr($tableSchema['fields'][$field]['default']); 331 } 332 else { 333 self::exception(self::DBEXECUTE_ERROR, 334 _s('Field "%1$s" cannot be set to NULL.', $field) 335 ); 336 } 337 } 338 else { 339 switch ($tableSchema['fields'][$field]['type']) { 340 case self::FIELD_TYPE_CHAR: 341 $length = mb_strlen($values[$field]); 342 343 if ($length > $tableSchema['fields'][$field]['length']) { 344 self::exception(self::SCHEMA_ERROR, _s('Value "%1$s" is too long for field "%2$s" - %3$d characters. Allowed length is %4$d characters.', 345 $values[$field], $field, $length, $tableSchema['fields'][$field]['length'])); 346 } 347 $values[$field] = zbx_dbstr($values[$field]); 348 break; 349 case self::FIELD_TYPE_ID: 350 case self::FIELD_TYPE_UINT: 351 if (!zbx_ctype_digit($values[$field])) { 352 self::exception(self::DBEXECUTE_ERROR, _s('Incorrect value "%1$s" for unsigned int field "%2$s".', $values[$field], $field)); 353 } 354 $values[$field] = zbx_dbstr($values[$field]); 355 break; 356 case self::FIELD_TYPE_INT: 357 if (!zbx_is_int($values[$field])) { 358 self::exception(self::DBEXECUTE_ERROR, _s('Incorrect value "%1$s" for int field "%2$s".', $values[$field], $field)); 359 } 360 $values[$field] = zbx_dbstr($values[$field]); 361 break; 362 case self::FIELD_TYPE_FLOAT: 363 if (!is_numeric($values[$field])) { 364 self::exception(self::DBEXECUTE_ERROR, _s('Incorrect value "%1$s" for float field "%2$s".', $values[$field], $field)); 365 } 366 $values[$field] = zbx_dbstr($values[$field]); 367 break; 368 case self::FIELD_TYPE_TEXT: 369 if ($DB['TYPE'] == ZBX_DB_DB2 || $DB['TYPE'] == ZBX_DB_ORACLE) { 370 $length = mb_strlen($values[$field]); 371 372 if ($length > 2048) { 373 self::exception(self::SCHEMA_ERROR, _s('Value "%1$s" is too long for field "%2$s" - %3$d characters. Allowed length is 2048 characters.', 374 $values[$field], $field, $length)); 375 } 376 } 377 $values[$field] = zbx_dbstr($values[$field]); 378 break; 379 } 380 } 381 } 382 } 383 384 /** 385 * Returns the records that match the given criteria. 386 * 387 * @static 388 * 389 * @param string $tableName 390 * @param array $criteria An associative array of field-value pairs, where value can be either a single value 391 * or an array (IN) 392 * 393 * @return array 394 */ 395 public static function find($tableName, array $criteria = []) { 396 // build the WHERE part 397 $sqlWhere = []; 398 foreach ($criteria as $field => $value) { 399 // check if the table has this field 400 if (!self::hasField($tableName, $field)) { 401 self::exception(self::DBEXECUTE_ERROR, _s('Table "%1$s" doesn\'t have a field named "%2$s".', $tableName, $field)); 402 } 403 404 $sqlWhere[] = dbConditionString($field, zbx_toArray($value)); 405 } 406 407 // build query 408 $sql = 'SELECT * FROM '.$tableName; 409 if ($sqlWhere) { 410 $sql .= ' WHERE '.implode(' AND ', $sqlWhere); 411 } 412 413 return DBfetchArray(DBSelect($sql)); 414 } 415 416 /** 417 * Insert data into DB. 418 * 419 * @param string $table 420 * @param array $values pair of fieldname => fieldvalue 421 * @param bool $getids 422 * 423 * @return array an array of ids with the keys preserved 424 */ 425 public static function insert($table, $values, $getids = true) { 426 if (empty($values)) { 427 return true; 428 } 429 430 $resultIds = []; 431 432 if ($getids) { 433 $id = self::reserveIds($table, count($values)); 434 } 435 436 $tableSchema = self::getSchema($table); 437 438 $values = self::addMissingFields($tableSchema, $values); 439 440 foreach ($values as $key => $row) { 441 if ($getids) { 442 $resultIds[$key] = $id; 443 $row[$tableSchema['key']] = $id; 444 $id = bcadd($id, 1, 0); 445 } 446 447 self::checkValueTypes($table, $row); 448 449 $sql = 'INSERT INTO '.$table.' ('.implode(',', array_keys($row)).')'. 450 ' VALUES ('.implode(',', array_values($row)).')'; 451 452 if (!DBexecute($sql)) { 453 self::exception(self::DBEXECUTE_ERROR, _s('SQL statement execution has failed "%1$s".', $sql)); 454 } 455 } 456 457 return $resultIds; 458 } 459 460 /** 461 * Insert batch data into DB. 462 * 463 * @param string $table 464 * @param array $values pair of fieldname => fieldvalue 465 * @param bool $getids 466 * 467 * @return array an array of ids with the keys preserved 468 */ 469 public static function insertBatch($table, $values, $getids = true) { 470 if (empty($values)) { 471 return true; 472 } 473 474 $resultIds = []; 475 476 $tableSchema = self::getSchema($table); 477 $values = self::addMissingFields($tableSchema, $values); 478 479 if ($getids) { 480 $id = self::reserveIds($table, count($values)); 481 } 482 483 $newValues = []; 484 foreach ($values as $key => $row) { 485 if ($getids) { 486 $resultIds[$key] = $id; 487 $row[$tableSchema['key']] = $id; 488 $values[$key][$tableSchema['key']] = $id; 489 $id = bcadd($id, 1, 0); 490 } 491 self::checkValueTypes($table, $row); 492 $newValues[] = $row; 493 } 494 495 $fields = array_keys(reset($newValues)); 496 497 $sql = self::getDbBackend()->createInsertQuery($table, $fields, $newValues); 498 499 if (!DBexecute($sql)) { 500 self::exception(self::DBEXECUTE_ERROR, _s('SQL statement execution has failed "%1$s".', $sql)); 501 } 502 503 return $resultIds; 504 } 505 506 /** 507 * Update data in DB. 508 * 509 * @param string $table 510 * @param array $data 511 * @param array $data[...]['values'] pair of fieldname => fieldvalue for SET clause 512 * @param array $data[...]['where'] pair of fieldname => fieldvalue for WHERE clause 513 * 514 * @return array of ids 515 */ 516 public static function update($table, $data) { 517 if (empty($data)) { 518 return true; 519 } 520 521 $tableSchema = self::getSchema($table); 522 523 $data = zbx_toArray($data); 524 foreach ($data as $row) { 525 // check 526 self::checkValueTypes($table, $row['values']); 527 if (empty($row['values'])) { 528 self::exception(self::DBEXECUTE_ERROR, _s('Cannot perform update statement on table "%1$s" without values.', $table)); 529 } 530 531 // set creation 532 $sqlSet = ''; 533 foreach ($row['values'] as $field => $value) { 534 $sqlSet .= ' '.$field.'='.$value.','; 535 } 536 $sqlSet = rtrim($sqlSet, ','); 537 538 if (!isset($row['where']) || empty($row['where']) || !is_array($row['where'])) { 539 self::exception(self::DBEXECUTE_ERROR, _s('Cannot perform update statement on table "%1$s" without where condition.', $table)); 540 } 541 542 // where condition processing 543 $sqlWhere = []; 544 foreach ($row['where'] as $field => $values) { 545 if (!isset($tableSchema['fields'][$field]) || is_null($values)) { 546 self::exception(self::DBEXECUTE_ERROR, _s('Incorrect field "%1$s" name or value in where statement for table "%2$s".', $field, $table)); 547 } 548 $values = zbx_toArray($values); 549 sort($values); // sorting ids to prevent deadlocks when two transactions depend on each other 550 551 $sqlWhere[] = dbConditionString($field, $values); 552 } 553 554 // sql execution 555 $sql = 'UPDATE '.$table.' SET '.$sqlSet.' WHERE '.implode(' AND ', $sqlWhere); 556 if (!DBexecute($sql)) { 557 self::exception(self::DBEXECUTE_ERROR, _s('SQL statement execution has failed "%1$s".', $sql)); 558 } 559 } 560 561 return true; 562 } 563 564 /** 565 * Updates the values by the given PK. 566 * 567 * @static 568 * 569 * @param string $tableName 570 * @param string $pk 571 * @param array $values 572 * 573 * @return bool 574 */ 575 public static function updateByPk($tableName, $pk, array $values) { 576 return self::update($tableName, [ 577 'where' => [self::getPk($tableName) => $pk], 578 'values' => $values 579 ]); 580 } 581 582 /** 583 * Saves the given records to the database. If the record has the primary key set, it is updated, otherwise - a new 584 * record is inserted. For new records the newly generated PK is added to the result. 585 * 586 * @static 587 * 588 * @param $tableName 589 * @param $data 590 * 591 * @return array the same records, that have been passed with the primary keys set for new records 592 */ 593 public static function save($tableName, array $data) { 594 $pk = self::getPk($tableName); 595 596 $newRecords = []; 597 foreach ($data as $key => $record) { 598 // if the pk is set - update the record 599 if (isset($record[$pk])) { 600 self::updateByPk($tableName, $record[$pk], $record); 601 } 602 // if no pk is set, create the record later 603 else { 604 $newRecords[$key] = $data[$key]; 605 } 606 } 607 608 // insert the new records 609 if ($newRecords) { 610 $newIds = self::insert($tableName, $newRecords); 611 foreach ($newIds as $key => $id) { 612 $data[$key][$pk] = $id; 613 } 614 } 615 616 return $data; 617 } 618 619 /** 620 * Replaces the records given in $oldRecords with the ones in $newRecords. 621 * 622 * If a record with the same primary key as a new one already exists in the old records, the record is updated 623 * only if they are different. For new records the newly generated PK is added to the result. Old records that are 624 * not present in the new records are deleted. 625 * 626 * All of the records must have the primary key defined. 627 * 628 * @static 629 * 630 * @param $tableName 631 * @param array $oldRecords 632 * @param array $newRecords 633 * 634 * @return array the new records, that have been passed with the primary keys set for newly inserted records 635 */ 636 public static function replace($tableName, array $oldRecords, array $newRecords) { 637 $pk = self::getPk($tableName); 638 $oldRecords = zbx_toHash($oldRecords, $pk); 639 640 $modifiedRecords = []; 641 foreach ($newRecords as $key => $record) { 642 // if it's a new or modified record - save it later 643 if (!isset($record[$pk]) || self::recordModified($tableName, $oldRecords[$record[$pk]], $record)) { 644 $modifiedRecords[$key] = $record; 645 } 646 647 // remove the existing records from the collection, the remaining ones will be deleted 648 if(isset($record[$pk])) { 649 unset($oldRecords[$record[$pk]]); 650 } 651 } 652 653 // save modified records 654 if ($modifiedRecords) { 655 $modifiedRecords = self::save($tableName, $modifiedRecords); 656 657 // add the new IDs to the new records 658 foreach ($modifiedRecords as $key => $record) { 659 $newRecords[$key][$pk] = $record[$pk]; 660 } 661 } 662 663 // delete remaining records 664 if ($oldRecords) { 665 DB::delete($tableName, [ 666 $pk => array_keys($oldRecords) 667 ]); 668 } 669 670 return $newRecords; 671 } 672 673 /** 674 * Replaces the records given in $groupedOldRecords with the ones given in $groupedNewRecords. 675 * 676 * This method can be used to replace related objects in one-to-many relations. Both old and new records 677 * must be grouped by the ID of the record they belong to. The records will be matched by position, instead of 678 * the primary key as in DB::replace(). That is, the first new record will update the first old one, second new 679 * record - the second old one, etc. Since the records are matched by position, the new records should not contain 680 * primary keys. 681 * 682 * Example 1: 683 * $old = array(2 => array( array('gitemid' => 1, 'color' => 'FF0000') )); 684 * $new = array(2 => array( array('color' => '00FF00') )); 685 * var_dump(DB::replaceByPosition('items', $old, $new)); 686 * // array(array('gitemid' => 1, 'color' => '00FF00')) 687 * 688 * The new record updated the old one. 689 * 690 * Example 2: 691 * $old = array(2 => array( array('gitemid' => 1, 'color' => 'FF0000') )); 692 * $new = array( 693 * 2 => array( 694 * array('color' => '00FF00'), 695 * array('color' => '0000FF') 696 * ) 697 * ); 698 * var_dump(DB::replaceByPosition('items', $old, $new)); 699 * // array(array('gitemid' => 1, 'color' => '00FF00'), array('gitemid' => 2, 'color' => '0000FF')) 700 * 701 * The first record was updated, the second one - created. 702 * 703 * Example 3: 704 * $old = array( 705 * 2 => array( 706 * array('gitemid' => 1, 'color' => 'FF0000'), 707 * array('gitemid' => 2, 'color' => '0000FF') 708 * ) 709 * ); 710 * $new = array(2 => array( array('color' => '00FF00') )); 711 * var_dump(DB::replaceByPosition('items', $old, $new)); 712 * // array(array('gitemid' => 1, 'color' => '00FF00')) 713 * 714 * The first record was updated, the second one - deleted. 715 * 716 * @param string $tableName table to update 717 * @param array $groupedOldRecords grouped old records 718 * @param array $groupedNewRecords grouped new records 719 * 720 * @return array array of new records not grouped (!). 721 */ 722 public static function replaceByPosition($tableName, array $groupedOldRecords, array $groupedNewRecords) { 723 $pk = self::getPk($tableName); 724 725 $allOldRecords = []; 726 $allNewRecords = []; 727 foreach ($groupedNewRecords as $key => $newRecords) { 728 // if records exist for the parent object - replace them, otherwise create new records 729 if (isset($groupedOldRecords[$key])) { 730 $oldRecords = $groupedOldRecords[$key]; 731 732 // updated the records by position 733 $newRecords = self::mergeRecords($oldRecords, $newRecords, $pk); 734 735 foreach ($oldRecords as $record) { 736 $allOldRecords[] = $record; 737 } 738 } 739 740 foreach ($newRecords as $record) { 741 $allNewRecords[] = $record; 742 } 743 } 744 745 // replace the old records with the new ones 746 return self::replace($tableName, $allOldRecords, $allNewRecords); 747 } 748 749 /** 750 * Compares the fields, that are present in both records, and returns true if any of the values differ. 751 * 752 * @static 753 * @param $tableName 754 * @param array $oldRecord 755 * @param array $newRecord 756 * 757 * @return bool 758 */ 759 public static function recordModified($tableName, array $oldRecord, array $newRecord) { 760 foreach ($oldRecord as $field => $value) { 761 if (self::hasField($tableName, $field) 762 && isset($newRecord[$field]) 763 && (string) $value !== (string) $newRecord[$field]) { 764 return true; 765 } 766 } 767 768 return false; 769 } 770 771 /** 772 * Replace each record in $oldRecords with a corresponding record in $newRecords, but keep the old record IDs. 773 * The records are match by position, that is, the first new record, replaces the first old record and etc. 774 * If there are less $newRecords than $oldRecords, the remaining old records will be discarded. 775 * 776 * @param array $oldRecords array of old records 777 * @param array $newRecords array of new records 778 * @param string $pk name of the private key column 779 * 780 * @return array array of new records with the primary keys from the old ones 781 */ 782 protected static function mergeRecords(array $oldRecords, array $newRecords, $pk) { 783 $result = []; 784 foreach ($newRecords as $i => $record) { 785 if (isset($oldRecords[$i])) { 786 $record[$pk] = $oldRecords[$i][$pk]; 787 } 788 789 $result[] = $record; 790 } 791 792 return $result; 793 } 794 795 /** 796 * Delete data from DB. 797 * 798 * Example: 799 * DB::delete('applications', array('applicationid'=>array(1, 8, 6))); 800 * DELETE FROM applications WHERE applicationid IN (1, 8, 6) 801 * 802 * DB::delete('applications', array('applicationid'=>array(1), 'templateid'=array(10))); 803 * DELETE FROM applications WHERE applicationid IN (1) AND templateid IN (10) 804 * 805 * @param string $table 806 * @param array $wheres pair of fieldname => fieldvalues 807 * @param bool $use_or 808 * 809 * @return bool 810 */ 811 public static function delete($table, $wheres, $use_or = false) { 812 if (empty($wheres) || !is_array($wheres)) { 813 self::exception(self::DBEXECUTE_ERROR, _s('Cannot perform delete statement on table "%1$s" without where condition.', $table)); 814 } 815 $table_schema = self::getSchema($table); 816 817 $sqlWhere = []; 818 foreach ($wheres as $field => $values) { 819 if (!isset($table_schema['fields'][$field]) || is_null($values)) { 820 self::exception(self::DBEXECUTE_ERROR, _s('Incorrect field "%1$s" name or value in where statement for table "%2$s".', $field, $table)); 821 } 822 $values = zbx_toArray($values); 823 sort($values); // sorting ids to prevent deadlocks when two transactions depends from each other 824 825 $sqlWhere[] = dbConditionString($field, $values); 826 } 827 828 $sql = 'DELETE FROM '.$table.' WHERE '.implode(($use_or ? ' OR ' : ' AND '), $sqlWhere); 829 if (!DBexecute($sql)) { 830 self::exception(self::DBEXECUTE_ERROR, _s('SQL statement execution has failed "%1$s"', $sql)); 831 } 832 833 return true; 834 } 835 836 /** 837 * Check if $type is numeric field type. 838 * 839 * @param int $type 840 * 841 * @return bool 842 */ 843 public static function isNumericFieldType($type) { 844 switch ($type) { 845 case self::FIELD_TYPE_ID: 846 case self::FIELD_TYPE_INT: 847 case self::FIELD_TYPE_UINT: 848 return true; 849 } 850 851 return false; 852 } 853} 854