assertInstanceOf('Query\QueryBuilder', sqlQueryBuilder()); $this->assertNotSame(sqlQueryBuilder(), sqlQueryBuilder()); } /** * @depends testGlobalFactoryFunction */ public function testWhereUsesAnd() { $specStub = function ($field, $value) { return function (QueryBuilder $qb) use ($field, $value) { return $qb->expr()->eq($field, $value); }; }; $rowCount = sqlQueryBuilder()->select('*')->from('users')->where( $specStub('city', "'Vrchlabí'"), $specStub('figure', "'Y'") )->execute()->rowCount(); $this->assertSame(2, $rowCount); } public function testOrderBySql() { $qb = sqlQueryBuilder(); $qb->orderBySql('expression < 0 Asc'); $this->assertSame(['expression < 0 Asc'], $qb->getQueryPart('orderBy')); } public function testMultiDirectValuesSql() { $qb = sqlQueryBuilder() ->insert('languages') ->multiDirectValues( [ 'id' => 've', 'name' => 'veverky', 'locale' => 've_VE', 'translate' => 0, ] ) ->multiDirectValues( [ 'id' => 'li', 'name' => 'lišky', 'locale' => 'li_LI', 'translate' => 0, ]) ->multiDirectValues( [ 'id' => 'ko', 'name' => 'kočky', 'locale' => 'ko_KO', 'translate' => 0, ]); $this->assertSame('INSERT INTO languages (`id`,`name`,`locale`,`translate`) VALUES (:multi0_id, :multi0_name, :multi0_locale, :multi0_translate),(:multi1_id, :multi1_name, :multi1_locale, :multi1_translate),(:multi2_id, :multi2_name, :multi2_locale, :multi2_translate) ', $qb->getSQL()); } public function testMultiValuesSql() { $qb = sqlQueryBuilder() ->insert('languages') ->multiValues( [ [ 'id' => 've', 'name' => 'veverky', 'locale' => 've_VE', 'translate' => 0, ], [ 'id' => 'li', 'name' => 'lišky', 'locale' => 'li_LI', 'translate' => 0, ], [ 'id' => 'ko', 'name' => 'kočky', 'locale' => 'ko_KO', 'translate' => 0, ], ] ); $this->assertSame('INSERT INTO languages (id,name,locale,translate) VALUES (ve, veverky, ve_VE, 0),(li, lišky, li_LI, 0),(ko, kočky, ko_KO, 0) ', $qb->getSQL()); } public function testDirectValues(): void { $params = [ 'foo' => 'fooFoo', 'bar' => 10, 'baz' => '1970-01-01 00:00:00', ]; $queryBuilder = sqlQueryBuilder() ->insert('users') ->directValues($params); $this->assertSame('INSERT INTO users (`foo`,`bar`,`baz`) VALUES (:foo, :bar, :baz) ', $queryBuilder->getSQL()); $this->assertSame($params, $queryBuilder->getParameters()); $this->assertSame( <<getRunnableSQL() ); } public function testForUpdate(): void { $queryBuilder = sqlQueryBuilder() ->select('count(*)', 'foo', 'baz') ->where('a', 'b.c') ->forUpdate(); $this->assertSame('SELECT count(*), foo, baz WHERE (a) AND (b.c) FOR UPDATE', $queryBuilder->getSQL()); } public function testCalcRows(): void { $queryBuilder = sqlQueryBuilder() ->select('*', 'foo') ->from('t') ->where('id >= :id') ->setParameter('id', 1) ->addCalcRows(); $this->assertSame('SELECT SQL_CALC_FOUND_ROWS *, foo FROM t WHERE id >= :id', $queryBuilder->getSQL()); $this->assertSame(['id' => 1], $queryBuilder->getParameters()); } public function testOnDuplicateKeyUpdate(): void { $queryBuilder = sqlQueryBuilder() ->insert('foo') ->values(['username' => 'jarin']) ->onDuplicateKeyUpdate(['foo', 'bar']); $this->assertSame('INSERT INTO foo (username) VALUES (jarin) ON DUPLICATE KEY UPDATE foo=VALUES(foo), bar=VALUES(bar) ', $queryBuilder->getSQL()); } public function testOnDuplicateKeyUpdateException(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Call of "onDuplicateKeyUpdate" is allowed only with insert query'); sqlQueryBuilder() ->select('foo') ->values(['username' => 'jarin']) ->onDuplicateKeyUpdate(['foo', 'bar']) ->getSQL(); } public function testSendMaster(): void { $queryBuilder = sqlQueryBuilder() ->select('foo') ->from('t') ->sendToMaster(); $this->assertSame('SELECT foo FROM t -- maxscale route to master', $queryBuilder->getSQL()); } public function testAddQueryBuilderParameters(): void { $queryBuilderForParameters = sqlQueryBuilder() ->setParameter('foo', 1) ->setParameter('bar', 'baz'); $queryBuilder = sqlQueryBuilder() ->select('*') ->from('t') ->where('foo != :foo') ->orWhere('bar = :bar') ->addQueryBuilderParameters($queryBuilderForParameters); $this->assertSame('SELECT * FROM t WHERE (foo != :foo) OR (bar = :bar)', $queryBuilder->getSQL()); $this->assertSame( <<getRunnableSQL() ); $this->assertSame($queryBuilderForParameters->getParameters(), $queryBuilder->getParameters()); } public function testSetForceIndexForJoin(): void { $queryBuilder = sqlQueryBuilder() ->select('id') ->from('t') ->setForceIndexForJoin('a', 'a_table'); // ignore when using non-existing join alias $this->assertSame('SELECT id FROM t', $queryBuilder->getSQL()); $queryBuilder = sqlQueryBuilder() ->select('id', 'MAX(max_1, max_2) as j') ->from('t', 'a') ->leftJoin('a', 'products', 'p', 'p.id = a.id_product') ->setForceIndexForJoin('j', 'pricelist_product') ->setForceIndexForJoin('a', 'blbost'); // ignore when using force index for from clause alias or select clause alias $this->assertSame('SELECT id, MAX(max_1, max_2) as j FROM t a LEFT JOIN products p ON p.id = a.id_product', $queryBuilder->getSQL()); $queryBuilder = sqlQueryBuilder() ->select('id', 'MAX(max_1, max_2) as j') ->from('t', 'a') ->leftJoin('a', 'products', 'p', 'p.id = a.id_product') ->setForceIndexForJoin('p', 'PRIMARY'); $this->assertSame('SELECT id, MAX(max_1, max_2) as j FROM t a LEFT JOIN products p FORCE INDEX (PRIMARY) ON p.id = a.id_product', $queryBuilder->getSQL()); } public function testLimitDeleteThrowsMultitable01() { $qb = sqlQueryBuilder()->delete('orders', 'o') ->setMaxResults(100); $this->expectException(\Doctrine\DBAL\Exception::class); $qb->execute(); } public function testLimitDeleteThrowsMultitable02() { $qb = sqlQueryBuilder()->delete('orders') ->join('orders', 'orders_history', 'oh', 'oh.id_order = orders.id') ->setMaxResults(100); $this->expectException(\Doctrine\DBAL\Exception::class); $qb->execute(); } public function testLimitDelete() { $qb = sqlQueryBuilder()->delete('orders') ->setMaxResults(1000); $qb->execute(); // Should not throw $this->assertSame('DELETE FROM orders LIMIT 1000', $qb->getSQL()); $qb = sqlQueryBuilder()->delete('orders_history') ->andWhere(Operator::exists( sqlQueryBuilder()->select('*') ->from('orders', 'o') ->where('o.id = id_order') ->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)') )) ->andWhere('custom_data IS NOT NULL') ->andWhere("JSON_EXISTS(custom_data, '$.email_type')") ->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'") ->orderBy('date', 'DESC') ->addOrderBy('id', 'ASC') ->setMaxResults(1000); $qb->execute(); // Should not throw $this->assertSame( "DELETE FROM orders_history WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE') ORDER BY date DESC, id ASC LIMIT 1000", $qb->getSQL(), ); // Test normal delete still functional $qb = sqlQueryBuilder()->delete() ->from('orders_history', 'oh') ->andWhere(Operator::exists( sqlQueryBuilder()->select('*') ->from('orders', 'o') ->where('o.id = id_order') ->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)') )) ->andWhere('custom_data IS NOT NULL') ->andWhere("JSON_EXISTS(custom_data, '$.email_type')") ->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'"); $qb->execute(); // Should not throw $this->assertSame( "DELETE oh FROM orders_history oh WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE')", $qb->getSQL(), ); $qb = sqlQueryBuilder()->delete('orders_history', 'oh') ->andWhere(Operator::exists( sqlQueryBuilder()->select('*') ->from('orders', 'o') ->where('o.id = id_order') ->andWhere('o.date_updated < (NOW() - INTERVAL 5 YEAR)') )) ->andWhere('custom_data IS NOT NULL') ->andWhere("JSON_EXISTS(custom_data, '$.email_type')") ->andWhere("JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE'"); $qb->execute(); // Should not throw $this->assertSame( "DELETE oh FROM orders_history oh WHERE (EXISTS (SELECT * FROM orders o WHERE (o.id = id_order) AND (o.date_updated < (NOW() - INTERVAL 5 YEAR)))) AND (custom_data IS NOT NULL) AND (JSON_EXISTS(custom_data, '$.email_type')) AND (JSON_EXTRACT(custom_data, '$.email_type') <> 'ORDER_CREATE')", $qb->getSQL(), ); } public function testLimitUpdate() { $qb = sqlQueryBuilder() ->update('orders_history', 'oh') ->set('oh.comment', 'NULL') ->orderBy('oh.date') ->setMaxResults(10000); $qb->execute(); $this->assertSame('UPDATE orders_history oh SET oh.comment = NULL ORDER BY oh.date ASC LIMIT 10000', $qb->getSQL()); $qb = sqlQueryBuilder() ->update('orders_history', 'oh') ->set('oh.comment', 'NULL'); $qb->execute(); $this->assertSame('UPDATE orders_history oh SET oh.comment = NULL', $qb->getSQL()); $qb = sqlQueryBuilder() ->update('orders_history', 'oh') ->join('oh', 'orders', 'o', 'oh.id_order = o.id') ->set('oh.comment', 'NULL'); $qb->execute(); $this->assertSame('UPDATE orders_history oh INNER JOIN orders o ON oh.id_order = o.id SET oh.comment = NULL', $qb->getSQL()); $qb = sqlQueryBuilder() ->update('orders_history', 'oh') ->join('oh', 'orders', 'o', 'oh.id_order = o.id') ->set('oh.comment', 'NULL') ->orderBy('oh.date', 'DESC') ->addOrderBy('o.date_updated', 'ASC') ->setMaxResults(10000); // https://mariadb.com/kb/en/update/#description // "Until MariaDB 10.3.2, for the multiple-table syntax, UPDATE updates rows in each table named in table_references that satisfy the conditions. // In this case, ORDER BY and LIMIT cannot be used. This restriction was lifted in MariaDB 10.3.2 and both clauses can be used with multiple-table updates." $qb->execute(); // Should not throw $this->assertSame('UPDATE orders_history oh INNER JOIN orders o ON oh.id_order = o.id SET oh.comment = NULL ORDER BY oh.date DESC, o.date_updated ASC LIMIT 10000', $qb->getSQL()); } }