XSDataSource.class.php 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001
  1. <?php
  2. /**
  3. * XSDataSource 批量索引数据源定义文件
  4. *
  5. * @author hightman
  6. * @link http://www.xunsearch.com/
  7. * @copyright Copyright &copy; 2011 HangZhou YunSheng Network Technology Co., Ltd.
  8. * @license http://www.xunsearch.com/license/
  9. * @version $Id$
  10. */
  11. /**
  12. * 索引数据源抽象基类
  13. * 此部分代码仅用于 indexer 工具程序
  14. *
  15. * @author hightman <hightman@twomice.net>
  16. * @version 1.0.0
  17. * @package XS.utilf
  18. */
  19. abstract class XSDataSource
  20. {
  21. protected $type, $arg;
  22. protected $inCli;
  23. private $dataList, $dataPos;
  24. /**
  25. * 构造函数
  26. * @param mixed $arg 对象参数, 常为 SQL 语句或要导入的文件路径
  27. */
  28. public function __construct($type, $arg)
  29. {
  30. $this->type = $type;
  31. $this->arg = $arg;
  32. $this->inCli = php_sapi_name() === 'dpcli';
  33. $this->init();
  34. }
  35. /**
  36. * 取得数据源对象实例
  37. * @param string $type 数据源类型, 如: mysql://.., json, csv ...
  38. * @param mixed $arg 建立对象的参数, 如 SQL 语句, JSON/CSV 文件
  39. * @return XSDataSource 初始化完毕的数据源对象
  40. */
  41. public static function instance($type, $arg = null)
  42. {
  43. $type2 = ($pos = strpos($type, ':')) ? 'database' : $type;
  44. $class = 'XS' . ucfirst(strtolower($type2)) . 'DataSource';
  45. if (!class_exists($class)) {
  46. throw new XSException("Undefined data source type: `$type2'");
  47. }
  48. return new $class($type, $arg);
  49. }
  50. /**
  51. * 从数据源中提取一条数据
  52. * 实际使用时, 一般是循环调用此函数提取数据, 每条数据是由字段名为键的关联数组
  53. * <pre>
  54. * while ($ds->getData() !== false)
  55. * {
  56. * ...
  57. * }
  58. * </pre>
  59. * @return mixed 返回一条完整数据, 若无数据则返回 false
  60. */
  61. final public function getData()
  62. {
  63. if ($this->dataPos === null || $this->dataPos === count($this->dataList)) {
  64. $this->dataPos = 0;
  65. $this->dataList = $this->getDataList();
  66. if (!is_array($this->dataList) || count($this->dataList) === 0) {
  67. $this->deinit();
  68. $this->dataList = $this->dataPos = null;
  69. return false;
  70. }
  71. }
  72. $data = $this->dataList[$this->dataPos];
  73. $this->dataPos++;
  74. return $data;
  75. }
  76. /**
  77. * 取得数据源的准确字符集
  78. * 如不能确定字符集, 请直接返回 false
  79. * @return string 字符集名称
  80. */
  81. public function getCharset()
  82. {
  83. return false;
  84. }
  85. /**
  86. * 执行数据提取的准备工作
  87. * 将自动在第一次提取数据前调用, 请在具体的数据源重载此函数
  88. */
  89. protected function init()
  90. {
  91. }
  92. /**
  93. * 执行数据提取完毕后的清理工作
  94. * 将自动在没有更多数据供提取时调用此函数, 请在具体的数据源重载此函数
  95. */
  96. protected function deinit()
  97. {
  98. }
  99. /**
  100. * 从数据源中提取若干条数据
  101. * 必须在数据源中定义此函数, 返回值必须是各条数据的数组
  102. * @return array
  103. */
  104. protected function getDataList()
  105. {
  106. return false;
  107. }
  108. }
  109. /**
  110. * SQL 数据库源
  111. *
  112. * @author hightman <hightman@twomice.net>
  113. * @version 1.0.0
  114. * @package XS.util
  115. */
  116. class XSDatabaseDataSource extends XSDataSource
  117. {
  118. const PLIMIT = 1000;
  119. private $sql, $offset, $limit;
  120. private $db; /* @var $db XSDatabase */
  121. /**
  122. * 返回数据库输出字符集
  123. * @return mixed 如果数据库不支持 UTF-8 转换则返回 false
  124. */
  125. public function getCharset()
  126. {
  127. if ($this->db->setUtf8()) {
  128. return 'UTF-8';
  129. }
  130. return parent::getCharset();
  131. }
  132. protected function init()
  133. {
  134. if (strstr($this->type, 'sqlite')) {
  135. $pos = strpos($this->type, ':');
  136. $param = array('scheme' => substr($this->type, 0, $pos));
  137. $param['path'] = substr($this->type, $pos + (substr($this->type, $pos + 1, 2) === '//' ? 3 : 1));
  138. } elseif (!($param = parse_url($this->type))) {
  139. throw new XSException('Wrong format of DB connection parameter');
  140. } else {
  141. if (isset($param['user'])) {
  142. $param['user'] = urldecode($param['user']);
  143. }
  144. if (isset($param['pass'])) {
  145. $param['pass'] = urldecode($param['pass']);
  146. }
  147. $param['path'] = isset($param['path']) ? trim($param['path'], '/') : '';
  148. if (empty($param['path'])) {
  149. throw new XSException('Not contain dbname of DB connection parameter');
  150. }
  151. if (($pos = strpos($param['path'], '/')) === false) {
  152. $param['dbname'] = $param['path'];
  153. } else {
  154. $param['dbname'] = substr($param['path'], 0, $pos);
  155. $param['table'] = substr($param['path'], $pos + 1);
  156. }
  157. }
  158. // get driver
  159. $driver = self::getDriverName($param['scheme']);
  160. $class = 'XSDatabase' . ucfirst($driver);
  161. if (!class_exists($class)) {
  162. throw new XSException("Undefined database driver: '$driver'");
  163. }
  164. $this->db = new $class;
  165. $this->db->connect($param);
  166. // set SQL & parse limit/offset
  167. $this->limit = $this->offset = 0;
  168. $sql = $this->arg;
  169. if (empty($sql)) {
  170. if (!isset($param['table'])) {
  171. throw new XSException('Not specified any query SQL or db table');
  172. }
  173. $sql = 'SELECT * FROM ' . $param['table'];
  174. } elseif (preg_match('/ limit\s+(\d+)(?:\s*,\s*(\d+)|\s+offset\s+(\d+))?\s*$/i', $sql, $match)) {
  175. if (isset($match[3])) { // LIMIT xxx OFFSET yyy
  176. $this->offset = intval($match[3]);
  177. $this->limit = intval($match[1]);
  178. } elseif (isset($match[2])) { // LIMIT yyy, xxx
  179. $this->offset = intval($match[1]);
  180. $this->limit = intval($match[2]);
  181. } else { // lIMIT xxx
  182. $this->limit = intval($match[1]);
  183. }
  184. $sql = substr($sql, 0, strlen($sql) - strlen($match[0]));
  185. }
  186. $this->sql = $sql;
  187. if ($this->limit == 0) {
  188. $sql = preg_replace('/SELECT\s+.+?FROM\s/i', 'SELECT COUNT(*) AS count FROM ', $sql);
  189. $res = $this->db->query1($sql);
  190. $this->limit = $res['count'] - $this->offset;
  191. }
  192. }
  193. protected function deinit()
  194. {
  195. $this->db->close();
  196. }
  197. /**
  198. * 返回一批数据
  199. * @return 结果数组, 没有更多数据时返回 false
  200. */
  201. protected function getDataList()
  202. {
  203. if ($this->limit <= 0) {
  204. return false;
  205. }
  206. $sql = $this->sql . ' LIMIT ' . min(self::PLIMIT, $this->limit) . ' OFFSET ' . $this->offset;
  207. $this->limit -= self::PLIMIT;
  208. $this->offset += self::PLIMIT;
  209. return $this->db->query($sql);
  210. }
  211. /**
  212. * 取解数据连接驱动名
  213. */
  214. private static function getDriverName($scheme)
  215. {
  216. $name = strtr(strtolower($scheme), '.-', '__');
  217. if ($name == 'mysql' && !function_exists('mysql_connect')) {
  218. if (class_exists('mysqli')) {
  219. $name = 'mysqli';
  220. } elseif (extension_loaded('pdo_mysql')) {
  221. $name = 'pdo_mysql';
  222. }
  223. }
  224. if ($name == 'sqlite' && !function_exists('sqlite_open')) {
  225. if (class_exists('sqlite3')) {
  226. $name = 'sqlite3';
  227. } elseif (extension_loaded('pdo_sqlite')) {
  228. $name = 'pdo_sqlite';
  229. }
  230. }
  231. if ($name == 'sqlite3' && !class_exists('sqlite3') && extension_loaded('pdo_sqlite')) {
  232. $name = 'pdo_sqlite';
  233. }
  234. if (substr($name, 0, 4) != 'pdo_' && extension_loaded('pdo_' . $name)) {
  235. $name = 'pdo_' . $name;
  236. }
  237. return $name;
  238. }
  239. }
  240. /**
  241. * JSON 数据源
  242. * 要求以 \n (换行符) 分割, 每行为一条完整的 json 数据
  243. *
  244. * @author hightman <hightman@twomice.net>
  245. * @version 1.0.0
  246. * @package XS.util
  247. */
  248. class XSJsonDataSource extends XSDataSource
  249. {
  250. private $fd, $line;
  251. public $invalidLines = 0;
  252. protected function init()
  253. {
  254. $file = $this->arg;
  255. if (empty($file) && $this->inCli) {
  256. echo "WARNING: input file not specified, read data from <STDIN>\n";
  257. $file = 'php://stdin';
  258. }
  259. if (!($this->fd = fopen($file, 'r'))) {
  260. throw new XSException("Can not open input file: '$file'");
  261. }
  262. $this->line = 0;
  263. }
  264. protected function deinit()
  265. {
  266. if ($this->fd) {
  267. fclose($this->fd);
  268. $this->fd = null;
  269. }
  270. }
  271. protected function getDataList()
  272. {
  273. // read line (check to timeout?)
  274. $line = '';
  275. while (true) {
  276. $buf = fgets($this->fd, 8192);
  277. if ($buf === false || strlen($buf) === 0) {
  278. break;
  279. }
  280. $line .= $buf;
  281. if (strlen($buf) < 8191 || substr($buf, - 1, 1) === "\n") {
  282. break;
  283. }
  284. }
  285. // empty line (end of file)
  286. if (empty($line)) {
  287. if ($this->inCli) {
  288. echo "INFO: reach end of the file, total lines: " . $this->line . "\n";
  289. }
  290. return false;
  291. }
  292. // try to decode the line
  293. $this->line++;
  294. $line = rtrim($line, "\r\n");
  295. if (strlen($line) === 0) {
  296. if ($this->inCli) {
  297. echo "WARNING: empty line #" . $this->line . "\n";
  298. }
  299. $this->invalidLines++;
  300. return $this->getDataList();
  301. }
  302. $item = json_decode($line, true);
  303. if (!is_array($item) || count($item) === 0) {
  304. switch (json_last_error()) {
  305. case JSON_ERROR_DEPTH:
  306. $error = ' - Maximum stack depth exceeded';
  307. break;
  308. case JSON_ERROR_CTRL_CHAR:
  309. $error = ' - Unexpected control character found';
  310. break;
  311. case JSON_ERROR_SYNTAX:
  312. $error = ' - Syntax error, malformed JSON';
  313. break;
  314. default :
  315. $error = (count($item) === 0 ? ' - Empty array' : '');
  316. break;
  317. }
  318. if ($this->inCli) {
  319. echo "WARNING: invalid line #" . $this->line . $error . "\n";
  320. }
  321. $this->invalidLines++;
  322. return $this->getDataList();
  323. }
  324. return array($item);
  325. }
  326. }
  327. /**
  328. * CSV 数据源
  329. * 可在文件开头指定字段(必须是有效字段), 否则将默认按照 {@link XS} 项目字段顺序填充
  330. *
  331. * @author hightman <hightman@twomice.net>
  332. * @version 1.0.0
  333. * @package XS.util
  334. */
  335. class XSCsvDataSource extends XSDataSource
  336. {
  337. private $delim = ',', $line;
  338. public $invalidLines = 0;
  339. protected function init()
  340. {
  341. $file = $this->arg;
  342. if (empty($file) && $this->inCli) {
  343. echo "WARNING: input file not specified, read data from <STDIN>\n";
  344. $file = 'php://stdin';
  345. }
  346. if (!($this->fd = fopen($file, 'r'))) {
  347. throw new XSException("Can not open input file: '$file'");
  348. }
  349. $this->line = 0;
  350. if (isset($_SERVER['XS_CSV_DELIMITER'])) {
  351. $this->delim = $_SERVER['XS_CSV_DELIMITER'];
  352. }
  353. }
  354. protected function deinit()
  355. {
  356. if ($this->fd) {
  357. fclose($this->fd);
  358. $this->fd = null;
  359. }
  360. }
  361. protected function getDataList()
  362. {
  363. if (($item = fgetcsv($this->fd, 0, $this->delim)) === false) {
  364. if ($this->inCli) {
  365. echo "INFO: reach end of file or error occured, total lines: " . $this->line . "\n";
  366. }
  367. return false;
  368. }
  369. $this->line++;
  370. if (count($item) === 1 && is_null($item[0])) {
  371. if ($this->inCli) {
  372. echo "WARNING: invalid csv line #" . $this->line . "\n";
  373. }
  374. $this->invalidLines++;
  375. return $this->getDataList();
  376. }
  377. return array($item);
  378. }
  379. }
  380. /**
  381. * 数据库操作基类
  382. * 定义了 SQL 数据库源的四个基本操作: connect/query/close/setUtf8
  383. *
  384. * @author hightman <hightman@twomice.net>
  385. * @version 1.0.0
  386. * @package XS.util.db
  387. */
  388. abstract class XSDatabase
  389. {
  390. /**
  391. * 连接数据库
  392. * @param array $param 连接参数, 采用 parse_url 解析, 可能包含: scheme,user,pass,host,path,table,dbname ...
  393. */
  394. abstract public function connect($param);
  395. /**
  396. * 关闭数据库连接
  397. */
  398. abstract public function close();
  399. /**
  400. * 查询 SQL 语句
  401. * @return mixed 非 SELECT 语句返回执行结果(true/false), SELECT 语句返回所有结果行的数组
  402. */
  403. abstract public function query($sql);
  404. /**
  405. * 设置数据库字符集为 UTF-8
  406. * @return bool 如果数据库能直接输出 UTF-8 编码则返回 true 否则返回 false
  407. */
  408. public function setUtf8()
  409. {
  410. return false;
  411. }
  412. /**
  413. * 查询数据库首行
  414. * @param string $sql
  415. * @return 查询结果首行, 失败或无数据则返回 false
  416. */
  417. public function query1($sql)
  418. {
  419. $sql = preg_replace('/ limit\s+(\d+)(?:\s*,\s*(\d+)|\s+offset\s+(\d+))?\s*$/i', '', $sql);
  420. $sql .= ' LIMIT 1';
  421. $res = $this->query($sql);
  422. return (is_array($res) && isset($res[0])) ? $res[0] : false;
  423. }
  424. }
  425. /**
  426. * 使用传统 MySQL 扩展
  427. *
  428. * @author hightman <hightman@twomice.net>
  429. * @version 1.0.0
  430. * @package XS.util.db
  431. */
  432. class XSDatabaseMySQL extends XSDatabase
  433. {
  434. private $link;
  435. /**
  436. * 连接数据库
  437. * @param array $param 连接参数, 包含: user,pass,host,table,dbname ...
  438. */
  439. public function connect($param)
  440. {
  441. $host = isset($param['host']) ? $param['host'] : ini_get('mysql.default_host');
  442. $host .= (isset($param['port']) && $param['port'] != 3306) ? ':' . $param['port'] : '';
  443. $user = isset($param['user']) ? $param['user'] : ini_get('mysql.default_user');
  444. $pass = isset($param['pass']) ? $param['pass'] : ini_get('mysql.default_pw');
  445. if (($this->link = mysql_connect($host, $user, $pass)) === false) {
  446. throw new XSException("Can not connect to mysql server: '$user@$host'");
  447. }
  448. if (!mysql_select_db($param['dbname'], $this->link)) {
  449. $this->close();
  450. throw new XSException("Can not switch to database name: '{$param['dbname']}'");
  451. }
  452. $this->setUtf8();
  453. }
  454. /**
  455. * 关闭数据库连接
  456. */
  457. public function close()
  458. {
  459. if ($this->link) {
  460. mysql_close($this->link);
  461. $this->link = null;
  462. }
  463. }
  464. /**
  465. * 执行 SQL 语句查询
  466. * @param string $sql 要执行的 SQL 语句
  467. * @return mixed
  468. */
  469. public function query($sql)
  470. {
  471. //echo "[DEBUG] SQL: $sql\n";
  472. $res = mysql_query($sql, $this->link);
  473. if ($res === false) {
  474. throw new XSException('MySQL ERROR(#' . mysql_errno($this->link) . '): ' . mysql_error($this->link));
  475. }
  476. if (!is_resource($res)) {
  477. $ret = $res;
  478. } else {
  479. $ret = array();
  480. while ($tmp = mysql_fetch_assoc($res)) {
  481. $ret[] = $tmp;
  482. }
  483. mysql_free_result($res);
  484. }
  485. return $ret;
  486. }
  487. /**
  488. * 将输出字符集设置为 UTF-8
  489. * @return bool MySQL 自 4.1.0 起支持字符集
  490. */
  491. public function setUtf8()
  492. {
  493. if (version_compare(mysql_get_server_info($this->link), '4.1.0', '>=')) {
  494. return @mysql_query("SET NAMES utf8", $this->link);
  495. }
  496. return false;
  497. }
  498. }
  499. /**
  500. * 面向对象的 PostgreSQL 扩展
  501. *
  502. * @author freechoice <freechoice@qq.com>
  503. * @version 1.0.0
  504. * @package XS.util.db
  505. */
  506. class XSDatabasePgSQL extends XSDatabase
  507. {
  508. private $link;
  509. public function connect($param)
  510. {
  511. $dsn = "host={$param['host']} ";
  512. $dsn .= isset($param['port']) ? "port={$param['port']} " : '';
  513. $dsn .= "dbname={$param['dbname']} user={$param['user']} password={$param['pass']}";
  514. if (!($this->link = @pg_connect($dsn))) {
  515. throw new XSException('Error connecting to PGSQL database:' . $param['dbname'] . '.');
  516. pg_set_error_verbosity($this->link, PGSQL_ERRORS_DEFAULT);
  517. pg_query('SET standard_conforming_strings=off');
  518. }
  519. }
  520. /**
  521. * 关闭数据库连接
  522. */
  523. public function close()
  524. {
  525. if (is_resource($this->link)) {
  526. pg_close($this->link);
  527. $this->link = null;
  528. }
  529. }
  530. /**
  531. * 执行 SQL 语句查询
  532. * @param string $sql 要执行的 SQL 语句
  533. * @return mixed
  534. */
  535. public function query($query)
  536. {
  537. //echo "[DEBUG] SQL: $sql\n";
  538. $res = pg_query($this->link, $query);
  539. if ($res === false) {
  540. throw new XSException('PgSQL ERROR: ' . pg_last_error($this->link));
  541. }
  542. $ret = array();
  543. while ($tmp = pg_fetch_assoc($res)) {
  544. $ret[] = $tmp;
  545. }
  546. pg_free_result($res);
  547. return $ret;
  548. }
  549. /**
  550. * 将输出字符集设置为 UTF-8
  551. */
  552. public function setUtf8()
  553. {
  554. pg_set_client_encoding($this->link, 'UTF8');
  555. }
  556. }
  557. /**
  558. * 面向对象的 MySQLI 扩展
  559. *
  560. * @author hightman <hightman@twomice.net>
  561. * @version 1.0.0
  562. * @package XS.util.db
  563. */
  564. class XSDatabaseMySQLI extends XSDatabase
  565. {
  566. private $obj;
  567. /**
  568. * 连接数据库
  569. * @param array $param 连接参数, 包含: user,pass,host,table,dbname ...
  570. */
  571. public function connect($param)
  572. {
  573. $host = isset($param['host']) ? $param['host'] : ini_get('mysqli.default_host');
  574. $user = isset($param['user']) ? $param['user'] : ini_get('mysqli.default_user');
  575. $pass = isset($param['pass']) ? $param['pass'] : ini_get('mysqli.default_pw');
  576. $port = isset($param['port']) ? $param['port'] : ini_get('mysqli.default_port');
  577. $this->obj = new mysqli($host, $user, $pass, '', $port);
  578. if ($this->obj->connect_error) {
  579. throw new XSException("Can not connect to mysql server: '$user@$host'");
  580. }
  581. if (!$this->obj->select_db($param['dbname'])) {
  582. $this->close();
  583. throw new XSException("Can not switch to database name: '{$param['dbname']}'");
  584. }
  585. $this->setUtf8();
  586. }
  587. /**
  588. * 关闭数据库连接
  589. */
  590. public function close()
  591. {
  592. if ($this->obj) {
  593. $this->obj->close();
  594. $this->obj = null;
  595. }
  596. }
  597. /**
  598. * 执行 SQL 语句查询
  599. * @param string $sql 要执行的 SQL 语句
  600. * @return mixed
  601. */
  602. public function query($sql)
  603. {
  604. //echo "[DEBUG] SQL: $sql\n";
  605. $res = $this->obj->query($sql);
  606. if ($res === false) {
  607. throw new XSException('MySQL ERROR(#' . $this->obj->error . '): ' . $this->obj->errno);
  608. }
  609. if (!is_object($res)) {
  610. $ret = $res;
  611. } else {
  612. $ret = array();
  613. while ($tmp = $res->fetch_assoc()) {
  614. $ret[] = $tmp;
  615. }
  616. $res->free();
  617. }
  618. return $ret;
  619. }
  620. /**
  621. * 将输出字符集设置为 UTF-8
  622. * @return bool 始终返回 true
  623. */
  624. public function setUtf8()
  625. {
  626. $this->obj->set_charset('utf8');
  627. return true;
  628. }
  629. }
  630. /**
  631. * 使用传统的 SQLite 扩展
  632. *
  633. * @author hightman <hightman@twomice.net>
  634. * @version 1.0.0
  635. * @package XS.util.db
  636. */
  637. class XSDatabaseSQLite extends XSDatabase
  638. {
  639. private $link;
  640. /**
  641. * 打开数据库
  642. * @param array $param 连接参数, 包含: path
  643. */
  644. public function connect($param)
  645. {
  646. if (($this->link = sqlite_open($param['path'])) === false) {
  647. throw new XSException("Can not open sqlite file: '{$param['path']}'");
  648. }
  649. }
  650. /**
  651. * 关闭数据库
  652. */
  653. public function close()
  654. {
  655. if ($this->link) {
  656. sqlite_close($this->link);
  657. $this->link = null;
  658. }
  659. }
  660. /**
  661. * 执行 SQL 语句查询
  662. * @param string $sql 要执行的 SQL 语句
  663. * @return mixed
  664. */
  665. public function query($sql)
  666. {
  667. //echo "[DEBUG] SQL: $sql\n";
  668. $res = sqlite_query($this->link, $sql);
  669. if ($res === false) {
  670. throw new XSException('SQLITE ERROR: ' . sqlite_error_string($this->link));
  671. }
  672. if (!is_resource($res)) {
  673. $ret = $res;
  674. } else {
  675. $ret = array();
  676. while ($tmp = sqlite_fetch_array($res, SQLITE_ASSOC)) {
  677. $ret[] = $tmp;
  678. }
  679. }
  680. return $ret;
  681. }
  682. }
  683. /**
  684. * 面向对象的 SQLite3 扩展
  685. *
  686. * @author hightman <hightman@twomice.net>
  687. * @version 1.0.0
  688. * @package XS.util.db
  689. */
  690. class XSDatabaseSQLite3 extends XSDatabase
  691. {
  692. private $obj;
  693. /**
  694. * 打开数据库
  695. * @param array $param 连接参数, 包含: path
  696. */
  697. public function connect($param)
  698. {
  699. try {
  700. $this->obj = new SQLite3($param['path'], SQLITE3_OPEN_READONLY);
  701. } catch (Exception $e) {
  702. throw new XSException($e->getMessage());
  703. }
  704. }
  705. /**
  706. * 关闭数据库
  707. */
  708. public function close()
  709. {
  710. if ($this->obj) {
  711. $this->obj->close();
  712. $this->obj = null;
  713. }
  714. }
  715. /**
  716. * 执行 SQL 语句查询
  717. * @param string $sql 要执行的 SQL 语句
  718. * @return mixed
  719. */
  720. public function query($sql)
  721. {
  722. //echo "[DEBUG] SQL: $sql\n";
  723. $res = $this->obj->query($sql);
  724. if ($res === false) {
  725. throw new XSException('SQLITE3 ERROR(#' . $this->obj->lastErrorCode() . '): ' . $this->obj->lastErrorMsg());
  726. }
  727. if (!is_object($res)) {
  728. $ret = $res;
  729. } else {
  730. $ret = array();
  731. while ($tmp = $res->fetchArray(SQLITE3_ASSOC)) {
  732. $ret[] = $tmp;
  733. }
  734. $res->finalize();
  735. }
  736. return $ret;
  737. }
  738. }
  739. /**
  740. * 面向对象的 PDO 扩展基类
  741. *
  742. * @author hightman <hightman@twomice.net>
  743. * @version 1.0.0
  744. * @package XS.util.db
  745. */
  746. abstract class XSDatabasePDO extends XSDatabase
  747. {
  748. protected $obj;
  749. /**
  750. * 连接数据库
  751. * 具体的每个类必须实现 {@link makeDsn} 来将参数转换为 dsn
  752. * @param array $param 连接参数, 包含: user, pass ...
  753. * @see makeDsn
  754. */
  755. public function connect($param)
  756. {
  757. $dsn = $this->makeDsn($param);
  758. $user = isset($param['user']) ? $param['user'] : 'root';
  759. $pass = isset($param['pass']) ? $param['pass'] : '';
  760. try {
  761. $this->obj = new PDO($dsn, $user, $pass);
  762. } catch (PDOException $e) {
  763. throw new XSException($e->getMessage());
  764. }
  765. }
  766. /**
  767. * 关闭数据库
  768. */
  769. public function close()
  770. {
  771. $this->obj = null;
  772. }
  773. /**
  774. * 执行 SQL 语句
  775. * @param string $sql 要执行的 SQL 语句
  776. * @return mixed
  777. */
  778. public function query($sql)
  779. {
  780. //echo "[DEBUG] SQL: $sql\n";
  781. $res = $this->obj->query($sql);
  782. if ($res === false) {
  783. $info = $this->obj->errorInfo();
  784. throw new XSException('SQLSTATE[' . $info[0] . '] [' . $info[1] . '] ' . $info[2]);
  785. }
  786. $ret = $res->fetchAll(PDO::FETCH_ASSOC);
  787. return $ret;
  788. }
  789. /**
  790. * 提取参数内容生成 PDO 连接专用的 DSN
  791. * @param array $param
  792. */
  793. abstract protected function makeDsn($param);
  794. }
  795. /**
  796. * PDO.MySQL 实现
  797. *
  798. * @author hightman <hightman@twomice.net>
  799. * @version 1.0.0
  800. * @package XS.util.db
  801. */
  802. class XSDatabasePDO_MySQL extends XSDatabasePDO
  803. {
  804. /**
  805. * 生成 MySQL DSN
  806. * @param array $param 包含 host, port, dbname
  807. * @return string
  808. */
  809. protected function makeDsn($param)
  810. {
  811. $dsn = 'mysql:host=' . (isset($param['host']) ? $param['host'] : 'localhost');
  812. if (isset($param['port']) && $param['port'] !== 3306) {
  813. $dsn .= ';port=' . $param['port'];
  814. }
  815. $dsn .= ';dbname=' . $param['dbname'];
  816. return $dsn;
  817. }
  818. /**
  819. * 将输出字符集设置为 UTF-8
  820. * @return bool 始终返回 true
  821. */
  822. public function setUtf8()
  823. {
  824. // BUGFIXED: 此处应为不带引号的 utf8
  825. return $this->obj->prepare("SET NAMES utf8")->execute();
  826. }
  827. }
  828. /**
  829. * PDO.Pgsql 实现
  830. *
  831. * @author freechoice <freechoice@qq.com>
  832. * @version 1.0.0
  833. * @package XS.util.db
  834. */
  835. class XSDatabasePDO_PgSQL extends XSDatabasePDO
  836. {
  837. /**
  838. * 生成 Postgres DSN
  839. * @param array $param 包含 path 为数据库路径
  840. * @return string
  841. */
  842. protected function makeDsn($param)
  843. {
  844. $dsn = "pgsql:host={$param['host']};";
  845. $dsn .= isset($param['port']) ? "port={$param['port']};" : '';
  846. $dsn .= "dbname={$param['dbname']};client_encoding=utf-8";
  847. return $dsn;
  848. }
  849. /**
  850. * 将输出字符集设置为 UTF-8
  851. */
  852. public function setUtf8()
  853. {
  854. return true;
  855. }
  856. }
  857. /**
  858. * PDO.SQLite 实现
  859. *
  860. * @author hightman <hightman@twomice.net>
  861. * @version 1.0.0
  862. * @package XS.util.db
  863. */
  864. class XSDatabasePDO_SQLite extends XSDatabasePDO
  865. {
  866. /**
  867. * 生成 SQLite DSN
  868. * @param array $param 包含 path 为数据库路径
  869. * @return string
  870. */
  871. protected function makeDsn($param)
  872. {
  873. $dsn = 'sqlite:' . $param['path'];
  874. return $dsn;
  875. }
  876. }
  877. /**
  878. * 数据过滤器的接口
  879. * 以便在提交到索引前有一个修改和调整数据的机会
  880. *
  881. * @author hightman <hightman@twomice.net>
  882. * @since 1.1.0
  883. * @package XS.util
  884. */
  885. interface XSDataFilter
  886. {
  887. /**
  888. * 字段数据处理函数
  889. * @param array $data 字段名和值组成的数据数组
  890. * @param mixed $cs 数据字符集, 默认 false 表示无法确定源字符集
  891. * @return mixed 返回处理后的数据数组, 返回 false 表示本条数据不加入索引
  892. */
  893. public function process($data, $cs = false);
  894. /**
  895. * 索引文档处理函数
  896. * 在此通过 {@link XSDocument::addIndex} 或 {@link XSDocument::addTerm} 做索引相关调整
  897. * @param XSDocument $doc 索引文档
  898. * @since 1.3.4
  899. */
  900. public function processDoc($doc);
  901. }
  902. /**
  903. * 内置调试过滤器, 直接打印数据内容
  904. *
  905. * @author hightman <hightman@twomice.net>
  906. * @version 1.0.0
  907. * @package XS.util
  908. */
  909. class XSDebugFilter implements XSDataFilter
  910. {
  911. public function process($data, $cs = false)
  912. {
  913. echo "\n----- DEBUG DATA INFO -----\n";
  914. print_r($data);
  915. return $data;
  916. }
  917. public function processDoc($doc)
  918. {
  919. }
  920. }