PageRenderTime 32ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/storage/qsprintf/qsprintf.php

http://github.com/facebook/phabricator
PHP | 313 lines | 199 code | 30 blank | 84 comment | 38 complexity | f9b31bcaa7879de957a34f912c2f2c44 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * Format an SQL query. This function behaves like sprintf(), except that
  19. * all the normal conversions (like %s) will be properly escaped, and
  20. * additional conversions are supported:
  21. *
  22. * %nd, %ns, %nf
  23. * "Nullable" versions of %d, %s and %f. Will produce 'NULL' if the
  24. * argument is a strict null.
  25. *
  26. * %=d, %=s, %=f
  27. * "Nullable Test" versions of %d, %s and %f. If you pass a value, you
  28. * get "= 3"; if you pass null, you get "IS NULL". For instance, this
  29. * will work properly if `hatID' is a nullable column and $hat is null.
  30. *
  31. * qsprintf($conn, 'WHERE hatID %=d', $hat);
  32. *
  33. * %Ld, %Ls, %Lf
  34. * "List" versions of %d, %s and %f. These are appropriate for use in
  35. * an "IN" clause. For example:
  36. *
  37. * qsprintf($conn, 'WHERE hatID IN(%Ld)', $list_of_hats);
  38. *
  39. * %T ("Table")
  40. * Escapes a table name.
  41. *
  42. * %C, %LC
  43. * Escapes a column name or a list of column names.
  44. *
  45. * %K ("Comment")
  46. * Escapes a comment.
  47. *
  48. * %Q ("Query Fragment")
  49. * Injects a raw query fragment. Extremely dangerous! Not escaped!
  50. *
  51. * %~ ("Substring")
  52. * Escapes a substring query for a LIKE (or NOT LIKE) clause. For example:
  53. *
  54. * // Find all rows with $search as a substing of `name`.
  55. * qsprintf($conn, 'WHERE name LIKE %~', $search);
  56. *
  57. * See also %> and %<.
  58. *
  59. * %> ("Prefix")
  60. * Escapes a prefix query for a LIKE clause. For example:
  61. *
  62. * // Find all rows where `name` starts with $prefix.
  63. * qsprintf($conn, 'WHERE name LIKE %>', $prefix);
  64. *
  65. * %< ("Suffix")
  66. * Escapes a suffix query for a LIKE clause. For example:
  67. *
  68. * // Find all rows where `name` ends with $suffix.
  69. * qsprintf($conn, 'WHERE name LIKE %<', $suffix);
  70. *
  71. * @group storage
  72. */
  73. function qsprintf($conn, $pattern/*, ... */) {
  74. $args = func_get_args();
  75. array_shift($args);
  76. return xsprintf('xsprintf_query', $conn, $args);
  77. }
  78. /**
  79. * @group storage
  80. */
  81. function vqsprintf($conn, $pattern, array $argv) {
  82. array_unshift($argv, $pattern);
  83. return xsprintf('xsprintf_query', $conn, $argv);
  84. }
  85. /**
  86. * xsprintf() callback for encoding SQL queries. See qsprintf().
  87. * @group storage
  88. */
  89. function xsprintf_query($userdata, &$pattern, &$pos, &$value, &$length) {
  90. $type = $pattern[$pos];
  91. $conn = $userdata;
  92. $next = (strlen($pattern) > $pos + 1) ? $pattern[$pos + 1] : null;
  93. $nullable = false;
  94. $done = false;
  95. $prefix = '';
  96. if (!($conn instanceof AphrontDatabaseConnection)) {
  97. throw new Exception("Invalid database connection!");
  98. }
  99. switch ($type) {
  100. case '=': // Nullable test
  101. switch ($next) {
  102. case 'd':
  103. case 'f':
  104. case 's':
  105. $pattern = substr_replace($pattern, '', $pos, 1);
  106. $length = strlen($pattern);
  107. $type = 's';
  108. if ($value === null) {
  109. $value = 'IS NULL';
  110. $done = true;
  111. } else {
  112. $prefix = '= ';
  113. $type = $next;
  114. }
  115. break;
  116. default:
  117. throw new Exception('Unknown conversion, try %=d, %=s, or %=f.');
  118. }
  119. break;
  120. case 'n': // Nullable...
  121. switch ($next) {
  122. case 'd': // ...integer.
  123. case 'f': // ...float.
  124. case 's': // ...string.
  125. $pattern = substr_replace($pattern, '', $pos, 1);
  126. $length = strlen($pattern);
  127. $type = $next;
  128. $nullable = true;
  129. break;
  130. default:
  131. throw new Exception('Unknown conversion, try %nd or %ns.');
  132. }
  133. break;
  134. case 'L': // List of..
  135. _qsprintf_check_type($value, "L{$next}", $pattern);
  136. $pattern = substr_replace($pattern, '', $pos, 1);
  137. $length = strlen($pattern);
  138. $type = 's';
  139. $done = true;
  140. switch ($next) {
  141. case 'd': // ...integers.
  142. $value = implode(', ', array_map('intval', $value));
  143. break;
  144. case 's': // ...strings.
  145. foreach ($value as $k => $v) {
  146. $value[$k] = "'".$conn->escapeString($v)."'";
  147. }
  148. $value = implode(', ', $value);
  149. break;
  150. case 'C': // ...columns.
  151. foreach ($value as $k => $v) {
  152. $value[$k] = $conn->escapeColumnName($v);
  153. }
  154. $value = implode(', ', $value);
  155. break;
  156. default:
  157. throw new Exception("Unknown conversion %L{$next}.");
  158. }
  159. break;
  160. }
  161. if (!$done) {
  162. _qsprintf_check_type($value, $type, $pattern);
  163. switch ($type) {
  164. case 's': // String
  165. if ($nullable && $value === null) {
  166. $value = 'NULL';
  167. } else {
  168. $value = "'".$conn->escapeString($value)."'";
  169. }
  170. $type = 's';
  171. break;
  172. case 'Q': // Query Fragment
  173. $type = 's';
  174. break;
  175. case '~': // Like Substring
  176. case '>': // Like Prefix
  177. case '<': // Like Suffix
  178. $value = $conn->escapeStringForLikeClause($value);
  179. switch ($type) {
  180. case '~': $value = "'%".$value."%'"; break;
  181. case '>': $value = "'" .$value."%'"; break;
  182. case '<': $value = "'%".$value. "'"; break;
  183. }
  184. $type = 's';
  185. break;
  186. case 'f': // Float
  187. if ($nullable && $value === null) {
  188. $value = 'NULL';
  189. } else {
  190. $value = (float)$value;
  191. }
  192. $type = 's';
  193. break;
  194. case 'd': // Integer
  195. if ($nullable && $value === null) {
  196. $value = 'NULL';
  197. } else {
  198. $value = (int)$value;
  199. }
  200. $type = 's';
  201. break;
  202. case 'T': // Table
  203. case 'C': // Column
  204. $value = $conn->escapeColumnName($value);
  205. $type = 's';
  206. break;
  207. case 'K': // Komment
  208. $value = $conn->escapeMultilineComment($value);
  209. $type = 's';
  210. break;
  211. default:
  212. throw new Exception("Unknown conversion '%{$type}'.");
  213. }
  214. }
  215. if ($prefix) {
  216. $value = $prefix.$value;
  217. }
  218. $pattern[$pos] = $type;
  219. }
  220. /**
  221. * @group storage
  222. */
  223. function _qsprintf_check_type($value, $type, $query) {
  224. switch ($type) {
  225. case 'Ld': case 'Ls': case 'LC': case 'LA': case 'LO':
  226. if (!is_array($value)) {
  227. throw new AphrontQueryParameterException(
  228. $query,
  229. "Expected array argument for %{$type} conversion.");
  230. }
  231. if (empty($value)) {
  232. throw new AphrontQueryParameterException(
  233. $query,
  234. "Array for %{$type} conversion is empty.");
  235. }
  236. foreach ($value as $scalar) {
  237. _qsprintf_check_scalar_type($scalar, $type, $query);
  238. }
  239. break;
  240. default:
  241. _qsprintf_check_scalar_type($value, $type, $query);
  242. }
  243. }
  244. /**
  245. * @group storage
  246. */
  247. function _qsprintf_check_scalar_type($value, $type, $query) {
  248. switch ($type) {
  249. case 'Q': case 'LC': case 'T': case 'C':
  250. if (!is_string($value)) {
  251. throw new AphrontQueryParameterException(
  252. $query,
  253. "Expected a string for %{$type} conversion.");
  254. }
  255. break;
  256. case 'Ld': case 'd': case 'f':
  257. if (!is_null($value) && !is_numeric($value)) {
  258. throw new AphrontQueryParameterException(
  259. $query,
  260. "Expected a numeric scalar or null for %{$type} conversion.");
  261. }
  262. break;
  263. case 'Ls': case 's':
  264. case '~': case '>': case '<': case 'K':
  265. if (!is_null($value) && !is_scalar($value)) {
  266. throw new AphrontQueryParameterException(
  267. $query,
  268. "Expected a scalar or null for %{$type} conversion.");
  269. }
  270. break;
  271. case 'LA': case 'LO':
  272. if (!is_null($value) && !is_scalar($value) &&
  273. !(is_array($value) && !empty($value))) {
  274. throw new AphrontQueryParameterException(
  275. $query,
  276. "Expected a scalar or null or non-empty array for ".
  277. "%{$type} conversion.");
  278. }
  279. break;
  280. default:
  281. throw new Exception("Unknown conversion '{$type}'.");
  282. }
  283. }