I've created a JIRA ticket for the issue CAY-2097 <https://issues.apache.org/jira/browse/CAY-2097> and provided a fix https://github.com/apache/cayenne/commit/697f38e5127852a144b13e7640787e 57ac3ebba1
It's a little different from your. It's similar to how we check for properties in case of flattened attributes DataDomainDBDiffBuilder.java#L95 <https://github.com/apache/cayenne/blob/master/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainDBDiffBuilder.java#L95> BTW, not sure that we should check it here. Perhaps, we should check it while filling currentPropertyDiff and currentArcDiff. Looks like in this case performance will be slightly better. 2016-08-03 21:24 GMT+03:00 Savva Kolbachev <[email protected]>: > Hi Dougan, > > I'm able to reproduce the problem you provided. I'll come back to you > after some investigation. > > 2016-07-29 2:47 GMT+03:00 Dougan Stuart <[email protected]>: > >> Sure, here’s the relevant parts: >> >> <db-entity name="company" schema="public"> >> <db-attribute name="name" type="VARCHAR" length="50”/> >> <db-attribute name="uuid" type="OTHER" isPrimaryKey="true" >> isMandatory="true" length="2147483647"/> >> </db-entity> >> <db-entity name="entity" schema="public”> >> <db-attribute name="audit_uuid" type="OTHER" length="2147483647 >> ”/> >> <db-attribute name="reference" type="VARCHAR" length="30"/> >> <db-attribute name="type" type="CHAR" isMandatory="true" >> length="1”/> >> <db-attribute name="uuid" type="OTHER" isPrimaryKey="true" >> isMandatory="true" length="2147483647”/> >> <db-attribute name="parent_entity_uuid" type="OTHER" length=" >> 2147483647"/> >> </db-entity> >> >> <obj-entity name="Company" superEntityName="Entity" >> className=“...data.models.Company”> >> <qualifier><![CDATA[type = "C"]]></qualifier> >> <obj-attribute name="name" type="java.lang.String" >> db-attribute-path="company.name”/> >> </obj-entity> >> <obj-entity name="Entity" abstract="true" className=“...data.models.Entity" >> dbEntityName="entity" superClassName=“...data.CayenneBaseDataObject"> >> <obj-attribute name="reference" type="java.lang.String" >> db-attribute-path="reference”/> >> <obj-attribute name="type" type=“...data.util.EntityType" >> db-attribute-path="type”/> >> </obj-entity> >> >> <db-relationship name="entity" source="company" target="entity" >> toMany="false”> >> <db-attribute-pair source="uuid" target="uuid"/> >> </db-relationship> >> <db-relationship name="company" source="entity" target="company" >> toDependentPK="true" toMany="false"> >> <db-attribute-pair source="uuid" target="uuid"/> >> </db-relationship> >> <db-relationship name="parentEntity" source="entity" target="entity" >> toMany="false”> >> <db-attribute-pair source="parent_entity_uuid" target="uuid”/> >> </db-relationship> >> <db-relationship name="subEntities" source="entity" target="entity" >> toMany="true”> >> <db-attribute-pair source="uuid" target="parent_entity_uuid”/> >> </db-relationship> >> >> <obj-relationship name="parentCompany" source="Company" target="Company" >> deleteRule="Nullify" db-relationship-path="parentEntity"/> >> <obj-relationship name="subCompanies" source="Company" target="Company" >> deleteRule="Deny" db-relationship-path="subEntities"/> >> >> >> I’ve created a fix for this issue in >> DataDomainDBDiffBuilder.appendForeignKeys >> on line 133 (within the nested loop): >> >> String joinSourceName = join.getSourceName(); >> if (dbDiff.get(joinSourceName) == null && >> dbEntity.getAttribute(joinSourceName) >> != null) { >> dbDiff.put(joinSourceName, value); >> } >> >> The only change is that I’m checking if the dbEntity actually has the >> attribute it’s trying to add from the join. If it doesn’t have the >> attribute, then it doesn’t make sense to put it on the dbDiff. In >> DataDomainUpdateBucket.updatedAttribtues (mentioned in my prior >> message), it expects everything in the dbDiff to be an attribute that >> exists on the dbEntity. The fix passed Cayenne’s tests and didn’t appear to >> cause any side effects. Of course, this is all moot if I just botched the >> data map. Let me know what you think. >> >> Thanks, >> Doug >> >> >> >> >> >> > On Jul 28, 2016, at 3:47 AM, Andrus Adamchik <[email protected]> >> wrote: >> > >> > Would you mind posting a relevant part of your DataMap XML? >> > >> > Andrus >> > >> >> On Jul 13, 2016, at 8:40 PM, Dougan Stuart <[email protected]> wrote: >> >> >> >> I have a class Company with a one-to-many relationship to other >> Companies (parentCompany <- subCompanies). I’m able to set a Company’s >> parent when creating one, but upon update I’m getting a >> NullPointerException in DefaultQuotingStrategy.quotedName because it’s >> been sent a null attribute. Company extends the abstract class Entity; on >> the DB side Entity and Company have a one-to-one relationship, and Entity >> has a parentEntity reference (which is modeled as parentCompany). The >> obj-relationship parentCompany is set up with the target Company and path >> parentEntity. >> >> >> >> The null attribute is being added in org.apache.cayenne.access. >> DataDomainUpdateBucket.updatedAttributes. The method is called first >> with the dbEntity Entity and the an updatedSnapshot containing >> parentEntity, which is what I would expect. However, after this >> updatedAttributes is called with Company and an updatedSnapshot containing >> parentEntity, so the following line >> >> >> >> attributes.add(entityAttributes.get(name)) >> >> >> >> will return a null attribute when trying to get the name >> “parentEntity” off Company’s attributes; that attribute only exists on >> Entity. I’d appreciate any help on preventing it from trying to add invalid >> attributes. >> >> >> >> I’m not sure if this is the root cause, but >> >> DataDomainUpdateBucket.appendQueriesInternal >> calls updatedAttributes based off the bucket’s dbEntities, which in this >> case are set in DataDomainSyncBucket.groupObjEntitiesBySpannedDbEntities; >> this method is only adding Company as a “secondary DbEntity” (as referred >> to in the comments) because Company has a flattened attribute “name" that >> does not exist on the Entity. >> >> >> >> Here’s the stack trace: >> >> >> >> java.lang.NullPointerException >> >> at org.apache.cayenne.dba.DefaultQuotingStrategy.quotedName( >> DefaultQuotingStrategy.java:62) >> >> at org.apache.cayenne.access.translator.batch. >> UpdateBatchTranslator.createSql(UpdateBatchTranslator.java:60) >> >> at org.apache.cayenne.access.translator.batch. >> DefaultBatchTranslator.ensureTranslated(DefaultBatchTranslator.java:52) >> >> at org.apache.cayenne.access.translator.batch. >> DefaultBatchTranslator.getSql(DefaultBatchTranslator.java:64) >> >> at org.apache.cayenne.access.jdbc.BatchAction.runAsBatch( >> BatchAction.java:103) >> >> at org.apache.cayenne.access.jdbc.BatchAction. >> performAction(BatchAction.java:90) >> >> at org.apache.cayenne.access.DataNodeQueryAction.runQuery( >> DataNodeQueryAction.java:97) >> >> at org.apache.cayenne.access.DataNode.performQueries( >> DataNode.java:293) >> >> at org.apache.cayenne.access.DataDomainFlushAction.runQueries( >> DataDomainFlushAction.java:233) >> >> at org.apache.cayenne.access.DataDomainFlushAction.flush( >> DataDomainFlushAction.java:154) >> >> at org.apache.cayenne.access.DataDomain.onSyncFlush( >> DataDomain.java:693) >> >> at org.apache.cayenne.access.DataDomain$2.transform( >> DataDomain.java:659) >> >> at org.apache.cayenne.access.DataDomain.runInTransaction( >> DataDomain.java:720) >> >> at org.apache.cayenne.access.DataDomain.onSyncNoFilters( >> DataDomain.java:655) >> >> at org.apache.cayenne.access.DataDomain$ >> DataDomainSyncFilterChain.onSync(DataDomain.java:863) >> >> at org.apache.cayenne.access.DataDomain.onSync(DataDomain. >> java:636) >> >> at org.apache.cayenne.access.DataContext.flushToParent( >> DataContext.java:727) >> >> at org.apache.cayenne.access.DataContext.commitChanges( >> DataContext.java:676) >> >> >> >> Thanks, >> >> Doug >> >> >> > >> >> > > > -- > Best Regards, > Savva Kolbachev > -- Best Regards, Savva Kolbachev
