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
