Your IP : 3.146.176.19


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/db/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/db/pgsqlconnection.php

<?php

namespace Bitrix\Main\DB;

use Bitrix\Main\Diag;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ORM\Fields\ScalarField;
use Bitrix\Main\ORM\Fields\IntegerField;

class PgsqlConnection extends Connection
{
	protected int $transactionLevel = 0;

	public function connectionErrorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = null)
	{
		throw new ConnectionException('Pgsql connect error: ', $errstr);
	}

	protected function connectInternal()
	{
		if ($this->isConnected)
		{
			return;
		}

		$host = $this->host;
		$port = 0;
		if (($pos = strpos($host, ":")) !== false)
		{
			$port = intval(substr($host, $pos + 1));
			$host = substr($host, 0, $pos);
		}

		$connectionString = " host='" . addslashes($host) . "'";
		if ($port > 0)
		{
			$connectionString .= " port='" . addslashes($port) . "'";
		}
		$connectionString .= " dbname='" . addslashes($this->database) . "'";
		$connectionString .= " user='" . addslashes($this->login) . "'";
		$connectionString .= " password='" . addslashes($this->password) . "'";

		if (isset($this->configuration['charset']))
		{
			$connectionString .= " options='--client_encoding=" . $this->configuration['charset'] . "'";
		}

		set_error_handler([$this, 'connectionErrorHandler']);
		if ($this->options & self::PERSISTENT)
		{
			$connection = @pg_pconnect($connectionString);
		}
		else
		{
			$connection = @pg_connect($connectionString);
		}
		restore_error_handler();

		if (!$connection)
		{
			throw new ConnectionException(
				'Pgsql connect error ['.$this->host.']',
				error_get_last()['message']
			);
		}

		$this->resource = $connection;
		$this->isConnected = true;

		$this->afterConnected();
	}

	protected function disconnectInternal()
	{
		if ($this->isConnected)
		{
			$this->isConnected = false;
			try
			{
				pg_close($this->resource);
				$this->resource = null;
			}
			catch (\Throwable)
			{
				// Ignore misterious error
				// pg_close(): supplied resource is not a valid PostgreSQL link resource (0)
			}
		}
	}

	protected function createSqlHelper()
	{
		return new PgsqlSqlHelper($this);
	}

	/**
	 * @inheritDoc
	 */
	protected function queryInternal($sql, array $binds = null, Diag\SqlTrackerQuery $trackerQuery = null)
	{
		$this->connectInternal();

		$trackerQuery?->startQuery($sql, $binds);

		$result = pg_query($this->resource, $sql);

		$trackerQuery?->finishQuery();

		$this->lastQueryResult = $result;

		if (!$result)
		{
			throw new SqlQueryException('Pgsql query error', $this->getErrorMessage(), $sql);
		}

		return $result;
	}

	/**
	 * @inheritDoc
	 */
	protected function createResult($result, Diag\SqlTrackerQuery $trackerQuery = null)
	{
		return new PgsqlResult($result, $this, $trackerQuery);
	}

	/**
	 * @inheritDoc
	 */
	public function add($tableName, array $data, $identity = "ID")
	{
		$insert = $this->getSqlHelper()->prepareInsert($tableName, $data);
		if(
			$identity !== null
			&& (
				!isset($data[$identity])
				|| $data[$identity] instanceof SqlExpression
			)
		)
		{
			$sql = "INSERT INTO ".$tableName."(".$insert[0].") VALUES (".$insert[1].") RETURNING ".$identity;
			$row = $this->query($sql)->fetch();
			return intval(array_shift($row));
		}
		else
		{
			$sql = "INSERT INTO ".$tableName."(".$insert[0].") VALUES (".$insert[1].")";
			$this->query($sql);
			return $data[$identity];
		}
	}

	/**
	 * @inheritDoc
	 */
	public function getInsertedId()
	{
		try
		{
			return (int)$this->query('SELECT bx_lastval() as X')->fetch()['X'];
		}
		catch (SqlQueryException)
		{
			return 0;
		}
	}

	/**
	 * @inheritDoc
	 */
	public function getAffectedRowsCount()
	{
		return pg_affected_rows($this->lastQueryResult);
	}

	/**
	 * @inheritDoc
	 */
	public function isTableExists($tableName)
	{
		$result = $this->query("
			SELECT tablename
			FROM  pg_tables
			WHERE schemaname = 'public'
			AND tablename  = '".$this->getSqlHelper()->forSql($tableName)."'
		");
		$row = $result->fetch();
		return is_array($row);
	}
	/**
	 * @inheritDoc
	 */
	public function isIndexExists($tableName, array $columns)
	{
		return $this->getIndexName($tableName, $columns) !== null;
	}
	/**
	 * @inheritDoc
	 */
	public function getIndexName($tableName, array $columns, $strict = false)
	{
		if (empty($columns))
		{
			return null;
		}

		$tableColumns = [];
		$r = $this->query("
			SELECT a.attnum, a.attname
			FROM pg_class t
			LEFT JOIN pg_attribute a ON a.attrelid = t.oid
			WHERE t.relname = '".$this->getSqlHelper()->forSql($tableName)."'
		");
		while ($a = $r->fetch())
		{
			if ($a['ATTNUM']> 0)
			{
				$tableColumns[$a['ATTNUM']] = $a['ATTNAME'];
			}
		}

		$r = $this->query("
			SELECT relname, indkey, pg_get_expr(pg_index.indexprs, pg_index.indrelid) full_text
			FROM pg_class, pg_index
			WHERE pg_class.oid = pg_index.indexrelid
			AND pg_class.oid IN (
				SELECT indexrelid
				FROM pg_index, pg_class
				WHERE pg_class.relname = '".$this->getSqlHelper()->forSql($tableName)."'
				AND pg_class.oid = pg_index.indrelid
			)
		");
		$indexes = [];
		while ($a = $r->fetch())
		{
			$indexes[$a['RELNAME']] = [];
			if ($a['FULL_TEXT'])
			{
				$match = [];
				if (preg_match_all('/,\s*([a-z0-9_]+)/i', $a['FULL_TEXT'], $match))
				{
					foreach ($match[1] as $i => $colName)
					{
						$indexes[$a['RELNAME']][$i] = mb_strtoupper($colName);
					}
				}
			}
			else
			{
				foreach (explode(' ', $a['INDKEY']) as $i => $indkey)
				{
					$indexes[$a['RELNAME']][$i] = mb_strtoupper($tableColumns[$indkey]);
				}
			}
		}

		return static::findIndex($indexes, $columns, $strict);
	}

	protected static function findIndex(array $indexes, array $columns, $strict)
	{
		$columnsList = mb_strtolower(implode(",", $columns));

		foreach ($indexes as $indexName => $indexColumns)
		{
			ksort($indexColumns);
			$indexColumnList = mb_strtolower(implode(",", $indexColumns));
			if ($strict)
			{
				if ($indexColumnList === $columnsList)
				{
					return $indexName;
				}
			}
			else
			{
				if (str_starts_with($indexColumnList, $columnsList))
				{
					return $indexName;
				}
			}
		}

		return null;
	}

	/**
	 * @inheritDoc
	 */
	public function getTableFields($tableName)
	{
		if (!isset($this->tableColumnsCache[$tableName]) || empty($this->tableColumnsCache[$tableName]))
		{
			$this->connectInternal();

			$sqlHelper = $this->getSqlHelper();
			$query = $this->query("
				SELECT
					column_name,
					data_type,
					character_maximum_length
				FROM
					information_schema.columns
				WHERE
					table_catalog = '" . $sqlHelper->forSql($this->getDatabase()) . "'
					and table_schema = 'public'
					and table_name = '" . $sqlHelper->forSql(mb_strtolower($tableName)) . "'
				ORDER BY
					ordinal_position
			");

			$this->tableColumnsCache[$tableName] = [];
			while ($fieldInfo = $query->fetch())
			{
				$fieldName = mb_strtoupper($fieldInfo['COLUMN_NAME']);
				$fieldType = $fieldInfo['DATA_TYPE'];
				$field = $sqlHelper->getFieldByColumnType($fieldName, $fieldType);
				if (
					$fieldInfo['CHARACTER_MAXIMUM_LENGTH']
					&& is_a($field, '\Bitrix\Main\ORM\Fields\StringField')
				)
				{
					$field->configureSize($fieldInfo['CHARACTER_MAXIMUM_LENGTH']);
				}

				$this->tableColumnsCache[$tableName][$fieldName] = $field;
			}
		}

		return $this->tableColumnsCache[$tableName];
	}

	/**
	 * @inheritDoc
	 */
	public function createTable($tableName, $fields, $primary = array(), $autoincrement = array())
	{
		$sql = 'CREATE TABLE IF NOT EXISTS '.$this->getSqlHelper()->quote($tableName).' (';
		$sqlFields = array();

		foreach ($fields as $columnName => $field)
		{
			if (!($field instanceof ScalarField))
			{
				throw new ArgumentException(sprintf(
					'Field `%s` should be an Entity\ScalarField instance', $columnName
				));
			}

			$realColumnName = $field->getColumnName();

			if (in_array($columnName, $autoincrement, true))
			{
				$type = 'INT GENERATED BY DEFAULT AS IDENTITY'; // size = 4

				if ($field instanceof IntegerField)
				{
					switch ($field->getSize())
					{
						case 2:
							$type = 'SMALLINT GENERATED BY DEFAULT AS IDENTITY';
							break;
						case 8:
							$type = 'BIGINT GENERATED BY DEFAULT AS IDENTITY';
							break;
					}
				}
			}
			else
			{
				$type = $this->getSqlHelper()->getColumnTypeByField($field);
			}
			$sqlFields[] = $this->getSqlHelper()->quote($realColumnName)
				. ' ' . $type
				. ($field->isNullable() ? '' : ' NOT NULL')
			;
		}

		$sql .= join(', ', $sqlFields);

		if (!empty($primary))
		{
			foreach ($primary as &$primaryColumn)
			{
				$realColumnName = $fields[$primaryColumn]->getColumnName();
				$primaryColumn = $this->getSqlHelper()->quote($realColumnName);
			}

			$sql .= ', PRIMARY KEY('.join(', ', $primary).')';
		}

		$sql .= ')';

		$this->query($sql);

	}

	/**
	 * @inheritDoc
	 */
	public function createIndex($tableName, $indexName, $columnNames, $columnLengths = null, $indexType = null)
	{
		if (!is_array($columnNames))
		{
			$columnNames = array($columnNames);
		}

		$sqlHelper = $this->getSqlHelper();

		foreach ($columnNames as &$columnName)
		{
			$columnName = $sqlHelper->quote($columnName);
		}
		unset($columnName);

		if ($indexType === static::INDEX_UNIQUE)
		{
			return $this->query('CREATE UNIQUE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
		}
		elseif ($indexType === static::INDEX_FULLTEXT)
		{
			return $this->query('CREATE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . ' USING GIN (to_tsvector(\'english\', ' . implode(',', $columnNames) . '))');
		}
		else
		{
			return $this->query('CREATE INDEX IF NOT EXISTS ' . $sqlHelper->quote($indexName) . ' ON ' . $sqlHelper->quote($tableName) . '(' . implode(',', $columnNames) . ')');
		}
	}

	/**
	 * @inheritDoc
	 */
	public function renameTable($currentName, $newName)
	{
		$this->query('ALTER TABLE '.$this->getSqlHelper()->quote($currentName).' RENAME TO '.$this->getSqlHelper()->quote($newName));
	}

	/**
	 * @inheritDoc
	 */
	public function dropTable($tableName)
	{
		$this->query('DROP TABLE '.$this->getSqlHelper()->quote($tableName));
	}

	/**
	 * @inheritDoc
	 */
	public function startTransaction()
	{
		if ($this->transactionLevel == 0)
		{
			$this->query("START TRANSACTION");
		}
		else
		{
			$this->query("SAVEPOINT TRANS{$this->transactionLevel}");
		}

		$this->transactionLevel++;
	}

	/**
	 * @inheritDoc
	 */
	public function commitTransaction()
	{
		$this->transactionLevel--;

		if ($this->transactionLevel < 0)
		{
			throw new TransactionException('Transaction was not started.');
		}

		if ($this->transactionLevel == 0)
		{
			// commits all nested transactions
			$this->query("COMMIT");
		}
	}

	/**
	 * @inheritDoc
	 */
	public function rollbackTransaction()
	{
		$this->transactionLevel--;

		if ($this->transactionLevel < 0)
		{
			throw new TransactionException('Transaction was not started.');
		}

		if ($this->transactionLevel == 0)
		{
			$this->query("ROLLBACK");
		}
		else
		{
			$this->query("ROLLBACK TO SAVEPOINT TRANS{$this->transactionLevel}");
		}
	}

	/*********************************************************
	 * Global named lock
	 *********************************************************/

	/**
	 * @inheritDoc
	 */
	public function lock($name, $timeout = 0)
	{
		$timeout = (int)$timeout;
		$name = $this->getLockName($name);

		$sql = 'SELECT bx_get_lock(' . $name . ', ' . $timeout . ') as L';
		$lock = $this->query($sql)->fetch();

		return ($lock['L'] == 0);
	}

	/**
	 * @inheritDoc
	 */
	public function unlock($name)
	{
		$name = $this->getLockName($name);

		$sql = 'SELECT bx_release_lock(' . $name . ') as L';
		$lock = $this->query($sql)->fetch();

		return ($lock['L'] == 0);
	}

	protected function getLockName($name)
	{
		$unique = \CMain::GetServerUniqID();

		return crc32($unique . '|' . $name);
	}

	/**
	 * @inheritDoc
	 */
	public function getType()
	{
		return "pgsql";
	}

	/**
	 * @inheritDoc
	 */
	public function getVersion()
	{
		if ($this->version == null)
		{
			$this->connectInternal();
			$version = trim(pg_version($this->resource)['server']);

			preg_match("#^.*?([0-9]+\\.[0-9]+)#", $version, $ar);
			$this->version = $ar[1];
		}

		return array($this->version, null);
	}

	/**
	 * @inheritDoc
	 */
	public function getErrorMessage()
	{
		return pg_last_error($this->resource);
	}
}