code:
<?php
// +----------------------------------------------------------------------+
// | PHP versions 4 and 5                                                 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
// | Stig. S. Bakken, Lukas Smith                                         |
// | All rights reserved.                                                 |
// +----------------------------------------------------------------------+
// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
// | API as well as database abstraction for PHP applications.            |
// | This LICENSE is in the BSD license style.                            |
// |                                                                      |
// | Redistribution and use in source and binary forms, with or without   |
// | modification, are permitted provided that the following conditions   |
// | are met:                                                             |
// |                                                                      |
// | Redistributions of source code must retain the above copyright       |
// | notice, this list of conditions and the following disclaimer.        |
// |                                                                      |
// | Redistributions in binary form must reproduce the above copyright    |
// | notice, this list of conditions and the following disclaimer in the  |
// | documentation and/or other materials provided with the distribution. |
// |                                                                      |
// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
// | Lukas Smith nor the names of his contributors may be used to endorse |
// | or promote products derived from this software without specific prior|
// | written permission.                                                  |
// |                                                                      |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
// | POSSIBILITY OF SUCH DAMAGE.                                          |
// +----------------------------------------------------------------------+
// | Author: Lukas Smith <smith@pooteeweet.org>                           |
// +----------------------------------------------------------------------+
//
// $Id$
//

require_once 'MDB2/Driver/Reverse/Common.php';

/**
 * MDB2 MySQLi driver for the schema reverse engineering module
 *
 * @package MDB2
 * @category Database
 * @author  Lukas Smith <smith@pooteeweet.org>
 * @author  Lorenzo Alberton <l.alberton@quipo.it>
 */
class MDB2_Driver_Reverse_mysqli extends MDB2_Driver_Reverse_Common
{
    /**
     * Array for converting MYSQLI_*_FLAG constants to text values
     * @var    array
     * @access public
     */
    var $flags = array(
        MYSQLI_NOT_NULL_FLAG        => 'not_null',
        MYSQLI_PRI_KEY_FLAG         => 'primary_key',
        MYSQLI_UNIQUE_KEY_FLAG      => 'unique_key',
        MYSQLI_MULTIPLE_KEY_FLAG    => 'multiple_key',
        MYSQLI_BLOB_FLAG            => 'blob',
        MYSQLI_UNSIGNED_FLAG        => 'unsigned',
        MYSQLI_ZEROFILL_FLAG        => 'zerofill',
        MYSQLI_AUTO_INCREMENT_FLAG  => 'auto_increment',
        MYSQLI_TIMESTAMP_FLAG       => 'timestamp',
        MYSQLI_SET_FLAG             => 'set',
        // MYSQLI_NUM_FLAG             => 'numeric',  // unnecessary
        // MYSQLI_PART_KEY_FLAG        => 'multiple_key',  // duplicatvie
        MYSQLI_GROUP_FLAG           => 'group_by'
    );

    /**
     * Array for converting MYSQLI_TYPE_* constants to text values
     * @var    array
     * @access public
     */
    var $types = array(
        MYSQLI_TYPE_DECIMAL     => 'decimal',
        246                     => 'decimal',
        MYSQLI_TYPE_TINY        => 'tinyint',
        MYSQLI_TYPE_SHORT       => 'int',
        MYSQLI_TYPE_LONG        => 'int',
        MYSQLI_TYPE_FLOAT       => 'float',
        MYSQLI_TYPE_DOUBLE      => 'double',
        // MYSQLI_TYPE_NULL        => 'DEFAULT NULL',  // let flags handle it
        MYSQLI_TYPE_TIMESTAMP   => 'timestamp',
        MYSQLI_TYPE_LONGLONG    => 'bigint',
        MYSQLI_TYPE_INT24       => 'mediumint',
        MYSQLI_TYPE_DATE        => 'date',
        MYSQLI_TYPE_TIME        => 'time',
        MYSQLI_TYPE_DATETIME    => 'datetime',
        MYSQLI_TYPE_YEAR        => 'year',
        MYSQLI_TYPE_NEWDATE     => 'date',
        MYSQLI_TYPE_ENUM        => 'enum',
        MYSQLI_TYPE_SET         => 'set',
        MYSQLI_TYPE_TINY_BLOB   => 'tinyblob',
        MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
        MYSQLI_TYPE_LONG_BLOB   => 'longblob',
        MYSQLI_TYPE_BLOB        => 'blob',
        MYSQLI_TYPE_VAR_STRING  => 'varchar',
        MYSQLI_TYPE_STRING      => 'char',
        MYSQLI_TYPE_GEOMETRY    => 'geometry',
    );

    // {{{ getTableFieldDefinition()

    /**
     * Get the structure of a field into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $field_name name of field that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableFieldDefinition($table_name, $field_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $result = $db->loadModule('Datatype', null, true);
        if (MDB2::isError($result)) {
            return $result;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $table = $db->quoteIdentifier($table, true);
        $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name);
        $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($columns)) {
            return $columns;
        }
        foreach ($columns as $column) {
            $column = array_change_key_case($column, CASE_LOWER);
            $column['name'] = $column['field'];
            unset($column['field']);
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $column['name'] = strtolower($column['name']);
                } else {
                    $column['name'] = strtoupper($column['name']);
                }
            } else {
                $column = array_change_key_case($column, $db->options['field_case']);
            }
            if ($field_name == $column['name']) {
                $mapped_datatype = $db->datatype->mapNativeDatatype($column);
                if (MDB2::isError($mapped_datatype)) {
                    return $mapped_datatype;
                }
                list($types, $length, $unsigned, $fixed) = $mapped_datatype;
                $notnull = false;
                if (empty($column['null']) || $column['null'] !== 'YES') {
                    $notnull = true;
                }
                $default = false;
                if (array_key_exists('default', $column)) {
                    $default = $column['default'];
                    if ((null === $default) && $notnull) {
                        $default = '';
                    }
                }
                $definition[0] = array(
                    'notnull' => $notnull,
                    'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type'])
                );
                $autoincrement = false;
                if (!empty($column['extra'])) {
                    if ($column['extra'] == 'auto_increment') {
                        $autoincrement = true;
                    } else {
                        $definition[0]['extra'] = $column['extra'];
                    }
                }
                $collate = null;
                if (!empty($column['collation'])) {
                    $collate = $column['collation'];
                    $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate);
                }

                if (null !== $length) {
                    $definition[0]['length'] = $length;
                }
                if (null !== $unsigned) {
                    $definition[0]['unsigned'] = $unsigned;
                }
                if (null !== $fixed) {
                    $definition[0]['fixed'] = $fixed;
                }
                if ($default !== false) {
                    $definition[0]['default'] = $default;
                }
                if ($autoincrement !== false) {
                    $definition[0]['autoincrement'] = $autoincrement;
                }
                if (null !== $collate) {
                    $definition[0]['collate'] = $collate;
                    $definition[0]['charset'] = $charset;
                }
                foreach ($types as $key => $type) {
                    $definition[$key] = $definition[0];
                    if ($type == 'clob' || $type == 'blob') {
                        unset($definition[$key]['default']);
                    } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) {
                        $definition[$key]['default'] = '0000-00-00 00:00:00';
                    }
                    $definition[$key]['type'] = $type;
                    $definition[$key]['mdb2type'] = $type;
                }
                return $definition;
            }
        }

        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
            'it was not specified an existing table column', __FUNCTION__);
    }

    // }}}
    // {{{ getTableIndexDefinition()

    /**
     * Get the structure of an index into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $index_name name of index that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableIndexDefinition($table_name, $index_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $table = $db->quoteIdentifier($table, true);
        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
        $index_name_mdb2 = $db->getIndexName($index_name);
        $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2)));
        if (!MDB2::isError($result) && (null !== $result)) {
            // apply 'idxname_format' only if the query succeeded, otherwise
            // fallback to the given $index_name, without transformation
            $index_name = $index_name_mdb2;
        }
        $result = $db->query(sprintf($query, $db->quote($index_name)));
        if (MDB2::isError($result)) {
            return $result;
        }
        $colpos = 1;
        $definition = array();
        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
            $row = array_change_key_case($row, CASE_LOWER);
            $key_name = $row['key_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $key_name = strtolower($key_name);
                } else {
                    $key_name = strtoupper($key_name);
                }
            }
            if ($index_name == $key_name) {
                if (!$row['non_unique']) {
                    return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                        $index_name . ' is not an existing table index', __FUNCTION__);
                }
                $column_name = $row['column_name'];
                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                    if ($db->options['field_case'] == CASE_LOWER) {
                        $column_name = strtolower($column_name);
                    } else {
                        $column_name = strtoupper($column_name);
                    }
                }
                $definition['fields'][$column_name] = array(
                    'position' => $colpos++
                );
                if (!empty($row['collation'])) {
                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
                        ? 'ascending' : 'descending');
                }
            }
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $index_name . ' is not an existing table index', __FUNCTION__);
        }
        return $definition;
    }

    // }}}
    // {{{ getTableConstraintDefinition()

    /**
     * Get the structure of a constraint into an array
     *
     * @param string $table_name      name of table that should be used in method
     * @param string $constraint_name name of constraint that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableConstraintDefinition($table_name, $constraint_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);
        $constraint_name_original = $constraint_name;

        $table = $db->quoteIdentifier($table, true);
        $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */";
        if (strtolower($constraint_name) != 'primary') {
            $constraint_name_mdb2 = $db->getIndexName($constraint_name);
            $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2)));
            if (!MDB2::isError($result) && (null !== $result)) {
                // apply 'idxname_format' only if the query succeeded, otherwise
                // fallback to the given $index_name, without transformation
                $constraint_name = $constraint_name_mdb2;
            }
        }
        $result = $db->query(sprintf($query, $db->quote($constraint_name)));
        if (MDB2::isError($result)) {
            return $result;
        }
        $colpos = 1;
        //default values, eventually overridden
        $definition = array(
            'primary' => false,
            'unique'  => false,
            'foreign' => false,
            'check'   => false,
            'fields'  => array(),
            'references' => array(
                'table'  => '',
                'fields' => array(),
            ),
            'onupdate'  => '',
            'ondelete'  => '',
            'match'     => '',
            'deferrable'        => false,
            'initiallydeferred' => false,
        );
        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
            $row = array_change_key_case($row, CASE_LOWER);
            $key_name = $row['key_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $key_name = strtolower($key_name);
                } else {
                    $key_name = strtoupper($key_name);
                }
            }
            if ($constraint_name == $key_name) {
                if ($row['non_unique']) {
                    //FOREIGN KEY?
                    return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
                }
                if ($row['key_name'] == 'PRIMARY') {
                    $definition['primary'] = true;
                } elseif (!$row['non_unique']) {
                    $definition['unique'] = true;
                }
                $column_name = $row['column_name'];
                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                    if ($db->options['field_case'] == CASE_LOWER) {
                        $column_name = strtolower($column_name);
                    } else {
                        $column_name = strtoupper($column_name);
                    }
                }
                $definition['fields'][$column_name] = array(
                    'position' => $colpos++
                );
                if (!empty($row['collation'])) {
                    $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A'
                        ? 'ascending' : 'descending');
                }
            }
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition);
        }
        return $definition;
    }

    // }}}
    // {{{ _getTableFKConstraintDefinition()

    /**
     * Get the FK definition from the CREATE TABLE statement
     *
     * @param string $table           table name
     * @param string $constraint_name constraint name
     * @param array  $definition      default values for constraint definition
     *
     * @return array|PEAR_Error
     * @access private
     */
    function _getTableFKConstraintDefinition($table, $constraint_name, $definition)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }
        //Use INFORMATION_SCHEMA instead?
        //SELECT *
        //  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
        // WHERE CONSTRAINT_SCHEMA = '$dbname'
        //   AND TABLE_NAME = '$table'
        //   AND CONSTRAINT_NAME = '$constraint_name';
        $query = 'SHOW CREATE TABLE '. $db->escape($table);
        $constraint = $db->queryOne($query, 'text', 1);
        if (!MDB2::isError($constraint) && !empty($constraint)) {
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $constraint = strtolower($constraint);
                } else {
                    $constraint = strtoupper($constraint);
                }
            }
            $constraint_name_original = $constraint_name;
            $constraint_name = $db->getIndexName($constraint_name);
            $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
            if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
                //fallback to original constraint name
                $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i';
            }
            if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) {
                $definition['foreign'] = true;
                $column_names = explode(',', $matches[1]);
                $referenced_cols = explode(',', $matches[3]);
                $definition['references'] = array(
                    'table'  => $matches[2],
                    'fields' => array(),
                );
                $colpos = 1;
                foreach ($column_names as $column_name) {
                    $definition['fields'][trim($column_name)] = array(
                        'position' => $colpos++
                    );
                }
                $colpos = 1;
                foreach ($referenced_cols as $column_name) {
                    $definition['references']['fields'][trim($column_name)] = array(
                        'position' => $colpos++
                    );
                }
                $definition['ondelete'] = empty($matches[4]) ? 'RESTRICT' : strtoupper($matches[4]);
                $definition['onupdate'] = empty($matches[5]) ? 'RESTRICT' : strtoupper($matches[5]);
                $definition['match']    = 'SIMPLE';
                return $definition;
            }
        }
        return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $constraint_name . ' is not an existing table constraint', __FUNCTION__);
    }

    // }}}
    // {{{ getTriggerDefinition()

    /**
     * Get the structure of a trigger into an array
     *
     * EXPERIMENTAL
     *
     * WARNING: this function is experimental and may change the returned value
     * at any time until labelled as non-experimental
     *
     * @param string    $trigger    name of trigger that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTriggerDefinition($trigger)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $query = 'SELECT trigger_name,
                         event_object_table AS table_name,
                         action_statement AS trigger_body,
                         action_timing AS trigger_type,
                         event_manipulation AS trigger_event
                    FROM information_schema.triggers
                   WHERE trigger_name = '. $db->quote($trigger, 'text');
        $types = array(
            'trigger_name'    => 'text',
            'table_name'      => 'text',
            'trigger_body'    => 'text',
            'trigger_type'    => 'text',
            'trigger_event'   => 'text',
        );
        $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($def)) {
            return $def;
        }
        $def['trigger_comment'] = '';
        $def['trigger_enabled'] = true;
        return $def;
    }

    // }}}
    // {{{ tableInfo()

    /**
     * Returns information about a table or a result set
     *
     * @param object|string  $result  MDB2_result object from a query or a
     *                                 string containing the name of a table.
     *                                 While this also accepts a query result
     *                                 resource identifier, this behavior is
     *                                 deprecated.
     * @param int            $mode    a valid tableInfo mode
     *
     * @return array  an associative array with the information requested.
     *                 A MDB2_Error object on failure.
     *
     * @see MDB2_Driver_Common::setOption()
     */
    function tableInfo($result, $mode = null)
    {
        if (is_string($result)) {
           return parent::tableInfo($result, $mode);
        }

        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
        if (!is_object($resource)) {
            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
                'Could not generate result resource', __FUNCTION__);
        }

        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $case_func = 'strtolower';
            } else {
                $case_func = 'strtoupper';
            }
        } else {
            $case_func = 'strval';
        }

        $count = @mysqli_num_fields($resource);
        $res = array();
        if ($mode) {
            $res['num_fields'] = $count;
        }

        $db->loadModule('Datatype', null, true);
        for ($i = 0; $i < $count; $i++) {
            $tmp = @mysqli_fetch_field($resource);

            $flags = '';
            foreach ($this->flags as $const => $means) {
                if ($tmp->flags & $const) {
                    $flags.= $means . ' ';
                }
            }
            if ($tmp->def) {
                $flags.= 'default_' . rawurlencode($tmp->def);
            }
            $flags = trim($flags);

            $res[$i] = array(
                'table'  => $case_func($tmp->table),
                'name'   => $case_func($tmp->name),
                'type'   => isset($this->types[$tmp->type])
                    ? $this->types[$tmp->type] : 'unknown',
                // http://bugs.php.net/?id=36579
                'length' => $tmp->length,
                'flags'  => $flags,
            );
            $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
            if (MDB2::isError($mdb2type_info)) {
               return $mdb2type_info;
            }
            $res[$i]['mdb2type'] = $mdb2type_info[0][0];
            if ($mode & MDB2_TABLEINFO_ORDER) {
                $res['order'][$res[$i]['name']] = $i;
            }
            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
            }
        }

        return $res;
    }
}
?>


code:
<?php
// +----------------------------------------------------------------------+
// | PHP versions 4 and 5                                                 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
// | Stig. S. Bakken, Lukas Smith, Frank M. Kromann, Lorenzo Alberton     |
// | All rights reserved.                                                 |
// +----------------------------------------------------------------------+
// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
// | API as well as database abstraction for PHP applications.            |
// | This LICENSE is in the BSD license style.                            |
// |                                                                      |
// | Redistribution and use in source and binary forms, with or without   |
// | modification, are permitted provided that the following conditions   |
// | are met:                                                             |
// |                                                                      |
// | Redistributions of source code must retain the above copyright       |
// | notice, this list of conditions and the following disclaimer.        |
// |                                                                      |
// | Redistributions in binary form must reproduce the above copyright    |
// | notice, this list of conditions and the following disclaimer in the  |
// | documentation and/or other materials provided with the distribution. |
// |                                                                      |
// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
// | Lukas Smith nor the names of his contributors may be used to endorse |
// | or promote products derived from this software without specific prior|
// | written permission.                                                  |
// |                                                                      |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
// | POSSIBILITY OF SUCH DAMAGE.                                          |
// +----------------------------------------------------------------------+
// | Authors: Lukas Smith <smith@pooteeweet.org>                          |
// |          Lorenzo Alberton <l.alberton@quipo.it>                      |
// +----------------------------------------------------------------------+
//
// $Id$
//

require_once 'MDB2/Driver/Reverse/Common.php';

/**
 * MDB2 Oracle driver for the schema reverse engineering module
 *
 * @package MDB2
 * @category Database
 * @author  Lukas Smith <smith@dybnet.de>
 */
class MDB2_Driver_Reverse_oci8 extends MDB2_Driver_Reverse_Common
{
    // {{{ getTableFieldDefinition()

    /**
     * Get the structure of a field into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $field_name name of field that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableFieldDefinition($table_name, $field_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $result = $db->loadModule('Datatype', null, true);
        if (MDB2::isError($result)) {
            return $result;
        }

        list($owner, $table) = $this->splitTableSchema($table_name);
        if (empty($owner)) {
            $owner = $db->dsn['username'];
        }

        $query = 'SELECT column_name name,
                         data_type "type",
                         nullable,
                         data_default "default",
                         COALESCE(data_precision, data_length) "length",
                         data_scale "scale"
                    FROM all_tab_columns
                   WHERE (table_name=? OR table_name=?)
                     AND (owner=? OR owner=?)
                     AND (column_name=? OR column_name=?)
                ORDER BY column_id';
        $stmt = $db->prepare($query);
        if (MDB2::isError($stmt)) {
            return $stmt;
        }
        $args = array(
            $table,
            strtoupper($table),
            $owner,
            strtoupper($owner),
            $field_name,
            strtoupper($field_name)
        );
        $result = $stmt->execute($args);
        if (MDB2::isError($result)) {
            return $result;
        }
        $column = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($column)) {
            return $column;
        }
        $stmt->free();
        $result->free();

        if (empty($column)) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $field_name . ' is not a column in table ' . $table_name, __FUNCTION__);
        }

        $column = array_change_key_case($column, CASE_LOWER);
        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $column['name'] = strtolower($column['name']);
            } else {
                $column['name'] = strtoupper($column['name']);
            }
        }
        $mapped_datatype = $db->datatype->mapNativeDatatype($column);
        if (MDB2::isError($mapped_datatype)) {
            return $mapped_datatype;
        }
        list($types, $length, $unsigned, $fixed) = $mapped_datatype;
        $notnull = false;
        if (!empty($column['nullable']) && $column['nullable'] == 'N') {
            $notnull = true;
        }
        $default = false;
        if (array_key_exists('default', $column)) {
            $default = $column['default'];
            if ($default === 'NULL') {
                $default = null;
            }
            if ((null === $default) && $notnull) {
                $default = '';
            }
        }

        $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']);
        if (null !== $length) {
            $definition[0]['length'] = $length;
        }
        if (null !== $unsigned) {
            $definition[0]['unsigned'] = $unsigned;
        }
        if (null !== $fixed) {
            $definition[0]['fixed'] = $fixed;
        }
        if ($default !== false) {
            $definition[0]['default'] = $default;
        }
        foreach ($types as $key => $type) {
            $definition[$key] = $definition[0];
            if ($type == 'clob' || $type == 'blob') {
                unset($definition[$key]['default']);
            }
            $definition[$key]['type'] = $type;
            $definition[$key]['mdb2type'] = $type;
        }
        if ($type == 'integer') {
            $query= "SELECT trigger_body
                       FROM all_triggers
                      WHERE table_name=?
                        AND triggering_event='INSERT'
                        AND trigger_type='BEFORE EACH ROW'";
			// ^^ pretty reasonable mimic for "auto_increment" in oracle?
			$stmt = $db->prepare($query);
            if (MDB2::isError($stmt)) {
                return $stmt;
            }
			$result = $stmt->execute(strtoupper($table));
	        if (MDB2::isError($result)) {
	            return $result;
	        }
	        while ($triggerstr = $result->fetchOne()) {
	           	if (preg_match('/.*SELECT\W+(.+)\.nextval +into +\:NEW\.'.$field_name.' +FROM +dual/im', $triggerstr, $matches)) {
					$definition[0]['autoincrement'] = $matches[1];
                }
            }
	        $stmt->free();
	        $result->free();
        }
        return $definition;
    }

    // }}}
    // {{{ getTableIndexDefinition()

    /**
     * Get the structure of an index into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $index_name name of index that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableIndexDefinition($table_name, $index_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($owner, $table) = $this->splitTableSchema($table_name);
        if (empty($owner)) {
            $owner = $db->dsn['username'];
        }

        $query = "SELECT aic.column_name,
                         aic.column_position,
                         aic.descend,
                         aic.table_owner,
                         alc.constraint_type
                    FROM all_ind_columns aic
               LEFT JOIN all_constraints alc
                      ON aic.index_name = alc.constraint_name
                     AND aic.table_name = alc.table_name
                     AND aic.table_owner = alc.owner
                   WHERE (aic.table_name=? OR aic.table_name=?)
                     AND (aic.index_name=? OR aic.index_name=?)
                     AND (aic.table_owner=? OR aic.table_owner=?)
                ORDER BY column_position";
        $stmt = $db->prepare($query);
        if (MDB2::isError($stmt)) {
            return $stmt;
        }
        $indexnames = array_unique(array($db->getIndexName($index_name), $index_name));
        $i = 0;
        $row = null;
        while ((null === $row) && array_key_exists($i, $indexnames)) {
            $args = array(
                $table,
                strtoupper($table),
                $indexnames[$i],
                strtoupper($indexnames[$i]),
                $owner,
                strtoupper($owner)
            );
        	$result = $stmt->execute($args);
        	if (MDB2::isError($result)) {
                return $result;
            }
        	$row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
        	if (MDB2::isError($row)) {
                return $row;
            }
        	$i++;
        }
        if (null === $row) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $index_name. ' is not an index on table '. $table_name, __FUNCTION__);
        }
        if ($row['constraint_type'] == 'U' || $row['constraint_type'] == 'P') {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $index_name. ' is a constraint, not an index on table '. $table_name, __FUNCTION__);
        }

        $definition = array();
        while (null !== $row) {
            $row = array_change_key_case($row, CASE_LOWER);
            $column_name = $row['column_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $column_name = strtolower($column_name);
                } else {
                    $column_name = strtoupper($column_name);
                }
            }
            $definition['fields'][$column_name] = array(
                'position' => (int)$row['column_position'],
            );
            if (!empty($row['descend'])) {
                $definition['fields'][$column_name]['sorting'] =
                    ($row['descend'] == 'ASC' ? 'ascending' : 'descending');
            }
            $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $index_name. ' is not an index on table '. $table_name, __FUNCTION__);
        }
        return $definition;
    }

    // }}}
    // {{{ getTableConstraintDefinition()

    /**
     * Get the structure of a constraint into an array
     *
     * @param string $table_name      name of table that should be used in method
     * @param string $constraint_name name of constraint that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableConstraintDefinition($table_name, $constraint_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($owner, $table) = $this->splitTableSchema($table_name);
        if (empty($owner)) {
            $owner = $db->dsn['username'];
        }

        $query = 'SELECT alc.constraint_name,
                         CASE alc.constraint_type WHEN \'P\' THEN 1 ELSE 0 END "primary",
                         CASE alc.constraint_type WHEN \'R\' THEN 1 ELSE 0 END "foreign",
                         CASE alc.constraint_type WHEN \'U\' THEN 1 ELSE 0 END "unique",
                         CASE alc.constraint_type WHEN \'C\' THEN 1 ELSE 0 END "check",
                         alc.DELETE_RULE "ondelete",
                         \'NO ACTION\' "onupdate",
                         \'SIMPLE\' "match",
                         CASE alc.deferrable WHEN \'NOT DEFERRABLE\' THEN 0 ELSE 1 END "deferrable",
                         CASE alc.deferred WHEN \'IMMEDIATE\' THEN 0 ELSE 1 END "initiallydeferred",
                         alc.search_condition,
                         alc.table_name,
                         cols.column_name,
                         cols.position,
                         r_alc.table_name "references_table",
                         r_cols.column_name "references_field",
                         r_cols.position "references_field_position"
                    FROM all_cons_columns cols
               LEFT JOIN all_constraints alc
                      ON alc.constraint_name = cols.constraint_name
                     AND alc.owner = cols.owner
               LEFT JOIN all_constraints r_alc
                      ON alc.r_constraint_name = r_alc.constraint_name
                     AND alc.r_owner = r_alc.owner
               LEFT JOIN all_cons_columns r_cols
                      ON r_alc.constraint_name = r_cols.constraint_name
                     AND r_alc.owner = r_cols.owner
                     AND cols.position = r_cols.position
                   WHERE (alc.constraint_name=? OR alc.constraint_name=?)
                     AND alc.constraint_name = cols.constraint_name
                     AND (alc.owner=? OR alc.owner=?)';
        $tablenames = array();
        if (!empty($table)) {
            $query.= ' AND (alc.table_name=? OR alc.table_name=?)';
            $tablenames = array($table, strtoupper($table));
        }
        $stmt = $db->prepare($query);
        if (MDB2::isError($stmt)) {
            return $stmt;
        }

        $constraintnames = array_unique(array($db->getIndexName($constraint_name), $constraint_name));
        $c = 0;
        $row = null;
        while ((null === $row) && array_key_exists($c, $constraintnames)) {
            $args = array(
                $constraintnames[$c],
                strtoupper($constraintnames[$c]),
                $owner,
                strtoupper($owner)
            );
            if (!empty($table)) {
                $args = array_merge($args, $tablenames);
            }
            $result = $stmt->execute($args);
            if (MDB2::isError($result)) {
                return $result;
            }
            $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
            if (MDB2::isError($row)) {
                return $row;
            }
            $c++;
        }

        $definition = array(
            'primary' => (boolean)$row['primary'],
            'unique'  => (boolean)$row['unique'],
            'foreign' => (boolean)$row['foreign'],
            'check'   => (boolean)$row['check'],
            'deferrable' => (boolean)$row['deferrable'],
            'initiallydeferred' => (boolean)$row['initiallydeferred'],
            'ondelete' => $row['ondelete'],
            'onupdate' => $row['onupdate'],
            'match'    => $row['match'],
        );

        if ($definition['check']) {
            // pattern match constraint for check constraint values into enum-style output:
			$enumregex = '/'.$row['column_name'].' in \((.+?)\)/i';
			if (preg_match($enumregex, $row['search_condition'], $rangestr)) {
				$definition['fields'][$column_name] = array();
				$allowed = explode(',', $rangestr[1]);
				foreach ($allowed as $val) {
					$val = trim($val);
					$val = preg_replace('/^\'/', '', $val);
					$val = preg_replace('/\'$/', '', $val);
					array_push($definition['fields'][$column_name], $val);
				}
			}
		}

        while (null !== $row) {
            $row = array_change_key_case($row, CASE_LOWER);
            $column_name = $row['column_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $column_name = strtolower($column_name);
                } else {
                    $column_name = strtoupper($column_name);
                }
            }
            $definition['fields'][$column_name] = array(
                'position' => (int)$row['position']
            );
            if ($row['foreign']) {
                $ref_column_name = $row['references_field'];
                $ref_table_name  = $row['references_table'];
                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                    if ($db->options['field_case'] == CASE_LOWER) {
                        $ref_column_name = strtolower($ref_column_name);
                        $ref_table_name  = strtolower($ref_table_name);
                    } else {
                        $ref_column_name = strtoupper($ref_column_name);
                        $ref_table_name  = strtoupper($ref_table_name);
                    }
                }
                $definition['references']['table'] = $ref_table_name;
                $definition['references']['fields'][$ref_column_name] = array(
                    'position' => (int)$row['references_field_position']
                );
            }
            $lastrow = $row;
            $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $constraint_name . ' is not a constraint on table '. $table_name, __FUNCTION__);
        }

        return $definition;
    }

    // }}}
    // {{{ getSequenceDefinition()

    /**
     * Get the structure of a sequence into an array
     *
     * @param string    $sequence   name of sequence that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getSequenceDefinition($sequence)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $sequence_name = $db->getSequenceName($sequence);
        $query = 'SELECT last_number FROM user_sequences';
        $query.= ' WHERE sequence_name='.$db->quote($sequence_name, 'text');
        $query.= '    OR sequence_name='.$db->quote(strtoupper($sequence_name), 'text');
        $start = $db->queryOne($query, 'integer');
        if (MDB2::isError($start)) {
            return $start;
        }
        $definition = array();
        if ($start != 1) {
            $definition = array('start' => $start);
        }
        return $definition;
    }

    // }}}
    // {{{ getTriggerDefinition()

    /**
     * Get the structure of a trigger into an array
     *
     * EXPERIMENTAL
     *
     * WARNING: this function is experimental and may change the returned value
     * at any time until labelled as non-experimental
     *
     * @param string    $trigger    name of trigger that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTriggerDefinition($trigger)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $query = 'SELECT trigger_name,
                         table_name,
                         trigger_body,
                         trigger_type,
                         triggering_event trigger_event,
                         description trigger_comment,
                         1 trigger_enabled,
                         when_clause
                    FROM user_triggers
                   WHERE trigger_name = \''. strtoupper($trigger).'\'';
        $types = array(
            'trigger_name'    => 'text',
            'table_name'      => 'text',
            'trigger_body'    => 'text',
            'trigger_type'    => 'text',
            'trigger_event'   => 'text',
            'trigger_comment' => 'text',
            'trigger_enabled' => 'boolean',
            'when_clause'     => 'text',
        );
        $result = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($result)) {
            return $result;
        }
        if (!empty($result['trigger_type'])) {
            //$result['trigger_type'] = array_shift(explode(' ', $result['trigger_type']));
            $result['trigger_type'] = preg_replace('/(\S+).*/', '\\1', $result['trigger_type']);
        }
        return $result;
    }

    // }}}
    // {{{ tableInfo()

    /**
     * Returns information about a table or a result set
     *
     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
     * is a table name.
     *
     * NOTE: flags won't contain index information.
     *
     * @param object|string  $result  MDB2_result object from a query or a
     *                                 string containing the name of a table.
     *                                 While this also accepts a query result
     *                                 resource identifier, this behavior is
     *                                 deprecated.
     * @param int            $mode    a valid tableInfo mode
     *
     * @return array  an associative array with the information requested.
     *                 A MDB2_Error object on failure.
     *
     * @see MDB2_Driver_Common::tableInfo()
     */
    function tableInfo($result, $mode = null)
    {
        if (is_string($result)) {
           return parent::tableInfo($result, $mode);
        }

        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
        if (!is_resource($resource)) {
            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
                'Could not generate result resource', __FUNCTION__);
        }

        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $case_func = 'strtolower';
            } else {
                $case_func = 'strtoupper';
            }
        } else {
            $case_func = 'strval';
        }

        $count = @OCINumCols($resource);
        $res = array();

        if ($mode) {
            $res['num_fields'] = $count;
        }

        $db->loadModule('Datatype', null, true);
        for ($i = 0; $i < $count; $i++) {
            $column = array(
                'table'  => '',
                'name'   => $case_func(@OCIColumnName($resource, $i+1)),
                'type'   => @OCIColumnType($resource, $i+1),
                'length' => @OCIColumnSize($resource, $i+1),
                'flags'  => '',
            );
            $res[$i] = $column;
            $res[$i]['mdb2type'] = $db->datatype->mapNativeDatatype($res[$i]);
            if ($mode & MDB2_TABLEINFO_ORDER) {
                $res['order'][$res[$i]['name']] = $i;
            }
            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
            }
        }
        return $res;
    }
}
?>

repo_name: ksuudilla/gnu

license: mit


code:
<?php

require_once 'MDB2/Driver/Reverse/Common.php';

/**
 * MDB2 MSSQL driver for the schema reverse engineering module
 *
 * @package MDB2
 * @category Database
 * @author  Lukas Smith <smith@dybnet.de>
 * @author  Lorenzo Alberton <l.alberton@quipo.it>
 */
class MDB2_Driver_Reverse_odbc extends MDB2_Driver_Reverse_Common
{
    // {{{ getTableFieldDefinition()

    /**
     * Get the structure of a field into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $field_name name of field that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableFieldDefinition($table_name, $field_name)
    {
        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $result = $db->loadModule('Datatype', null, true);
        if (MDB2::isError($result)) {
            return $result;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $table = $db->quoteIdentifier($table, true);
        $fldname = $db->quoteIdentifier($field_name, true);

        $res = odbc_columns($db->connection,"%","%",$table);
        $column = array();
        while($data = odbc_fetch_array($res)) {

        	if(strcasecmp($field_name,$data['COLUMN_NAME'])) {
        		$column['table_name'] = $data['TABLE_NAME'];
        		$column['name'] = $data['COLUMN_NAME'];
        		$column['type'] = $data['TYPE_NAME'];
        		if($data['IS_NULLABLE'] == "YES") {
       				$column['is_nullable'] = 1;
        		} else {
        			$column['is_nullable'] = 0;
        		}
        		$column['column_default'] = $data['COLUMN_DEF'];
        		$column['length'] = $data['COLUMNZ_SIZE'];
        		$column['numeric_precision'] = $data['NUM_PREC_RADIX'];
        		$column['numeric_scale'] = $data['DECIMAL_DIGITS'];
        		$column['collation_name'] = NULL;
        	}
        }

        /*
        $query = "SELECT t.table_name,
                         c.column_name 'name',
                         c.data_type 'type',
                         CASE c.is_nullable WHEN 'YES' THEN 1 ELSE 0 END AS 'is_nullable',
                         c.column_default,
                         c.character_maximum_length 'length',
                         c.numeric_precision,
                         c.numeric_scale,
                         c.character_set_name,
                         c.collation_name
                    FROM INFORMATION_SCHEMA.TABLES t,
                         INFORMATION_SCHEMA.COLUMNS c
                   WHERE t.table_name = c.table_name
                     AND t.table_name = '$table'
                     AND c.column_name = '$fldname'";
        if (!empty($schema)) {
            $query .= " AND t.table_schema = '" .$db->quoteIdentifier($schema, true) ."'";
        }
        $query .= ' ORDER BY t.table_name';
        $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($column)) {
            return $column;
        }
        */
        if (empty($column)) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                'it was not specified an existing table column', __FUNCTION__);
        }

        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $column['name'] = strtolower($column['name']);
            } else {
                $column['name'] = strtoupper($column['name']);
            }
        } else {
            $column = array_change_key_case($column, $db->options['field_case']);
        }
        $mapped_datatype = $db->datatype->mapNativeDatatype($column);
        if (MDB2::isError($mapped_datatype)) {
            return $mapped_datatype;
        }
        list($types, $length, $unsigned, $fixed) = $mapped_datatype;
        $notnull = true;
        if ($column['is_nullable']) {
            $notnull = false;
        }
        $default = false;
        if (array_key_exists('column_default', $column)) {
            $default = $column['column_default'];
            if (is_null($default) && $notnull) {
                $default = '';
            } elseif (strlen($default) > 4
                   && substr($default, 0, 1) == '('
                   &&  substr($default, -1, 1) == ')'
            ) {
                //mssql wraps the default value in parentheses: "((1234))", "(NULL)"
                $default = trim($default, '()');
                if ($default == 'NULL') {
                    $default = null;
                }
            }
        }
        $definition[0] = array(
            'notnull' => $notnull,
            'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type'])
        );
        if (!is_null($length)) {
            $definition[0]['length'] = $length;
        }
        if (!is_null($unsigned)) {
            $definition[0]['unsigned'] = $unsigned;
        }
        if (!is_null($fixed)) {
            $definition[0]['fixed'] = $fixed;
        }
        if ($default !== false) {
            $definition[0]['default'] = $default;
        }
        foreach ($types as $key => $type) {
            $definition[$key] = $definition[0];
            if ($type == 'clob' || $type == 'blob') {
                unset($definition[$key]['default']);
            }
            $definition[$key]['type'] = $type;
            $definition[$key]['mdb2type'] = $type;
        }
        return $definition;
    }

    // }}}
    // {{{ getTableIndexDefinition()

    /**
     * Get the structure of an index into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $index_name name of index that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableIndexDefinition($table_name, $index_name)
    {
        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $table = $db->quoteIdentifier($table, true);
        //$idxname = $db->quoteIdentifier($index_name, true);

        $query = "SELECT OBJECT_NAME(i.id) tablename,
                         i.name indexname,
                         c.name field_name,
                         CASE INDEXKEY_PROPERTY(i.id, i.indid, ik.keyno, 'IsDescending')
                           WHEN 1 THEN 'DESC' ELSE 'ASC'
                         END 'collation',
                         ik.keyno 'position'
                    FROM sysindexes i
                    JOIN sysindexkeys ik ON ik.id = i.id AND ik.indid = i.indid
                    JOIN syscolumns c ON c.id = ik.id AND c.colid = ik.colid
                   WHERE OBJECT_NAME(i.id) = '$table'
                     AND i.name = '%s'
                     AND NOT EXISTS (
                            SELECT *
                              FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
                             WHERE k.table_name = OBJECT_NAME(i.id)
                               AND k.constraint_name = i.name";
        if (!empty($schema)) {
            $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'";
        }
        $query .= ')
                ORDER BY tablename, indexname, ik.keyno';

        $index_name_mdb2 = $db->getIndexName($index_name);
        $result = $db->queryRow(sprintf($query, $index_name_mdb2));
        if (!MDB2::isError($result) && !is_null($result)) {
            // apply 'idxname_format' only if the query succeeded, otherwise
            // fallback to the given $index_name, without transformation
            $index_name = $index_name_mdb2;
        }
        $result = $db->query(sprintf($query, $index_name));
        if (MDB2::isError($result)) {
            return $result;
        }

        $definition = array();
        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
            $column_name = $row['field_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $column_name = strtolower($column_name);
                } else {
                    $column_name = strtoupper($column_name);
                }
            }
            $definition['fields'][$column_name] = array(
                'position' => (int)$row['position'],
            );
            if (!empty($row['collation'])) {
                $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC'
                    ? 'ascending' : 'descending');
            }
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                'it was not specified an existing table index', __FUNCTION__);
        }
        return $definition;
    }

    // }}}
    // {{{ getTableConstraintDefinition()

    /**
     * Get the structure of a constraint into an array
     *
     * @param string $table_name      name of table that should be used in method
     * @param string $constraint_name name of constraint that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableConstraintDefinition($table_name, $constraint_name)
    {
        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $table = $db->quoteIdentifier($table, true);
        $query = "SELECT k.table_name,
                         k.column_name field_name,
                         CASE c.constraint_type WHEN 'PRIMARY KEY' THEN 1 ELSE 0 END 'primary',
                         CASE c.constraint_type WHEN 'UNIQUE' THEN 1 ELSE 0 END 'unique',
                         CASE c.constraint_type WHEN 'FOREIGN KEY' THEN 1 ELSE 0 END 'foreign',
                         CASE c.constraint_type WHEN 'CHECK' THEN 1 ELSE 0 END 'check',
                         CASE c.is_deferrable WHEN 'NO' THEN 0 ELSE 1 END 'deferrable',
                         CASE c.initially_deferred WHEN 'NO' THEN 0 ELSE 1 END 'initiallydeferred',
                         rc.match_option 'match',
                		 rc.update_rule 'onupdate',
                         rc.delete_rule 'ondelete',
                         kcu.table_name 'references_table',
                         kcu.column_name 'references_field',
                         k.ordinal_position 'field_position'
                    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
                    LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS c
                      ON k.table_name = c.table_name
                     AND k.table_schema = c.table_schema
                     AND k.table_catalog = c.table_catalog
                     AND k.constraint_catalog = c.constraint_catalog
                     AND k.constraint_name = c.constraint_name
               LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
                      ON rc.constraint_schema = c.constraint_schema
                     AND rc.constraint_catalog = c.constraint_catalog
                     AND rc.constraint_name = c.constraint_name
               LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
                      ON rc.unique_constraint_schema = kcu.constraint_schema
                     AND rc.unique_constraint_catalog = kcu.constraint_catalog
                     AND rc.unique_constraint_name = kcu.constraint_name
					 AND k.ordinal_position = kcu.ordinal_position
                   WHERE k.constraint_catalog = DB_NAME()
                     AND k.table_name = '$table'
                     AND k.constraint_name = '%s'";
        if (!empty($schema)) {
            $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'";
        }
        $query .= ' ORDER BY k.constraint_name,
                             k.ordinal_position';

        $constraint_name_mdb2 = $db->getIndexName($constraint_name);
        $result = $db->queryRow(sprintf($query, $constraint_name_mdb2));
        if (!MDB2::isError($result) && !is_null($result)) {
            // apply 'idxname_format' only if the query succeeded, otherwise
            // fallback to the given $index_name, without transformation
            $constraint_name = $constraint_name_mdb2;
        }
        $result = $db->query(sprintf($query, $constraint_name));
        if (MDB2::isError($result)) {
            return $result;
        }

        $definition = array(
            'fields' => array()
        );
        while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) {
            $row = array_change_key_case($row, CASE_LOWER);
            $column_name = $row['field_name'];
            if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                if ($db->options['field_case'] == CASE_LOWER) {
                    $column_name = strtolower($column_name);
                } else {
                    $column_name = strtoupper($column_name);
                }
            }
            $definition['fields'][$column_name] = array(
                'position' => (int)$row['field_position']
            );
            if ($row['foreign']) {
                $ref_column_name = $row['references_field'];
                if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
                    if ($db->options['field_case'] == CASE_LOWER) {
                        $ref_column_name = strtolower($ref_column_name);
                    } else {
                        $ref_column_name = strtoupper($ref_column_name);
                    }
                }
                $definition['references']['table'] = $row['references_table'];
                $definition['references']['fields'][$ref_column_name] = array(
                    'position' => (int)$row['field_position']
                );
            }
            //collation?!?
            /*
            if (!empty($row['collation'])) {
                $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC'
                    ? 'ascending' : 'descending');
            }
            */
            $lastrow = $row;
            // otherwise $row is no longer usable on exit from loop
        }
        $result->free();
        if (empty($definition['fields'])) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                $constraint_name . ' is not an existing table constraint', __FUNCTION__);
        }

        $definition['primary'] = (boolean)$lastrow['primary'];
        $definition['unique']  = (boolean)$lastrow['unique'];
        $definition['foreign'] = (boolean)$lastrow['foreign'];
        $definition['check']   = (boolean)$lastrow['check'];
        $definition['deferrable'] = (boolean)$lastrow['deferrable'];
        $definition['initiallydeferred'] = (boolean)$lastrow['initiallydeferred'];
        $definition['onupdate'] = $lastrow['onupdate'];
        $definition['ondelete'] = $lastrow['ondelete'];
        $definition['match']    = $lastrow['match'];

        return $definition;
    }

    // }}}
    // {{{ getTriggerDefinition()

    /**
     * Get the structure of a trigger into an array
     *
     * EXPERIMENTAL
     *
     * WARNING: this function is experimental and may change the returned value
     * at any time until labelled as non-experimental
     *
     * @param string    $trigger    name of trigger that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTriggerDefinition($trigger)
    {
        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $query = "SELECT sys1.name trigger_name,
                         sys2.name table_name,
                         c.text trigger_body,
                         c.encrypted is_encripted,
                         CASE
                           WHEN OBJECTPROPERTY(sys1.id, 'ExecIsTriggerDisabled') = 1
                           THEN 0 ELSE 1
                         END trigger_enabled,
                         CASE
                           WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsertTrigger') = 1
                           THEN 'INSERT'
                           WHEN OBJECTPROPERTY(sys1.id, 'ExecIsUpdateTrigger') = 1
                           THEN 'UPDATE'
                           WHEN OBJECTPROPERTY(sys1.id, 'ExecIsDeleteTrigger') = 1
                           THEN 'DELETE'
                         END trigger_event,
                         CASE WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsteadOfTrigger') = 1
                           THEN 'INSTEAD OF' ELSE 'AFTER'
                         END trigger_type,
                         '' trigger_comment
                    FROM sysobjects sys1
                    JOIN sysobjects sys2 ON sys1.parent_obj = sys2.id
                    JOIN syscomments c ON sys1.id = c.id
                   WHERE sys1.xtype = 'TR'
                     AND sys1.name = ". $db->quote($trigger, 'text');

        $types = array(
            'trigger_name'    => 'text',
            'table_name'      => 'text',
            'trigger_body'    => 'text',
            'trigger_type'    => 'text',
            'trigger_event'   => 'text',
            'trigger_comment' => 'text',
            'trigger_enabled' => 'boolean',
            'is_encripted'    => 'boolean',
        );

        $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($def)) {
            return $def;
        }
        $trg_body = $db->queryCol('EXEC sp_helptext '. $db->quote($trigger, 'text'), 'text');
        if (!MDB2::isError($trg_body)) {
            $def['trigger_body'] = implode(' ', $trg_body);
        }
        return $def;
    }

    // }}}
    // {{{ tableInfo()

    /**
     * Returns information about a table or a result set
     *
     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
     * is a table name.
     *
     * @param object|string  $result  MDB2_result object from a query or a
     *                                 string containing the name of a table.
     *                                 While this also accepts a query result
     *                                 resource identifier, this behavior is
     *                                 deprecated.
     * @param int            $mode    a valid tableInfo mode
     *
     * @return array  an associative array with the information requested.
     *                 A MDB2_Error object on failure.
     *
     * @see MDB2_Driver_Common::tableInfo()
     */
    function tableInfo($result, $mode = null)
    {
        if (is_string($result)) {
           return parent::tableInfo($result, $mode);
        }

        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
        if (!is_resource($resource)) {
            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
                'Could not generate result resource', __FUNCTION__);
        }

        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $case_func = 'strtolower';
            } else {
                $case_func = 'strtoupper';
            }
        } else {
            $case_func = 'strval';
        }

        $count = @mssql_num_fields($resource);
        $res   = array();

        if ($mode) {
            $res['num_fields'] = $count;
        }

        $db->loadModule('Datatype', null, true);
        for ($i = 0; $i < $count; $i++) {
            $res[$i] = array(
                'table' => '',
                'name'  => $case_func(@mssql_field_name($resource, $i)),
                'type'  => @mssql_field_type($resource, $i),
                'length'   => @mssql_field_length($resource, $i),
                'flags' => '',
            );
            $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
            if (MDB2::isError($mdb2type_info)) {
               return $mdb2type_info;
            }
            $res[$i]['mdb2type'] = $mdb2type_info[0][0];
            if ($mode & MDB2_TABLEINFO_ORDER) {
                $res['order'][$res[$i]['name']] = $i;
            }
            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
            }
        }

        return $res;
    }

    // }}}
    // {{{ _mssql_field_flags()

    /**
     * Get a column's flags
     *
     * Supports "not_null", "primary_key",
     * "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
     * "unique_key" (mssql unique index, unique check or primary_key) and
     * "multiple_key" (multikey index)
     *
     * mssql timestamp is NOT similar to the mysql timestamp so this is maybe
     * not useful at all - is the behaviour of mysql_field_flags that primary
     * keys are alway unique? is the interpretation of multiple_key correct?
     *
     * @param string $table   the table name
     * @param string $column  the field name
     *
     * @return string  the flags
     *
     * @access protected
     * @author Joern Barthel <j_barthel@web.de>
     */
    function _mssql_field_flags($table, $column)
    {
        $db =& $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        static $tableName = null;
        static $flags = array();

        if ($table != $tableName) {

            $flags = array();
            $tableName = $table;

            // get unique and primary keys
            $res = $db->queryAll("EXEC SP_HELPINDEX[$table]", null, MDB2_FETCHMODE_ASSOC);

            foreach ($res as $val) {
                $val = array_change_key_case($val, CASE_LOWER);
                $keys = explode(', ', $val['index_keys']);

                if (sizeof($keys) > 1) {
                    foreach ($keys as $key) {
                        $this->_add_flag($flags[$key], 'multiple_key');
                    }
                }

                if (strpos($val['index_description'], 'primary key')) {
                    foreach ($keys as $key) {
                        $this->_add_flag($flags[$key], 'primary_key');
                    }
                } elseif (strpos($val['index_description'], 'unique')) {
                    foreach ($keys as $key) {
                        $this->_add_flag($flags[$key], 'unique_key');
                    }
                }
            }

            // get auto_increment, not_null and timestamp
            $res = $db->queryAll("EXEC SP_COLUMNS[$table]", null, MDB2_FETCHMODE_ASSOC);

            foreach ($res as $val) {
                $val = array_change_key_case($val, CASE_LOWER);
                if ($val['nullable'] == '0') {
                    $this->_add_flag($flags[$val['column_name']], 'not_null');
                }
                if (strpos($val['type_name'], 'identity')) {
                    $this->_add_flag($flags[$val['column_name']], 'auto_increment');
                }
                if (strpos($val['type_name'], 'timestamp')) {
                    $this->_add_flag($flags[$val['column_name']], 'timestamp');
                }
            }
        }

        if (!empty($flags[$column])) {
            return(implode(' ', $flags[$column]));
        }
        return '';
    }

    // }}}
    // {{{ _add_flag()

    /**
     * Adds a string to the flags array if the flag is not yet in there
     * - if there is no flag present the array is created
     *
     * @param array  &$array  the reference to the flag-array
     * @param string $value   the flag value
     *
     * @return void
     *
     * @access protected
     * @author Joern Barthel <j_barthel@web.de>
     */
    function _add_flag(&$array, $value)
    {
        if (!is_array($array)) {
            $array = array($value);
        } elseif (!in_array($value, $array)) {
            array_push($array, $value);
        }
    }

    // }}}
}
?>


code:
<?php
// +----------------------------------------------------------------------+
// | PHP versions 4 and 5                                                 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
// | Stig. S. Bakken, Lukas Smith                                         |
// | All rights reserved.                                                 |
// +----------------------------------------------------------------------+
// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
// | API as well as database abstraction for PHP applications.            |
// | This LICENSE is in the BSD license style.                            |
// |                                                                      |
// | Redistribution and use in source and binary forms, with or without   |
// | modification, are permitted provided that the following conditions   |
// | are met:                                                             |
// |                                                                      |
// | Redistributions of source code must retain the above copyright       |
// | notice, this list of conditions and the following disclaimer.        |
// |                                                                      |
// | Redistributions in binary form must reproduce the above copyright    |
// | notice, this list of conditions and the following disclaimer in the  |
// | documentation and/or other materials provided with the distribution. |
// |                                                                      |
// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
// | Lukas Smith nor the names of his contributors may be used to endorse |
// | or promote products derived from this software without specific prior|
// | written permission.                                                  |
// |                                                                      |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
// |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
// | POSSIBILITY OF SUCH DAMAGE.                                          |
// +----------------------------------------------------------------------+
// | Authors: Paul Cooper <pgc@ucecom.com>                                |
// |          Lorenzo Alberton <l.alberton@quipo.it>                      |
// +----------------------------------------------------------------------+
//
// $Id$

require_once 'MDB2/Driver/Reverse/Common.php';

/**
 * MDB2 PostGreSQL driver for the schema reverse engineering module
 *
 * @package  MDB2
 * @category Database
 * @author   Paul Cooper <pgc@ucecom.com>
 * @author   Lorenzo Alberton <l.alberton@quipo.it>
 */
class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common
{
    // {{{ getTableFieldDefinition()

    /**
     * Get the structure of a field into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $field_name name of field that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableFieldDefinition($table_name, $field_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $result = $db->loadModule('Datatype', null, true);
        if (MDB2::isError($result)) {
            return $result;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $query = "SELECT a.attname AS name,
                         t.typname AS type,
                         CASE a.attlen
                           WHEN -1 THEN
	                         CASE t.typname
	                           WHEN 'numeric' THEN (a.atttypmod / 65536)
	                           WHEN 'decimal' THEN (a.atttypmod / 65536)
	                           WHEN 'money'   THEN (a.atttypmod / 65536)
	                           ELSE CASE a.atttypmod
                                 WHEN -1 THEN NULL
	                             ELSE a.atttypmod - 4
	                           END
                             END
	                       ELSE a.attlen
                         END AS length,
	                     CASE t.typname
	                       WHEN 'numeric' THEN (a.atttypmod % 65536) - 4
	                       WHEN 'decimal' THEN (a.atttypmod % 65536) - 4
	                       WHEN 'money'   THEN (a.atttypmod % 65536) - 4
	                       ELSE 0
                         END AS scale,
                         a.attnotnull,
                         a.atttypmod,
                         a.atthasdef,
                         (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128)
                            FROM pg_attrdef d
                           WHERE d.adrelid = a.attrelid
                             AND d.adnum = a.attnum
                             AND a.atthasdef
                         ) as default
                    FROM pg_attribute a,
                         pg_class c,
                         pg_type t
                   WHERE c.relname = ".$db->quote($table, 'text')."
                     AND a.atttypid = t.oid
                     AND c.oid = a.attrelid
                     AND NOT a.attisdropped
                     AND a.attnum > 0
                     AND a.attname = ".$db->quote($field_name, 'text')."
                ORDER BY a.attnum";
        $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($column)) {
            return $column;
        }

        if (empty($column)) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                'it was not specified an existing table column', __FUNCTION__);
        }

        $column = array_change_key_case($column, CASE_LOWER);
        $mapped_datatype = $db->datatype->mapNativeDatatype($column);
        if (MDB2::isError($mapped_datatype)) {
            return $mapped_datatype;
        }
        list($types, $length, $unsigned, $fixed) = $mapped_datatype;
        $notnull = false;
        if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') {
            $notnull = true;
        }
        $default = null;
        if ($column['atthasdef'] === 't'
            && strpos($column['default'], 'NULL') !== 0
            && !preg_match("/nextval\('([^']+)'/", $column['default'])
        ) {
            $pattern = '/^\'(.*)\'::[\w ]+$/i';
            $default = $column['default'];#substr($column['adsrc'], 1, -1);
            if ((null === $default) && $notnull) {
                $default = '';
            } elseif (!empty($default) && preg_match($pattern, $default)) {
                //remove data type cast
                $default = preg_replace ($pattern, '\\1', $default);
            }
        }
        $autoincrement = false;
        if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) {
            $autoincrement = true;
        }
        $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']);
        if (null !== $length) {
            $definition[0]['length'] = $length;
        }
        if (null !== $unsigned) {
            $definition[0]['unsigned'] = $unsigned;
        }
        if (null !== $fixed) {
            $definition[0]['fixed'] = $fixed;
        }
        if ($default !== false) {
            $definition[0]['default'] = $default;
        }
        if ($autoincrement !== false) {
            $definition[0]['autoincrement'] = $autoincrement;
        }
        foreach ($types as $key => $type) {
            $definition[$key] = $definition[0];
            if ($type == 'clob' || $type == 'blob') {
                unset($definition[$key]['default']);
            }
            $definition[$key]['type'] = $type;
            $definition[$key]['mdb2type'] = $type;
        }
        return $definition;
    }

    // }}}
    // {{{ getTableIndexDefinition()

    /**
     * Get the structure of an index into an array
     *
     * @param string $table_name name of table that should be used in method
     * @param string $index_name name of index that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableIndexDefinition($table_name, $index_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $query = 'SELECT relname, indkey FROM pg_index, pg_class';
        $query.= ' WHERE pg_class.oid = pg_index.indexrelid';
        $query.= " AND indisunique != 't' AND indisprimary != 't'";
        $query.= ' AND pg_class.relname = %s';
        $index_name_mdb2 = $db->getIndexName($index_name);
        $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($row) || empty($row)) {
            // fallback to the given $index_name, without transformation
            $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC);
        }
        if (MDB2::isError($row)) {
            return $row;
        }

        if (empty($row)) {
            return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                'it was not specified an existing table index', __FUNCTION__);
        }

        $row = array_change_key_case($row, CASE_LOWER);

        $db->loadModule('Manager', null, true);
        $columns = $db->manager->listTableFields($table_name);

        $definition = array();

        $index_column_numbers = explode(' ', $row['indkey']);

        $colpos = 1;
        foreach ($index_column_numbers as $number) {
            $definition['fields'][$columns[($number - 1)]] = array(
                'position' => $colpos++,
                'sorting' => 'ascending',
            );
        }
        return $definition;
    }

    // }}}
    // {{{ getTableConstraintDefinition()

    /**
     * Get the structure of a constraint into an array
     *
     * @param string $table_name      name of table that should be used in method
     * @param string $constraint_name name of constraint that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     */
    function getTableConstraintDefinition($table_name, $constraint_name)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        list($schema, $table) = $this->splitTableSchema($table_name);

        $query = "SELECT c.oid,
                         c.conname AS constraint_name,
                         CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\",
                         CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\",
                         CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\",
                         CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\",
                         CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable,
                         CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred,
                         --array_to_string(c.conkey, ' ') AS constraint_key,
                         t.relname AS table_name,
                         t2.relname AS references_table,
                         CASE confupdtype
                           WHEN 'a' THEN 'NO ACTION'
                           WHEN 'r' THEN 'RESTRICT'
                           WHEN 'c' THEN 'CASCADE'
                           WHEN 'n' THEN 'SET NULL'
                           WHEN 'd' THEN 'SET DEFAULT'
                         END AS onupdate,
                         CASE confdeltype
                           WHEN 'a' THEN 'NO ACTION'
                           WHEN 'r' THEN 'RESTRICT'
                           WHEN 'c' THEN 'CASCADE'
                           WHEN 'n' THEN 'SET NULL'
                           WHEN 'd' THEN 'SET DEFAULT'
                         END AS ondelete,
                         CASE confmatchtype
                           WHEN 'u' THEN 'UNSPECIFIED'
                           WHEN 'f' THEN 'FULL'
                           WHEN 'p' THEN 'PARTIAL'
                         END AS match,
                         --array_to_string(c.confkey, ' ') AS fk_constraint_key,
                         consrc
                    FROM pg_constraint c
               LEFT JOIN pg_class t  ON c.conrelid  = t.oid
               LEFT JOIN pg_class t2 ON c.confrelid = t2.oid
                   WHERE c.conname = %s
                     AND t.relname = " . $db->quote($table, 'text');
        $constraint_name_mdb2 = $db->getIndexName($constraint_name);
        $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
        if (MDB2::isError($row) || empty($row)) {
            // fallback to the given $index_name, without transformation
            $constraint_name_mdb2 = $constraint_name;
            $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
        }
        if (MDB2::isError($row)) {
            return $row;
        }
        $uniqueIndex = false;
        if (empty($row)) {
            // We might be looking for a UNIQUE index that was not created
            // as a constraint but should be treated as such.
            $query = 'SELECT relname AS constraint_name,
                             indkey,
                             0 AS "check",
                             0 AS "foreign",
                             0 AS "primary",
                             1 AS "unique",
                             0 AS deferrable,
                             0 AS initiallydeferred,
                             NULL AS references_table,
                             NULL AS onupdate,
                             NULL AS ondelete,
                             NULL AS match
                        FROM pg_index, pg_class
                       WHERE pg_class.oid = pg_index.indexrelid
                         AND indisunique = \'t\'
                         AND pg_class.relname = %s';
            $constraint_name_mdb2 = $db->getIndexName($constraint_name);
            $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
            if (MDB2::isError($row) || empty($row)) {
                // fallback to the given $index_name, without transformation
                $constraint_name_mdb2 = $constraint_name;
                $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC);
            }
            if (MDB2::isError($row)) {
                return $row;
            }
            if (empty($row)) {
                return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
                    $constraint_name . ' is not an existing table constraint', __FUNCTION__);
            }
            $uniqueIndex = true;
        }

        $row = array_change_key_case($row, CASE_LOWER);

        $definition = array(
            'primary' => (boolean)$row['primary'],
            'unique'  => (boolean)$row['unique'],
            'foreign' => (boolean)$row['foreign'],
            'check'   => (boolean)$row['check'],
            'fields'  => array(),
            'references' => array(
                'table'  => $row['references_table'],
                'fields' => array(),
            ),
            'deferrable' => (boolean)$row['deferrable'],
            'initiallydeferred' => (boolean)$row['initiallydeferred'],
            'onupdate' => $row['onupdate'],
            'ondelete' => $row['ondelete'],
            'match'    => $row['match'],
        );

        if ($uniqueIndex) {
            $db->loadModule('Manager', null, true);
            $columns = $db->manager->listTableFields($table_name);
            $index_column_numbers = explode(' ', $row['indkey']);
            $colpos = 1;
            foreach ($index_column_numbers as $number) {
                $definition['fields'][$columns[($number - 1)]] = array(
                    'position' => $colpos++,
                    'sorting'  => 'ascending',
                );
            }
            return $definition;
        }

        $query = 'SELECT a.attname
                    FROM pg_constraint c
               LEFT JOIN pg_class t  ON c.conrelid  = t.oid
               LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey)
                   WHERE c.conname = %s
                     AND t.relname = ' . $db->quote($table, 'text');
        $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null);
        if (MDB2::isError($fields)) {
            return $fields;
        }
        $colpos = 1;
        foreach ($fields as $field) {
            $definition['fields'][$field] = array(
                'position' => $colpos++,
                'sorting' => 'ascending',
            );
        }

        if ($definition['foreign']) {
            $query = 'SELECT a.attname
                        FROM pg_constraint c
                   LEFT JOIN pg_class t  ON c.confrelid  = t.oid
                   LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey)
                       WHERE c.conname = %s
                         AND t.relname = ' . $db->quote($definition['references']['table'], 'text');
            $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null);
            if (MDB2::isError($foreign_fields)) {
                return $foreign_fields;
            }
            $colpos = 1;
            foreach ($foreign_fields as $foreign_field) {
                $definition['references']['fields'][$foreign_field] = array(
                    'position' => $colpos++,
                );
            }
        }

        if ($definition['check']) {
            $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')");
            // ...
        }
        return $definition;
    }

    // }}}
    // {{{ getTriggerDefinition()

    /**
     * Get the structure of a trigger into an array
     *
     * EXPERIMENTAL
     *
     * WARNING: this function is experimental and may change the returned value
     * at any time until labelled as non-experimental
     *
     * @param string $trigger name of trigger that should be used in method
     * @return mixed data array on success, a MDB2 error on failure
     * @access public
     *
     * @TODO: add support for plsql functions and functions with args
     */
    function getTriggerDefinition($trigger)
    {
        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $query = "SELECT trg.tgname AS trigger_name,
                         tbl.relname AS table_name,
                         CASE
                            WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();'
                            ELSE ''
                         END AS trigger_body,
                         CASE trg.tgtype & cast(2 as int2)
                            WHEN 0 THEN 'AFTER'
                            ELSE 'BEFORE'
                         END AS trigger_type,
                         CASE trg.tgtype & cast(28 as int2)
                            WHEN 16 THEN 'UPDATE'
                            WHEN 8 THEN 'DELETE'
                            WHEN 4 THEN 'INSERT'
                            WHEN 20 THEN 'INSERT, UPDATE'
                            WHEN 28 THEN 'INSERT, UPDATE, DELETE'
                            WHEN 24 THEN 'UPDATE, DELETE'
                            WHEN 12 THEN 'INSERT, DELETE'
                         END AS trigger_event,
                         CASE trg.tgenabled
                            WHEN 'O' THEN 't'
                            ELSE trg.tgenabled
                         END AS trigger_enabled,
                         obj_description(trg.oid, 'pg_trigger') AS trigger_comment
                    FROM pg_trigger trg,
                         pg_class tbl,
                         pg_proc p
                   WHERE trg.tgrelid = tbl.oid
                     AND trg.tgfoid = p.oid
                     AND trg.tgname = ". $db->quote($trigger, 'text');
        $types = array(
            'trigger_name'    => 'text',
            'table_name'      => 'text',
            'trigger_body'    => 'text',
            'trigger_type'    => 'text',
            'trigger_event'   => 'text',
            'trigger_comment' => 'text',
            'trigger_enabled' => 'boolean',
        );
        return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC);
    }

    // }}}
    // {{{ tableInfo()

    /**
     * Returns information about a table or a result set
     *
     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
     * is a table name.
     *
     * @param object|string  $result  MDB2_result object from a query or a
     *                                 string containing the name of a table.
     *                                 While this also accepts a query result
     *                                 resource identifier, this behavior is
     *                                 deprecated.
     * @param int            $mode    a valid tableInfo mode
     *
     * @return array  an associative array with the information requested.
     *                 A MDB2_Error object on failure.
     *
     * @see MDB2_Driver_Common::tableInfo()
     */
    function tableInfo($result, $mode = null)
    {
        if (is_string($result)) {
           return parent::tableInfo($result, $mode);
        }

        $db = $this->getDBInstance();
        if (MDB2::isError($db)) {
            return $db;
        }

        $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result;
        if (!is_resource($resource)) {
            return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
                'Could not generate result resource', __FUNCTION__);
        }

        if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
            if ($db->options['field_case'] == CASE_LOWER) {
                $case_func = 'strtolower';
            } else {
                $case_func = 'strtoupper';
            }
        } else {
            $case_func = 'strval';
        }

        $count = @pg_num_fields($resource);
        $res   = array();

        if ($mode) {
            $res['num_fields'] = $count;
        }

        $db->loadModule('Datatype', null, true);
        for ($i = 0; $i < $count; $i++) {
            $res[$i] = array(
                'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '',
                'name'  => $case_func(@pg_field_name($resource, $i)),
                'type'  => @pg_field_type($resource, $i),
                'length' => @pg_field_size($resource, $i),
                'flags' => '',
            );
            $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]);
            if (MDB2::isError($mdb2type_info)) {
               return $mdb2type_info;
            }
            $res[$i]['mdb2type'] = $mdb2type_info[0][0];
            if ($mode & MDB2_TABLEINFO_ORDER) {
                $res['order'][$res[$i]['name']] = $i;
            }
            if ($mode & MDB2_TABLEINFO_ORDERTABLE) {
                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
            }
        }

        return $res;
    }
}
?>