Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
connection.php
1<?php
2
3namespace Bitrix\Main\DB;
4
10
17abstract class Connection extends Data\Connection
18{
20 protected $sqlHelper;
21
23 protected $sqlTracker;
24 protected $trackSql = false;
25
26 protected $version;
27 protected $versionExpress;
28
29 protected $host;
30 protected $database;
31 protected $login;
32 protected $password;
33 protected $initCommand = 0;
34 protected $options = 0;
35 protected $nodeId = 0;
36 protected $utf8mb4 = array();
37
38 protected $tableColumnsCache = array();
40
45 protected $queryExecutingEnabled = true;
46
49
50 const PERSISTENT = 1;
51 const DEFERRED = 2;
52
53 const INDEX_UNIQUE = 'UNIQUE';
54 const INDEX_FULLTEXT = 'FULLTEXT';
55 const INDEX_SPATIAL = 'SPATIAL';
56
70 public function __construct(array $configuration)
71 {
72 parent::__construct($configuration);
73
74 $this->host = $configuration['host'] ?? '';
75 $this->database = $configuration['database'] ?? '';
76 $this->login = $configuration['login'] ?? '';
77 $this->password = $configuration['password'] ?? '';
78 $this->initCommand = $configuration['initCommand'] ?? '';
79 $this->options = intval($configuration['options'] ?? 2);
80 $this->utf8mb4 = (isset($configuration['utf8mb4']) && is_array($configuration['utf8mb4'])? $configuration['utf8mb4'] : []);
81 }
82
87 public function getDbName()
88 {
89 return $this->getDatabase();
90 }
91
97 public function getHost()
98 {
99 return $this->host;
100 }
101
107 public function getLogin()
108 {
109 return $this->login;
110 }
111
117 public function getPassword()
118 {
119 return $this->password;
120 }
121
127 public function getDatabase()
128 {
129 return $this->database;
130 }
131
141 public function disableQueryExecuting()
142 {
143 $this->queryExecutingEnabled = false;
144 }
145
154 public function enableQueryExecuting()
155 {
156 $this->queryExecutingEnabled = true;
157 }
158
165 public function isQueryExecutingEnabled()
166 {
167 return $this->queryExecutingEnabled;
168 }
169
179 {
180 $dump = $this->disabledQueryExecutingDump;
181 $this->disabledQueryExecutingDump = null;
182
183 return $dump;
184 }
185
186 /**********************************************************
187 * SqlHelper
188 **********************************************************/
189
190 abstract protected function createSqlHelper();
191
198 public function getSqlHelper()
199 {
200 if ($this->sqlHelper == null)
201 {
202 $this->sqlHelper = $this->createSqlHelper();
203 }
204
205 return $this->sqlHelper;
206 }
207
208 /***********************************************************
209 * Connection and disconnection
210 ***********************************************************/
211
217 public function connect()
218 {
219 $this->isConnected = false;
220
221 if (!$this->isDeferred())
222 {
223 parent::connect();
224 }
225 }
226
232 public function disconnect()
233 {
234 if (!$this->isPersistent())
235 {
236 parent::disconnect();
237 }
238 }
239
244 public function isDeferred()
245 {
246 return (($this->options & self::DEFERRED) !== 0);
247 }
248
253 public function isPersistent()
254 {
255 return (($this->options & self::PERSISTENT) !== 0);
256 }
257
258 /*********************************************************
259 * Query
260 *********************************************************/
261
276 abstract protected function queryInternal($sql, array $binds = null, Diag\SqlTrackerQuery $trackerQuery = null);
277
286 abstract protected function createResult($result, Diag\SqlTrackerQuery $trackerQuery = null);
287
306 public function query($sql)
307 {
308 list($sql, $binds, $offset, $limit) = self::parseQueryFunctionArgs(func_get_args());
309
310 if($limit > 0)
311 {
312 $sql = $this->getSqlHelper()->getTopSql($sql, $limit, $offset);
313 }
314
315 $trackerQuery = null;
316
317 if ($this->queryExecutingEnabled)
318 {
319 $connection = Main\Application::getInstance()->getConnectionPool()->getSlaveConnection($sql);
320 if($connection === null)
321 {
322 $connection = $this;
323 }
324
325 if ($this->trackSql)
326 {
327 $trackerQuery = $this->sqlTracker->getNewTrackerQuery();
328 $trackerQuery->setNode($connection->getNodeId());
329 }
330
331 $result = $connection->queryInternal($sql, $binds, $trackerQuery);
332 }
333 else
334 {
335 if ($this->disabledQueryExecutingDump === null)
336 {
337 $this->disabledQueryExecutingDump = array();
338 }
339
340 $this->disabledQueryExecutingDump[] = $sql;
341 $result = true;
342 }
343
344 return $this->createResult($result, $trackerQuery);
345 }
346
357 public function queryScalar($sql, array $binds = null)
358 {
359 $result = $this->query($sql, $binds, 0, 1);
360
361 if ($row = $result->fetch())
362 {
363 return array_shift($row);
364 }
365
366 return null;
367 }
368
378 public function queryExecute($sql, array $binds = null)
379 {
380 $this->query($sql, $binds);
381 }
382
391 protected static function parseQueryFunctionArgs($args)
392 {
393 /*
394 * query($sql)
395 * query($sql, $limit)
396 * query($sql, $offset, $limit)
397 * query($sql, $arBinds)
398 * query($sql, $arBinds, $limit)
399 * query($sql, $arBinds, $offset, $limit)
400 */
401 $numArgs = count($args);
402 if ($numArgs < 1)
403 throw new ArgumentNullException("sql");
404
405 $binds = array();
406 $offset = 0;
407 $limit = 0;
408
409 if ($numArgs == 1)
410 {
411 $sql = $args[0];
412 }
413 elseif ($numArgs == 2)
414 {
415 if (is_array($args[1]))
416 list($sql, $binds) = $args;
417 else
418 list($sql, $limit) = $args;
419 }
420 elseif ($numArgs == 3)
421 {
422 if (is_array($args[1]))
423 list($sql, $binds, $limit) = $args;
424 else
425 list($sql, $offset, $limit) = $args;
426 }
427 else
428 {
429 list($sql, $binds, $offset, $limit) = $args;
430 }
431
432 return array($sql, $binds, $offset, $limit);
433 }
434
447 public function add($tableName, array $data, $identity = "ID")
448 {
449 $insert = $this->getSqlHelper()->prepareInsert($tableName, $data);
450
451 $sql =
452 "INSERT INTO ".$this->getSqlHelper()->quote($tableName)."(".$insert[0].") ".
453 "VALUES (".$insert[1].")";
454
455 $this->queryExecute($sql);
456
457 return $this->getInsertedId();
458 }
459
468 public function addMulti($tableName, $rows, $identity = "ID")
469 {
470 $uniqueColumns = [];
471 $inserts = [];
472
473 // prepare data
474 foreach ($rows as $data)
475 {
476 $insert = $this->getSqlHelper()->prepareInsert($tableName, $data, true);
477 $inserts[] = $insert;
478
479 // and get unique column names
480 foreach ($insert[0] as $column)
481 {
482 $uniqueColumns[$column] = true;
483 }
484 }
485
486 // prepare sql
487 $sqlValues = [];
488
489 foreach ($inserts as $insert)
490 {
491
492 $columns = array_flip($insert[0]);
493 $values = $insert[1];
494
495 $finalValues = [];
496
497 foreach (array_keys($uniqueColumns) as $column)
498 {
499 if (array_key_exists($column, $columns))
500 {
501 // set real value
502 $finalValues[] = $values[$columns[$column]];
503 }
504 else
505 {
506 // set default
507 $finalValues[] = 'DEFAULT';
508 }
509 }
510
511 $sqlValues[] = '('.join(', ', $finalValues).')';
512 }
513
514 $sql = "INSERT INTO {$this->getSqlHelper()->quote($tableName)} (".join(', ', array_keys($uniqueColumns)).") ".
515 "VALUES ".join(', ', $sqlValues);
516
517 $this->queryExecute($sql);
518
519 return $this->getInsertedId();
520 }
521
525 abstract public function getInsertedId();
526
536 public function executeSqlBatch($sqlBatch, $stopOnError = false)
537 {
538 $result = [];
539 foreach ($this->parseSqlBatch($sqlBatch) as $sql)
540 {
541 try
542 {
543 $this->queryExecute($sql);
544 }
545 catch (SqlException $ex)
546 {
547 $result[] = $ex->getMessage();
548 if ($stopOnError)
549 {
550 return $result;
551 }
552 }
553 }
554
555 return $result;
556 }
557
564 public function parseSqlBatch($sqlBatch)
565 {
566 $delimiter = $this->getSqlHelper()->getQueryDelimiter();
567
568 $sqlBatch = trim($sqlBatch);
569
570 $statements = [];
571 $sql = "";
572
573 do
574 {
575 if (preg_match("%^(.*?)(['\"`#]|--|\\$\\$|".$delimiter.")%is", $sqlBatch, $match))
576 {
577 //Found string start
578 if ($match[2] == "\"" || $match[2] == "'" || $match[2] == "`")
579 {
580 $sqlBatch = mb_substr($sqlBatch, mb_strlen($match[0]));
581 $sql .= $match[0];
582 //find a quote not preceded by \
583 if (preg_match("%^(.*?)(?<!\\\\‍)".$match[2]."%s", $sqlBatch, $stringMatch))
584 {
585 $sqlBatch = mb_substr($sqlBatch, mb_strlen($stringMatch[0]));
586 $sql .= $stringMatch[0];
587 }
588 else
589 {
590 //String foll beyond end of file
591 $sql .= $sqlBatch;
592 $sqlBatch = "";
593 }
594 }
595 //Comment found
596 elseif ($match[2] == "#" || $match[2] == "--")
597 {
598 //Take that was before comment as part of sql
599 $sqlBatch = mb_substr($sqlBatch, mb_strlen($match[1]));
600 $sql .= $match[1];
601 //And cut the rest
602 $p = mb_strpos($sqlBatch, "\n");
603 if ($p === false)
604 {
605 $p1 = mb_strpos($sqlBatch, "\r");
606 if ($p1 === false)
607 {
608 $sqlBatch = "";
609 }
610 elseif ($p < $p1)
611 {
612 $sqlBatch = mb_substr($sqlBatch, $p);
613 }
614 else
615 {
616 $sqlBatch = mb_substr($sqlBatch, $p1);
617 }
618 }
619 else
620 {
621 $sqlBatch = mb_substr($sqlBatch, $p);
622 }
623 }
624 //$$ plpgsql body
625 elseif ($match[2] == '$$')
626 {
627 //Take that was before delimiter as part of sql
628 $sqlBatch = mb_substr($sqlBatch, mb_strlen($match[0]));
629 //Including $$
630 $sql .= $match[0];
631 //Find closing $$
632 $p = mb_strpos($sqlBatch, '$$');
633 if ($p === false)
634 {
635 $sql .= $sqlBatch;
636 $sqlBatch = '';
637 }
638 else
639 {
640 $sql .= mb_substr($sqlBatch, 0, $p + 2);
641 $sqlBatch = mb_substr($sqlBatch, $p + 2);
642 }
643 }
644 //Delimiter!
645 else
646 {
647 //Take that was before delimiter as part of sql
648 $sqlBatch = mb_substr($sqlBatch, mb_strlen($match[0]));
649 $sql .= $match[1];
650 //Delimiter must be followed by whitespace
651 if (preg_match("%^[\n\r\t ]%", $sqlBatch))
652 {
653 $sql = trim($sql);
654 if (!empty($sql))
655 {
656 $statements[] = str_replace("\r\n", "\n", $sql);
657 $sql = "";
658 }
659 }
660 //It was not delimiter!
661 elseif (!empty($sqlBatch))
662 {
663 $sql .= $match[2];
664 }
665 }
666 }
667 else //End of file is our delimiter
668 {
669 $sql .= $sqlBatch;
670 $sqlBatch = "";
671 }
672 }
673 while (!empty($sqlBatch));
674
675 $sql = trim($sql, " \t\n\r");
676 if (!empty($sql))
677 {
678 $statements[] = str_replace("\r\n", "\n", $sql);
679 }
680
681 return $statements;
682 }
683
689 abstract public function getAffectedRowsCount();
690
691 /*********************************************************
692 * DDL
693 *********************************************************/
694
702 abstract public function isTableExists($tableName);
703
715 abstract public function isIndexExists($tableName, array $columns);
716
726 abstract public function getIndexName($tableName, array $columns, $strict = false);
727
737 abstract public function getTableFields($tableName);
738
748 abstract public function createTable($tableName, $fields, $primary = array(), $autoincrement = array());
749
760 public function createPrimaryIndex($tableName, $columnNames)
761 {
762 if (!is_array($columnNames))
763 {
764 $columnNames = array($columnNames);
765 }
766
767 foreach ($columnNames as &$columnName)
768 {
769 $columnName = $this->getSqlHelper()->quote($columnName);
770 }
771
772 $sql = 'ALTER TABLE '.$this->getSqlHelper()->quote($tableName).' ADD PRIMARY KEY('.join(', ', $columnNames).')';
773
774 return $this->query($sql);
775 }
776
788 public function createIndex($tableName, $indexName, $columnNames)
789 {
790 if (!is_array($columnNames))
791 {
792 $columnNames = array($columnNames);
793 }
794
795 $sqlHelper = $this->getSqlHelper();
796
797 foreach ($columnNames as &$columnName)
798 {
799 $columnName = $sqlHelper->quote($columnName);
800 }
801 unset($columnName);
802
803 $sql = 'CREATE INDEX '.$sqlHelper->quote($indexName).' ON '.$sqlHelper->quote($tableName).' ('.join(', ', $columnNames).')';
804
805 return $this->query($sql);
806 }
807
817 public function getTableField($tableName, $columnName)
818 {
819 $tableFields = $this->getTableFields($tableName);
820
821 return ($tableFields[$columnName] ?? null);
822 }
823
830 public function truncateTable($tableName)
831 {
832 return $this->query('TRUNCATE TABLE '.$this->getSqlHelper()->quote($tableName));
833 }
834
844 abstract public function renameTable($currentName, $newName);
845
856 public function dropColumn($tableName, $columnName)
857 {
858 $this->query('ALTER TABLE '.$this->getSqlHelper()->quote($tableName).' DROP COLUMN '.$this->getSqlHelper()->quote($columnName));
859 }
860
869 abstract public function dropTable($tableName);
870
871 /*********************************************************
872 * Transaction
873 *********************************************************/
874
881 abstract public function startTransaction();
882
889 abstract public function commitTransaction();
890
897 abstract public function rollbackTransaction();
898
899 /*********************************************************
900 * Global named lock
901 *********************************************************/
902
909 public function lock($name, $timeout = 0)
910 {
911 return true;
912 }
913
919 public function unlock($name)
920 {
921 return true;
922 }
923
924 /*********************************************************
925 * Tracker
926 *********************************************************/
927
935 public function startTracker($reset = false)
936 {
937 if ($this->sqlTracker == null)
938 $this->sqlTracker = new Diag\SqlTracker();
939 if ($reset)
940 $this->sqlTracker->reset();
941
942 $this->trackSql = true;
943 return $this->sqlTracker;
944 }
945
951 public function stopTracker()
952 {
953 $this->trackSql = false;
954 }
955
962 public function getTracker()
963 {
964 return $this->sqlTracker;
965 }
966
974 public function setTracker(Diag\SqlTracker $sqlTracker = null)
975 {
976 $this->sqlTracker = $sqlTracker;
977 }
978
979 /*********************************************************
980 * Type, version, cache, etc.
981 *********************************************************/
982
993 abstract public function getType();
994
1004 abstract public function getVersion();
1005
1011 abstract public function getErrorMessage();
1012
1018 public function clearCaches()
1019 {
1020 $this->tableColumnsCache = array();
1021 }
1022
1029 public function setNodeId($nodeId)
1030 {
1031 $this->nodeId = $nodeId;
1032 }
1033
1039 public function getNodeId()
1040 {
1041 return $this->nodeId;
1042 }
1043
1044 protected function afterConnected()
1045 {
1046 if(isset($this->configuration["include_after_connected"]) && $this->configuration["include_after_connected"] <> '')
1047 {
1048 include($this->configuration["include_after_connected"]);
1049 }
1050 }
1051
1059 public function isUtf8mb4($table = null, $column = null)
1060 {
1061 if(isset($this->utf8mb4["global"]) && $this->utf8mb4["global"] === true)
1062 {
1063 return true;
1064 }
1065
1066 if($table !== null && isset($this->utf8mb4["tables"][$table]) && $this->utf8mb4["tables"][$table] === true)
1067 {
1068 return true;
1069 }
1070
1071 if($table !== null && $column !== null && isset($this->utf8mb4["tables"][$table][$column]) && $this->utf8mb4["tables"][$table][$column] === true)
1072 {
1073 return true;
1074 }
1075
1076 return false;
1077 }
1078
1079 protected static function findIndex(array $indexes, array $columns, $strict)
1080 {
1081 $columnsList = implode(",", $columns);
1082
1083 foreach ($indexes as $indexName => $indexColumns)
1084 {
1085 ksort($indexColumns);
1086 $indexColumnList = implode(",", $indexColumns);
1087 if ($strict)
1088 {
1089 if ($indexColumnList === $columnsList)
1090 {
1091 return $indexName;
1092 }
1093 }
1094 else
1095 {
1096 if (str_starts_with($indexColumnList, $columnsList))
1097 {
1098 return $indexName;
1099 }
1100 }
1101 }
1102
1103 return null;
1104 }
1105}
setTracker(Diag\SqlTracker $sqlTracker=null)
static findIndex(array $indexes, array $columns, $strict)
getTableField($tableName, $columnName)
static parseQueryFunctionArgs($args)
isUtf8mb4($table=null, $column=null)
getIndexName($tableName, array $columns, $strict=false)
createResult($result, Diag\SqlTrackerQuery $trackerQuery=null)
createIndex($tableName, $indexName, $columnNames)
createPrimaryIndex($tableName, $columnNames)
executeSqlBatch($sqlBatch, $stopOnError=false)
queryInternal($sql, array $binds=null, Diag\SqlTrackerQuery $trackerQuery=null)
renameTable($currentName, $newName)
lock($name, $timeout=0)
dropColumn($tableName, $columnName)
isIndexExists($tableName, array $columns)
addMulti($tableName, $rows, $identity="ID")
add($tableName, array $data, $identity="ID")
queryScalar($sql, array $binds=null)
queryExecute($sql, array $binds=null)
createTable($tableName, $fields, $primary=array(), $autoincrement=array())
__construct(array $configuration)