. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ namespace tests\unit\Espo\ORM; use Espo\ORM\EntityFactory; use Espo\ORM\EntityManager; use Espo\ORM\Metadata; use Espo\ORM\MetadataDataProvider; use Espo\ORM\Query\DeleteBuilder; use Espo\ORM\Query\InsertBuilder; use Espo\ORM\Query\Part\Condition; use Espo\ORM\Query\Part\Expression; use Espo\ORM\Query\Part\Join; use Espo\ORM\Query\SelectBuilder; use Espo\ORM\Query\UpdateBuilder; use Espo\ORM\QueryBuilder; use Espo\ORM\QueryComposer\PostgresqlQueryComposer as QueryComposer; require_once 'tests/unit/testData/DB/Entities.php'; require_once 'tests/unit/testData/DB/MockPDO.php'; require_once 'tests/unit/testData/DB/MockDBResult.php'; class PostgresqlQueryComposerTest extends \PHPUnit\Framework\TestCase { private ?QueryComposer $queryComposer; private ?EntityManager $entityManager; protected function setUp(): void { $ormMetadata = include('tests/unit/testData/DB/ormMetadata.php'); $metadataDataProvider = $this->createMock(MetadataDataProvider::class); $metadataDataProvider ->expects($this->any()) ->method('get') ->willReturn($ormMetadata); $metadata = new Metadata($metadataDataProvider); $this->queryBuilder = new QueryBuilder(); $pdo = $this->createMock('MockPDO'); $pdo ->expects($this->any()) ->method('quote') ->will($this->returnCallback(function() { $args = func_get_args(); return "'" . $args[0] . "'"; })); $this->entityManager = $this->createMock(EntityManager::class); $entityFactory = $this->createMock(EntityFactory::class); $entityFactory ->expects($this->any()) ->method('create') ->will( $this->returnCallback(function () use ($metadata) { $args = func_get_args(); $className = "tests\\unit\\testData\\DB\\" . $args[0]; $defs = $metadata->get($args[0]) ?? []; return new $className($args[0], $defs, $this->entityManager); }) ); $this->queryComposer = new QueryComposer($pdo, $entityFactory, $metadata); $this->post = $entityFactory->create('Post'); $this->comment = $entityFactory->create('Comment'); $this->tag = $entityFactory->create('Tag'); $this->note = $entityFactory->create('Note'); $this->contact = $entityFactory->create('Contact'); $this->account = $entityFactory->create('Account'); } public function testUpdate1(): void { $query = UpdateBuilder::create() ->in('Comment') ->set(['name' => '1']) ->where(['name' => 'post.name']) ->join('post') ->limit(1) ->order('name') ->build(); $sql = $this->queryComposer->composeUpdate($query); $expectedSql = 'UPDATE "comment" SET "name" = \'1\' WHERE "comment"."id" IN ' . '(SELECT "comment"."id" AS "id" FROM "comment" ' . 'JOIN "post" AS "post" ON "comment"."post_id" = "post"."id" AND "post"."deleted" = false ' . 'WHERE "comment"."name" = \'post.name\' AND "comment"."deleted" = false ' . 'ORDER BY "comment"."name" ASC LIMIT 1 OFFSET 0 FOR UPDATE)'; $this->assertEquals($expectedSql, $sql); } public function testSelect1() { $query = SelectBuilder::create() ->from('Comment') ->select(['name', 'postId', 'postName']) ->build(); $sql = $this->queryComposer->composeSelect($query); $expectedSql = 'SELECT "comment"."name" AS "name", "comment"."post_id" AS "postId", "post"."name" AS "postName" ' . 'FROM "comment" LEFT JOIN "post" AS "post" ON "comment"."post_id" = "post"."id" ' . 'AND "post"."deleted" = false ' . 'WHERE "comment"."deleted" = false'; $this->assertEquals($expectedSql, $sql); } public function testSelectForUpdate1() { $query = SelectBuilder::create() ->from('Comment') ->select(['name', 'postId', 'postName']) ->forUpdate() ->build(); $sql = $this->queryComposer->composeSelect($query); $expectedSql = 'SELECT "comment"."name" AS "name", "comment"."post_id" AS "postId" ' . 'FROM "comment" WHERE "comment"."deleted" = false FOR UPDATE'; $this->assertEquals($expectedSql, $sql); } public function testDelete1(): void { $query = DeleteBuilder::create() ->from('Account') ->where(['name' => 'test']) ->build(); $sql = $this->queryComposer->composeDelete($query); $expectedSql = "DELETE FROM \"account\" " . "WHERE \"account\".\"name\" = 'test'"; $this->assertEquals($expectedSql, $sql); } public function testDelete2(): void { $query = DeleteBuilder::create() ->from('Comment') ->join('post') ->where(['name' => 'post.name']) ->limit(1) ->order('name') ->build(); $sql = $this->queryComposer->composeDelete($query); $expectedSql = 'DELETE FROM "comment" WHERE "comment"."id" IN ' . '(SELECT "comment"."id" AS "id" FROM "comment" ' . 'JOIN "post" AS "post" ON "comment"."post_id" = "post"."id" AND "post"."deleted" = false ' . 'WHERE "comment"."name" = \'post.name\' AND "comment"."deleted" = false ' . 'ORDER BY "comment"."name" ASC LIMIT 1 OFFSET 0)'; $this->assertEquals($expectedSql, $sql); } public function testInsertUpdate1(): void { $query = InsertBuilder::create() ->into('PostTag') ->columns(['id', 'postId', 'tagId']) ->values([ 'id' => '1', 'postId' => 'post-id', 'tagId' => 'tag-id', ]) ->updateSet([ 'deleted' => 0 ]) ->build(); $sql = $this->queryComposer->composeInsert($query); $expectedSql = "INSERT INTO \"post_tag\" (\"id\", \"post_id\", \"tag_id\") VALUES ('1', 'post-id', 'tag-id') " . "ON CONFLICT(\"post_id\", \"tag_id\") DO UPDATE SET \"deleted\" = 0"; $this->assertEquals($expectedSql, $sql); } public function testInsertUpdate2(): void { $query = InsertBuilder::create() ->into('Account') ->columns(['id', 'name']) ->values([ 'id' => '1', 'name' => 'name', ]) ->updateSet([ 'deleted' => 0 ]) ->build(); $sql = $this->queryComposer->composeInsert($query); $expectedSql = "INSERT INTO \"account\" (\"id\", \"name\") VALUES ('1', 'name') " . "ON CONFLICT(\"id\") DO UPDATE SET \"deleted\" = 0"; $this->assertEquals($expectedSql, $sql); } public function testAlias4(): void { $subQuery = SelectBuilder::create() ->select('id', 'someId') ->from('Test') ->withDeleted() ->build(); $query = SelectBuilder::create() ->select('id') ->from('Post') ->leftJoin( Join::createWithSubQuery($subQuery, 'sqAlias') ->withConditions( Condition::equal( Expression::alias('sqAlias.someId'), Expression::column('id') ) ) ) ->withDeleted() ->build(); $expectedSql = "SELECT \"post\".\"id\" AS \"id\" FROM \"post\" LEFT JOIN " . "(SELECT \"test\".\"id\" AS \"someId\" FROM \"test\") AS \"sqAlias\" ON \"sqAlias\".\"someId\" = \"post\".\"id\""; $sql = $this->queryComposer->composeSelect($query); $this->assertEquals($expectedSql, $sql); } public function testSelectFunctionTz1() { $query = SelectBuilder::create() ->from('Comment') ->select( Expression::convertTimezone( Expression::column('createdAt'), 5.5 ), 'createdAt' ) ->withDeleted() ->build(); $sql = $this->queryComposer->composeSelect($query); $expectedSql = 'SELECT "comment"."created_at" + INTERVAL \'MINUTE 330\' AS "createdAt" ' . 'FROM "comment"'; $this->assertEquals($expectedSql, $sql); } public function testSelectFunctionTz2() { $query = SelectBuilder::create() ->from('Comment') ->select( Expression::convertTimezone( Expression::column('createdAt'), -5.5 ), 'createdAt' ) ->withDeleted() ->build(); $sql = $this->queryComposer->composeSelect($query); $expectedSql = 'SELECT "comment"."created_at" + INTERVAL \'MINUTE -330\' AS "createdAt" ' . 'FROM "comment"'; $this->assertEquals($expectedSql, $sql); } public function testSelectFunctionTz3() { $query = SelectBuilder::create() ->from('Comment') ->select( Expression::convertTimezone( Expression::column('createdAt'), 5 ), 'createdAt' ) ->withDeleted() ->build(); $sql = $this->queryComposer->composeSelect($query); $expectedSql = 'SELECT "comment"."created_at" + INTERVAL \'HOUR 5\' AS "createdAt" ' . 'FROM "comment"'; $this->assertEquals($expectedSql, $sql); } }