1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
pgsqlconnection.php
См. документацию.
1<?php
2
3namespace Bitrix\Main\DB;
4
5use Bitrix\Main\Diag;
6use Bitrix\Main\ArgumentException;
7use Bitrix\Main\ORM\Fields\ScalarField;
8use Bitrix\Main\ORM\Fields\IntegerField;
9
15{
16 protected int $transactionLevel = 0;
17
18 public function connectionErrorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = null)
19 {
20 throw new ConnectionException('Pgsql connect error: ', $errstr);
21 }
22
23 protected function connectInternal()
24 {
25 if ($this->isConnected)
26 {
27 return;
28 }
29
31 $port = 0;
32 if (($pos = strpos($host, ":")) !== false)
33 {
34 $port = intval(substr($host, $pos + 1));
35 $host = substr($host, 0, $pos);
36 }
37
38 $connectionString = " host='" . addslashes($host) . "'";
39 if ($port > 0)
40 {
41 $connectionString .= " port='" . addslashes($port) . "'";
42 }
43 $connectionString .= " dbname='" . addslashes($this->database) . "'";
44 $connectionString .= " user='" . addslashes($this->login) . "'";
45 $connectionString .= " password='" . addslashes($this->password) . "'";
46
47 if (isset($this->configuration['charset']))
48 {
49 $connectionString .= " options='--client_encoding=" . $this->configuration['charset'] . "'";
50 }
51
52 set_error_handler([$this, 'connectionErrorHandler']);
53 try
54 {
55 if ($this->isPersistent())
56 {
57 $connection = @pg_pconnect($connectionString);
58 }
59 else
60 {
61 $connection = @pg_connect($connectionString);
62 }
63 }
64 finally
65 {
66 restore_error_handler();
67 }
68
69 if (!$connection)
70 {
71 throw new ConnectionException(
72 'Pgsql connect error [' . $this->host . ']',
73 error_get_last()['message']
74 );
75 }
76
77 $this->resource = $connection;
78 $this->isConnected = true;
79
81 $this->afterConnected();
82 }
83
84 protected function disconnectInternal()
85 {
86 if ($this->isConnected)
87 {
88 $this->isConnected = false;
89 try
90 {
91 pg_close($this->resource);
92 $this->resource = null;
93 }
94 catch (\Throwable)
95 {
96 // Ignore misterious error
97 // pg_close(): supplied resource is not a valid PostgreSQL link resource (0)
98 }
99 }
100 }
101
102 protected function createSqlHelper()
103 {
104 return new PgsqlSqlHelper($this);
105 }
106
110 protected function queryInternal($sql, array $binds = null, Diag\SqlTrackerQuery $trackerQuery = null)
111 {
112 $this->connectInternal();
113
114 $trackerQuery?->startQuery($sql, $binds);
115
116 // Handle E_WARNING
117 set_error_handler(function () {
118 // noop
119 });
120
121 $result = pg_query($this->resource, $sql);
122
123 restore_error_handler();
124
125 $trackerQuery?->finishQuery();
126
127 $this->lastQueryResult = $result;
128
129 if (!$result)
130 {
131 throw $this->createQueryException($this->getErrorCode(), $this->getErrorMessage(), $sql);
132 }
133
134 return $result;
135 }
136
140 protected function createResult($result, Diag\SqlTrackerQuery $trackerQuery = null)
141 {
142 return new PgsqlResult($result, $this, $trackerQuery);
143 }
144
148 public function add($tableName, array $data, $identity = "ID")
149 {
150 $insert = $this->getSqlHelper()->prepareInsert($tableName, $data);
151 if (
152 $identity !== null
153 && (
154 !isset($data[$identity])
155 || $data[$identity] instanceof SqlExpression
156 )
157 )
158 {
159 $sql = "INSERT INTO " . $tableName . "(" . $insert[0] . ") VALUES (" . $insert[1] . ") RETURNING " . $identity;
160 $row = $this->query($sql)->fetch();
161 return intval(array_shift($row));
162 }
163 else
164 {
165 $sql = "INSERT INTO " . $tableName . "(" . $insert[0] . ") VALUES (" . $insert[1] . ")";
166 $this->query($sql);
167 return $data[$identity] ?? null;
168 }
169 }
170
174 public function getInsertedId()
175 {
176 try
177 {
178 return (int)$this->query('SELECT bx_lastval() as X')->fetch()['X'];
179 }
180 catch (SqlQueryException)
181 {
182 return 0;
183 }
184 }
185
189 public function getAffectedRowsCount()
190 {
191 return pg_affected_rows($this->lastQueryResult);
192 }
193
197 public function isTableExists($tableName)
198 {
199 $result = $this->query("
200 SELECT tablename
201 FROM pg_tables
202 WHERE schemaname = 'public'
203 AND tablename = '" . $this->getSqlHelper()->forSql($tableName) . "'
204 ");
205 $row = $result->fetch();
206 return is_array($row);
207 }
208
212 public function isIndexExists($tableName, array $columns)
213 {
214 return $this->getIndexName($tableName, $columns) !== null;
215 }
216
220 public function getIndexName($tableName, array $columns, $strict = false)
221 {
222 if (empty($columns))
223 {
224 return null;
225 }
226
227 $tableColumns = [];
228 $r = $this->query("
229 SELECT a.attnum, a.attname
230 FROM pg_class t
231 LEFT JOIN pg_attribute a ON a.attrelid = t.oid
232 WHERE t.relname = '" . $this->getSqlHelper()->forSql($tableName) . "'
233 ");
234 while ($a = $r->fetch())
235 {
236 if ($a['ATTNUM'] > 0)
237 {
238 $tableColumns[$a['ATTNUM']] = $a['ATTNAME'];
239 }
240 }
241
242 $r = $this->query("
243 SELECT relname, indkey, pg_get_expr(pg_index.indexprs, pg_index.indrelid) full_text
244 FROM pg_class, pg_index
245 WHERE pg_class.oid = pg_index.indexrelid
246 AND pg_class.oid IN (
247 SELECT indexrelid
248 FROM pg_index, pg_class
249 WHERE pg_class.relname = '" . $this->getSqlHelper()->forSql($tableName) . "'
250 AND pg_class.oid = pg_index.indrelid
251 )
252 ");
253 $indexes = [];
254 while ($a = $r->fetch())
255 {
256 $indexes[$a['RELNAME']] = [];
257 if ($a['FULL_TEXT'])
258 {
259 $match = [];
260 if (preg_match_all('/,\s*\‍(?([a-z0-9_]+)(?:\‍)::text)?/i', $a['FULL_TEXT'], $match))
261 {
262 foreach ($match[1] as $i => $colName)
263 {
264 $indexes[$a['RELNAME']][$i] = mb_strtoupper($colName);
265 }
266 }
267 }
268 else
269 {
270 foreach (explode(' ', $a['INDKEY']) as $i => $indkey)
271 {
272 $indexes[$a['RELNAME']][$i] = mb_strtoupper($tableColumns[$indkey]);
273 }
274 }
275 }
276
277 return static::findIndex($indexes, $columns, $strict);
278 }
279
280 protected static function findIndex(array $indexes, array $columns, $strict)
281 {
282 $columnsList = mb_strtolower(implode(",", $columns));
283
284 foreach ($indexes as $indexName => $indexColumns)
285 {
286 ksort($indexColumns);
287 $indexColumnList = mb_strtolower(implode(",", $indexColumns));
288 if ($strict)
289 {
290 if ($indexColumnList === $columnsList)
291 {
292 return $indexName;
293 }
294 }
295 else
296 {
297 if (str_starts_with($indexColumnList, $columnsList))
298 {
299 return $indexName;
300 }
301 }
302 }
303
304 return null;
305 }
306
307 public function getTableFullTextFields($tableName)
308 {
309 $sqlHelper = $this->getSqlHelper();
310 $fullTextColumns = [];
311
312 $sql = "
313 SELECT relname, indkey, pg_get_expr(pg_index.indexprs, pg_index.indrelid) full_text
314 FROM pg_class, pg_index
315 WHERE pg_class.oid = pg_index.indexrelid
316 AND pg_class.oid IN (
317 SELECT indexrelid
318 FROM pg_index, pg_class
319 WHERE pg_class.relname = '" . $sqlHelper->forSql(mb_strtolower($tableName)) . "'
320 AND pg_class.oid = pg_index.indrelid
321 )
322 ";
323 $res = $this->query($sql);
324 while ($row = $res->fetch())
325 {
326 if ($row['FULL_TEXT'])
327 {
328 $match = [];
329 if (preg_match_all('/,\s*\‍(?([a-z0-9_]+)(?:\‍)::text)?/i', $row['FULL_TEXT'], $match))
330 {
331 foreach ($match[1] as $i => $colName)
332 {
333 $fullTextColumns[mb_strtoupper($colName)] = true;
334 }
335 }
336 }
337 }
338
339 return $fullTextColumns;
340 }
341
345 public function getTableFields($tableName)
346 {
347 if (!isset($this->tableColumnsCache[$tableName]) || empty($this->tableColumnsCache[$tableName]))
348 {
349 $this->connectInternal();
350
351 $sqlHelper = $this->getSqlHelper();
352
353 $fullTextColumns = $this->getTableFullTextFields($tableName);
354
355 $query = $this->query("
356 SELECT
357 column_name,
358 data_type,
359 character_maximum_length
360 FROM
361 information_schema.columns
362 WHERE
363 table_catalog = '" . $sqlHelper->forSql($this->getDatabase()) . "'
364 and table_schema = 'public'
365 and table_name = '" . $sqlHelper->forSql(mb_strtolower($tableName)) . "'
366 ORDER BY
367 ordinal_position
368 ");
369
370 $this->tableColumnsCache[$tableName] = [];
371 while ($fieldInfo = $query->fetch())
372 {
373 $fieldName = mb_strtoupper($fieldInfo['COLUMN_NAME']);
374 $fieldType = $fieldInfo['DATA_TYPE'];
375 $field = $sqlHelper->getFieldByColumnType($fieldName, $fieldType);
376 if (is_a($field, '\Bitrix\Main\ORM\Fields\StringField'))
377 {
378 if ($fieldInfo['CHARACTER_MAXIMUM_LENGTH'])
379 {
380 $field->configureSize($fieldInfo['CHARACTER_MAXIMUM_LENGTH']);
381 }
382
383 if (array_key_exists($fieldName, $fullTextColumns))
384 {
385 $field->configureFulltext(true);
386 }
387 }
388
389 $this->tableColumnsCache[$tableName][$fieldName] = $field;
390 }
391 }
392
393 return $this->tableColumnsCache[$tableName];
394 }
395
399 public function createTable($tableName, $fields, $primary = [], $autoincrement = [])
400 {
401 $sql = 'CREATE TABLE IF NOT EXISTS ' . $this->getSqlHelper()->quote($tableName) . ' (';
402 $sqlFields = [];
403
404 foreach ($fields as $columnName => $field)
405 {
406 if (!($field instanceof ScalarField))
407 {
408 throw new ArgumentException(sprintf(
409 'Field `%s` should be an Entity\ScalarField instance', $columnName
410 ));
411 }
412
413 $realColumnName = $field->getColumnName();
414
415 if (in_array($columnName, $autoincrement, true))
416 {
417 $type = 'INT GENERATED BY DEFAULT AS IDENTITY'; // size = 4
418
419 if ($field instanceof IntegerField)
420 {
421 switch ($field->getSize())
422 {
423 case 2:
424 $type = 'SMALLINT GENERATED BY DEFAULT AS IDENTITY';
425 break;
426 case 8:
427 $type = 'BIGINT GENERATED BY DEFAULT AS IDENTITY';
428 break;
429 }
430 }
431 }
432 else
433 {
434 $type = $this->getSqlHelper()->getColumnTypeByField($field);
435 }
436 $sqlFields[] = $this->getSqlHelper()->quote($realColumnName)
437 . ' ' . $type
438 . ($field->isNullable() ? '' : ' NOT NULL');
439 }
440
441 $sql .= join(', ', $sqlFields);
442
443 if (!empty($primary))
444 {
445 foreach ($primary as &$primaryColumn)
446 {
447 $realColumnName = $fields[$primaryColumn]->getColumnName();
448 $primaryColumn = $this->getSqlHelper()->quote($realColumnName);
449 }
450
451 $sql .= ', PRIMARY KEY(' . join(', ', $primary) . ')';
452 }
453
454 $sql .= ')';
455
456 $this->query($sql);
457 }
458
462 public function createIndex($tableName, $indexName, $columnNames, $columnLengths = null, $indexType = null)
463 {
464 if (!is_array($columnNames))
465 {
466 $columnNames = [$columnNames];
467 }
468
469 $sqlHelper = $this->getSqlHelper();
470
471 foreach ($columnNames as &$columnName)
472 {
473 $columnName = $sqlHelper->quote($columnName);
474 }
475 unset($columnName);
476
477 if ($indexType === static::INDEX_UNIQUE)
478 {
479 return $this->query('CREATE UNIQUE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
480 }
481 elseif ($indexType === static::INDEX_FULLTEXT)
482 {
483 return $this->query('CREATE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . ' USING GIN (to_tsvector(\'english\', ' . implode(',', $columnNames) . '))');
484 }
485 else
486 {
487 return $this->query('CREATE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
488 }
489 }
490
494 public function renameTable($currentName, $newName)
495 {
496 $this->query('ALTER TABLE ' . $this->getSqlHelper()->quote($currentName) . ' RENAME TO ' . $this->getSqlHelper()->quote($newName));
497 }
498
502 public function dropTable($tableName)
503 {
504 $this->query('DROP TABLE ' . $this->getSqlHelper()->quote($tableName));
505 }
506
510 public function startTransaction()
511 {
512 if ($this->transactionLevel == 0)
513 {
514 $this->query("START TRANSACTION");
515 }
516 else
517 {
518 $this->query("SAVEPOINT TRANS{$this->transactionLevel}");
519 }
520
521 $this->transactionLevel++;
522 }
523
527 public function commitTransaction()
528 {
529 $this->transactionLevel--;
530
531 if ($this->transactionLevel < 0)
532 {
533 throw new TransactionException('Transaction was not started.');
534 }
535
536 if ($this->transactionLevel == 0)
537 {
538 // commits all nested transactions
539 $this->query("COMMIT");
540 }
541 }
542
546 public function rollbackTransaction()
547 {
548 $this->transactionLevel--;
549
550 if ($this->transactionLevel < 0)
551 {
552 throw new TransactionException('Transaction was not started.');
553 }
554
555 if ($this->transactionLevel == 0)
556 {
557 $this->query("ROLLBACK");
558 }
559 else
560 {
561 $this->query("ROLLBACK TO SAVEPOINT TRANS{$this->transactionLevel}");
562 }
563 }
564
565 /*********************************************************
566 * Global named lock
567 *********************************************************/
568
572 public function lock($name, $timeout = 0)
573 {
574 $timeout = (int)$timeout;
575 $name = $this->getLockName($name);
576
577 $sql = 'SELECT bx_get_lock(' . $name . ', ' . $timeout . ') as L';
578 $lock = $this->query($sql)->fetch();
579
580 return ($lock['L'] == 0);
581 }
582
586 public function unlock($name)
587 {
588 $name = $this->getLockName($name);
589
590 $sql = 'SELECT bx_release_lock(' . $name . ') as L';
591 $lock = $this->query($sql)->fetch();
592
593 return ($lock['L'] == 0);
594 }
595
596 protected function getLockName($name)
597 {
598 $unique = \CMain::GetServerUniqID();
599
600 return crc32($unique . '|' . $name);
601 }
602
606 public function getType()
607 {
608 return "pgsql";
609 }
610
614 public function getVersion()
615 {
616 if ($this->version == null)
617 {
618 $this->connectInternal();
619 $version = trim(pg_version($this->resource)['server']);
620
621 preg_match("#^.*?([0-9]+\\.[0-9]+)#", $version, $ar);
622 $this->version = $ar[1];
623 }
624
625 return [$this->version, null];
626 }
627
631 public function getErrorMessage()
632 {
633 return pg_last_error($this->resource);
634 }
635
639 public function getErrorCode()
640 {
641 if (preg_match("/ERROR:\\s*([^:]+):/i", $this->getErrorMessage(), $matches))
642 {
643 return $matches[1];
644 }
645 return '';
646 }
647
648 protected function configureErrorVerbosity()
649 {
650 pg_set_error_verbosity($this->resource, PGSQL_ERRORS_VERBOSE);
651 }
652
656 public function createQueryException($code = '', $databaseMessage = '', $query = '')
657 {
658 if ($code == '23505')
659 {
660 return new DuplicateEntryException('Pgsql query error', $databaseMessage, $query);
661 }
662 return new SqlQueryException('Pgsql query error', $databaseMessage, $query);
663 }
664}
$connection
Определения actionsdefinitions.php:38
$type
Определения options.php:106
xml version
Определения yandex.php:67
Определения duplicateentryexception.php:9
isTableExists($tableName)
Определения pgsqlconnection.php:197
static findIndex(array $indexes, array $columns, $strict)
Определения pgsqlconnection.php:280
dropTable($tableName)
Определения pgsqlconnection.php:502
connectionErrorHandler($errno, $errstr, $errfile='', $errline=0, $errcontext=null)
Определения pgsqlconnection.php:18
getIndexName($tableName, array $columns, $strict=false)
Определения pgsqlconnection.php:220
getTableFields($tableName)
Определения pgsqlconnection.php:345
createResult($result, Diag\SqlTrackerQuery $trackerQuery=null)
Определения pgsqlconnection.php:140
queryInternal($sql, array $binds=null, Diag\SqlTrackerQuery $trackerQuery=null)
Определения pgsqlconnection.php:110
renameTable($currentName, $newName)
Определения pgsqlconnection.php:494
lock($name, $timeout=0)
Определения pgsqlconnection.php:572
isIndexExists($tableName, array $columns)
Определения pgsqlconnection.php:212
createIndex($tableName, $indexName, $columnNames, $columnLengths=null, $indexType=null)
Определения pgsqlconnection.php:462
createQueryException($code='', $databaseMessage='', $query='')
Определения pgsqlconnection.php:656
add($tableName, array $data, $identity="ID")
Определения pgsqlconnection.php:148
getTableFullTextFields($tableName)
Определения pgsqlconnection.php:307
createTable($tableName, $fields, $primary=[], $autoincrement=[])
Определения pgsqlconnection.php:399
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$res
Определения filter_act.php:7
$result
Определения get_property_values.php:14
$query
Определения get_search.php:11
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
$name
Определения menu_edit.php:35
Определения cachetracker.php:2
$host
Определения mysql_to_pgsql.php:32
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$ar
Определения options.php:199
$i
Определения factura.php:643
else $a
Определения template.php:137
$matches
Определения index.php:22
$fields
Определения yandex_run.php:501