DbPdo.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. defined('THINK_PATH') or exit();
  12. /**
  13. * PDO数据库驱动
  14. * @category Extend
  15. * @package Extend
  16. * @subpackage Driver.Db
  17. * @author liu21st <liu21st@gmail.com>
  18. */
  19. class DbPdo extends Db{
  20. protected $PDOStatement = null;
  21. private $table = '';
  22. /**
  23. * 架构函数 读取数据库配置信息
  24. * @access public
  25. * @param array $config 数据库配置数组
  26. */
  27. public function __construct($config=''){
  28. if ( !class_exists('PDO') ) {
  29. throw_exception(L('_NOT_SUPPERT_').':PDO');
  30. }
  31. if(!empty($config)) {
  32. $this->config = $config;
  33. if(empty($this->config['params'])) {
  34. $this->config['params'] = array();
  35. }
  36. }
  37. }
  38. /**
  39. * 连接数据库方法
  40. * @access public
  41. */
  42. public function connect($config='',$linkNum=0) {
  43. if ( !isset($this->linkID[$linkNum]) ) {
  44. if(empty($config)) $config = $this->config;
  45. if($this->pconnect) {
  46. $config['params'][PDO::ATTR_PERSISTENT] = true;
  47. }
  48. //$config['params'][PDO::ATTR_CASE] = C("DB_CASE_LOWER")?PDO::CASE_LOWER:PDO::CASE_UPPER;
  49. try{
  50. $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$config['params']);
  51. }catch (PDOException $e) {
  52. throw_exception($e->getMessage());
  53. }
  54. // 因为PDO的连接切换可能导致数据库类型不同,因此重新获取下当前的数据库类型
  55. $this->dbType = $this->_getDsnType($config['dsn']);
  56. if(in_array($this->dbType,array('MSSQL','ORACLE','IBASE','OCI'))) {
  57. // 由于PDO对于以上的数据库支持不够完美,所以屏蔽了 如果仍然希望使用PDO 可以注释下面一行代码
  58. throw_exception('由于目前PDO暂时不能完美支持'.$this->dbType.' 请使用官方的'.$this->dbType.'驱动');
  59. }
  60. $this->linkID[$linkNum]->exec('SET NAMES '.C('DB_CHARSET'));
  61. // 标记连接成功
  62. $this->connected = true;
  63. // 注销数据库连接配置信息
  64. if(1 != C('DB_DEPLOY_TYPE')) unset($this->config);
  65. }
  66. return $this->linkID[$linkNum];
  67. }
  68. /**
  69. * 释放查询结果
  70. * @access public
  71. */
  72. public function free() {
  73. $this->PDOStatement = null;
  74. }
  75. /**
  76. * 执行查询 返回数据集
  77. * @access public
  78. * @param string $str sql指令
  79. * @param array $bind 参数绑定
  80. * @return mixed
  81. */
  82. public function query($str,$bind=array()) {
  83. $this->initConnect(false);
  84. if ( !$this->_linkID ) return false;
  85. $this->queryStr = $str;
  86. if(!empty($bind)){
  87. $this->queryStr .= '[ '.print_r($bind,true).' ]';
  88. }
  89. //释放前次的查询结果
  90. if ( !empty($this->PDOStatement) ) $this->free();
  91. N('db_query',1);
  92. // 记录开始执行时间
  93. G('queryStartTime');
  94. $this->PDOStatement = $this->_linkID->prepare($str);
  95. if(false === $this->PDOStatement)
  96. throw_exception($this->error());
  97. $result = $this->PDOStatement->execute($bind);
  98. $this->debug();
  99. if ( false === $result ) {
  100. $this->error();
  101. return false;
  102. } else {
  103. return $this->getAll();
  104. }
  105. }
  106. /**
  107. * 执行语句
  108. * @access public
  109. * @param string $str sql指令
  110. * @param array $bind 参数绑定
  111. * @return integer
  112. */
  113. public function execute($str,$bind=array()) {
  114. $this->initConnect(true);
  115. if ( !$this->_linkID ) return false;
  116. $this->queryStr = $str;
  117. if(!empty($bind)){
  118. $this->queryStr .= '[ '.print_r($bind,true).' ]';
  119. }
  120. $flag = false;
  121. if($this->dbType == 'OCI')
  122. {
  123. if(preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $this->queryStr, $match)) {
  124. $this->table = C("DB_SEQUENCE_PREFIX").str_ireplace(C("DB_PREFIX"), "", $match[2]);
  125. $flag = (boolean)$this->query("SELECT * FROM user_sequences WHERE sequence_name='" . strtoupper($this->table) . "'");
  126. }
  127. }//modify by wyfeng at 2009.08.28
  128. //释放前次的查询结果
  129. if ( !empty($this->PDOStatement) ) $this->free();
  130. N('db_write',1);
  131. // 记录开始执行时间
  132. G('queryStartTime');
  133. $this->PDOStatement = $this->_linkID->prepare($str);
  134. if(false === $this->PDOStatement) {
  135. throw_exception($this->error());
  136. }
  137. $result = $this->PDOStatement->execute($bind);
  138. $this->debug();
  139. if ( false === $result) {
  140. $this->error();
  141. return false;
  142. } else {
  143. $this->numRows = $this->PDOStatement->rowCount();
  144. if($flag || preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
  145. $this->lastInsID = $this->getLastInsertId();
  146. }
  147. return $this->numRows;
  148. }
  149. }
  150. /**
  151. * 启动事务
  152. * @access public
  153. * @return void
  154. */
  155. public function startTrans() {
  156. $this->initConnect(true);
  157. if ( !$this->_linkID ) return false;
  158. //数据rollback 支持
  159. if ($this->transTimes == 0) {
  160. $this->_linkID->beginTransaction();
  161. }
  162. $this->transTimes++;
  163. return ;
  164. }
  165. /**
  166. * 用于非自动提交状态下面的查询提交
  167. * @access public
  168. * @return boolen
  169. */
  170. public function commit() {
  171. if ($this->transTimes > 0) {
  172. $result = $this->_linkID->commit();
  173. $this->transTimes = 0;
  174. if(!$result){
  175. $this->error();
  176. return false;
  177. }
  178. }
  179. return true;
  180. }
  181. /**
  182. * 事务回滚
  183. * @access public
  184. * @return boolen
  185. */
  186. public function rollback() {
  187. if ($this->transTimes > 0) {
  188. $result = $this->_linkID->rollback();
  189. $this->transTimes = 0;
  190. if(!$result){
  191. $this->error();
  192. return false;
  193. }
  194. }
  195. return true;
  196. }
  197. /**
  198. * 获得所有的查询数据
  199. * @access private
  200. * @return array
  201. */
  202. private function getAll() {
  203. //返回数据集
  204. $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
  205. $this->numRows = count( $result );
  206. return $result;
  207. }
  208. /**
  209. * 取得数据表的字段信息
  210. * @access public
  211. */
  212. public function getFields($tableName) {
  213. $this->initConnect(true);
  214. if(C('DB_DESCRIBE_TABLE_SQL')) {
  215. // 定义特殊的字段查询SQL
  216. $sql = str_replace('%table%',$tableName,C('DB_DESCRIBE_TABLE_SQL'));
  217. }else{
  218. switch($this->dbType) {
  219. case 'MSSQL':
  220. case 'SQLSRV':
  221. $sql = "SELECT column_name as 'Name', data_type as 'Type', column_default as 'Default', is_nullable as 'Null'
  222. FROM information_schema.tables AS t
  223. JOIN information_schema.columns AS c
  224. ON t.table_catalog = c.table_catalog
  225. AND t.table_schema = c.table_schema
  226. AND t.table_name = c.table_name
  227. WHERE t.table_name = '$tableName'";
  228. break;
  229. case 'SQLITE':
  230. $sql = 'PRAGMA table_info ('.$tableName.') ';
  231. break;
  232. case 'ORACLE':
  233. case 'OCI':
  234. $sql = "SELECT a.column_name \"Name\",data_type \"Type\",decode(nullable,'Y',0,1) notnull,data_default \"Default\",decode(a.column_name,b.column_name,1,0) \"pk\" "
  235. ."FROM user_tab_columns a,(SELECT column_name FROM user_constraints c,user_cons_columns col "
  236. ."WHERE c.constraint_name=col.constraint_name AND c.constraint_type='P' and c.table_name='".strtoupper($tableName)
  237. ."') b where table_name='".strtoupper($tableName)."' and a.column_name=b.column_name(+)";
  238. break;
  239. case 'PGSQL':
  240. $sql = 'select fields_name as "Name",fields_type as "Type",fields_not_null as "Null",fields_key_name as "Key",fields_default as "Default",fields_default as "Extra" from table_msg('.$tableName.');';
  241. break;
  242. case 'IBASE':
  243. break;
  244. case 'MYSQL':
  245. default:
  246. $sql = 'DESCRIBE '.$tableName;//备注: 驱动类不只针对mysql,不能加``
  247. }
  248. }
  249. $result = $this->query($sql);
  250. $info = array();
  251. if($result) {
  252. foreach ($result as $key => $val) {
  253. $val = array_change_key_case($val);
  254. $val['name'] = isset($val['name'])?$val['name']:"";
  255. $val['type'] = isset($val['type'])?$val['type']:"";
  256. $name = isset($val['field'])?$val['field']:$val['name'];
  257. $info[$name] = array(
  258. 'name' => $name ,
  259. 'type' => $val['type'],
  260. 'notnull' => (bool)(((isset($val['null'])) && ($val['null'] === '')) || ((isset($val['notnull'])) && ($val['notnull'] === ''))), // not null is empty, null is yes
  261. 'default' => isset($val['default'])? $val['default'] :(isset($val['dflt_value'])?$val['dflt_value']:""),
  262. 'primary' => isset($val['key'])?strtolower($val['key']) == 'pri':(isset($val['pk'])?$val['pk']:false),
  263. 'autoinc' => isset($val['extra'])?strtolower($val['extra']) == 'auto_increment':(isset($val['key'])?$val['key']:false),
  264. );
  265. }
  266. }
  267. return $info;
  268. }
  269. /**
  270. * 取得数据库的表信息
  271. * @access public
  272. */
  273. public function getTables($dbName='') {
  274. if(C('DB_FETCH_TABLES_SQL')) {
  275. // 定义特殊的表查询SQL
  276. $sql = str_replace('%db%',$dbName,C('DB_FETCH_TABLES_SQL'));
  277. }else{
  278. switch($this->dbType) {
  279. case 'ORACLE':
  280. case 'OCI':
  281. $sql = 'SELECT table_name FROM user_tables';
  282. break;
  283. case 'MSSQL':
  284. case 'SQLSRV':
  285. $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'";
  286. break;
  287. case 'PGSQL':
  288. $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
  289. break;
  290. case 'IBASE':
  291. // 暂时不支持
  292. throw_exception(L('_NOT_SUPPORT_DB_').':IBASE');
  293. break;
  294. case 'SQLITE':
  295. $sql = "SELECT name FROM sqlite_master WHERE type='table' "
  296. . "UNION ALL SELECT name FROM sqlite_temp_master "
  297. . "WHERE type='table' ORDER BY name";
  298. break;
  299. case 'MYSQL':
  300. default:
  301. if(!empty($dbName)) {
  302. $sql = 'SHOW TABLES FROM '.$dbName;
  303. }else{
  304. $sql = 'SHOW TABLES ';
  305. }
  306. }
  307. }
  308. $result = $this->query($sql);
  309. $info = array();
  310. foreach ($result as $key => $val) {
  311. $info[$key] = current($val);
  312. }
  313. return $info;
  314. }
  315. /**
  316. * limit分析
  317. * @access protected
  318. * @param mixed $lmit
  319. * @return string
  320. */
  321. protected function parseLimit($limit) {
  322. $limitStr = '';
  323. if(!empty($limit)) {
  324. switch($this->dbType){
  325. case 'PGSQL':
  326. case 'SQLITE':
  327. $limit = explode(',',$limit);
  328. if(count($limit)>1) {
  329. $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' ';
  330. }else{
  331. $limitStr .= ' LIMIT '.$limit[0].' ';
  332. }
  333. break;
  334. case 'MSSQL':
  335. case 'SQLSRV':
  336. break;
  337. case 'IBASE':
  338. // 暂时不支持
  339. break;
  340. case 'ORACLE':
  341. case 'OCI':
  342. break;
  343. case 'MYSQL':
  344. default:
  345. $limitStr .= ' LIMIT '.$limit.' ';
  346. }
  347. }
  348. return $limitStr;
  349. }
  350. /**
  351. * 字段和表名处理
  352. * @access protected
  353. * @param string $key
  354. * @return string
  355. */
  356. protected function parseKey(&$key) {
  357. if($this->dbType=='MYSQL'){
  358. $key = trim($key);
  359. if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) {
  360. $key = '`'.$key.'`';
  361. }
  362. return $key;
  363. }else{
  364. return parent::parseKey($key);
  365. }
  366. }
  367. /**
  368. * 关闭数据库
  369. * @access public
  370. */
  371. public function close() {
  372. $this->_linkID = null;
  373. }
  374. /**
  375. * 数据库错误信息
  376. * 并显示当前的SQL语句
  377. * @access public
  378. * @return string
  379. */
  380. public function error() {
  381. if($this->PDOStatement) {
  382. $error = $this->PDOStatement->errorInfo();
  383. $this->error = $error[1].':'.$error[2];
  384. }else{
  385. $this->error = '';
  386. }
  387. if('' != $this->queryStr){
  388. $this->error .= "\n [ SQL语句 ] : ".$this->queryStr;
  389. }
  390. trace($this->error,'','ERR');
  391. return $this->error;
  392. }
  393. /**
  394. * SQL指令安全过滤
  395. * @access public
  396. * @param string $str SQL指令
  397. * @return string
  398. */
  399. public function escapeString($str) {
  400. switch($this->dbType) {
  401. case 'PGSQL':
  402. case 'MSSQL':
  403. case 'SQLSRV':
  404. case 'MYSQL':
  405. return addslashes($str);
  406. case 'IBASE':
  407. case 'SQLITE':
  408. case 'ORACLE':
  409. case 'OCI':
  410. return str_ireplace("'", "''", $str);
  411. }
  412. }
  413. /**
  414. * value分析
  415. * @access protected
  416. * @param mixed $value
  417. * @return string
  418. */
  419. protected function parseValue($value) {
  420. if(is_string($value)) {
  421. $value = strpos($value,':') === 0 ? $this->escapeString($value) : '\''.$this->escapeString($value).'\'';
  422. }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){
  423. $value = $this->escapeString($value[1]);
  424. }elseif(is_array($value)) {
  425. $value = array_map(array($this, 'parseValue'),$value);
  426. }elseif(is_bool($value)){
  427. $value = $value ? '1' : '0';
  428. }elseif(is_null($value)){
  429. $value = 'null';
  430. }
  431. return $value;
  432. }
  433. /**
  434. * 获取最后插入id
  435. * @access public
  436. * @return integer
  437. */
  438. public function getLastInsertId() {
  439. switch($this->dbType) {
  440. case 'PGSQL':
  441. case 'SQLITE':
  442. case 'MSSQL':
  443. case 'SQLSRV':
  444. case 'IBASE':
  445. case 'MYSQL':
  446. return $this->_linkID->lastInsertId();
  447. case 'ORACLE':
  448. case 'OCI':
  449. $sequenceName = $this->table;
  450. $vo = $this->query("SELECT {$sequenceName}.currval currval FROM dual");
  451. return $vo?$vo[0]["currval"]:0;
  452. }
  453. }
  454. }