<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
 * SugarCRM is a customer relationship management program developed by
 * SugarCRM, Inc. Copyright (C) 2004-2010 SugarCRM Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * 
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
 * technical reasons, the Appropriate Legal Notices must display the words
 * "Powered by SugarCRM".
 ********************************************************************************/
/*********************************************************************************

* Description: This file is an abstract class and handles the Data base functionality for
* the application. It is called by the DBManager class to generate various sql statements.
*
* All the functions in this class will work with any bean which implements the meta interface.
* Please refer the DBManager documentation for the details.
*
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
* All Rights Reserved.
* Contributor(s): ______________________________________..
********************************************************************************/

abstract class DBHelper
{
    /**
     * Instance of the related DBManager object
     *
     * @var object DBManager instance
     */
    public $db;

    /**
     * Instance of the related SugarBean object
     *
     * @var object SugarBean instance
     */
    public $bean;

    /**
	 * Generates sql for create table statement for a bean.
	 *
	 * @param  object $bean SugarBean instance
	 * @return string SQL Create Table statement
	 */
	public function createTableSQL(
        SugarBean $bean
        )
    {
		$tablename = $bean->getTableName();
		$fieldDefs = $bean->getFieldDefinitions();
		$indices = $bean->getIndices();
		return $this->createTableSQLParams($tablename, $fieldDefs, $indices);

	}

	/**
	 * Generates sql for create table statement for a bean.
	 *
	 * @param  string $tablename
	 * @param  array  $fieldDefs
     * @param  array  $indices
     * @param  string $engine
     * @return string SQL Create Table statement
	 */
	public function createTableSQLParams(
        $tablename,
        $fieldDefs,
        $indices,
        $engine = null
        )
    {
        $columns = $this->columnSQLRep($fieldDefs, false, $tablename);
        if (empty($columns))
            return false;

        $keys = $this->keysSQL($indices);
        if (!empty($keys))
            $keys = ",$keys";

        // cn: bug 9873 - module tables do not get created in utf8 with assoc collation
        return "CREATE TABLE $tablename ($columns $keys) CHARACTER SET utf8 COLLATE utf8_general_ci";
	}


	/**
     * Generates SQL for insert statement.
     *
     * @param  object $bean SugarBean instance
     * @return string SQL Create Table statement
     */
    public function insertSQL(
        SugarBean $bean
        )
    {
		// get column names and values
		$values = array();
		foreach ($bean->getFieldDefinitions() as $fieldDef)
		{
            if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')
                continue;

            $val = $bean->getFieldValue($fieldDef['name']);
            if (strlen($val) <= 0) {
                if(isset($fieldDef['default']) && (strlen($fieldDef['default']) > 0))
                    $val = $fieldDef['default'];
                else
                    $val = null;
            }

            //handle auto increment values here only need to do this on insert not create
            if (isset($fieldDef['auto_increment']) && $fieldDef['auto_increment']) {
                $values[$fieldDef['name']] =
                    $this->getAutoIncrement($bean->getTableName(), $fieldDef['name']);
            }
            elseif (isset($bean->$fieldDef['name'])) {
                // need to do some thing about types of values
                $values[$fieldDef['name']] = $this->massageValue($val, $fieldDef);
            }
            elseif ($fieldDef['name'] == 'deleted'){
                $values['deleted'] = $val;
            }
		}

		if ( sizeof($values) == 0 )
            return ""; // no columns set

		// get the entire sql
		return "INSERT INTO ".$bean->getTableName()."
                    (".implode(",", array_keys($values)).")
                    VALUES (".implode(",", $values).")";
	}

	/**
     * Generates SQL for update statement.
     *
     * @param  object $bean SugarBean instance
     * @param  array  $where Optional, where conditions in an array
     * @return string SQL Create Table statement
     */
    public function updateSQL(
        SugarBean $bean,
        array $where = array()
        )
    {
        $primaryField = $bean->getPrimaryFieldDefinition();
        $columns = array();

		// get column names and values
		foreach ($bean->getFieldDefinitions() as $field => $fieldDef) {
           // Do not write out the id field on the update statement.
           // We are not allowed to change ids.
           if ($fieldDef['name'] == $primaryField['name'])
               continue;

           // If the field is an auto_increment field, then we shouldn't be setting it.  This was added
           // specially for Bugs and Cases which have a number associated with them.
           if (isset($bean->field_name_map[$field]['auto_increment']) &&
                    $bean->field_name_map[$field]['auto_increment'] == true)
               continue;

           //custom fields handle their save seperatley
           if(isset($bean->field_name_map) && !empty($bean->field_name_map[$field]['custom_type']))
               continue;

           if (isset($bean->$fieldDef['name'])
                    && (!isset($fieldDef['source']) || $fieldDef['source'] == 'db')) {
               $val = $bean->getFieldValue($fieldDef['name']);

               // need to do some thing about types of values
               if (strlen($val) <= 0)
                    $columns[] = "{$fieldDef['name']}=null";
		       else
                    $columns[] = "{$fieldDef['name']}=".$this->massageValue($val, $fieldDef);
           }
		}

		if ( sizeof($columns) == 0 )
            return ""; // no columns set

        // build where clause
        $where = $this->updateWhereArray($bean, $where);
        $where = $this->getWhereClause($bean, $where);

        return "update ".$bean->getTableName()."
                    set ".implode(",", $columns)."
                    $where and deleted=0";
	}

    /**
     * This method returns a where array so that it has id entry if
     * where is not an array or is empty
     *
     * @param  object $bean SugarBean instance
     * @param  array  $where Optional, where conditions in an array
     * @return array
     */
    protected function updateWhereArray(
        SugarBean $bean,
        array $where = array()
        )
    {
		if (sizeof($where) == 0) {
            $fieldDef = $bean->getPrimaryFieldDefinition();
            $primaryColumn = $fieldDef['name'];

            $val = $bean->getFieldValue($fieldDef['name']);
            if ($val != FALSE){
                $where[$primaryColumn] = $val;
            }
        }

        return $where;
	}

    /**
     * Returns a where clause without the 'where' key word
     *
     * The clause returned does not have an 'and' at the beginning and the columns
     * are joined by 'and'.
     *
     * @param  string $table table name
     * @param  array  $whereArray Optional, where conditions in an array
     * @return string
     */
    protected function getColumnWhereClause(
        $table,
        array $whereArray = array()
        )
    {
        foreach ($whereArray as $name => $val) {
            $op = "=";
            if (is_array($val)) {
                $op = "IN";
                $temp = array();
                foreach ($val as $tval){
                    $temp[] = "'$tval'";
                }
                $val = implode(",", $temp);
                $val = "($val)";
            }
            else
                $val = "'$val'";

            $where[] = " $table.$name $op $val";
        }

        if (is_array($where))
            $where = implode(" and ", $where);

        return $where;
    }

    /**
     * This method returns a complete where clause built from the
     * where values specified.
     *
     * @param  string $table table name
     * @param  array  $whereArray Optional, where conditions in an array
     * @return string
     */
	protected function getWhereClause(
        SugarBean $bean,
        array $whereArray
        )
	{
       return " where " . $this->getColumnWhereClause($bean->getTableName(), $whereArray);
	}

    /**
     * Generates SQL for delete statement identified by id.
     *
     * @param  object $bean SugarBean instance
     * @param  array  $where where conditions in an array
     * @return string SQL Update Statement
     */
	public function deleteSQL(
        SugarBean $bean,
        array $where
        )
    {
        $where = $this->updateWhereArray($bean, $where);
        $where = $this->getWhereClause($bean, $where);

        return "update ".$bean->getTableName()." set deleted=1 $where";
	}



    /**
     * Generates SQL for select statement for any bean identified by id.
     *
     * @param  object $bean SugarBean instance
     * @param  array  $where where conditions in an array
     * @return string SQL Select Statement
     */
	public function retrieveSQL(
        SugarBean $bean,
        array $where
        )
    {
        $where = $this->updateWhereArray($bean, $where);
        $where = $this->getWhereClause($bean, $where);

        return "select * from ".$bean->getTableName()." $where and deleted=0";
    }

    /**
     * This method implements a generic sql for a collection of beans.
     *
     * Currently, this function does not support outer joins.
     *
     * @param  array $bean value returned by get_class method as the keys and a bean as
     *      the value for that key. These beans will be joined in the sql by the key
     *      attribute of field defs.
     * @param  array $cols Optional, columns to be returned with the keys as names of bean
     *      as identified by get_class of bean. Values of this array is the array of fieldDefs
     *      to be returned for a bean. If an empty array is passed, all columns are selected.
     * @param  array $whereClause Optional, values with the keys as names of bean as identified
     *      by get_class of bean. Each value at the first level is an array of values for that
     *      bean identified by name of fields. If we want to pass multiple values for a name,
     *      pass it as an array. If where is not passed, all the rows will be returned.
     * @return string SQL Select Statement
     */
    public function retrieveViewSQL(
        array $beans,
        array $cols = array(),
        array $whereClause = array()
        )
    {
        $relations = array(); // stores relations between tables as they are discovered

        foreach ($beans as $beanID => $bean) {
            $tableName = $bean->getTableName();
            $beanTables[$beanID] = $tableName;

            $table = "$beanID";
            $tables[$table] = $tableName;
            $aliases[$tableName][] = $table;

            // build part of select for this table
            if (is_array($cols[$beanID]))
                foreach ($cols[$beanID] as $def) $select[] = $table.".".$def['name'];

            // build part of where clause
            if (is_array($whereClause[$beanID])){
                $where[] = $this->getColumnWhereClause($table, $whereClause[$beanID]);
            }
            // initialize so that it can be used properly in form clause generation
            $table_used_in_from[$table] = false;

            $indices = $bean->getIndices();
            foreach ($indices as $index){
                if ($index['type'] == 'foreign') {
                    $relationship[$table][] = array('foreignTable'=> $index['foreignTable']
                                                   ,'foreignColumn'=>$index['foreignField']
                                                   ,'localColumn'=> $index['fields']
                                                   );
                }
            }
            $where[] = " $table.deleted = 0";
        }

        // join these clauses
        $select = (sizeof($select) > 0) ? implode(",", $select) : "*";
        $where = implode(" and ", $where);

        // generate the from clause. Use relations array to generate outer joins
        // all the rest of the tables will be used as a simple from
        // relations table define relations between table1 and table2 through column on table 1
        // table2 is assumed to joing through primaty key called id
        $separator = "";
        foreach ($relations as $table1 => $rightsidearray){
            if ($table_used_in_from[$table1]) continue; // table has been joined

            $from .= $separator." ".$table1;
            $table_used_in_from[$table1] = true;
            foreach ($rightsidearray as $tablearray){
                $table2 = $tablearray['foreignTable']; // get foreign table
                $tableAlias = $aliases[$table2]; // get a list of aliases fo thtis table
                foreach ($tableAlias as $table2) {
                    //choose first alias that does not match
                    // we are doing this because of self joins.
                    // in case of self joins, the same table will bave many aliases.
                    if ($table2 != $table1) break;
                }

                $col = $tablearray['foreingColumn'];
                $name = $tablearray['localColumn'];
                $from .= " LEFT JOIN $table on ($table1.$name = $table2.$col)";
                $table_used_in_from[$table2] = true;
            }
            $separator = ",";
        }

        return "select $select from $from where $where";
    }

    /**
     * Generates SQL for create index statement for a bean.
     *
     * @param  object $bean SugarBean instance
     * @param  array  $fields fields used in the index
     * @param  string $name index name
     * @param  bool   $unique Optional, set to true if this is an unique index
     * @return string SQL Select Statement
     */
	public function createIndexSQL(
        SugarBean $bean,
        array $fields,
        $name,
        $unique = TRUE
        )
    {
		$unique = ($unique) ? "unique" : "";
		$tablename = $bean->getTableName();

		// get column names
		foreach ($fields as $fieldDef)
            $columns[] = $fieldDef['name'];

        if (sizeof($columns) == 0)
            return "";

        $columns = implode(",", $columns);

        return "create $unique index $name on $tablename ($columns)";
	}

    /**
     * Returns the type of the variable in the field
     *
     * @param  array $fieldDef
     * @return string
     */
    public function getFieldType(
        $fieldDef
        )
    {
        // get the type for db type. if that is not set,
        // get it from type. This is done so that
        // we do not have change a lot of existing code
        // and add dbtype where type is being used for some special
        // purposes like referring to foreign table etc.
        if(!empty($fieldDef['dbType']))
            return  $fieldDef['dbType'];
        if(!empty($fieldDef['dbtype']))
            return  $fieldDef['dbtype'];
        if (!empty($fieldDef['type']))
            return  $fieldDef['type'];
        if (!empty($fieldDef['Type']))
            return  $fieldDef['Type'];
        if (!empty($fieldDef['data_type']))
            return  $fieldDef['data_type'];

        return null;
    }

    /**
     * Returns the defintion for a single column
     *
     * @param  array  $fieldDef
     * @param  bool   $ignoreRequired  Optional, true if we should ignor this being a required field
     * @param  string $table           Optional, table name
     * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
     * @return string or array if $return_as_array is true
     */
	protected function oneColumnSQLRep(
        $fieldDef,
        $ignoreRequired = false,
        $table = '',
        $return_as_array = false
        )
    {

        $name = $fieldDef['name'];
        $type = $this->getFieldType($fieldDef);
        $colType = $this->getColumnType($type, $name, $table);

        if (( $colType == 'nvarchar' 
				or $colType == 'nchar' 
				or $colType == 'varchar' 
				or $colType == 'char' 
				or $colType == 'varchar2') ) {
            if( !empty($fieldDef['len']))
                $colType .= "(".$fieldDef['len'].")";
            else
                $colType .= "(255)";
        }
       if($colType == 'decimal' || $colType == 'float'){
	        if(!empty($fieldDef	['len'])){
	        	if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
	        		if(strpos($fieldDef	['len'],',') === false){
	                    $colType .= "(".$fieldDef['len'].",".$fieldDef['precision'].")";
	        		}else{
	                    $colType .= "(".$fieldDef['len'].")";
	        		}
	        	else 
	                    $colType .= "(".$fieldDef['len'].")";
	        }
       }        

       
        if (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0)
            $default = " DEFAULT '".$fieldDef['default']."'";
        elseif (!isset($default) && $type == 'bool')
            $default = " DEFAULT 0 ";
        elseif (!isset($default))
            $default = '';

        $auto_increment = '';
        if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
        	$auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);

        $required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
        if (!$ignoreRequired && ( isset($fieldDef['required'])
                && ($fieldDef['required'] === true
                    || $fieldDef['required'] === 'true'
                    || $fieldDef['required'] == 1)))//bug 25834
            $required =  "NOT NULL";
        if ($name == 'id' && !isset($fieldDef['required']))
            $required =  "NOT NULL";
        if ($ignoreRequired)
            $required = "";

        if ( $return_as_array )
            return array(
                'name' => $name,
                'colType' => $colType,
                'default' => $default,
                'required' => $required,
                'auto_increment' => $auto_increment,
                'full' => "$name $colType $default $required $auto_increment",
                );
	    else
	    	return "$name $colType $default $required $auto_increment";

	}

    /**
     * Returns SQL defintions for all columns in a table
     *
     * @param  array  $fieldDefs
     * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
     * @param  string $tablename      Optional, table name
     * @return string SQL column definitions
     */
	protected function columnSQLRep(
        $fieldDefs,
        $ignoreRequired = false,
        $tablename
        )
    {
		$columns = array();

		if ($this->isFieldArray($fieldDefs)) {
			foreach ($fieldDefs as $fieldDef) {
				if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
					$columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
				}
			}
			$columns = implode(",", $columns);
		}
		else {
			$columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
		}

		return $columns;
	}

	/**
     * Returns the next value for an auto increment
     *
     * @param  string $table tablename
     * @param  string $field_name
     * @return string
     */
	public static function getAutoIncrement(
        $table,
        $field_name
        )
    {
		return "";
	}

	/**
     * Either creates an auto increment through queries or returns sql for auto increment
     * that can be appended to the end of column defination (mysql)
     *
     * @param  string $table tablename
     * @param  string $field_name
     * @return string
     */
	protected function setAutoIncrement(
        $table,
        $field_name
        )
    {
        $this->deleteAutoIncrement($table, $field_name);
        return "";
	}

	/**
     * Deletes an auto increment (for oracle not mysql)
     *
     * @param string $table tablename
     * @param string $field_name
     */
	public function deleteAutoIncrement(
        $table,
        $field_name
        )
    {
        return;
	}

	/**
     * Generates the SQL for changing columns
     *
     * @param string $tablename
     * @param array  $fieldDefs
     * @param string $action
     * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
	 */
	abstract protected function changeColumnSQL(
        $tablename,
        $fieldDefs,
        $action,
        $ignoreRequired = false);

    /**
     * This method generates sql for adding a column to table identified by field def.
     *
     * @param  string $tablename
     * @param  array  $fieldDefs
     * @return string SQL statement
     */
	public function addColumnSQL(
        $tablename,
        $fieldDefs,
        $ignoreRequired = false
        )
    {
       return $this->changeColumnSQL($tablename, $fieldDefs, 'add', $ignoreRequired);
	}

    /**
     * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
     *
     * @param  string $tablename
     * @param  array  $newFieldDefs
     * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
     * @return string SQL statement
     */
	public function alterColumnSQL(
        $tablename,
        $newFieldDefs,
        $ignorerequired = false
        )
    {
        return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
    }

    /**
     * Generates SQL for dropping a table.
     *
     * @param  object $bean Sugarbean instance
     * @return string SQL statement
     */
	public function dropTableSQL(
        SugarBean $bean
        )
    {
		return $this->dropTableNameSQL($bean->getTableName());
	}

	/**
     * Generates SQL for dropping a table.
     *
     * @param  string $name table name
     * @return string SQL statement
     */
	public function dropTableNameSQL(
        $name
        )
    {
		return "drop table if exists ".$name;
	}

    /**
     * This method generates sql that deletes a column identified by fieldDef.
     *
     * @param  object $bean      Sugarbean instance
     * @param  array  $fieldDefs
     * @return string SQL statement
     */
	public function deleteColumnSQL(
        SugarBean $bean,
        $fieldDefs
        )
    {
        if ($this->isFieldArray($fieldDefs))
            foreach ($fieldDefs as $fieldDef)
                $columns[] = $fieldDef['name'];
        else
            $columns[] = $fieldDefs['name'];

        return "alter table ".$bean->getTableName()." drop (".implode(", ", $columns).")";
	}
    
    /**
     * This method generates sql that drops a column identified by fieldDef.
     * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
     *
     * @param  string $tablename
     * @param  array  $fieldDefs
     * @return string SQL statement
     */
	public function dropColumnSQL(
        $tablename,
        $fieldDefs
        )
    {
        return $this->changeColumnSQL($tablename, $fieldDefs, 'drop');
	}

    /**
     * Generates SQL for key statement for any bean identified by id.
     *
     * The passes array is an array of field definitions or a field definition
     * itself. The keys generated will be either primary, foreign, unique, index
     * or none at all depending on the setting of the "key" parameter of a field definition
     *
     * @param  array  $indices
     * @param  bool   $alter_table
     * @param  string $alter_action
     * @return string SQL Statement
     */
    protected function keysSQL(
        $indices,
        $alter_table = false,
        $alter_action = ''
        )
	{
        return '';
    }

    /**
     * Outputs a correct string for the sql statement according to value
     *
     * @param  mixed $val
     * @param  array $fieldDef field definition
     * @return mixed
     */
	public function massageValue(
        $val,
        $fieldDef
        )
    {
        if ( strlen($val) <= 0 )
            return "''";

        $type = $this->getFieldType($fieldDef);

        switch ($type) {
        case 'int':
        case 'double':
        case 'float':
        case 'uint':
        case 'ulong':
        case 'long':
        case 'short':
        case 'tinyint':
        case 'bool':
            return $val;
            break;
		}

        $qval = $this->quote($val);

        switch ($type) {
        case 'varchar':
        case 'char':
        case 'longtext':
        case 'text':
        case 'enum':
        case 'multienum':
        case 'html':
        case 'blob':
        case 'longblob':
        case 'clob':
        case 'id':
        case 'datetime':
            return $qval;
            break;
        case 'date':
        case 'time':
            return "$qval";
            break;
        }

        return $val;
	}

    /**
     * Massages the field defintions to fill in anything else the DB backend may add
     *
     * @param  array  $fieldDef
     * @param  string $tablename
     * @return array
     */
    public function massageFieldDef(
        &$fieldDef,
        $tablename
        )
    {
        if ( !isset($fieldDef['dbType']) ) {
            if ( isset($fieldDef['dbtype']) )
                $fieldDef['dbType'] = $fieldDef['dbtype'];
            else
                $fieldDef['dbType'] = $fieldDef['type'];
        }
        $type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
        $matches = array();
        preg_match_all("/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)/i", $type, $matches);
        if ( isset($matches[1][0]) )
            $fieldDef['type'] = $matches[1][0];
        if ( isset($matches[2][0]) && empty($fieldDef['len']) )
            $fieldDef['len'] = $matches[2][0];
        if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) )
            $fieldDef['len'] .= ",{$fieldDef['precision']}";
        if (isset($fieldDef['required']) && ($fieldDef['required'] == true
                || $fieldDef['required'] == '1'
                || $fieldDef['required'] == 1)
                || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) )
            $fieldDef['required'] = 'true';
    }

    /**
     * Returns the valid type for a column given the type in fieldDef
     *
     * @param  string $type field type
     * @return string valid type for the given field
     */
    abstract public function getColumnType(
        $type,
        $name = '',
        $table = ''
        );

    /**
     * Checks to see if passed array is truely an array of defitions
     *
     * Such an array may have type as a key but it will point to an array
     * for a true array of definitions an to a col type for a definition only
     *
     * @param  mixed $defArray
     * @return bool
     */
    public function isFieldArray(
        $defArray
        )
    {
        if ( !is_array($defArray) )
            return false;

        if ( isset($defArray['type']) ){
            // type key exists. May be an array of defs or a simple definition
            $type = $defArray['type'];
            return is_array($type); // type is not an array => definition else array
        }

        // type does not exist. Must be array of definitions
        return true;
    }

    /**
     * returns true if the type can be mapped to a valid column type
     *
     * @param  string $type
     * @return bool
     */
    protected function validColumnType(
        $type
        )
    {
        $coltype = $this->getColumnType($type);
        return ($coltype) ? true : false;
    }

    /**
     * Saves changes to module's audit table
     *
     * @param object $bean    Sugarbean instance
     * @param array  $changes changes
     * @see DBHelper::getDataChanges()
     */
    public function save_audit_records(
        SugarBean &$bean,
        &$changes
        )
	{
		global $current_user;
		$sql = "INSERT INTO ".$bean->get_audit_table_name();
		//get field defs for the audit table.
		require('metadata/audit_templateMetaData.php');
		$fieldDefs = $dictionary['audit']['fields'];

		$values=array();
		$values['id']=$this->massageValue(create_guid(), $fieldDefs['id']);
		$values['parent_id']=$bean->dbManager->getHelper()->massageValue($bean->id, $fieldDefs['parent_id']);
		$values['field_name']=$bean->dbManager->getHelper()->massageValue($changes['field_name'], $fieldDefs['field_name']);
		$values['data_type']=$bean->dbManager->getHelper()->massageValue($changes['data_type'], $fieldDefs['data_type']);
		if ($changes['data_type']=='text') {
			$bean->fetched_row[$changes['field_name']]=$changes['after'];;
			$values['before_value_text']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_text']);
			$values['after_value_text']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_text']);
		} else {
			$bean->fetched_row[$changes['field_name']]=$changes['after'];;
			$values['before_value_string']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_string']);
			$values['after_value_string']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_string']);
		}
		$values['date_created']=$bean->dbManager->getHelper()->massageValue(gmdate($GLOBALS['timedate']->get_db_date_time_format()), $fieldDefs['date_created']);
		$values['created_by']=$bean->dbManager->getHelper()->massageValue($current_user->id, $fieldDefs['created_by']);

		$sql .= "(".implode(",", array_keys($values)).") ";
		$sql .= "VALUES(".implode(",", $values).")";
		
        if ( $this->db->dbType == 'oci8' && $changes['data_type'] == 'text' ) {
            $sql .= " RETURNING before_value_text, after_value_text INTO :before_value_text, :after_value_text";
            $stmt = oci_parse($this->db->getDatabase(), $sql);
            $err = oci_error($this->db->getDatabase());
            if ($err != false){
                $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
                return false;
            }
            $before_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
            oci_bind_by_name($stmt, ":before_value_text", $before_value_text_LOB, -1, OCI_B_CLOB);
            $after_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
            oci_bind_by_name($stmt, ":after_value_text", $after_value_text_LOB, -1, OCI_B_CLOB);
            oci_execute($stmt, OCI_DEFAULT);
            $err = oci_error($this->db->getDatabase());
            if ($err != false){
                $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
                return false;
            }
            $before_value_text_LOB->save($changes['before']);
            $after_value_text_LOB->save($changes['after']);
            oci_commit($this->db->getDatabase());
            $before_value_text_LOB->free();
            $after_value_text_LOB->free();
            oci_free_statement($stmt);
        }
        else {
            $bean->db->query($sql);
        }
	}

    /**
     * Uses the audit enabled fields array to find fields whose value has changed.
	 * The before and after values are stored in the bean.
     *
     * @param object $bean Sugarbean instance
     * @return array
     */
	public function getDataChanges(
        SugarBean &$bean
        )
    {
    	$changed_values=array();
		$audit_fields=$bean->getAuditEnabledFieldDefinitions();

		if (is_array($audit_fields) and count($audit_fields) > 0) {
			foreach ($audit_fields as $field=>$properties) {

				if (!empty($bean->fetched_row) && array_key_exists($field, $bean->fetched_row)) {

					$before_value=$bean->fetched_row[$field];
					$after_value=$bean->$field;
					if (isset($properties['type']))
						$field_type=$properties['type'];
					else {
						if (isset($properties['dbType']))
							$field_type=$properties['dbType'];
						else if(isset($properties['data_type']))
							$field_type=$properties['data_type'];
						else
							$field_type=$properties['dbtype'];
					}
					
					//Because of bug #25078(sqlserver haven't 'date' type, trim extra "00:00:00" when insert into *_cstm table). so when we read the audit datetime field from sqlserver, we have to replace the extra "00:00:00" again.
					if(!empty($field_type) && $field_type == 'date'){
						$before_value = from_db_convert($before_value , $field_type);
					}
					
					//if the type and values match, do nothing.
					if (!(emptyValue($before_value,$field_type) && emptyValue($after_value,$field_type))) {
						if (trim($before_value) !== trim($after_value)) {
							if (!(isTypeNumber($field_type) && (trim($before_value)+0) == (trim($after_value)+0))) {
								if (!(isTypeBoolean($field_type) && (getBooleanValue($before_value)== getBooleanValue($after_value)))) {
									$changed_values[$field]=array('field_name'=>$field,
										'data_type'=>$field_type,
										'before'=>$before_value,
										'after'=>$after_value);
								}
							}
						}
					}
				}
			}
		}
		return $changed_values;
	}

    /**
     * Function returns true is full-text indexing is available in the connected database.
     *
     * Default value is false.
     *
     * @param  string $dbname
     * @return bool
     */
	abstract protected function full_text_indexing_enabled(
        $dbname = null
        );

    /**
     * Quotes a string for storing in the database
     *
     * Return value will be surrounded by quotes
     *
     * @param  string $string
     * @return string
     */
    public function quote(
        $string
        )
    {
        return "'".$this->db->quote($string)."'";
    }

    /**
     * Quotes a string for storing in the database
     *
     * Return value will be not surrounded by quotes
     *
     * @param  string $string
     * @return string
     */
    public function escape_quote(
        $string
        )
    {
        return $this->db->quote($string);
    }

    /**
     * Returns definitions of all indies for passed table.
     *
     * return will is a multi-dimensional array that
     * categorizes the index definition by types, unique, primary and index.
     * <code>
     * <?php
     * array(
     *       'index1'=> array (
     *           'name'   => 'index1',
     *           'type'   => 'primary',
     *           'fields' => array('field1','field2')
     *           )
     *       )
     * ?>
     * </code>
     * This format is similar to how indicies are defined in vardef file.
     *
     * @param  string $tablename
     * @return array
     */
    abstract public function get_indices(
        $tablename
        );

    /**
     * Returns definitions of all indies for passed table.
     *
     * return will is a multi-dimensional array that
     * categorizes the index definition by types, unique, primary and index.
     * <code>
     * <?php
     * array(
     *       'field1'=> array (
     *           'name'   => 'field1',
     *           'type'   => 'varchar',
     *           'len' => '200'
     *           )
     *       )
     * ?>
     * </code>
     * This format is similar to how indicies are defined in vardef file.
     *
     * @param  string $tablename
     * @return array
     */
    abstract public function get_columns(
        $tablename
        );

    /**
     * Generates alter constraint statement given a table name and vardef definition.
     *
     * Supports both adding and droping a constraint.
     *
     * @param  string $table     tablename
     * @param  array  $defintion field definition
     * @param  bool   $drop      true if we are dropping the constraint, false if we are adding it
     * @return string SQL statement
     */
    abstract public function add_drop_constraint(
        $table,
        $definition,
        $drop = false);

    /**
     * Renames an index definition
     *
     * @param  array  $old_definition
     * @param  array  $new_definition
     * @param  string $tablename
     * @return string SQL statement
     */
    public function rename_index(
        $old_definition,
        $new_definition,
        $table_name
        )
    {
        $ret_commands   = array();
        $ret_commands[] = $this->add_drop_constraint($table_name,$old_definition,true);
        $ret_commands[] = $this->add_drop_constraint($table_name,$new_definition);

        return $ret_commands;
    }

    /**
     * Returns the number of columns in a table
     *
     * @param  string $table_name
     * @return int
     */
    abstract public function number_of_columns(
        $table_name
        );
}
?>
