I`ve got two tables described in YAML.
For example :
Entities\User:
type: entity
table: users
id:
id:
type: integer
generator:
strategy: AUTO
fields:
username:
type: string
length: 64
oneToMany:
children:
targetEntity: UserToUser
mappedBy: parent
parents:
targetEntity: UserToUser
mappedBy: child
Entities\UserToUser:
type: entity
table: user_to_user
id:
id:
type: integer
generator:
strategy: AUTO
fields:
user_id:
type: integer
nullable: false
child_id:
type: integer
nullable: false
manyToOne:
parent:
targetEntity: User
inversedBy: children
joinColumn:
name: user_id
referencedColumnName: id
child:
targetEntity: User
inversedBy: parents
joinColumn:
name: child_id
referencedColumnName: id
in this case everything generates well but in fact in database in table user_to_user there in no unique index for fields: user_id and child_id.
So there is the possibility to add 2 entries with the same value.
I was trying to add constraints
uniqueConstraints:
child_user_idx:
columns: child_id,user_id
or 2 other ways:
id:
user_id:
type: integer
child_id:
type: integer
or
id:
parent:
associationKey: true
child:
associationKey: true
Trying to combine these options but as the result using doctrine console validation, there were errors every time but generated SQL was exactly what I needed.
One of them for example:
The join columns of the association parent have to match to ALL identifier columns of the source entity Entities\UserToUser, however, child_id is missing.
The join columns of the association child have to match to ALL identifier columns of the source entity Entities\UserToUser, however, user_id is missing.
I do not really understand what I have to add so the validation pass correctly
While using YAML you can try to use the following notation:
Vendor\CategoryBundle\Entity\Category:
type: entity
table: category
indexes:
# the name of the index
category_slug_idx:
# Columns is an array, specify multiple columns for
# a compound index
columns: [ slug ]
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
slug:
type: string
you can see that you can declare the indexes under the indexes: node
I think this is what you are looking for https://gist.github.com/1845982
Vendor\BlogBundle\Entity\BlogImage:
type: entity
table: blog_image
# use two fields for our identifier
id:
blog:
# flag as an association key
associationKey: true
image:
associationKey: true
fields:
caption:
type: string
# Specify our relationships for above
manyToOne:
project:
targetEntity: Vendor\BlogBundle\Entity\Blog
inversedBy: images
oneToOne:
image:
targetEntity: Vendor\ImageBundle\Entity\Image
Related
I have a GCP datastore query in my code:
cls.query(
ndb.OR(cls.is_miscategorized == True,
cls.is_category_recommendation_mismatched == True,
cls.is_class_recommendation_mismatched == True),
cls.account_type == account_type,
cls.transaction_date >= start_date,
cls.transaction_date <= end_date,
)
For which I have created this index:
- kind: Table name
properties:
- name: is_miscategorized
- name: is_category_recommendation_mismatched
- name: is_class_recommendation_mismatched
- name: account_type
- name: transaction_date
direction: desc
As per my understanding of indexes, I have included all the expected fields in right order here. But I still get the error that Indexes are missing
no matching index found. recommended index is:
- kind: Table name
properties:
- name: account_type
- name: is_class_recommendation_mismatched
- name: transaction_date
direction: desc
- name: create_time
I have added the composite index suggested in the error message too. But I still get the same error, weirdly with the same suggested index in the error message.
Now I have two questions:
The indexes suggested by GCP seem wrong. They have fields missing. So how does GCP suggest indexes? Are they worth relying upon?
What am I doing wrong with indexes here? Why am I getting the error with my original index?
NDB's implementation of OR does a separate query for each OR clause. So you aren't doing one query to the backend, but 3. E.g.
class.query(cls.is_miscategorized == True,
cls.transaction_date >= start_date,
cls.transaction_date <= end_date)
This query requires a composite index, and the other 2 queries require their own composite index.
Your recommended index has account_type & create_time, but that is not in your example. Did you leave those out?
I have a DynamoDB table where each Item has a key of the name 'DataType'.
Also there is a GSI on this table with this 'DataType' as the HashKey and 'timestamp' as rangeKey.
Around 10 per cent of the table items have the 'DataType' value as 'A'.
I want to scan all the items of this GSI with HashKey fixed as 'A'. Is there any way to perform this using scan/parallel scan? Or do i need to use query on GSI itself to perform this operation?
As per the documentation,
https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/spec/ScanSpec.html
i could not find any way to specify a GSI on which i can scan with HashKey fixed.
Given that you want to only look at items with the Hash key "A", you'll need to use the Query API rather than the Scan API, provide the index name, and query for items in the index that have that partition key.
Here's some sample code using the Node AWS SDK V3 that creates three items in a table with a Global Secondary Index (called GSI1). Two of the items have a GSI1PK value of "orange", while the other has a GSI1PK value of "gold". The query returns the two matches:
const tableName = getFromEnv(`TABLE_NAME`);
const client = new DynamoDBClient({});
async function createItem(
name: string,
PK: string,
SK: string,
GSI1PK: string,
GSI1SK: string,
): Promise<void> {
const item = { PK, SK, GSI1PK, GSI1SK, name };
const putCommand = new PutItemCommand({
TableName: tableName,
Item: marshall(item)
});
await client.send(putCommand);
log(`Created item: ${name} with GSI1PK ${GSI1PK}`);
}
await createItem(`foo`, `fooPK`, `fooSK`, `orange`, `blue`);
await createItem(`bar`, `barPK`, `barSK`, `orange`, `white`);
await createItem(`baz`, `bazPK`, `bazSK`, `gold`, `garnet`);
log(`Waiting 5 seconds, as GSIs don't support consistent reads`)
await wait(5);
const query: QueryCommandInput = {
TableName: tableName,
IndexName: `GSI1`,
KeyConditionExpression: `#pk = :pk`,
ExpressionAttributeNames: {
'#pk': `GSI1PK`,
},
ExpressionAttributeValues: {
':pk': { S: `orange` },
},
}
const result = await client.send(new QueryCommand(query));
log(`Querying GSI1 for "orange"`);
result.Items.forEach((entry) => {
log(`Received: `, unmarshall(entry).name);
});
This produces the output of:
Created item: foo with GSI1PK orange
Created item: bar with GSI1PK orange
Created item: baz with GSI1PK gold
Waiting 5 seconds, as GSIs don't support consistent reads
Querying GSI1 for "orange"
Received: foo
Received: bar
One thing worth noting from this example is that GSIs don't allow consistent reads. So if your use case requires immediate consistency, you'll need to find another solution.
When I'm using DQL with joins to get an entity with its TO_MANY associations and if there's no element linked to the entity through associations, the entity related properties are populated with NULL instead of empty ArrayCollections (actually, they are not populated at all and stays with default class properties, since no constructor nor method is called) . Any idea on how to solve this issue ?
Example :
DQL :
SELECT node, ownerLink, node_linkedEntity1, node_linkedEntity1_ownerLink, node_linkedEntity2, node_linkedEntity3 FROM MyNamespace\MyObject node LEFT JOIN node.ownerLink ownerLink LEFT JOIN node.entities1 node_linkedEntity1 LEFT JOIN node_linkedEntity1.ownerLink node_linkedEntity1_ownerLink LEFT JOIN node.entity2 node_linkedEntity2 LEFT JOIN node.entities3 node_linkedEntity3 WHERE node.root=:root_id AND node.id<>:root_id ORDER BY node.lft ASC
Mapping :
MyNamespace\MyObject:
type: entity
table: objects
gedmo:
tree:
type: nested
id:
id:
type: smallint
nullable: false
options:
unsigned: true
id: true
column: id
generator:
strategy: IDENTITY
fields:
lft:
type: integer
options:
unsigned: true
gedmo:
- treeLeft
rgt:
type: integer
options:
unsigned: true
gedmo:
- treeRight
root:
type: smallint
options:
unsigned: true
nullable: true
gedmo:
- treeRoot
lvl:
type: smallint
options:
unsigned: true
gedmo:
- treeLevel
-- anyOtherField
oneToOne:
ownerLink:
targetEntity: MyNamespace\OwnerLink
mappedBy: object
fetch: EAGER
cascade:
- persist
entity2:
targetEntity: MyNamespace\Entity2
cascade:
- persist
fetch: LAZY
joinColumns:
entity2_id:
referencedColumnName: id
orphanRemoval: false
manyToOne:
parent:
targetEntity: MyNamespace\MyObject
inversedBy: children
joinColumn:
name: parent_id
referencedColumnName: id
onDelete: CASCADE
cascade: [persist]
gedmo:
- treeParent
oneToMany:
children:
targetEntity: MyNamespace\MyObject
mappedBy: parent
cascade: [persist]
orderBy:
lft: ASC
manyToMany:
entities1:
targetEntity: MyNamespace\Entity1
mappedBy: object
fetch: EXTRA_LAZY
joinTable:
name: object_entity1
joinColumns:
id:
referencedColumnName: object_id
inverseJoinColumns:
entity1_id:
referencedColumnName: id
entities3:
targetEntity: MyNamepace\Entity3
fetch: EXTRA_LAZY
joinTable:
name: object_entity3
joinColumns:
id:
referencedColumnName: object_id
inverseJoinColumns:
entity3_id:
referencedColumnName: id
lifecycleCallbacks: { }
What's going on is that, at entity creation time, since associations are fetched from the query result, the null values corresponding to empty join results are passed as is to reflection field setValue method, and that's all (fetchAlias fields are ignored after that). If I don't fetch the associations (by removing theme from the select statement), the TO_MANY associations are correctly initiated to empty collections.
I have three entities: HandsetSubscription, Handset and Subscription.
The yaml of HandsetSubscription is:
App\SoBundle\Entity\HandsetSubscription:
type: entity
table: handset_subscription
manyToOne:
handset:
targetEntity: Handset
subscription:
targetEntity: Subscription
id:
id:
type: integer
generator: { strategy: AUTO }
options: { unsigned: true }
fields:
amount:
type: integer
nullable: false
options: { default: 0, unsigned: true }
discount:
type: integer
nullable: false
options: { default: 0, unsigned: true }
The query:
SELECT hs,s,h
FROM \App\SoBundle\Entity\HandsetSubscription hs
JOIN \App\SoBundle\Entity\Subscription s with s.id = hs.subscription
AND s.mins = 150
AND s.mb = 250
AND s.sms = 150
JOIN \App\SoBundle\Entity\Handset h with h.id = hs.handset
These are the class names of the entries retrieved:
App\SoBundle\Entity\HandsetSubscription
Proxies\__CG__\App\SoBundle\Entity\Subscription
Proxies\__CG__\App\SoBundle\Entity\Handset
App\SoBundle\Entity\HandsetSubscription
Proxies\__CG__\App\SoBundle\Entity\Handset
App\SoBundle\Entity\HandsetSubscription
Proxies\__CG__\App\SoBundle\Entity\Handset
…
I would expect to get only HandsetSubscription entities back. Why am I getting proxies of Subscription and Handset too?
By adding fetch eager to the handset and subscription mappings and removing handset and subscription from the SELECT statement in the query I would get only HandsetSubscription but I would like to do this through fetch joins, as stated in the manual (http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#joins).
UPDATE
Quote from the link posted above:
Fetch join of the address:
<?php
$query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'");
$users = $query->getResult();
When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the root level of the result array. In the previous example an array of User instances is returned and the address of each user is fetched and hydrated into the User#address variable. If you access the address Doctrine does not need to lazy load the association with another query.
Big thanks goes to veonik from the #doctrine irc channel for solving this.
Instead of joining with the fully qualified names of the entities you should join with the association. So the query becomes:
SELECT hs,s,h
FROM \App\SoBundle\Entity\HandsetSubscription hs
JOIN hs.subscription s with s.id = hs.subscription
AND s.mins = 150
AND s.mb = 250
AND s.sms = 150
JOIN hs.handset h with h.id = hs.handset
I have a Doctrine model (Assignment), which has a many-to-one relationship with another model (Region). Assignments are owned by users (with each user having only one assignment per region at a time), and I am trying to use indexBy to have the user's array of assignments be keyed by the ID of the assignment's region. However, I only get standard 0..n numeric keys.
When I try to run a DQL query like SELECT am, reg, user FROM Assignment am INDEX BY [...] JOIN am.region reg JOIN am.user user WHERE user.id = ?1, none of these values for INDEX BY work:
region (Error: Invalid PathExpression. Must be a StateFieldPathExpression.)
region_id (Error: Class ...\Assignment has no field or association named region_id)
region.id (Error: Expected end of string, got '.')
Is this possible? If not, then what would be a convenient way to access a User's assignment on a region without indexBy?
I was dealing with the same problem today. Fortunately I've found the solution : )
First of all, you have to declare additional column in ORM mapping:
Abdulklarapl\My\EntityA:
type: entity
table: entityA
manyToOne:
entityB:
targetEntity: EntityB
joinColumn:
name: label_id
referencedColumnName: id
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
value:
type: text
entityB_id:
type: integer
lifecycleCallbacks: { }
notice that I've declared entityB_id as a field + I've configured manyToOne relation by adding a joinColumn clause
so now you can use entityB_id as scalar value
$doctrine->getEntityManager()->createQueryBuilder()
->select('c')
->from('AbdulklaraplMyBundle:EntityA', 'c', 'c.entityB_id')
->getQuery()->getResult();
it will return assoc array
[
c.entityB_id: {
id: "",
value: ""
entityB_id: ""
}
]
you can also use AbstractQuery::HYDRATE_ARRAY as a argument for getResult() - it will return assoc array with array instead the objects
If you need to INDEX BY a foreign key e.g. "region_id" it is possible in your mapping:
/**
* #ORM\OneToMany(targetEntity="Region", mappedBy="user", indexBy="region_id")
*/
protected $regions;
The feature was added here.
Unfortunately it does not seem to be documented that you have to use the name of the column of the foreign key itself.
Working with Indexed Associations
Import the Query class (optional):
use \Doctrine\ORM\Query;
Create the query:
$query = $this->data->em->createQuery('
SELECT a
FROM Assignment a
INDEX BY a.reg //to set array custom key
WHERE a.user = :user');
$query->setParameter('user', 3); //user with id 3
//set the hidration mode in order to work with read-only arrays
$assignments = $query->getResult(Query::HYDRATE_ARRAY);