1
0

ExpressionBuilder.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\DB\QueryBuilder\ExpressionBuilder;
  8. use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder;
  9. use OC\DB\ConnectionAdapter;
  10. use OC\DB\QueryBuilder\CompositeExpression;
  11. use OC\DB\QueryBuilder\FunctionBuilder\FunctionBuilder;
  12. use OC\DB\QueryBuilder\Literal;
  13. use OC\DB\QueryBuilder\QueryFunction;
  14. use OC\DB\QueryBuilder\QuoteHelper;
  15. use OCP\DB\QueryBuilder\ICompositeExpression;
  16. use OCP\DB\QueryBuilder\IExpressionBuilder;
  17. use OCP\DB\QueryBuilder\ILiteral;
  18. use OCP\DB\QueryBuilder\IParameter;
  19. use OCP\DB\QueryBuilder\IQueryBuilder;
  20. use OCP\DB\QueryBuilder\IQueryFunction;
  21. use OCP\IDBConnection;
  22. use Psr\Log\LoggerInterface;
  23. class ExpressionBuilder implements IExpressionBuilder {
  24. /** @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder */
  25. protected $expressionBuilder;
  26. /** @var QuoteHelper */
  27. protected $helper;
  28. /** @var IDBConnection */
  29. protected $connection;
  30. /** @var LoggerInterface */
  31. protected $logger;
  32. /** @var FunctionBuilder */
  33. protected $functionBuilder;
  34. public function __construct(ConnectionAdapter $connection, IQueryBuilder $queryBuilder, LoggerInterface $logger) {
  35. $this->connection = $connection;
  36. $this->logger = $logger;
  37. $this->helper = new QuoteHelper();
  38. $this->expressionBuilder = new DoctrineExpressionBuilder($connection->getInner());
  39. $this->functionBuilder = $queryBuilder->func();
  40. }
  41. /**
  42. * Creates a conjunction of the given boolean expressions.
  43. *
  44. * Example:
  45. *
  46. * [php]
  47. * // (u.type = ?) AND (u.role = ?)
  48. * $expr->andX('u.type = ?', 'u.role = ?'));
  49. *
  50. * @param mixed ...$x Optional clause. Defaults = null, but requires
  51. * at least one defined when converting to string.
  52. *
  53. * @return \OCP\DB\QueryBuilder\ICompositeExpression
  54. */
  55. public function andX(...$x): ICompositeExpression {
  56. if (empty($x)) {
  57. $this->logger->debug('Calling ' . IQueryBuilder::class . '::' . __FUNCTION__ . ' without parameters is deprecated and will throw soon.', ['exception' => new \Exception('No parameters in call to ' . __METHOD__)]);
  58. }
  59. return new CompositeExpression(CompositeExpression::TYPE_AND, $x);
  60. }
  61. /**
  62. * Creates a disjunction of the given boolean expressions.
  63. *
  64. * Example:
  65. *
  66. * [php]
  67. * // (u.type = ?) OR (u.role = ?)
  68. * $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?'));
  69. *
  70. * @param mixed ...$x Optional clause. Defaults = null, but requires
  71. * at least one defined when converting to string.
  72. *
  73. * @return \OCP\DB\QueryBuilder\ICompositeExpression
  74. */
  75. public function orX(...$x): ICompositeExpression {
  76. if (empty($x)) {
  77. $this->logger->debug('Calling ' . IQueryBuilder::class . '::' . __FUNCTION__ . ' without parameters is deprecated and will throw soon.', ['exception' => new \Exception('No parameters in call to ' . __METHOD__)]);
  78. }
  79. return new CompositeExpression(CompositeExpression::TYPE_OR, $x);
  80. }
  81. /**
  82. * Creates a comparison expression.
  83. *
  84. * @param mixed $x The left expression.
  85. * @param string $operator One of the IExpressionBuilder::* constants.
  86. * @param mixed $y The right expression.
  87. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  88. * required when comparing text fields for oci compatibility
  89. *
  90. * @return string
  91. */
  92. public function comparison($x, string $operator, $y, $type = null): string {
  93. $x = $this->prepareColumn($x, $type);
  94. $y = $this->prepareColumn($y, $type);
  95. return $this->expressionBuilder->comparison($x, $operator, $y);
  96. }
  97. /**
  98. * Creates an equality comparison expression with the given arguments.
  99. *
  100. * First argument is considered the left expression and the second is the right expression.
  101. * When converted to string, it will generated a <left expr> = <right expr>. Example:
  102. *
  103. * [php]
  104. * // u.id = ?
  105. * $expr->eq('u.id', '?');
  106. *
  107. * @param mixed $x The left expression.
  108. * @param mixed $y The right expression.
  109. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  110. * required when comparing text fields for oci compatibility
  111. *
  112. * @return string
  113. */
  114. public function eq($x, $y, $type = null): string {
  115. $x = $this->prepareColumn($x, $type);
  116. $y = $this->prepareColumn($y, $type);
  117. return $this->expressionBuilder->eq($x, $y);
  118. }
  119. /**
  120. * Creates a non equality comparison expression with the given arguments.
  121. * First argument is considered the left expression and the second is the right expression.
  122. * When converted to string, it will generated a <left expr> <> <right expr>. Example:
  123. *
  124. * [php]
  125. * // u.id <> 1
  126. * $q->where($q->expr()->neq('u.id', '1'));
  127. *
  128. * @param mixed $x The left expression.
  129. * @param mixed $y The right expression.
  130. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  131. * required when comparing text fields for oci compatibility
  132. *
  133. * @return string
  134. */
  135. public function neq($x, $y, $type = null): string {
  136. $x = $this->prepareColumn($x, $type);
  137. $y = $this->prepareColumn($y, $type);
  138. return $this->expressionBuilder->neq($x, $y);
  139. }
  140. /**
  141. * Creates a lower-than comparison expression with the given arguments.
  142. * First argument is considered the left expression and the second is the right expression.
  143. * When converted to string, it will generated a <left expr> < <right expr>. Example:
  144. *
  145. * [php]
  146. * // u.id < ?
  147. * $q->where($q->expr()->lt('u.id', '?'));
  148. *
  149. * @param mixed $x The left expression.
  150. * @param mixed $y The right expression.
  151. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  152. * required when comparing text fields for oci compatibility
  153. *
  154. * @return string
  155. */
  156. public function lt($x, $y, $type = null): string {
  157. $x = $this->prepareColumn($x, $type);
  158. $y = $this->prepareColumn($y, $type);
  159. return $this->expressionBuilder->lt($x, $y);
  160. }
  161. /**
  162. * Creates a lower-than-equal comparison expression with the given arguments.
  163. * First argument is considered the left expression and the second is the right expression.
  164. * When converted to string, it will generated a <left expr> <= <right expr>. Example:
  165. *
  166. * [php]
  167. * // u.id <= ?
  168. * $q->where($q->expr()->lte('u.id', '?'));
  169. *
  170. * @param mixed $x The left expression.
  171. * @param mixed $y The right expression.
  172. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  173. * required when comparing text fields for oci compatibility
  174. *
  175. * @return string
  176. */
  177. public function lte($x, $y, $type = null): string {
  178. $x = $this->prepareColumn($x, $type);
  179. $y = $this->prepareColumn($y, $type);
  180. return $this->expressionBuilder->lte($x, $y);
  181. }
  182. /**
  183. * Creates a greater-than comparison expression with the given arguments.
  184. * First argument is considered the left expression and the second is the right expression.
  185. * When converted to string, it will generated a <left expr> > <right expr>. Example:
  186. *
  187. * [php]
  188. * // u.id > ?
  189. * $q->where($q->expr()->gt('u.id', '?'));
  190. *
  191. * @param mixed $x The left expression.
  192. * @param mixed $y The right expression.
  193. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  194. * required when comparing text fields for oci compatibility
  195. *
  196. * @return string
  197. */
  198. public function gt($x, $y, $type = null): string {
  199. $x = $this->prepareColumn($x, $type);
  200. $y = $this->prepareColumn($y, $type);
  201. return $this->expressionBuilder->gt($x, $y);
  202. }
  203. /**
  204. * Creates a greater-than-equal comparison expression with the given arguments.
  205. * First argument is considered the left expression and the second is the right expression.
  206. * When converted to string, it will generated a <left expr> >= <right expr>. Example:
  207. *
  208. * [php]
  209. * // u.id >= ?
  210. * $q->where($q->expr()->gte('u.id', '?'));
  211. *
  212. * @param mixed $x The left expression.
  213. * @param mixed $y The right expression.
  214. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  215. * required when comparing text fields for oci compatibility
  216. *
  217. * @return string
  218. */
  219. public function gte($x, $y, $type = null): string {
  220. $x = $this->prepareColumn($x, $type);
  221. $y = $this->prepareColumn($y, $type);
  222. return $this->expressionBuilder->gte($x, $y);
  223. }
  224. /**
  225. * Creates an IS NULL expression with the given arguments.
  226. *
  227. * @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be restricted by IS NULL.
  228. *
  229. * @return string
  230. */
  231. public function isNull($x): string {
  232. $x = $this->helper->quoteColumnName($x);
  233. return $this->expressionBuilder->isNull($x);
  234. }
  235. /**
  236. * Creates an IS NOT NULL expression with the given arguments.
  237. *
  238. * @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be restricted by IS NOT NULL.
  239. *
  240. * @return string
  241. */
  242. public function isNotNull($x): string {
  243. $x = $this->helper->quoteColumnName($x);
  244. return $this->expressionBuilder->isNotNull($x);
  245. }
  246. /**
  247. * Creates a LIKE() comparison expression with the given arguments.
  248. *
  249. * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by LIKE() comparison.
  250. * @param mixed $y Argument to be used in LIKE() comparison.
  251. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  252. * required when comparing text fields for oci compatibility
  253. *
  254. * @return string
  255. */
  256. public function like($x, $y, $type = null): string {
  257. $x = $this->helper->quoteColumnName($x);
  258. $y = $this->helper->quoteColumnName($y);
  259. return $this->expressionBuilder->like($x, $y);
  260. }
  261. /**
  262. * Creates a ILIKE() comparison expression with the given arguments.
  263. *
  264. * @param string $x Field in string format to be inspected by ILIKE() comparison.
  265. * @param mixed $y Argument to be used in ILIKE() comparison.
  266. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  267. * required when comparing text fields for oci compatibility
  268. *
  269. * @return string
  270. * @since 9.0.0
  271. */
  272. public function iLike($x, $y, $type = null): string {
  273. return $this->expressionBuilder->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
  274. }
  275. /**
  276. * Creates a NOT LIKE() comparison expression with the given arguments.
  277. *
  278. * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison.
  279. * @param mixed $y Argument to be used in NOT LIKE() comparison.
  280. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  281. * required when comparing text fields for oci compatibility
  282. *
  283. * @return string
  284. */
  285. public function notLike($x, $y, $type = null): string {
  286. $x = $this->helper->quoteColumnName($x);
  287. $y = $this->helper->quoteColumnName($y);
  288. return $this->expressionBuilder->notLike($x, $y);
  289. }
  290. /**
  291. * Creates a IN () comparison expression with the given arguments.
  292. *
  293. * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by IN() comparison.
  294. * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by IN() comparison.
  295. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  296. * required when comparing text fields for oci compatibility
  297. *
  298. * @return string
  299. */
  300. public function in($x, $y, $type = null): string {
  301. $x = $this->helper->quoteColumnName($x);
  302. $y = $this->helper->quoteColumnNames($y);
  303. return $this->expressionBuilder->in($x, $y);
  304. }
  305. /**
  306. * Creates a NOT IN () comparison expression with the given arguments.
  307. *
  308. * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison.
  309. * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison.
  310. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants
  311. * required when comparing text fields for oci compatibility
  312. *
  313. * @return string
  314. */
  315. public function notIn($x, $y, $type = null): string {
  316. $x = $this->helper->quoteColumnName($x);
  317. $y = $this->helper->quoteColumnNames($y);
  318. return $this->expressionBuilder->notIn($x, $y);
  319. }
  320. /**
  321. * Creates a $x = '' statement, because Oracle needs a different check
  322. *
  323. * @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
  324. * @return string
  325. * @since 13.0.0
  326. */
  327. public function emptyString($x): string {
  328. return $this->eq($x, $this->literal('', IQueryBuilder::PARAM_STR));
  329. }
  330. /**
  331. * Creates a `$x <> ''` statement, because Oracle needs a different check
  332. *
  333. * @param string|ILiteral|IParameter|IQueryFunction $x The field in string format to be inspected by the comparison.
  334. * @return string
  335. * @since 13.0.0
  336. */
  337. public function nonEmptyString($x): string {
  338. return $this->neq($x, $this->literal('', IQueryBuilder::PARAM_STR));
  339. }
  340. /**
  341. * Binary AND Operator copies a bit to the result if it exists in both operands.
  342. *
  343. * @param string|ILiteral $x The field or value to check
  344. * @param int $y Bitmap that must be set
  345. * @return IQueryFunction
  346. * @since 12.0.0
  347. */
  348. public function bitwiseAnd($x, int $y): IQueryFunction {
  349. return new QueryFunction($this->connection->getDatabasePlatform()->getBitAndComparisonExpression(
  350. $this->helper->quoteColumnName($x),
  351. $y
  352. ));
  353. }
  354. /**
  355. * Binary OR Operator copies a bit if it exists in either operand.
  356. *
  357. * @param string|ILiteral $x The field or value to check
  358. * @param int $y Bitmap that must be set
  359. * @return IQueryFunction
  360. * @since 12.0.0
  361. */
  362. public function bitwiseOr($x, int $y): IQueryFunction {
  363. return new QueryFunction($this->connection->getDatabasePlatform()->getBitOrComparisonExpression(
  364. $this->helper->quoteColumnName($x),
  365. $y
  366. ));
  367. }
  368. /**
  369. * Quotes a given input parameter.
  370. *
  371. * @param mixed $input The parameter to be quoted.
  372. * @param int $type One of the IQueryBuilder::PARAM_* constants
  373. *
  374. * @return ILiteral
  375. */
  376. public function literal($input, $type = IQueryBuilder::PARAM_STR): ILiteral {
  377. return new Literal($this->expressionBuilder->literal($input, $type));
  378. }
  379. /**
  380. * Returns a IQueryFunction that casts the column to the given type
  381. *
  382. * @param string|IQueryFunction $column
  383. * @param mixed $type One of IQueryBuilder::PARAM_*
  384. * @psalm-param IQueryBuilder::PARAM_* $type
  385. * @return IQueryFunction
  386. */
  387. public function castColumn($column, $type): IQueryFunction {
  388. return new QueryFunction(
  389. $this->helper->quoteColumnName($column)
  390. );
  391. }
  392. /**
  393. * @param mixed $column
  394. * @param mixed|null $type
  395. * @return array|IQueryFunction|string
  396. */
  397. protected function prepareColumn($column, $type) {
  398. return $this->helper->quoteColumnNames($column);
  399. }
  400. }