mirror of
https://github.com/silverstripe/silverstripe-postgresql
synced 2024-10-22 17:05:45 +02:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
091a4c2416 | ||
|
b37c19d6d5 | ||
|
24dbdeb564 | ||
|
a60fc4cf24 | ||
|
d2fbce5319 | ||
|
beb0f84f2d | ||
|
6d5c35116a | ||
|
41fd4718a2 | ||
|
448828c20a | ||
|
faf9d033ff | ||
|
d7b4ccb202 | ||
|
c9bce8fe57 | ||
|
4bbabf2421 | ||
|
22e3951244 | ||
|
321d0d853b | ||
|
84e13ffde3 | ||
|
17e9f5388c | ||
|
222f20529c | ||
|
c46272e751 | ||
|
afec73997a | ||
|
d3c8e2915e | ||
|
a709a741b0 | ||
|
dd4df9800b | ||
|
082742ad23 | ||
|
a7c3450d43 | ||
|
0be39423a3 | ||
|
3d50b3f9ec | ||
|
3e38f845e3 | ||
|
4e3d3df565 | ||
|
753d73e1fe | ||
|
beed6c7fb7 | ||
|
bf4fb87a01 | ||
|
66376db094 | ||
|
d607a2bfa9 | ||
|
f2ec228c72 | ||
|
82f8a06afa | ||
|
75f4a35f71 | ||
|
08c8293328 | ||
|
0ffaf90512 | ||
|
04000ad878 | ||
|
3d6920c121 | ||
|
b6bab3561f | ||
|
fd27c17a80 | ||
|
f85b46d047 | ||
|
32a0aad720 | ||
|
0d9fcabc80 | ||
|
8f70ac89ca | ||
|
72787ae83e | ||
|
4c6034f350 | ||
|
edfa209a3c | ||
|
e123f69b7b | ||
|
4c89d103c5 | ||
|
6378003540 | ||
|
b210c7284f | ||
|
7fe935fc89 | ||
|
694c4059b9 | ||
|
6cfc30952c | ||
|
e0d5536715 | ||
|
01cc78ec94 | ||
|
513c969c93 | ||
|
e3825697d0 | ||
|
47a6ebb4e3 | ||
|
2bbd73620d | ||
|
97afbd9a88 | ||
|
f2392eb7c6 | ||
|
1f6d892609 | ||
|
2d11336dce | ||
|
aa16771922 | ||
|
05e15d85d6 | ||
|
390cb09928 | ||
|
d110b92fc8 | ||
|
851309f187 | ||
|
ee356b1ad7 |
25
.github/workflows/ci.yml
vendored
Normal file
25
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
Normal file
16
.github/workflows/dispatch-ci.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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
Normal file
17
.github/workflows/keepalive.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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
|
@ -1,15 +0,0 @@
|
|||||||
inherit: true
|
|
||||||
|
|
||||||
build:
|
|
||||||
nodes:
|
|
||||||
analysis:
|
|
||||||
tests:
|
|
||||||
override: [php-scrutinizer-run]
|
|
||||||
|
|
||||||
checks:
|
|
||||||
php:
|
|
||||||
code_rating: true
|
|
||||||
duplication: true
|
|
||||||
|
|
||||||
filter:
|
|
||||||
paths: [code/*, tests/*]
|
|
43
.travis.yml
43
.travis.yml
@ -1,43 +0,0 @@
|
|||||||
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,34 +1,54 @@
|
|||||||
# PostgreSQL Module Module
|
# PostgreSQL Module Module
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/silverstripe/silverstripe-postgresql.png?branch=master)](https://travis-ci.org/silverstripe/silverstripe-postgresql)
|
[![CI](https://github.com/silverstripe/silverstripe-postgresql/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-postgresql/actions/workflows/ci.yml)
|
||||||
|
[![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 8.3.x or greater must be installed
|
* PostgreSQL >=9.2
|
||||||
* PostgreSQL <8.3.0 may work if T-Search is manually installed
|
* Note: PostgreSQL 10 has not been tested
|
||||||
* 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
|
```
|
||||||
so they reside as a `postgresql` directory inside your SilverStripe project code
|
composer require silverstripe/postgresql
|
||||||
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 for more information about configuring the module.
|
See [docs/en](docs/en/README.md) 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,6 +36,11 @@ 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.
|
||||||
*
|
*
|
||||||
@ -362,6 +367,9 @@ 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.
|
||||||
@ -440,7 +448,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(
|
||||||
'tsearch' => ", to_tsquery('" . self::search_language() . "', $keywords) AS q"
|
'q' => ", to_tsquery('" . self::search_language() . "', $keywords)"
|
||||||
));
|
));
|
||||||
$query->setSelect(array());
|
$query->setSelect(array());
|
||||||
|
|
||||||
@ -516,6 +524,9 @@ 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) {
|
||||||
@ -526,6 +537,8 @@ 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)
|
||||||
{
|
{
|
||||||
@ -534,17 +547,41 @@ 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);
|
||||||
} else {
|
return true;
|
||||||
$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)
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,23 @@ 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.
|
||||||
@ -25,6 +42,11 @@ 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()
|
||||||
@ -36,8 +58,10 @@ class PostgreSQLQuery extends Query
|
|||||||
|
|
||||||
public function seek($row)
|
public function seek($row)
|
||||||
{
|
{
|
||||||
pg_result_seek($this->handle, $row);
|
// Specifying the zero-th record here will reset the pointer
|
||||||
return pg_fetch_assoc($this->handle);
|
$result = pg_fetch_array($this->handle, $row, PGSQL_NUM);
|
||||||
|
|
||||||
|
return $this->parseResult($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function numRecords()
|
public function numRecords()
|
||||||
@ -47,6 +71,39 @@ class PostgreSQLQuery extends Query
|
|||||||
|
|
||||||
public function nextRecord()
|
public function nextRecord()
|
||||||
{
|
{
|
||||||
return pg_fetch_assoc($this->handle);
|
$row = pg_fetch_array($this->handle, null, PGSQL_NUM);
|
||||||
|
|
||||||
|
// 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,9 +494,15 @@ 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 '';
|
||||||
@ -537,8 +543,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);
|
||||||
@ -560,8 +566,17 @@ 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)
|
||||||
@ -843,7 +858,9 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
|||||||
$argList = array();
|
$argList = array();
|
||||||
$nextArg = "";
|
$nextArg = "";
|
||||||
foreach ($bytes as $byte) {
|
foreach ($bytes as $byte) {
|
||||||
if ($byte == "00") {
|
if ($byte == '\x') {
|
||||||
|
continue;
|
||||||
|
} elseif ($byte == "00") {
|
||||||
$argList[] = $nextArg;
|
$argList[] = $nextArg;
|
||||||
$nextArg = "";
|
$nextArg = "";
|
||||||
} else {
|
} else {
|
||||||
@ -961,6 +978,28 @@ 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,11 +15,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"silverstripe/framework": "^4",
|
"silverstripe/framework": "^4.10",
|
||||||
"silverstripe/vendor-plugin": "^1.0"
|
"silverstripe/vendor-plugin": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^5.7"
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -1,86 +1,5 @@
|
|||||||
# 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
|
||||||
@ -301,21 +220,3 @@ 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,6 +2,9 @@
|
|||||||
<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,3 +1,4 @@
|
|||||||
|
<?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">
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
138
tests/PostgreSQLSchemaManagerTest.php
Normal file
138
tests/PostgreSQLSchemaManagerTest.php
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<?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