'=', 'more' => '>=', 'excess' => '>', 'less' => '<=', 'below' => '<', 'notequal' => '<>', 'notnull' => 'is not null', 'null' => 'is null', ); var $fd = NULL; ///< connector resource or file description var $result = NULL; ///< result var $errno = 0; ///< 에러 발생시 에러 코드 (0이면 에러가 없다고 정의) var $errstr = ''; ///< 에러 발생시 에러 메세지 var $query = ''; ///< 가장 최근에 수행된 query string var $elapsed_time = 0; ///< 가장 최근에 수행된 query 의 실행시간 var $transaction_started = false; ///< 트랙잭션 처리 flag var $is_connected = false; ///< DB에 접속이 되었는지에 대한 flag var $supported_list = array(); ///< 지원하는 DB의 종류, classes/DB/DB***.class.php 를 이용하여 동적으로 작성됨 var $cache_file = 'files/cache/queries/'; ///< query cache파일의 위치 /** * @brief DB를 상속받는 특정 db type의 instance를 생성 후 return **/ function &getInstance($db_type = NULL) { if(!$db_type) $db_type = Context::getDBType(); if(!$db_type && Context::isInstalled()) return new Object(-1, 'msg_db_not_setted'); if(!$GLOBALS['__DB__']) { $class_name = sprintf("DB%s%s", strtoupper(substr($db_type,0,1)), strtolower(substr($db_type,1))); $class_file = sprintf("%sclasses/db/%s.class.php", _XE_PATH_, $class_name); if(!file_exists($class_file)) new Object(-1, 'msg_db_not_setted'); require_once($class_file); $eval_str = sprintf('$GLOBALS[\'__DB__\'][\''.$db_type.'\'] = new %s();', $class_name); eval($eval_str); } return $GLOBALS['__DB__'][$db_type]; } /** * @brief constructor **/ function DB() { $this->count_cache_path = _XE_PATH_.$this->count_cache_path; $this->cache_file = _XE_PATH_.$this->cache_file; } /** * @brief 지원 가능한 DB 목록을 return **/ function getSupportedList() { $oDB = new DB(); return $oDB->_getSupportedList(); } /** * @brief 지원 가능한 DB 목록을 return **/ function _getSupportedList() { $db_classes_path = _XE_PATH_."classes/db/"; $filter = "/^DB([^\.]+)\.class\.php/i"; $supported_list = FileHandler::readDir($db_classes_path, $filter, true); sort($supported_list); // 구해진 클래스의 객체 생성후 isSupported method를 통해 지원 여부를 판단 for($i=0;$idb_type = $db_type; $obj->enable = $oDB->isSupported()?true:false; $this->supported_list[] = $obj; } return $this->supported_list; } /** * @brief 지원하는 DB인지에 대한 check **/ function isSupported($db_type) { $supported_list = DB::getSupportedList(); return in_array($db_type, $supported_list); } /** * @brief 접속되었는지 return **/ function isConnected() { return $this->is_connected?true:false; } /** * @brief 로그 남김 **/ function actStart($query) { $this->setError(0,'success'); $this->query = $query; $this->act_start = getMicroTime(); $this->elapsed_time = 0; } function actFinish() { if(!$this->query ) return; $this->act_finish = getMicroTime(); $elapsed_time = $this->act_finish - $this->act_start; $this->elapsed_time = $elapsed_time; $GLOBALS['__db_elapsed_time__'] += $elapsed_time; $log['query'] = $this->query; $log['elapsed_time'] = $elapsed_time; // 에러 발생시 에러 로그를 남김 (__DEBUG_DB_OUTPUT__이 지정되어 있을경우) if($this->isError()) { $log['result'] = 'Failed'; $log['errno'] = $this->errno; $log['errstr'] = $this->errstr; if(__DEBUG_DB_OUTPUT__ == 1) { $debug_file = _XE_PATH_."files/_debug_db_query.php"; $buff = sprintf("%s\n",print_r($log,true)); if($display_line) $buff = "\n\n"; if(@!$fp = fopen($debug_file,"a")) return; fwrite($fp, $buff); fclose($fp); } } else { $log['result'] = 'Success'; } $GLOBALS['__db_queries__'][] = $log; // __LOG_SLOW_QUERY__ 가 정해져 있다면 시간 체크후 쿼리 로그 남김 if(__LOG_SLOW_QUERY__>0 && $elapsed_time > __LOG_SLOW_QUERY__) { $buff = ''; $log_file = _XE_PATH_.'files/_db_slow_query.php'; if(!file_exists($log_file)) { $buff = ''."\n"; } $buff .= sprintf("%s\t%s\n\t%0.6f sec\n\n", date("Y-m-h H:i"), $this->query, $elapsed_time); if($fp = fopen($log_file,'a')) { fwrite($fp, $buff); fclose($fp); } } } /** * @brief 에러발생시 에러 메세지를 남기고 debug 모드일때는 GLOBALS 변수에 에러 로깅 **/ function setError($errno = 0, $errstr = 'success') { $this->errno = $errno; $this->errstr = $errstr; } /** * @brief 오류가 발생하였는지 return **/ function isError() { return $this->errno===0?false:true; } /** * @brief 에러결과를 Object 객체로 return **/ function getError() { return new Object($this->errno, $this->errstr); } /** * @brief query xml 파일을 실행하여 결과를 return * * query_id = module.queryname * query_id에 해당하는 xml문(or 캐싱파일)을 찾아서 컴파일 후 실행 **/ function executeQuery($query_id, $args = NULL) { if(!$query_id) return new Object(-1, 'msg_invalid_queryid'); $id_args = explode('.', $query_id); if(count($id_args)==2) { $target = 'modules'; $module = $id_args[0]; $id = $id_args[1]; } elseif(count($id_args)==3) { $target = $id_args[0]; if(!in_array($target, array('addons','widgets'))) return; $module = $id_args[1]; $id = $id_args[2]; } if(!$target || !$module || !$id) return new Object(-1, 'msg_invalid_queryid'); $xml_file = sprintf('%s%s/%s/queries/%s.xml', _XE_PATH_, $target, $module, $id); if(!file_exists($xml_file)) return new Object(-1, 'msg_invalid_queryid'); // 캐쉬파일을 찾아 본다 $cache_file = $this->checkQueryCacheFile($query_id,$xml_file); // 쿼리를 실행한다 return $this->_executeQuery($cache_file, $args, $query_id); } /** * @brief 캐쉬파일을 찾아 본다 * **/ function checkQueryCacheFile($query_id,$xml_file){ // 일단 cache 파일을 찾아본다 $cache_file = sprintf('%s%s%s.cache.php', _XE_PATH_, $this->cache_file, $query_id); if(file_exists($cache_file)) $cache_time = filemtime($cache_file); else $cache_time = -1; // 캐시 파일이 없거나 시간 비교하여 최근것이 아니면 원본 쿼리 xml파일을 찾아서 파싱을 한다 if($cache_timeparse($query_id, $xml_file, $cache_file); } return $cache_file; } /** * @brief 쿼리문을 실행하고 결과를 return한다 **/ function _executeQuery($cache_file, $source_args, $query_id) { global $lang; if(!file_exists($cache_file)) return new Object(-1, 'msg_invalid_queryid'); if($source_args) $args = @clone($source_args); $output = @include($cache_file); if( (is_a($output, 'Object')||is_subclass_of($output,'Object'))&&!$output->toBool()) return $output; $output->_tables = ($output->_tables && is_array($output->_tables)) ? $output->_tables : array(); // action값에 따라서 쿼리 생성으로 돌입 switch($output->action) { case 'insert' : $this->resetCountCache($output->tables); $output = $this->_executeInsertAct($output); break; case 'update' : $this->resetCountCache($output->tables); $output = $this->_executeUpdateAct($output); break; case 'delete' : $this->resetCountCache($output->tables); $output = $this->_executeDeleteAct($output); break; case 'select' : $output = $this->_executeSelectAct($output); break; } if($this->errno != 0 ) $output = new Object($this->errno, $this->errstr); else if(!is_a($output, 'Object') && !is_subclass_of($output, 'Object')) $output = new Object(); $output->add('_query', $this->query); $output->add('_elapsed_time', sprintf("%0.5f",$this->elapsed_time)); return $output; } /** * @brief $val을 $filter_type으로 검사 * XmlQueryParser에서 사용하도록 함 **/ function checkFilter($key, $val, $filter_type) { global $lang; switch($filter_type) { case 'email' : case 'email_address' : if(!preg_match('/^[_0-9a-z-]+(\.[_0-9a-z-]+)*@[0-9a-z-]+(\.[0-9a-z-]+)*$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_email, $lang->{$key}?$lang->{$key}:$key)); break; case 'homepage' : if(!preg_match('/^(http|https)+(:\/\/)+[0-9a-z_-]+\.[^ ]+$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_homepage, $lang->{$key}?$lang->{$key}:$key)); break; case 'userid' : case 'user_id' : if(!preg_match('/^[a-zA-Z]+([_0-9a-zA-Z]+)*$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_userid, $lang->{$key}?$lang->{$key}:$key)); break; case 'number' : case 'numbers' : if(!preg_match('/^(-?)[0-9,]+$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_number, $lang->{$key}?$lang->{$key}:$key)); break; case 'alpha' : if(!preg_match('/^[a-z]+$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_alpha, $lang->{$key}?$lang->{$key}:$key)); break; case 'alpha_number' : if(!preg_match('/^[0-9a-z]+$/is', $val)) return new Object(-1, sprintf($lang->filter->invalid_alpha_number, $lang->{$key}?$lang->{$key}:$key)); break; } return new Object(); } /** * @brief 컬럼의 타입을 구해옴 * 컬럼의 경우 a.b 와 같이 되어 있는 경우가 있어서 별도 함수가 필요 **/ function getColumnType($column_type_list, $name) { if(strpos($name,'.')===false) return $column_type_list[$name]; list($prefix, $name) = explode('.',$name); return $column_type_list[$name]; } /** * @brief 이름, 값, operation, type으로 값을 변경 * like, like_prefix의 경우 value자체가 변경됨 * type == number가 아니면 addQuotes()를 하고 ' ' 로 묶음 **/ function getConditionValue($name, $value, $operation, $type, $column_type) { if($type == 'number') { if(strpos($value,',')===false && strpos($value,'(')===false) return (int)$value; return $value; } if(strpos($name,'.')!==false&&strpos($value,'.')!==false) { list($table_name, $column_name) = explode('.',$value); if($column_type[$column_name]) return $value; } $value = preg_replace('/(^\'|\'$){1}/','',$value); switch($operation) { case 'like_prefix' : $value = $value.'%'; break; case 'like_tail' : $value = '%'.$value; break; case 'like' : $value = '%'.$value.'%'; break; case 'notin' : return "'".$value."'"; break; case 'in' : return "'".$value."'"; break; } return "'".$this->addQuotes($value)."'"; } /** * @brief 이름, 값, operation으로 조건절 작성 * 조건절을 완성하기 위해 세부 조건절 마다 정리를 해서 return **/ function getConditionPart($name, $value, $operation) { switch($operation) { case 'equal' : case 'more' : case 'excess' : case 'less' : case 'below' : case 'like_tail' : case 'like_prefix' : case 'like' : case 'in' : case 'notin' : case 'notequal' : // 변수가 세팅되지 않고, 문자열이나 숫자형이 아니면 리턴 if(!isset($value)) return; if($value === '') return; if(!in_array(gettype($value), array('string', 'integer'))) return; } switch($operation) { case 'equal' : return $name.' = '.$value; break; case 'more' : return $name.' >= '.$value; break; case 'excess' : return $name.' > '.$value; break; case 'less' : return $name.' <= '.$value; break; case 'below' : return $name.' < '.$value; break; case 'like_tail' : case 'like_prefix' : case 'like' : return $name.' like '.$value; break; case 'in' : return $name.' in ('.$value.')'; break; case 'notin' : return $name.' not in ('.$value.')'; break; case 'notequal' : return $name.' <> '.$value; break; case 'notnull' : return $name.' is not null'; break; case 'null' : return $name.' is null'; break; } } /** * @brief condition key를 return **/ function getConditionList($output) { $conditions = array(); if(count($output->conditions)) { foreach($output->conditions as $key => $val) { if($val['condition']) { foreach($val['condition'] as $k => $v) { $conditions[] = $v['column']; } } } } return $conditions; } /** * @brief 카운터 캐시 데이터 얻어오기 **/ function getCountCache($tables, $condition) { return false; if(!$tables) return false; if(!is_dir($this->count_cache_path)) return FileHandler::makeDir($this->count_cache_path); $condition = md5($condition); if(!is_array($tables)) $tables_str = $tables; else $tables_str = implode('.',$tables); $cache_path = sprintf('%s/%s%s', $this->count_cache_path, $this->prefix, $tables_str); if(!is_dir($cache_path)) FileHandler::makeDir($cache_path); $cache_filename = sprintf('%s/%s.%s', $cache_path, $tables_str, $condition); if(!file_exists($cache_filename)) return false; $cache_mtime = filemtime($cache_filename); if(!is_array($tables)) $tables = array($tables); foreach($tables as $alias => $table) { $table_filename = sprintf('%s/cache.%s%s', $this->count_cache_path, $this->prefix, $table) ; if(!file_exists($table_filename) || filemtime($table_filename) > $cache_mtime) return false; } $count = (int)FileHandler::readFile($cache_filename); return $count; } /** * @brief 카운터 캐시 데이터 저장 **/ function putCountCache($tables, $condition, $count = 0) { return false; if(!$tables) return false; if(!is_dir($this->count_cache_path)) return FileHandler::makeDir($this->count_cache_path); $condition = md5($condition); if(!is_array($tables)) $tables_str = $tables; else $tables_str = implode('.',$tables); $cache_path = sprintf('%s/%s%s', $this->count_cache_path, $this->prefix, $tables_str); if(!is_dir($cache_path)) FileHandler::makeDir($cache_path); $cache_filename = sprintf('%s/%s.%s', $cache_path, $tables_str, $condition); FileHandler::writeFile($cache_filename, $count); } /** * @brief 카운터 캐시 리셋 **/ function resetCountCache($tables) { return false; if(!$tables) return false; if(!is_dir($this->count_cache_path)) return FileHandler::makeDir($this->count_cache_path); if(!is_array($tables)) $tables = array($tables); foreach($tables as $alias => $table) { $filename = sprintf('%s/cache.%s%s', $this->count_cache_path, $this->prefix, $table); FileHandler::removeFile($filename); FileHandler::writeFile( $filename, '' ); } return true; } function getSupportedDatabase(){ $result = array(); if(function_exists('mysql_connect')) $result[] = 'MySQL'; if(function_exists('cubrid_connect')) $result[] = 'Cubrid'; if(function_exists('ibase_connect')) $result[] = 'FireBird'; if(function_exists('pg_connect')) $result[] = 'Postgre'; if(function_exists('sqlite_open')) $result[] = 'sqlite2'; if(function_exists('mssql_connect')) $result[] = 'MSSQL'; if(function_exists('PDO')) $result[] = 'sqlite3(PDO)'; return $result; } } ?>