* @author Alejadro Borges * @author Jonathan Alberto Rivera Gomez * @copyright Copyright (C) 2003,2004 Free Software Foundation, Inc. http://www.fsf.org/ * @license http://www.fsf.org/licenses/gpl.html GNU General Public License * @package phpgwapi * @subpackage database * @version $Id: class.sql_entity.inc.php 21045 2010-03-25 22:41:38Z Caeies $ * @internal Development of this application was funded by http://www.sogrp.com * @link http://www.sogrp.com/ */ /** * SQL entity alias substitution string EASS */ define (PHPGW_SQL_EASS, '|%-'); /** * SQL default method */ define (PHPGW_SQL_DEFAULT_METHOD, -1); /** * SQL lazy key */ define (PHPGW_SQL_LAZY_KEY, 1); /** * SQL required key */ define (PHPGW_SQL_REQUIRED_KEY, 2); /** * SQL all match */ define (PHPGW_SQL_ALL_MATCH, 1); /** * SQL exclusive match */ define (PHPGW_SQL_EXCLUSIVE_MATCH, 2); /** * SQL return records */ define (PHPGW_SQL_RETURN_RECORDS, 1); /** * SQL run SQL */ define (PHPGW_SQL_RUN_SQL, 1); /** * SQL return SQL */ define (PHPGW_SQL_RETURN_SQL, 2); /** * SQL Generator ENTITY - helps to construct queries statements * * This class provide common methods to create transaction sql queries. * Isolates an entity. * @package phpgwapi * @subpackage database */ class sql_entity { /* List of fields to mantain in each query, it morph if select, * if insert, if update. */ var $fields; var $inserts; var $insert_index; var $operation; var $criteria; // need in list form, for easy search var $field_list; // var $field_array; var $alias; // Just INSERT (update?) var $values; // Imported links var $ilink = array(); // Exported links var $elink = array(); var $ondebug; var $table; function sql_entity() { } /*************************************************************\ * Entity, class and general section * \*************************************************************/ function _constructor($table='', $alias='') { $this->table = $table; // Temp alias name, just if not empty $this->alias = ($alias)? $alias: $alias; $this->ldebug('_constructor', array('Table' => $table, 'Alias' => $alias)); } /** * Set the alias for the table * * @param string $alias The alias name * @return The alias name which will be used for SELECT. */ function set_identity($alias) { $this->alias = ($alias)? $alias : get_class($this); $this->table = ($this->table)? $this->table : get_class($this); $this->ldebug('set_identity', array('Alias' => $this->alias, 'Table' => $this->table)); } function set_table_name($table) { $this->table = $table; } function set_alias($alias) { $this->alias = $alias; } /** * Get the list of false fields from instance. * * @return Array List of false fields. */ function get_false_fields() { return array_keys($this->map); } /** * Determines whether if operation must be changed or not. * * Operation is used to decide if alias is attached to fields names or not * @param string $opertation Actual action that is proposed to be the operation. */ function set_operation($operation) { if (empty($this->operation) && $operation != 'criteria' && !empty($operation)) { $this->operation = $operation; } } /** * Forced change of actual operation * * Operation is used to decide if alias is attached to fields names or not * @param string $operation action to be set. */ function change_operation($operation) { if (!empty($operation) && $operation != 'criteria') { $this->operation = $operation; } } /*************************************************************\ * Alias section * \*************************************************************/ /** * Replace the alias string with the one true alias name * * @return Tow string, fields and criteria with the correct alias name. */ function run_alias() { if ($this->alias) { $this->field_alias(); $this->fields = str_replace(PHPGW_SQL_EASS, $this->alias, $this->fields); $this->criteria = str_replace(PHPGW_SQL_EASS, $this->alias, $this->criteria); $this->ldebug('run_alias', array('Fields' => $this->fields, 'Criteria' => $this->criteria, 'Alias' => $this->alias)); } } /** * Set a alias to the table if is required * * @return string with the table name, with alias if is required. */ function get_identity() { return ($this->alias != $this->table)? $this->table .' AS '.$this->alias : $this->table; } function get_alias() { return $this->alias; } /** * @param string $field Name of the field. * @return the string ready for replace s/EASS/alias/ */ function put_alias($field) { return ($this->operation == 'select')? PHPGW_SQL_EASS.'.'.$field : $field; } /** * Set the alias to a any field * @param string $field Name of the field. * @return the string ready for replace s/EASS/alias/ */ function put_real_alias($field) { return ($this->operation == 'select')? $this->alias.'.'.$field : $field; } /*************************************************************\ * Select section * \*************************************************************/ /** * Add the field to list, with alias $alias * * @param string $field Any sql instruction like count(field_name) * @param string $alias Alias for $field */ function add_field($alias, $field) { $this->field_list[$alias] = $field; } /** * Add the field to list * * @param array $element with real_field and false field (field) * @access private */ function _add_field($element) { $this->add_field($element['field'], $this->put_alias($element['real_field'])); $this->ldebug('_add_field', array('Field_list' => $this->field_list), 'dump'); } /** * Set the alias for the select query * * @param string $real_field The real name of the field * @param string $alias_field the alias that field will take * @access private */ function set_field_alias($real_field, $alias_field) { $this->ldebug('set_field_alias', array('Field list' => $this->field_list), 'dump'); $this->fields .= ', '.$real_field.' AS '.$alias_field; } function field_alias() { $this->ldebug('field_alias', array('Field list' => $this->field_list), 'dump'); if($this->field_list) { $alias_field = key($this->field_list); $field = array_shift($this->field_list); $this->fields = $field.' AS '.$alias_field; array_walk($this->field_list, array(&$this,'set_field_alias')); } } /** * local select, if I'm the only one? or for catalog entities? * * @return string A select easy to use. */ function select() { $this->run_alias(); $sql_select = 'SELECT ' . $this->fields. ' FROM ' . $this->get_identity() . ($this->get_criteria()?' WHERE ' . $this->get_criteria():''); return $sql_select; } /** * @return array (field, identity, alias, criteria) for: * SELECT FROM WHERE and for the on construction depending on identity */ function get_select() { $this->run_alias(); $select_value = array($this->fields, $this->get_identity(), $this->alias, $this->get_criteria()); $this->ldebug('get_select', array('Criteria' => $select_value[4])); return $select_value; } /*************************************************************\ * Criteria section * \*************************************************************/ /** * Get the criteria * * @return string with the criteria which was autogenerate. */ function get_criteria() { return $this->criteria; } /** * When no special method defined for $elemnent['field'] this is the method that will run. And add to the criteria list * * @param string $element['real_field'] The associated field. * @param string $element['value'] Criteria for this field. */ function default_criteria($element) { $this->ldebug('default_criteria', array('Element' => $element), 'dump'); $field = (($this->operation == 'select') ? $this->put_alias($element['real_field']) : $element['real_field']); $this->ldebug('default_criteria', array('Field' => $field)); $new_criteria = sql_criteria::has($field, $element['value']); $this->ldebug('default_criteria', array('New Criteria' => $new_criteria)); $this->_add_criteria($new_criteria); } /** * Add criteria to list * * @param string $new_criteria with the new criteria which was autegenerate. * @return string with the criteria. */ function _add_criteria($new_criteria) { $this->ldebug('_add_criteria', array('New Criteria' => $new_criteria, 'All Criteria Prev' => $this->criteria)); $this->criteria = sql_criteria::append_and(array($new_criteria, $this->criteria)); $this->ldebug('_add_criteria', array('All Criteria Post' => $this->criteria)); } /** * Especial criteria for index or id, it decides if must call equal or in operator * * @param array $element with field, value, real_name * @return string with a usefull criteria to use for many (a in clause) or just one id (equal). */ function index_criteria($element) { $field = $this->put_alias($element['real_field']); if(is_array($element['value'])) { if(count($element['value']) == 1) { $value = $this->cast(current($element['value']), $element['field']); return sql_criteria::equal($field, $value); } elseif(count($element['value']) > 1) { return sql_criteria::in($field, $element['value'], $this->get_datatype($field)); } } else { $value = $this->cast($element['value'], $element['field']); return sql_criteria::equal($field, $value); } } /** * Analize a criteria created by tokens and create a string that represent it, useful for any kind of operation that use criteria I guess. * * @param $token_criteria array Array with all the criteria in tokens, generated with sql_criteria * @return string Criteria string (All that goes in WHERE clause) * @see sql_criteria */ function entity_criteria($token_criteria) { /* Things to care about: - `_append_and', `_append_or' arrays have two elements: 1. array with criterias, 2. token - `in' is a three element: 1. field name, 2. array with values, 3. token */ $num_elements = count($token_criteria); switch($num_elements) { case 0: case 1: $local_criteria = $token_criteria; break; case 2: case 3: $operator = array_pop($token_criteria); $left = array_shift($token_criteria); $right = array_shift($token_criteria); if(is_array($left) && $operator != 'in') { $left = $this->entity_criteria($left); } else { $left = $this->real_field($left); } if(is_array($right)) { $right = $this->entity_criteria($right); } $local_criteria = sql_criteria::operate($operator,$left,$right); break; default: $operator = array_pop($token_criteria); $local_criteria = sql_criteria::operate($operator,$token_criteria); } return $local_criteria; } /*************************************************************\ * Insert (input data) section * \*************************************************************/ /** * Wrapper for calling add_insert_element, when we have $element ready. * * @param array $element Form: ('field', 'real_field', 'value'). */ function set_insert_data($element) { $this->add_insert_element($element['field'],$element['real_field'],$element['value']); } /** * Genarete two string with fields and values list * * @param string $false_field Field in map. * @param string $field BD field and which use in insert. * @param string $value Value for use in insert. */ function add_insert_element($false_field,$field,$value) { $this->inserts[$this->insert_index]['data'][$false_field] = array('field' => $field, 'value' => $value); $this->ldebug('add_insert_element', array('False Field' => $false_field, 'DB Field' => $field, 'Value' => $value_insert)); } /*************************************************************\ * Insert (return data) section * \*************************************************************/ /** * Definitive interfase for get the array of inserts sql queries * * @param array $entities with the list of entity that are present in the transaction * @return Array with the sql insert, just with imported keys missing */ function get_multiple_insert($entities) { foreach ($entities as $entity_name) { $link = $this-get_ilink($entity_name); $field = $this->real_field($link['lfield']); $fields_to_prototype[$field] = '{'.$link['lfield'].'}'; } foreach ($this->inserts as $index => $insert) { // First element, the only one than don't begin with `,' $false_field = key($this->inserts[$index]['data']); $this->inserts[$index]['fields'] = $insert['data'][$false_field]['field']; $this->inserts[$index]['values'] = $insert['data'][$false_field]['value']; // Go for next elements array_walk($this->inserts[$index]['data'],array(&$this,'set_fields_insert'),$index); reset($this->inserts[$index]['data']); array_walk($field_to_prototype,array(&$this,'set_field_inserts_prototyped'),$index); $inserts[$index] = $this->insert($idx); } return $inserts; } function insert($data, $action=PHPGW_SQL_RETURN_SQL) { $this->_insert($data, 0); $sql = $this->get_single_insert(0); switch($action) { case PHPGW_SQL_RETURN_RECORDS: case PHPGW_SQL_RUN_SQL: $GLOBALS['phpgw']->db->query($sql, __LINE__, __FILE__); $this->ldebug('insert', $sql, 'msg'); break; case PHPGW_SQL_RETURN_SQL: return $sql; } } function _insert($data, $index = 0) { foreach($data as $field => $value) { $this->add_insert($field, $value, $index); } } /** * Get the insert sql statement for one entry * * @param int $index with the index of data which we want to insert * @return string Corresponding sql insert. */ function get_single_insert($index = 0) { // First element, the only one than don't begin with `,' $false_field = key($this->inserts[$index]['data']); $this->inserts[$index]['fields'] = $this->inserts[$index]['data'][$false_field]['field']; $this->inserts[$index]['values'] = $this->cast($this->inserts[$index]['data'][$false_field]['value'], $false_field); // Go for next elements while(next($this->inserts[$index]['data'])) { $false_field = key($this->inserts[$index]['data']); $this->ldebug('get_single_insert', array('data for index '.$index => $this->inserts[$index]['data']), 'dump'); $this->inserts[$index]['fields'] .= ', '.$this->inserts[$index]['data'][$false_field]['field']; $this->inserts[$index]['values'] .= ', '.$this->cast($this->inserts[$index]['data'][$false_field]['value'], $false_field); } return $this->_single_insert($index); } /** * Get the right value for the datatype of the false field * * @param mixed $data value that want to cast. * @param string $false_field Field for search datatype * @return string Corresponding string with sql for datatype */ function cast($data, $false_field) { if(is_array($data)) { return $this->index_criteria($data); } $type = $this->get_datatype($false_field); return ($data == sql::null())? sql::null() : sql::$type($data); } /** * Genarete the insert string * * @return The string which will be use for insert query. */ function _single_insert($index) { $sql_insert = 'INSERT INTO ' . $this->table . ' ('. $this->get_insert_fields($index). ') VALUES ('.$this->get_insert_values($index).')'; return $sql_insert; } function set_fields_insert_prototyped($field, $value, $index) { if(!array_key_exist($field, $this->inserts[$index]['data'])) { $this->inserts[$index]['fields'] .= ', '.$field; $this->inserts[$index]['values'] .= ', '.$value; } } function get_insert_fields($index) { return $this->inserts[$index]['fields']; } function get_insert_values($index) { return $this->inserts[$index]['values']; } /*************************************************************\ * Update section * \*************************************************************/ /** * Create an update query for this entity * * @param Array $data Fields that want change value and their values * @param Array $criteria With criterias that set the rows to edit * @param integer action * @return string SQL update string */ function update($data,$criteria, $action=PHPGW_SQL_RETURN_SQL) { if(is_array($data) && count($data) > 0) { array_walk($data, array(&$this,'add_update')); } else { list($field,$value) = explode('=',$data); $this->add_update($field, $value); } if (is_string($criteria)) { $this->set_criteria($criteria); } else { $this->set_criteria(sql_criteria::criteria($criteria)); } if(!empty($this->values)) { switch($action) { case PHPGW_SQL_RETURN_RECORDS: case PHPGW_SQL_RUN_SQL: $sql = $this->return_update(); $GLOBALS['phpgw']->db->query($sql, __LINE__, __FILE__); $this->ldebug('update', $sql, 'msg'); return; case PHPGW_SQL_RETURN_SQL: return $this->return_update(); } } } function set_criteria($criteria) { $this->criteria = $criteria; } /** * Genarete the update string * * @return The string which will be used for update query. */ function return_update() { $sql_update = 'UPDATE ' . $this->table . ' SET ' . $this->values; if ($this->criteria) { $sql_update .=' WHERE ' . $this->criteria; } $this->values = ''; $this->criteria = ''; return $sql_update; } function get_update() { return (array('fields' => $this->get_update_data(), 'criteria'=> $this->get_criteria(), 'identity'=> $this->get_identity())); } /** * Genarete a string with field = value to use in update * * @param string $fields * @param string $values * @return string with field=value list. */ function set_update_data($element) { $value = ($element['value'] || $element['value'] == 0) ? $this->cast($element['value'], $element['field']) : sql::null(); $this->values .= (($this->values)?', ':'').$element['real_field'].' = '.$value; } /** * Get the complete field=value listo to use in the update * * @return string with field=value comma separate. */ function get_update_data() { return $this->values; } /** * Genarete the delete string * * @param string $criteria the criteria for select the rows to delete * @param integer $action PHPGW_SQL_RETURN_SQL | PHPGW_SQL_RUN_SQL * @return string which will be used for delete query. */ function delete($criteria, $action) { if($criteria) { $sql = 'DELETE FROM '.$this->table.' WHERE '.$criteria; $this->set_criteria(''); switch($action) { case PHPGW_SQL_RETURN_RECORDS: case PHPGW_SQL_RUN_SQL: $GLOBALS['phpgw']->db->query($sql, __LINE__, __FILE__); return; case PHPGW_SQL_RETURN_SQL: return $sql; } } } /** * Get the complete fields list to use in the insert or select * * @return string with fields comma separate. */ function get_fields() { return $this->fields; } /*************************************************************\ * Links and keys section * \*************************************************************/ /** * Genarete an array with all imported links * * @param string $fl with local field * @param string $t with table * @param string $ff with foreign field * @param int $key_type PHPGW_SQL_LAZY_KEY if want that this link be joined via OUTER (LEFT o RIGHT); PHPGW_SQL_REQUIRED_KEY * if want that be joined with INNER LINK. This is the setting by default, and could be changed per query execution. */ function set_ilinks($fl,$t,$ff,$key_type = PHPGW_SQL_LAZY_KEY) { $this->ilink[$t] = array('lfield' => $fl, 'ffield' => $ff, 'type' => $key_type); } /** * Genarete an array with all exported links * * @param string $fl with local field * @param string $t with table * @param string $ff with foreign field */ function set_elinks($fl,$t,$ff) { $this->elink[$t] = array('lfield' => $fl, 'ffield' => $ff); } /** * Get the lfield and ffield from any of elink or ilink according to $entity. * * @param string $entity Name of entity to search link with. * @return Array $lfield that is the local field of the link, and $ffield that is the foreign field. */ function get_link($entity = '') { if ($entity != '') { if(array_key_exists($entity, (isset($this->ilink)? $this->ilink : array()))) { return $this->ilink[$entity]; } elseif (array_key_exists($entity, (isset($this->elink)? $this->elink : array()))) { return $this->elink[$entity]; } } // Must raise error return ''; } /** * Get the lfield and ffield from any of ilink according to $entity. * * @param strnig $entity Name of entity to search link with. * @return Array $lfield that is the local field of the link, and $ffield that is the foreign field. */ function get_ilink($entity = '') { $this->ldebug('get_ilink', array('entity' => $entity)); $this->ldebug('get_ilink', array('ilinks' => $this->ilink), 'dump'); if ($entity != '') { if(array_key_exists($entity, (isset($this->ilink)? $this->ilink : array()))) { return $this->ilink[$entity]; } } // Must raise error return ; } /** * Get the lfield and ffield from any of elink according to $entity. * * @param string $entity Name of entity to search link with. * @return Array $lfield that is the local field of the link, and $ffield that is the foreign field. */ function get_elink($entity = '') { if ($entity != '') { if (array_key_exists($entity, (isset($this->elink)? $this->elink : array()))) { return $this->elink[$entity]; } } return ; } /** * Set an array with all imported or exported links * * @return array with imported or exported links. */ function get_ilinks() { return $this->ilink; } /** * Set an array with all exported links * * @return array with imported or exported links. */ function get_elinks() { return $this->elink; } function get_fields_links($entities) { foreach ($entities as $entity_name) { $link = get_ilink($entity_name); $fields_return[] = $link['ffalse']; } return $fields_return; } /*************************************************************\ * add_element `Sniper' section * \*************************************************************/ function add_element($action, $element) { $this->set_operation($action); $method = $this->get_method($action,$element['field']); $element['real_field'] = $this->real_field($element['field']); switch($action) { case 'select': $method_default = '_add_field'; break; case 'insert': case 'delete': case 'update': $method_default = 'set_'.$action.'_data'; break; case 'criteria': $method_default = 'default_criteria'; break; default: $this->dont_exist($action); } $this->ldebug('add_element', array('Element' => $element), 'dump'); if($method == PHPGW_SQL_DEFAULT_METHOD) { $this->ldebug('add_element', array('Method_Default DEF' => $method_default, 'Method DEF' => $default)); $this->$method_default($element); } elseif ($method) { $this->ldebug('add_element', array('Method_Default' => $method_default, 'Method' => $default)); $this->$method($element); } else { $this->ldebug('add_element', 'Never be here, hope', 'msg'); $this->dont_exist($element); } } function get_method($action, $field) { if(isset($this->map[$field])) { $method = $this->map[$field][$action]; if(isset($this->map[$field][$action]) && method_exists($this, $method)) { return $method; } elseif(method_exists($this, $action.'_'.$field)) { return $action.'_'.$field; } else { return PHPGW_SQL_DEFAULT_METHOD; } } // this is an error :/ not $field in map return; } function real_field($field) { if(isset($this->map[$field])) { if(isset($this->map[$field]['field']) && !empty($this->map[$field]['field'])) { return $this->map[$field]['field']; } else { return $field; } } // this is an error :/ not $field in map return; } /** * Get the real field name with alias of table. (Used in criteria). * * @param string $field False field name. * @return string alias.real_name */ function alias_field($field) { return $this->get_alias().'.'.$this->real_field($field); } function get_datatype($field) { if(isset($this->map[$field])) { if(isset($this->map[$field]['type']) && !empty($this->map[$field]['type'])) { return $this->map[$field]['type']; } else { return 'string'; } } // this is an error :/ not $field in map return; } function make_pair($field, $value) { return array('field' => $field, 'value' => $value); } /*************************************************************\ * sql_builder API section * \*************************************************************/ function add_select($field) { $this->add_element('select',$this->make_pair($field,'')); $this->ldebug('add_select', array('Field' => $field)); } function add_criteria($field, $value) { $this->add_element('criteria',$this->make_pair($field, $value)); $this->ldebug('add_criteria', array('Field' => $field)); } function add_update($value, $field) { $this->add_element('update',$this->make_pair($field, $value)); } function add_delete($field, $value) { $this->add_element('delete',$this->make_pair($field, $value)); } function add_insert($field, $value, $idx = 0) { $this->insert_index = $idx; $this->add_element('insert',$this->make_pair($field, $value)); } /** * Must raise errors for this class, don't know if phpgw have anything already, if yes, net call it * * @param mixed $data What dont exist */ function dont_exist($data) { } /** * Get the field name which correspond to sort * * @param strngi $field The field * @return The alias and real name for field. */ function get_order($field) { if($this->map[$field]['sort']) { return $this->alias.'.'.$this->map[$field]['sort']; } } function ldebug($myfoo, $data, $type = 'string', $err = '') { // if (!((($myfoo != '') xor // ($myfoo != 'default_criteria')) xor // ($myfoo == '')) xor // ($myfoo == '')) // { return; // } $classname = 'Class: '.get_class($this)."
Function: $myfoo
"; switch($type) { case 'string': foreach($data as $vari => $value) { if (is_array($value)) { $this->ldebug($myfoo.' recursivecall', array('  -$vari: ' => $value), 'dump'); } else { $output .= "  -$vari = $value
"; } } break; case 'dump': foreach($data as $vari => $value) { $output .= "  -$vari = "; $output .= var_dump($value)."
"; } break; default: $output .= "
$data
"; } if ($err != '') { $output = $classname.'Error: '.$output.'
'; } else { $output = $classname.$output.'
'; } echo $output; } } ?>