2003-10-19 21:05:23 +02:00
< ? php
/**
2005-09-26 12:12:10 +02:00
V4 . 65 22 July 2005 ( c ) 2000 - 2005 John Lim ( jlim @ natsoft . com . my ) . All rights reserved .
2003-10-19 21:05:23 +02:00
Released under both BSD license and Lesser GPL library license .
Whenever there is any discrepancy between the two licenses ,
the BSD license will take precedence .
Set tabs to 4 for best viewing .
*/
2004-07-10 09:19:40 +02:00
// security - hide paths
if ( ! defined ( 'ADODB_DIR' )) die ();
2003-10-19 21:05:23 +02:00
class ADODB2_postgres extends ADODB_DataDict {
var $databaseType = 'postgres' ;
var $seqField = false ;
var $seqPrefix = 'SEQ_' ;
var $addCol = ' ADD COLUMN' ;
2004-03-15 23:17:52 +01:00
var $quote = '"' ;
2004-08-12 01:22:25 +02:00
var $renameTable = 'ALTER TABLE %s RENAME TO %s' ; // at least since 7.1
2003-10-19 21:05:23 +02:00
function MetaType ( $t , $len =- 1 , $fieldobj = false )
{
if ( is_object ( $t )) {
$fieldobj = $t ;
$t = $fieldobj -> type ;
$len = $fieldobj -> max_length ;
}
2004-10-14 22:35:50 +02:00
$is_serial = is_object ( $fieldobj ) && $fieldobj -> primary_key && $fieldobj -> unique &&
$fieldobj -> has_default && substr ( $fieldobj -> default_value , 0 , 8 ) == 'nextval(' ;
2003-10-19 21:05:23 +02:00
switch ( strtoupper ( $t )) {
case 'INTERVAL' :
case 'CHAR' :
case 'CHARACTER' :
case 'VARCHAR' :
case 'NAME' :
case 'BPCHAR' :
if ( $len <= $this -> blobSize ) return 'C' ;
case 'TEXT' :
return 'X' ;
case 'IMAGE' : // user defined type
case 'BLOB' : // user defined type
case 'BIT' : // This is a bit string, not a single bit, so don't return 'L'
case 'VARBIT' :
case 'BYTEA' :
return 'B' ;
case 'BOOL' :
case 'BOOLEAN' :
return 'L' ;
case 'DATE' :
return 'D' ;
case 'TIME' :
case 'DATETIME' :
case 'TIMESTAMP' :
case 'TIMESTAMPTZ' :
return 'T' ;
2004-10-14 22:35:50 +02:00
case 'INTEGER' : return ! $is_serial ? 'I' : 'R' ;
2003-10-19 21:05:23 +02:00
case 'SMALLINT' :
2004-10-14 22:35:50 +02:00
case 'INT2' : return ! $is_serial ? 'I2' : 'R' ;
case 'INT4' : return ! $is_serial ? 'I4' : 'R' ;
2003-10-19 21:05:23 +02:00
case 'BIGINT' :
2004-10-14 22:35:50 +02:00
case 'INT8' : return ! $is_serial ? 'I8' : 'R' ;
2003-10-19 21:05:23 +02:00
case 'OID' :
case 'SERIAL' :
return 'R' ;
case 'FLOAT4' :
case 'FLOAT8' :
case 'DOUBLE PRECISION' :
case 'REAL' :
return 'F' ;
default :
return 'N' ;
}
}
function ActualType ( $meta )
{
switch ( $meta ) {
case 'C' : return 'VARCHAR' ;
case 'XL' :
case 'X' : return 'TEXT' ;
case 'C2' : return 'VARCHAR' ;
case 'X2' : return 'TEXT' ;
case 'B' : return 'BYTEA' ;
case 'D' : return 'DATE' ;
case 'T' : return 'TIMESTAMP' ;
2005-09-26 12:12:10 +02:00
case 'L' : return 'BOOLEAN' ;
2003-10-19 21:05:23 +02:00
case 'I' : return 'INTEGER' ;
case 'I1' : return 'SMALLINT' ;
case 'I2' : return 'INT2' ;
case 'I4' : return 'INT4' ;
case 'I8' : return 'INT8' ;
case 'F' : return 'FLOAT8' ;
case 'N' : return 'NUMERIC' ;
default :
return $meta ;
}
}
2004-09-19 10:26:45 +02:00
/**
* Adding a new Column
*
* reimplementation of the default function as postgres does NOT allow to set the default in the same statement
*
* @ param string $tabname table - name
* @ param string $flds column - names and types for the changed columns
* @ return array with SQL strings
*/
function AddColumnSQL ( $tabname , $flds )
{
$tabname = $this -> TableName ( $tabname );
$sql = array ();
list ( $lines , $pkey ) = $this -> _GenFields ( $flds );
$alter = 'ALTER TABLE ' . $tabname . $this -> addCol . ' ' ;
foreach ( $lines as $v ) {
2004-10-23 17:00:44 +02:00
if (( $not_null = preg_match ( '/NOT NULL/i' , $v ))) {
$v = preg_replace ( '/NOT NULL/i' , '' , $v );
}
2005-09-26 12:28:28 +02:00
if ( preg_match ( '/^([^ ]+) .*DEFAULT ([^ ]+)/' , $v , $matches )) {
2004-09-19 10:26:45 +02:00
list (, $colname , $default ) = $matches ;
2005-09-26 12:28:28 +02:00
$sql [] = $alter . str_replace ( 'DEFAULT ' . $default , '' , $v );
$sql [] = 'UPDATE ' . $tabname . ' SET ' . $colname . '=' . $default ;
$sql [] = 'ALTER TABLE ' . $tabname . ' ALTER COLUMN ' . $colname . ' SET DEFAULT ' . $default ;
2004-09-19 10:26:45 +02:00
} else {
$sql [] = $alter . $v ;
}
2004-10-23 17:00:44 +02:00
if ( $not_null ) {
list ( $colname ) = explode ( ' ' , $v );
$sql [] = 'ALTER TABLE ' . $tabname . ' ALTER COLUMN ' . $colname . ' SET NOT NULL' ;
}
2004-09-19 10:26:45 +02:00
}
return $sql ;
}
2004-08-12 01:22:25 +02:00
/**
* Change the definition of one column
*
* Postgres can 't do that on it' s own , you need to supply the complete defintion of the new table ,
* to allow , recreating the table and copying the content over to the new table
* @ param string $tabname table - name
* @ param string $flds column - name and type for the changed column
* @ param string $tableflds complete defintion of the new table , eg . for postgres , default ''
* @ param array / $tableoptions options for the new table see CreateTableSQL , default ''
* @ return array with SQL strings
*/
function AlterColumnSQL ( $tabname , $flds , $tableflds = '' , $tableoptions = '' )
{
if ( ! $tableflds ) {
if ( $this -> debug ) ADOConnection :: outp ( " AlterColumnSQL needs a complete table-definiton for PostgreSQL " );
return array ();
}
return $this -> _recreate_copy_table ( $tabname , False , $tableflds , $tableoptions );
}
2003-10-19 21:05:23 +02:00
2004-08-12 01:22:25 +02:00
/**
* Drop one column
*
* Postgres < 7.3 can 't do that on it' s own , you need to supply the complete defintion of the new table ,
* to allow , recreating the table and copying the content over to the new table
* @ param string $tabname table - name
* @ param string $flds column - name and type for the changed column
* @ param string $tableflds complete defintion of the new table , eg . for postgres , default ''
* @ param array / $tableoptions options for the new table see CreateTableSQL , default ''
* @ return array with SQL strings
*/
function DropColumnSQL ( $tabname , $flds , $tableflds = '' , $tableoptions = '' )
2003-10-19 21:05:23 +02:00
{
2005-09-26 12:12:10 +02:00
$has_drop_column = 7.3 <= ( float ) @ $this -> serverInfo [ 'version' ];
2004-08-12 01:22:25 +02:00
if ( ! $has_drop_column && ! $tableflds ) {
if ( $this -> debug ) ADOConnection :: outp ( " DropColumnSQL needs complete table-definiton for PostgreSQL < 7.3 " );
2005-09-26 12:12:10 +02:00
return array ();
}
2004-08-12 01:22:25 +02:00
if ( $has_drop_column ) {
return ADODB_DataDict :: DropColumnSQL ( $tabname , $flds );
}
return $this -> _recreate_copy_table ( $tabname , $flds , $tableflds , $tableoptions );
2003-10-19 21:05:23 +02:00
}
2004-08-12 01:22:25 +02:00
/**
* Save the content into a temp . table , drop and recreate the original table and copy the content back in
*
* We also take care to set the values of the sequenz and recreate the indexes .
* All this is done in a transaction , to not loose the content of the table , if something went wrong !
* @ internal
* @ param string $tabname table - name
* @ param string $dropflds column - names to drop
2004-10-23 17:00:44 +02:00
* @ param string $tableflds complete defintion of the new table , eg . for postgres
* @ param array / string $tableoptions options for the new table see CreateTableSQL , default ''
2004-08-12 01:22:25 +02:00
* @ return array with SQL strings
*/
function _recreate_copy_table ( $tabname , $dropflds , $tableflds , $tableoptions = '' )
{
if ( $dropflds && ! is_array ( $dropflds )) $dropflds = explode ( ',' , $dropflds );
$copyflds = array ();
2007-05-12 18:58:47 +02:00
foreach (( $meta = $this -> MetaColumns ( $tabname )) as $fld ) {
2004-08-12 01:22:25 +02:00
if ( ! $dropflds || ! in_array ( $fld -> name , $dropflds )) {
2004-10-23 17:00:44 +02:00
// we need to explicit convert varchar to a number to be able to do an AlterColumn of a char column to a nummeric one
if ( preg_match ( '/' . $fld -> name . ' (I|I2|I4|I8|N|F)/i' , $tableflds , $matches ) &&
in_array ( $fld -> type , array ( 'varchar' , 'char' , 'text' , 'bytea' ))) {
2005-09-26 12:28:28 +02:00
$copyflds [] = " to_number( $fld->name ,'S9999999999999D99') " ;
2007-05-12 18:58:47 +02:00
} elseif ( preg_match ( '/' . $fld -> name . ' ([\w]+)/i' , $tableflds , $matches ) &&
strtoupper ( $fld -> type ) != ( $type = $this -> ActualType ( $matches [ 1 ]))) {
$copyflds [] = " CAST( $fld->name AS $type ) " ;
2004-10-23 17:00:44 +02:00
} else {
$copyflds [] = $fld -> name ;
}
2004-08-13 20:59:00 +02:00
// identify the sequence name and the fld its on
2004-08-12 01:22:25 +02:00
if ( $fld -> primary_key && $fld -> has_default &&
2007-05-12 18:58:47 +02:00
preg_match ( " /nextval \ ('([^']+)'::(text|regclass) \ )/ " , $fld -> default_value , $matches )) {
2004-08-12 01:22:25 +02:00
$seq_name = $matches [ 1 ];
$seq_fld = $fld -> name ;
}
}
}
2005-09-26 12:12:10 +02:00
$copyflds = implode ( ', ' , $copyflds );
2004-08-12 01:22:25 +02:00
$tempname = $tabname . '_tmp' ;
$aSql [] = 'BEGIN' ; // we use a transaction, to make sure not to loose the content of the table
$aSql [] = " SELECT * INTO TEMPORARY TABLE $tempname FROM $tabname " ;
$aSql = array_merge ( $aSql , $this -> DropTableSQL ( $tabname ));
$aSql = array_merge ( $aSql , $this -> CreateTableSQL ( $tabname , $tableflds , $tableoptions ));
$aSql [] = " INSERT INTO $tabname SELECT $copyflds FROM $tempname " ;
if ( $seq_name && $seq_fld ) { // if we have a sequence we need to set it again
2004-10-23 17:00:44 +02:00
$seq_name = $tabname . '_' . $seq_fld . '_seq' ; // has to be the name of the new implicit sequence
2004-08-12 01:22:25 +02:00
$aSql [] = " SELECT setval(' $seq_name ',MAX( $seq_fld )) FROM $tabname " ;
}
$aSql [] = " DROP TABLE $tempname " ;
// recreate the indexes, if they not contain one of the droped columns
foreach ( $this -> MetaIndexes ( $tabname ) as $idx_name => $idx_data )
{
if ( substr ( $idx_name , - 5 ) != '_pkey' && ( ! $dropflds || ! count ( array_intersect ( $dropflds , $idx_data [ 'columns' ])))) {
$aSql = array_merge ( $aSql , $this -> CreateIndexSQL ( $idx_name , $tabname , $idx_data [ 'columns' ],
$idx_data [ 'unique' ] ? array ( 'UNIQUE' ) : False ));
}
}
$aSql [] = 'COMMIT' ;
return $aSql ;
}
2003-10-19 21:05:23 +02:00
2004-08-12 01:22:25 +02:00
function DropTableSQL ( $tabname )
2003-10-19 21:05:23 +02:00
{
2004-08-12 01:22:25 +02:00
$sql = ADODB_DataDict :: DropTableSQL ( $tabname );
$drop_seq = $this -> _DropAutoIncrement ( $tabname );
if ( $drop_seq ) $sql [] = $drop_seq ;
return $sql ;
2003-10-19 21:05:23 +02:00
}
2005-09-26 12:12:10 +02:00
2003-10-19 21:05:23 +02:00
// return string must begin with space
function _CreateSuffix ( $fname , & $ftype , $fnotnull , $fdefault , $fautoinc , $fconstraint )
{
if ( $fautoinc ) {
$ftype = 'SERIAL' ;
return '' ;
}
$suffix = '' ;
if ( strlen ( $fdefault )) $suffix .= " DEFAULT $fdefault " ;
2004-03-15 23:17:52 +01:00
if ( $fnotnull ) $suffix .= ' NOT NULL' ;
2003-10-19 21:05:23 +02:00
if ( $fconstraint ) $suffix .= ' ' . $fconstraint ;
return $suffix ;
}
2004-08-12 01:22:25 +02:00
// search for a sequece for the given table (asumes the seqence-name contains the table-name!)
// if yes return sql to drop it
// this is still necessary if postgres < 7.3 or the SERIAL was created on an earlier version!!!
function _DropAutoIncrement ( $tabname )
2003-10-19 21:05:23 +02:00
{
2004-08-12 01:22:25 +02:00
$tabname = $this -> connection -> quote ( '%' . $tabname . '%' );
2004-08-15 21:52:49 +02:00
$seq = $this -> connection -> GetOne ( " SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relname LIKE $tabname AND relkind='S' " );
// check if a tables depends on the sequenz and it therefor cant and dont need to be droped separatly
if ( ! $seq || $this -> connection -> GetOne ( " SELECT relname FROM pg_class JOIN pg_depend ON pg_class.relfilenode=pg_depend.objid WHERE relname=' $seq ' AND relkind='S' AND deptype='i' " )) {
return False ;
}
return " DROP SEQUENCE " . $seq ;
2003-10-19 21:05:23 +02:00
}
/*
CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
{ column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ]
| table_constraint } [, ... ]
)
[ INHERITS ( parent_table [, ... ] ) ]
[ WITH OIDS | WITHOUT OIDS ]
where column_constraint is :
[ CONSTRAINT constraint_name ]
{ NOT NULL | NULL | UNIQUE | PRIMARY KEY |
CHECK ( expression ) |
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ]
[ ON DELETE action ] [ ON UPDATE action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
and table_constraint is :
[ CONSTRAINT constraint_name ]
{ UNIQUE ( column_name [, ... ] ) |
PRIMARY KEY ( column_name [, ... ] ) |
CHECK ( expression ) |
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
*/
/*
CREATE [ UNIQUE ] INDEX index_name ON table
[ USING acc_method ] ( column [ ops_name ] [, ... ] )
[ WHERE predicate ]
CREATE [ UNIQUE ] INDEX index_name ON table
[ USING acc_method ] ( func_name ( column [, ... ]) [ ops_name ] )
[ WHERE predicate ]
*/
function _IndexSQL ( $idxname , $tabname , $flds , $idxoptions )
{
2004-03-15 23:17:52 +01:00
$sql = array ();
2003-10-19 21:05:23 +02:00
2004-03-15 23:17:52 +01:00
if ( isset ( $idxoptions [ 'REPLACE' ]) || isset ( $idxoptions [ 'DROP' ]) ) {
$sql [] = sprintf ( $this -> dropIndex , $idxname , $tabname );
if ( isset ( $idxoptions [ 'DROP' ]) )
return $sql ;
}
if ( empty ( $flds ) ) {
return $sql ;
}
$unique = isset ( $idxoptions [ 'UNIQUE' ]) ? ' UNIQUE' : '' ;
$s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' ' ;
if ( isset ( $idxoptions [ 'HASH' ]))
$s .= 'USING HASH ' ;
if ( isset ( $idxoptions [ $this -> upperName ]) )
$s .= $idxoptions [ $this -> upperName ];
if ( is_array ( $flds ) )
$flds = implode ( ', ' , $flds );
$s .= '(' . $flds . ')' ;
2003-10-19 21:05:23 +02:00
$sql [] = $s ;
return $sql ;
}
}
2005-09-26 12:12:10 +02:00
?>