mirror of
https://github.com/silverstripe/silverstripe-postgresql
synced 2024-10-22 17:05:45 +02:00
Compare commits
No commits in common. "2" and "2.0.0" have entirely different histories.
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@ -1,25 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
name: CI
|
|
||||||
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1
|
|
||||||
with:
|
|
||||||
# set phpunit to false to prevent automatic generation of mysql phpunit jobs
|
|
||||||
phpunit: false
|
|
||||||
extra_jobs: |
|
|
||||||
- php: 7.4
|
|
||||||
db: pgsql
|
|
||||||
phpunit: true
|
|
||||||
composer_args: --prefer-lowest
|
|
||||||
- php: 8.0
|
|
||||||
db: pgsql
|
|
||||||
phpunit: true
|
|
||||||
- php: 8.1
|
|
||||||
db: pgsql
|
|
||||||
phpunit: true
|
|
16
.github/workflows/dispatch-ci.yml
vendored
16
.github/workflows/dispatch-ci.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Dispatch CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
# At 12:20 PM UTC, only on Thursday and Friday
|
|
||||||
schedule:
|
|
||||||
- cron: '20 12 * * 4,5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dispatch-ci:
|
|
||||||
name: Dispatch CI
|
|
||||||
# Only run cron on the silverstripe account
|
|
||||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Dispatch CI
|
|
||||||
uses: silverstripe/gha-dispatch-ci@v1
|
|
17
.github/workflows/keepalive.yml
vendored
17
.github/workflows/keepalive.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
name: Keepalive
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
# The 4th of every month at 10:50am UTC
|
|
||||||
schedule:
|
|
||||||
- cron: '50 10 4 * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
keepalive:
|
|
||||||
name: Keepalive
|
|
||||||
# Only run cron on the silverstripe account
|
|
||||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Keepalive
|
|
||||||
uses: silverstripe/gha-keepalive@v1
|
|
15
.scrutinizer.yml
Normal file
15
.scrutinizer.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
inherit: true
|
||||||
|
|
||||||
|
build:
|
||||||
|
nodes:
|
||||||
|
analysis:
|
||||||
|
tests:
|
||||||
|
override: [php-scrutinizer-run]
|
||||||
|
|
||||||
|
checks:
|
||||||
|
php:
|
||||||
|
code_rating: true
|
||||||
|
duplication: true
|
||||||
|
|
||||||
|
filter:
|
||||||
|
paths: [code/*, tests/*]
|
43
.travis.yml
Normal file
43
.travis.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
language: php
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.composer/cache/files
|
||||||
|
|
||||||
|
php:
|
||||||
|
- 5.6
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- DB="PGSQL"
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
include:
|
||||||
|
- php: 5.6
|
||||||
|
env:
|
||||||
|
- PHPUNIT_TEST=framework
|
||||||
|
- php: 5.6
|
||||||
|
env:
|
||||||
|
- PHPUNIT_TEST=postgresql
|
||||||
|
- PHPCS_TEST=1
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
# Init PHP
|
||||||
|
- phpenv rehash
|
||||||
|
- phpenv config-rm xdebug.ini
|
||||||
|
- export PATH=~/.composer/vendor/bin:$PATH
|
||||||
|
- echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
|
||||||
|
|
||||||
|
# Install composer dependencies
|
||||||
|
- composer validate
|
||||||
|
- composer require --no-update silverstripe/recipe-cms:1.0.x-dev
|
||||||
|
- composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile
|
||||||
|
- if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi
|
||||||
|
|
||||||
|
script:
|
||||||
|
- if [[ $PHPUNIT_TEST == postgresql ]]; then vendor/bin/phpunit ./tests; fi
|
||||||
|
- if [[ $PHPUNIT_TEST == framework ]]; then vendor/bin/phpunit ./vendor/silverstripe/framework/tests/php; fi
|
||||||
|
- if [[ $PHPCS_TEST ]]; then composer run-script lint; fi
|
48
README.md
48
README.md
@ -1,54 +1,34 @@
|
|||||||
# PostgreSQL Module Module
|
# PostgreSQL Module Module
|
||||||
|
|
||||||
[![CI](https://github.com/silverstripe/silverstripe-postgresql/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-postgresql/actions/workflows/ci.yml)
|
[![Build Status](https://travis-ci.org/silverstripe/silverstripe-postgresql.png?branch=master)](https://travis-ci.org/silverstripe/silverstripe-postgresql)
|
||||||
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
|
|
||||||
|
## Maintainer Contact
|
||||||
|
|
||||||
|
* Sam Minnee (Nickname: sminnee) <sam@silverstripe.com>
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* Silverstripe 4.0
|
* SilverStripe 4.0
|
||||||
* PostgreSQL >=9.2
|
* PostgreSQL 8.3.x or greater must be installed
|
||||||
* Note: PostgreSQL 10 has not been tested
|
* PostgreSQL <8.3.0 may work if T-Search is manually installed
|
||||||
|
* Known to work on OS X Leopard, Windows Server 2008 R2 and Linux
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```
|
1. Install via composer `composer require silverstripe/postgresql` or extract the contents
|
||||||
composer require silverstripe/postgresql
|
so they reside as a `postgresql` directory inside your SilverStripe project code
|
||||||
```
|
2. Open the installer by browsing to install.php, e.g. http://localhost/silverstripe/install.php
|
||||||
|
3. Select PostgreSQL in the database list and enter your database details
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment file
|
|
||||||
|
|
||||||
Add the following settings to your `.env` file:
|
|
||||||
|
|
||||||
```
|
|
||||||
SS_DATABASE_CLASS=PostgreSQLDatabase
|
|
||||||
SS_DATABASE_USERNAME=
|
|
||||||
SS_DATABASE_PASSWORD=
|
|
||||||
```
|
|
||||||
|
|
||||||
See [environment variables](https://docs.silverstripe.org/en/4/getting_started/environment_management) for more details. Note that a database will automatically be created via `dev/build`.
|
|
||||||
|
|
||||||
### Through the installer
|
|
||||||
|
|
||||||
Open the installer by browsing to install.php, e.g. http://localhost/install.php
|
|
||||||
Select PostgreSQL in the database list and enter your database details
|
|
||||||
|
|
||||||
## Usage Overview
|
## Usage Overview
|
||||||
|
|
||||||
See [docs/en](docs/en/README.md) for more information about configuring the module.
|
See docs/en for more information about configuring the module.
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
All column and table names must be double-quoted. PostgreSQL automatically
|
All column and table names must be double-quoted. PostgreSQL automatically
|
||||||
lower-cases columns, and your queries will fail if you don't.
|
lower-cases columns, and your queries will fail if you don't.
|
||||||
|
|
||||||
Collations have known issues when installed on Alpine, MacOS X and BSD derivatives
|
|
||||||
(see [PostgreSQL FAQ](https://wiki.postgresql.org/wiki/FAQ#Why_do_my_strings_sort_incorrectly.3F)).
|
|
||||||
We do not support such installations, although they still may work correctly for you.
|
|
||||||
As a workaround for PostgreSQL 10+ you could manually switch to ICU collations (e.g. und-x-icu).
|
|
||||||
There are no known workarounds for PostgreSQL <10.
|
|
||||||
|
|
||||||
Ts_vector columns are not automatically detected by the built-in search
|
Ts_vector columns are not automatically detected by the built-in search
|
||||||
filters. That means if you're doing a search through the CMS on a ModelAdmin
|
filters. That means if you're doing a search through the CMS on a ModelAdmin
|
||||||
object, it will use LIKE queries which are very slow. If you're writing your
|
object, it will use LIKE queries which are very slow. If you're writing your
|
||||||
|
@ -36,11 +36,6 @@ class PostgreSQLDatabase extends Database
|
|||||||
*/
|
*/
|
||||||
protected $schema;
|
protected $schema;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $transactionNesting = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle if transactions are supported. Defaults to true.
|
* Toggle if transactions are supported. Defaults to true.
|
||||||
*
|
*
|
||||||
@ -367,9 +362,6 @@ class PostgreSQLDatabase extends Database
|
|||||||
*/
|
*/
|
||||||
public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "ts_rank DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false)
|
public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "ts_rank DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false)
|
||||||
{
|
{
|
||||||
$start = (int)$start;
|
|
||||||
$pageLength = (int)$pageLength;
|
|
||||||
|
|
||||||
//Fix the keywords to be ts_query compatitble:
|
//Fix the keywords to be ts_query compatitble:
|
||||||
//Spaces must have pipes
|
//Spaces must have pipes
|
||||||
//@TODO: properly handle boolean operators here.
|
//@TODO: properly handle boolean operators here.
|
||||||
@ -448,7 +440,7 @@ class PostgreSQLDatabase extends Database
|
|||||||
|
|
||||||
// Could parameterise this, but convention is only to to so for where conditions
|
// Could parameterise this, but convention is only to to so for where conditions
|
||||||
$query->addFrom(array(
|
$query->addFrom(array(
|
||||||
'q' => ", to_tsquery('" . self::search_language() . "', $keywords)"
|
'tsearch' => ", to_tsquery('" . self::search_language() . "', $keywords) AS q"
|
||||||
));
|
));
|
||||||
$query->setSelect(array());
|
$query->setSelect(array());
|
||||||
|
|
||||||
@ -524,9 +516,6 @@ class PostgreSQLDatabase extends Database
|
|||||||
|
|
||||||
public function transactionStart($transaction_mode = false, $session_characteristics = false)
|
public function transactionStart($transaction_mode = false, $session_characteristics = false)
|
||||||
{
|
{
|
||||||
if ($this->transactionNesting > 0) {
|
|
||||||
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
|
|
||||||
} else {
|
|
||||||
$this->query('BEGIN;');
|
$this->query('BEGIN;');
|
||||||
|
|
||||||
if ($transaction_mode) {
|
if ($transaction_mode) {
|
||||||
@ -537,8 +526,6 @@ class PostgreSQLDatabase extends Database
|
|||||||
$this->query("SET SESSION CHARACTERISTICS AS TRANSACTION {$session_characteristics};");
|
$this->query("SET SESSION CHARACTERISTICS AS TRANSACTION {$session_characteristics};");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++$this->transactionNesting;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function transactionSavepoint($savepoint)
|
public function transactionSavepoint($savepoint)
|
||||||
{
|
{
|
||||||
@ -547,41 +534,17 @@ class PostgreSQLDatabase extends Database
|
|||||||
|
|
||||||
public function transactionRollback($savepoint = false)
|
public function transactionRollback($savepoint = false)
|
||||||
{
|
{
|
||||||
// Named savepoint
|
|
||||||
if ($savepoint) {
|
if ($savepoint) {
|
||||||
$this->query('ROLLBACK TO ' . $savepoint);
|
$this->query("ROLLBACK TO {$savepoint};");
|
||||||
return true;
|
} else {
|
||||||
|
$this->query('ROLLBACK;');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort if unable to unnest, otherwise jump up a level
|
|
||||||
if (!$this->transactionNesting) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
--$this->transactionNesting;
|
|
||||||
|
|
||||||
// Rollback nested
|
|
||||||
if ($this->transactionNesting > 0) {
|
|
||||||
return $this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback top level
|
|
||||||
$this->query('ROLLBACK');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function transactionDepth()
|
|
||||||
{
|
|
||||||
return $this->transactionNesting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transactionEnd($chain = false)
|
public function transactionEnd($chain = false)
|
||||||
{
|
{
|
||||||
--$this->transactionNesting;
|
|
||||||
if ($this->transactionNesting <= 0) {
|
|
||||||
$this->transactionNesting = 0;
|
|
||||||
$this->query('COMMIT;');
|
$this->query('COMMIT;');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false)
|
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false)
|
||||||
{
|
{
|
||||||
@ -620,7 +583,7 @@ class PostgreSQLDatabase extends Database
|
|||||||
{
|
{
|
||||||
preg_match_all('/%(.)/', $format, $matches);
|
preg_match_all('/%(.)/', $format, $matches);
|
||||||
foreach ($matches[1] as $match) {
|
foreach ($matches[1] as $match) {
|
||||||
if (array_search($match, array('Y','m','d','H','i','s','U')) === false) {
|
if (array_search($match, array('Y', 'm', 'd', 'H', 'i', 's', 'U')) === false) {
|
||||||
user_error('formattedDatetimeClause(): unsupported format character %' . $match, E_USER_WARNING);
|
user_error('formattedDatetimeClause(): unsupported format character %' . $match, E_USER_WARNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,6 @@ class PostgreSQLQuery extends Query
|
|||||||
*/
|
*/
|
||||||
private $handle;
|
private $handle;
|
||||||
|
|
||||||
private $columnNames = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping of postgresql types to PHP types
|
|
||||||
* Note that the bool => int mapping is by design, designed to mimic MySQL's behaviour
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected static $typeMapping = [
|
|
||||||
'bool' => 'int',
|
|
||||||
'int2' => 'int',
|
|
||||||
'int4' => 'int',
|
|
||||||
'int8' => 'int',
|
|
||||||
'float4' => 'float',
|
|
||||||
'float8' => 'float',
|
|
||||||
'numeric' => 'float',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook the result-set given into a Query class, suitable for use by sapphire.
|
* Hook the result-set given into a Query class, suitable for use by sapphire.
|
||||||
* @param resource $handle the internal Postgres handle that is points to the resultset.
|
* @param resource $handle the internal Postgres handle that is points to the resultset.
|
||||||
@ -42,11 +25,6 @@ class PostgreSQLQuery extends Query
|
|||||||
public function __construct($handle)
|
public function __construct($handle)
|
||||||
{
|
{
|
||||||
$this->handle = $handle;
|
$this->handle = $handle;
|
||||||
|
|
||||||
$numColumns = pg_num_fields($handle);
|
|
||||||
for ($i = 0; $i<$numColumns; $i++) {
|
|
||||||
$this->columnNames[$i] = pg_field_name($handle, $i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
@ -58,10 +36,8 @@ class PostgreSQLQuery extends Query
|
|||||||
|
|
||||||
public function seek($row)
|
public function seek($row)
|
||||||
{
|
{
|
||||||
// Specifying the zero-th record here will reset the pointer
|
pg_result_seek($this->handle, $row);
|
||||||
$result = pg_fetch_array($this->handle, $row, PGSQL_NUM);
|
return pg_fetch_assoc($this->handle);
|
||||||
|
|
||||||
return $this->parseResult($result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function numRecords()
|
public function numRecords()
|
||||||
@ -71,39 +47,6 @@ class PostgreSQLQuery extends Query
|
|||||||
|
|
||||||
public function nextRecord()
|
public function nextRecord()
|
||||||
{
|
{
|
||||||
$row = pg_fetch_array($this->handle, null, PGSQL_NUM);
|
return pg_fetch_assoc($this->handle);
|
||||||
|
|
||||||
// Correct non-string types
|
|
||||||
if ($row) {
|
|
||||||
return $this->parseResult($row);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $row
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function parseResult(array $row)
|
|
||||||
{
|
|
||||||
$record = [];
|
|
||||||
|
|
||||||
foreach ($row as $i => $v) {
|
|
||||||
$k = $this->columnNames[$i];
|
|
||||||
$record[$k] = $v;
|
|
||||||
$type = pg_field_type($this->handle, $i);
|
|
||||||
if (isset(self::$typeMapping[$type])) {
|
|
||||||
if ($type === 'bool' && $record[$k] === 't') {
|
|
||||||
$record[$k] = 1;
|
|
||||||
|
|
||||||
// Note that boolean 'f' will be converted to 0 by this
|
|
||||||
} else {
|
|
||||||
settype($record[$k], self::$typeMapping[$type]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $record;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -494,15 +494,9 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
// First, we split the column specifications into parts
|
// First, we split the column specifications into parts
|
||||||
// TODO: this returns an empty array for the following string: int(11) not null auto_increment
|
// TODO: this returns an empty array for the following string: int(11) not null auto_increment
|
||||||
// on second thoughts, why is an auto_increment field being passed through?
|
// on second thoughts, why is an auto_increment field being passed through?
|
||||||
$pattern = '/^([\w(\,)]+)\s?((?:not\s)?null)?\s?(default\s[\w\.\'\\\\]+)?\s?(check\s[\w()\'",\s\\\\]+)?$/i';
|
|
||||||
|
$pattern = '/^([\w(\,)]+)\s?((?:not\s)?null)?\s?(default\s[\w\.\']+)?\s?(check\s[\w()\'",\s]+)?$/i';
|
||||||
preg_match($pattern, $colSpec, $matches);
|
preg_match($pattern, $colSpec, $matches);
|
||||||
// example value this regex is expected to parse:
|
|
||||||
// varchar(255) not null default 'SS\Test\Player' check ("ClassName" in ('SS\Test\Player', 'Player', null))
|
|
||||||
// split into:
|
|
||||||
// * varchar(255)
|
|
||||||
// * not null
|
|
||||||
// * default 'SS\Test\Player'
|
|
||||||
// * check ("ClassName" in ('SS\Test\Player', 'Player', null))
|
|
||||||
|
|
||||||
if (sizeof($matches) == 0) {
|
if (sizeof($matches) == 0) {
|
||||||
return '';
|
return '';
|
||||||
@ -543,8 +537,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
if ($this->hasTable("{$tableName}_Live")) {
|
if ($this->hasTable("{$tableName}_Live")) {
|
||||||
$updateConstraint .= "UPDATE \"{$tableName}_Live\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
$updateConstraint .= "UPDATE \"{$tableName}_Live\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
||||||
}
|
}
|
||||||
if ($this->hasTable("{$tableName}_Versions")) {
|
if ($this->hasTable("{$tableName}_versions")) {
|
||||||
$updateConstraint .= "UPDATE \"{$tableName}_Versions\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
$updateConstraint .= "UPDATE \"{$tableName}_versions\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->query($updateConstraint);
|
$this->query($updateConstraint);
|
||||||
@ -566,17 +560,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
|
|
||||||
public function renameTable($oldTableName, $newTableName)
|
public function renameTable($oldTableName, $newTableName)
|
||||||
{
|
{
|
||||||
$constraints = $this->getConstraintForTable($oldTableName);
|
|
||||||
$this->query("ALTER TABLE \"$oldTableName\" RENAME TO \"$newTableName\"");
|
$this->query("ALTER TABLE \"$oldTableName\" RENAME TO \"$newTableName\"");
|
||||||
|
|
||||||
if ($constraints) {
|
|
||||||
foreach ($constraints as $old) {
|
|
||||||
$new = preg_replace('/^' . $oldTableName . '/', $newTableName, $old);
|
|
||||||
$this->query("ALTER TABLE \"$newTableName\" RENAME CONSTRAINT \"$old\" TO \"$new\";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unset(self::$cached_fieldlists[$oldTableName]);
|
unset(self::$cached_fieldlists[$oldTableName]);
|
||||||
unset(self::$cached_constraints[$oldTableName]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkAndRepairTable($tableName)
|
public function checkAndRepairTable($tableName)
|
||||||
@ -858,9 +843,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
$argList = array();
|
$argList = array();
|
||||||
$nextArg = "";
|
$nextArg = "";
|
||||||
foreach ($bytes as $byte) {
|
foreach ($bytes as $byte) {
|
||||||
if ($byte == '\x') {
|
if ($byte == "00") {
|
||||||
continue;
|
|
||||||
} elseif ($byte == "00") {
|
|
||||||
$argList[] = $nextArg;
|
$argList[] = $nextArg;
|
||||||
$nextArg = "";
|
$nextArg = "";
|
||||||
} else {
|
} else {
|
||||||
@ -978,28 +961,6 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
return self::$cached_constraints[$constraint];
|
return self::$cached_constraints[$constraint];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a list of constraints for the provided table name.
|
|
||||||
* @param string $tableName
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getConstraintForTable($tableName)
|
|
||||||
{
|
|
||||||
// Note the PostgreSQL `like` operator is case sensitive
|
|
||||||
$constraints = $this->preparedQuery(
|
|
||||||
"
|
|
||||||
SELECT conname
|
|
||||||
FROM pg_catalog.pg_constraint r
|
|
||||||
INNER JOIN pg_catalog.pg_namespace n
|
|
||||||
ON r.connamespace = n.oid
|
|
||||||
WHERE r.contype = 'c' AND conname like ? AND n.nspname = ?
|
|
||||||
ORDER BY 1;",
|
|
||||||
array($tableName . '_%', $this->database->currentSchema())
|
|
||||||
)->column('conname');
|
|
||||||
|
|
||||||
return $constraints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function to return the field names and datatypes for the particular table
|
* A function to return the field names and datatypes for the particular table
|
||||||
*
|
*
|
||||||
|
@ -15,12 +15,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"silverstripe/framework": "^4.10",
|
"silverstripe/framework": "^4",
|
||||||
"silverstripe/vendor-plugin": "^1.0"
|
"silverstripe/vendor-plugin": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^5.7"
|
||||||
"squizlabs/php_codesniffer": "^3"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -1,5 +1,86 @@
|
|||||||
# PostgreSQL Database Module
|
# PostgreSQL Database Module
|
||||||
|
|
||||||
|
SilverStripe now has tentative support for PostgreSQL ('Postgres').
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
SilverStripe 2.4.0 or greater. (PostgreSQL support is NOT available
|
||||||
|
in 2.3.).
|
||||||
|
|
||||||
|
SilverStripe supports Postgres versions 8.3.x, 8.4.x and onwards.
|
||||||
|
Postgres 8.3.0 launched in February 2008, so SilverStripe has a fairly
|
||||||
|
modern but not bleeding edge Postgres version requirement.
|
||||||
|
|
||||||
|
Support for 8.2.x is theoretically possible if you're willing to manually
|
||||||
|
install T-search. 8.2.x has not been tested either, so there may be other
|
||||||
|
compatibility issues. The EnterpriseDB versions of Postgres also work, if
|
||||||
|
you'd prefer a tuned version.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You have three options to install PostgreSQL support with SilverStripe.
|
||||||
|
|
||||||
|
### Option 1 - Installer
|
||||||
|
|
||||||
|
The first option is to use the installer. However, this is currently only
|
||||||
|
supported since SilverStripe 2.4.0 beta2 (or using the daily builds).
|
||||||
|
|
||||||
|
1. Set up SilverStripe somewhere where you can start the installer - you
|
||||||
|
should only see one database “MySQL” to install with.
|
||||||
|
2. Download a copy of the “postgresql” module from here:
|
||||||
|
http://silverstripe.org/postgresql-module
|
||||||
|
3. Extract the archive you downloaded. Rename the directory from
|
||||||
|
“postgresql-trunk-rxxxx” to “postgresql” and copy it into the SilverStripe
|
||||||
|
directory you just set up
|
||||||
|
4. Open the installer once again, and a new option “PostgreSQL” should appear.
|
||||||
|
You can now proceed through the installation without having to change any code.
|
||||||
|
|
||||||
|
### Option 2 - Manual
|
||||||
|
|
||||||
|
The second option is to setup PostgreSQL support manually. This can be achieved
|
||||||
|
by following these instructions:
|
||||||
|
|
||||||
|
1. Set up a fresh working copy of SilverStripe
|
||||||
|
2. Download a copy of the “postgresql” module from here: http://silverstripe.org/postgresql-module
|
||||||
|
3. Extract the archive you downloaded. Rename the directory from
|
||||||
|
“postgresql-trunk-rxxxx” to “postgresql” and copy it into the SilverStripe
|
||||||
|
directory you just set up.
|
||||||
|
4. Open up your mysite/_config.php file and add (or update) the $databaseConfig
|
||||||
|
array like so:
|
||||||
|
|
||||||
|
> $databaseConfig = array(
|
||||||
|
> 'type' => 'PostgreSQLDatabase',
|
||||||
|
> 'server' => '[server address e.g. localhost]',
|
||||||
|
> 'username' => 'postgres',
|
||||||
|
> 'password' => 'mypassword',
|
||||||
|
> 'database' => 'SS_mysite'
|
||||||
|
> );
|
||||||
|
|
||||||
|
Finally, visit dev/build so that SilverStripe can build the database schema and
|
||||||
|
default records.
|
||||||
|
|
||||||
|
### Option 3 - Environment file
|
||||||
|
|
||||||
|
Finally, the third option is to change your environment to point to
|
||||||
|
PostgreSQLDatabase as a database class. Do this if you're currently using an
|
||||||
|
_ss_environment.php file.
|
||||||
|
|
||||||
|
1. Download a copy of the “postgresql” module from here: http://silverstripe.org/postgresql-module
|
||||||
|
2. Extract the archive you downloaded. Rename the directory from
|
||||||
|
postgresql-trunk-rxxxx” to “postgresql” and copy it into your SS directory
|
||||||
|
3. Add the following to your existing _ss_environment.php file:
|
||||||
|
|
||||||
|
> define('SS_DATABASE_CLASS', 'PostgreSQLDatabase');
|
||||||
|
|
||||||
|
Last steps:
|
||||||
|
|
||||||
|
1. Ensure your SS_DATABASE_USERNAME and SS_DATABASE_PASSWORD defines in
|
||||||
|
_ss_environment.php are correct to the PostgreSQL server.
|
||||||
|
2. Ensure that your mysite/_config.php file has a database name defined, such
|
||||||
|
as “SS_mysite”.
|
||||||
|
3. Visit dev/build so that SilverStripe can build the database schema and
|
||||||
|
default records
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Here is a quick list of what's different in the Postgres module (a full
|
Here is a quick list of what's different in the Postgres module (a full
|
||||||
@ -220,3 +301,21 @@ connection, no matter what you entered in the "Database Name" field during
|
|||||||
installation.
|
installation.
|
||||||
|
|
||||||
Make sure you have set the "search_path" correct for your database user.
|
Make sure you have set the "search_path" correct for your database user.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
All column and table names must be double-quoted. PostgreSQL automatically
|
||||||
|
lower-cases columns, and your queries will fail if you don't.
|
||||||
|
|
||||||
|
Ts_vector columns are not automatically detected by the built-in search filters.
|
||||||
|
That means if you're doing a search through the CMS on a ModelAdmin object, it
|
||||||
|
will use LIKE queries which are very slow.
|
||||||
|
|
||||||
|
If you're writing your own front-end search system, you can specify the columns
|
||||||
|
to use for search purposes, and you get the full benefits of T-Search.
|
||||||
|
|
||||||
|
If you are using unsupported modules, there may be instances of MySQL-specific
|
||||||
|
SQL queries which will need to be made database-agnostic where possible.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
<ruleset name="SilverStripe">
|
<ruleset name="SilverStripe">
|
||||||
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
|
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
|
||||||
|
|
||||||
<file>code</file>
|
|
||||||
<file>tests</file>
|
|
||||||
|
|
||||||
<!-- base rules are PSR-2 -->
|
<!-- base rules are PSR-2 -->
|
||||||
<rule ref="PSR2" >
|
<rule ref="PSR2" >
|
||||||
<!-- Current exclusions -->
|
<!-- Current exclusions -->
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
|
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
|
||||||
|
|
||||||
<testsuite name="Default">
|
<testsuite name="Default">
|
||||||
|
57
tests/PostgreSQLDatabaseTest.php
Normal file
57
tests/PostgreSQLDatabaseTest.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\PostgreSQL\Tests;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Page;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\PostgreSQL\PostgreSQLDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package postgresql
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
|
class PostgreSQLDatabaseTest extends SapphireTest
|
||||||
|
{
|
||||||
|
protected $usesDatabase = true;
|
||||||
|
|
||||||
|
public function testReadOnlyTransaction()
|
||||||
|
{
|
||||||
|
if (DB::get_conn()->supportsTransactions() == true
|
||||||
|
&& DB::get_conn() instanceof PostgreSQLDatabase
|
||||||
|
) {
|
||||||
|
$page = new Page();
|
||||||
|
$page->Title = 'Read only success';
|
||||||
|
$page->write();
|
||||||
|
|
||||||
|
DB::get_conn()->transactionStart('READ ONLY');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$page = new Page();
|
||||||
|
$page->Title = 'Read only page failed';
|
||||||
|
$page->write();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
//could not write this record
|
||||||
|
//We need to do a rollback or a commit otherwise we'll get error messages
|
||||||
|
DB::get_conn()->transactionRollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::get_conn()->transactionEnd();
|
||||||
|
|
||||||
|
DataObject::flush_and_destroy_cache();
|
||||||
|
|
||||||
|
$success = DataObject::get('Page', "\"Title\"='Read only success'");
|
||||||
|
$fail = DataObject::get('Page', "\"Title\"='Read only page failed'");
|
||||||
|
|
||||||
|
//This page should be in the system
|
||||||
|
$this->assertTrue(is_object($success) && $success->exists());
|
||||||
|
|
||||||
|
//This page should NOT exist, we had 'read only' permissions
|
||||||
|
$this->assertFalse(is_object($fail) && $fail->exists());
|
||||||
|
} else {
|
||||||
|
$this->markTestSkipped('Current database is not PostgreSQL');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace SilverStripe\PostgreSQL\Tests;
|
|
||||||
|
|
||||||
use SilverStripe\Core\ClassInfo;
|
|
||||||
use SilverStripe\Dev\SapphireTest;
|
|
||||||
use SilverStripe\ORM\Connect\Database;
|
|
||||||
use SilverStripe\ORM\Connect\DatabaseException;
|
|
||||||
use SilverStripe\ORM\DB;
|
|
||||||
use SilverStripe\PostgreSQL\PostgreSQLConnector;
|
|
||||||
use SilverStripe\PostgreSQL\PostgreSQLSchemaManager;
|
|
||||||
|
|
||||||
class PostgreSQLSchemaManagerTest extends SapphireTest
|
|
||||||
{
|
|
||||||
|
|
||||||
protected $usesTransactions = false;
|
|
||||||
|
|
||||||
public function testAlterTable()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/** @var PostgreSQLSchemaManager $dbSchema */
|
|
||||||
$dbSchema = DB::get_schema();
|
|
||||||
$dbSchema->quiet();
|
|
||||||
|
|
||||||
$this->createSS3Table();
|
|
||||||
|
|
||||||
try {
|
|
||||||
DB::query('INSERT INTO "ClassNamesUpgrade" ("ClassName") VALUES (\'App\MySite\FooBar\')');
|
|
||||||
$this->assertFalse(true, 'SS3 Constaint should have blocked the previous insert.');
|
|
||||||
} catch (DatabaseException $ex) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$dbSchema->schemaUpdate(function () use ($dbSchema) {
|
|
||||||
$dbSchema->requireTable(
|
|
||||||
'ClassNamesUpgrade',
|
|
||||||
[
|
|
||||||
'ID' => 'PrimaryKey',
|
|
||||||
'ClassName' => 'Enum(array("App\\\\MySite\\\\FooBar"))',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
DB::query('INSERT INTO "ClassNamesUpgrade" ("ClassName") VALUES (\'App\MySite\FooBar\')');
|
|
||||||
$count = DB::query('SELECT count(*) FROM "ClassNamesUpgrade" WHERE "ClassName" = \'App\MySite\FooBar\'')
|
|
||||||
->value();
|
|
||||||
|
|
||||||
$this->assertEquals(1, $count);
|
|
||||||
} finally {
|
|
||||||
DB::query('DROP TABLE IF EXISTS "ClassNamesUpgrade"');
|
|
||||||
DB::query('DROP SEQUENCE IF EXISTS "ClassNamesUpgrade_ID_seq"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createSS3Table()
|
|
||||||
{
|
|
||||||
DB::query(<<<SQL
|
|
||||||
CREATE SEQUENCE "ClassNamesUpgrade_ID_seq" start 1 increment 1;
|
|
||||||
CREATE TABLE "ClassNamesUpgrade"
|
|
||||||
(
|
|
||||||
"ID" bigint NOT NULL DEFAULT nextval('"ClassNamesUpgrade_ID_seq"'::regclass),
|
|
||||||
"ClassName" character varying(255) DEFAULT 'ClassNamesUpgrade'::character varying,
|
|
||||||
CONSTRAINT "ClassNamesUpgrade_pkey" PRIMARY KEY ("ID"),
|
|
||||||
CONSTRAINT "ClassNamesUpgrade_ClassName_check" CHECK ("ClassName"::text = ANY (ARRAY['FooBar'::character varying::text]))
|
|
||||||
)
|
|
||||||
WITH (
|
|
||||||
OIDS=FALSE
|
|
||||||
);
|
|
||||||
SQL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRenameTable()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/** @var PostgreSQLSchemaManager $dbSchema */
|
|
||||||
$dbSchema = DB::get_schema();
|
|
||||||
$dbSchema->quiet();
|
|
||||||
|
|
||||||
$this->createSS3VersionedTable();
|
|
||||||
|
|
||||||
$this->assertConstraintCount(1, 'ClassNamesUpgrade_versioned_ClassName_check');
|
|
||||||
|
|
||||||
$dbSchema->schemaUpdate(function () use ($dbSchema) {
|
|
||||||
$dbSchema->renameTable(
|
|
||||||
'ClassNamesUpgrade_versioned',
|
|
||||||
'ClassNamesUpgrade_Versioned'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->assertTableCount(0, 'ClassNamesUpgrade_versioned');
|
|
||||||
$this->assertTableCount(1, 'ClassNamesUpgrade_Versioned');
|
|
||||||
$this->assertConstraintCount(0, 'ClassNamesUpgrade_versioned_ClassName_check');
|
|
||||||
$this->assertConstraintCount(1, 'ClassNamesUpgrade_Versioned_ClassName_check');
|
|
||||||
} finally {
|
|
||||||
DB::query('DROP TABLE IF EXISTS "ClassNamesUpgrade_Versioned"');
|
|
||||||
DB::query('DROP TABLE IF EXISTS "ClassNamesUpgrade_versioned"');
|
|
||||||
DB::query('DROP SEQUENCE IF EXISTS "ClassNamesUpgrade_versioned_ID_seq"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function assertConstraintCount($expected, $constraintName)
|
|
||||||
{
|
|
||||||
$count = DB::prepared_query(
|
|
||||||
'SELECT count(*) FROM pg_catalog.pg_constraint WHERE conname like ?',
|
|
||||||
[$constraintName]
|
|
||||||
)->value();
|
|
||||||
|
|
||||||
$this->assertEquals($expected, $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function assertTableCount($expected, $tableName)
|
|
||||||
{
|
|
||||||
$count = DB::prepared_query(
|
|
||||||
'SELECT count(*) FROM pg_catalog.pg_tables WHERE "tablename" like ?',
|
|
||||||
[$tableName]
|
|
||||||
)->value();
|
|
||||||
|
|
||||||
$this->assertEquals($expected, $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createSS3VersionedTable()
|
|
||||||
{
|
|
||||||
DB::query(<<<SQL
|
|
||||||
CREATE SEQUENCE "ClassNamesUpgrade_versioned_ID_seq" start 1 increment 1;
|
|
||||||
CREATE TABLE "ClassNamesUpgrade_versioned"
|
|
||||||
(
|
|
||||||
"ID" bigint NOT NULL DEFAULT nextval('"ClassNamesUpgrade_versioned_ID_seq"'::regclass),
|
|
||||||
"ClassName" character varying(255) DEFAULT 'ClassNamesUpgrade'::character varying,
|
|
||||||
CONSTRAINT "ClassNamesUpgrade_pkey" PRIMARY KEY ("ID"),
|
|
||||||
CONSTRAINT "ClassNamesUpgrade_versioned_ClassName_check" CHECK ("ClassName"::text = ANY (ARRAY['FooBar'::character varying::text]))
|
|
||||||
)
|
|
||||||
WITH (
|
|
||||||
OIDS=FALSE
|
|
||||||
);
|
|
||||||
SQL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user