Compare commits

..

No commits in common. "2" and "2.2.0" have entirely different histories.
2 ... 2.2.0

14 changed files with 206 additions and 314 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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:4.3.x-dev
- composer install --prefer-source --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

View File

@ -1,54 +1,35 @@
# 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/) [![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

View File

@ -448,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(
'q' => ", to_tsquery('" . self::search_language() . "', $keywords)" 'tsearch' => ", to_tsquery('" . self::search_language() . "', $keywords) AS q"
)); ));
$query->setSelect(array()); $query->setSelect(array());

View File

@ -58,10 +58,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 $this->nextRecord();
return $this->parseResult($result);
} }
public function numRecords() public function numRecords()
@ -75,18 +73,6 @@ class PostgreSQLQuery extends Query
// Correct non-string types // Correct non-string types
if ($row) { if ($row) {
return $this->parseResult($row);
}
return false;
}
/**
* @param array $row
* @return array
*/
protected function parseResult(array $row)
{
$record = []; $record = [];
foreach ($row as $i => $v) { foreach ($row as $i => $v) {
@ -106,4 +92,7 @@ class PostgreSQLQuery extends Query
return $record; return $record;
} }
return false;
}
} }

View File

@ -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
* *

View File

@ -15,12 +15,16 @@
} }
], ],
"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" },
"extra": {
"branch-alias": {
"2.x-dev": "2.2.x-dev"
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -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.

View File

@ -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 -->

View File

@ -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">

View File

@ -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
);
}
}