[345] | 1 | <?php |
---|
| 2 | |
---|
| 3 | /* |
---|
| 4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
---|
| 5 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
---|
| 6 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
---|
| 7 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
---|
| 8 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
---|
| 9 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
---|
| 10 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
---|
| 11 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
---|
| 12 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
---|
| 13 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
---|
| 14 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
| 15 | * |
---|
| 16 | * This software consists of voluntary contributions made by many individuals |
---|
| 17 | * and is licensed under the LGPL. For more information, see |
---|
| 18 | * <http://www.doctrine-project.org>. |
---|
| 19 | */ |
---|
| 20 | |
---|
| 21 | namespace Doctrine\DBAL\Schema; |
---|
| 22 | |
---|
| 23 | /** |
---|
| 24 | * PostgreSQL Schema Manager |
---|
| 25 | * |
---|
| 26 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL |
---|
| 27 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi> |
---|
| 28 | * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library) |
---|
| 29 | * @author Benjamin Eberlei <kontakt@beberlei.de> |
---|
| 30 | * @since 2.0 |
---|
| 31 | */ |
---|
| 32 | class PostgreSqlSchemaManager extends AbstractSchemaManager |
---|
| 33 | { |
---|
| 34 | /** |
---|
| 35 | * @var array |
---|
| 36 | */ |
---|
| 37 | private $existingSchemaPaths; |
---|
| 38 | |
---|
| 39 | /** |
---|
| 40 | * Get all the existing schema names. |
---|
| 41 | * |
---|
| 42 | * @return array |
---|
| 43 | */ |
---|
| 44 | public function getSchemaNames() |
---|
| 45 | { |
---|
| 46 | $rows = $this->_conn->fetchAll('SELECT schema_name FROM information_schema.schemata'); |
---|
| 47 | return array_map(function($v) { return $v['schema_name']; }, $rows); |
---|
| 48 | } |
---|
| 49 | |
---|
| 50 | /** |
---|
| 51 | * Return an array of schema search paths |
---|
| 52 | * |
---|
| 53 | * This is a PostgreSQL only function. |
---|
| 54 | * |
---|
| 55 | * @return array |
---|
| 56 | */ |
---|
| 57 | public function getSchemaSearchPaths() |
---|
| 58 | { |
---|
| 59 | $params = $this->_conn->getParams(); |
---|
| 60 | $schema = explode(",", $this->_conn->fetchColumn('SHOW search_path')); |
---|
| 61 | if (isset($params['user'])) { |
---|
| 62 | $schema = str_replace('"$user"', $params['user'], $schema); |
---|
| 63 | } |
---|
| 64 | return $schema; |
---|
| 65 | } |
---|
| 66 | |
---|
| 67 | /** |
---|
| 68 | * Get names of all existing schemas in the current users search path. |
---|
| 69 | * |
---|
| 70 | * This is a PostgreSQL only function. |
---|
| 71 | * |
---|
| 72 | * @return array |
---|
| 73 | */ |
---|
| 74 | public function getExistingSchemaSearchPaths() |
---|
| 75 | { |
---|
| 76 | if ($this->existingSchemaPaths === null) { |
---|
| 77 | $this->determineExistingSchemaSearchPaths(); |
---|
| 78 | } |
---|
| 79 | return $this->existingSchemaPaths; |
---|
| 80 | } |
---|
| 81 | |
---|
| 82 | /** |
---|
| 83 | * Use this to set or reset the order of the existing schemas in the current search path of the user |
---|
| 84 | * |
---|
| 85 | * This is a PostgreSQL only function. |
---|
| 86 | * |
---|
| 87 | * @return type |
---|
| 88 | */ |
---|
| 89 | public function determineExistingSchemaSearchPaths() |
---|
| 90 | { |
---|
| 91 | $names = $this->getSchemaNames(); |
---|
| 92 | $paths = $this->getSchemaSearchPaths(); |
---|
| 93 | |
---|
| 94 | $this->existingSchemaPaths = array_filter($paths, function ($v) use ($names) { |
---|
| 95 | return in_array($v, $names); |
---|
| 96 | }); |
---|
| 97 | } |
---|
| 98 | |
---|
| 99 | protected function _getPortableTableForeignKeyDefinition($tableForeignKey) |
---|
| 100 | { |
---|
| 101 | $onUpdate = null; |
---|
| 102 | $onDelete = null; |
---|
| 103 | |
---|
| 104 | if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { |
---|
| 105 | $onUpdate = $match[1]; |
---|
| 106 | } |
---|
| 107 | if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { |
---|
| 108 | $onDelete = $match[1]; |
---|
| 109 | } |
---|
| 110 | |
---|
| 111 | if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) { |
---|
| 112 | // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get |
---|
| 113 | // the idea to trim them here. |
---|
| 114 | $localColumns = array_map('trim', explode(",", $values[1])); |
---|
| 115 | $foreignColumns = array_map('trim', explode(",", $values[3])); |
---|
| 116 | $foreignTable = $values[2]; |
---|
| 117 | } |
---|
| 118 | |
---|
| 119 | return new ForeignKeyConstraint( |
---|
| 120 | $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], |
---|
| 121 | array('onUpdate' => $onUpdate, 'onDelete' => $onDelete) |
---|
| 122 | ); |
---|
| 123 | } |
---|
| 124 | |
---|
| 125 | public function dropDatabase($database) |
---|
| 126 | { |
---|
| 127 | $params = $this->_conn->getParams(); |
---|
| 128 | $params["dbname"] = "postgres"; |
---|
| 129 | $tmpPlatform = $this->_platform; |
---|
| 130 | $tmpConn = $this->_conn; |
---|
| 131 | |
---|
| 132 | $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); |
---|
| 133 | $this->_platform = $this->_conn->getDatabasePlatform(); |
---|
| 134 | |
---|
| 135 | parent::dropDatabase($database); |
---|
| 136 | |
---|
| 137 | $this->_platform = $tmpPlatform; |
---|
| 138 | $this->_conn = $tmpConn; |
---|
| 139 | } |
---|
| 140 | |
---|
| 141 | public function createDatabase($database) |
---|
| 142 | { |
---|
| 143 | $params = $this->_conn->getParams(); |
---|
| 144 | $params["dbname"] = "postgres"; |
---|
| 145 | $tmpPlatform = $this->_platform; |
---|
| 146 | $tmpConn = $this->_conn; |
---|
| 147 | |
---|
| 148 | $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); |
---|
| 149 | $this->_platform = $this->_conn->getDatabasePlatform(); |
---|
| 150 | |
---|
| 151 | parent::createDatabase($database); |
---|
| 152 | |
---|
| 153 | $this->_platform = $tmpPlatform; |
---|
| 154 | $this->_conn = $tmpConn; |
---|
| 155 | } |
---|
| 156 | |
---|
| 157 | protected function _getPortableTriggerDefinition($trigger) |
---|
| 158 | { |
---|
| 159 | return $trigger['trigger_name']; |
---|
| 160 | } |
---|
| 161 | |
---|
| 162 | protected function _getPortableViewDefinition($view) |
---|
| 163 | { |
---|
| 164 | return new View($view['viewname'], $view['definition']); |
---|
| 165 | } |
---|
| 166 | |
---|
| 167 | protected function _getPortableUserDefinition($user) |
---|
| 168 | { |
---|
| 169 | return array( |
---|
| 170 | 'user' => $user['usename'], |
---|
| 171 | 'password' => $user['passwd'] |
---|
| 172 | ); |
---|
| 173 | } |
---|
| 174 | |
---|
| 175 | protected function _getPortableTableDefinition($table) |
---|
| 176 | { |
---|
| 177 | $schemas = $this->getExistingSchemaSearchPaths(); |
---|
| 178 | $firstSchema = array_shift($schemas); |
---|
| 179 | |
---|
| 180 | if ($table['schema_name'] == $firstSchema) { |
---|
| 181 | return $table['table_name']; |
---|
| 182 | } else { |
---|
| 183 | return $table['schema_name'] . "." . $table['table_name']; |
---|
| 184 | } |
---|
| 185 | } |
---|
| 186 | |
---|
| 187 | /** |
---|
| 188 | * @license New BSD License |
---|
| 189 | * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html |
---|
| 190 | * @param array $tableIndexes |
---|
| 191 | * @param string $tableName |
---|
| 192 | * @return array |
---|
| 193 | */ |
---|
| 194 | protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) |
---|
| 195 | { |
---|
| 196 | $buffer = array(); |
---|
| 197 | foreach ($tableIndexes AS $row) { |
---|
| 198 | $colNumbers = explode(' ', $row['indkey']); |
---|
| 199 | $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )'; |
---|
| 200 | $columnNameSql = "SELECT attnum, attname FROM pg_attribute |
---|
| 201 | WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;"; |
---|
| 202 | |
---|
| 203 | $stmt = $this->_conn->executeQuery($columnNameSql); |
---|
| 204 | $indexColumns = $stmt->fetchAll(); |
---|
| 205 | |
---|
| 206 | // required for getting the order of the columns right. |
---|
| 207 | foreach ($colNumbers AS $colNum) { |
---|
| 208 | foreach ($indexColumns as $colRow) { |
---|
| 209 | if ($colNum == $colRow['attnum']) { |
---|
| 210 | $buffer[] = array( |
---|
| 211 | 'key_name' => $row['relname'], |
---|
| 212 | 'column_name' => trim($colRow['attname']), |
---|
| 213 | 'non_unique' => !$row['indisunique'], |
---|
| 214 | 'primary' => $row['indisprimary'] |
---|
| 215 | ); |
---|
| 216 | } |
---|
| 217 | } |
---|
| 218 | } |
---|
| 219 | } |
---|
| 220 | return parent::_getPortableTableIndexesList($buffer, $tableName); |
---|
| 221 | } |
---|
| 222 | |
---|
| 223 | protected function _getPortableDatabaseDefinition($database) |
---|
| 224 | { |
---|
| 225 | return $database['datname']; |
---|
| 226 | } |
---|
| 227 | |
---|
| 228 | protected function _getPortableSequenceDefinition($sequence) |
---|
| 229 | { |
---|
| 230 | if ($sequence['schemaname'] != 'public') { |
---|
| 231 | $sequenceName = $sequence['schemaname'] . "." . $sequence['relname']; |
---|
| 232 | } else { |
---|
| 233 | $sequenceName = $sequence['relname']; |
---|
| 234 | } |
---|
| 235 | |
---|
| 236 | $data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $sequenceName); |
---|
| 237 | return new Sequence($sequenceName, $data[0]['increment_by'], $data[0]['min_value']); |
---|
| 238 | } |
---|
| 239 | |
---|
| 240 | protected function _getPortableTableColumnDefinition($tableColumn) |
---|
| 241 | { |
---|
| 242 | $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); |
---|
| 243 | |
---|
| 244 | if (strtolower($tableColumn['type']) === 'varchar') { |
---|
| 245 | // get length from varchar definition |
---|
| 246 | $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); |
---|
| 247 | $tableColumn['length'] = $length; |
---|
| 248 | } |
---|
| 249 | |
---|
| 250 | $matches = array(); |
---|
| 251 | |
---|
| 252 | $autoincrement = false; |
---|
| 253 | if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { |
---|
| 254 | $tableColumn['sequence'] = $matches[1]; |
---|
| 255 | $tableColumn['default'] = null; |
---|
| 256 | $autoincrement = true; |
---|
| 257 | } |
---|
| 258 | |
---|
| 259 | if (stripos($tableColumn['default'], 'NULL') === 0) { |
---|
| 260 | $tableColumn['default'] = null; |
---|
| 261 | } |
---|
| 262 | |
---|
| 263 | $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null; |
---|
| 264 | if ($length == '-1' && isset($tableColumn['atttypmod'])) { |
---|
| 265 | $length = $tableColumn['atttypmod'] - 4; |
---|
| 266 | } |
---|
| 267 | if ((int) $length <= 0) { |
---|
| 268 | $length = null; |
---|
| 269 | } |
---|
| 270 | $fixed = null; |
---|
| 271 | |
---|
| 272 | if (!isset($tableColumn['name'])) { |
---|
| 273 | $tableColumn['name'] = ''; |
---|
| 274 | } |
---|
| 275 | |
---|
| 276 | $precision = null; |
---|
| 277 | $scale = null; |
---|
| 278 | |
---|
| 279 | $dbType = strtolower($tableColumn['type']); |
---|
| 280 | if (strlen($tableColumn['domain_type']) && !$this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) { |
---|
| 281 | $dbType = strtolower($tableColumn['domain_type']); |
---|
| 282 | $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; |
---|
| 283 | } |
---|
| 284 | |
---|
| 285 | $type = $this->_platform->getDoctrineTypeMapping($dbType); |
---|
| 286 | $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); |
---|
| 287 | $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); |
---|
| 288 | |
---|
| 289 | switch ($dbType) { |
---|
| 290 | case 'smallint': |
---|
| 291 | case 'int2': |
---|
| 292 | $length = null; |
---|
| 293 | break; |
---|
| 294 | case 'int': |
---|
| 295 | case 'int4': |
---|
| 296 | case 'integer': |
---|
| 297 | $length = null; |
---|
| 298 | break; |
---|
| 299 | case 'bigint': |
---|
| 300 | case 'int8': |
---|
| 301 | $length = null; |
---|
| 302 | break; |
---|
| 303 | case 'bool': |
---|
| 304 | case 'boolean': |
---|
| 305 | $length = null; |
---|
| 306 | break; |
---|
| 307 | case 'text': |
---|
| 308 | $fixed = false; |
---|
| 309 | break; |
---|
| 310 | case 'varchar': |
---|
| 311 | case 'interval': |
---|
| 312 | case '_varchar': |
---|
| 313 | $fixed = false; |
---|
| 314 | break; |
---|
| 315 | case 'char': |
---|
| 316 | case 'bpchar': |
---|
| 317 | $fixed = true; |
---|
| 318 | break; |
---|
| 319 | case 'float': |
---|
| 320 | case 'float4': |
---|
| 321 | case 'float8': |
---|
| 322 | case 'double': |
---|
| 323 | case 'double precision': |
---|
| 324 | case 'real': |
---|
| 325 | case 'decimal': |
---|
| 326 | case 'money': |
---|
| 327 | case 'numeric': |
---|
| 328 | if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { |
---|
| 329 | $precision = $match[1]; |
---|
| 330 | $scale = $match[2]; |
---|
| 331 | $length = null; |
---|
| 332 | } |
---|
| 333 | break; |
---|
| 334 | case 'year': |
---|
| 335 | $length = null; |
---|
| 336 | break; |
---|
| 337 | } |
---|
| 338 | |
---|
| 339 | if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) { |
---|
| 340 | $tableColumn['default'] = $match[1]; |
---|
| 341 | } |
---|
| 342 | |
---|
| 343 | $options = array( |
---|
| 344 | 'length' => $length, |
---|
| 345 | 'notnull' => (bool) $tableColumn['isnotnull'], |
---|
| 346 | 'default' => $tableColumn['default'], |
---|
| 347 | 'primary' => (bool) ($tableColumn['pri'] == 't'), |
---|
| 348 | 'precision' => $precision, |
---|
| 349 | 'scale' => $scale, |
---|
| 350 | 'fixed' => $fixed, |
---|
| 351 | 'unsigned' => false, |
---|
| 352 | 'autoincrement' => $autoincrement, |
---|
| 353 | 'comment' => $tableColumn['comment'], |
---|
| 354 | ); |
---|
| 355 | |
---|
| 356 | return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); |
---|
| 357 | } |
---|
| 358 | |
---|
| 359 | } |
---|