PageRenderTime 38ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/mcmis/reportico/swsql.php

http://mcmis.googlecode.com/
PHP | 676 lines | 523 code | 87 blank | 66 comment | 69 complexity | 83407c9982fa2ce3eb24228be1e257f6 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /*
  3. Reportico - PHP Reporting Tool
  4. Copyright (C) 2010-2011 Peter Deed
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * File: reportico.php
  17. *
  18. * Contains functionality for parsing SQL statements and
  19. * converting them to queries that can be used by the
  20. * Reportico engine
  21. *
  22. * @link http://www.reportico.org/
  23. * @copyright 2010-2011 Peter Deed
  24. * @author Peter Deed <info@reportico.org>
  25. * @package Reportico
  26. * @version $Id: swsql.php,v 1.4 2011-09-22 21:57:30 peter Exp $
  27. */
  28. /**
  29. * Class reportico_sql_parser
  30. *
  31. * Parses SQL statements entered by user during
  32. * report design mode and imports them into
  33. * the Reportico engine
  34. */
  35. class reportico_sql_parser
  36. {
  37. var $sql;
  38. var $columns = array();
  39. var $tables = array();
  40. var $table_text;
  41. var $where = "";
  42. var $group = "";
  43. var $orders = array();
  44. var $status_message = "";
  45. var $unique = false;
  46. function reportico_sql_parser( $in_sql )
  47. {
  48. $this->sql = $in_sql;
  49. }
  50. function import_into_query( &$in_query )
  51. {
  52. //When importing into query, we need to ensure that we remove
  53. // any columns already existing which do not appear in the
  54. // new query
  55. $delete_columns = array();
  56. foreach ( $in_query->columns as $k => $v )
  57. {
  58. if ( $v->in_select )
  59. {
  60. $delete_columns[$v->query_name] = true;
  61. }
  62. }
  63. foreach ( $this->columns as $col )
  64. {
  65. $qn = $col["name"];
  66. if ( $col["alias"] )
  67. $qn = $col["alias"];
  68. $in_query->create_criteria_column(
  69. $qn, $col["table"], $col["name"], "char", 30, "####", true);
  70. if ( array_key_exists($qn, $delete_columns ) )
  71. {
  72. $delete_columns[$qn] = false;
  73. }
  74. }
  75. $ct = 0;
  76. $tabtext = "";
  77. foreach ( $this->tables as $col )
  78. {
  79. if ( $ct++ > 0 )
  80. $tabtext .= ",";
  81. switch ( $col["jointype"] )
  82. {
  83. case "outer":
  84. $tabtext .= "outer ";
  85. break;
  86. case "inner":
  87. case "default":
  88. }
  89. $tabtext .= $col["name"];
  90. if ( $col["alias"] )
  91. $tabtext .= " ".$col["alias"];
  92. }
  93. $in_query->table_text = $tabtext;
  94. $in_query->table_text = $this->table_text;
  95. $in_query->where_text = "AND ".$this->where;
  96. if ( substr($in_query->where_text, 0, 9) == "AND 1 = 1" )
  97. {
  98. $in_query->where_text = substr($in_query->where_text, 9);
  99. }
  100. if ( $this->group )
  101. $in_query->group_text = "GROUP BY ".$this->group;
  102. else
  103. $in_query->group_text = "";
  104. // Delete existing order columns
  105. $in_query->order_set = array();
  106. foreach ( $this->orders as $col )
  107. {
  108. if ( ($qc = get_query_column($col["name"], $in_query->columns)) )
  109. $in_query->create_order_column( $col["name"], $col["type"] );
  110. }
  111. // Now remove from the parent query any columns which were not in the
  112. // imported SQL
  113. foreach ( $delete_columns as $k => $v )
  114. {
  115. if ( $v )
  116. {
  117. $in_query->remove_column($k);
  118. }
  119. }
  120. // Now order the query columns in the reportico query to reflect the order specified in
  121. // the select statement
  122. $pos = 0;
  123. $xx = false;
  124. foreach ( $this->columns as $col )
  125. {
  126. $pos2 = 0;
  127. $cut = false;
  128. foreach ( $in_query->columns as $k => $v )
  129. {
  130. if ( $v->query_name == $col["alias"] )
  131. {
  132. $cut = array_splice($in_query->columns, $pos2, 1 );
  133. break;
  134. }
  135. $pos2++;
  136. }
  137. if ( $cut )
  138. {
  139. array_splice($in_query->columns, $pos, 0,
  140. $cut );
  141. }
  142. $pos++;
  143. }
  144. $in_query->rowselection = "all";
  145. if ( $this->unique )
  146. $in_query->rowselection = "unique";
  147. }
  148. function display()
  149. {
  150. echo "Columns<br>\n=======<br>\n";
  151. foreach ( $this->columns as $col )
  152. {
  153. echo $col["table"].".".$col["name"];
  154. echo " (".$col["alias"].")";
  155. echo " => ",$col["expression"];
  156. echo "<br>\n";
  157. }
  158. echo "<br>\nTables<br>\n======<br>\n";
  159. foreach ( $this->tables as $col )
  160. {
  161. echo $col["name"];
  162. echo " (".$col["alias"].")";
  163. echo " - ".$col["jointype"];
  164. echo "<br>\n";
  165. }
  166. echo "<br>\nWhere<br>\n=====<br>\n";
  167. echo $this->where;
  168. echo "<br>\n";
  169. echo "<br>\nOrder<br>\n=====<br>\n";
  170. foreach ( $this->orders as $col )
  171. {
  172. echo $col["name"]." ";
  173. echo $col["type"];
  174. echo "<br>\n";
  175. }
  176. }
  177. function parse()
  178. {
  179. $err = false;
  180. $sel_match = "/^\s*SELECT\s*(.*)/is";
  181. $seldup_match = "/^\s*SELECT\s*DISTINCT\s*(.*)/is";
  182. $seldup1_match = "/^\s*SELECT\s*UNIQUE\s*(.*)/is";
  183. $upd_match = "/^\s*UPDATE\s*(.*)/is";
  184. $del_match = "/^\s*DELETE\s*(.*)/is";
  185. $ord = "";
  186. $sql =& $this->sql;
  187. $this->unique = false;
  188. if ( preg_match($sel_match, $sql, $cpt ) )
  189. {
  190. $sel_type = "SELECT";
  191. if ( preg_match($seldup_match, $sql, $cpt ) )
  192. {
  193. $this->unique = true;
  194. }
  195. else if ( preg_match($seldup1_match, $sql, $cpt ) )
  196. {
  197. $this->unique = true;
  198. }
  199. else
  200. {
  201. preg_match($sel_match, $sql, $cpt );
  202. }
  203. // Knock out distinct records ...
  204. $col_match = "/(.*)\s+FROM\s+(.*?WHERE.*)/is";
  205. $tab_match = "/(.*?)\s+WHERE\s+(.*)/is";
  206. $whr_match = "/(.*?)\s+ORDER\s+BY\s+(.*)/is";
  207. $grp_match = "/(.*?)\s+GROUP\s+BY\s+(.*)/is";
  208. $rest = $cpt[1];
  209. if ( !preg_match($col_match, $rest, $cpt ) )
  210. {
  211. $err = "Failure in FROM Decode";
  212. trigger_error("No FROM clause specified.", E_USER_ERROR);
  213. }
  214. else
  215. {
  216. $col = $cpt[1];
  217. $rest = $cpt[2];
  218. }
  219. if ( !$err && !preg_match($tab_match, $rest, $cpt ) )
  220. {
  221. $err = "Failure in TABLE Decode";
  222. trigger_error("No WHERE clause specified. If no clause is required use WHERE 1 = 1", E_USER_ERROR);
  223. }
  224. else
  225. {
  226. $tables = $cpt[1];
  227. $rest = $cpt[2];
  228. }
  229. if ( !$err )
  230. {
  231. if ( !preg_match($whr_match, $rest, $cpt ) )
  232. {
  233. $this->where = $rest;
  234. $rest = "";
  235. }
  236. else
  237. {
  238. $this->where = $cpt[1];
  239. $rest = $cpt[2];
  240. }
  241. }
  242. if ( !$err && $rest )
  243. {
  244. $ord = $rest;
  245. }
  246. if ( !$err && $this->where )
  247. {
  248. if ( preg_match($grp_match, $this->where, $cpt ) )
  249. {
  250. $this->where = $cpt[1];
  251. $this->group = $cpt[2];
  252. }
  253. }
  254. if ( $err )
  255. return false;
  256. if ( $col )
  257. {
  258. $this->parse_column_list($col);
  259. }
  260. if ( $tables )
  261. {
  262. $this->table_text = $tables;
  263. $this->parse_table ( $tables );
  264. }
  265. if ( $this->where )
  266. {
  267. //echo "Where: $this->where\n";
  268. }
  269. if ( $this->group )
  270. {
  271. //echo "Group: $this->group\n";
  272. }
  273. if ( $ord )
  274. {
  275. $this->parse_order ( $ord );
  276. }
  277. }
  278. else
  279. trigger_error("no SELECT clause specified. Query must begin with 'SELECT'", E_USER_ERROR);
  280. if ( preg_match($upd_match, $sql, $cpt ) )
  281. {
  282. print_r($cpt);
  283. $sel_type = "UPDATE";
  284. }
  285. if ( preg_match($del_match, $sql, $cpt ) )
  286. {
  287. print_r($cpt);
  288. $sel_type = "DELETE";
  289. }
  290. }
  291. // -----------------------------------------------------------------------------
  292. // Function : parse_order
  293. // -----------------------
  294. // Will take an order list from a select statement and generate the
  295. // order items
  296. // ----------------------------------------------------------------------------
  297. function parse_order( $in_string )
  298. {
  299. $err = false;
  300. $ordname = "";
  301. if ( ! $collist = preg_split("/\s*,\s*/", $in_string) )
  302. {
  303. $err = "Decode Table List Failure";
  304. return false;
  305. }
  306. foreach ( $collist as $colitem )
  307. {
  308. if ( ! $colset = preg_split("/\s+/", $colitem) )
  309. {
  310. $err = "Decode Column Set List Failure";
  311. return false;
  312. }
  313. // Split out type
  314. $coltype = 'ASC';
  315. if ( preg_match("/\s+/", $colitem, $out_match) )
  316. {
  317. $colitem = $colset[0];
  318. $coltype = $colset[1];
  319. }
  320. // Orderby can be column name, table.column or number
  321. // find the appropriate column from the list and pass
  322. // it to the order list
  323. $tabname = false;
  324. $colname = $colset[0];
  325. if ( preg_match("/\./", $colitem, $out_match) )
  326. {
  327. $colset = preg_split("/\./", $colitem);
  328. $tabname = $colset[0];
  329. $colname = $colset[1];
  330. }
  331. // Handle numeric column
  332. if ( preg_match("/^[0-9]*$/", $colname ) )
  333. {
  334. $i = (int)$colname;
  335. if ( count($this->columns) < $i )
  336. trigger_error("Order By Number ".$i." To High for Select", E_USER_ERROR);
  337. else
  338. $colname = $this->columns[$i-1]["alias"];
  339. if ( !$colname )
  340. $colname = $this->columns[$i-1]["name"];
  341. }
  342. else
  343. {
  344. $matchno=0;
  345. foreach ( $this->columns as $col )
  346. {
  347. if ( $colname == $col["name"] )
  348. {
  349. if ( $tabname && $col["table"] )
  350. {
  351. if ( $tabname == $col["table"] )
  352. {
  353. $colname = $col["alias"];
  354. if ( !$colname )
  355. $colname = $col["name"];
  356. $matchno++;
  357. }
  358. }
  359. else
  360. {
  361. $colname = $col["alias"];
  362. if ( !$colname )
  363. $colname = $col["name"];
  364. $matchno++;
  365. }
  366. }
  367. }
  368. if ( $matchno == 0 )
  369. $this->status_message = "Order $colset[0] cannot be matched in column list";
  370. if ( $matchno > 1 )
  371. trigger_error("Order $colset[0] ambiguous in column list", E_USER_ERROR);
  372. }
  373. $this->orders[] = array
  374. (
  375. "name" => $colname,
  376. "type" => $coltype
  377. );
  378. }
  379. }
  380. // -----------------------------------------------------------------------------
  381. // Function : parse_table
  382. // -----------------------
  383. // Will take a column item from an SQL statement and parse it to identify
  384. // any alias, table identifieror expression
  385. // ----------------------------------------------------------------------------
  386. function parse_table( $in_string )
  387. {
  388. $err = false;
  389. $tabname = "";
  390. $tabalias = "";
  391. if ( ! $collist = preg_split("/\s*,\s*/", $in_string) )
  392. {
  393. $err = "Decode Table List Failure";
  394. return false;
  395. }
  396. foreach ( $collist as $colitem )
  397. {
  398. if ( ! $colset = preg_split("/\s+/", $colitem) )
  399. {
  400. $err = "Decode Column Set List Failure";
  401. return false;
  402. }
  403. // Fetch the table list, fetching details of aliases and outer joins
  404. $ptr = 0;
  405. $tabname = $colset[$ptr];
  406. $jointype = "inner";
  407. if ( $tabname == "outer" )
  408. {
  409. $jointype = "outer";
  410. $ptr++;
  411. }
  412. $tabname = $colset[$ptr++];
  413. if ( count($colset) > $ptr )
  414. $tabalias = $colset[$ptr];
  415. else
  416. $tabalias = "";
  417. $this->tables[] = array
  418. (
  419. "name" => $tabname,
  420. "alias" => $tabalias,
  421. "jointype" => $jointype
  422. );
  423. }
  424. }
  425. function tokenise_columns( $in_string )
  426. {
  427. $escaped = false;
  428. $level_stack = array();
  429. $in_dquote = false;
  430. $in_squote = false;
  431. $rbracket_level = 0;
  432. $sbracket_level = 0;
  433. $collist = array();
  434. $cur = false;
  435. for ( $ct = 0; $ct < strlen($in_string); $ct++ )
  436. {
  437. if ( $ct == 0 )
  438. {
  439. $collist[] = "";
  440. end($collist);
  441. $ky = key($collist);
  442. $cur =& $collist[$ky];
  443. }
  444. $ch = substr($in_string,$ct,1);
  445. $ok_to_add = true;
  446. switch ( $ch )
  447. {
  448. case ",":
  449. if ( !($in_dquote || $in_squote || $rbracket_level > 0 || $sbracket_level > 0) )
  450. {
  451. $collist[] = "";
  452. end($collist);
  453. $ky = key($collist);
  454. $cur =& $collist[$ky];
  455. $ok_to_add = false;
  456. }
  457. break;
  458. case "\"":
  459. if ( $in_dquote )
  460. $in_dquote = false;
  461. else
  462. if ( !$in_squote )
  463. $in_dquote = true;
  464. break;
  465. case "'":
  466. if ( $in_squote )
  467. $in_squote = false;
  468. else
  469. if ( !$in_dquote )
  470. $in_squote = true;
  471. break;
  472. case "(":
  473. if ( !$in_squote && !$in_dquote )
  474. $rbracket_level++;
  475. break;
  476. case ")":
  477. if ( !$in_squote && !$in_dquote )
  478. $rbracket_level--;
  479. break;
  480. case "[":
  481. if ( !$in_squote && !$in_dquote )
  482. $sbracket_level++;
  483. break;
  484. case "]":
  485. if ( !$in_squote && !$in_dquote )
  486. $sbracket_level--;
  487. break;
  488. }
  489. if ($ok_to_add )
  490. $cur .= $ch;
  491. }
  492. return $collist;
  493. }
  494. function parse_column_list( $in_string )
  495. {
  496. $collist = $this->tokenise_columns($in_string);
  497. foreach ( $collist as $k => $colitem )
  498. {
  499. if ( !$this->parse_column($k + 1, trim($colitem)) )
  500. return false;
  501. }
  502. return true;
  503. }
  504. // -----------------------------------------------------------------------------
  505. // Function : parse_column
  506. // -----------------------
  507. // Will take a column item from an SQL statement and parse it to identify
  508. // any alias, table identifieror expression
  509. // ----------------------------------------------------------------------------
  510. function parse_column( $in_colno, $in_string )
  511. {
  512. $err = false;
  513. $colalias = "";
  514. $colname = "";
  515. $coltable = "";
  516. $colexp = "";
  517. // Check for an alias ( any final word which is preceded by any non
  518. // numeric or expression character
  519. // Split out the last two elements
  520. if ( preg_match("/(.+)\s+([^S]*)\s*\$/s", $in_string, $out_match) )
  521. {
  522. if ( preg_match ( "/^\w+$/s", $out_match[2] ) )
  523. {
  524. $colalias = $out_match[2];
  525. $colname = $out_match[1];
  526. $colexp = $colname;
  527. }
  528. else
  529. {
  530. if ( preg_match("/[^0-9A-Za-z_\r\n\t .]/", $in_string ) )
  531. {
  532. $colalias = "column".$in_colno;
  533. trigger_error("Expression value unnamed. Column Name $colalias allocated to expression ($in_string).", E_USER_WARNING);
  534. }
  535. $colname = $in_string;
  536. $colexp = $in_string;
  537. }
  538. }
  539. else
  540. {
  541. // Single column value only so assume no alias
  542. if ( preg_match("/[^0-9A-Za-z_\r\n\t .]/", $in_string ) )
  543. {
  544. $colalias = "column".$in_colno;
  545. trigger_error("Expression value unnamed. Column Name $colalias allocated to expression ($in_string).", E_USER_WARNING);
  546. }
  547. $colname = $in_string;
  548. $colexp = $in_string;
  549. }
  550. // Now with what's left of the column try to ascertain a table name
  551. // and column part
  552. if ( preg_match("/^(\w+)\.(\w+)$/", $colname, $out_match) )
  553. {
  554. $coltable = $out_match[1];
  555. $colname = $out_match[2];
  556. }
  557. $this->columns[] = array(
  558. "name" => $colname,
  559. "table" => $coltable,
  560. "alias" => $colalias,
  561. "expression" => $colexp
  562. )
  563. ;
  564. return true;
  565. }
  566. }