'Invalid schema name' error thrown by Doctrine, but the raw SQL seems to work - doctrine-orm

I'm using some custom DQL functions to filter rows by some JSONB fields in PostgreSQL. Here's my query function:
private function findTopLevelResources(): array {
return $this->createQueryBuilder('r')
->where("JSON_EXISTS(r.contents, '-1') = FALSE")
->getQuery()
->getResult();
}
Running this code results in DriverException from AbstractPostgreSQLDriver:
An exception occurred while executing 'SELECT r0_.id AS id_0, r0_.marking AS marking_1, r0_.contents AS contents_2, r0_.kind_id AS kind_id_3 FROM resource r0_ WHERE r0_.contents?'-1' = false':
SQLSTATE[3F000]: Invalid schema name: 7 ERROR: schema "r0_" does not exist
LINE 1: ... r0_.kind_id AS kind_id_3 FROM resource r0_ WHERE r0_.conten...
^
I tried to execute the raw SQL query manually from PHPStorm and it worked, no errors.
How do I get this to work in Doctrine?
Why doesn't this query work with Doctrine, but does when I test it manually?
Here's JSON_EXISTS: (based on syslogic/doctrine-json-functions)
class JsonExists extends FunctionNode
{
const FUNCTION_NAME = 'JSON_EXISTS';
const OPERATOR = '?';
public $jsonData;
public $jsonPath;
public function getSql(SqlWalker $sqlWalker)
{
$jsonData = $sqlWalker->walkStringPrimary($this->jsonData);
$jsonPath = $this->jsonPath->value;
return $jsonData . self::OPERATOR . "'$jsonPath'";
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->jsonData = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->jsonPath = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
Registered via Symfony's YAML config like this:
doctrine:
orm:
dql:
numeric_functions:
json_exists: Syslogic\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonExists
Versions of stuff:
PHP 7.1.1
doctrine/dbal v2.6.1
doctrine/orm dev-master e3ecec3 (== 2.6.x-dev)
symfony/symfony v3.3.4

The error message is a false clue.
Actual problem is caused by PDO (this is why it works from PHPStorm). When it sees a query like this:
SELECT * FROM foo WHERE contents?'bar'
It treats it like a parametrized query and the question mark ? as a parameter. For some reason it sometimes results in nonsensical error messages. This specific case could be solved by adding a space after the question mark, but it won't work for operators ?| and ?& which can't have a space in the middle.
The solution is to use functions corresponding to operators (this question saved the day). One can find out how they are called using queries like this one:
SELECT oprname, oprcode FROM pg_operator WHERE oprname IN ('?', '?|', '?&')
Here's the part of result related to JSON:
? → jsonb_exists
?| → jsonb_exists_any
?& → jsonb_exists_all
So instead of previous query which causes problems via PDO, one can use this equivalent one:
SELECT * FROM foo WHERE jsonb_exists(contents, 'bar')

Related

"org.jdbi.v3.core.statement.UnableToCreateStatementException: Undefined attribute for token '<endif>'

Method in Dao Interface
#RegisterRowMapper(MapMapper.class)
#SqlQuery(
"SELECT Table1.tenantId,Table1.sacTenantId, sacLogId,currentStep,status from Table1 inner join Table2 on Table1.tenantId = Table2.tenantId where <if(tenantId)>Table1.tenantId = :tenantId and<endif> Table2.status = 'FAILED'")
List<Map<String, Object>> getTenantFailedJobDetails(#Define("tenantId") #Bind("tenantId") String tenantId);
Error trace:
"level":"ERROR","categories":[],"msg":"Servlet.service() for servlet
[dispatcherServlet] in context with path [] threw exception [Request
processing failed; nested exception is
org.jdbi.v3.core.statement.UnableToCreateStatementException: Error
rendering SQL template: 'SELECT Table1.tenantId,Table1.sacTenantId,
sacLogId,currentStep,status from Table1 inner join Table2 on
Table1.tenantId = Table2.tenantId where <if(tenantId)>Table1.tenantId
= :tenantId and Table2.status = 'FAILED'' [statement:"null", arguments:{positional:{0:DUMMY-TENANT}, named:{tenantId:DUMMY-TENANT},
finder:[]}]] with root
cause","stacktrace":["org.jdbi.v3.core.statement.UnableToCreateStatementException:
Undefined attribute for token '' [statement:"null",
arguments:{positional:{0:DUMMY-TENANT}, named:{tenantId:DUMMY-TENANT},
finder:[]}]"
What could be wrong with the if condition?
To make if condition in jdbi query work I added annotation #UseStringTemplateEngine of package org.jdbi.v3.stringtemplate4 to the Dao method
If tenandId is not null then where clause will be
Table1.tenantId = :tenantId and Table2.status = 'FAILED'
else where clause will be just
Table2.status = 'FAILED'
One more information to add, for the else part annotation #AllowUnusedBindings package org.jdbi.v3.sqlobject.customizer is required
The syntax you posted is stringtemplate4, so you need to use the stringtemplate 4 engine (which you select with the annotation that you posted). Otherwise you end up with the default engine which does support only very simply substitutions (and not st4 syntax).

Doctrine Orm - Searching embedded associations

I have an Asset Entity that uses an embedded association:
/**
* #ORM\Entity
*/
class Asset
{
....
/**
* #ORM\Embedded(class="Money\Money")
*/
private $code;
I want to search this class and my first instinct was to do something like this:
public function findOneByCurrencyCode(string $currencyCode)
{
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->innerJoin('asset.code', 'code')
->where('code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
}
This, however, returns the following:
[Semantical Error] line 0, col 65 near 'code WHERE code.currency': Error:
Class Domain\Asset\Asset has no association named code
How do you search embedded classes?
EDIT:
I can get a result by doing something like this, however, this I feel is a hack:
$query = "SELECT * from asset where code_currency='BTC';";
$statement = $this->objectManager->getConnection()->prepare($query);
$statement->execute();
$result = $statement->fetchAll();
return $result;
I tried a bunch of different things and managed to get the answer:
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->where('asset.code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
Turns out no inner join is required. Not sure why and perhaps someone can answer this in time, however the above appears to work with embedded objects
Hope this helps someone else.
"Embedded associations" does NOT exist. So, you doesn't need JOINS.
An embedded is part of your entity along with the others properties.
You have to do something like that:
SELECT u
FROM User u
WHERE u.address.city
(In this query, your entity is User, your embedded is Address and *city is the property of your
embedded).
It's pretty good explained in Doctrine documentation:
Doctrine embeddables

Doctrine CreateQueryBuilder returns repeated result

In Symfony3 I'm running
php app/console generate:doctrine:entity --entity=AcmeBlogBundle:Post
It creates 2 files: Post (in entity folder) and PostRepository (in repository folder extending \Doctrine\ORM\EntityRepository)
Everything is fine until I try to run the following repository-custom-function in my controller
$rir = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post");
$replacementInstruction = $rir->getOneBy(
array("id" => 6)
);
My custom repository function is as follow
public function getOneBy($option)
{
$alias = "p";
$fields = $this->prepareRequestSelectFields("p");
$qb = $this->createQueryBuilder('Post');
$qb->select($fields)
->from('AcmeBlogBundle:Post', $alias)
->where($alias . '.id = :id')
->setParameter('id', 6)
;
$result = $qb->getQuery()->getResult();
}
private function prepareRequestSelectFields($alias)
{
return $alias. ".id";
}
In my database there are 10 posts with id from 1 to 10, so I expect it to return 1 result, however it return correct Post (id 6) 10 times
Why is that?
p.s. if I move the query builder to a custom service wrapper e.g. PostManager it works just fine (returning 1)
This doesn't really answer my question but apparently createQueryBuilder() in EntityRepository and in EntityManager are different, thanks to https://maltronic.io/2014/12/22/doctrine-createquerybuilder-entitymanager-vs-entityrepository/
So in my EntityRepository it should be
...
$qb = $this->createQueryBuilder('p'); // this should be the alias
$qb->select($fields)
// ->from('AcmeBlogBundle:Post', $alias) // remove this
->where($alias . '.id = :id')
->setParameter('id', 6)
;
...
Personally I dislike how 2 different functions has the same name, but I guess it's fine because one is from Doctrine and one is from Symfony. They are decoupled
edit Actually I might be wrong, they are both from Doctrine ... sigh
Still tho, it doesn't answer why in my original code, it returns multiple time
What about getSingleResult() rather than getResult() ? And you should maybe use the doctrine method findOneBy()
You can use the helper methods provided by the repositories, allowing you to fetch one or multiples entities from repositories with dynamic method names, in your case you want to fetch one post by id:
//fetch one
$replacementInstruction = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post")->findOneById(6);
For information, fetching all entities is done with ->findByProperty

zf2 acl doctrine 2

Actually using Zend Framework 2, I am looking for a way to implement a performant ACL strategy based on a database.
The whole idea is to directly filter the DQL queries depending on the currently logged in user, and it's permissions.
I found an implementation of this mecanisme in Symfony 2 http://symfony.com/doc/current/cookbook/security/acl_advanced.html, in this case one table seems to store for each user if he has access to a single row, so we can easily dynamically load only allowed rows by joining this table.
To synthesize,I am looking for a way to define access rules to entities based on criterias, but want to be able to get results in a single query to be able to do some ordering, and pagination.
Are there any ZF2 modules to resolve this case ?
It looks like integrating the SF2 security component as standalone is not an option: Security component from Symfony 2.0 as standalone
You have to use doctrine filter for load things for current member
example of my codes adding the filter for member query :
$em = $sm->get('doctrine.entitymanager.orm_default');
$ormconfig = $sm->get('doctrine.configuration.orm_default');
$ormconfig->addFilter("member", "\PatrickCore\Script\ORM\Functional\MemberAccessFilter");
//
$currentUser = $membersService->getCurrentUser();
$uid = $currentUser->getId();
$filter = $em->getFilters()->enable("member");
$filter->setParameter('member', $uid);
and this file \PatrickCore\Script\ORM\Functional\MemberAccessFilter :
<?php
namespace PatrickCore\Script\ORM\Functional;
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MemberAccessFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('\PatrickCore\Entity\MemberAccessAware')) {
return "";
}
return $targetTableAlias.'.member_id = ' . $this->getParameter('member'); // getParameter applies quoting automatically
}
}

Doctrine2 case-sensitive query

for some reason I need to query 'case-sensitive' in MySql + doctrine 2. Is it possible?
neither
$em->find('UserEn', 'Bob')
nor
$q = $this->em->createQuery('select u from UserEn u where u.name = :name');
$q->setParameter('name', 'Bob');
$result = $q->getResult();
is working. Any idea?
Maybe you are using a MySQL collation ending with "_ci", like "utf8_general_ci". "ci" stands for "case insensitive".
If this is the case, it is not a Doctrine issue, but a MySQL issue.
See http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
"The default character set and collation are latin1 and latin1_swedish_ci, so nonbinary string comparisons are case insensitive by default."
For those that are unable to change their database collation, you can use the BINARY operator in order to force case sensitivity on the criteria.
The BINARY operator casts the string following it to a binary string.
This is an easy way to force a comparison to be done byte by byte
rather than character by character. BINARY also causes trailing spaces
to be significant.
See MySQL BINARY Operator for more details.
To enable the BINARY operator in Doctrine DQLs, you can install the Doctrine Extensions library.
Or create your own Binary String Function like so.
use Doctrine\ORM\Query\AST\Functions\FunctionNode,
Doctrine\ORM\Query\Lexer;
class Binary extends FunctionNode
{
private $stringPrimary;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'BINARY('.$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary).')';
}
}
Next you'll need to register the binary string function with your doctrine configuration. You can do so in your configuration settings or add it as needed like so.
$em->getConfiguration()->addCustomStringFunction('binary', 'DoctrineExtensions\\Query\\Mysql\\Binary');
Then you will be able to use the binary(...) function in your DQL like so.
$q = $em->createQuery('select u from UserEn u where binary(u.name) = :name');
echo $q->getSQL();
/* SELECT ... FROM ... WHERE BINARY(u0_.name) = ? */
To add the binary string function using the Symfony Framework, in your config.yml file change the doctrine.orm.entity_managers.%entity_manager%.dql setting like so.
doctrine:
orm:
#...
entity_managers:
#...
default:
#...
dql:
#...
string_functions:
#...
binary: 'DoctrineExtensions\Query\Mysql\Binary'
See Symfony Doctrine Configuration Documentation for more details
It's not Doctrine issue you have to change table collation to binary then case sensitive would work.
Do alter table and change this
CHARSET=utf8 COLLATE=utf8_general_ci
to this
CHARSET=utf8 COLLATE=utf8_bin