mirror of
https://github.com/silverstripe/silverstripe-postgresql
synced 2024-10-22 15:05:45 +00:00
commit
c22f7faa53
@ -473,6 +473,38 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
return $tableName;
|
return $tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the internal Postgres index name given the silverstripe table and index name
|
||||||
|
* @param string $tableName
|
||||||
|
* @param string $indexName
|
||||||
|
* @param string $prefix The optional prefix for the index. Defaults to "ix" for indexes.
|
||||||
|
* @return string The postgres name of the index
|
||||||
|
*/
|
||||||
|
function buildPostgresIndexName($tableName, $indexName, $prefix = 'ix') {
|
||||||
|
|
||||||
|
// Replace namespace character
|
||||||
|
$tableNameSafe = str_replace("\\", "_", $tableName);
|
||||||
|
|
||||||
|
// Assume all indexes also contain the table name
|
||||||
|
$indexNamePG = "{$prefix}_{$tableNameSafe}_{$indexName}";
|
||||||
|
|
||||||
|
// Limit to 63 characters
|
||||||
|
if (strlen($indexNamePG) > 63)
|
||||||
|
return substr($indexNamePG, 0, 63);
|
||||||
|
return $indexNamePG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the internal Postgres trigger name given the silverstripe table and trigger name
|
||||||
|
* @param string $tableName
|
||||||
|
* @param string $triggerName
|
||||||
|
* @return string The postgres name of the trigger
|
||||||
|
*/
|
||||||
|
function buildPostgresTriggerName($tableName, $triggerName) {
|
||||||
|
// Kind of cheating, but behaves the same way as indexes
|
||||||
|
return $this->buildPostgresIndexName($tableName, $triggerName, 'ts');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alter a table's schema.
|
* Alter a table's schema.
|
||||||
* @param $table The name of the table to alter
|
* @param $table The name of the table to alter
|
||||||
@ -483,17 +515,14 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
*/
|
*/
|
||||||
public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null) {
|
public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null) {
|
||||||
|
|
||||||
$fieldSchemas = $indexSchemas = "";
|
|
||||||
$alterList = array();
|
$alterList = array();
|
||||||
if($newFields) foreach($newFields as $k => $v) $alterList[] .= "ADD \"$k\" $v";
|
if($newFields) foreach($newFields as $fieldName => $fieldSpec) {
|
||||||
|
$alterList[] = "ADD \"$fieldName\" $fieldSpec";
|
||||||
if($alteredFields) {
|
|
||||||
foreach($alteredFields as $k => $v) {
|
|
||||||
|
|
||||||
$val=$this->alterTableAlterColumn($tableName, $k, $v);
|
|
||||||
if($val!='')
|
|
||||||
$alterList[] .= $val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($alteredFields) foreach ($alteredFields as $indexName => $indexSpec) {
|
||||||
|
$val = $this->alterTableAlterColumn($tableName, $indexName, $indexSpec);
|
||||||
|
if (!empty($val)) $alterList[] = $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Do we need to do anything with the tablespaces?
|
//Do we need to do anything with the tablespaces?
|
||||||
@ -510,15 +539,16 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
$fulltexts=false;
|
$fulltexts=false;
|
||||||
$drop_triggers=false;
|
$drop_triggers=false;
|
||||||
$triggers=false;
|
$triggers=false;
|
||||||
if($alteredIndexes) foreach($alteredIndexes as $key=>$v) {
|
if($alteredIndexes) foreach($alteredIndexes as $indexName=>$indexSpec) {
|
||||||
//We are only going to delete indexes which exist
|
|
||||||
$indexes=$this->indexList($tableName);
|
|
||||||
|
|
||||||
if($v['type']=='fulltext'){
|
$indexSpec = $this->parseIndexSpec($indexName, $indexSpec);
|
||||||
|
$indexNamePG = $this->buildPostgresIndexName($tableName, $indexName);
|
||||||
|
|
||||||
|
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($v, $tableName, $key);
|
$ts_details = $this->fulltext($indexSpec, $tableName, $indexName);
|
||||||
|
|
||||||
//Drop this column if it already exists:
|
//Drop this column if it already exists:
|
||||||
|
|
||||||
@ -527,60 +557,42 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
$fulltexts.="ALTER TABLE \"{$tableName}\" DROP COLUMN \"{$ts_details['ts_name']}\";";
|
$fulltexts.="ALTER TABLE \"{$tableName}\" DROP COLUMN \"{$ts_details['ts_name']}\";";
|
||||||
}
|
}
|
||||||
|
|
||||||
$drop_triggers.= 'DROP TRIGGER IF EXISTS ts_' . strtolower($tableName) . '_' . strtolower($key) . ' ON "' . $tableName . '";';
|
|
||||||
$alterIndexList[] = 'DROP INDEX IF EXISTS ix_' . strtolower($tableName) . '_' . strtolower($v['value']) . ';';
|
|
||||||
|
|
||||||
// We'll execute these later:
|
// We'll execute these later:
|
||||||
|
$triggerNamePG = $this->buildPostgresTriggerName($tableName, $indexName);
|
||||||
|
$drop_triggers.= "DROP TRIGGER IF EXISTS \"$triggerNamePG\" ON \"$tableName\";";
|
||||||
$fulltexts .= "ALTER TABLE \"{$tableName}\" ADD COLUMN {$ts_details['fulltexts']};";
|
$fulltexts .= "ALTER TABLE \"{$tableName}\" ADD COLUMN {$ts_details['fulltexts']};";
|
||||||
$triggers .= $ts_details['triggers'];
|
$triggers .= $ts_details['triggers'];
|
||||||
} else {
|
|
||||||
if(isset($indexes[$v['value']])){
|
|
||||||
if(is_array($v))
|
|
||||||
$alterIndexList[] = 'DROP INDEX IF EXISTS ix_' . strtolower($tableName) . '_' . strtolower($v['value']) . ';';
|
|
||||||
else
|
|
||||||
$alterIndexList[] = 'DROP INDEX IF EXISTS ix_' . strtolower($tableName) . '_' . strtolower(trim($v, '()')) . ';';
|
|
||||||
|
|
||||||
$k=$v['value'];
|
|
||||||
$createIndex=$this->getIndexSqlDefinition($tableName, $k, $v);
|
|
||||||
if($createIndex!==false)
|
|
||||||
$alterIndexList[] .= $createIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create index action (including fulltext)
|
||||||
|
$alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";";
|
||||||
|
$createIndex = $this->getIndexSqlDefinition($tableName, $indexName, $indexSpec);
|
||||||
|
if($createIndex!==false) $alterIndexList[] = $createIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the new indexes:
|
||||||
|
if($newIndexes) foreach($newIndexes as $indexName => $indexSpec){
|
||||||
|
|
||||||
|
$indexSpec = $this->parseIndexSpec($indexName, $indexSpec);
|
||||||
|
$indexNamePG = $this->buildPostgresIndexName($tableName, $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($newIndexes){
|
if($indexSpec['type']=='fulltext') {
|
||||||
foreach($newIndexes as $name=>$this_index){
|
$ts_details=$this->fulltext($indexSpec, $tableName, $indexName);
|
||||||
if(is_array($this_index) && $this_index['type']=='fulltext'){
|
|
||||||
$ts_details=$this->fulltext($this_index, $tableName, $name);
|
|
||||||
if(!isset($fieldList[$ts_details['ts_name']])){
|
if(!isset($fieldList[$ts_details['ts_name']])){
|
||||||
$fulltexts.="ALTER TABLE \"{$tableName}\" ADD COLUMN {$ts_details['fulltexts']};";
|
$fulltexts.="ALTER TABLE \"{$tableName}\" ADD COLUMN {$ts_details['fulltexts']};";
|
||||||
$triggers.=$ts_details['triggers'];
|
$triggers.=$ts_details['triggers'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add the new indexes:
|
|
||||||
if($newIndexes) foreach($newIndexes as $k=>$v){
|
|
||||||
//Check that this index doesn't already exist:
|
//Check that this index doesn't already exist:
|
||||||
$indexes=$this->indexList($tableName);
|
$indexes=$this->indexList($tableName);
|
||||||
if(!is_array($v)){
|
if(isset($indexes[$indexName])){
|
||||||
$name=trim($v, '()');
|
$alterIndexList[] = "DROP INDEX IF EXISTS \"$indexNamePG\";";
|
||||||
} else {
|
|
||||||
$name=(isset($v['name'])) ? $v['name'] : $k;
|
|
||||||
}
|
|
||||||
if(isset($indexes[$name])){
|
|
||||||
if(is_array($v)){
|
|
||||||
$alterIndexList[] = 'DROP INDEX IF EXISTS ix_' . strtolower($tableName) . '_' . strtolower($v['value']) . ';';
|
|
||||||
} else {
|
|
||||||
$alterIndexList[] = 'DROP INDEX IF EXISTS ' . $indexes[$name]['indexname'] . ';';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$createIndex=$this->getIndexSqlDefinition($tableName, $k, $v);
|
$createIndex=$this->getIndexSqlDefinition($tableName, $indexName, $indexSpec);
|
||||||
if($createIndex!==false)
|
if($createIndex!==false)
|
||||||
$alterIndexList[] = $createIndex;
|
$alterIndexList[] = $createIndex;
|
||||||
}
|
}
|
||||||
@ -605,10 +617,8 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create any fulltext columns and triggers here:
|
//Create any fulltext columns and triggers here:
|
||||||
if($fulltexts)
|
if($fulltexts) $this->query($fulltexts);
|
||||||
$this->query($fulltexts);
|
if($drop_triggers) $this->query($drop_triggers);
|
||||||
if($drop_triggers)
|
|
||||||
$this->query($drop_triggers);
|
|
||||||
|
|
||||||
if($triggers) {
|
if($triggers) {
|
||||||
$this->query($triggers);
|
$this->query($triggers);
|
||||||
@ -624,8 +634,7 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($alterIndexList as $alteration)
|
foreach($alterIndexList as $alteration) $this->query($alteration);
|
||||||
$this->query($alteration);
|
|
||||||
|
|
||||||
//If we have a partitioning requirement, we do that here:
|
//If we have a partitioning requirement, we do that here:
|
||||||
if($advancedOptions && isset($advancedOptions['partitions'])){
|
if($advancedOptions && isset($advancedOptions['partitions'])){
|
||||||
@ -634,7 +643,8 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
|
|
||||||
//Lastly, clustering goes here:
|
//Lastly, clustering goes here:
|
||||||
if ($advancedOptions && isset($advancedOptions['cluster'])) {
|
if ($advancedOptions && isset($advancedOptions['cluster'])) {
|
||||||
DB::query("CLUSTER \"$tableName\" USING ix_{$tableName}_{$advancedOptions['cluster']};");
|
$clusterIndex = $this->buildPostgresIndexName($tableName, $advancedOptions['cluster']);
|
||||||
|
DB::query("CLUSTER \"$tableName\" USING \"$clusterIndex\";");
|
||||||
} else {
|
} else {
|
||||||
//Check that clustering is not on this table, and if it is, remove it:
|
//Check that clustering is not on this table, and if it is, remove it:
|
||||||
|
|
||||||
@ -668,17 +678,17 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
$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 '';
|
|
||||||
|
|
||||||
if(isset($matches[1])) {
|
if(isset($matches[1])) {
|
||||||
$alterCol = "ALTER COLUMN \"$colName\" TYPE $matches[1]\n";
|
$alterCol = "ALTER COLUMN \"$colName\" TYPE $matches[1]\n";
|
||||||
|
|
||||||
// SET null / not null
|
// SET null / not null
|
||||||
if(!empty($matches[2])) $alterCol .= ",\nALTER COLUMN \"$colName\" SET $matches[2]";
|
if(!empty($matches[2])) {
|
||||||
|
$alterCol .= ",\nALTER COLUMN \"$colName\" SET $matches[2]";
|
||||||
|
}
|
||||||
|
|
||||||
// SET default (we drop it first, for reasons of precaution)
|
// SET default (we drop it first, for reasons of precaution)
|
||||||
if(!empty($matches[3])) {
|
if(!empty($matches[3])) {
|
||||||
@ -699,10 +709,12 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
//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);";
|
||||||
|
}
|
||||||
|
|
||||||
DB::query($updateConstraint);
|
DB::query($updateConstraint);
|
||||||
}
|
}
|
||||||
@ -793,7 +805,6 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
//Check to see if there's a constraint attached to this column:
|
//Check to see if there's a constraint attached to this column:
|
||||||
//$constraint=$this->query("SELECT conname,pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE r.contype = 'c' AND conname='" . $table . '_' . $field['column_name'] . "_check' ORDER BY 1;")->first();
|
//$constraint=$this->query("SELECT conname,pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r WHERE r.contype = 'c' AND conname='" . $table . '_' . $field['column_name'] . "_check' ORDER BY 1;")->first();
|
||||||
$constraint=$this->constraintExists($table . '_' . $field['column_name'] . '_check');
|
$constraint=$this->constraintExists($table . '_' . $field['column_name'] . '_check');
|
||||||
$enum='';
|
|
||||||
if($constraint){
|
if($constraint){
|
||||||
//Now we need to break this constraint text into bits so we can see what we have:
|
//Now we need to break this constraint text into bits so we can see what we have:
|
||||||
//Examples:
|
//Examples:
|
||||||
@ -882,10 +893,8 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
function clearCachedFieldlist($tableName=false){
|
function clearCachedFieldlist($tableName=false){
|
||||||
if($tableName!=false){
|
if($tableName) unset(self::$cached_fieldlists[$tableName]);
|
||||||
unset(self::$cached_fieldlists[$tableName]);
|
else self::$cached_fieldlists=array();
|
||||||
} else
|
|
||||||
self::$cached_fieldlists=array();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -898,8 +907,7 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
*/
|
*/
|
||||||
public function createIndex($tableName, $indexName, $indexSpec) {
|
public function createIndex($tableName, $indexName, $indexSpec) {
|
||||||
$createIndex=$this->getIndexSqlDefinition($tableName, $indexName, $indexSpec);
|
$createIndex=$this->getIndexSqlDefinition($tableName, $indexName, $indexSpec);
|
||||||
if($createIndex!==false)
|
if($createIndex!==false) $this->query();
|
||||||
$this->query();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -907,6 +915,7 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
* and turns it into a proper string.
|
* and turns it into a proper string.
|
||||||
* Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific
|
* Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific
|
||||||
* arrays to be created.
|
* arrays to be created.
|
||||||
|
* @see parseIndexSpec() for approximate inverse
|
||||||
*/
|
*/
|
||||||
public function convertIndexSpec($indexSpec, $asDbValue=false, $table=''){
|
public function convertIndexSpec($indexSpec, $asDbValue=false, $table=''){
|
||||||
|
|
||||||
@ -915,9 +924,7 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
//Here we create a db-specific version of whatever index we need to create.
|
//Here we create a db-specific version of whatever index we need to create.
|
||||||
switch($indexSpec['type']){
|
switch($indexSpec['type']){
|
||||||
case 'fulltext':
|
case 'fulltext':
|
||||||
//We need to include the fields so if we change the columns it's indexing, but not the name,
|
$indexSpec='fulltext (' . $indexSpec['value'] . ')';
|
||||||
//then the change will be picked up.
|
|
||||||
$indexSpec='(' . $indexSpec['name'] . ',' . $indexSpec['value'] . ')';
|
|
||||||
break;
|
break;
|
||||||
case 'unique':
|
case 'unique':
|
||||||
$indexSpec='unique (' . $indexSpec['value'] . ')';
|
$indexSpec='unique (' . $indexSpec['value'] . ')';
|
||||||
@ -938,6 +945,82 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
return $indexSpec;
|
return $indexSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a spec string safely, considering quoted columns, whitespace,
|
||||||
|
* and cleaning brackets
|
||||||
|
* @param string $spec The input index specification
|
||||||
|
* @return array List of columns in the spec
|
||||||
|
*/
|
||||||
|
function explodeColumnString($spec) {
|
||||||
|
// Remove any leading/trailing brackets and outlying modifiers
|
||||||
|
// E.g. 'unique (Title, "QuotedColumn");' => 'Title, "QuotedColumn"'
|
||||||
|
$containedSpec = preg_replace('/(.*\(\s*)|(\s*\).*)/', '', $spec);
|
||||||
|
|
||||||
|
// Split potentially quoted modifiers
|
||||||
|
// E.g. 'Title, "QuotedColumn"' => array('Title', 'QuotedColumn')
|
||||||
|
return preg_split('/"?\s*,\s*"?/', trim($containedSpec, '(") '));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a properly quoted column list from an array
|
||||||
|
* @param array $columns List of columns to implode
|
||||||
|
* @return string A properly quoted list of column names
|
||||||
|
*/
|
||||||
|
function implodeColumnList($columns) {
|
||||||
|
if(empty($columns)) return '';
|
||||||
|
return '"' . implode('","', $columns) . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an index specification in the form of a string ensure that each
|
||||||
|
* column name is property quoted, stripping brackets and modifiers.
|
||||||
|
* This index may also be in the form of a "CREATE INDEX..." sql fragment
|
||||||
|
* @param string $spec The input specification or query. E.g. 'unique (Column1, Column2)'
|
||||||
|
* @return string The properly quoted column list. E.g. '"Column1", "Column2"'
|
||||||
|
*/
|
||||||
|
function quoteColumnSpecString($spec) {
|
||||||
|
$bits = $this->explodeColumnString($spec);
|
||||||
|
return $this->implodeColumnList($bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an index spec determines the index type
|
||||||
|
* @param type $spec
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function determineIndexType($spec) {
|
||||||
|
// check array spec
|
||||||
|
if(is_array($spec) && isset($spec['type'])) {
|
||||||
|
return $spec['type'];
|
||||||
|
} elseif (!is_array($spec) && preg_match('/(?<type>\w+)\s*\(/', $spec, $matchType)) {
|
||||||
|
return strtolower($matchType['type']);
|
||||||
|
} else {
|
||||||
|
return 'index';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an array or string index spec into a universally useful array
|
||||||
|
* @see convertIndexSpec() for approximate inverse
|
||||||
|
* @param string|array $spec
|
||||||
|
* @return array The resulting spec array with the required fields name, type, and value
|
||||||
|
*/
|
||||||
|
function parseIndexSpec($name, $spec){
|
||||||
|
|
||||||
|
// Do minimal cleanup on any already parsed spec
|
||||||
|
if(is_array($spec)) {
|
||||||
|
$spec['value'] = $this->quoteColumnSpecString($spec['value']);
|
||||||
|
return $spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nicely formatted spec!
|
||||||
|
return array(
|
||||||
|
'name' => $name,
|
||||||
|
'value' => $this->quoteColumnSpecString($spec),
|
||||||
|
'type' => $this->determineIndexType($spec)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function getIndexSqlDefinition($tableName, $indexName, $indexSpec, $asDbValue=false) {
|
protected function getIndexSqlDefinition($tableName, $indexName, $indexSpec, $asDbValue=false) {
|
||||||
|
|
||||||
//TODO: create table partition support
|
//TODO: create table partition support
|
||||||
@ -947,85 +1030,55 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
//Therefore, we now check for the existance of indexes before we create them.
|
//Therefore, we now check for the existance of indexes before we create them.
|
||||||
//This is techically a bug, since new tables will not be indexed.
|
//This is techically a bug, since new tables will not be indexed.
|
||||||
|
|
||||||
if(!$asDbValue){
|
// If requesting the definition rather than the DDL
|
||||||
|
if($asDbValue) {
|
||||||
$tableCol= 'ix_' . str_replace("\\", "_", $tableName) . '_' . $indexName;
|
$indexName=trim($indexName, '()');
|
||||||
if(strlen($tableCol)>64){
|
return $indexName;
|
||||||
$tableCol=substr($indexName, 0, 59) . rand(1000, 9999);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//It is possible to specify indexes through strings:
|
// Determine index name
|
||||||
if(!is_array($indexSpec)){
|
$tableCol = $this->buildPostgresIndexName($tableName, $indexName);
|
||||||
$indexSpec=trim($indexSpec, '()');
|
|
||||||
$bits=explode(',', $indexSpec);
|
|
||||||
$indexes="\"" . implode("\",\"", $bits) . "\"";
|
|
||||||
// if some of the indexes have already been quoted then we need to remove the suplus double quotes
|
|
||||||
$indexes = str_replace('""', '"', $indexes);
|
|
||||||
|
|
||||||
//One last check:
|
// Consolidate/Cleanup spec into array format
|
||||||
$existing=DB::query("SELECT tablename FROM pg_indexes WHERE indexname='" . strtolower($tableCol) . "';")->first();
|
$indexSpec = $this->parseIndexSpec($indexName, $indexSpec);
|
||||||
if(!$existing)
|
|
||||||
return "create index $tableCol ON \"" . $tableName . "\" (" . $indexes . ");";
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
//Arrays offer much more flexibility and many more options:
|
|
||||||
|
|
||||||
//Misc options first:
|
//Misc options first:
|
||||||
$fillfactor = $where = '';
|
$fillfactor = $where = '';
|
||||||
if(isset($indexSpec['fillfactor']))
|
if (isset($indexSpec['fillfactor'])) {
|
||||||
$fillfactor = 'WITH (FILLFACTOR = ' . $indexSpec['fillfactor'] . ')';
|
$fillfactor = 'WITH (FILLFACTOR = ' . $indexSpec['fillfactor'] . ')';
|
||||||
if(isset($indexSpec['where']))
|
|
||||||
$where='WHERE ' . $indexSpec['where'];
|
|
||||||
|
|
||||||
//Fix up the value entry to be quoted:
|
|
||||||
$value_bits=explode(',', $indexSpec['value']);
|
|
||||||
$new_values=Array();
|
|
||||||
foreach($value_bits as $value){
|
|
||||||
$new_values[]="\"" . trim($value, ' "') . "\"";
|
|
||||||
}
|
}
|
||||||
$indexSpec['value']=implode(',', $new_values);
|
if (isset($indexSpec['where'])) {
|
||||||
|
$where = 'WHERE ' . $indexSpec['where'];
|
||||||
|
}
|
||||||
|
|
||||||
//One last check:
|
|
||||||
$existing=DB::query("SELECT tablename FROM pg_indexes WHERE indexname='" . strtolower($tableCol) . "';");
|
|
||||||
if(!$existing->first()){
|
|
||||||
//create a type-specific index
|
//create a type-specific index
|
||||||
// NOTE: hash should be removed. This is only here to demonstrate how other indexes can be made
|
// NOTE: hash should be removed. This is only here to demonstrate how other indexes can be made
|
||||||
|
// NOTE: Quote the index name to preserve case sensitivity
|
||||||
switch ($indexSpec['type']) {
|
switch ($indexSpec['type']) {
|
||||||
case 'fulltext':
|
case 'fulltext':
|
||||||
$spec="create index $tableCol ON \"" . $tableName . "\" USING " . $this->default_fts_cluster_method . "(\"ts_" . $indexName . "\") $fillfactor $where";
|
// @see fulltext() for the definition of the trigger that ts_$IndexName uses for fulltext searching
|
||||||
|
$spec = "create index \"$tableCol\" ON \"$tableName\" USING " . $this->default_fts_cluster_method . "(\"ts_" . $indexName . "\") $fillfactor $where";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unique':
|
case 'unique':
|
||||||
$spec="create unique index $tableCol ON \"" . $tableName . "\" (" . $indexSpec['value'] . ") $fillfactor $where";
|
$spec = "create unique index \"$tableCol\" ON \"$tableName\" (" . $indexSpec['value'] . ") $fillfactor $where";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'btree':
|
case 'btree':
|
||||||
$spec="create index $tableCol ON \"" . $tableName . "\" USING btree (" . $indexSpec['value'] . ") $fillfactor $where";
|
$spec = "create index \"$tableCol\" ON \"$tableName\" USING btree (" . $indexSpec['value'] . ") $fillfactor $where";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hash':
|
case 'hash':
|
||||||
//NOTE: this is not a recommended index type
|
//NOTE: this is not a recommended index type
|
||||||
$spec="create index $tableCol ON \"" . $tableName . "\" USING hash (" . $indexSpec['value'] . ") $fillfactor $where";
|
$spec = "create index \"$tableCol\" ON \"$tableName\" USING hash (" . $indexSpec['value'] . ") $fillfactor $where";
|
||||||
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 . "\" (" . $indexSpec['value'] . ") $fillfactor $where";
|
$spec = "create index \"$tableCol\" ON \"$tableName\" (" . $indexSpec['value'] . ") $fillfactor $where";
|
||||||
}
|
}
|
||||||
|
|
||||||
return trim($spec) . ';';
|
return trim($spec) . ';';
|
||||||
} else {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$indexName=trim($indexName, '()');
|
|
||||||
return $indexName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDbSqlDefinition($tableName, $indexName, $indexSpec) {
|
function getDbSqlDefinition($tableName, $indexName, $indexSpec) {
|
||||||
@ -1050,10 +1103,40 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
$indexType = "index";
|
$indexType = "index";
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->query("DROP INDEX $indexName");
|
$this->query("DROP INDEX \"$indexName\"");
|
||||||
$this->query("ALTER TABLE \"$tableName\" ADD $indexType \"$indexName\" $indexFields");
|
$this->query("ALTER TABLE \"$tableName\" ADD $indexType \"$indexName\" $indexFields");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a trigger name attempt to determine the columns upon which it acts
|
||||||
|
* @param string $triggerName Postgres trigger name
|
||||||
|
* @return array List of columns
|
||||||
|
*/
|
||||||
|
protected function extractTriggerColumns($triggerName)
|
||||||
|
{
|
||||||
|
$trigger = DB::query($statement = sprintf(
|
||||||
|
"SELECT tgargs FROM pg_catalog.pg_trigger WHERE tgname='%s'", $this->addslashes($triggerName)
|
||||||
|
))->first();
|
||||||
|
|
||||||
|
// Trigger columns will be extracted in an ugly hex format with null-
|
||||||
|
// terminated strings, needs some coaxing into a readable format
|
||||||
|
$tgargsHex = $trigger['tgargs'];
|
||||||
|
$tgargs = array();
|
||||||
|
$tgarg = '';
|
||||||
|
for ($i = 0; $i < strlen($tgargsHex); $i+=2) {
|
||||||
|
$hexChar = substr($tgargsHex, $i, 2);
|
||||||
|
if($hexChar == '00') {
|
||||||
|
$tgargs[] = $tgarg;
|
||||||
|
$tgarg = '';
|
||||||
|
} else {
|
||||||
|
$tgarg .= chr(hexdec($hexChar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop first two arguments (trigger name and config name) and implode into nice list
|
||||||
|
return array_slice($tgargs, 2);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of indexes in a table.
|
* Return the list of indexes in a table.
|
||||||
* @param string $table The table name.
|
* @param string $table The table name.
|
||||||
@ -1067,36 +1150,50 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
|
|
||||||
$indexList=Array();
|
$indexList=Array();
|
||||||
foreach($indexes as $index) {
|
foreach($indexes as $index) {
|
||||||
|
|
||||||
|
// Determine the name of the index
|
||||||
|
if (stristr($index['indexname'], '_pkey')) {
|
||||||
|
$indexName = 'ID';
|
||||||
|
} else {
|
||||||
|
// Extract index name by splitting the ix_TableName_ from the start of the name
|
||||||
|
$indexNamePrefix = $this->buildPostgresIndexName($table, '');
|
||||||
|
$indexName = substr($index['indexname'], strlen($indexNamePrefix));
|
||||||
|
}
|
||||||
|
|
||||||
//We don't actually need the entire created command, just a few bits:
|
//We don't actually need the entire created command, just a few bits:
|
||||||
$prefix='';
|
$prefix='';
|
||||||
|
|
||||||
//Check for uniques:
|
//Check for uniques:
|
||||||
if(substr($index['indexdef'], 0, 13)=='CREATE UNIQUE')
|
if(substr($index['indexdef'], 0, 13)=='CREATE UNIQUE') {
|
||||||
$prefix='unique ';
|
$prefix='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) {
|
||||||
$prefix='using hash ';
|
$prefix='using hash ';
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Fix me: btree is the default index type:
|
//TODO: Fix me: btree is the default index type:
|
||||||
//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) {
|
||||||
$prefix='using rtree ';
|
$prefix='using rtree ';
|
||||||
|
|
||||||
$value=explode(' ', substr($index['indexdef'], strpos($index['indexdef'], ' USING ')+7));
|
|
||||||
|
|
||||||
if(sizeof($value)>2){
|
|
||||||
for($i=2; $i<sizeof($value); $i++)
|
|
||||||
$value[1].=$value[$i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$key=substr($value[1], 0, strpos($value[1], ')'));
|
// For fulltext indexes we need to extract the columns from another source
|
||||||
$key=trim(trim(str_replace("\"", '', $key), '()'));
|
if (stristr($index['indexdef'], 'using gin')) {
|
||||||
$indexList[$key]['indexname']=$index['indexname'];
|
$prefix = 'fulltext ';
|
||||||
$indexList[$key]['spec']=$prefix . '("' . preg_replace('/ *, */','","',$key) . '")';
|
// Extract trigger information from postgres
|
||||||
|
$triggerName = $this->buildPostgresTriggerName($table, $indexName);
|
||||||
|
$columns = $this->extractTriggerColumns($triggerName);
|
||||||
|
$columnString = $this->implodeColumnList($columns);
|
||||||
|
} else {
|
||||||
|
$columnString = $this->quoteColumnSpecString($index['indexdef']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$indexList[$indexName]['indexname'] = $index['indexname'];
|
||||||
|
$indexList[$indexName]['spec'] = "$prefix($columnString)";
|
||||||
}
|
}
|
||||||
|
|
||||||
return isset($indexList) ? $indexList : null;
|
return isset($indexList) ? $indexList : null;
|
||||||
@ -1121,15 +1218,16 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
return $tables;
|
return $tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a table exists
|
||||||
|
* @param string $tableName
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
function TableExists($tableName){
|
function TableExists($tableName){
|
||||||
$schema_SQL = pg_escape_string($this->dbConn, $this->schema);
|
$schema_SQL = pg_escape_string($this->dbConn, $this->schema);
|
||||||
$result=$this->query("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = '{$schema_SQL}' AND tablename='" . $this->addslashes($tableName) . "';")->first();
|
$result=$this->query("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = '{$schema_SQL}' AND tablename='" . $this->addslashes($tableName) . "';")->first();
|
||||||
|
|
||||||
if($result)
|
return !empty($result);
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1449,18 +1547,14 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
*/
|
*/
|
||||||
function fulltext($this_index, $tableName, $name){
|
function fulltext($this_index, $tableName, $name){
|
||||||
//For full text search, we need to create a column for the index
|
//For full text search, we need to create a column for the index
|
||||||
$columns=explode(',', $this_index['value']);
|
$columns = $this->quoteColumnSpecString($this_index['value']);
|
||||||
for($i=0; $i<sizeof($columns);$i++)
|
|
||||||
$columns[$i]="\"" . trim($columns[$i]) . "\"";
|
|
||||||
|
|
||||||
$columns=implode(', ', $columns);
|
|
||||||
|
|
||||||
$fulltexts = "\"ts_$name\" tsvector";
|
$fulltexts = "\"ts_$name\" tsvector";
|
||||||
$triggerName="ts_{$tableName}_{$name}";
|
$triggerName = $this->buildPostgresTriggerName($tableName, $name);
|
||||||
$language = $this->get_search_language();
|
$language = $this->get_search_language();
|
||||||
|
|
||||||
$this->dropTrigger($triggerName, $tableName);
|
$this->dropTrigger($triggerName, $tableName);
|
||||||
$triggers="CREATE TRIGGER $triggerName BEFORE INSERT OR UPDATE
|
$triggers = "CREATE TRIGGER \"$triggerName\" BEFORE INSERT OR UPDATE
|
||||||
ON \"$tableName\" FOR EACH ROW EXECUTE PROCEDURE
|
ON \"$tableName\" FOR EACH ROW EXECUTE PROCEDURE
|
||||||
tsvector_update_trigger(\"ts_$name\", 'pg_catalog.$language', $columns);";
|
tsvector_update_trigger(\"ts_$name\", 'pg_catalog.$language', $columns);";
|
||||||
|
|
||||||
@ -1570,13 +1664,9 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
/*
|
/*
|
||||||
* This changes the index name depending on database requirements.
|
* This changes the index name depending on database requirements.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function modifyIndex($index, $spec) {
|
function modifyIndex($index, $spec) {
|
||||||
|
return $index;
|
||||||
if(is_array($spec) && $spec['type']=='fulltext')
|
|
||||||
return 'ts_' . str_replace(',', '_', $index);
|
|
||||||
else
|
|
||||||
return str_replace('_', ',', $index);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1689,6 +1779,7 @@ class PostgreSQLDatabase extends SS_Database {
|
|||||||
if(isset($objects)) $results = new ArrayList($objects);
|
if(isset($objects)) $results = new ArrayList($objects);
|
||||||
else $results = new ArrayList();
|
else $results = new ArrayList();
|
||||||
$list = new PaginatedList($results);
|
$list = new PaginatedList($results);
|
||||||
|
$list->setLimitItems(false);
|
||||||
$list->setPageStart($start);
|
$list->setPageStart($start);
|
||||||
$list->setPageLength($pageLength);
|
$list->setPageLength($pageLength);
|
||||||
$list->setTotalItems($totalCount);
|
$list->setTotalItems($totalCount);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user