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
|
||||
|
||||
[![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/)
|
||||
[![Build Status](https://travis-ci.org/silverstripe/silverstripe-postgresql.png?branch=master)](https://travis-ci.org/silverstripe/silverstripe-postgresql)
|
||||
|
||||
## Maintainer Contact
|
||||
|
||||
* Sam Minnee (Nickname: sminnee) <sam@silverstripe.com>
|
||||
|
||||
## Requirements
|
||||
|
||||
* Silverstripe 4.0
|
||||
* PostgreSQL >=9.2
|
||||
* Note: PostgreSQL 10 has not been tested
|
||||
* SilverStripe 4.0
|
||||
* PostgreSQL 8.3.x or greater must be installed
|
||||
* 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
|
||||
|
||||
```
|
||||
composer require silverstripe/postgresql
|
||||
```
|
||||
|
||||
## 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
|
||||
1. Install via composer `composer require silverstripe/postgresql` or extract the contents
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
All column and table names must be double-quoted. PostgreSQL automatically
|
||||
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
|
||||
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
|
||||
|
@ -36,11 +36,6 @@ class PostgreSQLDatabase extends Database
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $transactionNesting = 0;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$start = (int)$start;
|
||||
$pageLength = (int)$pageLength;
|
||||
|
||||
//Fix the keywords to be ts_query compatitble:
|
||||
//Spaces must have pipes
|
||||
//@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
|
||||
$query->addFrom(array(
|
||||
'q' => ", to_tsquery('" . self::search_language() . "', $keywords)"
|
||||
'tsearch' => ", to_tsquery('" . self::search_language() . "', $keywords) AS q"
|
||||
));
|
||||
$query->setSelect(array());
|
||||
|
||||
@ -524,9 +516,6 @@ class PostgreSQLDatabase extends Database
|
||||
|
||||
public function transactionStart($transaction_mode = false, $session_characteristics = false)
|
||||
{
|
||||
if ($this->transactionNesting > 0) {
|
||||
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
|
||||
} else {
|
||||
$this->query('BEGIN;');
|
||||
|
||||
if ($transaction_mode) {
|
||||
@ -537,8 +526,6 @@ class PostgreSQLDatabase extends Database
|
||||
$this->query("SET SESSION CHARACTERISTICS AS TRANSACTION {$session_characteristics};");
|
||||
}
|
||||
}
|
||||
++$this->transactionNesting;
|
||||
}
|
||||
|
||||
public function transactionSavepoint($savepoint)
|
||||
{
|
||||
@ -547,41 +534,17 @@ class PostgreSQLDatabase extends Database
|
||||
|
||||
public function transactionRollback($savepoint = false)
|
||||
{
|
||||
// Named savepoint
|
||||
if ($savepoint) {
|
||||
$this->query('ROLLBACK TO ' . $savepoint);
|
||||
return true;
|
||||
$this->query("ROLLBACK TO {$savepoint};");
|
||||
} 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)
|
||||
{
|
||||
--$this->transactionNesting;
|
||||
if ($this->transactionNesting <= 0) {
|
||||
$this->transactionNesting = 0;
|
||||
$this->query('COMMIT;');
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -18,23 +18,6 @@ class PostgreSQLQuery extends Query
|
||||
*/
|
||||
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.
|
||||
* @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)
|
||||
{
|
||||
$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()
|
||||
@ -58,10 +36,8 @@ class PostgreSQLQuery extends Query
|
||||
|
||||
public function seek($row)
|
||||
{
|
||||
// Specifying the zero-th record here will reset the pointer
|
||||
$result = pg_fetch_array($this->handle, $row, PGSQL_NUM);
|
||||
|
||||
return $this->parseResult($result);
|
||||
pg_result_seek($this->handle, $row);
|
||||
return pg_fetch_assoc($this->handle);
|
||||
}
|
||||
|
||||
public function numRecords()
|
||||
@ -71,39 +47,6 @@ class PostgreSQLQuery extends Query
|
||||
|
||||
public function nextRecord()
|
||||
{
|
||||
$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;
|
||||
return pg_fetch_assoc($this->handle);
|
||||
}
|
||||
}
|
||||
|
@ -494,15 +494,9 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
||||
// First, we split the column specifications into parts
|
||||
// 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?
|
||||
$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);
|
||||
// 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) {
|
||||
return '';
|
||||
@ -543,8 +537,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
||||
if ($this->hasTable("{$tableName}_Live")) {
|
||||
$updateConstraint .= "UPDATE \"{$tableName}_Live\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
||||
}
|
||||
if ($this->hasTable("{$tableName}_Versions")) {
|
||||
$updateConstraint .= "UPDATE \"{$tableName}_Versions\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
||||
if ($this->hasTable("{$tableName}_versions")) {
|
||||
$updateConstraint .= "UPDATE \"{$tableName}_versions\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
|
||||
}
|
||||
|
||||
$this->query($updateConstraint);
|
||||
@ -566,17 +560,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
||||
|
||||
public function renameTable($oldTableName, $newTableName)
|
||||
{
|
||||
$constraints = $this->getConstraintForTable($oldTableName);
|
||||
$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_constraints[$oldTableName]);
|
||||
}
|
||||
|
||||
public function checkAndRepairTable($tableName)
|
||||
@ -858,9 +843,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
||||
$argList = array();
|
||||
$nextArg = "";
|
||||
foreach ($bytes as $byte) {
|
||||
if ($byte == '\x') {
|
||||
continue;
|
||||
} elseif ($byte == "00") {
|
||||
if ($byte == "00") {
|
||||
$argList[] = $nextArg;
|
||||
$nextArg = "";
|
||||
} else {
|
||||
@ -978,28 +961,6 @@ class PostgreSQLSchemaManager extends DBSchemaManager
|
||||
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
|
||||
*
|
||||
|
@ -15,12 +15,11 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"silverstripe/framework": "^4.10",
|
||||
"silverstripe/framework": "^4",
|
||||
"silverstripe/vendor-plugin": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"squizlabs/php_codesniffer": "^3"
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -1,5 +1,86 @@
|
||||
# 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
|
||||
|
||||
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.
|
||||
|
||||
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">
|
||||
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
|
||||
|
||||
<file>code</file>
|
||||
<file>tests</file>
|
||||
|
||||
<!-- base rules are PSR-2 -->
|
||||
<rule ref="PSR2" >
|
||||
<!-- Current exclusions -->
|
||||
|
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
|
||||
|
||||
<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