You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2019/09/02 09:29:29 UTC
[cayenne] 01/03: CAY-2606 Can't resolve obj path with embeddable
component
This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 02ad8881dec2ab9de7578e34f4c4250d034ac9a5
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Thu Aug 29 10:24:18 2019 +0300
CAY-2606 Can't resolve obj path with embeddable component
---
RELEASE-NOTES.txt | 1 +
.../java/org/apache/cayenne/gen/CgenModule.java | 4 +-
.../java/org/apache/cayenne/gen/ImportUtils.java | 5 +
.../java/org/apache/cayenne/gen/PropertyUtils.java | 81 +++++++------
.../EmbeddablePropertyDescriptorCreator.java | 35 +++---
.../templates/v4_1/embeddable-singleclass.vm | 64 ++++++++++-
.../templates/v4_1/embeddable-superclass.vm | 60 +++++++++-
.../org/apache/cayenne/gen/PropertyUtilsTest.java | 13 ++-
...ranslationResult.java => EmbeddableObject.java} | 44 +++----
.../cayenne/access/DataDomainQueryAction.java | 51 ++++++++-
.../jdbc/reader/DefaultRowReaderFactory.java | 11 +-
.../access/jdbc/reader/EmbeddableRowReader.java | 79 +++++++++++++
.../select/CustomColumnSetExtractor.java | 25 ++++
.../access/translator/select/ObjPathProcessor.java | 17 +++
.../access/translator/select/PathProcessor.java | 6 +
.../translator/select/PathTranslationResult.java | 3 +
.../translator/select/QualifierTranslator.java | 90 ++++++++++-----
.../cayenne/exp/property/EmbeddableProperty.java} | 24 ++--
...RelationshipProperty.java => PathProperty.java} | 62 +++-------
.../cayenne/exp/property/PropertyFactory.java | 5 +
.../cayenne/exp/property/RelationshipProperty.java | 120 +------------------
.../org/apache/cayenne/map/EmbeddedResult.java | 59 ++++++++++
.../apache/cayenne/map/PathComponentIterator.java | 15 ++-
.../java/org/apache/cayenne/map/SQLResult.java | 20 +++-
.../DefaultEmbeddableResultSegment.java} | 42 ++++---
.../cayenne/query/EmbeddableResultSegment.java} | 22 +++-
.../org/apache/cayenne/access/EmbeddingIT.java | 127 ++++++++++++++++++++-
.../cayenne/testdo/embeddable/EmbedEntity1.java | 2 +
.../cayenne/testdo/embeddable/EmbedEntity2.java | 9 ++
.../testdo/embeddable/auto/_EmbedEntity1.java | 33 +++++-
.../{_EmbedEntity1.java => _EmbedEntity2.java} | 74 ++++++------
.../testdo/embeddable/auto/_Embeddable1.java | 41 ++++++-
.../src/test/resources/cayenne-embeddable.xml | 2 +
.../src/test/resources/embeddable.map.xml | 25 +++-
34 files changed, 881 insertions(+), 390 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index b638050..f7413ee 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -81,6 +81,7 @@ CAY-2600 Modeler DbImport: Can't retrieve schema for databases with no catalog s
CAY-2601 Modeler DbImport: result dialog issues
CAY-2603 NPE reloading project in the model
CAY-2605 Modeler: Unable to save - java.nio.file.InvalidPathException
+CAY-2606 Can't resolve obj path with embeddable component
CAY-2608 CayenneModeler: NPE when reverse engineering with an auto-adapter DataSource
CAY-2609 Modeler: can't close dbImport result dialog window
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/CgenModule.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/CgenModule.java
index 59cf873..076a5b6 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/CgenModule.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/CgenModule.java
@@ -26,6 +26,7 @@ import org.apache.cayenne.di.Module;
import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
import org.apache.cayenne.gen.property.DatePropertyDescriptorCreator;
+import org.apache.cayenne.gen.property.EmbeddablePropertyDescriptorCreator;
import org.apache.cayenne.gen.property.NumericPropertyDescriptorCreator;
import org.apache.cayenne.gen.property.PropertyDescriptorCreator;
import org.apache.cayenne.gen.property.StringPropertyDescriptorCreator;
@@ -49,7 +50,8 @@ public class CgenModule implements Module{
contributeUserProperties(binder)
.add(NumericPropertyDescriptorCreator.class)
.add(DatePropertyDescriptorCreator.class)
- .add(StringPropertyDescriptorCreator.class);
+ .add(StringPropertyDescriptorCreator.class)
+ .add(EmbeddablePropertyDescriptorCreator.class);
}
public static ListBuilder<PropertyDescriptorCreator> contributeUserProperties(Binder binder) {
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
index ccd72f7..24d8b91 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.gen;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.EmbeddableAttribute;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.util.Util;
@@ -222,6 +223,10 @@ public class ImportUtils {
return attribute.isMandatory() && isPrimitive(attribute.getType());
}
+ public boolean canUsePrimitive(EmbeddableAttribute attribute) {
+ return isPrimitive(attribute.getType());
+ }
+
/**
* Generate package and list of import statements based on the registered
* types.
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
index dfef034..08277e1 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.AdhocObjectFactory;
@@ -34,6 +35,7 @@ import org.apache.cayenne.di.DIRuntimeException;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.property.BaseProperty;
import org.apache.cayenne.exp.property.DateProperty;
+import org.apache.cayenne.exp.property.EmbeddableProperty;
import org.apache.cayenne.exp.property.EntityProperty;
import org.apache.cayenne.exp.property.ListProperty;
import org.apache.cayenne.exp.property.MapProperty;
@@ -69,6 +71,7 @@ public class PropertyUtils {
FACTORY_METHODS.put(ListProperty.class.getName(), "createList");
FACTORY_METHODS.put(SetProperty.class.getName(), "createSet");
FACTORY_METHODS.put(MapProperty.class.getName(), "createMap");
+ FACTORY_METHODS.put(EmbeddableProperty.class.getName(), "createEmbeddable");
}
private static final List<Class<?>> JAVA_DATE_TYPES = Arrays.asList(
@@ -124,23 +127,21 @@ public class PropertyUtils {
}
public void addImport(ObjAttribute attribute) throws ClassNotFoundException {
- if(attribute instanceof EmbeddedAttribute) {
- addImport((EmbeddedAttribute)attribute);
- return;
- }
importUtils.addType(PropertyFactory.class.getName());
importUtils.addType(attribute.getType());
importUtils.addType(getPropertyDescriptor(attribute.getType()).getPropertyType());
}
public void addImport(EmbeddedAttribute attribute) throws ClassNotFoundException {
- Embeddable embeddable = attribute.getEmbeddable();
- importUtils.addType(embeddable.getClassName());
- for(EmbeddableAttribute embeddableAttribute : embeddable.getAttributes()) {
- importUtils.addType(embeddableAttribute.getType());
- importUtils.addType(getPropertyDescriptor(embeddableAttribute.getType()).getPropertyType());
- importUtils.addType(ExpressionFactory.class.getName());
- }
+ importUtils.addType(PropertyFactory.class.getName());
+ importUtils.addType(attribute.getType());
+ importUtils.addType(getPropertyDescriptor(EmbeddableObject.class.getName()).getPropertyType());
+ }
+
+ public void addImport(EmbeddableAttribute attribute) throws ClassNotFoundException {
+ importUtils.addType(PropertyFactory.class.getName());
+ importUtils.addType(attribute.getType());
+ importUtils.addType(getPropertyDescriptor(attribute.getType()).getPropertyType());
}
public void addImport(ObjRelationship relationship) {
@@ -182,10 +183,6 @@ public class PropertyUtils {
}
public String propertyDefinition(ObjAttribute attribute, boolean client) throws ClassNotFoundException {
- if(attribute instanceof EmbeddedAttribute) {
- return propertyDefinition((EmbeddedAttribute)attribute);
- }
-
StringUtils utils = StringUtils.getInstance();
String attributeType = utils.stripGeneric(importUtils.formatJavaType(attribute.getType(), false));
PropertyDescriptor propertyDescriptor = getPropertyDescriptor(attribute.getType());
@@ -216,31 +213,41 @@ public class PropertyUtils {
public String propertyDefinition(EmbeddedAttribute attribute) throws ClassNotFoundException {
StringUtils utils = StringUtils.getInstance();
+ String attributeType = utils.stripGeneric(importUtils.formatJavaType(attribute.getType(), false));
+ PropertyDescriptor propertyDescriptor = getPropertyDescriptor(EmbeddableObject.class.getName());
+ return String.format("public static final %s<%s> %s = %s(\"%s\", %s.class);",
+ importUtils.formatJavaType(propertyDescriptor.getPropertyType()),
+ attributeType,
+ generatePropertyName(attribute),
+ propertyDescriptor.getPropertyFactoryMethod(),
+ attribute.getName(),
+ attributeType
+ );
+ }
+
+ public String propertyDefinition(EmbeddableAttribute attribute) throws ClassNotFoundException {
+ StringUtils utils = StringUtils.getInstance();
+ String attributeType = utils.stripGeneric(importUtils.formatJavaType(attribute.getType(), false));
+ PropertyDescriptor propertyDescriptor = getPropertyDescriptor(attribute.getType());
+ return String.format("public static final %s<%s> %s = %s(\"%s\", %s.class);",
+ importUtils.formatJavaType(propertyDescriptor.getPropertyType()),
+ attributeType,
+ generatePropertyName(attribute),
+ propertyDescriptor.getPropertyFactoryMethod(),
+ attribute.getName(),
+ attributeType
+ );
+ }
+ protected String generatePropertyName(EmbeddableAttribute attribute) {
+ StringUtils utils = StringUtils.getInstance();
Embeddable embeddable = attribute.getEmbeddable();
- Collection<EmbeddableAttribute> attributes = embeddable.getAttributes();
-
- String[] attributesDefinitions = new String[attributes.size()];
- int i = 0;
- for(EmbeddableAttribute embeddableAttribute : attributes) {
- PropertyDescriptor propertyDescriptor = getPropertyDescriptor(embeddableAttribute.getType());
- String attributeType = utils.stripGeneric(importUtils.formatJavaType(embeddableAttribute.getType(), false));
- String path = attribute.getAttributeOverrides()
- .getOrDefault(embeddableAttribute.getName(), embeddableAttribute.getDbAttributeName());
-
- String propertyName = utils.capitalizedAsConstant(attribute.getName()) + "_" + utils.capitalizedAsConstant(embeddableAttribute.getName());
- attributesDefinitions[i++] = String.format("public static final %s<%s> %s " +
- "= %s(ExpressionFactory.dbPathExp(\"%s\"), %s.class);",
- importUtils.formatJavaType(propertyDescriptor.getPropertyType()),
- attributeType,
- propertyName,
- propertyDescriptor.getPropertyFactoryMethod(),
- path,
- attributeType
- );
+ String name = utils.capitalizedAsConstant(attribute.getName());
+ // ensure that final name is unique
+ while(embeddable.getAttribute(name) != null) {
+ name = name + DUPLICATE_NAME_SUFFIX;
}
-
- return String.join("\n ", attributesDefinitions);
+ return name;
}
public String propertyDefinition(ObjRelationship relationship, boolean client) {
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/property/EmbeddablePropertyDescriptorCreator.java
similarity index 57%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
copy to cayenne-cgen/src/main/java/org/apache/cayenne/gen/property/EmbeddablePropertyDescriptorCreator.java
index ef2ab3f..93b62bc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/property/EmbeddablePropertyDescriptorCreator.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -17,35 +17,26 @@
* under the License.
****************************************************************/
-package org.apache.cayenne.access.translator.select;
+package org.apache.cayenne.gen.property;
-import java.util.List;
import java.util.Optional;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.EmbeddableObject;
+import org.apache.cayenne.exp.property.EmbeddableProperty;
+import org.apache.cayenne.gen.PropertyDescriptor;
/**
- * Interface that describes result of path translation
- *
* @since 4.2
*/
-interface PathTranslationResult {
-
- String getFinalPath();
-
- Optional<DbRelationship> getDbRelationship();
-
- List<DbAttribute> getDbAttributes();
+public class EmbeddablePropertyDescriptorCreator implements PropertyDescriptorCreator {
- List<String> getAttributePaths();
+ private static final String FACTORY_METHOD = "PropertyFactory.createEmbeddable";
- default DbAttribute getLastAttribute() {
- return getDbAttributes().get(getDbAttributes().size() - 1);
+ @Override
+ public Optional<PropertyDescriptor> apply(Class<?> aClass) {
+ if(EmbeddableObject.class.isAssignableFrom(aClass)) {
+ return Optional.of(new PropertyDescriptor(EmbeddableProperty.class.getName(), FACTORY_METHOD));
+ }
+ return Optional.empty();
}
-
- default String getLastAttributePath() {
- return getAttributePaths().get(getAttributePaths().size() - 1);
- }
-
}
diff --git a/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-singleclass.vm b/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-singleclass.vm
index 336cb86..6ffb578 100644
--- a/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-singleclass.vm
+++ b/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-singleclass.vm
@@ -5,7 +5,7 @@
## to you under the Apache License, Version 2.0 (the
## "License"); you may not use this file except in compliance
## with the License. You may obtain a copy of the License at
-##
+##x
## https://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing,
@@ -37,23 +37,30 @@
${importUtils.setPackage($subPackageName)}##
${importUtils.addType("java.io.Serializable")}##
${importUtils.addReservedType("${subPackageName}.${subClassName}")}##
-${importUtils.addType("${basePackageName}.${baseClassName}")}##
+${importUtils.addType("org.apache.cayenne.EmbeddableObject")}##
${importUtils.addType("org.apache.cayenne.Persistent")}##
#foreach( $attr in ${object.Attributes} )
-$importUtils.addType(${attr.Type})##
+$propertyUtils.addImport($attr)##
#end
${importUtils.generate()}
-## extends ${baseClassName} - always Object, so skip it
-public abstract class ${subClassName} implements Serializable {
-
+## extends ${baseClassName} - always Object, so skip it
+public class ${subClassName} implements EmbeddableObject, Serializable {
## Create property names
#if( $createPropertyNames )
+
#foreach( $attr in ${object.Attributes} )
public static final String ${stringUtils.capitalizedAsConstant($attr.Name)}_PROPERTY = "${attr.Name}";
#end
+#end
+#######################
+## Create Properties ##
+#######################
+#foreach( $attr in ${object.Attributes} )
+ $propertyUtils.propertyDefinition($attr)
#end
+
// special properties injected by Cayenne
private Persistent owner;
private String embeddedProperty;
@@ -96,4 +103,49 @@ public abstract class ${subClassName} implements Serializable {
#end
#end
+###########################################################
+## Create writePropertyDirect/readPropertyDirect methods ##
+###########################################################
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+#foreach( $attr in ${object.Attributes} )
+#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
+ case "${attr.Name}":
+ return this.${name};
+#end
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+#foreach( $attr in ${object.Attributes} )
+#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
+#set ( $flag = $importUtils.canUsePrimitive($attr) )
+#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)")
+ case "${attr.Name}":
+#if ( $importUtils.isBoolean($type) )
+ this.${name} = val == null ? false : ($type)val;
+#elseif ($importUtils.isPrimitive($type))
+ this.${name} = val == null ? 0 : ($type)val;
+#else
+ this.${name} = ($type)val;
+#end
+ break;
+#end
+ default:
+ throw new IllegalArgumentException("Unknown property: " + propName);
+ }
+ }
}
diff --git a/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-superclass.vm b/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-superclass.vm
index 573e697..c040c3e 100644
--- a/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-superclass.vm
+++ b/cayenne-cgen/src/main/resources/templates/v4_1/embeddable-superclass.vm
@@ -37,10 +37,10 @@
${importUtils.setPackage($superPackageName)}##
${importUtils.addType("java.io.Serializable")}##
${importUtils.addReservedType("${superPackageName}.${superClassName}")}##
-${importUtils.addType("${basePackageName}.${baseClassName}")}##
+${importUtils.addType("org.apache.cayenne.EmbeddableObject")}##
${importUtils.addType("org.apache.cayenne.Persistent")}##
#foreach( $attr in ${object.Attributes} )
-$importUtils.addType(${attr.Type})##
+$propertyUtils.addImport($attr)##
#end
${importUtils.generate()}
@@ -51,15 +51,22 @@ ${importUtils.generate()}
* If you need to make any customizations, please use subclass.
*/
## extends ${baseClassName} - always Object, so skip it
-public abstract class ${superClassName} implements Serializable {
-
+public abstract class ${superClassName} implements EmbeddableObject, Serializable {
## Create property names
#if( $createPropertyNames )
+
#foreach( $attr in ${object.Attributes} )
public static final String ${stringUtils.capitalizedAsConstant($attr.Name)}_PROPERTY = "${attr.Name}";
#end
+#end
+#######################
+## Create Properties ##
+#######################
+#foreach( $attr in ${object.Attributes} )
+ $propertyUtils.propertyDefinition($attr)
#end
+
// special properties injected by Cayenne
private Persistent owner;
private String embeddedProperty;
@@ -102,4 +109,49 @@ public abstract class ${superClassName} implements Serializable {
#end
#end
+###########################################################
+## Create writePropertyDirect/readPropertyDirect methods ##
+###########################################################
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+#foreach( $attr in ${object.Attributes} )
+#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
+ case "${attr.Name}":
+ return this.${name};
+#end
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+#foreach( $attr in ${object.Attributes} )
+#set ( $name = "$stringUtils.formatVariableName(${attr.Name})")
+#set ( $flag = $importUtils.canUsePrimitive($attr) )
+#set ( $type = "$importUtils.formatJavaType(${attr.Type}, $flag)")
+ case "${attr.Name}":
+#if ( $importUtils.isBoolean($type) )
+ this.${name} = val == null ? false : ($type)val;
+#elseif ($importUtils.isPrimitive($type))
+ this.${name} = val == null ? 0 : ($type)val;
+#else
+ this.${name} = ($type)val;
+#end
+ break;
+#end
+ default:
+ throw new IllegalArgumentException("Unknown property: " + propName);
+ }
+ }
}
diff --git a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
index 7b75365..d5920a6 100644
--- a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
+++ b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
@@ -27,6 +27,7 @@ import org.apache.cayenne.configuration.server.ServerModule;
import org.apache.cayenne.di.DIBootstrap;
import org.apache.cayenne.di.spi.DefaultScope;
import org.apache.cayenne.exp.property.DateProperty;
+import org.apache.cayenne.exp.property.EmbeddableProperty;
import org.apache.cayenne.exp.property.EntityProperty;
import org.apache.cayenne.exp.property.ListProperty;
import org.apache.cayenne.exp.property.MapProperty;
@@ -203,14 +204,14 @@ public class PropertyUtilsTest {
dataMap.addEmbeddable(embeddable);
- String definition = propertyUtils.propertyDefinition(embeddedAttribute);
- assertEquals("public static final StringProperty<String> A_TEST_EMB_ATTR = PropertyFactory.createString(ExpressionFactory.dbPathExp(\"testPath\"), String.class);",
+ String definition = propertyUtils.propertyDefinition(attribute);
+ assertEquals("public static final StringProperty<String> TEST_EMB_ATTR = PropertyFactory.createString(\"testEmbAttr\", String.class);",
definition);
}
@Test
public void simpleNumericPropertyEmbDefinition() throws ClassNotFoundException {
- importUtils.addType(NumericProperty.class.getName());
+ importUtils.addType(EmbeddableProperty.class.getName());
DataMap dataMap = new DataMap();
@@ -234,7 +235,7 @@ public class PropertyUtilsTest {
dataMap.addEmbeddable(embeddable);
String definition = propertyUtils.propertyDefinition(embeddedAttribute);
- assertEquals("public static final NumericProperty<Integer> A_TEST_EMB_ATTR = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp(\"testPath\"), Integer.class);",
+ assertEquals("public static final EmbeddableProperty<test> A = PropertyFactory.createEmbeddable(\"a\", test.class);",
definition);
}
@@ -264,8 +265,8 @@ public class PropertyUtilsTest {
dataMap.addEmbeddable(embeddable);
- String definition = propertyUtils.propertyDefinition(embeddedAttribute);
- assertEquals("public static final CustomProperty<TimestampType> A_TEST_EMB_ATTR = new CustomProperty(ExpressionFactory.dbPathExp(\"testPath\"), TimestampType.class);",
+ String definition = propertyUtils.propertyDefinition(attribute);
+ assertEquals("public static final CustomProperty<TimestampType> TEST_EMB_ATTR = new CustomProperty(\"testEmbAttr\", TimestampType.class);",
definition);
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-server/src/main/java/org/apache/cayenne/EmbeddableObject.java
similarity index 55%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
copy to cayenne-server/src/main/java/org/apache/cayenne/EmbeddableObject.java
index ef2ab3f..c098b99 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/EmbeddableObject.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -17,35 +17,27 @@
* under the License.
****************************************************************/
-package org.apache.cayenne.access.translator.select;
-
-import java.util.List;
-import java.util.Optional;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbRelationship;
+package org.apache.cayenne;
/**
- * Interface that describes result of path translation
*
* @since 4.2
*/
-interface PathTranslationResult {
-
- String getFinalPath();
-
- Optional<DbRelationship> getDbRelationship();
-
- List<DbAttribute> getDbAttributes();
-
- List<String> getAttributePaths();
-
- default DbAttribute getLastAttribute() {
- return getDbAttributes().get(getDbAttributes().size() - 1);
- }
-
- default String getLastAttributePath() {
- return getAttributePaths().get(getAttributePaths().size() - 1);
- }
+public interface EmbeddableObject {
+
+ /**
+ * Modifies a value of a named property without altering the object state in any way,
+ * and without triggering any database operations. This method is intended mostly for
+ * internal use by Cayenne framework, and shouldn't be called from the application
+ * code.
+ */
+ void writePropertyDirectly(String propertyName, Object val);
+
+ /**
+ * Returns mapped property value as currently stored in the DataObject. Returned value
+ * maybe a fault or a real value. This method will not attempt to resolve faults, or
+ * to read unmapped properties.
+ */
+ Object readPropertyDirectly(String propertyName);
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index dba1041..8697160 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataRow;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
@@ -31,9 +32,11 @@ import org.apache.cayenne.cache.QueryCacheEntryFactory;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.EntityInheritanceTree;
import org.apache.cayenne.map.LifecycleEvent;
import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.query.EmbeddableResultSegment;
import org.apache.cayenne.query.EntityResultSegment;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.PrefetchSelectQuery;
@@ -483,6 +486,8 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
if (metadata.isSingleResultSetMapping()) {
if (rsMapping.get(0) instanceof EntityResultSegment) {
converter = new SingleObjectConversionStrategy();
+ } else if(rsMapping.get(0) instanceof EmbeddableResultSegment) {
+ converter = new SingleEmbeddableConversionStrategy();
} else {
converter = new SingleScalarConversionStrategy();
}
@@ -705,6 +710,33 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
}
}
+ class SingleEmbeddableConversionStrategy extends ObjectConversionStrategy<DataRow> {
+
+ @Override
+ void convert(List<DataRow> mainRows) {
+ EmbeddableResultSegment resultSegment = (EmbeddableResultSegment)metadata.getResultSetMapping().get(0);
+ Embeddable embeddable = resultSegment.getEmbeddable();
+ Class<?> embeddableClass;
+ try {
+ embeddableClass = Class.forName(embeddable.getClassName());
+ } catch (Exception e) {
+ throw new CayenneRuntimeException("Unable create Embeddable class %s", e, embeddable.getClassName());
+ }
+ List<EmbeddableObject> result = new ArrayList<>(mainRows.size());
+ mainRows.forEach(dataRow -> {
+ EmbeddableObject eo;
+ try {
+ eo = (EmbeddableObject)embeddableClass.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new CayenneRuntimeException("Unable create instance of Embeddable %s", e, embeddable.getClassName());
+ }
+ dataRow.forEach(eo::writePropertyDirectly);
+ result.add(eo);
+ });
+ updateResponse(mainRows, result);
+ }
+ }
+
class MixedConversionStrategy extends ObjectConversionStrategy<Object[]> {
protected PrefetchProcessorNode toResultsTree(ClassDescriptor descriptor, PrefetchTreeNode prefetchTree,
@@ -748,9 +780,8 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
List<Object> rsMapping = metadata.getResultSetMapping();
int width = rsMapping.size();
- // no conversions needed for scalar positions; reuse Object[]'s to
- // fill them
- // with resolved objects
+ // no conversions needed for scalar positions;
+ // reuse Object[]'s to fill them with resolved objects
List<PrefetchProcessorNode> segmentNodes = new ArrayList<>(width);
for (int i = 0; i < width; i++) {
Object mapping = rsMapping.get(i);
@@ -767,6 +798,20 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
Object[] row = mainRows.get(j);
row[i] = objects.get(j);
}
+ } else if (mapping instanceof EmbeddableResultSegment) {
+ EmbeddableResultSegment resultSegment = (EmbeddableResultSegment)mapping;
+ Embeddable embeddable = resultSegment.getEmbeddable();
+ try {
+ Class<?> embeddableClass = Class.forName(embeddable.getClassName());
+ for(Object[] row : mainRows) {
+ DataRow dataRow = (DataRow)row[i];
+ EmbeddableObject eo = (EmbeddableObject)embeddableClass.newInstance();
+ dataRow.forEach(eo::writePropertyDirectly);
+ row[i] = eo;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
index db41752..4394a08 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
@@ -35,6 +35,7 @@ import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.query.EmbeddableResultSegment;
import org.apache.cayenne.query.EntityResultSegment;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.query.ScalarResultSegment;
@@ -69,6 +70,8 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
if (segment instanceof EntityResultSegment) {
return createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment,
postProcessorFactory);
+ } else if (segment instanceof EmbeddableResultSegment) {
+ return createEmbeddableRowReader(descriptor, queryMetadata, (EmbeddableResultSegment) segment);
} else {
return createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment);
}
@@ -83,6 +86,8 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
i,
createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment,
postProcessorFactory));
+ } else if(segment instanceof EmbeddableResultSegment) {
+ reader.addRowReader(i, createEmbeddableRowReader(descriptor, queryMetadata, (EmbeddableResultSegment) segment));
} else {
reader.addRowReader(i, createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment));
}
@@ -92,8 +97,12 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
}
}
+ private RowReader<?> createEmbeddableRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EmbeddableResultSegment segment) {
+ return new EmbeddableRowReader(descriptor, queryMetadata, segment);
+ }
+
protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, ScalarResultSegment segment) {
- return new ScalarRowReader<Object>(descriptor, segment);
+ return new ScalarRowReader<>(descriptor, segment);
}
protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java
new file mode 100644
index 0000000..d9056e5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java
@@ -0,0 +1,79 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.jdbc.reader;
+
+import java.sql.ResultSet;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.access.jdbc.RowDescriptor;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.query.EmbeddableResultSegment;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.util.Util;
+
+/**
+ * @since 4.2
+ */
+class EmbeddableRowReader implements RowReader<DataRow> {
+
+ private final int startIndex;
+ private final int mapCapacity;
+ private final ExtendedType[] converters;
+ private final String[] labels;
+ private final int[] types;
+
+ EmbeddableRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EmbeddableResultSegment segment) {
+ int segmentWidth = segment.getFields().size();
+ this.startIndex = segment.getColumnOffset();
+ this.converters = new ExtendedType[segmentWidth];
+ this.types = new int[segmentWidth];
+ this.labels = new String[segmentWidth];
+
+ ExtendedType[] converters = descriptor.getConverters();
+ ColumnDescriptor[] columns = descriptor.getColumns();
+ for (int i = 0; i < segmentWidth; i++) {
+ this.converters[i] = converters[startIndex + i];
+ types[i] = columns[startIndex + i].getJdbcType();
+ labels[i] = segment.getFields().get(columns[startIndex +i].getName());
+ }
+ this.mapCapacity = (int) Math.ceil(segmentWidth / 0.75);
+ }
+
+ @Override
+ public DataRow readRow(ResultSet resultSet) {
+ try {
+ DataRow row = new DataRow(mapCapacity);
+ int len = converters.length;
+ for (int i = 0; i < len; i++) {
+ // note: jdbc column indexes start from 1, not 0 as in arrays
+ Object val = converters[i].materializeObject(resultSet, startIndex + i + 1, types[i]);
+ row.put(labels[i], val);
+ }
+ return row;
+ } catch (CayenneRuntimeException cex) {
+ // rethrow unmodified
+ throw cex;
+ } catch (Exception otherex) {
+ throw new CayenneRuntimeException("Exception materializing id column.", Util.unwindException(otherex));
+ }
+ }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
index 818731a..1519b60 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
@@ -23,11 +23,15 @@ import java.util.Collection;
import java.util.Map;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EmbeddedAttribute;
+import org.apache.cayenne.map.EmbeddedResult;
import org.apache.cayenne.map.JoinType;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.reflect.ClassDescriptor;
@@ -51,6 +55,8 @@ class CustomColumnSetExtractor implements ColumnExtractor {
for (BaseProperty<?> property : columns) {
if (isFullObjectProp(property)) {
extractFullObject(prefix, property);
+ } else if(isEmbeddedProp(property)) {
+ extractEmbeddedObject(prefix, property);
} else {
extractSimpleProperty(property);
}
@@ -82,6 +88,25 @@ class CustomColumnSetExtractor implements ColumnExtractor {
&& Persistent.class.isAssignableFrom(property.getType()));
}
+ private boolean isEmbeddedProp(BaseProperty<?> property) {
+ return EmbeddableObject.class.isAssignableFrom(property.getType());
+ }
+
+ private void extractEmbeddedObject(String prefix, BaseProperty<?> property) {
+ Object o = property.getExpression().evaluate(context.getMetadata().getObjEntity());
+ if(!(o instanceof EmbeddedAttribute)) {
+ throw new CayenneRuntimeException("EmbeddedAttribute expected, %s found", o);
+ }
+ EmbeddedAttribute attribute = (EmbeddedAttribute) o;
+ EmbeddedResult result = new EmbeddedResult(attribute.getEmbeddable(), attribute.getAttributes().size());
+ attribute.getAttributes().forEach(attr -> {
+ Node sqlNode = context.getQualifierTranslator().translate(ExpressionFactory.dbPathExp(attr.getDbAttributePath()));
+ context.addResultNode(sqlNode, true, null, null);
+ result.addAttribute(attr);
+ });
+ context.getSqlResult().addEmbeddedResult(result);
+ }
+
private void extractFullObject(String prefix, BaseProperty<?> property) {
prefix = calculatePrefix(prefix, property);
ensureJoin(prefix);
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
index 5775a5c..e01ec36 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ObjPathProcessor.java
@@ -19,7 +19,10 @@
package org.apache.cayenne.access.translator.select;
+import java.util.Optional;
+
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.EmbeddedAttribute;
import org.apache.cayenne.map.JoinType;
import org.apache.cayenne.map.ObjAttribute;
@@ -86,6 +89,13 @@ class ObjPathProcessor extends PathProcessor<ObjEntity> {
protected void processAttribute(ObjAttribute attribute) {
if(attribute instanceof EmbeddedAttribute) {
embeddedAttribute = (EmbeddedAttribute)attribute;
+ if(lastComponent) {
+ embeddedAttribute.getAttributes().forEach(a -> {
+ int len = currentDbPath.length();
+ processAttribute(a);
+ currentDbPath.delete(len, currentDbPath.length());
+ });
+ }
return;
}
@@ -132,4 +142,11 @@ class ObjPathProcessor extends PathProcessor<ObjEntity> {
currentDbPath.append(result.getFinalPath());
}
+ @Override
+ public Optional<Embeddable> getEmbeddable() {
+ if(embeddedAttribute != null) {
+ return Optional.of(embeddedAttribute.getEmbeddable());
+ }
+ return Optional.empty();
+ }
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
index 18ec894..425c570 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java
@@ -27,6 +27,7 @@ import java.util.Optional;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.Entity;
/**
@@ -110,6 +111,11 @@ abstract class PathProcessor<T extends Entity> implements PathTranslationResult
}
@Override
+ public Optional<Embeddable> getEmbeddable() {
+ return Optional.empty();
+ }
+
+ @Override
public String getFinalPath() {
return currentDbPath.toString();
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
index ef2ab3f..fe4968b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
@@ -24,6 +24,7 @@ import java.util.Optional;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
/**
* Interface that describes result of path translation
@@ -36,6 +37,8 @@ interface PathTranslationResult {
Optional<DbRelationship> getDbRelationship();
+ Optional<Embeddable> getEmbeddable();
+
List<DbAttribute> getDbAttributes();
List<String> getAttributePaths();
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index 2311f0f..c199f49 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -22,11 +22,13 @@ package org.apache.cayenne.access.translator.select;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
@@ -56,6 +58,7 @@ import org.apache.cayenne.exp.property.BaseProperty;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.aliased;
import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.function;
@@ -238,7 +241,9 @@ class QualifierTranslator implements TraversalHandler {
}
private Node processPathTranslationResult(Expression node, Expression parentNode, PathTranslationResult result) {
- if(result.getDbRelationship().isPresent()
+ if(result.getEmbeddable().isPresent()) {
+ return createEmbeddableMatch(node, parentNode, result);
+ } else if(result.getDbRelationship().isPresent()
&& result.getDbAttributes().size() > 1
&& result.getDbRelationship().get().getTargetEntity().getPrimaryKeys().size() > 1) {
return createMultiAttributeMatch(node, parentNode, result);
@@ -250,6 +255,39 @@ class QualifierTranslator implements TraversalHandler {
}
}
+ private Node createEmbeddableMatch(Expression node, Expression parentNode, PathTranslationResult result) {
+ Embeddable embeddable = result.getEmbeddable()
+ .orElseThrow(() -> new CayenneRuntimeException("Incorrect path '%s' translation, embeddable expected"
+ , result.getFinalPath()));
+
+ Map<String, Object> valueSnapshot = getEmbeddableValueSnapshot(embeddable, node, parentNode);
+
+ expressionsToSkip.add(node);
+ expressionsToSkip.add(parentNode);
+
+ return buildMultiValueComparision(result, valueSnapshot);
+ }
+
+ private Map<String, Object> getEmbeddableValueSnapshot(Embeddable embeddable, Expression node, Expression parentNode) {
+ int siblings = parentNode.getOperandCount();
+ for(int i=0; i<siblings; i++) {
+ Object operand = parentNode.getOperand(i);
+ if(node == operand) {
+ continue;
+ }
+
+ if(operand instanceof EmbeddableObject) {
+ EmbeddableObject embeddableObject = (EmbeddableObject)operand;
+ Map<String, Object> snapshot = new HashMap<>(embeddable.getAttributes().size());
+ embeddable.getAttributeMap().forEach((name, attr) ->
+ snapshot.put(attr.getDbAttributeName(), embeddableObject.readPropertyDirectly(name)));
+ return snapshot;
+ }
+ }
+
+ throw new CayenneRuntimeException("Embeddable attribute ObjPath isn't matched with valid value.");
+ }
+
private Node createMultiAttributeMatch(Expression node, Expression parentNode, PathTranslationResult result) {
DbRelationship relationship = result.getDbRelationship()
.orElseThrow(() -> new CayenneRuntimeException("Incorrect path '%s' translation, relationship expected"
@@ -262,37 +300,16 @@ class QualifierTranslator implements TraversalHandler {
, relationship.getSourceEntityName(), relationship.getName());
}
- expressionsToSkip.add(node);
- expressionsToSkip.add(parentNode);
-
Map<String, Object> valueSnapshot = getMultiAttributeValueSnapshot(node, parentNode);
- if(valueSnapshot == null) {
- throw new CayenneRuntimeException("Multi attribute ObjPath isn't matched with valid value. " +
- "List or Persistent object required.");
- }
-
- ExpressionNodeBuilder expressionNodeBuilder = null;
- ExpressionNodeBuilder eq;
-
- String path = result.getLastAttributePath();
- String alias = context.getTableTree().aliasForPath(path);
-
// convert snapshot if we have attributes from source, not target
if(result.getLastAttribute().getEntity() == relationship.getSourceEntity()) {
valueSnapshot = relationship.srcFkSnapshotWithTargetSnapshot(valueSnapshot);
}
- for(DbAttribute attribute : result.getDbAttributes()) {
- Object nextValue = valueSnapshot.get(attribute.getName());
- eq = table(alias).column(attribute).eq(value(nextValue));
- if (expressionNodeBuilder == null) {
- expressionNodeBuilder = eq;
- } else {
- expressionNodeBuilder = expressionNodeBuilder.and(eq);
- }
- }
+ expressionsToSkip.add(node);
+ expressionsToSkip.add(parentNode);
- return expressionNodeBuilder.build();
+ return buildMultiValueComparision(result, valueSnapshot);
}
private Map<String, Object> getMultiAttributeValueSnapshot(Expression node, Expression parentNode) {
@@ -313,7 +330,28 @@ class QualifierTranslator implements TraversalHandler {
}
}
- return null;
+ throw new CayenneRuntimeException("Multi attribute ObjPath isn't matched with valid value. " +
+ "List or Persistent object required.");
+ }
+
+ private Node buildMultiValueComparision(PathTranslationResult result, Map<String, Object> valueSnapshot) {
+ ExpressionNodeBuilder expressionNodeBuilder = null;
+ ExpressionNodeBuilder eq;
+
+ String path = result.getLastAttributePath();
+ String alias = context.getTableTree().aliasForPath(path);
+
+ for (DbAttribute attribute : result.getDbAttributes()) {
+ Object nextValue = valueSnapshot.get(attribute.getName());
+ eq = table(alias).column(attribute).eq(value(nextValue));
+ if (expressionNodeBuilder == null) {
+ expressionNodeBuilder = eq;
+ } else {
+ expressionNodeBuilder = expressionNodeBuilder.and(eq);
+ }
+ }
+
+ return expressionNodeBuilder.build();
}
private boolean nodeProcessed(Expression node) {
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EmbeddableProperty.java
similarity index 56%
copy from cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
copy to cayenne-server/src/main/java/org/apache/cayenne/exp/property/EmbeddableProperty.java
index 119a0ce..cb5c564 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EmbeddableProperty.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -16,13 +16,23 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.testdo.embeddable;
-import org.apache.cayenne.testdo.embeddable.auto._EmbedEntity1;
+package org.apache.cayenne.exp.property;
-public class EmbedEntity1 extends _EmbedEntity1 {
+/**
+ * Property that represents object attribute mapped on {@link org.apache.cayenne.map.Embeddable} object.
+ * @since 4.2
+ */
+public class EmbeddableProperty<E> extends BaseProperty<E> implements PathProperty<E> {
+ /**
+ * Constructs a new property with the given name and type
+ *
+ * @param name of the property (will be used as alias for the expression)
+ * @param type of the property
+ * @see PropertyFactory#createEmbeddable(String, Class)
+ */
+ protected EmbeddableProperty(String name, Class<? super E> type) {
+ super(name, null, type);
+ }
}
-
-
-
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PathProperty.java
similarity index 71%
copy from cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
copy to cayenne-server/src/main/java/org/apache/cayenne/exp/property/PathProperty.java
index 5412306..9746950 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PathProperty.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -19,19 +19,16 @@
package org.apache.cayenne.exp.property;
-import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.Persistent;
-import org.apache.cayenne.query.PrefetchTreeNode;
/**
- * Interface (or "Trait") that provides basic functionality for all types of relationships.
- * <p>
- * Provides "dot", prefetch and "outer" functionality.
+ * Property that represents path segment (relationship or embeddable).
+ * Basically it provides {@code dot()} operator.
*
- * @see org.apache.cayenne.exp.property
* @since 4.2
*/
-public interface RelationshipProperty<E> extends Property<E> {
+public interface PathProperty<E> extends Property<E> {
/**
* Constructs a property path by appending the argument to the existing property separated by a dot.
@@ -103,7 +100,7 @@ public interface RelationshipProperty<E> extends Property<E> {
* @param property to append to path
* @return a newly created Property object.
*/
- default <T extends Persistent> EntityProperty<T> dot(EntityProperty<T> property) {
+ default <T extends Persistent > EntityProperty<T> dot(EntityProperty<T> property) {
String path = getName() + "." + property.getName();
return PropertyFactory.createEntity(path,
PropertyUtils.buildExp(path, getExpression().getPathAliases()),
@@ -151,46 +148,13 @@ public interface RelationshipProperty<E> extends Property<E> {
}
/**
- * Returns a version of this property that represents an OUTER join. It is
- * up to caller to ensure that the property corresponds to a relationship,
- * as "outer" attributes make no sense.
- */
- BaseProperty<E> outer();
-
- /**
- * Returns a prefetch tree that follows this property path, potentially
- * spanning a number of phantom nodes, and having a single leaf with "joint"
- * prefetch semantics.
- */
- default PrefetchTreeNode joint() {
- if(!getExpression().getPathAliases().isEmpty()) {
- throw new CayenneRuntimeException("Can't use aliases with prefetch");
- }
- return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
- }
-
- /**
- * Returns a prefetch tree that follows this property path, potentially
- * spanning a number of phantom nodes, and having a single leaf with
- * "disjoint" prefetch semantics.
- */
- default PrefetchTreeNode disjoint() {
- if(!getExpression().getPathAliases().isEmpty()) {
- throw new CayenneRuntimeException("Can't use aliases with prefetch");
- }
- return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
- }
-
- /**
- * Returns a prefetch tree that follows this property path, potentially
- * spanning a number of phantom nodes, and having a single leaf with
- * "disjoint by id" prefetch semantics.
+ * Constructs a new property path by appending the argument to the existing property separated by a dot.
+ *
+ * @param property to append to path
+ * @return a newly created Property object.
*/
- default PrefetchTreeNode disjointById() {
- if(!getExpression().getPathAliases().isEmpty()) {
- throw new CayenneRuntimeException("Can't use aliases with prefetch");
- }
- return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
+ default <T extends EmbeddableObject> EmbeddableProperty<T> dot(EmbeddableProperty<T> property) {
+ String path = getName() + "." + property.getName();
+ return PropertyFactory.createEmbeddable(path, property.getType());
}
-
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
index 14b004d..cf1ef6e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.exp.property;
import java.time.LocalDateTime;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
@@ -372,4 +373,8 @@ public class PropertyFactory {
public static <K, V extends Persistent> MapProperty<K, V> createMap(String name, Class<K> keyType, Class<V> entityType) {
return createMap(name, null, keyType, entityType);
}
+
+ public static <T extends EmbeddableObject> EmbeddableProperty<T> createEmbeddable(String name, Class<T> embeddableType) {
+ return new EmbeddableProperty<>(name, embeddableType);
+ }
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
index 5412306..4ccd893 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
@@ -20,7 +20,6 @@
package org.apache.cayenne.exp.property;
import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.Persistent;
import org.apache.cayenne.query.PrefetchTreeNode;
/**
@@ -31,124 +30,7 @@ import org.apache.cayenne.query.PrefetchTreeNode;
* @see org.apache.cayenne.exp.property
* @since 4.2
*/
-public interface RelationshipProperty<E> extends Property<E> {
-
- /**
- * Constructs a property path by appending the argument to the existing property separated by a dot.
- *
- * @return a newly created Property object.
- */
- default BaseProperty<Object> dot(String property) {
- String path = getName() + "." + property;
- return PropertyFactory.createBase(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- null);
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T> BaseProperty<T> dot(BaseProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createBase(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T extends Number> NumericProperty<T> dot(NumericProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createNumeric(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T extends CharSequence> StringProperty<T> dot(StringProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createString(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T> DateProperty<T> dot(DateProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createDate(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T extends Persistent> EntityProperty<T> dot(EntityProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createEntity(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T extends Persistent> ListProperty<T> dot(ListProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createList(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getEntityType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <T extends Persistent> SetProperty<T> dot(SetProperty<T> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createSet(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getEntityType());
- }
-
- /**
- * Constructs a new property path by appending the argument to the existing property separated by a dot.
- *
- * @param property to append to path
- * @return a newly created Property object.
- */
- default <K, V extends Persistent> MapProperty<K, V> dot(MapProperty<K, V> property) {
- String path = getName() + "." + property.getName();
- return PropertyFactory.createMap(path,
- PropertyUtils.buildExp(path, getExpression().getPathAliases()),
- property.getKeyType(),
- property.getEntityType());
- }
+public interface RelationshipProperty<E> extends PathProperty<E> {
/**
* Returns a version of this property that represents an OUTER join. It is
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedResult.java b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedResult.java
new file mode 100644
index 0000000..529bccf
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedResult.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.map;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A metadata object that provides mapping of a set of result columns to an Embeddable object.
+ * Used by {@link SQLResult}.
+ * Note that fields in the EmbeddedResult are not required to follow the order of columns
+ * in the actual query, and can be added in the arbitrary order.
+ *
+ * @since 4.2
+ */
+public class EmbeddedResult {
+
+ private final Map<String, String> fields;
+ private final Embeddable embeddable;
+
+ public EmbeddedResult(Embeddable embeddable, int size) {
+ this.embeddable = embeddable;
+ this.fields = new HashMap<>((int) Math.ceil(size / 0.75));
+ }
+
+ public void addAttribute(ObjAttribute attr) {
+ fields.put(attr.getDbAttributePath(), getAttributeName(attr));
+ }
+
+ private static String getAttributeName(ObjAttribute attr) {
+ String name = attr.getName();
+ return name.substring(name.lastIndexOf('.') + 1);
+ }
+
+ public Map<String, String> getFields() {
+ return fields;
+ }
+
+ public Embeddable getEmbeddable() {
+ return embeddable;
+ }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/PathComponentIterator.java b/cayenne-server/src/main/java/org/apache/cayenne/map/PathComponentIterator.java
index 0ff4e0a..3d2bee8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/PathComponentIterator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/PathComponentIterator.java
@@ -37,6 +37,7 @@ class PathComponentIterator implements Iterator<PathComponent<Attribute, Relatio
private final String path;
private final Map<String, String> aliasMap;
+ private EmbeddedAttribute embeddedAttribute;
private Entity currentEntity;
PathComponentIterator(Entity root, String path, Map<String, String> aliasMap) {
@@ -44,6 +45,7 @@ class PathComponentIterator implements Iterator<PathComponent<Attribute, Relatio
this.path = Objects.requireNonNull(path);
this.aliasMap = Objects.requireNonNull(aliasMap);
this.toks = new StringTokenizer(path, Entity.PATH_SEPARATOR);
+ this.embeddedAttribute = null;
}
public boolean hasNext() {
@@ -62,10 +64,19 @@ class PathComponentIterator implements Iterator<PathComponent<Attribute, Relatio
}
// see if this is an attribute
- Attribute attr = currentEntity.getAttribute(pathComp);
+ Attribute attr;
+ if(embeddedAttribute != null) {
+ attr = embeddedAttribute.getAttribute(pathComp);
+ embeddedAttribute = null;
+ } else {
+ attr = currentEntity.getAttribute(pathComp);
+ }
+
if (attr != null) {
// do a sanity check...
- if (toks.hasMoreTokens()) {
+ if(attr instanceof EmbeddedAttribute) {
+ embeddedAttribute = (EmbeddedAttribute)attr;
+ } else if (toks.hasMoreTokens()) {
throw new ExpressionException(
"Attribute must be the last component of the path: '" + pathComp + "'.", path, null);
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLResult.java b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLResult.java
index 1993451..18bde42 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLResult.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLResult.java
@@ -18,6 +18,7 @@
****************************************************************/
package org.apache.cayenne.map;
+import org.apache.cayenne.query.DefaultEmbeddableResultSegment;
import org.apache.cayenne.reflect.ClassDescriptor;
import java.util.ArrayList;
@@ -69,6 +70,11 @@ public class SQLResult {
ClassDescriptor classDescriptor = resolver.getClassDescriptor(entityName);
resolvedComponents.add(new DefaultEntityResultSegment(classDescriptor, fields, offset));
offset = offset + fields.size();
+ } else if (component instanceof EmbeddedResult) {
+ EmbeddedResult embeddedResult = (EmbeddedResult)component;
+ Map<String, String> fields = embeddedResult.getFields();
+ resolvedComponents.add(new DefaultEmbeddableResultSegment(embeddedResult.getEmbeddable(), fields, offset));
+ offset = offset + fields.size();
} else {
throw new IllegalArgumentException("Unsupported result descriptor component: " + component);
}
@@ -103,21 +109,25 @@ public class SQLResult {
}
public void addEntityResult(EntityResult entityResult) {
- if (resultDescriptors == null) {
- resultDescriptors = new ArrayList<>(3);
- }
+ checkAndAdd(entityResult);
+ }
- resultDescriptors.add(entityResult);
+ public void addEmbeddedResult(EmbeddedResult embeddedResult) {
+ checkAndAdd(embeddedResult);
}
/**
* Adds a result set column name to the mapping.
*/
public void addColumnResult(String column) {
+ checkAndAdd(column);
+ }
+
+ private void checkAndAdd(Object result) {
if (resultDescriptors == null) {
resultDescriptors = new ArrayList<>(3);
}
- resultDescriptors.add(column);
+ resultDescriptors.add(result);
}
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultEmbeddableResultSegment.java
similarity index 54%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
copy to cayenne-server/src/main/java/org/apache/cayenne/query/DefaultEmbeddableResultSegment.java
index ef2ab3f..0e94d65 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultEmbeddableResultSegment.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -17,35 +17,39 @@
* under the License.
****************************************************************/
-package org.apache.cayenne.access.translator.select;
+package org.apache.cayenne.query;
-import java.util.List;
-import java.util.Optional;
+import java.util.Map;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
/**
- * Interface that describes result of path translation
- *
* @since 4.2
*/
-interface PathTranslationResult {
-
- String getFinalPath();
-
- Optional<DbRelationship> getDbRelationship();
+public class DefaultEmbeddableResultSegment implements EmbeddableResultSegment {
- List<DbAttribute> getDbAttributes();
+ private final Embeddable embeddable;
+ private final Map<String, String> fields;
+ private final int offset;
- List<String> getAttributePaths();
+ public DefaultEmbeddableResultSegment(Embeddable embeddable, Map<String, String> fields, int offset) {
+ this.embeddable = embeddable;
+ this.fields = fields;
+ this.offset = offset;
+ }
- default DbAttribute getLastAttribute() {
- return getDbAttributes().get(getDbAttributes().size() - 1);
+ @Override
+ public Map<String, String> getFields() {
+ return fields;
}
- default String getLastAttributePath() {
- return getAttributePaths().get(getAttributePaths().size() - 1);
+ @Override
+ public int getColumnOffset() {
+ return offset;
}
+ @Override
+ public Embeddable getEmbeddable() {
+ return embeddable;
+ }
}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java b/cayenne-server/src/main/java/org/apache/cayenne/query/EmbeddableResultSegment.java
similarity index 68%
copy from cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
copy to cayenne-server/src/main/java/org/apache/cayenne/query/EmbeddableResultSegment.java
index 119a0ce..f61dfc7 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/EmbeddableResultSegment.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
@@ -16,13 +16,25 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.testdo.embeddable;
-import org.apache.cayenne.testdo.embeddable.auto._EmbedEntity1;
+package org.apache.cayenne.query;
-public class EmbedEntity1 extends _EmbedEntity1 {
+import java.util.Map;
-}
+import org.apache.cayenne.map.Embeddable;
+
+/**
+ * @since 4.2
+ */
+public interface EmbeddableResultSegment {
+ Embeddable getEmbeddable();
+ Map<String, String> getFields();
+ /**
+ * Returns a zero-based column index of the first column of this segment in the
+ * ResultSet.
+ */
+ int getColumnOffset();
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java
index 87797ad..0f3f291 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java
@@ -26,6 +26,7 @@ import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.embeddable.EmbedEntity1;
+import org.apache.cayenne.testdo.embeddable.EmbedEntity2;
import org.apache.cayenne.testdo.embeddable.Embeddable1;
import org.apache.cayenne.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.ServerCase;
@@ -48,21 +49,29 @@ public class EmbeddingIT extends ServerCase {
protected DBHelper dbHelper;
protected TableHelper tEmbedEntity1;
+ protected TableHelper tEmbedEntity2;
@Before
public void setUp() throws Exception {
tEmbedEntity1 = new TableHelper(dbHelper, "EMBED_ENTITY1");
tEmbedEntity1.setColumns("ID", "NAME", "EMBEDDED10", "EMBEDDED20", "EMBEDDED30", "EMBEDDED40");
+
+ tEmbedEntity2 = new TableHelper(dbHelper, "EMBED_ENTITY2");
+ tEmbedEntity2.setColumns("ID", "NAME", "ENTITY1_ID", "EMBEDDED10", "EMBEDDED20");
}
protected void createSelectDataSet() throws Exception {
- tEmbedEntity1.delete().execute();
tEmbedEntity1.insert(1, "n1", "e1", "e2", "e3", "e4");
tEmbedEntity1.insert(2, "n2", "ex1", "ex2", "ex3", "ex4");
}
-
+
+ protected void createSelectDataSet2() throws Exception {
+ createSelectDataSet();
+ tEmbedEntity2.insert(1, "n2-1", 1, "e1", "e2");
+ tEmbedEntity2.insert(2, "n2-1", 2, "e1", "e2");
+ }
+
protected void createUpdateDataSet() throws Exception {
- tEmbedEntity1.delete().execute();
tEmbedEntity1.insert(1, "n1", "e1", "e2", "e3", "e4");
}
@@ -105,8 +114,8 @@ public class EmbeddingIT extends ServerCase {
createSelectDataSet();
List<EmbedEntity1> result = ObjectSelect.query(EmbedEntity1.class)
- .where(EmbedEntity1.EMBEDDED1_EMBEDDED10.eq("e1"))
- .orderBy(EmbedEntity1.EMBEDDED2_EMBEDDED10.asc())
+ .where(EmbedEntity1.EMBEDDED1.dot(Embeddable1.EMBEDDED10).eq("e1"))
+ .orderBy(EmbedEntity1.EMBEDDED1.dot(Embeddable1.EMBEDDED10).asc())
.select(context);
assertEquals(1, result.size());
assertEquals("e1", result.get(0).getEmbedded1().getEmbedded10());
@@ -181,4 +190,112 @@ public class EmbeddingIT extends ServerCase {
assertNotNull(row);
assertEquals("x1", row.get("EMBEDDED10"));
}
+
+ @Test
+ public void testPropertyExpression() throws Exception {
+ createSelectDataSet();
+
+ List<EmbedEntity1> result = ObjectSelect.query(EmbedEntity1.class)
+ .where(EmbedEntity1.EMBEDDED1.dot(Embeddable1.EMBEDDED10).eq("e1"))
+ .orderBy(EmbedEntity1.EMBEDDED2.dot(Embeddable1.EMBEDDED20).desc())
+ .select(context);
+
+ assertEquals(1, result.size());
+ }
+
+ @Test
+ public void testRelatedEmbedded() throws Exception {
+ createSelectDataSet2();
+
+ List<EmbedEntity2> result = ObjectSelect.query(EmbedEntity2.class)
+ .where(EmbedEntity2.ENTITY1.dot(EmbedEntity1.EMBEDDED1).dot(Embeddable1.EMBEDDED10).eq("e1"))
+ .orderBy(EmbedEntity2.ENTITY1.dot(EmbedEntity1.EMBEDDED2).dot(Embeddable1.EMBEDDED20).desc())
+ .select(context);
+
+ assertEquals(1, result.size());
+ }
+
+ @Test
+ public void testPrefetchWithEmbedded() throws Exception {
+ createSelectDataSet2();
+
+ List<EmbedEntity2> result = ObjectSelect.query(EmbedEntity2.class)
+ .prefetch(EmbedEntity2.ENTITY1.joint())
+ .select(context);
+
+ assertEquals(2, result.size());
+ assertNotNull(result.get(0).getEntity1().getEmbedded1());
+ assertNotNull(result.get(1).getEntity1().getEmbedded1());
+ }
+
+ @Test
+ public void testInMemoryFilteringByEmbeddable() throws Exception {
+ createSelectDataSet();
+
+ List<EmbedEntity1> result = ObjectSelect.query(EmbedEntity1.class).select(context);
+ assertEquals(2, result.size());
+
+ List<EmbedEntity1> filtered = EmbedEntity1.EMBEDDED1.dot(Embeddable1.EMBEDDED10).eq("e1").filterObjects(result);
+ assertEquals(1, filtered.size());
+ assertEquals("n1", filtered.get(0).getName());
+ }
+
+ @Test
+ public void testColumnSelect() throws Exception {
+ createSelectDataSet2();
+
+ List<Embeddable1> result = ObjectSelect.columnQuery(EmbedEntity1.class, EmbedEntity1.EMBEDDED2)
+ .orderBy(EmbedEntity1.EMBEDDED2.dot(Embeddable1.EMBEDDED10).asc())
+ .select(context);
+ assertEquals(2, result.size());
+ assertEquals("e3", result.get(0).getEmbedded10());
+ assertEquals("e4", result.get(0).getEmbedded20());
+ assertEquals("ex3", result.get(1).getEmbedded10());
+ assertEquals("ex4", result.get(1).getEmbedded20());
+
+ result.get(0).setEmbedded10("test");
+ context.commitChanges();
+ }
+
+ @Test
+ public void testColumnSelectMultiple() throws Exception {
+ createSelectDataSet2();
+
+ List<Object[]> result = ObjectSelect.columnQuery(EmbedEntity1.class, EmbedEntity1.EMBEDDED1, EmbedEntity1.EMBEDDED2)
+ .orderBy(EmbedEntity1.EMBEDDED2.dot(Embeddable1.EMBEDDED10).asc())
+ .select(context);
+ assertEquals(2, result.size());
+ assertEquals("e3", ((Embeddable1)result.get(0)[1]).getEmbedded10());
+ assertEquals("e4", ((Embeddable1)result.get(0)[1]).getEmbedded20());
+ assertEquals("ex3", ((Embeddable1)result.get(1)[1]).getEmbedded10());
+ assertEquals("ex4", ((Embeddable1)result.get(1)[1]).getEmbedded20());
+ }
+
+ @Test
+ public void testColumnSelectMixed() throws Exception {
+ createSelectDataSet2();
+
+ List<Object[]> result = ObjectSelect.columnQuery(EmbedEntity1.class, EmbedEntity1.EMBEDDED1.dot(Embeddable1.EMBEDDED10), EmbedEntity1.EMBEDDED2)
+ .orderBy(EmbedEntity1.EMBEDDED2.dot(Embeddable1.EMBEDDED10).asc())
+ .select(context);
+ assertEquals(2, result.size());
+ assertEquals("e3", ((Embeddable1)result.get(0)[1]).getEmbedded10());
+ assertEquals("e4", ((Embeddable1)result.get(0)[1]).getEmbedded20());
+ assertEquals("ex3", ((Embeddable1)result.get(1)[1]).getEmbedded10());
+ assertEquals("ex4", ((Embeddable1)result.get(1)[1]).getEmbedded20());
+ }
+
+ @Test
+ public void testWhere() throws Exception {
+ createSelectDataSet2();
+
+ Embeddable1 embeddable1 = new Embeddable1();
+ embeddable1.setEmbedded10("e1");
+ embeddable1.setEmbedded20("e2");
+
+ List<EmbedEntity1> result = ObjectSelect.query(EmbedEntity1.class)
+ .where(EmbedEntity1.EMBEDDED1.eq(embeddable1))
+ .select(context);
+ assertEquals(1, result.size());
+ }
}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
index 119a0ce..f2c2eaa 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity1.java
@@ -22,6 +22,8 @@ import org.apache.cayenne.testdo.embeddable.auto._EmbedEntity1;
public class EmbedEntity1 extends _EmbedEntity1 {
+ private static final long serialVersionUID = 1L;
+
}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity2.java
new file mode 100644
index 0000000..128e79b
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/EmbedEntity2.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.testdo.embeddable;
+
+import org.apache.cayenne.testdo.embeddable.auto._EmbedEntity2;
+
+public class EmbedEntity2 extends _EmbedEntity2 {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java
index 53fd122..e5d29cb 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java
@@ -3,11 +3,14 @@ package org.apache.cayenne.testdo.embeddable.auto;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.util.List;
import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.EmbeddableProperty;
+import org.apache.cayenne.exp.property.ListProperty;
import org.apache.cayenne.exp.property.PropertyFactory;
import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.testdo.embeddable.EmbedEntity2;
import org.apache.cayenne.testdo.embeddable.Embeddable1;
/**
@@ -22,16 +25,16 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
public static final String ID_PK_COLUMN = "ID";
- public static final StringProperty<String> EMBEDDED1_EMBEDDED20 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED20"), String.class);
- public static final StringProperty<String> EMBEDDED1_EMBEDDED10 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED10"), String.class);
- public static final StringProperty<String> EMBEDDED2_EMBEDDED20 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED40"), String.class);
- public static final StringProperty<String> EMBEDDED2_EMBEDDED10 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED30"), String.class);
+ public static final EmbeddableProperty<Embeddable1> EMBEDDED1 = PropertyFactory.createEmbeddable("embedded1", Embeddable1.class);
+ public static final EmbeddableProperty<Embeddable1> EMBEDDED2 = PropertyFactory.createEmbeddable("embedded2", Embeddable1.class);
public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
+ public static final ListProperty<EmbedEntity2> EMBED_ENTITY2S = PropertyFactory.createList("embedEntity2s", EmbedEntity2.class);
protected Embeddable1 embedded1;
protected Embeddable1 embedded2;
protected String name;
+ protected Object embedEntity2s;
public void setEmbedded1(Embeddable1 embedded1) {
beforePropertyWrite("embedded1", this.embedded1, embedded1);
@@ -63,6 +66,19 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
return this.name;
}
+ public void addToEmbedEntity2s(EmbedEntity2 obj) {
+ addToManyTarget("embedEntity2s", obj, true);
+ }
+
+ public void removeFromEmbedEntity2s(EmbedEntity2 obj) {
+ removeToManyTarget("embedEntity2s", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<EmbedEntity2> getEmbedEntity2s() {
+ return (List<EmbedEntity2>)readProperty("embedEntity2s");
+ }
+
@Override
public Object readPropertyDirectly(String propName) {
if(propName == null) {
@@ -76,6 +92,8 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
return this.embedded2;
case "name":
return this.name;
+ case "embedEntity2s":
+ return this.embedEntity2s;
default:
return super.readPropertyDirectly(propName);
}
@@ -97,6 +115,9 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
case "name":
this.name = (String)val;
break;
+ case "embedEntity2s":
+ this.embedEntity2s = val;
+ break;
default:
super.writePropertyDirectly(propName, val);
}
@@ -116,6 +137,7 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
out.writeObject(this.embedded1);
out.writeObject(this.embedded2);
out.writeObject(this.name);
+ out.writeObject(this.embedEntity2s);
}
@Override
@@ -124,6 +146,7 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
this.embedded1 = (Embeddable1)in.readObject();
this.embedded2 = (Embeddable1)in.readObject();
this.name = (String)in.readObject();
+ this.embedEntity2s = in.readObject();
}
}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity2.java
similarity index 56%
copy from cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java
copy to cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity2.java
index 53fd122..5d867b2 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_EmbedEntity2.java
@@ -5,52 +5,42 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.EmbeddableProperty;
+import org.apache.cayenne.exp.property.EntityProperty;
import org.apache.cayenne.exp.property.PropertyFactory;
import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.testdo.embeddable.EmbedEntity1;
import org.apache.cayenne.testdo.embeddable.Embeddable1;
/**
- * Class _EmbedEntity1 was generated by Cayenne.
+ * Class _EmbedEntity2 was generated by Cayenne.
* It is probably a good idea to avoid changing this class manually,
* since it may be overwritten next time code is regenerated.
* If you need to make any customizations, please use subclass.
*/
-public abstract class _EmbedEntity1 extends BaseDataObject {
+public abstract class _EmbedEntity2 extends BaseDataObject {
private static final long serialVersionUID = 1L;
public static final String ID_PK_COLUMN = "ID";
- public static final StringProperty<String> EMBEDDED1_EMBEDDED20 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED20"), String.class);
- public static final StringProperty<String> EMBEDDED1_EMBEDDED10 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED10"), String.class);
- public static final StringProperty<String> EMBEDDED2_EMBEDDED20 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED40"), String.class);
- public static final StringProperty<String> EMBEDDED2_EMBEDDED10 = PropertyFactory.createString(ExpressionFactory.dbPathExp("EMBEDDED30"), String.class);
+ public static final EmbeddableProperty<Embeddable1> EMBEDDED = PropertyFactory.createEmbeddable("embedded", Embeddable1.class);
public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
+ public static final EntityProperty<EmbedEntity1> ENTITY1 = PropertyFactory.createEntity("entity1", EmbedEntity1.class);
- protected Embeddable1 embedded1;
- protected Embeddable1 embedded2;
+ protected Embeddable1 embedded;
protected String name;
+ protected Object entity1;
- public void setEmbedded1(Embeddable1 embedded1) {
- beforePropertyWrite("embedded1", this.embedded1, embedded1);
- this.embedded1 = embedded1;
+ public void setEmbedded(Embeddable1 embedded) {
+ beforePropertyWrite("embedded", this.embedded, embedded);
+ this.embedded = embedded;
}
- public Embeddable1 getEmbedded1() {
- beforePropertyRead("embedded1");
- return this.embedded1;
- }
-
- public void setEmbedded2(Embeddable1 embedded2) {
- beforePropertyWrite("embedded2", this.embedded2, embedded2);
- this.embedded2 = embedded2;
- }
-
- public Embeddable1 getEmbedded2() {
- beforePropertyRead("embedded2");
- return this.embedded2;
+ public Embeddable1 getEmbedded() {
+ beforePropertyRead("embedded");
+ return this.embedded;
}
public void setName(String name) {
@@ -63,6 +53,14 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
return this.name;
}
+ public void setEntity1(EmbedEntity1 entity1) {
+ setToOneTarget("entity1", entity1, true);
+ }
+
+ public EmbedEntity1 getEntity1() {
+ return (EmbedEntity1)readProperty("entity1");
+ }
+
@Override
public Object readPropertyDirectly(String propName) {
if(propName == null) {
@@ -70,12 +68,12 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
}
switch(propName) {
- case "embedded1":
- return this.embedded1;
- case "embedded2":
- return this.embedded2;
+ case "embedded":
+ return this.embedded;
case "name":
return this.name;
+ case "entity1":
+ return this.entity1;
default:
return super.readPropertyDirectly(propName);
}
@@ -88,15 +86,15 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
}
switch (propName) {
- case "embedded1":
- this.embedded1 = (Embeddable1)val;
- break;
- case "embedded2":
- this.embedded2 = (Embeddable1)val;
+ case "embedded":
+ this.embedded = (Embeddable1)val;
break;
case "name":
this.name = (String)val;
break;
+ case "entity1":
+ this.entity1 = val;
+ break;
default:
super.writePropertyDirectly(propName, val);
}
@@ -113,17 +111,17 @@ public abstract class _EmbedEntity1 extends BaseDataObject {
@Override
protected void writeState(ObjectOutputStream out) throws IOException {
super.writeState(out);
- out.writeObject(this.embedded1);
- out.writeObject(this.embedded2);
+ out.writeObject(this.embedded);
out.writeObject(this.name);
+ out.writeObject(this.entity1);
}
@Override
protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
super.readState(in);
- this.embedded1 = (Embeddable1)in.readObject();
- this.embedded2 = (Embeddable1)in.readObject();
+ this.embedded = (Embeddable1)in.readObject();
this.name = (String)in.readObject();
+ this.entity1 = in.readObject();
}
}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_Embeddable1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_Embeddable1.java
index 905765c..169d3de 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_Embeddable1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/embeddable/auto/_Embeddable1.java
@@ -2,7 +2,10 @@ package org.apache.cayenne.testdo.embeddable.auto;
import java.io.Serializable;
+import org.apache.cayenne.EmbeddableObject;
import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
/**
* Embeddable class _Embeddable1 was generated by Cayenne.
@@ -10,7 +13,10 @@ import org.apache.cayenne.Persistent;
* since it may be overwritten next time code is regenerated.
* If you need to make any customizations, please use subclass.
*/
-public abstract class _Embeddable1 implements Serializable {
+public abstract class _Embeddable1 implements EmbeddableObject, Serializable {
+
+ public static final StringProperty<String> EMBEDDED20 = PropertyFactory.createString("embedded20", String.class);
+ public static final StringProperty<String> EMBEDDED10 = PropertyFactory.createString("embedded10", String.class);
// special properties injected by Cayenne
private Persistent owner;
@@ -48,4 +54,37 @@ public abstract class _Embeddable1 implements Serializable {
return embedded10;
}
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "embedded20":
+ return this.embedded20;
+ case "embedded10":
+ return this.embedded10;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "embedded20":
+ this.embedded20 = (String)val;
+ break;
+ case "embedded10":
+ this.embedded10 = (String)val;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown property: " + propName);
+ }
+ }
}
diff --git a/cayenne-server/src/test/resources/cayenne-embeddable.xml b/cayenne-server/src/test/resources/cayenne-embeddable.xml
index aedacf3..eabee1b 100644
--- a/cayenne-server/src/test/resources/cayenne-embeddable.xml
+++ b/cayenne-server/src/test/resources/cayenne-embeddable.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<domain xmlns="http://cayenne.apache.org/schema/10/domain"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain https://cayenne.apache.org/schema/10/domain.xsd"
project-version="10">
<map name="embeddable"/>
</domain>
diff --git a/cayenne-server/src/test/resources/embeddable.map.xml b/cayenne-server/src/test/resources/embeddable.map.xml
index 10c4db6..dbcae05 100644
--- a/cayenne-server/src/test/resources/embeddable.map.xml
+++ b/cayenne-server/src/test/resources/embeddable.map.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap http://cayenne.apache.org/schema/10/modelMap.xsd"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd"
project-version="10">
<property name="defaultPackage" value="org.apache.cayenne.testdo.embeddable"/>
<embeddable className="org.apache.cayenne.testdo.embeddable.Embeddable1">
- <embeddable-attribute name="embedded10" type="java.lang.String" db-attribute-name="EMBEDDED10"/>
<embeddable-attribute name="embedded20" type="java.lang.String" db-attribute-name="EMBEDDED20"/>
+ <embeddable-attribute name="embedded10" type="java.lang.String" db-attribute-name="EMBEDDED10"/>
</embeddable>
<db-entity name="EMBED_ENTITY1">
<db-attribute name="EMBEDDED10" type="VARCHAR" length="100"/>
@@ -16,12 +16,31 @@
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
<db-attribute name="NAME" type="VARCHAR" length="100"/>
</db-entity>
+ <db-entity name="EMBED_ENTITY2">
+ <db-attribute name="EMBEDDED10" type="VARCHAR" length="100"/>
+ <db-attribute name="EMBEDDED20" type="VARCHAR" length="100"/>
+ <db-attribute name="ENTITY1_ID" type="INTEGER"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ <db-attribute name="NAME" type="VARCHAR" length="100"/>
+ </db-entity>
<obj-entity name="EmbedEntity1" className="org.apache.cayenne.testdo.embeddable.EmbedEntity1" dbEntityName="EMBED_ENTITY1">
<embedded-attribute name="embedded1" type="org.apache.cayenne.testdo.embeddable.Embeddable1"/>
<embedded-attribute name="embedded2" type="org.apache.cayenne.testdo.embeddable.Embeddable1">
- <embeddable-attribute-override name="embedded10" db-attribute-path="EMBEDDED30"/>
<embeddable-attribute-override name="embedded20" db-attribute-path="EMBEDDED40"/>
+ <embeddable-attribute-override name="embedded10" db-attribute-path="EMBEDDED30"/>
</embedded-attribute>
<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
</obj-entity>
+ <obj-entity name="EmbedEntity2" className="org.apache.cayenne.testdo.embeddable.EmbedEntity2" dbEntityName="EMBED_ENTITY2">
+ <embedded-attribute name="embedded" type="org.apache.cayenne.testdo.embeddable.Embeddable1"/>
+ <obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
+ </obj-entity>
+ <db-relationship name="untitledRel" source="EMBED_ENTITY1" target="EMBED_ENTITY2" toMany="true">
+ <db-attribute-pair source="ID" target="ENTITY1_ID"/>
+ </db-relationship>
+ <db-relationship name="untitledRel" source="EMBED_ENTITY2" target="EMBED_ENTITY1">
+ <db-attribute-pair source="ENTITY1_ID" target="ID"/>
+ </db-relationship>
+ <obj-relationship name="embedEntity2s" source="EmbedEntity1" target="EmbedEntity2" deleteRule="Deny" db-relationship-path="untitledRel"/>
+ <obj-relationship name="entity1" source="EmbedEntity2" target="EmbedEntity1" deleteRule="Nullify" db-relationship-path="untitledRel"/>
</data-map>