*
  • $obj_conexion : El objeto de conexión con el que trabaja.
  • *
  • $obj_errorConexion : Referencia al objeto de error global
  • * * * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License v.2 * @version $Id: IgepConexion.php,v 1.91 2018-08-11 12:48:44 belenguer_jor Exp $ * @author David Pascual * @author Keka Bermejo * @author Vero Navarro * @author Raquel Borjabad * @author Toni Felix * @author Jorge Belenguer * * @package gvHidra */ class IgepConexion { const DEFAULT_DRIVER = 'mdb2'; /** * Objeto de conexión a la BD. * @var PDO|MDB2_Driver_Common */ var $obj_conexion; /** * variable de error * @var IgepError */ var $obj_errorConexion; /** * Matriz con la descripción del dsn al que se conecta. * @var array */ var $v_dsn; /** * Indica si se quiere utilizar conexion permanente. Solo disponible para SGBD Postgresql. * @var boolean */ private $persistent; /** * Contiene la información del driver de conexión a utilizar. Por defecto es mdb2 * @var string */ private $driver = self::DEFAULT_DRIVER; /** * Constructor. Recibe como parámetro un array con la definición * del dsn. * * @param array $dsn * @param bool $persistent (Opcional) Por omisión, es false. */ public function __construct($dsn, $persistent=false) { //Cogemos la referencia de la variable de error global global $g_error; $this->obj_errorConexion = & $g_error; //Realizamos la conexión $this->driver = self::DEFAULT_DRIVER; if( isset($dsn['driver']) && $dsn['driver']=='pdo' ) { $this->driver = 'pdo'; } unset( $dsn['driver'] ); $this->v_dsn = $dsn; $this->persistent = $persistent; $this->obj_conexion = null; } //Fin del constructor public function __sleep() { $this->obj_conexion = null; return 0; } //Fin de __sleep public function __wakeUp() { $this->obj_conexion = null; return 0; } //Fin de __wakeUp //destructor public function __destruct() { $desconectar = ( $this->driver=='pdo' ? 'desconectarPDO' : 'desconectarMDB2'); $this->$desconectar(); } //Fin de __destruct /** * Conectar */ public function conectar() { $dsn = $this->v_dsn; //Si ya hay conexión existente, salimos. if( !is_null($this->obj_conexion) ) { return; } //No hay conexion, conectamos $conectar = ( $this->driver=='pdo' ? 'conectarPDO' : 'conectarMDB2'); $this->obj_conexion = $this->$conectar( $dsn ); } //Fin de conectar /** * Realiza la conexión a la base de datos especificada por MDB2 * * @param array $dsn * @return MDB2_Driver_Common */ private function conectarMDB2($dsn) { // truco para que el autoload cargue la clase, y asi esten definidas las constantes if (!class_exists('MDB2')) throw new gvHidraException('No existe la clase MDB2'); IgepDB::preConexion($dsn); $options = array( 'portability' => MDB2_PORTABILITY_NONE, ); //Para evitar problemas con el funcionamiento del MDB2, forzamos a que se utilice una //nueva conexion siempre. La informacion esta en el bug Pear::MDB2 (Bug #17198) //Forzamos a que siempre utilice una nueva conexion if(!$this->isPersistent()) $dsn['new_link'] = true; $res = MDB2::connect($dsn,$options); if (PEAR::isError($res)) { $this->obj_errorConexion->setError( 'IGEP-6', basename(__FILE__), __FUNCTION__, $res ); } else { IgepDB::postConexion($dsn,$res); //Finalmente marcamos el fetchMode por defecto $res->setFetchMode(MDB2_FETCHMODE_ASSOC); } return $res; } //Fin de conectarMDB2 /** * Realiza la conexión a la base de datos especificada por PDO * * @return PDO */ private function conectarPDO($dsn) { IgepDB::preConexion($dsn); if(!$this->isPersistent()) { $dsn['new_link'] = true; } if (!defined('PDO::MYSQL_ATTR_FOUND_ROWS')) { $options = array( PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC , //Finalmente marcamos el fetchMode por defecto // REDMINE #23346 //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING , ); } else { $options = array( PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC , //Finalmente marcamos el fetchMode por defecto // REDMINE #23346 //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION , PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING , PDO::MYSQL_ATTR_FOUND_ROWS => true //FIX error concurrencia. Cambiamos el comportamiento de MyQL para que en un update se devuelva el número de filas MATCHEADAS con WHERE y no el número de actualizadas ); } $user = $dsn['username']; $password = $dsn['password']; $phptype = $dsn['phptype']; if( $phptype == 'informix' ) { $database = $dsn['database']; $host = $dsn['hostspec']; if (isset($dsn['port'])) { $cadenaPort = "; service={$dsn['port']}"; } // TODO : Revisar si para Informix se debe usar 'client_locale=es_es.8859-1' ya que ahora se soporta/recomienda UTF8. $dsn_string = "{$phptype}:host={$host}{$cadenaPort};database={$database};server={$host};client_locale=es_es.8859-1;protocol=onsoctcp;"; } else { $dsn_string = self::buildConnectionStringPDO($dsn); } try { $res = new PDO( $dsn_string, $user, $password, $options ); } catch (PDOException $e) { error_log( "Error en conexion: ".$dsn_string.$e->getMessage().'. ['.print_r($res, true).']'); $this->obj_errorConexion->setError('IGEP-6', basename(__FILE__), __FUNCTION__, $res); } IgepDB::postConexion($dsn, $res); return $res; } //Fin de conectar /** * Realiza la desconexión a la base de datos a la que actualmente * se está conectado mediante MDB2. */ private function desconectarMDB2() { if( !isset($this->obj_conexion) || !is_object($this->obj_conexion) || !method_exists($this->obj_conexion, 'rollback') ) { return; } $this->obj_conexion->rollback(); //Si no es persistente, desconectamos. if( !$this->isPersistent() ) { $res = $this->obj_conexion->disconnect(); if( PEAR::isError($res) ) { $this->obj_errorConexion->setError( 'IGEP-7', basename(__FILE__), __FUNCTION__, $res ); } $this->obj_conexion = null; } } //Fin de desconectarMDB2 /** * Realiza la desconexión a la base de datos a la que actualmente * se está conectado mediante PDO. */ private function desconectarPDO() { if( !isset($this->obj_conexion) || !is_object($this->obj_conexion) || !method_exists($this->obj_conexion, 'rollBack') ) { return; } try { $this->obj_conexion->rollBack(); } catch( Exception $e ) { // NOP : en algunas ocasiones lanza excepción al no haber transacción activa } //Si no es persistente desconectamos if( !$this->isPersistent() ) { unset( $this->obj_conexion ); } } //Fin de desconectarPDO /** * Devuelve el objeto conexión al que se está conectado. * * @return IgepConexion */ public function getConnection() { $this->conectar(); return $this->obj_conexion; } //Fin de getConnection /** * Devuelve el objeto conexión al que se está conectado. * * @deprecated 3.2 - 21/01/2010 * @return IgepConexion */ /* public function getPEARConnection() { IgepDebug::setDebug( WARNING, 'DEPRECATED ' . __METHOD__ . '. Usar IgepConexion::getConnection '. '
    En version 4.4 se borrará.
    ' ); return $this->getConnection(); }*/ //Fin de getPEARConnection /** * Devuelve el dsn de la conexión. * * @return array */ public function getDSN() { return $this->v_dsn; } //Fin de getDSN /** * Indica si una conexion se ha establecido con el atributo persistente(reusable) * * @return boolean */ public function isPersistent() { return $this->persistent; } //Fin de isPersistent /** * Empieza una transacción (BEGIN) en la conexión a la que * está apuntando. */ public function empezarTransaccion() { //Si no hay conexion, conectamos $this->conectar(); //Debug:Indicamos que ejecutamos la consulta IgepDebug::setDebug( DEBUG_IGEP, 'Empezamos transacción.' ); $res = IgepDB::empezarTransaccion( $this->v_dsn, $this->obj_conexion ); if( ($this->driver=='mdb2'&& MDB2::isError($res)) || ($this->driver=='pdo' && $res===FALSE) ) { $this->obj_errorConexion->setError( 'IGEP-8', basename(__FILE__), __FUNCTION__, $res ); } } //Fin de empezarTransaccion /** * Finaliza una transacción (COMMIT o ROLLBACK) en la conexión a la que * está apuntando. Recibe un parámetro que indica si el procesado de las * diferentes operaciones que se han realizado ha concluido satisfactoriamente * o no. Dependiendo de ello se realizará el COMMIT o el ROLLBACK. Dicho * parámetro es $error *
      *
    • 0 : No ha habido ningún error en el proceso. Realizamos COMMIT.
    • *
    • 1 : Ha habido algún error durante el proceso. Realizamos ROLLBACK.
    • *
    * * @param integer $error */ public function acabarTransaccion($error) { //Si no hay conexion, conectamos $this->conectar(); //Debug:Indicamos que ejecutamos la consulta if( $error ) { IgepDebug::setDebug( DEBUG_IGEP, 'Acabamos transacción con ROLLBACK' ); } else { IgepDebug::setDebug( DEBUG_IGEP, 'Acabamos transacción con COMMIT' ); } $res = IgepDB::acabarTransaccion( $this->v_dsn, $this->obj_conexion, $error ); if( ($this->driver=='mdb2' && MDB2::isError($res) ) || ($this->driver=='pdo' && $res===FALSE) ) { $this->obj_errorConexion->setError( 'IGEP-9', basename(__FILE__), __FUNCTION__, $res ); } } //Fin de acabarTransaccion /** * Método encargado de construir las WHERE de las consultas. * * @param array $v_datos * @param string $str_where * @return string */ public function construirWhere( $v_datos, $str_where ) { if( !isset($v_datos) || (is_array($v_datos) && (count($v_datos)==0)) ) { return $str_where; } //Esta función construye una WHERE igualando los valores con los nombres de los campos. $str_condicion=''; foreach( $v_datos as $property => $value ) { $connector = (empty($str_condicion) ? '' : ' AND '); if( $value == '' ) { $operator = 'is'; $value = 'null'; } else { $operator = '='; $value = (gettype($value)=='string' ? "'{$value}'" : $value); } $expr = "{$connector}{$property} {$operator} {$value}"; $str_condicion .= "{$expr}"; }//FIN foreach if( trim($str_condicion) != '' ) { $inicio_condicion = (empty($str_where)? '' : 'OR'); $str_where .= "{$inicio_condicion}({$str_condicion})"; } return $str_where; } //Fin de construirWhere /** * Método encargado de construir las WHERE de las consultas incluyendo las condiciones de busqueda que ha seleccionado * el programador a partir del parámetro tipoConsulta. * * Este es un metodo alternativo a construirWhereBusqueda, donde solo se descartan caracteres especiales y mayusculas en tipo 3 * Llamado desde gvHidraForm_DB->prepareDataSource. * * Dependiendo del valor de la variable realiza una construcción de la Where u otra. Los tipos son: *
      *
    • 0 = Se contruye igualando los campos a los valores.
    • *
    • 1 = Se construye con like y comodines para cada campo.
    • *
    • 2 = Por defecto, se contruye con like sólo si el usuario ha especificado comodines.
    • *
    • 3 = Se construye con like, sin distinguir mayúsculas ni caracteres especiales.
    • *
    * * @see gvHidraForm_DB::prepareDataSource() * * @param array $v_datos Matriz con los datos. * @param string $str_where Cadena SQL del where * @param array|integer $tipoConsulta Valores: [ 0, 1, 2, 3 ] * @return string */ function construirWhereConLike( $v_datos, $str_where, $tipoConsulta ) { if( !isset($v_datos) || (is_array($v_datos) && (count($v_datos)==0)) ) { return $str_where; } if( $tipoConsulta == 0 ) { return $this->construirWhere( $v_datos, $str_where ); } // hay where previa cuando la consulta se hace sobre varios registros: se concatenan los criterios // en el mismo registro con and, y los de cada registro con or $str_condicion=''; $dsn = $this->getDsn(); foreach( $v_datos as $property => $value ) { if( $value == '' ) { continue; } $connector = (empty($str_condicion) ? '' : ' AND '); switch( $tipoConsulta ) { case 2 : //Son 3 iguales porque si colocamos 2 no distinguimos entre algo que esté en la posición 0 y el false. if( (strpos($value,'%')===false) && (strpos($value,'_')===false) ) { $operator = '='; $value = (gettype($value)=='string' ? "'{$value}'" : $value); } else { $property = IgepDB::toTextForVS( $dsn, $property ); $operator = 'LIKE'; $value = "'{$value}'"; } break; case 3: $property = 'lower(' . IgepDB::toTextForVS($dsn, $property) . ')'; $operator = 'LIKE'; $value = IgepDB::unDiacritic( $dsn, "lower('%{$value}%')" ); break; case 0 : if( $value == '' ) { $operator = 'is'; $value = 'null'; } else { $operator = '='; $value = (gettype($value)=='string' ? "'{$value}'" : $value); } break; case 1 : default: $property = IgepDB::toTextForVS( $dsn, $property ); $operator = 'LIKE'; $value = "'%{$value}%'"; break; } $expr = "{$property} {$operator} {$value}"; $str_condicion .= "{$connector}{$expr}"; }//FIN foreach if( trim($str_condicion) != '' ) { $inicio_condicion = (empty($str_where)? '' : 'OR'); $str_where .= "{$inicio_condicion}({$str_condicion})"; } return $str_where; } //Fin de construirWhereConLike /** * Construye las WHERE de las consultas incluyendo las condiciones de busqueda que ha seleccionado * el programador a partir del parámetro tipoConsulta. * * Este es un metodo alternativo a construirWhereConLike, donde siempre se descartan caracteres especiales y mayusculas * Llamado desde gvHidraForm_DB->prepareDataSource * * Dependiendo del valor de la variable realiza una construcción de la Where u otra. Los tipos son: *
      *
    • 0 = Se contruye igualando los campos a los valores.
    • *
    • 1 = Se construye con like y comodines para cada campo.
    • *
    • 2 = Por defecto, se contruye con like sólo si el usuario ha especificado comodines.
    • *
    • 3 = Coincide con tipo 1.
    • *
    * * @see gvHidraForm_DB::prepareDataSource() * * @param array $v_datos Matriz con los datos. * @param boolean[] $undiacritic * @param string $str_where Cadena SQL del where. * @param array|integer $tipoConsulta Valores: [ 0, 1, 2, 3 ] * @param mixed $queryModeParams Parámetros a usar para el queryMode. * @return string */ public function construirWhereBusqueda( $v_datos, $undiacritic, $str_where, $tipoConsulta, $queryModeParams = null) { if( !isset($v_datos) || (is_array($v_datos) && (count($v_datos)==0)) ) { return $str_where; } //Construimos la Where // hay where previa cuando la consulta se hace sobre varios registros: se concatenan los criterios // en el mismo registro con and, y los de cada registro con or $str_condicion=''; foreach( $v_datos as $property => $values ) { if( $values == '' ) { continue; } if( !is_array($values) ) { $values = array ($values); } $queryMode = is_array($tipoConsulta) ? $tipoConsulta[$property] : $tipoConsulta; $queryModeParam = is_array($queryModeParams) ? $queryModeParams[$property] : $queryModeParams; $expr = ''; $exprValues = array(); foreach ($values as $value) { if ($value == '' or is_null($value)) { continue; } $exprValues[] = $this->formatCondition( $property, $value, $queryMode, $undiacritic[$property], false, $queryModeParam); } if (!empty ($exprValues)) { $expr = implode( ' OR ', $exprValues ); if (is_array($exprValues) && (count ($exprValues) > 1) ) { $expr = "( {$expr} )"; } } $connector = (empty($str_condicion) ? '' : ' AND '); $str_condicion .= "{$connector}{$expr}"; }//FIN foreach if( trim($str_condicion) != '' ) { $inicio_condicion = (empty($str_where)? '' : ' OR '); $str_where .= "{$inicio_condicion}({$str_condicion})"; } return $str_where; } //Fin de construirWhereBusqueda /** * Para formar condiciones de búsqueda. * Actua como en las ventanas de selección (donde no se tienen en cuenta mayúsculas * ni caracteres especiales). * * @param string $column Nombre de la columna. * @param string $value Valor a filtrar en la columna. * @param integer $tipo (Opcional) Igual que el queryMode: [0..2]. Por omisión, es 1. * @param boolean $prepareChar (Opcional) Indica si hay que transformar el valor a bd. Por omisión, es true. * @param mixed $queryModeParams (Opcional) Parámetros a usar para el queryMode. * @return string */ public function unDiacriticCondition( $column, $value, $tipo=1, $prepareChar=true, $queryModeParams = null ) { return $this->formatCondition( $column, $value, $tipo, true, $prepareChar, $queryModeParams ); } //Fin de unDiacriticCondition /** * Para formar condiciones de busqueda con tipos de datos diferentes a string. * * @param string $column Nombre de la columna. * @param string $value Valor a filtrar en la columna. * @param integer $tipo (Opcional) [0..2], igual que el queryMode. Por omisión, es 1. * @param mixed $queryModeParams (Opcional) Parámetros a usar para el queryMode. * @return string */ public function normalCondition( $column, $value, $tipo=1, $queryModeParams = null) { return $this->formatCondition( $column, $value, $tipo, false, false, $queryModeParams ); } //Fin de normalCondition /** * Para formar condiciones de búsqueda. * * @param string $column Nombre de la columna. * @param string $value Valor a filtrar en la columna. * @param integer $tipo (Opcional) Igual que el queryMode: [0..2]. Por omisión, es 1. * @param boolean $undiacritic (Opcional) Ignorar, o no, acentos y mayúsculas. Por omisión, es true. * @param boolean $prepareChar (Opcional) Indica si hay que transformar el valor a bd. Por omisión, es true. * @param mixed $queryModeParams (Opcional) Parámetros a usar para el queryMode. * @return string */ public function formatCondition( $column, $value, $tipo=1, $undiacritic=false, $prepareChar=true, $queryModeParams = null) { $dsn = $this->getDSN(); if ($undiacritic) { $strtolower = (function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'); $value = $strtolower( $value, 'UTF-8' ); } if( $prepareChar ) { $this->prepararOperacion( $value, TIPO_CARACTER ); } $property = IgepDB::toTextForVS( $dsn, $column ); if ($undiacritic) { $property = IgepDB::unDiacritic( $dsn, "lower ({$property})" ); } $val_nolike = "'{$value}'"; if ($undiacritic) { $val_nolike = IgepDB::unDiacritic( $dsn, $val_nolike ); } $sqlEspecial = ''; $digitoEspecial = floor($tipo / 10); switch( $digitoEspecial ) { case '1' : $umbral = $queryModeParams; $sqlEspecial = "\n OR " . IgepDB::obtenerSimilarity( $dsn, $property, "'{$value}'" ) . " >= {$umbral}"; break; } switch( $tipo % 10 ) { case 0: $operator = '='; $value = $val_nolike; break; case 1: $operator = 'like'; $value = "'%{$value}%'"; if ($undiacritic) { $value = IgepDB::unDiacritic( $dsn, $value ); } break; case 2: $operator = 'like'; if( strpos($value,'%')===false && strpos($value,'_') === false ) { $operator = '='; } $value = $val_nolike; break; default: //if ($digitoEspecial == 0) { throw new gvHidraException( __METHOD__ . " : valor de tipo no soportado: {$tipo}" ); //} break; } $expresion = "{$property} {$operator} {$value}"; if (!empty ($sqlEspecial)) { $expresion = "($expresion {$sqlEspecial})"; } return $expresion; } //Fin de formatCondition /** * Método encargado de, dadas una serie de cadenas, componerlas para crear una única * cadena para la where de una SQL. * * @param array $v_cadenas Array que contiene las diferentes cadenas que componen la WHERE. * @return string */ public function combinarWhere( $v_cadenas ) { // Eliminamos las cadenas vacías (o con solo espacios). $v_cadenas = array_filter( $v_cadenas, function($value) { $value = trim($value); return !empty($value); } ); // Si no quedan cadenas, salimos. if( empty($v_cadenas) ) { return null; } // Parentizamos las cadenas de filtro. $v_cadenas = array_map( function($cadena){ return '( '.trim($cadena).' )'; }, $v_cadenas ); // Concatemos con 'AND' las cadenas de filtro, y devolvemos el WHERE $str_where = "\nWHERE " . implode( "\n\tAND ", $v_cadenas ); return $str_where; } //Fin de combinarWhere /** * Método encargado de construir el limit para las consultas * * @param string $str_where * @param int $int_limiteConsulta (Opcional) Por omisión, es 100. * @return string */ public function construirLimite( & $str_where, $int_limiteConsulta=100 ) { $limite = ''; if( $int_limiteConsulta != -1 && is_int($int_limiteConsulta) ) { $limite = IgepDB::obtenerLimit( $str_where, $this->v_dsn, $int_limiteConsulta ); } return $limite; } //Fin de construirLimite /** * Dada una una consulta realiza la SELECT correspondiente. * Si se indica el parametro typeDesc cambiará los datos de origen al formato FW. * * @param string $str_select Cadena con la query SQL. * @param array $typeDesc (Opcional) Descripcion de los campos para posibles transformaciones. Por omisión, es null. * @param string $debug Cadena con la que indicaremos si se quiere o no mostrar en el debug la consulta. * @return array */ // REDMINE #23299 public function consultar( $str_select, $typeDesc=NULL, $debug = NULL ) { //Si no hay conexion, conectamos $this->conectar(); //Debug:Indicamos que ejecutamos la consulta if ($debug === NULL) { IgepDebug::setDebug( DEBUG_IGEP, __CLASS__ . ": Ejecutamos consulta:
    {$str_select}" ); } elseif (!empty($debug)) { IgepDebug::setDebug( DEBUG_IGEP, __CLASS__ . ": Ejecutamos consulta:
    {$debug}" ); } if( $this->obj_errorConexion->hayError() ) { return null; } $resc = $this->obj_conexion->query( $str_select ); if( ($this->driver=='mdb2' && MDB2::isError($resc) ) || ($this->driver=='pdo' && $resc===FALSE) ) { $msg = $str_select; // REDMINE #23277 if (!empty($debug)) { $msg = $debug; } $this->obj_errorConexion->setError( 'IGEP-5', basename(__FILE__), __FUNCTION__, $resc, $msg ); return -1; } $res = $resc->fetchAll(); $this->transformResultSet( $res, $typeDesc ); return $res; } //Fin de consultar /** * Dada una consulta realiza la SELECT correspondiente pero bloqueando las filas para modificacion * * @param string $str_select * @param array $typeDesc (Opcional) Descripcion de los campos para posibles transformaciones. Por omisión, es null. * @return array */ public function consultarForUpdate( $str_select, $typeDesc=NULL ) { //Si no hay conexion, conectamos $this->conectar(); IgepDebug::setDebug( DEBUG_IGEP, __CLASS__ . ': Ejecutamos ' . __FUNCTION__ . ": {$str_select}" ); $tran = $this->obj_conexion->inTransaction(); // devuelve null si no hemos abierto la transaccion con mdb2 if( !is_null($tran) && !$tran ) { throw new gvHidraNotInTransException( 'Para llamar a consultarForUpdate hay que estar en una transaccion' ); } $str_select .= ' ' . IgepDB::obtenerBloqueo($this->getDSN()); $res = $this->consultar( $str_select, $typeDesc ); if( $res == -1 ) { // ver si error de bloqueado o timeout $cod = $this->obj_errorConexion->getDescErrorDB(); if( IgepDB::isLocked($this->getDSN(), $cod[0]) ) { $msg = __CLASS__ .': no se puede bloquear los registros solicitados'; IgepDebug::setDebug( DEBUG_IGEP, $msg ); throw new gvHidraLockException( $msg, 4, null, $this->obj_errorConexion->obj_dbError ); } } return $res; } //Fin de consultarForUpdate /** * Ejecución de consulta preparada * Si se usan placeholders con nombre, en los parametros pasaremos array asociativo. * * @param string $str_select * @param boolean $dml_ddl False para select, true para el resto. * @param array $params * @param array $typeDesc (Opcional) Descripcion de los campos para posibles transformaciones. Por omisión, es null. * @param PDOStatement $cur (Opcional) Por omisión, es null. * @return int|array Si dml_ddl, devuelve número de registros afectados; sino devuelve vector. * Excepciones: *
      *
    • 1 : error en prepare.
    • *
    • 2 : error en execute.
    • *
    • 3 : error en fetchAll.
    • *
    */ public function preparedQuery( $str_select, $dml_ddl, $params, $typeDesc=null, & $cur=null ) { //Si no hay conexion, conectamos $this->conectar(); if( empty($cur) ) { IgepDebug::setDebug( DEBUG_IGEP, "Preparando sql: $str_select con dml_ddl: ".($dml_ddl? 'true': 'false')); $db_pear = $this->obj_conexion; $in = null; // por defecto todo text $out = ( $dml_ddl ? MDB2_PREPARE_MANIP : null); $cur = $db_pear->prepare( $str_select, $in, $out ); if( PEAR::isError($cur) ) { $this->obj_errorConexion->setError( 'IGEP-5', basename(__FILE__), __FUNCTION__, $cur, $str_select ); throw new gvHidraPrepareException( 'Error: '.$cur->getMessage()." - Preparando sql: $str_select", 1, null, $cur ); } } IgepDebug::setDebug( DEBUG_IGEP, "Ejecutando sql: {$str_select}
    * parametros: ".var_export($params,true) ); $res = $cur->execute( $params ); if( PEAR::isError($res) ) { $this->obj_errorConexion->setError( 'IGEP-5', basename(__FILE__), __FUNCTION__, $res, $str_select.' y parámetros de entrada '.var_export($params,true) ); throw new gvHidraExecuteException('Error: '.$res->getMessage()." - Ejecutando sql ($str_select) con parámetros: ".var_export($params,true), 2, null, $res ); } if( $dml_ddl ) { return $res; } $vec = $res->fetchAll( MDB2_FETCHMODE_ASSOC ); if( PEAR::isError($vec) ) { $this->obj_errorConexion->setError( 'IGEP-5', basename(__FILE__), __FUNCTION__, $vec, $str_select ); throw new gvHidraFetchException( 'Error: '.$vec->getMessage()." - Ejecutando recuperación de registros", 3, null, $vec); } $this->transformResultSet( $vec, $typeDesc ); return $vec; } //Fin de preparedQuery /** * Dada una consulta realiza la SELECT correspondiente pero bloqueando las filas para modificacion * y usando sentencias preparadas. * * @param string $str_select * @param array $params * @param array $typeDesc (Opcional) Descripcion de los campos para posibles transformaciones. Por omisión, es null. * @param PDOStatement $cur (Opcional) Por omisión, es null. * @return int|array Devuelve vector. Excepciones: *
      *
    • 4 : hay bloqueo
    • *
    • resto : las propias de preparedQuery.
    • *
    */ public function preparedQueryForUpdate( $str_select, $params, $typeDesc=null, & $cur=null ) { //Si no hay conexion, conectamos $this->conectar(); IgepDebug::setDebug( DEBUG_IGEP, __CLASS__ . ': Ejecutamos ' . __FUNCTION__ . ": {$str_select}" ); $tran = $this->obj_conexion->inTransaction(); // devuelve null si no hemos abierto la transaccion con mdb2 if( !is_null($tran) && !$tran ) { throw new gvHidraNotInTransException( 'Para llamar a preparedQueryForUpdate hay que estar en una transaccion' ); } $str_select .= ' '.IgepDB::obtenerBloqueo( $this->getDSN() ); try { $res = $this->preparedQuery( $str_select, false, $params, $typeDesc, $cur ); } catch( gvHidraExecuteException $e ) { // ver si error de bloqueado o timeout $cod = $this->obj_errorConexion->getDescErrorDB(); if (IgepDB::isLocked( $this->getDSN(), $cod[0]) ) { throw new gvHidraLockException( __CLASS__ . ': no se puede bloquear los registros solicitados', 4, null, $e->getSqlerror() ); } throw $e; } return $res; } //Fin de preparedQueryForUpdate /** * Transforma los datos obtenidos por consultar y preparedQuery. * * @param array $res * @param array $typeDesc Descripcion de los campos para posibles transformaciones. */ public function transformResultSet( & $res, $typeDesc ) { if( empty($typeDesc) ) { return; } if( array_key_exists('DATATYPES',$typeDesc) ) { // convertimos los tipos a la estructura usada en this->v_descCamposPanel si es necesario $datatypes = array(); foreach( $typeDesc['DATATYPES'] as $clave => $valor ) { if( is_array($valor) and array_key_exists('tipo',$valor) ) { $datatypes[$clave] = $valor; } else { $datatypes[$clave] = array( 'tipo' => $valor ); } } } else { $datatypes = null; } $this->transform_BD2FW( $res, $datatypes ); } //Fin de transformResultSet /** * Dada una una consulta realiza la SELECT correspondiente. * * @param string $str_select * @return integer */ public function operar( $str_operar ) { //Si no hay conexion, conectamos $this->conectar(); //Debug:Indicamos que ejecutamos la operacion IgepDebug::setDebug( DEBUG_IGEP, "IgepConexion: Ejecutamos operación:
    {$str_operar}" ); if( $this->obj_errorConexion->hayError() ) { return null; } $res = $this->obj_conexion->exec( $str_operar ); if( ($this->driver=='mdb2' && MDB2::isError($res)) || ($this->driver=='pdo' && $res===FALSE) ) { $this->obj_errorConexion->setError( 'IGEP-11', basename(__FILE__), __FUNCTION__, $res, $str_operar ); return -1; } return $res; } //Fin de operar /** * Convierte de FW a DB * Este método debe ser invocado por el usuario antes de realizar cualquier consulta/operacion a la BD * para garantizar la portabilidad. * * @param array $a_parametros * @param mixed $a_tipo (Opcional) Por omisión, es TIPO_CARACTER. */ public function prepararOperacion( & $a_parametros, $a_tipo=TIPO_CARACTER ) { //Si no hay conexion, conectamos $this->conectar(); $transformer = new IgepTransformer(); $carfw = ConfigFramework::getNumericSeparatorsFW(); $backslash = IgepDB::backSlashScape( $this->getDsn() ); $carbd = IgepDB::caracteresNumericos( $this->getDsn() ); $transformer->setDecimal( $carfw['DECIMAL'], $carbd['DECIMAL'], $carfw['GROUP'], $carbd['GROUP'] ); //Cogemos la mascara a la que tenemos que transformar el timestamp $fechafw = ConfigFramework::getDateMaskFW(); $fechabd = IgepDB::mascaraFechas( $this->getDsn() ); $transformer->setDate( $fechafw, $fechabd ); $transformer->setCharacter( "'", "''" ); $transformer->setCharacter( "\\", $backslash ); $vector = true; if( !is_array($a_parametros) && $a_parametros!='' ) { //si el tipo es nulo le ponemos TIPO_CARACTER if( empty($a_tipo) ) { $a_tipo = TIPO_CARACTER; } // le doy estructura de vector para no repetir el codigo $vector = false; $a_parametros = array( array( 'col' => $a_parametros , ) , ); $a_tipo = array( 'col' => array( 'tipo' => $a_tipo , ) , ); } if (is_array($a_tipo)) { foreach( $a_parametros as $fila => $tupla ) { foreach( $tupla as $campo => $notUsed ) { if( empty($a_parametros[$fila][$campo]) ) { continue; } $tipo_efectivo = (empty($a_tipo[$campo]['tipo']) ? TIPO_CARACTER : ($a_tipo[$campo]['tipo']==TIPO_ENTERO ? TIPO_DECIMAL : $a_tipo[$campo]['tipo']) ); if ($tipo_efectivo == TIPO_DECIMAL) { $tupla[$campo] = $transformer->expandExponent( $tupla[$campo], $carfw['DECIMAL'], $carfw['GROUP'] ); } if( ($tipo_efectivo == TIPO_FECHA or $tipo_efectivo == TIPO_FECHAHORA) && is_object($tupla[$campo]) ) { $a_parametros[$fila][$campo] = $tupla[$campo]->format( $fechabd.($tipo_efectivo==TIPO_FECHAHORA ? ' H:i:s' : '') ); } else { $a_parametros[$fila][$campo] = $transformer->process($tipo_efectivo, $tupla[$campo] ); } } } } if( !$vector ) { $a_parametros = $a_parametros[0]['col']; } }//Fin de prepararOperacion /** * Convierte datos de DB a FW * No valida los datos en origen, por lo que de momento si en origen tenemos una fecha con hora, * en el destino estaría sin hora. * * @param array $a_parametros * @param string $a_tipo (Opcional) Por omisión, es TIPO_CARACTER. */ public function transform_BD2FW( & $a_parametros, $a_tipo=TIPO_CARACTER ) { $transformer = new IgepTransformer(); $carbd = IgepDB::caracteresNumericos( $this->getDsn() ); $carfw = ConfigFramework::getNumericSeparatorsFW(); $transformer->setDecimal( $carbd['DECIMAL'], $carfw['DECIMAL'], $carbd['GROUP'], $carfw['GROUP'] ); $fechabd = IgepDB::mascaraFechas( $this->getDsn() ); $fechafw = ConfigFramework::getDateMaskFW(); $transformer->setDate( $fechabd, $fechafw ); $vector = true; if( !is_array($a_parametros) ) { // le doy estructura de vector para no repetir el codigo $vector = false; $a_parametros = array( array( 'col' => $a_parametros , ) , ); $a_tipo = array( 'col' => array( 'tipo' => $a_tipo , ) , ); } if( is_array($a_tipo) ) { foreach( $a_parametros as $fila => $tupla ) { foreach ($tupla as $campo => $valor) { $tipo_efectivo = (empty($a_tipo[$campo]['tipo']) ? TIPO_CARACTER : ($a_tipo[$campo]['tipo']==TIPO_ENTERO ? TIPO_DECIMAL : $a_tipo[$campo]['tipo']) ); if( empty($a_parametros[$fila][$campo]) ) { if( $tipo_efectivo==TIPO_FECHA || $tipo_efectivo==TIPO_FECHAHORA ) { $a_parametros[$fila][$campo] = null; } continue; } $a_parametros[$fila][$campo] = $transformer->process( $tipo_efectivo, $valor ); if( $tipo_efectivo==TIPO_FECHA || $tipo_efectivo==TIPO_FECHAHORA ) { $a_parametros[$fila][$campo] = new gvHidraTimestamp( $a_parametros[$fila][$campo] ); } } } } if( !$vector ) { $a_parametros = $a_parametros[0]['col']; } } //Fin de transform_BD2FW /** *

    Transforma un numero de capa negocio a capa datos.

    *

    Nota: El argumento será tratado como de tipo TIPO_DECIMAL

    * * @param float $a_num * @return string */ public function prepararNumero( $a_num ) { $this->prepararOperacion( $a_num, TIPO_DECIMAL ); return $a_num; } //Fin de prepararNumero /** *

    Transforma una fecha de capa negocio a capa datos

    *

    Nota: El argumento será tratado como de tipo TIPO_FECHAHORA

    * * @param gvHidraTimestamp $a_fecha * @return string */ public function prepararFecha( $a_fecha ) { $this->prepararOperacion( $a_fecha, TIPO_FECHAHORA ); return $a_fecha; } //Fin de prepararFecha /** * Convierte de DB a User. * Si estamos convirtiendo un numero a decimal podemos indicar el numero de decimales. * * @param array $a_parametros * @param string $a_tipo * @param array $a_dsn * @param integer $a_decimales (Opcional) Por omisión, es 2. * @return array */ public static function transform_BD2User( $a_parametros, $a_tipo, $a_dsn, $a_decimales=2 ) { $vector = true; if( !is_array($a_parametros) && $a_parametros!='' && $a_tipo!='' ) { // le doy estructura de vector para no repetir el codigo $vector = false; $a_parametros = array( array( 'col' => $a_parametros , ) , ); $a_tipo = array( 'col' => array( 'tipo' => $a_tipo , 'parteDecimal' => $a_decimales ) , ); } if( is_array($a_tipo) ) { $transformer = new IgepTransformer(); $carbd = IgepDB::caracteresNumericos( $a_dsn ); $carconf = ConfigFramework::getNumericSeparatorsUser(); $transformer->setDecimal( $carbd['DECIMAL'], $carconf['DECIMAL'], $carbd['GROUP'], $carconf['GROUP'] ); $fechabd = IgepDB::mascaraFechas( $a_dsn ); $fechaconf = ConfigFramework::getDateMaskUser(); $transformer->setDate( $fechabd, $fechaconf ); foreach( $a_parametros as $fila => $tupla ) { foreach( $a_tipo as $campo => $descTipo ) { if( isset($tupla[$campo]) ) { $tipo_efectivo = (empty($descTipo['tipo']) ? TIPO_CARACTER : $descTipo['tipo']); if( $tipo_efectivo == TIPO_DECIMAL ) { $tupla[$campo] = $transformer->expandExponent( $tupla[$campo], $carbd['DECIMAL'], $carbd['GROUP'] ); } if( $descTipo['tipo'] == TIPO_DECIMAL ) { $tupla[$campo] = $transformer->decimalPadDatos( $tupla[$campo], $descTipo['parteDecimal'], $a_dsn ); } $a_parametros[$fila][$campo] = $transformer->process( $tipo_efectivo, $tupla[$campo] ); } } } } return ($vector ? $a_parametros : $a_parametros[0]['col']); } //Fin de prepararPresentacion /** * Este método devuelve el valor de una secuencia programada en la base de datos * para la conexion actual * * @param string $nombreSecuencia Nombre de la secuencia en la BD. * @return integer */ public function calcularSecuenciaBD( $nombreSecuencia ) { //Si no hay conexion, conectamos $this->conectar(); $sql = IgepDB::obtenerSecuenciaBD( $this->getDsn(), $nombreSecuencia ); IgepDebug::setDebug( DEBUG_IGEP, "IgepConexion: Calculamos secuencia BD: {$sql}" ); $resc = $this->obj_conexion->query( $sql ); if( ($this->driver == 'mdb2' && MDB2::isError($resc)) || ($this->driver == 'pdo' && $resc===FALSE) ) { $this->obj_errorConexion->setError( 'IGEP-12', basename(__FILE__), __FUNCTION__, $resc ); return -1; } $res = $resc->fetchAll(); return $res[0]['nextval']; } //Fin de calcularSecuenciaBD /** * Este método calcula una secuencia compuesta por varios campos de la misma tabla. * * @param string $tabla Nombre de la tabla de la BD. * @param string $campoSecuencia Campo del que se quiere obtener la secuencia. * @param array $camposDependientes Matriz asociativa que contiene el nombre de los campos de los cuales va a depender la secuencia y sus valores. Estructura [nombreBD] = valor. * @param integer $valorInicial (Opcional). Fija el valor inicial que devuelve calcularSecuencia en el caso de que no exístan tuplas en la tabla el valor. Por omisión, es 1. * @return integer */ public function calcularSecuencia( $tabla, $campoSecuencia, $camposDependientes, $valorInicial=1 ) { //Si no hay conexion, conectamos $this->conectar(); $i = 0; $where = ''; if( is_array($camposDependientes) ) { foreach( $camposDependientes as $campo => $valor ) { $where .= (empty($where) ? 'WHERE ' : ' AND '); $where .= "{$campo} = '{$valor}' "; ++$i; } }; $sql = "SELECT max({$campoSecuencia}) as \"secuencia\" FROM {$tabla} {$where}"; IgepDebug::setDebug( DEBUG_IGEP, "IgepConexion: Calculamos secuencia: {$sql}" ); $resc = $this->obj_conexion->query( $sql ); if( ($this->driver == 'mdb2' && MDB2::isError($resc)) || ($this->driver == 'pdo' && $resc===FALSE) ) { $this->obj_errorConexion->setError( 'IGEP-12', basename(__FILE__), __FUNCTION__, $resc ); return -1; } $res = $resc->fetchAll(); if( $res[0]['secuencia'] == '' || is_null( $res[0]['secuencia'] ) || !isset( $res[0]['secuencia']) ) { return $valorInicial; } return ($res[0]['secuencia'] + 1); } //Fin de calcularSecuencia /** * Método getDriver * * @return string */ public function getDriver() { return $this->driver; } //Fin de getDriver /** * Construye la cadena de conexión PDO correspondiente en función del SGBD */ public static function buildConnectionStringPDO($dsn) { /* [xmltype] => oracle-rac [xmlhost] => dbdoracle01.srv.gva.es [xmlport] => 1522 [xmldb] => predesa [xmlcharset] => latin1 [phptype] => oci8 [username] => EXGASPAR [password] => xxxx [hostspec] => dbdoracle01.srv.gva.es [port] => 1522 [service] => predesa [driver] => PDO */ $host = $dsn['xmlhost']; $database = $dsn['xmldb']; $port = isset($dsn['xmlport'])?$dsn['xmlport']:''; $cadenaConexion = ''; switch(strtolower($dsn['xmltype'])) { case 'postgres': case 'pgsql': if (!extension_loaded('pdo_pgsql')) { $msg = 'El driver de conexión está configurado como PDO. Sin embargo, no está disponible la extension "pdo_pgsql". Habilítela o utilice MDB2 o NATIVE.'; error_log(__FILE__.':'.__LINE__.':'. $msg); throw new \Exception($msg); } $port = empty($port)?'5432':$port; $cadenaConexion = 'pgsql:'. 'host='.$host.';'. 'port='.$port.';'. 'dbname='.$database.';'; break; case 'mysql': case 'mysqli': if (!extension_loaded('pdo_mysql')) { $msg = 'El driver de conexión está configurado como PDO. Sin embargo, no está disponible la extension "pdo_mysql". Habilítela o utilice MDB2 o NATIVE.'; error_log(__FILE__.':'.__LINE__.':'. $msg); throw new \Exception($msg); } $port = empty($port)?'3306':$port; $cadenaConexion = 'mysql:'. 'host='.$host.';'. 'port='.$port.';'. 'dbname='.$database.';'; break; case 'sqlite' : if (!extension_loaded('pdo_sqlite')) { $msg = 'El Tipo PDO SQLite implica conexión PDO, no es compatible con MdB2. Habilite el módulo PDO "pdo_sqlite".'; error_log(__FILE__.':'.__LINE__.':'. $msg); throw new \Exception($msg); } $port = ''; $cadenaConexion = 'sqlite:'.$host; break; case 'sqlsrv': case 'mssql': $port = '1433'; break; case 'oracle': case 'oci8': case 'oci': case 'thin': case 'oracle-thin': //En Oracle 11 se puede conectar vía SERVICE, comentamos la línea siguente y dejamos ocnexión común //$cadenaConexion = "oci:dbname=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=$host)(PORT=$port))(CONNECT_DATA=(SID=$database)))"; case 'oracle-rac': case 'oci-rac': case 'rac': if (!extension_loaded('pdo_oci')) { $msg = 'El driver de conexión está configurado como PDO. Sin embargo, no está disponible la extension "pdo_oci". Habilítela o utilice MDB2 o NATIVE.'; error_log(__FILE__.':'.__LINE__.':'. $msg); throw new \Exception($msg); } $port = empty($port)?'1521':$port; $cadenaConexion ='oci:dbname='; $cadenaConexion.= '(DESCRIPTION=(LOAD_BALANCE=on)(ADDRESS=(PROTOCOL=TCP)(HOST='.$host.')(PORT='.$port.'))(CONNECT_DATA=(SERVICE_NAME='.$database.')))'; if (in_array ($dsn['xmlcharset'], array ('ISO-8859-1', 'ISO8859-1', 'iso-8859-1', 'iso8859-1', 'latin1'))) { $cadenaConexion.=';charset=WE8ISO8859P1;'; } elseif (in_array ($dsn['xmlcharset'], array ('ISO-8859-15', 'ISO8859-15', 'iso-8859-15', 'iso8859-15', 'latin9'))) { $cadenaConexion.=';charset=WE8ISO8859P15;'; } else //Si no se especifica fijamos a UTF-8 la condificación de conexión { $cadenaConexion.=';charset=AL32UTF8;'; } break; default: $msg = 'El SGBD "'.$dsn['xmltype'].'" no está soportado con PDO.'; error_log(__FILE__.':'.__LINE__.':'. $msg); throw new \Exception($msg); break; } return $cadenaConexion; }//buildConnectionStringPDO } //End IgepConexion