BUG Fix postgres + PDO not working

BUG Fix empty enums
This commit is contained in:
Damian Mooyman 2017-11-17 10:58:14 +13:00
parent 8c5f95fdaa
commit 685e33cf84
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
11 changed files with 332 additions and 266 deletions

View File

@ -10,8 +10,15 @@ indent_style = space
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[{*.yml,*.json}] [*.md]
trim_trailing_whitespace = false
[*.{yml,js,json,css,scss,feature}]
indent_size = 2 indent_size = 2
indent_style = space
[composer.json]
indent_size = 4
# The indent size used in the package.json file cannot be changed: # The indent size used in the package.json file cannot be changed:
# https://github.com/npm/npm/pull/3180#issuecomment-16336516 # https://github.com/npm/npm/pull/3180#issuecomment-16336516

View File

@ -1,69 +1,15 @@
inherit: true inherit: true
build:
nodes:
analysis:
tests:
override: [php-scrutinizer-run]
checks: checks:
php: php:
verify_property_names: true
verify_argument_usable_as_reference: true
verify_access_scope_valid: true
useless_calls: true
use_statement_alias_conflict: true
variable_existence: true
unused_variables: true
unused_properties: true
unused_parameters: true
unused_methods: true
unreachable_code: true
too_many_arguments: true
sql_injection_vulnerabilities: true
simplify_boolean_return: true
side_effects_or_types: true
security_vulnerabilities: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
require_scope_for_properties: true
require_scope_for_methods: true
require_php_tag_first: true
psr2_switch_declaration: true
psr2_class_declaration: true
property_assignments: true
prefer_while_loop_over_for_loop: true
precedence_mistakes: true
precedence_in_conditions: true
phpunit_assertions: true
php5_style_constructor: true
parse_doc_comments: true
parameter_non_unique: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
optional_parameters_at_the_end: true
one_class_per_file: true
no_unnecessary_if: true
no_trailing_whitespace: true
no_property_on_interface: true
no_non_implemented_abstract_methods: true
no_error_suppression: true
no_duplicate_arguments: true
no_commented_out_code: true
newline_at_end_of_file: true
missing_arguments: true
method_calls_on_non_object: true
instanceof_class_exists: true
foreach_traversable: true
fix_line_ending: true
fix_doc_comments: true
duplication: true
deprecated_code_usage: true
deadlock_detection_in_loops: true
code_rating: true code_rating: true
closure_use_not_conflicting: true duplication: true
catch_class_exists: true
blank_line_after_namespace_declaration: false
avoid_multiple_statements_on_same_line: true
avoid_duplicate_types: true
avoid_conflicting_incrementers: true
avoid_closing_tag: true
assignment_of_null_return: true
argument_type_checks: true
filter: filter:
paths: [code/*, tests/*] paths: [code/*, tests/*]

View File

@ -1,8 +1,10 @@
language: php language: php
dist: precise dist: trusty
sudo: false cache:
directories:
- $HOME/.composer/cache/files
php: php:
- 5.6 - 5.6
@ -10,23 +12,33 @@ php:
env: env:
global: global:
- DB="PGSQL" - DB="PGSQL"
- CORE_RELEASE="4"
matrix:
fast_finish: true
include:
- php: 5.6
env:
- PHPUNIT_TEST=framework
- php: 5.6
env:
- PHPUNIT_TEST=postgresql
- PHPCS_TEST=1
before_script: before_script:
# Init PHP # Init PHP
- printf "\n" | pecl install imagick
- phpenv rehash - phpenv rehash
- phpenv config-rm xdebug.ini - 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 - echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
# Temporarily update to 1.5.x-dev of composer
- composer self-update --snapshot
# Install composer dependencies # Install composer dependencies
- composer validate
- composer install --prefer-dist - composer install --prefer-dist
- composer require --prefer-dist --no-update symfony/config:^3.2 silverstripe/framework:4.0.x-dev silverstripe/cms:4.0.x-dev silverstripe/siteconfig:4.0.x-dev silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev silverstripe/assets:1.0.x-dev silverstripe/versioned:1.0.x-dev - composer require --prefer-dist --no-update silverstripe/recipe-cms:1.0.x-dev
- composer update - composer update
- if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi
script: script:
- vendor/bin/phpunit ./tests - if [[ $PHPUNIT_TEST == postgresql ]]; then vendor/bin/phpunit ./tests; fi
- vendor/bin/phpunit ./framework/tests - 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

@ -282,7 +282,7 @@ class PostgreSQLDatabase extends Database
public function getDatabaseServer() public function getDatabaseServer()
{ {
return "postgresql"; return "pgsql";
} }
/** /**
@ -374,14 +374,15 @@ class PostgreSQLDatabase extends Database
// Get tables // Get tables
$tablesToSearch = []; $tablesToSearch = [];
foreach($classesToSearch as $class) { foreach ($classesToSearch as $class) {
$tablesToSearch[$class] = DataObject::getSchema()->baseDataTable($class); $tablesToSearch[$class] = DataObject::getSchema()->baseDataTable($class);
} }
//We can get a list of all the tsvector columns though this query: //We can get a list of all the tsvector columns though this query:
//We know what tables to search in based on the $classesToSearch variable: //We know what tables to search in based on the $classesToSearch variable:
$classesPlaceholders = DB::placeholders($classesToSearch); $classesPlaceholders = DB::placeholders($classesToSearch);
$searchableColumns = $this->preparedQuery(" $searchableColumns = $this->preparedQuery(
"
SELECT table_name, column_name, data_type SELECT table_name, column_name, data_type
FROM information_schema.columns FROM information_schema.columns
WHERE data_type='tsvector' AND table_name in ($classesPlaceholders);", WHERE data_type='tsvector' AND table_name in ($classesPlaceholders);",
@ -500,7 +501,7 @@ class PostgreSQLDatabase extends Database
/* /*
* This is a quick lookup to discover if the database supports particular extensions * This is a quick lookup to discover if the database supports particular extensions
*/ */
public function supportsExtensions($extensions=array('partitions', 'tablespaces', 'clustering')) public function supportsExtensions($extensions = array('partitions', 'tablespaces', 'clustering'))
{ {
if (isset($extensions['partitions'])) { if (isset($extensions['partitions'])) {
return true; return true;
@ -683,8 +684,10 @@ class PostgreSQLDatabase extends Database
public function schemaToDatabaseName($schema) public function schemaToDatabaseName($schema)
{ {
switch ($schema) { switch ($schema) {
case $this->schemaOriginal: return $this->databaseOriginal; case $this->schemaOriginal:
default: return $schema; return $this->databaseOriginal;
default:
return $schema;
} }
} }
@ -698,8 +701,10 @@ class PostgreSQLDatabase extends Database
public function databaseToSchemaName($database) public function databaseToSchemaName($database)
{ {
switch ($database) { switch ($database) {
case $this->databaseOriginal: return $this->schemaOriginal; case $this->databaseOriginal:
default: return $database; return $this->schemaOriginal;
default:
return $database;
} }
} }

View File

@ -113,6 +113,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
} }
return $this->postgresDatabaseList(); return $this->postgresDatabaseList();
} }
/** /**
* Drops a postgres database, ignoring model_schema_as_database * Drops a postgres database, ignoring model_schema_as_database
* *
@ -177,8 +178,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
*/ */
public function schemaList() public function schemaList()
{ {
return $this->query(" return $this->query(
SELECT nspname "SELECT nspname
FROM pg_catalog.pg_namespace FROM pg_catalog.pg_namespace
WHERE nspname <> 'information_schema' AND nspname !~ E'^pg_'" WHERE nspname <> 'information_schema' AND nspname !~ E'^pg_'"
)->column(); )->column();
@ -186,7 +187,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
public function createTable($table, $fields = null, $indexes = null, $options = null, $advancedOptions = null) public function createTable($table, $fields = null, $indexes = null, $options = null, $advancedOptions = null)
{ {
$fieldSchemas = $indexSchemas = ""; $fieldSchemas = "";
if ($fields) { if ($fields) {
foreach ($fields as $k => $v) { foreach ($fields as $k => $v) {
$fieldSchemas .= "\"$k\" $v,\n"; $fieldSchemas .= "\"$k\" $v,\n";
@ -194,9 +195,6 @@ class PostgreSQLSchemaManager extends DBSchemaManager
} }
if (!empty($options[self::ID])) { if (!empty($options[self::ID])) {
$addOptions = $options[self::ID]; $addOptions = $options[self::ID];
} elseif (!empty($options[get_class($this)])) {
Deprecation::notice('3.2', 'Use PostgreSQLSchemaManager::ID for referencing postgres-specific table creation options');
$addOptions = $options[get_class($this)];
} else { } else {
$addOptions = null; $addOptions = null;
} }
@ -211,20 +209,21 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//If we have a fulltext search request, then we need to create a special column //If we have a fulltext search request, then we need to create a special column
//for GiST searches //for GiST searches
$fulltexts = ''; $fulltexts = '';
$triggers = ''; $triggers = [];
if ($indexes) { if ($indexes) {
foreach ($indexes as $name => $this_index) { foreach ($indexes as $name => $this_index) {
if (is_array($this_index) && $this_index['type'] == 'fulltext') { if (is_array($this_index) && $this_index['type'] == 'fulltext') {
$ts_details = $this->fulltext($this_index, $table, $name); $ts_details = $this->fulltext($this_index, $table, $name);
$fulltexts .= $ts_details['fulltexts'] . ', '; $fulltexts .= $ts_details['fulltexts'] . ', ';
$triggers .= $ts_details['triggers']; $triggers[] = $ts_details['triggers'];
} }
} }
} }
$indexQueries = [];
if ($indexes) { if ($indexes) {
foreach ($indexes as $k => $v) { foreach ($indexes as $k => $v) {
$indexSchemas .= $this->getIndexSqlDefinition($table, $k, $v) . "\n"; $indexQueries[] = $this->getIndexSqlDefinition($table, $k, $v);
} }
} }
@ -239,14 +238,19 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$tableSpace = ''; $tableSpace = '';
} }
$this->query("CREATE TABLE \"$table\" ( $this->query(
$fieldSchemas "CREATE TABLE \"$table\" (
$fulltexts $fieldSchemas
primary key (\"ID\") $fulltexts
)$tableSpace; $indexSchemas $addOptions"); primary key (\"ID\")
)$tableSpace $addOptions"
);
foreach ($indexQueries as $indexQuery) {
$this->query($indexQuery);
}
if ($triggers!='') { foreach ($triggers as $trigger) {
$this->query($triggers); $this->query($trigger);
} }
//If we have a partitioning requirement, we do that here: //If we have a partitioning requirement, we do that here:
@ -256,7 +260,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//Lastly, clustering goes here: //Lastly, clustering goes here:
if ($advancedOptions && isset($advancedOptions['cluster'])) { if ($advancedOptions && isset($advancedOptions['cluster'])) {
$this->query("CLUSTER \"$table\" USING \"{$advancedOptions['cluster']}\";"); $this->query("CLUSTER \"$table\" USING \"{$advancedOptions['cluster']}\"");
} }
return $table; return $table;
@ -299,9 +303,16 @@ class PostgreSQLSchemaManager extends DBSchemaManager
return $this->buildPostgresIndexName($tableName, $triggerName, 'ts'); return $this->buildPostgresIndexName($tableName, $triggerName, 'ts');
} }
public function alterTable($table, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null) public function alterTable(
{ $table,
$alterList = array(); $newFields = null,
$newIndexes = null,
$alteredFields = null,
$alteredIndexes = null,
$alteredOptions = null,
$advancedOptions = null
) {
$alterList = [];
if ($newFields) { if ($newFields) {
foreach ($newFields as $fieldName => $fieldSpec) { foreach ($newFields as $fieldName => $fieldSpec) {
$alterList[] = "ADD \"$fieldName\" $fieldSpec"; $alterList[] = "ADD \"$fieldName\" $fieldSpec";
@ -319,46 +330,49 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//Do we need to do anything with the tablespaces? //Do we need to do anything with the tablespaces?
if ($alteredOptions && isset($advancedOptions['tablespace'])) { if ($alteredOptions && isset($advancedOptions['tablespace'])) {
$this->createOrReplaceTablespace($advancedOptions['tablespace']['name'], $advancedOptions['tablespace']['location']); $this->createOrReplaceTablespace(
$advancedOptions['tablespace']['name'],
$advancedOptions['tablespace']['location']
);
$this->query("ALTER TABLE \"$table\" SET TABLESPACE {$advancedOptions['tablespace']['name']};"); $this->query("ALTER TABLE \"$table\" SET TABLESPACE {$advancedOptions['tablespace']['name']};");
} }
//DB ABSTRACTION: we need to change the constraints to be a separate 'add' command, //DB ABSTRACTION: we need to change the constraints to be a separate 'add' command,
//see http://www.postgresql.org/docs/8.1/static/sql-altertable.html //see http://www.postgresql.org/docs/8.1/static/sql-altertable.html
$alterIndexList = array(); $alterIndexList = [];
//Pick up the altered indexes here: //Pick up the altered indexes here:
$fieldList = $this->fieldList($table); $fieldList = $this->fieldList($table);
$fulltexts = false; $fulltexts = [];
$drop_triggers = false; $dropTriggers = [];
$triggers = false; $triggers = [];
if ($alteredIndexes) { if ($alteredIndexes) {
foreach ($alteredIndexes as $indexName=>$indexSpec) { foreach ($alteredIndexes as $indexName => $indexSpec) {
$indexNamePG = $this->buildPostgresIndexName($table, $indexName); $indexNamePG = $this->buildPostgresIndexName($table, $indexName);
if ($indexSpec['type']=='fulltext') { if ($indexSpec['type'] == 'fulltext') {
//For full text indexes, we need to drop the trigger, drop the index, AND drop the column //For full text indexes, we need to drop the trigger, drop the index, AND drop the column
//Go and get the tsearch details: //Go and get the tsearch details:
$ts_details = $this->fulltext($indexSpec, $table, $indexName); $ts_details = $this->fulltext($indexSpec, $table, $indexName);
//Drop this column if it already exists: //Drop this column if it already exists:
//No IF EXISTS option is available for Postgres <9.0 //No IF EXISTS option is available for Postgres <9.0
if (array_key_exists($ts_details['ts_name'], $fieldList)) { if (array_key_exists($ts_details['ts_name'], $fieldList)) {
$fulltexts.="ALTER TABLE \"{$table}\" DROP COLUMN \"{$ts_details['ts_name']}\";"; $fulltexts[] = "ALTER TABLE \"{$table}\" DROP COLUMN \"{$ts_details['ts_name']}\";";
}
// We'll execute these later:
$triggerNamePG = $this->buildPostgresTriggerName($table, $indexName);
$dropTriggers[] = "DROP TRIGGER IF EXISTS \"$triggerNamePG\" ON \"$table\";";
$fulltexts[] = "ALTER TABLE \"{$table}\" ADD COLUMN {$ts_details['fulltexts']};";
$triggers[] = $ts_details['triggers'];
} }
// We'll execute these later: // Create index action (including fulltext)
$triggerNamePG = $this->buildPostgresTriggerName($table, $indexName); $alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";";
$drop_triggers.= "DROP TRIGGER IF EXISTS \"$triggerNamePG\" ON \"$table\";";
$fulltexts .= "ALTER TABLE \"{$table}\" ADD COLUMN {$ts_details['fulltexts']};";
$triggers .= $ts_details['triggers'];
}
// Create index action (including fulltext)
$alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";";
$createIndex = $this->getIndexSqlDefinition($table, $indexName, $indexSpec); $createIndex = $this->getIndexSqlDefinition($table, $indexName, $indexSpec);
if ($createIndex!==false) { if ($createIndex) {
$alterIndexList[] = $createIndex; $alterIndexList[] = $createIndex;
} }
} }
@ -368,25 +382,25 @@ class PostgreSQLSchemaManager extends DBSchemaManager
if ($newIndexes) { if ($newIndexes) {
foreach ($newIndexes as $indexName => $indexSpec) { foreach ($newIndexes as $indexName => $indexSpec) {
$indexNamePG = $this->buildPostgresIndexName($table, $indexName); $indexNamePG = $this->buildPostgresIndexName($table, $indexName);
//If we have a fulltext search request, then we need to create a special column //If we have a fulltext search request, then we need to create a special column
//for GiST searches //for GiST searches
//Pick up the new indexes here: //Pick up the new indexes here:
if ($indexSpec['type']=='fulltext') { if ($indexSpec['type'] == 'fulltext') {
$ts_details=$this->fulltext($indexSpec, $table, $indexName); $ts_details = $this->fulltext($indexSpec, $table, $indexName);
if (!isset($fieldList[$ts_details['ts_name']])) { if (!isset($fieldList[$ts_details['ts_name']])) {
$fulltexts.="ALTER TABLE \"{$table}\" ADD COLUMN {$ts_details['fulltexts']};"; $fulltexts[] = "ALTER TABLE \"{$table}\" ADD COLUMN {$ts_details['fulltexts']};";
$triggers.=$ts_details['triggers']; $triggers[] = $ts_details['triggers'];
}
} }
}
//Check that this index doesn't already exist: //Check that this index doesn't already exist:
$indexes=$this->indexList($table); $indexes = $this->indexList($table);
if (isset($indexes[$indexName])) { if (isset($indexes[$indexName])) {
$alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";"; $alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";";
} }
$createIndex=$this->getIndexSqlDefinition($table, $indexName, $indexSpec); $createIndex = $this->getIndexSqlDefinition($table, $indexName, $indexSpec);
if ($createIndex!==false) { if ($createIndex) {
$alterIndexList[] = $createIndex; $alterIndexList[] = $createIndex;
} }
} }
@ -399,7 +413,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//Do we need to create a tablespace for this item? //Do we need to create a tablespace for this item?
if ($advancedOptions && isset($advancedOptions['extensions']['tablespace'])) { if ($advancedOptions && isset($advancedOptions['extensions']['tablespace'])) {
$extensions=$advancedOptions['extensions']; $extensions = $advancedOptions['extensions'];
$this->createOrReplaceTablespace($extensions['tablespace']['name'], $extensions['tablespace']['location']); $this->createOrReplaceTablespace($extensions['tablespace']['name'], $extensions['tablespace']['location']);
} }
@ -412,24 +426,19 @@ class PostgreSQLSchemaManager extends DBSchemaManager
} }
//Create any fulltext columns and triggers here: //Create any fulltext columns and triggers here:
if ($fulltexts) { foreach ($fulltexts as $fulltext) {
$this->query($fulltexts); $this->query($fulltext);
} }
if ($drop_triggers) { foreach ($dropTriggers as $dropTrigger) {
$this->query($drop_triggers); $this->query($dropTrigger);
} }
if ($triggers) { foreach ($triggers as $trigger) {
$this->query($triggers); $this->query($trigger);
$triggerFields = $this->triggerFieldsFromTrigger($trigger);
$triggerbits=explode(';', $triggers); if ($triggerFields) {
foreach ($triggerbits as $trigger) { //We need to run a simple query to force the database to update the triggered columns
$trigger_fields=$this->triggerFieldsFromTrigger($trigger); $this->query("UPDATE \"{$table}\" SET \"{$triggerFields[0]}\"=\"$triggerFields[0]\";");
if ($trigger_fields) {
//We need to run a simple query to force the database to update the triggered columns
$this->query("UPDATE \"{$table}\" SET \"{$trigger_fields[0]}\"=\"$trigger_fields[0]\";");
}
} }
} }
@ -454,11 +463,12 @@ class PostgreSQLSchemaManager extends DBSchemaManager
"SELECT relid FROM pg_stat_user_tables WHERE relname = ?;", "SELECT relid FROM pg_stat_user_tables WHERE relname = ?;",
array($table) array($table)
)->first(); )->first();
$oid=$stats['relid']; $oid = $stats['relid'];
//Now we can run a long query to get the clustered status: //Now we can run a long query to get the clustered status:
//If anyone knows a better way to get the clustered status, then feel free to replace this! //If anyone knows a better way to get the clustered status, then feel free to replace this!
$clustered = $this->preparedQuery(" $clustered = $this->preparedQuery(
"
SELECT c2.relname, i.indisclustered SELECT c2.relname, i.indisclustered
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
WHERE c.oid = ? AND c.oid = i.indrelid AND i.indexrelid = c2.oid AND indisclustered='t';", WHERE c.oid = ? AND c.oid = i.indrelid AND i.indexrelid = c2.oid AND indisclustered='t';",
@ -488,11 +498,11 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$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);
if (sizeof($matches)==0) { if (sizeof($matches) == 0) {
return ''; return '';
} }
if ($matches[1]=='serial8') { if ($matches[1] == 'serial8') {
return ''; return '';
} }
@ -515,20 +525,20 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$constraintExists = $this->constraintExists($constraintName, false); $constraintExists = $this->constraintExists($constraintName, false);
if (isset($matches[4])) { if (isset($matches[4])) {
//Take this new constraint and see what's outstanding from the target table: //Take this new constraint and see what's outstanding from the target table:
$constraint_bits=explode('(', $matches[4]); $constraint_bits = explode('(', $matches[4]);
$constraint_values=trim($constraint_bits[2], ')'); $constraint_values = trim($constraint_bits[2], ')');
$constraint_values_bits=explode(',', $constraint_values); $constraint_values_bits = explode(',', $constraint_values);
$default=trim($constraint_values_bits[0], " '"); $default = trim($constraint_values_bits[0], " '");
//Now go and convert anything that's not in this list to 'Page' //Now go and convert anything that's not in this list to 'Page'
//We have to run this as a query, not as part of the alteration queries due to the way they are constructed. //We have to run this as a query, not as part of the alteration queries due to the way they are constructed.
$updateConstraint=''; $updateConstraint = '';
$updateConstraint.="UPDATE \"{$tableName}\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);"; $updateConstraint .= "UPDATE \"{$tableName}\" SET \"$colName\"='$default' WHERE \"$colName\" NOT IN ($constraint_values);";
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);
@ -595,13 +605,14 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//This gets us more information than we need, but I've included it all for the moment.... //This gets us more information than we need, but I've included it all for the moment....
//if(!isset(self::$cached_fieldlists[$table])){ //if(!isset(self::$cached_fieldlists[$table])){
$fields = $this->preparedQuery(" $fields = $this->preparedQuery(
"
SELECT ordinal_position, column_name, data_type, column_default, SELECT ordinal_position, column_name, data_type, column_default,
is_nullable, character_maximum_length, numeric_precision, numeric_scale is_nullable, character_maximum_length, numeric_precision, numeric_scale
FROM information_schema.columns WHERE table_name = ? and table_schema = ? FROM information_schema.columns WHERE table_name = ? and table_schema = ?
ORDER BY ordinal_position;", ORDER BY ordinal_position;",
array($table, $this->database->currentSchema()) array($table, $this->database->currentSchema())
); );
$output = array(); $output = array();
if ($fields) { if ($fields) {
@ -618,62 +629,70 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//CHECK ("ClassName"::text = 'PageComment'::text) //CHECK ("ClassName"::text = 'PageComment'::text)
//TODO: replace all this with a regular expression! //TODO: replace all this with a regular expression!
$value=$constraint['pg_get_constraintdef']; $value = $constraint['pg_get_constraintdef'];
$value=substr($value, strpos($value, '=')); $value = substr($value, strpos($value, '='));
$value=str_replace("''", "'", $value); $value = str_replace("''", "'", $value);
$in_value=false; $in_value = false;
$constraints=array(); $constraints = array();
$current_value=''; $current_value = '';
for ($i=0; $i<strlen($value); $i++) { for ($i = 0; $i < strlen($value); $i++) {
$char=substr($value, $i, 1); $char = substr($value, $i, 1);
if ($in_value) { if ($in_value) {
$current_value.=$char; $current_value .= $char;
} }
if ($char=="'") { if ($char == "'") {
if (!$in_value) { if (!$in_value) {
$in_value=true; $in_value = true;
} else { } else {
$in_value=false; $in_value = false;
$constraints[]=substr($current_value, 0, -1); $constraints[] = substr($current_value, 0, -1);
$current_value=''; $current_value = '';
} }
} }
} }
if (sizeof($constraints)>0) { if (sizeof($constraints) > 0) {
//Get the default: //Get the default:
$default=trim(substr($field['column_default'], 0, strpos($field['column_default'], '::')), "'"); $default = trim(substr(
$output[$field['column_name']]=$this->enum(array('default'=>$default, 'name'=>$field['column_name'], 'enums'=>$constraints)); $field['column_default'],
0,
strpos($field['column_default'], '::')
), "'");
$output[$field['column_name']] = $this->enum(array(
'default' => $default,
'name' => $field['column_name'],
'enums' => $constraints
));
} }
} else { } else {
$output[$field['column_name']]='varchar(' . $field['character_maximum_length'] . ')'; $output[$field['column_name']] = 'varchar(' . $field['character_maximum_length'] . ')';
} }
break; break;
case 'numeric': case 'numeric':
$output[$field['column_name']]='decimal(' . $field['numeric_precision'] . ',' . $field['numeric_scale'] . ') default ' . floatval($field['column_default']); $output[$field['column_name']] = 'decimal(' . $field['numeric_precision'] . ',' . $field['numeric_scale'] . ') default ' . floatval($field['column_default']);
break; break;
case 'integer': case 'integer':
$output[$field['column_name']]='integer default ' . (int)$field['column_default']; $output[$field['column_name']] = 'integer default ' . (int)$field['column_default'];
break; break;
case 'timestamp without time zone': case 'timestamp without time zone':
$output[$field['column_name']]='timestamp'; $output[$field['column_name']] = 'timestamp';
break; break;
case 'smallint': case 'smallint':
$output[$field['column_name']]='smallint default ' . (int)$field['column_default']; $output[$field['column_name']] = 'smallint default ' . (int)$field['column_default'];
break; break;
case 'time without time zone': case 'time without time zone':
$output[$field['column_name']]='time'; $output[$field['column_name']] = 'time';
break; break;
case 'double precision': case 'double precision':
$output[$field['column_name']]='float'; $output[$field['column_name']] = 'float';
break; break;
default: default:
@ -690,12 +709,12 @@ class PostgreSQLSchemaManager extends DBSchemaManager
return $output; return $output;
} }
public function clearCachedFieldlist($tableName=false) public function clearCachedFieldlist($tableName = false)
{ {
if ($tableName) { if ($tableName) {
unset(self::$cached_fieldlists[$tableName]); unset(self::$cached_fieldlists[$tableName]);
} else { } else {
self::$cached_fieldlists=array(); self::$cached_fieldlists = array();
} }
return true; return true;
} }
@ -761,7 +780,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
break; break;
case 'index': case 'index':
//'index' is the same as default, just a normal index with the default type decided by the database. //'index' is the same as default, just a normal index with the default type decided by the database.
default: default:
$spec = "create index \"$tableCol\" ON \"$tableName\" (" . $this->implodeColumnList($indexSpec['columns']) . ") $fillfactor $where"; $spec = "create index \"$tableCol\" ON \"$tableName\" (" . $this->implodeColumnList($indexSpec['columns']) . ") $fillfactor $where";
} }
@ -790,22 +809,36 @@ class PostgreSQLSchemaManager extends DBSchemaManager
* Given a trigger name attempt to determine the columns upon which it acts * Given a trigger name attempt to determine the columns upon which it acts
* *
* @param string $triggerName Postgres trigger name * @param string $triggerName Postgres trigger name
* @param string $table
* @return array List of columns * @return array List of columns
*/ */
protected function extractTriggerColumns($triggerName) protected function extractTriggerColumns($triggerName, $table)
{ {
$trigger = $this->preparedQuery( $trigger = $this->preparedQuery(
"SELECT tgargs FROM pg_catalog.pg_trigger WHERE tgname = ?", "SELECT t.tgargs
array($triggerName) FROM pg_catalog.pg_trigger t
INNER JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = ?
AND n.nspname = ?
AND t.tgname = ?",
[
$table,
$this->database->currentSchema(),
$triggerName
]
)->first(); )->first();
// Option 1: output as a string // Convert stream to string
if (strpos($trigger['tgargs'], '\000') !== false) { if (is_resource($trigger['tgargs'])) {
$argList = explode('\000', $trigger['tgargs']); $trigger['tgargs'] = stream_get_contents($trigger['tgargs']);
array_pop($argList); }
// Option 2: hex-encoded (not sure why this happens, depends on PGSQL config) if (strpos($trigger['tgargs'], "\000") !== false) {
// Option 1: output as a string (PDO)
$argList = array_filter(explode("\000", $trigger['tgargs']));
} else { } else {
// Option 2: hex-encoded (pg_sql non-pdo)
$bytes = str_split($trigger['tgargs'], 2); $bytes = str_split($trigger['tgargs'], 2);
$argList = array(); $argList = array();
$nextArg = ""; $nextArg = "";
@ -826,7 +859,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
public function indexList($table) public function indexList($table)
{ {
//Retrieve a list of indexes for the specified table //Retrieve a list of indexes for the specified table
$indexes = $this->preparedQuery(" $indexes = $this->preparedQuery(
"
SELECT tablename, indexname, indexdef SELECT tablename, indexname, indexdef
FROM pg_catalog.pg_indexes FROM pg_catalog.pg_indexes
WHERE tablename = ? AND schemaname = ?;", WHERE tablename = ? AND schemaname = ?;",
@ -843,12 +877,12 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$type = ''; $type = '';
//Check for uniques: //Check for uniques:
if (substr($index['indexdef'], 0, 13)=='CREATE UNIQUE') { if (substr($index['indexdef'], 0, 13) == 'CREATE UNIQUE') {
$type = 'unique'; $type = 'unique';
} }
//check for hashes, btrees etc: //check for hashes, btrees etc:
if (strpos(strtolower($index['indexdef']), 'using hash ')!==false) { if (strpos(strtolower($index['indexdef']), 'using hash ') !== false) {
$type = 'hash'; $type = 'hash';
} }
@ -856,7 +890,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//if(strpos(strtolower($index['indexdef']), 'using btree ')!==false) //if(strpos(strtolower($index['indexdef']), 'using btree ')!==false)
// $prefix='using btree '; // $prefix='using btree ';
if (strpos(strtolower($index['indexdef']), 'using rtree ')!==false) { if (strpos(strtolower($index['indexdef']), 'using rtree ') !== false) {
$type = 'rtree'; $type = 'rtree';
} }
@ -865,7 +899,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$type = 'fulltext'; $type = 'fulltext';
// Extract trigger information from postgres // Extract trigger information from postgres
$triggerName = preg_replace('/^ix_/', 'ts_', $index['indexname']); $triggerName = preg_replace('/^ix_/', 'ts_', $index['indexname']);
$columns = $this->extractTriggerColumns($triggerName); $columns = $this->extractTriggerColumns($triggerName, $table);
$columnString = $this->implodeColumnList($columns); $columnString = $this->implodeColumnList($columns);
} else { } else {
$columnString = $this->quoteColumnSpecString($index['indexdef']); $columnString = $this->quoteColumnSpecString($index['indexdef']);
@ -908,7 +942,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
protected function constraintExists($constraint, $cache = true) protected function constraintExists($constraint, $cache = true)
{ {
if (!$cache || !isset(self::$cached_constraints[$constraint])) { if (!$cache || !isset(self::$cached_constraints[$constraint])) {
$value = $this->preparedQuery(" $value = $this->preparedQuery(
"
SELECT conname,pg_catalog.pg_get_constraintdef(r.oid, true) SELECT conname,pg_catalog.pg_get_constraintdef(r.oid, true)
FROM pg_catalog.pg_constraint r FROM pg_catalog.pg_constraint r
INNER JOIN pg_catalog.pg_namespace n INNER JOIN pg_catalog.pg_namespace n
@ -968,7 +1003,8 @@ class PostgreSQLSchemaManager extends DBSchemaManager
*/ */
protected function dropTrigger($triggerName, $tableName) protected function dropTrigger($triggerName, $tableName)
{ {
$exists = $this->preparedQuery(" $exists = $this->preparedQuery(
"
SELECT trigger_name SELECT trigger_name
FROM information_schema.triggers FROM information_schema.triggers
WHERE trigger_name = ? AND trigger_schema = ?;", WHERE trigger_name = ? AND trigger_schema = ?;",
@ -988,18 +1024,18 @@ class PostgreSQLSchemaManager extends DBSchemaManager
protected function triggerFieldsFromTrigger($trigger) protected function triggerFieldsFromTrigger($trigger)
{ {
if ($trigger) { if ($trigger) {
$tsvector='tsvector_update_trigger'; $tsvector = 'tsvector_update_trigger';
$ts_pos=strpos($trigger, $tsvector); $ts_pos = strpos($trigger, $tsvector);
$details=trim(substr($trigger, $ts_pos+strlen($tsvector)), '();'); $details = trim(substr($trigger, $ts_pos + strlen($tsvector)), '();');
//Now split this into bits: //Now split this into bits:
$bits=explode(',', $details); $bits = explode(',', $details);
$fields=$bits[2]; $fields = $bits[2];
$field_bits=explode(',', str_replace('"', '', $fields)); $field_bits = explode(',', str_replace('"', '', $fields));
$result=array(); $result = array();
foreach ($field_bits as $field_bit) { foreach ($field_bits as $field_bit) {
$result[]=trim($field_bit); $result[] = trim($field_bit);
} }
return $result; return $result;
@ -1063,7 +1099,10 @@ class PostgreSQLSchemaManager extends DBSchemaManager
public function enum($values) public function enum($values)
{ {
$default = " default '{$values['default']}'"; $default = " default '{$values['default']}'";
return "varchar(255)" . $default . " check (\"" . $values['name'] . "\" in ('" . implode('\', \'', $values['enums']) . "'))"; return "varchar(255)" . $default . " check (\"" . $values['name'] . "\" in ('" . implode(
'\', \'',
$values['enums']
) . "', null))";
} }
/** /**
@ -1250,7 +1289,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
*/ */
protected function enumValuesFromConstraint($constraint) protected function enumValuesFromConstraint($constraint)
{ {
$constraint = substr($constraint, strpos($constraint, 'ANY (ARRAY[')+11); $constraint = substr($constraint, strpos($constraint, 'ANY (ARRAY[') + 11);
$constraint = substr($constraint, 0, -11); $constraint = substr($constraint, 0, -11);
$constraints = array(); $constraints = array();
$segments = explode(',', $constraint); $segments = explode(',', $constraint);
@ -1314,21 +1353,25 @@ class PostgreSQLSchemaManager extends DBSchemaManager
//We need the plpgsql language to be installed for this to work: //We need the plpgsql language to be installed for this to work:
$this->createLanguage('plpgsql'); $this->createLanguage('plpgsql');
$trigger='CREATE OR REPLACE FUNCTION ' . $tableName . '_insert_trigger() RETURNS TRIGGER AS $$ BEGIN '; $trigger = 'CREATE OR REPLACE FUNCTION ' . $tableName . '_insert_trigger() RETURNS TRIGGER AS $$ BEGIN ';
$first=true; $first = true;
//Do we need to create a tablespace for this item? //Do we need to create a tablespace for this item?
if ($extensions && isset($extensions['tablespace'])) { if ($extensions && isset($extensions['tablespace'])) {
$this->createOrReplaceTablespace($extensions['tablespace']['name'], $extensions['tablespace']['location']); $this->createOrReplaceTablespace($extensions['tablespace']['name'], $extensions['tablespace']['location']);
$tableSpace=' TABLESPACE ' . $extensions['tablespace']['name']; $tableSpace = ' TABLESPACE ' . $extensions['tablespace']['name'];
} else { } else {
$tableSpace=''; $tableSpace = '';
} }
foreach ($partitions as $partition_name => $partition_value) { foreach ($partitions as $partition_name => $partition_value) {
//Check that this child table does not already exist: //Check that this child table does not already exist:
if (!$this->hasTable($partition_name)) { if (!$this->hasTable($partition_name)) {
$this->query("CREATE TABLE \"$partition_name\" (CHECK (" . str_replace('NEW.', '', $partition_value) . ")) INHERITS (\"$tableName\")$tableSpace;"); $this->query("CREATE TABLE \"$partition_name\" (CHECK (" . str_replace(
'NEW.',
'',
$partition_value
) . ")) INHERITS (\"$tableName\")$tableSpace;");
} else { } else {
//Drop the constraint, we will recreate in in the next line //Drop the constraint, we will recreate in in the next line
$constraintName = "{$partition_name}_pkey"; $constraintName = "{$partition_name}_pkey";
@ -1342,10 +1385,10 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$this->query("ALTER TABLE \"$partition_name\" ADD CONSTRAINT \"{$partition_name}_pkey\" PRIMARY KEY (\"ID\");"); $this->query("ALTER TABLE \"$partition_name\" ADD CONSTRAINT \"{$partition_name}_pkey\" PRIMARY KEY (\"ID\");");
if ($first) { if ($first) {
$trigger.='IF'; $trigger .= 'IF';
$first=false; $first = false;
} else { } else {
$trigger.='ELSIF'; $trigger .= 'ELSIF';
} }
$trigger .= " ($partition_value) THEN INSERT INTO \"$partition_name\" VALUES (NEW.*);"; $trigger .= " ($partition_value) THEN INSERT INTO \"$partition_name\" VALUES (NEW.*);";
@ -1354,7 +1397,7 @@ class PostgreSQLSchemaManager extends DBSchemaManager
// We need to propogate the indexes through to the child pages. // We need to propogate the indexes through to the child pages.
// Some of this code is duplicated, and could be tidied up // Some of this code is duplicated, and could be tidied up
foreach ($indexes as $name => $this_index) { foreach ($indexes as $name => $this_index) {
if ($this_index['type']=='fulltext') { if ($this_index['type'] == 'fulltext') {
$fillfactor = $where = ''; $fillfactor = $where = '';
if (isset($this_index['fillfactor'])) { if (isset($this_index['fillfactor'])) {
$fillfactor = 'WITH (FILLFACTOR = ' . $this_index['fillfactor'] . ')'; $fillfactor = 'WITH (FILLFACTOR = ' . $this_index['fillfactor'] . ')';
@ -1363,7 +1406,10 @@ class PostgreSQLSchemaManager extends DBSchemaManager
$where = 'WHERE ' . $this_index['where']; $where = 'WHERE ' . $this_index['where'];
} }
$clusterMethod = PostgreSQLDatabase::default_fts_cluster_method(); $clusterMethod = PostgreSQLDatabase::default_fts_cluster_method();
$this->query("CREATE INDEX \"" . $this->buildPostgresIndexName($partition_name, $this_index['name']) . "\" ON \"" . $partition_name . "\" USING $clusterMethod(\"ts_" . $name . "\") $fillfactor $where"); $this->query("CREATE INDEX \"" . $this->buildPostgresIndexName(
$partition_name,
$this_index['name']
) . "\" ON \"" . $partition_name . "\" USING $clusterMethod(\"ts_" . $name . "\") $fillfactor $where");
$ts_details = $this->fulltext($this_index, $partition_name, $name); $ts_details = $this->fulltext($this_index, $partition_name, $name);
$this->query($ts_details['triggers']); $this->query($ts_details['triggers']);
} else { } else {

View File

@ -1,27 +1,41 @@
{ {
"name": "silverstripe/postgresql", "name": "silverstripe/postgresql",
"description": "SilverStripe now has tentative support for PostgreSQL ('Postgres')", "description": "SilverStripe now has tentative support for PostgreSQL ('Postgres')",
"type": "silverstripe-vendormodule", "type": "silverstripe-vendormodule",
"keywords": ["silverstripe", "postgresql", "database"], "keywords": [
"license": "BSD-3-Clause", "silverstripe",
"authors": [ "postgresql",
{ "database"
"name": "Sam Minnée", ],
"email": "sam@silverstripe.com" "license": "BSD-3-Clause",
} "authors": [
], {
"require": { "name": "Sam Minnée",
"silverstripe/framework": "^4.0@dev", "email": "sam@silverstripe.com"
"silverstripe/vendor-plugin": "^1.0" }
}, ],
"require-dev": { "require": {
"phpunit/phpunit": "^5.7" "silverstripe/framework": "^4",
}, "silverstripe/vendor-plugin": "^1.0"
"extra": { },
"branch-alias": { "require-dev": {
"dev-master": "2.0.x-dev" "phpunit/phpunit": "^5.7"
} },
}, "extra": {
"minimum-stability": "dev", "branch-alias": {
"prefer-stable": true "dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"SilverStripe\\PostgreSQL\\": "code/",
"SilverStripe\\PostgreSQL\\Tests\\": "tests/"
}
},
"scripts": {
"lint": "phpcs code/ tests/",
"lint-clean": "phpcbf code/ tests/"
},
"minimum-stability": "dev",
"prefer-stable": true
} }

30
phpcs.xml.dist Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<!-- base rules are PSR-2 -->
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols" />
<exclude name="PSR2.Classes.PropertyDeclaration" />
<exclude name="PSR2.ControlStructures.SwitchDeclaration" /> <!-- causes php notice while linting -->
<exclude name="PSR2.ControlStructures.SwitchDeclaration.WrongOpenercase" />
<exclude name="PSR2.ControlStructures.SwitchDeclaration.WrongOpenerdefault" />
<exclude name="PSR2.ControlStructures.SwitchDeclaration.TerminatingComment" />
<exclude name="PSR2.Methods.MethodDeclaration.Underscore" />
<exclude name="Squiz.Scope.MethodScope" />
<exclude name="Squiz.Classes.ValidClassName.NotCamelCaps" />
<exclude name="Generic.Files.LineLength.TooLong" />
<exclude name="PEAR.Functions.ValidDefaultValue.NotAtEnd" />
</rule>
<!-- include php files only -->
<arg name="extensions" value="php,lib,inc,php5"/>
<!-- PHP-PEG generated file not intended for human consumption -->
<exclude-pattern>*/SSTemplateParser.php$</exclude-pattern>
<exclude-pattern>*/_fakewebroot/*</exclude-pattern>
<exclude-pattern>*/fixtures/*</exclude-pattern>
</ruleset>

View File

@ -1,4 +1,4 @@
<phpunit bootstrap="cms/tests/bootstrap.php" colors="true"> <phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
<testsuite name="Default"> <testsuite name="Default">
<directory>tests</directory> <directory>tests</directory>

View File

@ -1,5 +1,7 @@
<?php <?php
namespace SilverStripe\PostgreSQL\Tests;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\PostgreSQL\PostgreSQLConnector; use SilverStripe\PostgreSQL\PostgreSQLConnector;
@ -23,7 +25,7 @@ class PostgreSQLConnectorTest extends SapphireTest
// Ignoring question mark placeholders within string literals // Ignoring question mark placeholders within string literals
$this->assertEquals( $this->assertEquals(
"SELECT * FROM Table WHERE ID = $1 AND Name = $2 AND Content = '<p>What is love?</p>'", "SELECT * FROM Table WHERE ID = $1 AND Name = $2 AND Content = '<p>What is love?</p>'",
$connector->replacePlaceholders( $connector->replacePlaceholders(
"SELECT * FROM Table WHERE ID = ? AND Name = ? AND Content = '<p>What is love?</p>'" "SELECT * FROM Table WHERE ID = ? AND Name = ? AND Content = '<p>What is love?</p>'"
) )
@ -31,7 +33,7 @@ class PostgreSQLConnectorTest extends SapphireTest
// Ignoring question mark placeholders within string literals with escaped slashes // Ignoring question mark placeholders within string literals with escaped slashes
$this->assertEquals( $this->assertEquals(
"SELECT * FROM Table WHERE ID = $1 AND Title = '\\'' AND Content = '<p>What is love?</p>' AND Name = $2", "SELECT * FROM Table WHERE ID = $1 AND Title = '\\'' AND Content = '<p>What is love?</p>' AND Name = $2",
$connector->replacePlaceholders( $connector->replacePlaceholders(
"SELECT * FROM Table WHERE ID = ? AND Title = '\\'' AND Content = '<p>What is love?</p>' AND Name = ?" "SELECT * FROM Table WHERE ID = ? AND Title = '\\'' AND Content = '<p>What is love?</p>' AND Name = ?"
) )
@ -39,7 +41,7 @@ class PostgreSQLConnectorTest extends SapphireTest
// same as above, but use double single quote escape syntax // same as above, but use double single quote escape syntax
$this->assertEquals( $this->assertEquals(
"SELECT * FROM Table WHERE ID = $1 AND Title = '''' AND Content = '<p>What is love?</p>' AND Name = $2", "SELECT * FROM Table WHERE ID = $1 AND Title = '''' AND Content = '<p>What is love?</p>' AND Name = $2",
$connector->replacePlaceholders( $connector->replacePlaceholders(
"SELECT * FROM Table WHERE ID = ? AND Title = '''' AND Content = '<p>What is love?</p>' AND Name = ?" "SELECT * FROM Table WHERE ID = ? AND Title = '''' AND Content = '<p>What is love?</p>' AND Name = ?"
) )

View File

@ -1,5 +1,9 @@
<?php <?php
namespace SilverStripe\PostgreSQL\Tests;
use Exception;
use Page;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
@ -15,19 +19,18 @@ class PostgreSQLDatabaseTest extends SapphireTest
public function testReadOnlyTransaction() public function testReadOnlyTransaction()
{ {
if ( if (DB::get_conn()->supportsTransactions() == true
DB::get_conn()->supportsTransactions() == true
&& DB::get_conn() instanceof PostgreSQLDatabase && DB::get_conn() instanceof PostgreSQLDatabase
) { ) {
$page=new Page(); $page = new Page();
$page->Title='Read only success'; $page->Title = 'Read only success';
$page->write(); $page->write();
DB::get_conn()->transactionStart('READ ONLY'); DB::get_conn()->transactionStart('READ ONLY');
try { try {
$page=new Page(); $page = new Page();
$page->Title='Read only page failed'; $page->Title = 'Read only page failed';
$page->write(); $page->write();
} catch (Exception $e) { } catch (Exception $e) {
//could not write this record //could not write this record
@ -39,8 +42,8 @@ class PostgreSQLDatabaseTest extends SapphireTest
DataObject::flush_and_destroy_cache(); DataObject::flush_and_destroy_cache();
$success=DataObject::get('Page', "\"Title\"='Read only success'"); $success = DataObject::get('Page', "\"Title\"='Read only success'");
$fail=DataObject::get('Page', "\"Title\"='Read only page failed'"); $fail = DataObject::get('Page', "\"Title\"='Read only page failed'");
//This page should be in the system //This page should be in the system
$this->assertTrue(is_object($success) && $success->exists()); $this->assertTrue(is_object($success) && $success->exists());

View File

@ -1,5 +1,6 @@
<?php <?php
namespace SilverStripe\PostgreSQL\Tests;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLSelect;