Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
pgsqlconnection.php
1<?php
2
3namespace Bitrix\Main\DB;
4
9
11{
12 protected int $transactionLevel = 0;
13
14 public function connectionErrorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = null)
15 {
16 throw new ConnectionException('Pgsql connect error: ', $errstr);
17 }
18
19 protected function connectInternal()
20 {
21 if ($this->isConnected)
22 {
23 return;
24 }
25
26 $host = $this->host;
27 $port = 0;
28 if (($pos = strpos($host, ":")) !== false)
29 {
30 $port = intval(substr($host, $pos + 1));
31 $host = substr($host, 0, $pos);
32 }
33
34 $connectionString = " host='" . addslashes($host) . "'";
35 if ($port > 0)
36 {
37 $connectionString .= " port='" . addslashes($port) . "'";
38 }
39 $connectionString .= " dbname='" . addslashes($this->database) . "'";
40 $connectionString .= " user='" . addslashes($this->login) . "'";
41 $connectionString .= " password='" . addslashes($this->password) . "'";
42
43 if (isset($this->configuration['charset']))
44 {
45 $connectionString .= " options='--client_encoding=" . $this->configuration['charset'] . "'";
46 }
47
48 set_error_handler([$this, 'connectionErrorHandler']);
49 if ($this->options & self::PERSISTENT)
50 {
51 $connection = @pg_pconnect($connectionString);
52 }
53 else
54 {
55 $connection = @pg_connect($connectionString);
56 }
57 restore_error_handler();
58
59 if (!$connection)
60 {
61 throw new ConnectionException(
62 'Pgsql connect error ['.$this->host.']',
63 error_get_last()['message']
64 );
65 }
66
67 $this->resource = $connection;
68 $this->isConnected = true;
69
70 $this->afterConnected();
71 }
72
73 protected function disconnectInternal()
74 {
75 if ($this->isConnected)
76 {
77 $this->isConnected = false;
78 try
79 {
80 pg_close($this->resource);
81 $this->resource = null;
82 }
83 catch (\Throwable)
84 {
85 // Ignore misterious error
86 // pg_close(): supplied resource is not a valid PostgreSQL link resource (0)
87 }
88 }
89 }
90
91 protected function createSqlHelper()
92 {
93 return new PgsqlSqlHelper($this);
94 }
95
99 protected function queryInternal($sql, array $binds = null, Diag\SqlTrackerQuery $trackerQuery = null)
100 {
101 $this->connectInternal();
102
103 $trackerQuery?->startQuery($sql, $binds);
104
105 $result = pg_query($this->resource, $sql);
106
107 $trackerQuery?->finishQuery();
108
109 $this->lastQueryResult = $result;
110
111 if (!$result)
112 {
113 throw new SqlQueryException('Pgsql query error', $this->getErrorMessage(), $sql);
114 }
115
116 return $result;
117 }
118
122 protected function createResult($result, Diag\SqlTrackerQuery $trackerQuery = null)
123 {
124 return new PgsqlResult($result, $this, $trackerQuery);
125 }
126
130 public function add($tableName, array $data, $identity = "ID")
131 {
132 $insert = $this->getSqlHelper()->prepareInsert($tableName, $data);
133 if(
134 $identity !== null
135 && (
136 !isset($data[$identity])
137 || $data[$identity] instanceof SqlExpression
138 )
139 )
140 {
141 $sql = "INSERT INTO ".$tableName."(".$insert[0].") VALUES (".$insert[1].") RETURNING ".$identity;
142 $row = $this->query($sql)->fetch();
143 return array_shift($row);
144 }
145 else
146 {
147 $sql = "INSERT INTO ".$tableName."(".$insert[0].") VALUES (".$insert[1].")";
148 $this->query($sql);
149 return $data[$identity];
150 }
151 }
152
156 public function getInsertedId()
157 {
158 try
159 {
160 return (int)$this->query('SELECT bx_lastval() as X')->fetch()['X'];
161 }
162 catch (SqlQueryException)
163 {
164 return 0;
165 }
166 }
167
171 public function getAffectedRowsCount()
172 {
173 return pg_affected_rows($this->lastQueryResult);
174 }
175
179 public function isTableExists($tableName)
180 {
181 $result = $this->query("
182 SELECT tablename
183 FROM pg_tables
184 WHERE schemaname = 'public'
185 AND tablename = '".$this->getSqlHelper()->forSql($tableName)."'
186 ");
187 $row = $result->fetch();
188 return is_array($row);
189 }
193 public function isIndexExists($tableName, array $columns)
194 {
195 return $this->getIndexName($tableName, $columns) !== null;
196 }
200 public function getIndexName($tableName, array $columns, $strict = false)
201 {
202 if (empty($columns))
203 {
204 return null;
205 }
206
207 $tableColumns = [];
208 $r = $this->query("
209 SELECT a.attnum, a.attname
210 FROM pg_class t
211 LEFT JOIN pg_attribute a ON a.attrelid = t.oid
212 WHERE t.relname = '".$this->getSqlHelper()->forSql($tableName)."'
213 ");
214 while ($a = $r->fetch())
215 {
216 if ($a['ATTNUM']> 0)
217 {
218 $tableColumns[$a['ATTNUM']] = $a['ATTNAME'];
219 }
220 }
221
222 $r = $this->query("
223 SELECT relname, indkey, pg_get_expr(pg_index.indexprs, pg_index.indrelid) full_text
224 FROM pg_class, pg_index
225 WHERE pg_class.oid = pg_index.indexrelid
226 AND pg_class.oid IN (
227 SELECT indexrelid
228 FROM pg_index, pg_class
229 WHERE pg_class.relname = '".$this->getSqlHelper()->forSql($tableName)."'
230 AND pg_class.oid = pg_index.indrelid
231 )
232 ");
233 $indexes = [];
234 while ($a = $r->fetch())
235 {
236 $indexes[$a['RELNAME']] = [];
237 if ($a['FULL_TEXT'])
238 {
239 $match = [];
240 if (preg_match_all('/,\s*([a-z0-9_]+)/i', $a['FULL_TEXT'], $match))
241 {
242 foreach ($match[1] as $i => $colName)
243 {
244 $indexes[$a['RELNAME']][$i] = mb_strtoupper($colName);
245 }
246 }
247 }
248 else
249 {
250 foreach (explode(' ', $a['INDKEY']) as $i => $indkey)
251 {
252 $indexes[$a['RELNAME']][$i] = mb_strtoupper($tableColumns[$indkey]);
253 }
254 }
255 }
256
257 return static::findIndex($indexes, $columns, $strict);
258 }
259
260 protected static function findIndex(array $indexes, array $columns, $strict)
261 {
262 $columnsList = mb_strtolower(implode(",", $columns));
263
264 foreach ($indexes as $indexName => $indexColumns)
265 {
266 ksort($indexColumns);
267 $indexColumnList = mb_strtolower(implode(",", $indexColumns));
268 if ($strict)
269 {
270 if ($indexColumnList === $columnsList)
271 {
272 return $indexName;
273 }
274 }
275 else
276 {
277 if (str_starts_with($indexColumnList, $columnsList))
278 {
279 return $indexName;
280 }
281 }
282 }
283
284 return null;
285 }
286
290 public function getTableFields($tableName)
291 {
292 if (!isset($this->tableColumnsCache[$tableName]) || empty($this->tableColumnsCache[$tableName]))
293 {
294 $this->connectInternal();
295
296 $sqlHelper = $this->getSqlHelper();
297 $query = $this->query("
298 SELECT
299 column_name,
300 data_type,
301 character_maximum_length
302 FROM
303 information_schema.columns
304 WHERE
305 table_catalog = '" . $sqlHelper->forSql($this->getDatabase()) . "'
306 and table_schema = 'public'
307 and table_name = '" . $sqlHelper->forSql(mb_strtolower($tableName)) . "'
308 ORDER BY
309 ordinal_position
310 ");
311
312 $this->tableColumnsCache[$tableName] = [];
313 while ($fieldInfo = $query->fetch())
314 {
315 $fieldName = mb_strtoupper($fieldInfo['COLUMN_NAME']);
316 $fieldType = $fieldInfo['DATA_TYPE'];
317 $field = $sqlHelper->getFieldByColumnType($fieldName, $fieldType);
318 if (
319 $fieldInfo['CHARACTER_MAXIMUM_LENGTH']
320 && is_a($field, '\Bitrix\Main\ORM\Fields\StringField')
321 )
322 {
323 $field->configureSize($fieldInfo['CHARACTER_MAXIMUM_LENGTH']);
324 }
325
326 $this->tableColumnsCache[$tableName][$fieldName] = $field;
327 }
328 }
329
330 return $this->tableColumnsCache[$tableName];
331 }
332
336 public function createTable($tableName, $fields, $primary = array(), $autoincrement = array())
337 {
338 $sql = 'CREATE TABLE '.$this->getSqlHelper()->quote($tableName).' (';
339 $sqlFields = array();
340
341 foreach ($fields as $columnName => $field)
342 {
343 if (!($field instanceof ScalarField))
344 {
345 throw new ArgumentException(sprintf(
346 'Field `%s` should be an Entity\ScalarField instance', $columnName
347 ));
348 }
349
350 $realColumnName = $field->getColumnName();
351
352 if (in_array($columnName, $autoincrement, true))
353 {
354 $type = 'INT GENERATED BY DEFAULT AS IDENTITY'; // size = 4
355
356 if ($field instanceof IntegerField)
357 {
358 switch ($field->getSize())
359 {
360 case 2:
361 $type = 'SMALLINT GENERATED BY DEFAULT AS IDENTITY';
362 break;
363 case 8:
364 $type = 'BIGINT GENERATED BY DEFAULT AS IDENTITY';
365 break;
366 }
367 }
368 }
369 else
370 {
371 $type = $this->getSqlHelper()->getColumnTypeByField($field);
372 }
373 $sqlFields[] = $this->getSqlHelper()->quote($realColumnName)
374 . ' ' . $type
375 . ($field->isNullable() ? '' : ' NOT NULL')
376 ;
377 }
378
379 $sql .= join(', ', $sqlFields);
380
381 if (!empty($primary))
382 {
383 foreach ($primary as &$primaryColumn)
384 {
385 $realColumnName = $fields[$primaryColumn]->getColumnName();
386 $primaryColumn = $this->getSqlHelper()->quote($realColumnName);
387 }
388
389 $sql .= ', PRIMARY KEY('.join(', ', $primary).')';
390 }
391
392 $sql .= ')';
393
394 $this->query($sql);
395
396 }
397
401 public function createIndex($tableName, $indexName, $columnNames, $columnLengths = null, $indexType = null)
402 {
403 if (!is_array($columnNames))
404 {
405 $columnNames = array($columnNames);
406 }
407
408 $sqlHelper = $this->getSqlHelper();
409
410 foreach ($columnNames as &$columnName)
411 {
412 $columnName = $sqlHelper->quote($columnName);
413 }
414 unset($columnName);
415
416 if ($indexType === static::INDEX_UNIQUE)
417 {
418 return $this->query('CREATE UNIQUE INDEX ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
419 }
420 elseif ($indexType === static::INDEX_FULLTEXT)
421 {
422 return $this->query('CREATE INDEX ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . ' USING GIN (to_tsvector(\'english\', ' . implode(',', $columnNames) . '))');
423 }
424 else
425 {
426 return $this->query('CREATE INDEX ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
427 }
428 }
429
433 public function renameTable($currentName, $newName)
434 {
435 $this->query('ALTER TABLE '.$this->getSqlHelper()->quote($currentName).' RENAME TO '.$this->getSqlHelper()->quote($newName));
436 }
437
441 public function dropTable($tableName)
442 {
443 $this->query('DROP TABLE '.$this->getSqlHelper()->quote($tableName));
444 }
445
449 public function startTransaction()
450 {
451 if ($this->transactionLevel == 0)
452 {
453 $this->query("START TRANSACTION");
454 }
455 else
456 {
457 $this->query("SAVEPOINT TRANS{$this->transactionLevel}");
458 }
459
460 $this->transactionLevel++;
461 }
462
466 public function commitTransaction()
467 {
468 $this->transactionLevel--;
469
470 if ($this->transactionLevel < 0)
471 {
472 throw new TransactionException('Transaction was not started.');
473 }
474
475 if ($this->transactionLevel == 0)
476 {
477 // commits all nested transactions
478 $this->query("COMMIT");
479 }
480 }
481
485 public function rollbackTransaction()
486 {
487 $this->transactionLevel--;
488
489 if ($this->transactionLevel < 0)
490 {
491 throw new TransactionException('Transaction was not started.');
492 }
493
494 if ($this->transactionLevel == 0)
495 {
496 $this->query("ROLLBACK");
497 }
498 else
499 {
500 $this->query("ROLLBACK TO SAVEPOINT TRANS{$this->transactionLevel}");
501 }
502 }
503
504 /*********************************************************
505 * Global named lock
506 *********************************************************/
507
511 public function lock($name, $timeout = 0)
512 {
513 $timeout = (int)$timeout;
514 $name = $this->getLockName($name);
515
516 $sql = 'SELECT bx_get_lock(' . $name . ', ' . $timeout . ') as L';
517 $lock = $this->query($sql)->fetch();
518
519 return ($lock['L'] == 0);
520 }
521
525 public function unlock($name)
526 {
527 $name = $this->getLockName($name);
528
529 $sql = 'SELECT bx_release_lock(' . $name . ') as L';
530 $lock = $this->query($sql)->fetch();
531
532 return ($lock['L'] == 0);
533 }
534
535 protected function getLockName($name)
536 {
537 $unique = \CMain::GetServerUniqID();
538
539 return crc32($unique . '|' . $name);
540 }
541
545 public function getType()
546 {
547 return "pgsql";
548 }
549
553 public function getVersion()
554 {
555 if ($this->version == null)
556 {
557 $this->connectInternal();
558 $version = trim(pg_version($this->resource)['server']);
559
560 preg_match("#^.*?([0-9]+\\.[0-9]+)#", $version, $ar);
561 $this->version = $ar[1];
562 }
563
564 return array($this->version, null);
565 }
566
570 public function getErrorMessage()
571 {
572 return pg_last_error($this->resource);
573 }
574}
static findIndex(array $indexes, array $columns, $strict)
connectionErrorHandler($errno, $errstr, $errfile='', $errline=0, $errcontext=null)
getIndexName($tableName, array $columns, $strict=false)
createResult($result, Diag\SqlTrackerQuery $trackerQuery=null)
queryInternal($sql, array $binds=null, Diag\SqlTrackerQuery $trackerQuery=null)
renameTable($currentName, $newName)
isIndexExists($tableName, array $columns)
createIndex($tableName, $indexName, $columnNames, $columnLengths=null, $indexType=null)
add($tableName, array $data, $identity="ID")
createTable($tableName, $fields, $primary=array(), $autoincrement=array())