pquery.sql.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <?php
  2. /**
  3. * pQuery plugin for executing MySQL queries.
  4. *
  5. * @package pQuery
  6. */
  7. /**
  8. * @todo Documentation
  9. * @property $query The query that is being evaluated.
  10. */
  11. class pQuerySql extends pQuery implements pQueryExtension {
  12. static $accepts = array('string' => 'parse_query', 'resource');
  13. /**
  14. * The pattern to use for specifying variables in a query.
  15. */
  16. const VARIABLE_PATTERN = '/\[\s*%s\s*\]/';
  17. /**
  18. * The default row fetching type, one of 'assoc', 'object' or 'array'.
  19. */
  20. const DEFAULT_FETCH_TYPE = 'assoc';
  21. /**
  22. * The MySQL link identifier.
  23. *
  24. * @var resource
  25. */
  26. static $link;
  27. /**
  28. * @see pQuery::$variable_alias
  29. * @var string|array
  30. */
  31. static $variable_alias = 'query';
  32. /**
  33. * Database login data, should be an associative array containing
  34. * values for 'host', 'username', 'password' and 'dbname'
  35. *
  36. * @var array
  37. */
  38. static $login_data = array();
  39. /**
  40. * The result of the current query.
  41. *
  42. * @var resource|bool
  43. */
  44. var $result;
  45. /**
  46. * Indicates whether the query has been executed yet.
  47. *
  48. * @var bool
  49. */
  50. var $executed;
  51. /**
  52. * Parse the given query string.
  53. */
  54. function parse_query() {
  55. $args = $this->arguments;
  56. if( !count($args) )
  57. return;
  58. // Parse arguments as variables. Arrays and
  59. // Replace variable indices by names equal to their indices
  60. $variables = array();
  61. foreach( $args as $i => $argument ) {
  62. if( is_array($argument) )
  63. $variables = array_merge($variables, $argument);
  64. else
  65. $variables[$i] = $argument;
  66. }
  67. // Replace variables by their escaped values
  68. $this->set($variables);
  69. }
  70. /**
  71. * Replace a set of variables with their (optionally escaped)
  72. * values in the current query.
  73. *
  74. * @param array $variables The variables to replace.
  75. * @param bool $escape Whether to escape the variable values.
  76. * @returns pQuerySql The current query object.
  77. */
  78. function replace_variables($variables, $escape) {
  79. $patterns = array_map('pQuerySql::variable_pattern', array_keys($variables));
  80. $escape && $variables = array_map('pQuerySql::escape', $variables);
  81. $this->variable = preg_replace($patterns, $variables, $this->variable);
  82. $this->executed = false;
  83. return $this;
  84. }
  85. /**
  86. * Replace a set of variables with their escaped values in the current query.
  87. *
  88. * @param array $variables The variables to replace.
  89. * @returns pQuerySql The current query object.
  90. */
  91. function set($variables) {
  92. return $this->replace_variables($variables, true);
  93. }
  94. /**
  95. * Replace a set of variables with their non-escaped values in the current query.
  96. *
  97. * @param array $variables The variables to replace.
  98. * @returns pQuerySql The current query object.
  99. */
  100. function set_unescaped($variables) {
  101. return $this->replace_variables($variables, false);
  102. }
  103. /**
  104. * Transform a variable name to a regex to be used as a replacement
  105. * pattern in a query.
  106. *
  107. * @param string $name The variable name to transform.
  108. * @returns string The variable's replacement pattern.
  109. */
  110. static function variable_pattern($name) {
  111. return sprintf(self::VARIABLE_PATTERN, $name);
  112. }
  113. /**
  114. * Execute the current query.
  115. *
  116. * @returns pQuerySql The current query object.
  117. */
  118. function execute() {
  119. self::assert_connection();
  120. $result = mysql_query($this->query, self::$link);
  121. if( !$result )
  122. return self::mysql_error($this->query);
  123. $this->result = $result;
  124. $this->executed = true;
  125. return $this;
  126. }
  127. /**
  128. * Find the number of resulting rows of the current query.
  129. *
  130. * @returns int The number of result rows.
  131. * @uses mysql_num_rows
  132. */
  133. function num_rows() {
  134. $this->assert_execution();
  135. return is_resource($this->result) ? mysql_num_rows($this->result) : 0;
  136. }
  137. /**
  138. * Find the number of rows affected by the current query.
  139. *
  140. * @returns int The number of affected rows.
  141. * @uses mysql_affected_rows
  142. */
  143. function affected_rows() {
  144. $this->assert_execution();
  145. return is_resource($this->result) ? mysql_affected_rows($this->result) : 0;
  146. }
  147. /**
  148. * Fetch a row from the current result.
  149. *
  150. * @param string $type The format of the result row.
  151. * @returns mixed The fetched row in the requested format.
  152. */
  153. function fetch($type=self::DEFAULT_FETCH_TYPE) {
  154. $this->assert_execution();
  155. if( !is_resource($this->result) )
  156. return self::error('Query result is not a resource.');
  157. $func = 'mysql_fetch_'.$type;
  158. if( !function_exists($func) )
  159. return self::error('Fetch type "%s" is not supported.', $type);
  160. return $func($this->result);
  161. }
  162. /**
  163. * Fetch all rows from the current result.
  164. *
  165. * @param string $type The format of the result rows.
  166. * @returns array The result set.
  167. */
  168. function fetch_all($type=self::DEFAULT_FETCH_TYPE) {
  169. $results = array();
  170. while( ($row = $this->fetch($type)) !== false )
  171. $results[] = $row;
  172. return $results;
  173. return $func($this->result);
  174. }
  175. /**
  176. * Assert that the current query has been executed.
  177. */
  178. function assert_execution() {
  179. $this->executed || $this->execute();
  180. }
  181. /**
  182. * Set database server login data.
  183. *
  184. * @param string $host The database server to connect with.
  185. * @param string $username The username to login with on the database server.
  186. * @param string $password The password to login with on the database server.
  187. * @param string $dbname The name of the database to select after connecting to the server.
  188. */
  189. static function set_login_data($host, $username, $password, $dbname) {
  190. // Close any existing connection
  191. self::disconnect();
  192. self::$login_data = array_merge(self::$login_data,
  193. compact('host', 'username', 'password', 'dbname'));
  194. }
  195. /**
  196. * Assert that the database server config has been set.
  197. */
  198. static function assert_login_data_exist() {
  199. if( !isset(self::$login_data['host']) )
  200. return self::error('No MySQL database server host is specified.');
  201. if( !isset(self::$login_data['username']) )
  202. return self::error('No username is specified for the MySQL server.');
  203. if( !isset(self::$login_data['password']) )
  204. return self::error('No password is specified for the MySQL server.');
  205. if( !isset(self::$login_data['host']) )
  206. return self::error('No MySQL database name is specified.');
  207. }
  208. /**
  209. * Assert that the MySQL connection is opened.
  210. *
  211. * @uses mysql_connect,mysql_select_db
  212. */
  213. static function assert_connection() {
  214. // Return if the connection has already been opened
  215. if( self::$link )
  216. return;
  217. self::assert_login_data_exist();
  218. // Connect to the database
  219. $c = self::$login_data;
  220. $link = @mysql_connect($c['host'], $c['username'], $c['password']);
  221. if( $link === false )
  222. return self::mysql_error();
  223. self::$link = $link;
  224. // Select the correct database
  225. if( !@mysql_select_db($c['dbname'], $link) )
  226. return self::mysql_error();
  227. }
  228. /**
  229. * Close the current connection, if any.
  230. *
  231. * @uses mysql_close
  232. */
  233. static function disconnect() {
  234. // Return if the connection has already been closed
  235. if( !self::$link )
  236. return;
  237. mysql_close(self::$link);
  238. self::$link = null;
  239. }
  240. /**
  241. * Echo the latest MySQL error.
  242. * If a query is specified and debug mode is on, add the query to the error message.
  243. *
  244. * @param string $query The query that was executed, if any.
  245. */
  246. static function mysql_error($query='') {
  247. $error = sprintf('MySQL error %d: %s.', mysql_errno(), mysql_error());
  248. PQUERY_DEBUG && $error .= "\nQuery: ".$query;
  249. self::error($error);
  250. }
  251. /**
  252. * Extention of {@link pQuery::error}, returning FALSE (useful in result loops).
  253. * Also, the current query is printed in debug mode.
  254. *
  255. * @param string $error The error message
  256. * @returns bool FALSE
  257. */
  258. static function error($error /* [ , $arg1 [ , ... ] ] */) {
  259. $args = func_get_args();
  260. call_user_func_array('pQuery::error', $args);
  261. return false;
  262. }
  263. /**
  264. * Escape a string for safe use in a query.
  265. *
  266. * @param string $value The string to escape.
  267. * @uses mysql_real_escape_string
  268. */
  269. static function escape($value) {
  270. self::assert_connection();
  271. return mysql_real_escape_string($value, self::$link);
  272. }
  273. /**
  274. * Insert a record in the given table.
  275. *
  276. * @param string $table The table to insert into.
  277. * @param array $values The values to insert, pointed to by their column names.
  278. * @param bool $escape Whether to escape the values. Defaults to TRUE.
  279. * @returns pQuerySql The created query instance.
  280. */
  281. static function insert_row($table, $values, $escape=true) {
  282. $columns = array_keys($values);
  283. $escape && array_walk($values, 'pQuerySql::escape');
  284. return _sql('INSERT INTO `[table]`([columns]) VALUES([values]);')
  285. ->set_unescaped(array(
  286. 'table' => $table,
  287. 'columns' => "`".implode("`, `", $columns)."`",
  288. 'values' => "'".implode("', '", $values)."'"
  289. ));
  290. }
  291. /**
  292. * Delete all records from the given table that match the constraints.
  293. *
  294. * @param string $table The table to insert into.
  295. * @param array $constraints Column names pointing to their values
  296. * @param bool $escape Whether to escape the constraint values. Defaults to TRUE.
  297. * @returns pQuerySql The created query instance.
  298. */
  299. static function delete($table, $constraints, $escape=true) {
  300. return _sql('DELETE FROM `[table]` WHERE [constraints];')
  301. ->set_unescaped(array(
  302. 'table' => $table,
  303. 'constraints' => self::parse_constraints($constraints, $escape)
  304. ));
  305. }
  306. /**
  307. * Parse a list of constraints.
  308. *
  309. * @param mixed $constraints One of:
  310. * - A variable that evaluates as "empty", which will yield the string '1'.
  311. * - A string, which will be returned unchanged.
  312. * - A list of column names pointing to their values. A value may be
  313. * a list, wich will yield a query with the MySQL 'IN' selector.
  314. * @param bool $escape Whether to escape the values.
  315. * @returns string The parsed constraints.
  316. */
  317. static function parse_constraints($constraints, $escape) {
  318. if( empty($constraints) )
  319. return "1";
  320. if( is_string($constraints) )
  321. return $constraints;
  322. if( !is_array($constraints) )
  323. return self::error('Unknown constraints type.');
  324. $conditions = array();
  325. foreach( $constraints as $column => $value ) {
  326. $condition = "`$column` ";
  327. if( is_array($value) ) {
  328. $escape && array_walk($value, 'pQuerySql::escape');
  329. $condition .= "IN ('".implode("', '", $value)."')";
  330. } else {
  331. $escape && $value = self::escape($value);
  332. $condition .= "= '$value'";
  333. }
  334. $conditions[] = $condition;
  335. }
  336. return implode(" AND ", $conditions);
  337. }
  338. }
  339. /**
  340. * Shortcut constructor for {@link pQuerySql}.
  341. *
  342. * @param string $query A MySQL query to evaluate.
  343. * @returns pQuerySql A new SQL query instance.
  344. */
  345. function _sql($query /* [ , $arg1, ... ] */) {
  346. $args = func_get_args();
  347. $query = array_shift($args);
  348. array_unshift($args, 'sql', $query);
  349. return call_user_func_array('pQuery::create', $args);
  350. }
  351. pQuery::extend('pQuerySql', 'sql');
  352. ?>