PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/Query.php

https://bitbucket.org/mstetson/obiblio/
PHP | 372 lines | 295 code | 12 blank | 65 comment | 49 complexity | 9147d9da2adeb783e5f0c7e6fb153cd7 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /* This file is part of a copyrighted work; it is distributed with NO WARRANTY.
  3. * See the file COPYRIGHT.html for more details.
  4. */
  5. $_Query_lock_depth = 0;
  6. class Query {
  7. var $_link;
  8. /* This constructor will never do more than call connect_e() and throw a
  9. * fatal error if it fails. If you want to catch the error, subclass Query and
  10. * call connect_e() yourself.
  11. */
  12. function Query() {
  13. $e = $this->connect_e();
  14. if ($e) {
  15. Fatal::dbError($e->sql, $e->msg, $e->dberror);
  16. }
  17. }
  18. function connect_e() {
  19. list($this->_link, $e) = Query::_connect_e();
  20. return $e;
  21. }
  22. /* This static method shares the actual DBMS connection
  23. * with all Query instances.
  24. */
  25. function _connect_e() {
  26. static $link;
  27. if (!isset($link)) {
  28. if (!function_exists('mysql_connect')) {
  29. return array(NULL, new DbError("Checking for MySQL Extension...",
  30. "Unable to connect to database.",
  31. "The MySQL extension is not available"));
  32. }
  33. $link = mysql_connect(OBIB_HOST,OBIB_USERNAME,OBIB_PWD);
  34. if (!$link) {
  35. return array(NULL, new DbError("Connecting to database server...",
  36. "Cannot connect to database server.",
  37. mysql_error()));
  38. }
  39. $rc = mysql_select_db(OBIB_DATABASE, $link);
  40. if (!$rc) {
  41. return array(NULL, new DbError("Selecting database...",
  42. "Cannot select database.",
  43. mysql_error($link)));
  44. }
  45. }
  46. return array($link, NULL);
  47. }
  48. function act($sql) {
  49. $results = $this->_act($sql);
  50. if (!is_bool($results)) {
  51. Fatal::dbError($sql, "Action query returned results.", 'No DBMS error.');
  52. }
  53. }
  54. function select($sql) {
  55. $results = $this->_act($sql);
  56. if (is_bool($results)) {
  57. Fatal::dbError($sql, "Select did not return results.", 'No DBMS error.');
  58. }
  59. return new DbIter($results);
  60. }
  61. function select1($sql) {
  62. $r = $this->select($sql);
  63. if ($r->count() != 1) {
  64. Fatal::dbError($sql, 'Wrong number of result rows: expected 1, got '.$r->count(),
  65. 'No DBMS Error');
  66. } else {
  67. return $r->next();
  68. }
  69. }
  70. function select01($sql) {
  71. $r = $this->select($sql);
  72. if ($r->count() == 0) {
  73. return NULL;
  74. } else if ($r->count() != 1) {
  75. Fatal::dbError($sql, 'Wrong number of result rows: expected 0 or 1, got '.$r->count(),
  76. 'No DBMS Error');
  77. } else {
  78. return $r->next();
  79. }
  80. }
  81. function _act($sql) {
  82. if (!$this->_link) {
  83. Fatal::internalError('Tried to make database query before connection.');
  84. }
  85. $r = mysql_query($sql, $this->_link);
  86. if ($r === false) {
  87. Fatal::dbError($sql, 'Database query failed', mysql_error());
  88. }
  89. return $r;
  90. }
  91. /* This is not easily portable to many SQL DBMSs. A better scheme
  92. * might be something like PEAR::DB's sequences.
  93. */
  94. function getInsertID() {
  95. return mysql_insert_id($this->_link);
  96. }
  97. /* Locking functions
  98. *
  99. * Besides switching to InnoDB for transactions, I haven't been able to
  100. * come up with a good way to do locking reliably. For now, we'll get
  101. * and release one big advisory lock around every sensitive transaction
  102. * and every database write except per-session data. This should make
  103. * everything work, even if it is heavy-handed.
  104. *
  105. * Calls to lock/unlock may be nested, but must be paired.
  106. */
  107. function lock() {
  108. global $_Query_lock_depth;
  109. if ($_Query_lock_depth < 0) {
  110. Fatal::internalError('Negative lock depth');
  111. }
  112. if ($_Query_lock_depth == 0) {
  113. $row = $this->select1($this->mkSQL('select get_lock(%Q, %N) as locked',
  114. OBIB_LOCK_NAME, OBIB_LOCK_TIMEOUT));
  115. if (!isset($row['locked']) or $row['locked'] != 1) {
  116. Fatal::cantLock();
  117. }
  118. }
  119. $_Query_lock_depth++;
  120. }
  121. function unlock() {
  122. global $_Query_lock_depth;
  123. if ($_Query_lock_depth <= 0) {
  124. Fatal::internalError('Tried to unlock an unlocked database.');
  125. }
  126. $_Query_lock_depth--;
  127. if ($_Query_lock_depth == 0) {
  128. $this->act($this->mkSQL('do release_lock(%Q)',
  129. OBIB_LOCK_NAME));
  130. }
  131. }
  132. /****************************************************************************
  133. * Makes SQL by interpolating values into a format string.
  134. * This function works something like printf() for SQL queries. Format
  135. * strings contain %-escapes signifying that a value from the argument
  136. * list should be inserted into the string at that point. The routine
  137. * properly quotes or transforms the value to make sure that it will be
  138. * handled correctly by the SQL server. The recognized format strings
  139. * are as follows:
  140. * %% - is replaced by a single '%' character and does not consume a
  141. * value form the argument list.
  142. * %! - inserts the argument in the query unaltered -- BE CAREFUL!
  143. * %B - treates the argument as a boolean value and inserts either
  144. * 'Y' or 'N'as appropriate.
  145. * %C - treats the argument as a column reference. This differs from
  146. * %I below only in that it passes the '.' operator for separating
  147. * table and column names on to the SQL server unquoted.
  148. * %I - treats the argument as an identifier to be quoted.
  149. * %i - does the same escaping as %I, but does not add surrounding
  150. * quotation marks.
  151. * %N - treats the argument as a number and strips off all of it but
  152. * an initial numeric string with optional sign and decimal point.
  153. * %Q - treats the argument as a string and quotes it.
  154. * %q - does the same escaping as %Q, but does not add surrounding
  155. * quotation marks.
  156. * @param string $fmt format string
  157. * @param string ... optional argument values
  158. * @return string the result of interpreting $fmt
  159. * @access public
  160. ****************************************************************************
  161. */
  162. function mkSQL() {
  163. $n = func_num_args();
  164. if ($n < 1) {
  165. Fatal::internalError('Not enough arguments given to mkSQL().');
  166. }
  167. $i = 1;
  168. $SQL = "";
  169. $fmt = func_get_arg(0);
  170. while (strlen($fmt)) {
  171. $p = strpos($fmt, "%");
  172. if ($p === false) {
  173. $SQL .= $fmt;
  174. break;
  175. }
  176. $SQL .= substr($fmt, 0, $p);
  177. if (strlen($fmt) < $p+2) {
  178. Fatal::internalError('Bad mkSQL() format string.');
  179. }
  180. if ($fmt{$p+1} == '%') {
  181. $SQL .= "%";
  182. } else {
  183. if ($i >= $n) {
  184. Fatal::internalError('Not enough arguments given to mkSQL().');
  185. }
  186. $arg = func_get_arg($i++);
  187. switch ($fmt{$p+1}) {
  188. case '!':
  189. /* very dangerous, but sometimes very useful -- be careful */
  190. $SQL .= $arg;
  191. break;
  192. case 'B':
  193. if ($arg) {
  194. $SQL .= "'Y'";
  195. } else {
  196. $SQL .= "'N'";
  197. }
  198. break;
  199. case 'C':
  200. $a = array();
  201. foreach (explode('.', $arg) as $ident) {
  202. array_push($a, '`'.$this->_ident($ident).'`');
  203. }
  204. $SQL .= implode('.', $a);
  205. break;
  206. case 'I':
  207. $SQL .= '`'.$this->_ident($arg).'`';
  208. break;
  209. case 'i':
  210. $SQL .= $this->_ident($arg);
  211. break;
  212. case 'N':
  213. $SQL .= $this->_numstr($arg);
  214. break;
  215. case 'Q':
  216. $SQL .= "'".mysql_real_escape_string($arg, $this->_link)."'";
  217. break;
  218. case 'q':
  219. $SQL .= mysql_real_escape_string($arg, $this->_link);
  220. break;
  221. default:
  222. Fatal::internalError('Bad mkSQL() format string.');
  223. }
  224. }
  225. $fmt = substr($fmt, $p+2);
  226. }
  227. if ($i != $n) {
  228. Fatal::internalError('Too many arguments to mkSQL().');
  229. }
  230. return $SQL;
  231. }
  232. function _ident($i) {
  233. # Because the MySQL manual is unclear on how to include a ` in a `-quoted
  234. # identifer, we just drop them. The manual does not say whether backslash
  235. # escapes are interpreted in quoted identifiers, so I assume they are not.
  236. return str_replace('`', '', $i);
  237. }
  238. function _numstr($n) {
  239. if (preg_match("/^([+-]?[0-9]+(\.[0-9]*)?([Ee][0-9]+)?)/", $n, $subs)) {
  240. return $subs[1];
  241. } else {
  242. return "0";
  243. }
  244. }
  245. /* Everything below is just a compatibility interface
  246. * for the last few iterations of this design. Don't use
  247. * it. This will be removed as soon as I get time to
  248. * update everything that depends on this stuff.
  249. */
  250. function connect($conn=false) {
  251. return true;
  252. }
  253. function close() {
  254. return true;
  255. }
  256. function _exec($sql) {
  257. $r = $this->_act($sql);
  258. $this->_conn = new DbOld($r, $this->getInsertId());
  259. return $r;
  260. }
  261. function exec($sql) {
  262. $r = $this->_exec($sql);
  263. if (is_bool($r)) {
  264. return $r;
  265. } else {
  266. $rows = array();
  267. while($row = mysql_fetch_assoc($r)) {
  268. $rows[] = $row;
  269. }
  270. return $rows;
  271. }
  272. }
  273. function eexec($sql) {
  274. return $this->exec($sql);
  275. }
  276. function _query($sql, $msg) {
  277. $r = $this->_act($sql);
  278. $this->_conn = new DbOld($r, $this->getInsertId());
  279. return $r;
  280. }
  281. function _checkSubQuery(&$q, $result) {
  282. return $result;
  283. }
  284. function resetResult() {
  285. $this->_conn->resetResult();
  286. }
  287. function clearErrors() {
  288. return;
  289. }
  290. function errorOccurred() {
  291. return false;
  292. }
  293. function getError() {
  294. if (isset($this->_error)){
  295. return $this->_error;
  296. } else {
  297. return "";
  298. }
  299. }
  300. function getDbErrno() {
  301. return 0;
  302. }
  303. function getDbError() {
  304. return "";
  305. }
  306. function getSQL() {
  307. return "";
  308. }
  309. }
  310. class DbIter extends Iter {
  311. function DbIter($results) {
  312. $this->results = $results;
  313. }
  314. function count() {
  315. return mysql_num_rows($this->results);
  316. }
  317. function next() {
  318. $r = mysql_fetch_assoc($this->results);
  319. if ($r === false) {
  320. return NULL;
  321. }
  322. return $r;
  323. }
  324. }
  325. /* More compatibility for old Query/DbConnection classes.
  326. * FIXME - lose this cruft.
  327. */
  328. class DbOld {
  329. function DbOld($results, $id) {
  330. $this->results = $results;
  331. $this->id = $id;
  332. }
  333. function getInsertId() {
  334. return $this->id;
  335. }
  336. function numRows() {
  337. return mysql_num_rows($this->results);
  338. }
  339. function fetchRow($arrayType=OBIB_ASSOC) {
  340. if (is_bool($this->results)) {
  341. return false;
  342. }
  343. switch ($arrayType) {
  344. case OBIB_NUM:
  345. return mysql_fetch_row($this->results);
  346. break;
  347. case OBIB_BOTH:
  348. return mysql_fetch_array($this->results, MYSQL_BOTH);
  349. break;
  350. case OBIB_ASSOC:
  351. default:
  352. return mysql_fetch_assoc($this->results);
  353. }
  354. return false;
  355. }
  356. function resetResult() {
  357. mysql_data_seek($this->results, 0);
  358. }
  359. }
  360. ?>